@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.
- package/CHANGELOG.md +32 -2
- package/README.md +47 -1
- package/lib/index.mjs +6 -6
- package/lib/src/file-generator.mjs +7 -5
- package/lib/src/package-manager.mjs +54 -45
- package/lib/src/prompts.mjs +11 -11
- package/lib/src/utils.mjs +2 -1
- package/package.json +1 -1
- package/templates/base/.dockerignore.hbs +10 -1
- package/templates/base/Dockerfile.hbs +28 -8
- package/templates/base/README.md.hbs +23 -0
- package/templates/base/nginx.conf.hbs +43 -0
- package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +2 -1
- package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +11 -11
- package/templates/base/src/core/base-classes/base-component/helpers/token.helper.ts.hbs +3 -2
- package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +55 -27
- package/templates/base/src/core/services/http-client/http-client.ts.hbs +24 -17
- package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +25 -10
- package/templates/base/src/core/services/router/router.ts.hbs +1 -0
- package/templates/base/src/index.html.hbs +1 -2
- package/templates/base/src/libs/popover/popover.ts.hbs +2 -1
- package/templates/base/src/styles/main.scss.hbs +2 -1
- package/templates/base/tools/builder/builder.js.hbs +17 -27
- package/templates/base/tools/builder/core/copy-static.js.hbs +16 -20
- package/templates/base/tools/builder/core/esbuild.js.hbs +36 -0
- package/templates/base/tools/builder/core/finalize-build.js.hbs +24 -0
- package/templates/base/tools/builder/core/minify-html.js.hbs +60 -0
- package/templates/base/tools/builder/core/paths.js.hbs +19 -0
- package/templates/base/tools/builder/core/sass.config.js.hbs +16 -9
- package/templates/base/tools/builder/core/tsc.js.hbs +8 -0
- package/templates/base/tools/builder/core/validate-html.js.hbs +26 -11
- package/templates/base/tools/dev.js.hbs +43 -0
- package/templates/base/tools/generator/generator.js.hbs +9 -2
- package/templates/base/tools/generator/handle-file.js.hbs +23 -0
- package/templates/base/tools/generator/templates/index.js.hbs +3 -0
- package/templates/base/tools/serve.js.hbs +23 -0
- package/templates/base/tools/utils/export-flags.js.hbs +5 -0
- package/templates/base/tools/utils/get-files-recursive.js.hbs +1 -3
- package/templates/base/tools/utils/index.js.hbs +5 -1
- package/templates/base/tools/utils/prompt-boolean.js.hbs +15 -0
- package/templates/base/tools/utils/run-command.js.hbs +5 -0
- package/templates/base/tools/utils/run-script.js.hbs +22 -0
- package/templates/base/tools/watcher.js.hbs +7 -7
- package/templates/features/i18n/tools/builder/core/build-i18n.js.hbs +11 -16
- package/templates/features/stylinNutin/src/styles/_custom-classes.scss.hbs +1 -1
- package/templates/features/stylinNutin/src/styles/components/_index.scss.hbs +3 -0
- package/templates/features/stylinNutin/tools/generator/templates/scss.template.js.hbs +4 -0
- 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
|
-
|
|
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.
|
|
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', '
|
|
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.
|
|
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
|
|
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
|
-
|
|
27
|
-
const { testinNutin,
|
|
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":
|
|
42
|
-
"minor":
|
|
43
|
-
"major":
|
|
44
|
-
"build
|
|
45
|
-
"build
|
|
46
|
-
"serve": "
|
|
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
|
-
|
|
51
|
-
? {
|
|
52
|
-
...baseScripts,
|
|
56
|
+
const testinNutinScripts = {
|
|
53
57
|
"test": "node test/runner.js",
|
|
54
|
-
"test
|
|
55
|
-
"test:watch":
|
|
56
|
-
"serve":
|
|
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
|
-
|
|
63
|
+
return testinNutin
|
|
63
64
|
? {
|
|
64
|
-
...
|
|
65
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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":
|
|
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
|
},
|
package/lib/src/prompts.mjs
CHANGED
|
@@ -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 ||
|
|
38
|
-
i18n: cliOptions.i18n ||
|
|
39
|
-
|
|
40
|
-
|
|
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:
|
|
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
|
|
69
|
-
default:
|
|
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:
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
5
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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(
|
|
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}-${
|
|
59
|
-
el.dataset.index = String(
|
|
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(
|
|
64
|
-
const item = config.array[
|
|
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:
|
|
68
|
-
: { value: item, index:
|
|
67
|
+
? { ...(item as object), index: index }
|
|
68
|
+
: { value: item, index: index };
|
|
69
69
|
|
|
70
70
|
componentConfigs.push(
|
|
71
71
|
{
|
|
72
|
-
selector: `${config.elementName}-${
|
|
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
|
|
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
|
|
93
|
+
public static registerPrefixedToken(
|
|
93
94
|
prefix: string,
|
|
94
95
|
resolver: (suffix: string, el: HTMLElement) => any
|
|
95
96
|
) {
|