@nutin/cli 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +32 -2
  2. package/README.md +47 -1
  3. package/lib/index.mjs +6 -6
  4. package/lib/src/file-generator.mjs +7 -5
  5. package/lib/src/package-manager.mjs +54 -45
  6. package/lib/src/prompts.mjs +11 -11
  7. package/lib/src/utils.mjs +2 -1
  8. package/package.json +1 -1
  9. package/templates/base/.dockerignore.hbs +10 -1
  10. package/templates/base/Dockerfile.hbs +28 -8
  11. package/templates/base/README.md.hbs +23 -0
  12. package/templates/base/nginx.conf.hbs +43 -0
  13. package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +2 -1
  14. package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +11 -11
  15. package/templates/base/src/core/base-classes/base-component/helpers/token.helper.ts.hbs +3 -2
  16. package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +55 -27
  17. package/templates/base/src/core/services/http-client/http-client.ts.hbs +24 -17
  18. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +25 -10
  19. package/templates/base/src/core/services/router/router.ts.hbs +1 -0
  20. package/templates/base/src/index.html.hbs +1 -2
  21. package/templates/base/src/libs/popover/popover.ts.hbs +2 -1
  22. package/templates/base/src/styles/main.scss.hbs +2 -1
  23. package/templates/base/tools/builder/builder.js.hbs +17 -27
  24. package/templates/base/tools/builder/core/copy-static.js.hbs +16 -20
  25. package/templates/base/tools/builder/core/esbuild.js.hbs +36 -0
  26. package/templates/base/tools/builder/core/finalize-build.js.hbs +24 -0
  27. package/templates/base/tools/builder/core/minify-html.js.hbs +60 -0
  28. package/templates/base/tools/builder/core/paths.js.hbs +19 -0
  29. package/templates/base/tools/builder/core/sass.config.js.hbs +16 -9
  30. package/templates/base/tools/builder/core/tsc.js.hbs +8 -0
  31. package/templates/base/tools/builder/core/validate-html.js.hbs +26 -11
  32. package/templates/base/tools/dev.js.hbs +43 -0
  33. package/templates/base/tools/generator/generator.js.hbs +9 -2
  34. package/templates/base/tools/generator/handle-file.js.hbs +23 -0
  35. package/templates/base/tools/generator/templates/index.js.hbs +3 -0
  36. package/templates/base/tools/serve.js.hbs +23 -0
  37. package/templates/base/tools/utils/export-flags.js.hbs +5 -0
  38. package/templates/base/tools/utils/get-files-recursive.js.hbs +1 -3
  39. package/templates/base/tools/utils/index.js.hbs +5 -1
  40. package/templates/base/tools/utils/prompt-boolean.js.hbs +15 -0
  41. package/templates/base/tools/utils/run-command.js.hbs +5 -0
  42. package/templates/base/tools/utils/run-script.js.hbs +22 -0
  43. package/templates/base/tools/watcher.js.hbs +7 -7
  44. package/templates/features/i18n/tools/builder/core/build-i18n.js.hbs +11 -16
  45. package/templates/features/stylinNutin/src/styles/_custom-classes.scss.hbs +1 -1
  46. package/templates/features/stylinNutin/src/styles/components/_index.scss.hbs +3 -0
  47. package/templates/features/stylinNutin/tools/generator/templates/scss.template.js.hbs +4 -0
  48. package/templates/features/template/tools/builder/core/merge-templates.js.hbs +13 -26
package/CHANGELOG.md CHANGED
@@ -13,7 +13,7 @@
13
13
  - Added index access in CatalogConfig. Use with `config.index`.
14
14
  ```typescript
15
15
  interface CatalogItemBase {
16
- idx: number;
16
+ index: number;
17
17
  }
18
18
  type CatalogItemObject<T extends object> = T & CatalogItemBase;
19
19
  interface CatalogItemPrimitive extends CatalogItemBase {
@@ -25,4 +25,34 @@ type CatalogItemConfig<T = any> =
25
25
  T extends object ? CatalogItemObject<T> : CatalogItemPrimitive;
26
26
  ```
27
27
 
28
- **Notes:** *Primitive data arrays (string, number, etc) needs to be accessed with `config.value`.*
28
+ **Notes:** *Primitive data arrays (string, number, etc) needs to be accessed with `config.value`.*
29
+
30
+ ## V1.2.0
31
+
32
+ - Fixed `Cannot GET '/'` ponctual issue on changes with `npm run dev` command. Added middleware to reload nested routes.
33
+
34
+ - Improved `data-optional` tag:
35
+ > Can now specify which attribute to check with `data-optional="attrName"` (`src`, `href`, ...)
36
+ > Now works for `img`, `input`, etc
37
+
38
+ - Event Bus :
39
+ > Tracks both event name + callback.
40
+ > cleanupEventListeners now works correctly.
41
+ > New property: `once`. `once` subscriptions automatically remove themselves after first call.
42
+
43
+ - HTTP client :
44
+ > Optional request / response interceptors.
45
+ > Full timeout / abort controller support preserved.
46
+ > onDestroy now clears interceptors to prevent memory leaks.
47
+
48
+ - Improved builder
49
+
50
+ Implemented esbuild.
51
+ Added flags `--bundle` (for production-ready build - equivalent to `npm run build:prod`) and `--log` (verbose output).
52
+ Commands: `npm run build`, `npm run build:prod`, `npm run serve:only`, `npm run serve`, `npm run dev`.
53
+
54
+ *Note: `--prod` or `--production` will set NODE_ENV to production, and have the same effect as `--bundle`.* `const isProd = process.env.npm_config_bundle || process.env.NODE_ENV === 'production';`
55
+
56
+ - Improved default Dockerfile (nginx conf + gzip)
57
+
58
+ **NOTE :***Update nginx.conf as needed. Example: CSP connect-src if the app makes API calls.*
package/README.md CHANGED
@@ -2,4 +2,50 @@
2
2
 
3
3
  **A lightweight, highly tweakable frontend toolkit, crafted for building lean, dynamic single-page applications without the complexity of full-scale frameworks.**
4
4
  Includes essential features out of the box, with zero runtime dependencies.
5
- *TypeScript/Javascript,SASS,HTML*
5
+ *TypeScript / Javascript, SASS, HTML*
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ # install package globally
11
+ npm install -g @nutin/cli
12
+ ```
13
+
14
+ ## New App
15
+
16
+ ```bash
17
+ # create a new app
18
+ create-nutin-app # or
19
+ nutin-new
20
+ ```
21
+
22
+ - Flags
23
+
24
+ ```bash
25
+ # argument: project-name
26
+
27
+ -d --defaults
28
+ -pm --package-manager <manager> # npm (default), yarn, pnpm
29
+
30
+ --template # Use external templates
31
+ --no-stylin-nutin # Do not use built-in SCSS utility
32
+ --testin-nutin # Use testin-nutin toolkit
33
+ --i18n # Use i18n & json-based content
34
+ --transition # Use animated view transitions. Note: May interfere with CSS `position: fixed`, `z-index`...
35
+
36
+ # Defaults
37
+ --no-template # (Default) Do not use external templates
38
+ --stylin-nutin # (Default) Use built-in SCSS utility classes & mixins
39
+ --no-i18n # (Default) Do not use i18n & json-based content
40
+ --no-transition # (Default) Do not use animated view transitions
41
+ --no-testin-nutin # (Default) Do not use testin-nutin toolkit
42
+
43
+ # Help
44
+ -h --help
45
+ ```
46
+
47
+ ## [First app tutorial](https://www.nutin.org/tutorial)
48
+
49
+
50
+ - Visit the [website](https://www.nutin.org)
51
+ - [Project repository](https://github.com/LoisKOUNINEF/nutin)
package/lib/index.mjs CHANGED
@@ -6,20 +6,20 @@ import { displaySuccessMessage } from './src/utils.mjs';
6
6
 
7
7
  export async function createApp() {
8
8
  program
9
- .version('1.0.0')
9
+ .version('1.1.0')
10
10
  .argument('[projectName]', 'Name of the project')
11
11
  .option('-d, --defaults', 'Use default options')
12
- .option('-pm, --package-manager <manager>', 'Specify package manager (npm, yarn, pnpm)')
13
- .option('--template', 'Use external templates')
14
- .option('--no-template', '(Default) Do not use external templates')
12
+ .option('-pm, --package-manager <manager>', 'Specify package manager (npm, yarn, pnpm, bun)')
13
+ .option('--template', '(Default) Use external templates')
14
+ .option('--no-template', 'Do not use external templates')
15
15
  .option('--stylin-nutin', '(Default) Use built-in SCSS utility classes & mixins')
16
16
  .option('--no-stylin-nutin', 'Do not use built-in SCSS utility')
17
17
  .option('--i18n', 'Use i18n & json-based content')
18
18
  .option('--no-i18n', '(Default) Do not use i18n & json-based content')
19
+ .option('--testin-nutin', '(Default) Use testin-nutin toolkit')
20
+ .option('--no-testin-nutin', 'Do not use testin-nutin toolkit')
19
21
  .option('--transition', 'Use animated view transitions\nNote: May interfere with CSS `position: fixed`, `z-index`...')
20
22
  .option('--no-transition', '(Default) Do not use animated view transitions')
21
- .option('--testin-nutin', 'Use testin-nutin toolkit')
22
- .option('--no-testin-nutin', '(Default) Do not use testin-nutin toolkit')
23
23
  .addHelpText('afterAll', '\nChaining options :\nExample: -d -pm npm --i18n --testinNutin')
24
24
  .action(async (projectName, cliOptions) => {
25
25
  print.blue('🚀 Welcome to nutin !');
@@ -4,7 +4,7 @@ import * as fsExtra from 'fs-extra';
4
4
  import { TemplateCompiler } from './template-compiler.mjs';
5
5
  import { print } from './print.mjs';
6
6
  import { initializeGit } from './git-manager.mjs';
7
- import { installDependencies, generatePackageJson, generateTsconfigJson } from './package-manager.mjs';
7
+ import { installDependencies, generatePackageJson, generateTsconfigJson, getCiCommand } from './package-manager.mjs';
8
8
 
9
9
  const fs = fsExtra.default;
10
10
  const __filename = fileURLToPath(import.meta.url);
@@ -55,7 +55,8 @@ export class ProjectGenerator {
55
55
  }
56
56
 
57
57
  buildContext(answers) {
58
- const version = '1.1.0';
58
+ const version = '1.2.0';
59
+ const ciCommand = getCiCommand(answers.packageManager);
59
60
 
60
61
  return {
61
62
  projectName: answers.projectName,
@@ -71,6 +72,7 @@ export class ProjectGenerator {
71
72
 
72
73
  year: new Date().getFullYear(),
73
74
  packageManager: answers.packageManager || 'npm',
75
+ ciCommand: ciCommand,
74
76
  version: version
75
77
  };
76
78
  }
@@ -111,7 +113,7 @@ export class ProjectGenerator {
111
113
  if (BINARY_EXTENSIONS.has(fileExt)) {
112
114
  const outputPath = path.join(outputDir, fileName);
113
115
  await fs.copy(templatePath, outputPath);
114
- print.info(`📄 Copied: ${fileName}`);
116
+ // print.info(`📄 Copied: ${fileName}`);
115
117
  } else if (fileName.endsWith('.hbs')) {
116
118
 
117
119
  const outputFileName = fileName.replace('.hbs', '');
@@ -120,7 +122,7 @@ export class ProjectGenerator {
120
122
  try {
121
123
  const compiledContent = await this.compiler.compileFile(templatePath, context);
122
124
  await fs.writeFile(outputPath, compiledContent);
123
- print.info(`📝 Generated: ${outputFileName}`);
125
+ // print.info(`📝 Generated: ${outputFileName}`);
124
126
  } catch (error) {
125
127
  print.boldError(`❌ Failed to process template: ${fileName}`);
126
128
  throw error;
@@ -128,7 +130,7 @@ export class ProjectGenerator {
128
130
  } else {
129
131
  const outputPath = path.join(outputDir, fileName);
130
132
  await fs.copy(templatePath, outputPath);
131
- print.info(`📄 Copied: ${fileName}`);
133
+ // print.info(`📄 Copied: ${fileName}`);
132
134
  }
133
135
  }
134
136
 
@@ -12,84 +12,92 @@ export async function installDependencies(projectPath, packageManager) {
12
12
  await promiseExec(installCommand, { cwd: projectPath });
13
13
  }
14
14
 
15
- export function getInstallCommand(packageManager) {
15
+ export function getCiCommand(packageManager) {
16
+ switch (packageManager) {
17
+ case 'yarn':
18
+ return 'yarn install --frozen-lockfile';
19
+ case 'pnpm':
20
+ return 'pnpm install --frozen-lockfile';
21
+ case 'bun':
22
+ return 'bun install --frozen-lockfile'
23
+ default:
24
+ return 'npm ci';
25
+ }
26
+ }
27
+
28
+ function getInstallCommand(packageManager) {
16
29
  switch (packageManager) {
17
30
  case 'yarn':
18
31
  return 'yarn install';
19
32
  case 'pnpm':
20
33
  return 'pnpm install';
34
+ case 'bun':
35
+ return 'bun install'
21
36
  default:
22
37
  return 'npm install';
23
38
  }
24
39
  }
25
40
 
26
- export async function generatePackageJson(projectPath, answers) {
27
- const { testinNutin, projectName, template } = answers;
28
-
29
- const baseDevDeps = {
30
- "concurrently": "^9.1.2",
31
- "live-server": "^1.2.2",
32
- "sass": "^1.89.0",
33
- "typescript": "^5.8.3"
34
- };
35
-
36
- const devDependencies = testinNutin
37
- ? { ...baseDevDeps, "jsdom": "^26.1.0" }
38
- : baseDevDeps;
41
+ function getScripts(answers) {
42
+ const { testinNutin, packageManager } = answers;
39
43
 
40
44
  const baseScripts = {
41
- "patch": "npm version patch -m 'CI/CD: Bump version to %s'",
42
- "minor": "npm version minor -m 'CI/CD: Bump version to %s'",
43
- "major": "npm version major -m 'CI/CD: Bump version to %s'",
44
- "build:ts": "tsc --project tsconfig.json",
45
- "build-static": "node tools/builder/builder.js",
46
- "serve": "npm run build && live-server dist/src --port=9090 --entry-file=index.html",
45
+ "patch": `${packageManager} version patch -m 'CI/CD: Bump version to %s'`,
46
+ "minor": `${packageManager} version minor -m 'CI/CD: Bump version to %s'`,
47
+ "major": `${packageManager} version major -m 'CI/CD: Bump version to %s'`,
48
+ "build": "node tools/builder/builder.js",
49
+ "build:prod": "NODE_ENV=production node tools/builder/builder.js",
50
+ "serve:only": "node tools/serve.js",
51
+ "serve": `${packageManager} run build && ${packageManager} run serve:only`,
52
+ "dev": "node tools/dev.js",
47
53
  "generate": "node tools/generator/generator.js"
48
- }
54
+ };
49
55
 
50
- let scripts = testinNutin
51
- ? {
52
- ...baseScripts,
56
+ const testinNutinScripts = {
53
57
  "test": "node test/runner.js",
54
- "test--rebuild": "npm run build && npm run test",
55
- "test:watch": "npm run build && node test/watch-tests.js",
56
- "serve": "npm run build && npm run serve:only",
57
- "serve:only": "live-server dist/src --port=9090 --entry-file=index.html --open",
58
- "dev": "concurrently \"node tools/watcher.js\" \"npm run build && npm run serve:only > /dev/null 2>&1\""
58
+ "test:rebuild": `${packageManager} run build && ${packageManager} run test`,
59
+ "test:watch": `${packageManager} run build && node test/watch-tests.js`,
60
+ "serve": `${packageManager} run build && ${packageManager} run serve:only`
59
61
  }
60
- : baseScripts;
61
62
 
62
- scripts = template
63
+ return testinNutin
63
64
  ? {
64
- ...scripts,
65
- "build:clear": "rm -rf dist/src/* dist-build",
66
- "build:finalize": "mkdir -p dist && mv dist-build/src dist/ && rm -rf dist-build",
67
- "build": "npm run build:clear && npm run build:ts && npm run build-static && npm run build:finalize"
65
+ ...baseScripts,
66
+ ...testinNutinScripts
68
67
  }
69
- : {
70
- ...scripts,
71
- "build:clear": "rm -rf dist",
72
- "build": "npm run build:clear && npm run build:ts && npm run build-static"
73
- };
68
+ : baseScripts;
69
+ }
70
+
71
+ export async function generatePackageJson(projectPath, answers) {
72
+ const { testinNutin, projectName, packageManager } = answers;
73
+
74
+ const devDependencies = {
75
+ "esbuild": "0.25.12",
76
+ "html-minifier-terser": "7.2.0",
77
+ "jsdom": "26.1.0",
78
+ "live-server": "1.2.2",
79
+ "sass": "1.89.0",
80
+ "typescript": "5.8.3"
81
+ };
82
+
83
+ const scripts = getScripts(answers);
74
84
 
75
85
  const packageJson = {
76
86
  "name": projectName,
77
87
  "version": "0.1.0",
78
- "main": "index.js",
79
88
  "type": "module",
80
89
  "imports": testinNutin ? {
81
90
  "#root/*.js": "./*.js"
82
91
  } : {},
83
92
  scripts,
84
- devDependencies
93
+ devDependencies,
94
+ "packageManager": `${packageManager}@latest`
85
95
  };
86
96
 
87
97
  await fs.writeJSON(path.join(projectPath, 'package.json'), packageJson, { spaces: 2 });
88
98
  }
89
99
 
90
100
  export async function generateTsconfigJson(projectPath, answers) {
91
- const { template } = answers;
92
-
93
101
  const tsconfig = {
94
102
  "compilerOptions": {
95
103
  "baseUrl": "./",
@@ -98,12 +106,13 @@ export async function generateTsconfigJson(projectPath, answers) {
98
106
  "module": "NodeNext",
99
107
  "moduleResolution": "NodeNext",
100
108
  "rootDir": ".",
101
- "outDir": template ? "dist-build" : 'dist',
109
+ "outDir": "dist-build",
102
110
  "strict": true,
103
111
  "esModuleInterop": true,
104
112
  "allowSyntheticDefaultImports": true,
105
113
  "lib": ["es2022", "DOM"],
106
114
  "types": [],
115
+ "removeComments": true,
107
116
  "resolveJsonModule": true,
108
117
  "typeRoots": ["src/types", "node_modules/@types"]
109
118
  },
@@ -22,7 +22,7 @@ export async function promptUser(initialName, cliOptions = {}) {
22
22
  type: 'list',
23
23
  name: 'packageManager',
24
24
  message: 'Package manager:',
25
- choices: ['npm', 'yarn', 'pnpm'],
25
+ choices: ['npm', 'yarn', 'pnpm', 'bun'],
26
26
  default: 'npm'
27
27
  }
28
28
  ]);
@@ -34,10 +34,10 @@ export async function promptUser(initialName, cliOptions = {}) {
34
34
  projectName,
35
35
  packageManager,
36
36
  stylinNutin: cliOptions.stylinNutin || true,
37
- template: cliOptions.template || false,
38
- i18n: cliOptions.i18n || false,
39
- transition: cliOptions.transitions || false,
40
- testinNutin: cliOptions.testinNutin || false
37
+ template: cliOptions.template || true,
38
+ i18n: cliOptions.i18n || true,
39
+ testinNutin: cliOptions.testinNutin || true,
40
+ transition: cliOptions.transitions || false
41
41
  };
42
42
  }
43
43
 
@@ -47,7 +47,7 @@ export async function promptUser(initialName, cliOptions = {}) {
47
47
  questions.push({
48
48
  type: 'confirm',
49
49
  name: 'stylinNutin',
50
- message: 'Use built-in SCSS utility classes?',
50
+ message: 'Use built-in SCSS utility classes ?',
51
51
  default: true
52
52
  });
53
53
  }
@@ -56,8 +56,8 @@ export async function promptUser(initialName, cliOptions = {}) {
56
56
  questions.push({
57
57
  type: 'confirm',
58
58
  name: 'template',
59
- message: 'Use external templates?',
60
- default: false
59
+ message: 'Use external templates ?',
60
+ default: true
61
61
  });
62
62
  }
63
63
 
@@ -65,8 +65,8 @@ export async function promptUser(initialName, cliOptions = {}) {
65
65
  questions.push({
66
66
  type: 'confirm',
67
67
  name: 'i18n',
68
- message: 'Use i18n & json-based content?',
69
- default: false
68
+ message: 'Use i18n with json-based content ?',
69
+ default: true
70
70
  });
71
71
  }
72
72
 
@@ -84,7 +84,7 @@ export async function promptUser(initialName, cliOptions = {}) {
84
84
  type: 'confirm',
85
85
  name: 'testinNutin',
86
86
  message: 'Use testin-nutin toolkit?',
87
- default: false
87
+ default: true
88
88
  });
89
89
  }
90
90
 
package/lib/src/utils.mjs CHANGED
@@ -5,11 +5,12 @@ import { print } from './print.mjs';
5
5
  export const promiseExec = promisify(exec);
6
6
 
7
7
  export function displaySuccessMessage(answers) {
8
- const { projectName, installDeps, packageManager } = answers;
8
+ const { projectName } = answers;
9
9
 
10
10
  print.boldSuccess('\n🎉 Your project is ready!');
11
11
  print.info('\nNext steps:');
12
12
  print.info(` cd ${projectName} && npm run serve`);
13
+ print.boldInfo(`\nDocumentation on https://www.nutin.org`);
13
14
 
14
15
  print.success('\nHappy coding! 🚀\n');
15
16
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nutin/cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "A lightweight, highly tweakable frontend toolkit, crafted for building lean, dynamic single-page applications without the complexity of full-scale frameworks.",
5
5
  "preferGlobal": true,
6
6
  "main": "lib/index.mjs",
@@ -1,5 +1,6 @@
1
1
  node_modules
2
2
  dist
3
+ dist-build
3
4
  .github
4
5
  .gitignore
5
6
  .git
@@ -7,6 +8,14 @@ dist
7
8
  *.md
8
9
  .env
9
10
  *.txt
10
- {{#if i18n}}
11
+ {{#if testinNutin}}
11
12
  *.test
12
13
  {{/if}}
14
+ *.log
15
+
16
+ npm-debug.log
17
+ .nyc_output
18
+ coverage
19
+ .DS_Store
20
+ .cache
21
+ .parcel-cache
@@ -1,13 +1,33 @@
1
- FROM node:22.3.0 AS builder
1
+ ARG NODE_VERSION=22.3.0
2
+ ARG NGINX_VERSION=1.27-alpine
3
+
4
+ FROM node:${NODE_VERSION} AS builder
2
5
  WORKDIR /usr/src/app
3
6
  COPY . .
4
- RUN npm ci && \
5
- npm run build
7
+ RUN {{ciCommand}} && \
8
+ {{packageManager}} run build:prod
9
+
10
+ FROM nginx:${NGINX_VERSION}
11
+
12
+ RUN rm /etc/nginx/conf.d/default.conf
13
+ COPY --from=builder /usr/src/app/nginx.conf /etc/nginx/conf.d/default.conf
14
+
15
+ RUN sed -i '/http {/a \
16
+ # Gzip compression\n\
17
+ gzip on;\n\
18
+ gzip_vary on;\n\
19
+ gzip_proxied any;\n\
20
+ gzip_comp_level 6;\n\
21
+ gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;\n\
22
+ gzip_min_length 1000;\n\
23
+ ' /etc/nginx/nginx.conf && \
24
+ touch /var/run/nginx.pid
6
25
 
7
- FROM nginx:1.21-alpine
8
- RUN sed -i 's/listen .*/listen 9090;/' /etc/nginx/conf.d/default.conf && \
9
- sed -i 's|index index.html index.htm;|index index.html index.htm;\n try_files \$uri /index.html;|' /etc/nginx/conf.d/default.conf && \
10
- mkdir -p /usr/share/nginx/html
11
26
  COPY --from=builder /usr/src/app/dist/src /usr/share/nginx/html
12
27
 
13
- CMD ["nginx", "-g", "daemon off;"]
28
+ EXPOSE 9090
29
+
30
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
31
+ CMD curl -f http://localhost:9090/health || exit 1
32
+
33
+ CMD ["nginx", "-g", "daemon off;"]
@@ -1 +1,24 @@
1
1
  # {{projectName}} - Built with nutin
2
+
3
+ ## Start
4
+
5
+ - `{{packageManager}} run serve` (rebuild), `{{packageManager}} run serve:only` (no rebuild), `{{packageManager}} run dev` (watch).
6
+
7
+ ## Build
8
+
9
+ - `{{packageManager}} run build` (dev) or `{{packageManager}} run build:prod` (production)
10
+
11
+ Accepts `--bundle` (= build:prod) and `--log` (verbose output) flags
12
+
13
+ ## Generate Component, View, Service
14
+
15
+ - `{{packageManager}} run generate ELEMENT ELEMENT_NAME`
16
+
17
+ Supports nested paths.
18
+
19
+ {{#if testinNutin}}
20
+ ## Test
21
+
22
+ - `{{packageManager}} run test:rebuild`, `{{packageManager}} run test`, `{{packageManager}} run test:watch`.
23
+
24
+ {{/if}}
@@ -0,0 +1,43 @@
1
+ server {
2
+ listen 9090;
3
+ server_name _;
4
+ root /usr/share/nginx/html;
5
+ index index.html;
6
+
7
+ # Security headers
8
+ add_header X-Frame-Options "SAMEORIGIN" always;
9
+ add_header X-Content-Type-Options "nosniff" always;
10
+ add_header X-XSS-Protection "1; mode=block" always;
11
+ add_header Referrer-Policy "strict-origin-when-cross-origin" always;
12
+ add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
13
+
14
+ # Content Security Policy - ADJUST BASED ON YOUR APP'S NEEDS
15
+ add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self';" always;
16
+
17
+ location / {
18
+ try_files $uri $uri/ /index.html;
19
+ }
20
+
21
+ # Static assets with long cache
22
+ location ~* \.(js|css|png|jpg|jpeg|gif|avif|ico|svg|woff|woff2|ttf|eot)$ {
23
+ expires 1y;
24
+ add_header Cache-Control "public, immutable";
25
+ }
26
+
27
+ # HTML files - no cache
28
+ location ~* \.html$ {
29
+ expires -1;
30
+ add_header Cache-Control "no-cache, no-store, must-revalidate";
31
+ add_header Pragma "no-cache";
32
+ }
33
+
34
+ # Health check endpoint
35
+ location /health {
36
+ access_log off;
37
+ return 200 "healthy\n";
38
+ add_header Content-Type text/plain;
39
+ }
40
+
41
+ # Hide nginx version
42
+ server_tokens off;
43
+ }
@@ -6,6 +6,7 @@ import { I18nHelper } from './helpers/i18n.helper.js';
6
6
  import { PipeHelper } from './helpers/pipe.helper.js';
7
7
  import { ChildrenHelper } from './helpers/children.helper.js';
8
8
  import { CatalogHelper, CatalogConfig } from './helpers/catalog.helper.js';
9
+ export { CatalogItemConfig } from './helpers/catalog.helper.js';
9
10
 
10
11
  /**```typescript
11
12
  * export interface ComponentConfig {
@@ -59,7 +60,7 @@ export abstract class BaseComponent<T extends HTMLElement = HTMLElement> {
59
60
  /**
60
61
  * ```typescript
61
62
  interface CatalogConfig {
62
- array: any[];
63
+ array: CatalogItemConfig[];
63
64
  elementName: string;
64
65
  elementTag?: keyof HTMLElementTagNameMap;
65
66
  selector: string;
@@ -1,7 +1,7 @@
1
1
  import { BaseComponent, ComponentConfig } from "../base-component.js";
2
2
 
3
3
  export interface CatalogConfig {
4
- array: any[];
4
+ array: CatalogItemConfig[];
5
5
  elementName: string;
6
6
  elementTag?: keyof HTMLElementTagNameMap;
7
7
  selector: string;
@@ -9,7 +9,7 @@ export interface CatalogConfig {
9
9
  };
10
10
 
11
11
  export interface CatalogItemBase {
12
- idx: number;
12
+ index: number;
13
13
  }
14
14
 
15
15
  export type CatalogItemObject<T extends object> = T & CatalogItemBase;
@@ -24,7 +24,7 @@ export type CatalogItemConfig<T = any> =
24
24
  /**
25
25
  * ```typescript
26
26
  interface CatalogConfig {
27
- array: any[];
27
+ array: CatalogItemConfig[];
28
28
  elementName: string;
29
29
  elementTag?: keyof HTMLElementTagNameMap;
30
30
  selector: string;
@@ -53,23 +53,23 @@ export class CatalogHelper {
53
53
  return componentConfigs;
54
54
  }
55
55
 
56
- private static createElements(idx: number, config: CatalogConfig, container: HTMLElement) {
56
+ private static createElements(index: number, config: CatalogConfig, container: HTMLElement) {
57
57
  const el = document.createElement(config.elementTag || 'div');
58
- el.setAttribute('data-component', `${config.elementName}-${idx}`);
59
- el.dataset.index = String(idx);
58
+ el.setAttribute('data-component', `${config.elementName}-${index}`);
59
+ el.dataset.index = String(index);
60
60
  container?.appendChild(el);
61
61
  }
62
62
 
63
- private static pushConfig(idx: number, componentConfigs: ComponentConfig[], config: CatalogConfig) {
64
- const item = config.array[idx];
63
+ private static pushConfig(index: number, componentConfigs: ComponentConfig[], config: CatalogConfig) {
64
+ const item = config.array[index];
65
65
 
66
66
  const configWithIndex: CatalogItemConfig = (item && typeof item === 'object')
67
- ? { ...(item as object), index: idx }
68
- : { value: item, index: idx };
67
+ ? { ...(item as object), index: index }
68
+ : { value: item, index: index };
69
69
 
70
70
  componentConfigs.push(
71
71
  {
72
- selector: `${config.elementName}-${idx}`,
72
+ selector: `${config.elementName}-${index}`,
73
73
  factory: (el) => new config.component(el, configWithIndex)
74
74
  },
75
75
  )
@@ -36,6 +36,7 @@ export class TokenHelper {
36
36
 
37
37
  public static resolve(token: string, el: Element, event: Event): any {
38
38
  const htmlEl = el as HTMLElement;
39
+ event.preventDefault();
39
40
 
40
41
  return (
41
42
  this.resolveExact(token, htmlEl, event) ??
@@ -81,7 +82,7 @@ export class TokenHelper {
81
82
  }
82
83
 
83
84
  /** Register a new exact token like "@foo" */
84
- public static registerToken(
85
+ public static registerCustomToken(
85
86
  name: string,
86
87
  resolver: (el: HTMLElement, ev: Event) => any
87
88
  ) {
@@ -89,7 +90,7 @@ export class TokenHelper {
89
90
  }
90
91
 
91
92
  /** Register a new prefixed token like "@style:" */
92
- public static registerPrefixed(
93
+ public static registerPrefixedToken(
93
94
  prefix: string,
94
95
  resolver: (suffix: string, el: HTMLElement) => any
95
96
  ) {