Skip to content

Commit 9120d24

Browse files
committed
Portfolio site — Angular 22 SSR, i18n (en/ru), résumé
0 parents  commit 9120d24

112 files changed

Lines changed: 7007 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.editorconfig

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Editor configuration, see https://editorconfig.org
2+
root = true
3+
4+
[*]
5+
charset = utf-8
6+
indent_style = space
7+
indent_size = 2
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true
10+
11+
[*.ts]
12+
quote_type = single
13+
ij_typescript_use_double_quotes = false
14+
15+
[*.md]
16+
max_line_length = off
17+
trim_trailing_whitespace = false

.eslintignore

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
dist
2+
coverage
3+
.angular
4+
_legacy
5+
content
6+
scripts
7+
seo
8+
eslint-local-plugin
9+
stylelint-rules
10+
*.scss
11+
*.svg
12+
*.json
13+
src/index.html
14+
src/main.ts
15+
src/main.server.ts
16+
src/server.ts
17+
src/app/app.config.server.ts
18+
src/app/app.routes.server.ts
19+
*.config.ts
20+
*.config.mjs

.eslintrc.cjs

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
'use strict';
2+
3+
const tsRules = {
4+
'@typescript-eslint/no-deprecated': 'error',
5+
'local-rules/no-inject-explicit-type': 'error',
6+
'local-rules/no-filter-map': 'error',
7+
'@typescript-eslint/consistent-type-assertions': ['error', { assertionStyle: 'never' }],
8+
'@typescript-eslint/no-non-null-assertion': 'error',
9+
'padding-line-between-statements': ['error', { blankLine: 'always', prev: '*', next: 'if' }],
10+
'no-restricted-globals': [
11+
'error',
12+
{ name: 'window', message: 'Используйте inject(DOCUMENT)/inject(WINDOW), а не глобальный window.' },
13+
{ name: 'document', message: 'Используйте inject(DOCUMENT), а не глобальный document.' },
14+
],
15+
'no-restricted-syntax': [
16+
'error',
17+
{ selector: 'MethodDefinition[accessibility="private"]', message: 'Используйте методы с # вместо private' },
18+
{ selector: 'PropertyDefinition[accessibility="private"]', message: 'Используйте поля с # вместо private' },
19+
{
20+
selector: 'PropertyDefinition[value.callee.name=/^inject/][readonly!=true]',
21+
message: 'Зависимости из inject*(...) должны быть readonly.',
22+
},
23+
{ selector: 'TSAsExpression > TSUnknownKeyword', message: 'Использование "as unknown" запрещено. Найдите правильную типизацию.' },
24+
],
25+
'@angular-eslint/prefer-inject': ['error'],
26+
'@typescript-eslint/no-empty-function': ['error', { allow: ['methods'] }],
27+
'dot-notation': 'off',
28+
'@typescript-eslint/dot-notation': ['error', { allowIndexSignaturePropertyAccess: true }],
29+
'@typescript-eslint/no-unused-vars': ['error', { ignoreRestSiblings: true, argsIgnorePattern: '^_' }],
30+
'@typescript-eslint/no-unused-expressions': 'off',
31+
'import/no-duplicates': ['error', { considerQueryString: true }],
32+
'@typescript-eslint/no-explicit-any': ['error'],
33+
'@typescript-eslint/no-inferrable-types': ['error'],
34+
'@typescript-eslint/explicit-function-return-type': ['error', { allowExpressions: true }],
35+
'@typescript-eslint/explicit-member-accessibility': ['error', { accessibility: 'no-public' }],
36+
'@typescript-eslint/member-ordering': [
37+
'error',
38+
{
39+
default: {
40+
memberTypes: [
41+
['#private-readonly-field'],
42+
'static-field',
43+
'public-field',
44+
'protected-field',
45+
'private-static-field',
46+
'private-field',
47+
'#private-field',
48+
'abstract-field',
49+
'constructor',
50+
'public-method',
51+
'protected-method',
52+
'private-method',
53+
'#private-method',
54+
'abstract-method',
55+
],
56+
},
57+
},
58+
],
59+
'@angular-eslint/use-lifecycle-interface': ['error'],
60+
'no-console': ['error', { allow: ['info', 'time', 'timeEnd'] }],
61+
'optimize-regex/optimize-regex': 'error',
62+
'max-lines': ['error', { max: 500, skipBlankLines: true, skipComments: true }],
63+
'max-lines-per-function': ['error', { max: 50, skipBlankLines: true, skipComments: true }],
64+
'lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }],
65+
'object-shorthand': 'error',
66+
'@angular-eslint/no-output-on-prefix': 'error',
67+
'@angular-eslint/no-output-rename': 'error',
68+
'@angular-eslint/use-pipe-transform-interface': 'error',
69+
curly: ['error'],
70+
'@angular-eslint/directive-selector': ['error', { type: 'attribute', prefix: 'app', style: 'camelCase' }],
71+
'@angular-eslint/component-selector': ['error', { type: 'element', prefix: 'app', style: 'kebab-case' }],
72+
'@angular-eslint/prefer-signals': 'error',
73+
'@angular-eslint/prefer-output-emitter-ref': 'error',
74+
'@typescript-eslint/prefer-includes': 'error',
75+
'@typescript-eslint/prefer-optional-chain': 'error',
76+
'@typescript-eslint/prefer-nullish-coalescing': 'error',
77+
'@typescript-eslint/sort-type-constituents': 'error',
78+
eqeqeq: ['error', 'always'],
79+
'change-detection-strategy/on-push': 'error',
80+
'@eslint-community/eslint-comments/disable-enable-pair': 'error',
81+
'@eslint-community/eslint-comments/no-unlimited-disable': 'error',
82+
'@eslint-community/eslint-comments/no-duplicate-disable': 'error',
83+
'@eslint-community/eslint-comments/no-unused-disable': 'error',
84+
'@eslint-community/eslint-comments/require-description': ['error', { ignore: [] }],
85+
};
86+
87+
module.exports = {
88+
root: true,
89+
parser: '@typescript-eslint/parser',
90+
parserOptions: {
91+
project: ['./tsconfig.app.json', './tsconfig.spec.json'],
92+
tsconfigRootDir: __dirname,
93+
},
94+
plugins: [
95+
'@typescript-eslint',
96+
'optimize-regex',
97+
'@angular-eslint',
98+
'import',
99+
'change-detection-strategy',
100+
'local-rules',
101+
'@eslint-community/eslint-comments',
102+
],
103+
overrides: [
104+
{
105+
files: ['src/**/*.ts'],
106+
excludedFiles: ['**/*.spec.ts'],
107+
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
108+
rules: tsRules,
109+
},
110+
{
111+
files: ['src/**/spec-utils/**/*.ts'],
112+
excludedFiles: ['**/*.spec.ts'],
113+
rules: {
114+
'@typescript-eslint/consistent-type-assertions': ['warn', { assertionStyle: 'as' }],
115+
'@typescript-eslint/no-explicit-any': 'warn',
116+
'@eslint-community/eslint-comments/require-description': 'off',
117+
'max-lines-per-function': 'off',
118+
},
119+
},
120+
{
121+
files: ['src/**/*.spec.ts'],
122+
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
123+
rules: {
124+
...tsRules,
125+
'@typescript-eslint/consistent-type-assertions': ['warn', { assertionStyle: 'never' }],
126+
'local-rules/no-provide-auto-spy-directive': 'error',
127+
'local-rules/no-compile-components-for-standalone': 'error',
128+
'max-lines-per-function': 'off',
129+
'no-restricted-properties': [
130+
'error',
131+
{ object: 'describe', property: 'skip', message: 'Do not commit skipped tests (describe.skip)' },
132+
{ object: 'it', property: 'skip', message: 'Do not commit skipped tests (it.skip)' },
133+
{ object: 'test', property: 'skip', message: 'Do not commit skipped tests (test.skip)' },
134+
],
135+
},
136+
},
137+
{
138+
files: ['*.html'],
139+
parser: '@angular-eslint/template-parser',
140+
plugins: ['@angular-eslint/eslint-plugin-template'],
141+
rules: {
142+
'@angular-eslint/template/no-duplicate-attributes': 'error',
143+
'@angular-eslint/template/alt-text': 'error',
144+
'@angular-eslint/template/banana-in-box': 'error',
145+
'@angular-eslint/template/eqeqeq': 'error',
146+
'@angular-eslint/template/i18n': ['error', { checkId: true, checkText: true, checkAttributes: false, checkDuplicateId: true }],
147+
'@angular-eslint/template/no-any': 'error',
148+
'@angular-eslint/template/no-negated-async': 'error',
149+
},
150+
},
151+
],
152+
};

.github/workflows/ci.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
workflow_dispatch:
8+
9+
jobs:
10+
quality:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- uses: oven-sh/setup-bun@v2
16+
with:
17+
bun-version: 1.3.14
18+
19+
- name: Install dependencies
20+
run: bun install --frozen-lockfile
21+
22+
- name: Lint (ESLint)
23+
run: bun run eslint
24+
25+
- name: Lint (Stylelint)
26+
run: bun run stylelint:check
27+
28+
- name: Unit tests + 100% coverage
29+
run: bun run test:coverage

.github/workflows/deploy.yml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Deploy to GitHub Pages
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: pages
15+
cancel-in-progress: true
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- uses: oven-sh/setup-bun@v2
24+
with:
25+
bun-version: 1.3.14
26+
27+
- name: Install dependencies
28+
run: bun install --frozen-lockfile
29+
30+
- name: Build (EN + RU, prerendered)
31+
run: bun run build
32+
33+
- name: Assemble site root (redirect, robots, sitemap, og-image)
34+
run: node scripts/assemble-dist.mjs
35+
36+
- name: Upload Pages artifact
37+
uses: actions/upload-pages-artifact@v3
38+
with:
39+
path: dist/ngportfolio/browser
40+
41+
deploy:
42+
needs: build
43+
runs-on: ubuntu-latest
44+
environment:
45+
name: github-pages
46+
url: ${{ steps.deployment.outputs.page_url }}
47+
steps:
48+
- name: Deploy to GitHub Pages
49+
id: deployment
50+
uses: actions/deploy-pages@v4

.gitignore

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
2+
3+
# Compiled output
4+
/dist
5+
/tmp
6+
/out-tsc
7+
/bazel-out
8+
9+
# Node
10+
/node_modules
11+
npm-debug.log
12+
yarn-error.log
13+
14+
# IDEs and editors
15+
.idea/
16+
.project
17+
.classpath
18+
.c9/
19+
*.launch
20+
.settings/
21+
*.sublime-workspace
22+
23+
# Visual Studio Code
24+
.vscode/*
25+
!.vscode/settings.json
26+
!.vscode/tasks.json
27+
!.vscode/launch.json
28+
!.vscode/extensions.json
29+
!.vscode/mcp.json
30+
.history/*
31+
32+
# Miscellaneous
33+
/.angular/cache
34+
.sass-cache/
35+
/connect.lock
36+
/coverage
37+
/libpeerconnection.log
38+
testem.log
39+
/typings
40+
__screenshots__/
41+
42+
# System files
43+
.DS_Store
44+
Thumbs.db
45+
46+
# Private portfolio master — NEVER publish (contains company names / parallel work)
47+
/content/PORTFOLIO.md
48+
49+
# Legacy vanilla site (kept locally as design reference only)
50+
/_legacy
51+
52+
# Tooling caches
53+
.eslintcache
54+
.stylelintcache
55+
*.tsbuildinfo

.husky/pre-commit

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
echo '-------------- Prettier (staged) --------------'
2+
bunx pretty-quick --staged
3+
4+
echo '-------------- Pre-commit checks done --------------'

.husky/pre-push

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
echo '----------- Type-check (app + specs) --------------'
2+
bunx tsc -p tsconfig.app.json --noEmit
3+
bunx tsc -p tsconfig.spec.json --noEmit
4+
5+
echo '----------- Stylelint --------------'
6+
bun run stylelint:check
7+
8+
echo '----------- Pre-push checks done --------------'

.prettierignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
dist
2+
coverage
3+
.angular
4+
_legacy
5+
content
6+
src/locale

.prettierrc

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"attributeGroups": ["^#", "$ANGULAR_STRUCTURAL_DIRECTIVE", "$DEFAULT", "$ANGULAR_INPUT", "$ANGULAR_TWO_WAY_BINDING", "$ANGULAR_OUTPUT"],
3+
"attributeSort": "ASC",
4+
"bracketSpacing": true,
5+
"cssDeclarationSorterOrder": "smacss",
6+
"importOrder": ["^@app/(.*)$", "^@core/(.*)$", "^@shared/(.*)$", "^[./]", "^[../]"],
7+
"importOrderParserPlugins": ["typescript", "decorators-legacy"],
8+
"importOrderSeparation": true,
9+
"importOrderSortSpecifiers": true,
10+
"overrides": [
11+
{
12+
"files": "*.html",
13+
"options": {
14+
"parser": "angular"
15+
}
16+
}
17+
],
18+
"plugins": [
19+
"prettier-plugin-organize-attributes",
20+
"prettier-plugin-css-order",
21+
"@trivago/prettier-plugin-sort-imports",
22+
"prettier-plugin-sort-json",
23+
"@prettier/plugin-oxc"
24+
],
25+
"printWidth": 140,
26+
"semi": true,
27+
"singleQuote": true,
28+
"tabWidth": 2,
29+
"useTabs": false
30+
}

0 commit comments

Comments
 (0)