@nutin/cli 1.2.0 → 1.2.1

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 (79) hide show
  1. package/CHANGELOG.md +63 -30
  2. package/README.md +9 -8
  3. package/lib/index.mjs +6 -6
  4. package/lib/src/file-generator.mjs +18 -5
  5. package/lib/src/package-manager.mjs +33 -21
  6. package/lib/src/prompts.mjs +2 -2
  7. package/lib/src/utils.mjs +2 -0
  8. package/package.json +4 -3
  9. package/templates/base/.dockerignore.hbs +2 -1
  10. package/templates/base/builder.config.js.hbs +28 -0
  11. package/templates/base/src/app/components/common/anchor/anchor.component.ts.hbs +11 -12
  12. package/templates/base/src/app/dom.test.js.hbs +8 -1
  13. package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +3 -0
  14. package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.ts.hbs +3 -0
  15. package/templates/base/src/core/base-classes/base-component/helpers/token.helper.ts.hbs +2 -2
  16. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +3 -3
  17. package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.ts.hbs +2 -2
  18. package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.ts.hbs +4 -3
  19. package/templates/base/src/core/utils/button-manager/button-manager.utils.test.js.hbs +1 -1
  20. package/templates/base/src/index.html.hbs +1 -0
  21. package/templates/base/src/styles/main.scss.hbs +2 -0
  22. package/templates/base/tools/builder/builder.js.hbs +10 -7
  23. package/templates/base/tools/builder/core/copy-static.js.hbs +27 -2
  24. package/templates/base/tools/builder/core/esbuild.js.hbs +5 -15
  25. package/templates/base/tools/builder/core/finalize-build.js.hbs +2 -2
  26. package/templates/base/tools/builder/core/minify-html.js.hbs +2 -2
  27. package/templates/base/tools/builder/core/{sass.config.js.hbs → sass.js.hbs} +5 -7
  28. package/templates/base/tools/builder/core/tsc.js.hbs +8 -4
  29. package/templates/base/tools/builder/core/validate-html.js.hbs +19 -14
  30. package/templates/base/tools/deployment/Dockerfile.hbs +22 -0
  31. package/templates/base/tools/deployment/gzip.conf.hbs +19 -0
  32. package/templates/base/tools/deployment/nginx.conf.hbs +78 -0
  33. package/templates/base/tools/dev/dev-serve.js.hbs +32 -0
  34. package/templates/base/tools/{watcher.js.hbs → dev/watcher.js.hbs} +1 -1
  35. package/templates/base/tools/generator/generator.js.hbs +13 -5
  36. package/templates/base/tools/generator/handle-file.js.hbs +15 -8
  37. package/templates/base/tools/utils/export-flags.js.hbs +2 -1
  38. package/templates/base/tools/utils/index.js.hbs +4 -1
  39. package/templates/base/tools/utils/run-command.js.hbs +10 -3
  40. package/templates/base/tools/utils/run-script.js.hbs +1 -1
  41. package/templates/features/i18n/src/core/config.ts.hbs +3 -0
  42. package/templates/features/i18n/src/core/services/i18n/i18n.test.js.hbs +2 -0
  43. package/templates/features/i18n/src/core/services/i18n/i18n.ts.hbs +10 -1
  44. package/templates/features/i18n/src/core/services/i18n/languages.ts.hbs +7 -0
  45. package/templates/features/i18n/tools/builder/core/build-i18n.js.hbs +2 -2
  46. package/templates/features/i18n/tools/utils/languages.js.hbs +7 -0
  47. package/templates/features/stylinNutin/src/styles/components/_index.scss.hbs +0 -3
  48. package/templates/features/stylinNutin/src/styles/views/_index.scss.hbs +0 -0
  49. package/templates/features/template/tools/builder/core/merge-templates.js.hbs +1 -1
  50. package/templates/features/testinNutin/{test → testin-nutin}/core/index.js.hbs +1 -1
  51. package/templates/features/testinNutin/testin-nutin/core/src/globals/assertion-lib.js.hbs +136 -0
  52. package/templates/features/testinNutin/testin-nutin/core/src/globals/jsdom-setup.js.hbs +101 -0
  53. package/templates/features/testinNutin/{test → testin-nutin}/core/src/globals/register-test-globals.js.hbs +15 -1
  54. package/templates/features/testinNutin/testin-nutin/core/src/globals/spyon.js.hbs +59 -0
  55. package/templates/features/testinNutin/{test → testin-nutin}/core/src/utils/print.js.hbs +3 -1
  56. package/templates/features/testinNutin/{test → testin-nutin}/core/src/utils/test-discovery.js.hbs +5 -1
  57. package/templates/features/testinNutin/{test → testin-nutin}/core/src/utils/test-queue.js.hbs +3 -1
  58. package/templates/features/testinNutin/{test → testin-nutin}/core/tests/assertions.test.js.hbs +26 -2
  59. package/templates/features/testinNutin/testin-nutin/runner.js.hbs +35 -0
  60. package/templates/features/testinNutin/{test → testin-nutin}/watch-tests.js.hbs +1 -1
  61. package/templates/features/testinNutin/testin-nutin.config.js.hbs +14 -0
  62. package/templates/base/Dockerfile.hbs +0 -33
  63. package/templates/base/nginx.conf.hbs +0 -43
  64. package/templates/base/tools/builder/docs.md.hbs +0 -9
  65. package/templates/base/tools/dev.js.hbs +0 -43
  66. package/templates/features/testinNutin/test/core/src/globals/assertion-lib.js.hbs +0 -118
  67. package/templates/features/testinNutin/test/core/src/globals/spyon.js.hbs +0 -22
  68. package/templates/features/testinNutin/test/core/src/jsdom-setup.js.hbs +0 -12
  69. package/templates/features/testinNutin/test/docs.md.hbs +0 -39
  70. package/templates/features/testinNutin/test/runner.js.hbs +0 -36
  71. /package/templates/base/tools/{serve.js.hbs → dev/serve.js.hbs} +0 -0
  72. /package/templates/features/testinNutin/{test → testin-nutin}/core/src/utils/queue.js.hbs +0 -0
  73. /package/templates/features/testinNutin/{test → testin-nutin}/core/tests/globals-each-all.test.js.hbs +0 -0
  74. /package/templates/features/testinNutin/{test → testin-nutin}/mocks/create-mock-method.js.hbs +0 -0
  75. /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-event-bus.js.hbs +0 -0
  76. /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-http-client.js.hbs +0 -0
  77. /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-i18n.js.hbs +0 -0
  78. /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-router.js.hbs +0 -0
  79. /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-store.js.hbs +0 -0
package/CHANGELOG.md CHANGED
@@ -1,12 +1,67 @@
1
1
  # Changelog
2
2
 
3
- ## V1.0.1
3
+ ## V1.2.1
4
4
 
5
- - Fixed typo issue when using i18n feature
5
+ - Builder
6
6
 
7
- ## V1.0.2
7
+ - Added config file `builder.config.js` file (esbuild, verbose)
8
8
 
9
- - Partially fixed `npm run dev` script when using internal templates. *Page still needs to be reload manually from time to time.*
9
+ - I18N
10
+
11
+ - Added JSON config file for centralized languages (`config/languages.json`)
12
+
13
+ - testin-nutin
14
+
15
+ - Added config file `test.config.js` (origins array, verbose boolean)
16
+ - Added assertions + automated 'not' counterparts
17
+ - Improved spyOn
18
+ - Improved JSDOM setup & global registration
19
+
20
+ - Improved deployment tools Dockerfile (nginx conf + gzip)
21
+
22
+ - Fixed typo and added comments in `nginx.conf`.
23
+ - Extracted gzip config from Dockerfile (removed `sed` command)
24
+ - Moved Dockerfile and deployment config files (`nginx.conf`, `gzip.conf`) into their own folder `tools/deployment`.
25
+ - Added `docker:build` and `docker:run` scripts in package.json.
26
+
27
+ See [DEPLOYMENT.md file](../../docs/DEPLOYMENT_HELPERS.md) for details.
28
+
29
+ - Moved dev tools (`serve.js`, `dev-serve.js`, `watcher.js`) into their own `tools/dev` folder.
30
+
31
+ - Added explicit chokidar devDependency (sass & live-server transitive).
32
+
33
+ ## V1.2.0
34
+
35
+ - Fixed `npm run dev` command. Added middleware to reload nested routes.
36
+
37
+ - Improved `data-optional` tag:
38
+ - Can now specify which attribute to check with `data-optional="attrName"` (`src`, `href`, ...)
39
+ - Now works for `img`, `input`, etc
40
+
41
+ - Event Bus :
42
+ - Tracks both event name + callback.
43
+ - cleanupEventListeners now works correctly.
44
+ - New property: `once`. `once` subscriptions automatically remove themselves after first call.
45
+
46
+ - HTTP client :
47
+ - Optional request / response interceptors.
48
+ - Full timeout / abort controller support preserved.
49
+ - onDestroy now clears interceptors to prevent memory leaks.
50
+
51
+ - Improved builder
52
+
53
+ - Implemented `esbuild` & `html-minifier-terser`.
54
+ - Added flags `--bundle` (for production-ready build. *`npm run build --bundle` is equivalent to `npm run build:prod`)* and `--log` (verbose output). **Doesn't work with yarn or bun**.
55
+ - Commands: `npm run build`, `npm run build:prod`, `npm run serve:only`, `npm run serve`, `npm run dev`.
56
+
57
+ *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';`
58
+
59
+ - Improved deployment tools Dockerfile (nginx conf + gzip)
60
+
61
+ See [DEPLOYMENT.md file](../../docs/DEPLOYMENT_HELPERS.md) for details.
62
+
63
+ - stylin-nutin (generator)
64
+ - When generating a component, prompts (boolean) to generate a `_component-name.scss` file in `styles/components` (forwarded by `stymes/components/_index.scss`).
10
65
 
11
66
  ## V1.1.0
12
67
 
@@ -27,32 +82,10 @@ type CatalogItemConfig<T = any> =
27
82
 
28
83
  **Notes:** *Primitive data arrays (string, number, etc) needs to be accessed with `config.value`.*
29
84
 
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`.
85
+ ## V1.0.2
53
86
 
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';`
87
+ - Partially fixed `npm run dev` script when using internal templates. *Page still needs to be reload manually from time to time.*
55
88
 
56
- - Improved default Dockerfile (nginx conf + gzip)
89
+ ## V1.0.1
57
90
 
58
- **NOTE :***Update nginx.conf as needed. Example: CSP connect-src if the app makes API calls.*
91
+ - Fixed typo issue when using i18n feature
package/README.md CHANGED
@@ -1,9 +1,11 @@
1
1
  # [nutin](https://www.nutin.org)
2
2
 
3
- **A lightweight, highly tweakable frontend toolkit, crafted for building lean, dynamic single-page applications without the complexity of full-scale frameworks.**
3
+ **Build SPAs by understanding SPAs. A minimal frontend toolkit crafted for learning and lean applications.**
4
4
  Includes essential features out of the box, with zero runtime dependencies.
5
5
  *TypeScript / Javascript, SASS, HTML*
6
6
 
7
+ - Required : Node >=18 **If using testin-nutin: Node >=20.19.0 (JSDOM requirement : `{ node: '^20.19.0 || ^22.12.0 || >=24.0.0' }`)**
8
+
7
9
  ## Install
8
10
 
9
11
  ```bash
@@ -25,20 +27,20 @@ nutin-new
25
27
  # argument: project-name
26
28
 
27
29
  -d --defaults
28
- -pm --package-manager <manager> # npm (default), yarn, pnpm
30
+ -pm --package-manager <manager> # npm (Default), yarn, pnpm, bun
29
31
 
30
- --template # Use external templates
32
+ --no-template # Use inline templates
31
33
  --no-stylin-nutin # Do not use built-in SCSS utility
32
34
  --testin-nutin # Use testin-nutin toolkit
33
- --i18n # Use i18n & json-based content
35
+ --no-i18n # Do not use i18n & json-based content
34
36
  --transition # Use animated view transitions. Note: May interfere with CSS `position: fixed`, `z-index`...
35
37
 
36
38
  # Defaults
37
- --no-template # (Default) Do not use external templates
39
+ --template # (Default) Use external templates
38
40
  --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
+ --i18n # (Default) Use i18n & json-based content
41
42
  --no-testin-nutin # (Default) Do not use testin-nutin toolkit
43
+ --no-transition # (Default) Do not use animated view transitions
42
44
 
43
45
  # Help
44
46
  -h --help
@@ -46,6 +48,5 @@ nutin-new
46
48
 
47
49
  ## [First app tutorial](https://www.nutin.org/tutorial)
48
50
 
49
-
50
51
  - Visit the [website](https://www.nutin.org)
51
52
  - [Project repository](https://github.com/LoisKOUNINEF/nutin)
package/lib/index.mjs CHANGED
@@ -2,11 +2,11 @@ import { program } from 'commander';
2
2
  import { print } from './src/print.mjs';
3
3
  import { promptUser } from './src/prompts.mjs';
4
4
  import { createProject } from './src/file-generator.mjs';
5
- import { displaySuccessMessage } from './src/utils.mjs';
5
+ import { displaySuccessMessage, packageVersion } from './src/utils.mjs';
6
6
 
7
7
  export async function createApp() {
8
8
  program
9
- .version('1.1.0')
9
+ .version(packageVersion)
10
10
  .argument('[projectName]', 'Name of the project')
11
11
  .option('-d, --defaults', 'Use default options')
12
12
  .option('-pm, --package-manager <manager>', 'Specify package manager (npm, yarn, pnpm, bun)')
@@ -14,10 +14,10 @@ export async function createApp() {
14
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
- .option('--i18n', 'Use i18n & json-based content')
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')
17
+ .option('--i18n', '(Default) Use i18n & json-based content')
18
+ .option('--no-i18n', 'Do not use i18n & json-based content')
19
+ .option('--testin-nutin', 'Use testin-nutin toolkit')
20
+ .option('--no-testin-nutin', '(Default) Do not use testin-nutin toolkit')
21
21
  .option('--transition', 'Use animated view transitions\nNote: May interfere with CSS `position: fixed`, `z-index`...')
22
22
  .option('--no-transition', '(Default) Do not use animated view transitions')
23
23
  .addHelpText('afterAll', '\nChaining options :\nExample: -d -pm npm --i18n --testinNutin')
@@ -5,6 +5,7 @@ import { TemplateCompiler } from './template-compiler.mjs';
5
5
  import { print } from './print.mjs';
6
6
  import { initializeGit } from './git-manager.mjs';
7
7
  import { installDependencies, generatePackageJson, generateTsconfigJson, getCiCommand } from './package-manager.mjs';
8
+ import { packageVersion } from './utils.mjs';
8
9
 
9
10
  const fs = fsExtra.default;
10
11
  const __filename = fileURLToPath(import.meta.url);
@@ -48,14 +49,14 @@ export class ProjectGenerator {
48
49
  const context = this.buildContext(answers);
49
50
  const templateDir = this.getTemplateDirectory(answers);
50
51
 
51
- print.section('\n📝 Processing templates...');
52
+ print.section('📝 Processing templates...');
52
53
  await this.processTemplateDirectory(templateDir, projectPath, context);
53
54
 
54
55
  await this.processFeatureTemplates(projectPath, context);
55
56
  }
56
57
 
57
58
  buildContext(answers) {
58
- const version = '1.2.0';
59
+ const version = packageVersion;
59
60
  const ciCommand = getCiCommand(answers.packageManager);
60
61
 
61
62
  return {
@@ -148,7 +149,7 @@ export class ProjectGenerator {
148
149
  const featureTemplateDir = path.join(featuresDir, feature);
149
150
 
150
151
  if (await fs.pathExists(featureTemplateDir)) {
151
- print.info(`\n🔧 Adding ${feature} feature...`);
152
+ print.info(`🔧 Adding ${feature} feature...`);
152
153
  await this.processTemplateDirectory(featureTemplateDir, projectPath, context);
153
154
  }
154
155
  }
@@ -158,6 +159,18 @@ export class ProjectGenerator {
158
159
  async generateJsonFiles(projectPath, answers) {
159
160
  await generatePackageJson(projectPath, answers);
160
161
  await generateTsconfigJson(projectPath, answers);
162
+ if (answers.i18n) this.generateConfigFiles(projectPath);
163
+ }
164
+
165
+ async generateConfigFiles(projectPath) {
166
+ const configPath = path.join(projectPath, 'config');
167
+ await fs.ensureDir(configPath);
168
+ const languages = {
169
+ "languages": ["en"],
170
+ "defaultLanguage": "en"
171
+ };
172
+
173
+ await fs.writeJSON(path.join(configPath, 'languages.json'), languages, { spaces: 2 });
161
174
  }
162
175
 
163
176
  async runPostSetupTasks(projectPath, answers) {
@@ -170,10 +183,10 @@ export class ProjectGenerator {
170
183
  }
171
184
 
172
185
  async runSetupScripts(projectPath, answers) {
173
- print.section('\n⚙️ Running setup scripts...');
186
+ print.section('⚙️ Running setup scripts...');
174
187
 
175
188
  try {
176
- const setupScriptsTemplate = path.join(__dirname, '../../templates/scripts');
189
+ const setupScriptsTemplate = path.join(__dirname, '..', '..', 'templates', 'scripts');
177
190
 
178
191
  if (await fs.pathExists(setupScriptsTemplate)) {
179
192
  const scriptsDir = path.join(projectPath, 'scripts');
@@ -39,7 +39,7 @@ function getInstallCommand(packageManager) {
39
39
  }
40
40
 
41
41
  function getScripts(answers) {
42
- const { testinNutin, packageManager } = answers;
42
+ const { testinNutin, packageManager, projectName } = answers;
43
43
 
44
44
  const baseScripts = {
45
45
  "patch": `${packageManager} version patch -m 'CI/CD: Bump version to %s'`,
@@ -47,17 +47,18 @@ function getScripts(answers) {
47
47
  "major": `${packageManager} version major -m 'CI/CD: Bump version to %s'`,
48
48
  "build": "node tools/builder/builder.js",
49
49
  "build:prod": "NODE_ENV=production node tools/builder/builder.js",
50
- "serve:only": "node tools/serve.js",
50
+ "serve:only": "node tools/dev/serve.js",
51
51
  "serve": `${packageManager} run build && ${packageManager} run serve:only`,
52
- "dev": "node tools/dev.js",
53
- "generate": "node tools/generator/generator.js"
52
+ "dev": "node tools/dev/dev-serve.js",
53
+ "generate": "node tools/generator/generator.js",
54
+ "docker:build": `docker build -t ${projectName} -f tools/deployment/Dockerfile .`,
55
+ "docker:run": `docker run -p 9090:9090 ${projectName}:latest`
54
56
  };
55
57
 
56
58
  const testinNutinScripts = {
57
- "test": "node test/runner.js",
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
+ "test": "node testin-nutin/runner.js",
60
+ "test:rebuild": `${packageManager} run build && ${packageManager} run test`,
61
+ "test:watch": `${packageManager} run build && node testin-nutin/watch-tests.js`
61
62
  }
62
63
 
63
64
  return testinNutin
@@ -72,12 +73,15 @@ export async function generatePackageJson(projectPath, answers) {
72
73
  const { testinNutin, projectName, packageManager } = answers;
73
74
 
74
75
  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"
76
+ "chokidar": "^4.0.3",
77
+ "esbuild": "^0.25.12",
78
+ "html-minifier-terser": "^7.2.0",
79
+ "live-server": "^1.2.2",
80
+ "sass": "^1.89.0",
81
+ "typescript": "^5.8.3",
82
+ ...(testinNutin && {
83
+ "jsdom": "^26.1.0",
84
+ })
81
85
  };
82
86
 
83
87
  const scripts = getScripts(answers);
@@ -86,12 +90,18 @@ export async function generatePackageJson(projectPath, answers) {
86
90
  "name": projectName,
87
91
  "version": "0.1.0",
88
92
  "type": "module",
89
- "imports": testinNutin ? {
90
- "#root/*.js": "./*.js"
91
- } : {},
93
+ ...(testinNutin && {
94
+ "imports": {
95
+ "#root/*.js": "./*.js"
96
+ }
97
+ }),
92
98
  scripts,
93
99
  devDependencies,
94
- "packageManager": `${packageManager}@latest`
100
+ ...(testinNutin && {
101
+ "engines": {
102
+ "node": ">=20.19.0"
103
+ }
104
+ })
95
105
  };
96
106
 
97
107
  await fs.writeJSON(path.join(projectPath, 'package.json'), packageJson, { spaces: 2 });
@@ -105,13 +115,15 @@ export async function generateTsconfigJson(projectPath, answers) {
105
115
  "target": "ESNext",
106
116
  "module": "NodeNext",
107
117
  "moduleResolution": "NodeNext",
108
- "rootDir": ".",
109
- "outDir": "dist-build",
118
+ "rootDir": "src",
119
+ "outDir": "dist-build/src",
110
120
  "strict": true,
121
+ "skipLibCheck": true,
122
+ "forceConsistentCasingInFileNames": true,
123
+ "noUncheckedIndexedAccess": true,
111
124
  "esModuleInterop": true,
112
125
  "allowSyntheticDefaultImports": true,
113
126
  "lib": ["es2022", "DOM"],
114
- "types": [],
115
127
  "removeComments": true,
116
128
  "resolveJsonModule": true,
117
129
  "typeRoots": ["src/types", "node_modules/@types"]
@@ -36,7 +36,7 @@ export async function promptUser(initialName, cliOptions = {}) {
36
36
  stylinNutin: cliOptions.stylinNutin || true,
37
37
  template: cliOptions.template || true,
38
38
  i18n: cliOptions.i18n || true,
39
- testinNutin: cliOptions.testinNutin || true,
39
+ testinNutin: cliOptions.testinNutin || false,
40
40
  transition: cliOptions.transitions || false
41
41
  };
42
42
  }
@@ -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: true
87
+ default: false
88
88
  });
89
89
  }
90
90
 
package/lib/src/utils.mjs CHANGED
@@ -14,3 +14,5 @@ export function displaySuccessMessage(answers) {
14
14
 
15
15
  print.success('\nHappy coding! 🚀\n');
16
16
  }
17
+
18
+ export const packageVersion = '1.2.1';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nutin/cli",
3
- "version": "1.2.0",
4
- "description": "A lightweight, highly tweakable frontend toolkit, crafted for building lean, dynamic single-page applications without the complexity of full-scale frameworks.",
3
+ "version": "1.2.1",
4
+ "description": "Build SPAs by understanding SPAs. A minimal frontend toolkit crafted for learning and lean applications.",
5
5
  "preferGlobal": true,
6
6
  "main": "lib/index.mjs",
7
7
  "bin": {
@@ -19,6 +19,7 @@
19
19
  "nutin",
20
20
  "Frontend",
21
21
  "SPA",
22
+ "Learning",
22
23
  "Minimalist",
23
24
  "Lighweight",
24
25
  "Customizable"
@@ -32,7 +33,7 @@
32
33
  "inquirer": "^9.0.0"
33
34
  },
34
35
  "engines": {
35
- "node": ">=18.0.0"
36
+ "node": ">=18"
36
37
  },
37
38
  "repository": {
38
39
  "type": "git",
@@ -9,7 +9,8 @@ dist-build
9
9
  .env
10
10
  *.txt
11
11
  {{#if testinNutin}}
12
- *.test
12
+ *.test.js
13
+ testin-nutin
13
14
  {{/if}}
14
15
  *.log
15
16
 
@@ -0,0 +1,28 @@
1
+ export default {
2
+ esbuild: {
3
+ bundle: true,
4
+ minify: true,
5
+ sourcemap: false,
6
+ platform: 'browser',
7
+ format: 'esm',
8
+ target: ['es2015'],
9
+ legalComments: 'none',
10
+ loader: {
11
+ '.json': 'json',
12
+ },
13
+ define: {
14
+ 'process.env.NODE_ENV': '"production"',
15
+ },
16
+ },
17
+ sass: {
18
+ paths: [
19
+ 'base',
20
+ 'core',
21
+ {{#if stylinNutin}}
22
+ 'components',
23
+ 'views',
24
+ {{/if}}
25
+ ], // in styles/
26
+ },
27
+ verbose: false,
28
+ }
@@ -2,21 +2,19 @@ import { AnchorManager, Component, IAnchorConfig } from '../../../../core/index.
2
2
 
3
3
  /**
4
4
  * ```typescript
5
- interface IAttributesConfig {
6
- {{#if i18n}}
7
- i18nKey?: string;
8
- {{/if}}
5
+ * interface IAnchorConfig {
6
+ // prefix href with '#' for internal anchor
7
+ href: string;
8
+ target?: string;
9
9
  textContent?: string;
10
+ {{#if i18n}}
11
+ i18nKey?: string;
12
+ {{/if}}
10
13
  className?: string;
11
14
  style?: string;
12
- // use regular pipe syntax for arguments / chaining
15
+ // use regular pipe syntax for arguments / chaining
13
16
  pipes?: string;
14
- }
15
- interface IAnchorConfig extends IAttributesConfig {
16
- // prefix href with '#' for internal anchor
17
- href: string;
18
- // _blank, etc
19
- target?: string;
17
+ tagName?: keyof HTMLElementTagNameMap;
20
18
  }
21
19
  ```
22
20
  */
@@ -25,9 +23,10 @@ export class AnchorComponent extends Component<HTMLAnchorElement, IAnchorConfig>
25
23
  constructor(mountTarget: HTMLElement, config: IAnchorConfig) {
26
24
  super({
27
25
  mountTarget,
26
+ tagName: config.tagName,
28
27
  config,
29
28
  normalizeKeys: ['style', 'pipes', 'className']
30
29
  });
31
30
  new AnchorManager(config, this.element);
32
31
  }
33
- }
32
+ }
@@ -1,6 +1,13 @@
1
1
  describe('App container', () => {
2
- it('should exist', () => {
2
+ it('should be defined', () => {
3
3
  const appContainer = document.getElementById('app');
4
4
  expect(appContainer).toBeDefined();
5
5
  });
6
+ it('should have title', () => {
7
+ expect(document.title).toEqual('{{projectName}}');
8
+ });
9
+ it('should have meta description', () => {
10
+ const desc = document.querySelector('meta[name="description"]').content;
11
+ expect(desc).toEqual('{{projectName}}');
12
+ });
6
13
  });
@@ -10,6 +10,9 @@ export class EventHelper {
10
10
  element.querySelectorAll('[data-event]').forEach(el => {
11
11
  const parts = el.getAttribute('data-event')!.split(':');
12
12
  const [eventName, handlerName, argsString] = parts;
13
+
14
+ if (!handlerName || !eventName) return;
15
+
13
16
  const handler = (component as any)[handlerName];
14
17
 
15
18
  if (typeof handler === 'function') {
@@ -28,6 +28,9 @@ export class PipeHelper {
28
28
 
29
29
  for (const pipe of pipes) {
30
30
  const [pipeName, ...argParts] = pipe.split(':');
31
+
32
+ if(!pipeName) return;
33
+
31
34
  const args = argParts.length ? argParts.join(':').split(',') : [];
32
35
  value = AppPipeRegistry.apply(pipeName.trim(), value, args.map(a => a.trim()));
33
36
  }
@@ -53,9 +53,9 @@ export class TokenHelper {
53
53
  }
54
54
 
55
55
  private static resolvePrefixed(token: string, el: HTMLElement): any | null {
56
- for (const prefix in PREFIXED_TOKEN_RESOLVERS) {
56
+ for (const [prefix, resolver] of Object.entries(PREFIXED_TOKEN_RESOLVERS)) {
57
57
  if (token.startsWith(prefix)) {
58
- return PREFIXED_TOKEN_RESOLVERS[prefix](token.slice(prefix.length), el);
58
+ return resolver(token.slice(prefix.length), el);
59
59
  }
60
60
  }
61
61
  return null;
@@ -68,11 +68,11 @@ export class NavigationManager {
68
68
 
69
69
  const params: Record<string, string> = {};
70
70
  paramNames.forEach((name, i) => {
71
- if (match[i + 1]) {
72
- params[name] = match[i + 1];
71
+ const value = match[i + 1];
72
+ if (value) {
73
+ params[name] = value;
73
74
  }
74
75
  });
75
-
76
76
  return params;
77
77
  }
78
78
  }
@@ -58,12 +58,12 @@ export class RouteGuardsManager {
58
58
  const guardResult = await this.runGuards(guards, params);
59
59
 
60
60
  if (guardResult === false) {
61
- console.log(`Navigation to ${targetPath} blocked by route guard`);
61
+ // console.log(`Navigation to ${targetPath} blocked by route guard`);
62
62
  return { allowed: false };
63
63
  }
64
64
 
65
65
  if (typeof guardResult === 'string') {
66
- console.log(`Navigation to ${targetPath} redirected to ${guardResult} by route guard`);
66
+ // console.log(`Navigation to ${targetPath} redirected to ${guardResult} by route guard`);
67
67
  return { allowed: false, redirectTo: guardResult };
68
68
  }
69
69
 
@@ -3,9 +3,9 @@ import { AttributesHelper, IAttributesConfig } from "../helpers/attributes.helpe
3
3
  /**
4
4
  * ```typescript
5
5
  interface IAttributesConfig {
6
- {{#if i18n}}
6
+ {{#if i18n}}
7
7
  i18nKey?: string;
8
- {{/if}}
8
+ {{/if}}
9
9
  textContent?: string;
10
10
  className?: string;
11
11
  style?: string;
@@ -15,14 +15,15 @@ interface IAttributesConfig {
15
15
  interface IAnchorConfig extends IAttributesConfig {
16
16
  // prefix href with '#' for internal anchor
17
17
  href: string;
18
- // _blank, etc
19
18
  target?: string;
19
+ tagName?: keyof HTMLElementTagNameMap;
20
20
  }
21
21
  ```
22
22
  */
23
23
  export interface IAnchorConfig extends IAttributesConfig {
24
24
  href: string;
25
25
  target?: string;
26
+ tagName?: keyof HTMLElementTagNameMap;
26
27
  }
27
28
 
28
29
  export class AnchorManager {
@@ -109,7 +109,7 @@ describe('ButtonManager', () => {
109
109
 
110
110
  it('should set button className correctly', () => {
111
111
  const mockComponent = {};
112
- const manager = new ButtonManager(mockComponent, [], options);
112
+ const manager = new ButtonManager(mockComponent, []);
113
113
  const btn = {
114
114
  i18nKey: 'save',
115
115
  className: 'btn-primary'
@@ -6,6 +6,7 @@
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <link rel="stylesheet" href="/main.css">
8
8
  <link rel="icon" type="image/x-icon" href="/favicon.ico" />
9
+ <title>{{projectName}}</title>
9
10
  </head>
10
11
  <body>
11
12
  <main id="app"></main>
@@ -14,4 +14,6 @@
14
14
  @use "custom-classes";
15
15
  // components styles
16
16
  @use "components";
17
+ // views styles
18
+ @use "views";
17
19
  {{/if}}