@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.
- package/CHANGELOG.md +63 -30
- package/README.md +9 -8
- package/lib/index.mjs +6 -6
- package/lib/src/file-generator.mjs +18 -5
- package/lib/src/package-manager.mjs +33 -21
- package/lib/src/prompts.mjs +2 -2
- package/lib/src/utils.mjs +2 -0
- package/package.json +4 -3
- package/templates/base/.dockerignore.hbs +2 -1
- package/templates/base/builder.config.js.hbs +28 -0
- package/templates/base/src/app/components/common/anchor/anchor.component.ts.hbs +11 -12
- package/templates/base/src/app/dom.test.js.hbs +8 -1
- package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +3 -0
- package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.ts.hbs +3 -0
- package/templates/base/src/core/base-classes/base-component/helpers/token.helper.ts.hbs +2 -2
- package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +3 -3
- package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.ts.hbs +2 -2
- package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.ts.hbs +4 -3
- package/templates/base/src/core/utils/button-manager/button-manager.utils.test.js.hbs +1 -1
- package/templates/base/src/index.html.hbs +1 -0
- package/templates/base/src/styles/main.scss.hbs +2 -0
- package/templates/base/tools/builder/builder.js.hbs +10 -7
- package/templates/base/tools/builder/core/copy-static.js.hbs +27 -2
- package/templates/base/tools/builder/core/esbuild.js.hbs +5 -15
- package/templates/base/tools/builder/core/finalize-build.js.hbs +2 -2
- package/templates/base/tools/builder/core/minify-html.js.hbs +2 -2
- package/templates/base/tools/builder/core/{sass.config.js.hbs → sass.js.hbs} +5 -7
- package/templates/base/tools/builder/core/tsc.js.hbs +8 -4
- package/templates/base/tools/builder/core/validate-html.js.hbs +19 -14
- package/templates/base/tools/deployment/Dockerfile.hbs +22 -0
- package/templates/base/tools/deployment/gzip.conf.hbs +19 -0
- package/templates/base/tools/deployment/nginx.conf.hbs +78 -0
- package/templates/base/tools/dev/dev-serve.js.hbs +32 -0
- package/templates/base/tools/{watcher.js.hbs → dev/watcher.js.hbs} +1 -1
- package/templates/base/tools/generator/generator.js.hbs +13 -5
- package/templates/base/tools/generator/handle-file.js.hbs +15 -8
- package/templates/base/tools/utils/export-flags.js.hbs +2 -1
- package/templates/base/tools/utils/index.js.hbs +4 -1
- package/templates/base/tools/utils/run-command.js.hbs +10 -3
- package/templates/base/tools/utils/run-script.js.hbs +1 -1
- package/templates/features/i18n/src/core/config.ts.hbs +3 -0
- package/templates/features/i18n/src/core/services/i18n/i18n.test.js.hbs +2 -0
- package/templates/features/i18n/src/core/services/i18n/i18n.ts.hbs +10 -1
- package/templates/features/i18n/src/core/services/i18n/languages.ts.hbs +7 -0
- package/templates/features/i18n/tools/builder/core/build-i18n.js.hbs +2 -2
- package/templates/features/i18n/tools/utils/languages.js.hbs +7 -0
- package/templates/features/stylinNutin/src/styles/components/_index.scss.hbs +0 -3
- package/templates/features/stylinNutin/src/styles/views/_index.scss.hbs +0 -0
- package/templates/features/template/tools/builder/core/merge-templates.js.hbs +1 -1
- package/templates/features/testinNutin/{test → testin-nutin}/core/index.js.hbs +1 -1
- package/templates/features/testinNutin/testin-nutin/core/src/globals/assertion-lib.js.hbs +136 -0
- package/templates/features/testinNutin/testin-nutin/core/src/globals/jsdom-setup.js.hbs +101 -0
- package/templates/features/testinNutin/{test → testin-nutin}/core/src/globals/register-test-globals.js.hbs +15 -1
- package/templates/features/testinNutin/testin-nutin/core/src/globals/spyon.js.hbs +59 -0
- package/templates/features/testinNutin/{test → testin-nutin}/core/src/utils/print.js.hbs +3 -1
- package/templates/features/testinNutin/{test → testin-nutin}/core/src/utils/test-discovery.js.hbs +5 -1
- package/templates/features/testinNutin/{test → testin-nutin}/core/src/utils/test-queue.js.hbs +3 -1
- package/templates/features/testinNutin/{test → testin-nutin}/core/tests/assertions.test.js.hbs +26 -2
- package/templates/features/testinNutin/testin-nutin/runner.js.hbs +35 -0
- package/templates/features/testinNutin/{test → testin-nutin}/watch-tests.js.hbs +1 -1
- package/templates/features/testinNutin/testin-nutin.config.js.hbs +14 -0
- package/templates/base/Dockerfile.hbs +0 -33
- package/templates/base/nginx.conf.hbs +0 -43
- package/templates/base/tools/builder/docs.md.hbs +0 -9
- package/templates/base/tools/dev.js.hbs +0 -43
- package/templates/features/testinNutin/test/core/src/globals/assertion-lib.js.hbs +0 -118
- package/templates/features/testinNutin/test/core/src/globals/spyon.js.hbs +0 -22
- package/templates/features/testinNutin/test/core/src/jsdom-setup.js.hbs +0 -12
- package/templates/features/testinNutin/test/docs.md.hbs +0 -39
- package/templates/features/testinNutin/test/runner.js.hbs +0 -36
- /package/templates/base/tools/{serve.js.hbs → dev/serve.js.hbs} +0 -0
- /package/templates/features/testinNutin/{test → testin-nutin}/core/src/utils/queue.js.hbs +0 -0
- /package/templates/features/testinNutin/{test → testin-nutin}/core/tests/globals-each-all.test.js.hbs +0 -0
- /package/templates/features/testinNutin/{test → testin-nutin}/mocks/create-mock-method.js.hbs +0 -0
- /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-event-bus.js.hbs +0 -0
- /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-http-client.js.hbs +0 -0
- /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-i18n.js.hbs +0 -0
- /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-router.js.hbs +0 -0
- /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-store.js.hbs +0 -0
|
@@ -3,25 +3,28 @@
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { print, isProd, runScript } from '../utils/index.js';
|
|
5
5
|
|
|
6
|
-
const scriptsDir = path.
|
|
6
|
+
const scriptsDir = path.join(process.cwd(),'tools', 'builder', 'core');
|
|
7
7
|
|
|
8
|
-
print.
|
|
8
|
+
if (!isProd) print.boldInfo('For production, use build:prod.\n');
|
|
9
|
+
print.boldHead(`Starting build...\n`);
|
|
9
10
|
|
|
10
11
|
runScript(path.join(scriptsDir, 'copy-static.js'), 'Copying files...');
|
|
12
|
+
|
|
11
13
|
if(!isProd) runScript(path.join(scriptsDir, 'tsc.js'), 'Running TypeScript compiler...');
|
|
12
14
|
|
|
13
15
|
{{#if template}}
|
|
14
|
-
runScript(path.join(scriptsDir, 'merge-templates.js'), 'Merging HTML templates in
|
|
16
|
+
runScript(path.join(scriptsDir, 'merge-templates.js'), 'Merging HTML templates in temp files...');
|
|
15
17
|
{{else}}
|
|
16
18
|
runScript(path.join(scriptsDir, 'minify-html.js'), 'Minifying inline templates...');
|
|
17
19
|
{{/if}}
|
|
18
|
-
|
|
20
|
+
{{#if i18n}}
|
|
19
21
|
runScript(path.join(scriptsDir, 'build-i18n.js'), 'Combining locales for production...');
|
|
20
|
-
|
|
22
|
+
{{/if}}
|
|
23
|
+
runScript(path.join(scriptsDir, 'sass.js'), 'Compiling styles from main.scss...');
|
|
21
24
|
runScript(path.join(scriptsDir, 'validate-html.js'), 'Validating tags in index.html...');
|
|
25
|
+
|
|
22
26
|
if (isProd) runScript(path.join(scriptsDir, 'esbuild.js'), 'Running esbuild...');
|
|
27
|
+
|
|
23
28
|
runScript(path.join(scriptsDir, 'finalize-build.js'), 'Finalizing build...')
|
|
24
29
|
|
|
25
30
|
print.boldSuccess(`\nBuild successful!\n`);
|
|
26
|
-
|
|
27
|
-
if (!isProd) print.boldInfo('For production, use build:prod.');
|
|
@@ -20,20 +20,43 @@ async function copyFavicon() {
|
|
|
20
20
|
|
|
21
21
|
try {
|
|
22
22
|
await copyFile(srcPath, destPath);
|
|
23
|
-
if (isVerbose) print.info('
|
|
23
|
+
if (isVerbose) print.info('Copied: favicon.ico');
|
|
24
24
|
} catch {
|
|
25
25
|
print.boldError('⚠️ No favicon found at public/favicon.ico');
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
{{#if i18n}}
|
|
30
|
+
async function copyConfig() {
|
|
31
|
+
const srcPath = path.resolve(path.join('config'));
|
|
32
|
+
const temp = isProd ? PATHS.temp : PATHS.tempSource;
|
|
33
|
+
const destPath = path.join(temp, 'config');
|
|
34
|
+
try {
|
|
35
|
+
await copyJsonFiles(srcPath, destPath);
|
|
36
|
+
{{#if testinNutin}}
|
|
37
|
+
// for tests to run
|
|
38
|
+
if (!isProd) await copyJsonFiles(srcPath, path.join(PATHS.temp, 'config'))
|
|
39
|
+
{{/if}}
|
|
40
|
+
if (isVerbose) print.info('Config files copied');
|
|
41
|
+
} catch {
|
|
42
|
+
print.boldError('Issue when copying config files.');
|
|
43
|
+
exit(1);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
{{/if}}
|
|
29
48
|
async function copyStatic() {
|
|
30
49
|
const extensions = [...BINARY_EXTENSIONS, 'html'];
|
|
31
50
|
if (isProd) extensions.push('.ts');
|
|
32
51
|
|
|
33
|
-
print.info(`File types that will be copied: ${Array.from(extensions)}`);
|
|
52
|
+
if (isVerbose) print.info(`File types that will be copied: ${Array.from(extensions)}`);
|
|
34
53
|
|
|
35
54
|
await ensureDir(PATHS.tempSource);
|
|
55
|
+
|
|
36
56
|
await copyFavicon();
|
|
57
|
+
{{#if i18n}}
|
|
58
|
+
await copyConfig();
|
|
59
|
+
{{/if}}
|
|
37
60
|
|
|
38
61
|
for (const extension of extensions) {
|
|
39
62
|
const files = await getFilesRecursive(PATHS.source, extension);
|
|
@@ -53,6 +76,8 @@ async function copyStatic() {
|
|
|
53
76
|
}
|
|
54
77
|
|
|
55
78
|
if (isProd) await copyJsonFiles(PATHS.source, PATHS.tempSource);
|
|
79
|
+
|
|
80
|
+
if (isVerbose) print.boldInfo(`Copy complete.\n`);
|
|
56
81
|
}
|
|
57
82
|
|
|
58
83
|
async function copyJsonFiles(sourceDir, destDir) {
|
|
@@ -1,35 +1,25 @@
|
|
|
1
1
|
import esbuild from 'esbuild';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { exit } from 'process';
|
|
4
|
-
import { print } from '../../utils/index.js';
|
|
4
|
+
import { print, isVerbose } from '../../utils/index.js';
|
|
5
5
|
import { PATHS } from './paths.js';
|
|
6
|
+
import builderConfig from '../../../builder.config.js';
|
|
6
7
|
|
|
7
8
|
const ENTRY_FILE = path.join(PATHS.tempApp, 'main.ts');
|
|
8
9
|
const OUT_FILE = path.join(PATHS.tempSource, 'bundle.js');
|
|
9
10
|
|
|
10
11
|
async function build() {
|
|
11
12
|
await esbuild.build({
|
|
13
|
+
...builderConfig.esbuild,
|
|
12
14
|
entryPoints: [ENTRY_FILE],
|
|
13
15
|
outfile: OUT_FILE,
|
|
14
|
-
bundle: true,
|
|
15
|
-
minify: true,
|
|
16
16
|
keepNames: true, // View names are used to update meta.title
|
|
17
|
-
sourcemap: false,
|
|
18
|
-
platform: 'browser',
|
|
19
|
-
format: 'esm',
|
|
20
|
-
target: ['es2015'],
|
|
21
|
-
legalComments: 'none',
|
|
22
|
-
loader: {
|
|
23
|
-
'.json': 'json',
|
|
24
|
-
},
|
|
25
|
-
define: {
|
|
26
|
-
'process.env.NODE_ENV': '"production"',
|
|
27
|
-
},
|
|
28
17
|
});
|
|
29
18
|
|
|
30
|
-
print.
|
|
19
|
+
if (isVerbose) print.boldInfo(`ESBuild complete.\n`);
|
|
31
20
|
}
|
|
32
21
|
|
|
22
|
+
|
|
33
23
|
build().catch((err) => {
|
|
34
24
|
print.boldError(`ESBuild failed: ${err.message}`);
|
|
35
25
|
exit(1);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { print, isProd } from "../../utils/index.js";
|
|
1
|
+
import { print, isProd, isVerbose } from "../../utils/index.js";
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { PATHS } from "./paths.js";
|
|
@@ -18,7 +18,7 @@ function replaceDir(src, dest) {
|
|
|
18
18
|
function finalizeBuild() {
|
|
19
19
|
if (isProd) removeFoldersAfterBundle();
|
|
20
20
|
replaceDir(PATHS.temp, PATHS.build);
|
|
21
|
-
print.
|
|
21
|
+
if (isVerbose) print.boldInfo('Build finalized.\n');
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
finalizeBuild();
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { minify } from 'html-minifier-terser';
|
|
2
2
|
{{#unless template}}
|
|
3
|
-
import path from 'path';
|
|
4
3
|
import fs from 'fs';
|
|
5
4
|
import { exit } from 'process';
|
|
6
5
|
import { getFilesRecursive, print, isProd, isVerbose } from '../../utils/index.js';
|
|
@@ -50,11 +49,12 @@ async function minifyInlineHTML() {
|
|
|
50
49
|
if (isVerbose) print.info(`Minified inline HTML in: ${file}`);
|
|
51
50
|
}
|
|
52
51
|
}
|
|
53
|
-
print.
|
|
52
|
+
if (isVerbose) print.boldInfo('Inline templates minified.\n');
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
minifyInlineHTML().catch((err) => {
|
|
57
56
|
print.boldError(`Unexpected error: ${err.message}`);
|
|
58
57
|
exit(1);
|
|
59
58
|
});
|
|
59
|
+
|
|
60
60
|
{{/unless}}
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
import * as sass from 'sass';
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { print } from '../../utils/index.js';
|
|
4
|
+
import { isVerbose, print } from '../../utils/index.js';
|
|
5
5
|
import { PATHS } from './paths.js';
|
|
6
|
+
import builderConfig from '../../../builder.config.js';
|
|
6
7
|
|
|
7
8
|
const stylesInput = path.join(PATHS.source, 'styles');
|
|
8
9
|
const stylesOutput = path.join(PATHS.tempSource, 'main.css');
|
|
9
10
|
|
|
10
11
|
const scssPath = (origin) => path.join(stylesInput, origin);
|
|
11
|
-
const scssOrigins =
|
|
12
|
-
|
|
13
|
-
'core'{{#if stylinNutin}},
|
|
14
|
-
'components'{{/if}}
|
|
15
|
-
];
|
|
12
|
+
const scssOrigins = builderConfig.sass.paths;
|
|
13
|
+
|
|
16
14
|
const pathsToLoad = scssOrigins.map(origin => scssPath(origin));
|
|
17
15
|
|
|
18
16
|
sass.compileAsync(path.join(stylesInput, 'main.scss'), {
|
|
@@ -20,5 +18,5 @@ sass.compileAsync(path.join(stylesInput, 'main.scss'), {
|
|
|
20
18
|
style: 'compressed'
|
|
21
19
|
}).then(result => {
|
|
22
20
|
fs.writeFileSync(stylesOutput, result.css);
|
|
23
|
-
print.
|
|
21
|
+
if (isVerbose) print.boldInfo('Global styles compiled.\n');
|
|
24
22
|
});
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import { runCommand, print } from "../../utils/index.js";
|
|
1
|
+
import { runCommand, print, isVerbose } from "../../utils/index.js";
|
|
2
|
+
import { exit } from 'process';
|
|
2
3
|
|
|
3
4
|
async function compileTS() {
|
|
4
|
-
runCommand('tsc --project tsconfig.json');
|
|
5
|
-
print.
|
|
5
|
+
await runCommand('tsc', ['--project', 'tsconfig.json']);
|
|
6
|
+
if (isVerbose) print.boldInfo('TypeScript compiled.\n');
|
|
6
7
|
}
|
|
7
8
|
|
|
8
|
-
compileTS()
|
|
9
|
+
compileTS().catch((err) => {
|
|
10
|
+
print.boldError(`Unexpected error: ${err.message}`);
|
|
11
|
+
exit(1);
|
|
12
|
+
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { readFile, writeFile } from 'fs/promises';
|
|
2
|
-
import { JSDOM } from 'jsdom';
|
|
3
2
|
import { exit } from 'process';
|
|
4
|
-
import { print, isProd } from '../../utils/index.js';
|
|
3
|
+
import { print, isProd, isVerbose } from '../../utils/index.js';
|
|
5
4
|
import { PATHS } from './paths.js';
|
|
6
5
|
import path from 'path';
|
|
7
6
|
|
|
@@ -11,21 +10,23 @@ function errorExit(message) {
|
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
async function addEntrypoint(htmlContent, filePath) {
|
|
14
|
-
print.info('Adding script in HTML...');
|
|
13
|
+
if (isVerbose) print.info('Adding script in HTML...');
|
|
15
14
|
|
|
16
|
-
const
|
|
17
|
-
const
|
|
15
|
+
const scriptSrc = isProd ? "bundle.js" : "app/main.js";
|
|
16
|
+
const scriptTag = `<script type="module" src="/${scriptSrc}"></script>`;
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
let modifiedHtml;
|
|
19
|
+
if (/<\/body>/i.test(htmlContent)) {
|
|
20
|
+
modifiedHtml = htmlContent.replace(/<\/body>/i, `${scriptTag}</body>`);
|
|
21
|
+
} else {
|
|
22
|
+
modifiedHtml = htmlContent + scriptTag;
|
|
23
|
+
}
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
const serialized = dom.serialize().replace(/>\s+</g, '><')
|
|
25
|
+
const minified = modifiedHtml.replace(/>\s+</g, '><');
|
|
25
26
|
|
|
26
|
-
writeFile(filePath,
|
|
27
|
+
writeFile(filePath, minified, "utf8");
|
|
27
28
|
|
|
28
|
-
return
|
|
29
|
+
return modifiedHtml;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
async function validateHtml() {
|
|
@@ -46,9 +47,13 @@ async function validateHtml() {
|
|
|
46
47
|
errorExit('No stylesheet found in HTML');
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
await addEntrypoint(htmlContent, filePath);
|
|
50
|
+
htmlContent = await addEntrypoint(htmlContent, filePath).catch(err => errorExit(err.message));
|
|
51
|
+
|
|
52
|
+
if (!/<script[^>]*type=["']module["']/.test(htmlContent)) {
|
|
53
|
+
errorExit('No module scripts found in HTML');
|
|
54
|
+
}
|
|
50
55
|
|
|
51
|
-
print.
|
|
56
|
+
if (isVerbose) print.boldInfo('HTML validation passed\n');
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
validateHtml();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
ARG NODE_VERSION=22.3.0
|
|
2
|
+
ARG NGINX_VERSION=1.27-alpine
|
|
3
|
+
|
|
4
|
+
FROM node:${NODE_VERSION} AS builder
|
|
5
|
+
WORKDIR /usr/src/app
|
|
6
|
+
COPY . .
|
|
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/tools/deployment/nginx.conf /etc/nginx/conf.d/default.conf
|
|
14
|
+
COPY --from=builder /usr/src/app/tools/deployment/gzip.conf /etc/nginx/conf.d/gzip.conf
|
|
15
|
+
COPY --from=builder /usr/src/app/dist/src /usr/share/nginx/html
|
|
16
|
+
|
|
17
|
+
EXPOSE 9090
|
|
18
|
+
|
|
19
|
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
|
20
|
+
CMD curl -f http://localhost:9090/health || exit 1
|
|
21
|
+
|
|
22
|
+
CMD ["nginx", "-g", "daemon off;"]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
gzip on;
|
|
2
|
+
gzip_vary on;
|
|
3
|
+
gzip_proxied any;
|
|
4
|
+
gzip_comp_level 6;
|
|
5
|
+
gzip_min_length 1000;
|
|
6
|
+
|
|
7
|
+
gzip_types
|
|
8
|
+
text/plain
|
|
9
|
+
text/css
|
|
10
|
+
text/xml
|
|
11
|
+
text/javascript
|
|
12
|
+
application/json
|
|
13
|
+
application/javascript
|
|
14
|
+
application/xml+rss
|
|
15
|
+
application/rss+xml
|
|
16
|
+
font/truetype
|
|
17
|
+
font/opentype
|
|
18
|
+
application/vnd.ms-fontobject
|
|
19
|
+
image/svg+xml;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
server {
|
|
2
|
+
# ---------------------------
|
|
3
|
+
# LISTEN PORTS
|
|
4
|
+
# ---------------------------
|
|
5
|
+
listen 9090;
|
|
6
|
+
server_name _;
|
|
7
|
+
|
|
8
|
+
# ---------------------------
|
|
9
|
+
# ROOT + ROUTING
|
|
10
|
+
# ---------------------------
|
|
11
|
+
root /usr/share/nginx/html;
|
|
12
|
+
index index.html;
|
|
13
|
+
|
|
14
|
+
# SPA routing: serve index.html for unknown paths
|
|
15
|
+
location / {
|
|
16
|
+
try_files $uri $uri/ /index.html;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
# ---------------------------
|
|
20
|
+
# SECURITY HEADERS
|
|
21
|
+
# ---------------------------
|
|
22
|
+
|
|
23
|
+
# Prevent clickjacking
|
|
24
|
+
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
25
|
+
|
|
26
|
+
# Block MIME type sniffing
|
|
27
|
+
add_header X-Content-Type-Options "nosniff" always;
|
|
28
|
+
|
|
29
|
+
# Legacy XSS filter
|
|
30
|
+
add_header X-XSS-Protection "1; mode=block" always;
|
|
31
|
+
|
|
32
|
+
# Limit referrer leakage
|
|
33
|
+
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
34
|
+
|
|
35
|
+
# Restrict powerful browser APIs
|
|
36
|
+
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
|
|
37
|
+
|
|
38
|
+
# Content Security Policy
|
|
39
|
+
add_header Content-Security-Policy "default-src 'self'; \
|
|
40
|
+
script-src 'self'; \
|
|
41
|
+
style-src 'self' 'unsafe-inline'; \
|
|
42
|
+
img-src 'self' data: https:; \
|
|
43
|
+
font-src 'self'; \
|
|
44
|
+
connect-src 'self'; \
|
|
45
|
+
object-src 'none'; \
|
|
46
|
+
base-uri 'self'; \
|
|
47
|
+
form-action 'self'; \
|
|
48
|
+
frame-ancestors 'none';" always;
|
|
49
|
+
|
|
50
|
+
# Hide nginx version
|
|
51
|
+
server_tokens off;
|
|
52
|
+
|
|
53
|
+
# ---------------------------
|
|
54
|
+
# STATIC ASSETS CACHING
|
|
55
|
+
# ---------------------------
|
|
56
|
+
|
|
57
|
+
# Immutable cache for static assets
|
|
58
|
+
location ~* \.(js|css|png|jpg|jpeg|gif|avif|ico|svg|woff|woff2|ttf|eot)$ {
|
|
59
|
+
expires 1y;
|
|
60
|
+
add_header Cache-Control "public, immutable";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# No caching for HTML files (SPA shell)
|
|
64
|
+
location ~* \.html$ {
|
|
65
|
+
expires -1;
|
|
66
|
+
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
|
67
|
+
add_header Pragma "no-cache";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# ---------------------------
|
|
71
|
+
# HEALTH ENDPOINT
|
|
72
|
+
# ---------------------------
|
|
73
|
+
location /health {
|
|
74
|
+
access_log off;
|
|
75
|
+
add_header Content-Type text/plain;
|
|
76
|
+
return 200 "healthy\n";
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { print, runCommand } from '../utils/index.js';
|
|
4
|
+
|
|
5
|
+
async function startDev() {
|
|
6
|
+
try {
|
|
7
|
+
console.clear();
|
|
8
|
+
print.boldHead('🚀 Starting Dev Environment...');
|
|
9
|
+
|
|
10
|
+
await runCommand('{{packageManager}}', ['run', 'build']);
|
|
11
|
+
|
|
12
|
+
const { serve, watcher } = await Promise.all([
|
|
13
|
+
runCommand('npm', ['run', 'serve:only']),
|
|
14
|
+
runCommand('node', ['tools/dev/watcher.js'])
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
serve.on('close', (code) => {
|
|
18
|
+
print.error(`live-server exited with code ${code}`);
|
|
19
|
+
process.exit(code);
|
|
20
|
+
});
|
|
21
|
+
watcher.on('close', (code) => {
|
|
22
|
+
print.error(`watcher exited with code ${code}`);
|
|
23
|
+
});
|
|
24
|
+
} catch (err) {
|
|
25
|
+
print.boldError(`Dev startup failed: ${err.message}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
startDev().catch((err) => {
|
|
30
|
+
print.boldError(`Unexpected error: ${err.message}`);
|
|
31
|
+
exit(1);
|
|
32
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import path from "path";
|
|
3
4
|
import { allFormats, getLastWord, print{{#if stylinNutin}}, promptBoolean{{/if}} } from "../utils/index.js";
|
|
4
5
|
import { generateFile, appendToIndex{{#if i18n}}, generateJson{{/if}}{{#if stylinNutin}}, generateStylesheet{{/if}} } from "./handle-file.js";
|
|
5
6
|
import { serviceTemplate, componentTemplate, viewTemplate{{#if template}}, htmlTemplate{{/if}} } from "./templates/index.js";
|
|
@@ -14,7 +15,7 @@ if (!rawType || !rawFullPath) {
|
|
|
14
15
|
const type = allFormats(rawType);
|
|
15
16
|
const fullPath = allFormats(rawFullPath);
|
|
16
17
|
const name = allFormats(getLastWord(fullPath.kebab));
|
|
17
|
-
const targetPath =
|
|
18
|
+
const targetPath = path.join('src', 'app', `${type.kebab}s/${fullPath.kebab}`);
|
|
18
19
|
|
|
19
20
|
// Creator Mapping
|
|
20
21
|
const creators = {
|
|
@@ -27,7 +28,7 @@ const creators = {
|
|
|
27
28
|
handleError("Failed to generate service", err);
|
|
28
29
|
}
|
|
29
30
|
},
|
|
30
|
-
component: (name, targetPath) => {
|
|
31
|
+
component: {{#if stylinNutin}}async {{/if}}(name, targetPath) => {
|
|
31
32
|
print.section(`Creating component: ${name.capitalized}`);
|
|
32
33
|
{{#if stylinNutin}}
|
|
33
34
|
const doCreateStylesheet = await promptBoolean('Create component\'s stylesheet in styles/components ?');
|
|
@@ -42,15 +43,19 @@ const creators = {
|
|
|
42
43
|
generateJson({ targetPath, name });
|
|
43
44
|
{{/if}}
|
|
44
45
|
{{#if stylinNutin}}
|
|
45
|
-
if (doCreateStylesheet) generateStylesheet(name);
|
|
46
|
+
if (doCreateStylesheet) generateStylesheet({ name, suffix: 'component' });
|
|
46
47
|
{{/if}}
|
|
47
48
|
appendToIndex({ name, targetPath, suffix: 'component' });
|
|
48
49
|
} catch (err) {
|
|
49
50
|
handleError("Failed to generate component", err);
|
|
50
51
|
}
|
|
51
52
|
},
|
|
52
|
-
view: (name, targetPath) => {
|
|
53
|
+
view: {{#if stylinNutin}}async {{/if}}(name, targetPath) => {
|
|
53
54
|
print.section(`Creating view: ${name.capitalized}`);
|
|
55
|
+
{{#if stylinNutin}}
|
|
56
|
+
const doCreateStylesheet = await promptBoolean('Create component\'s stylesheet in styles/components ?');
|
|
57
|
+
{{/if}}
|
|
58
|
+
|
|
54
59
|
try {
|
|
55
60
|
generateFile({ name, targetPath, templateFn: viewTemplate, suffix: 'view' });
|
|
56
61
|
{{#if template}}
|
|
@@ -59,6 +64,9 @@ const creators = {
|
|
|
59
64
|
{{#if i18n}}
|
|
60
65
|
generateJson({ targetPath, name });
|
|
61
66
|
{{/if}}
|
|
67
|
+
{{#if stylinNutin}}
|
|
68
|
+
if (doCreateStylesheet) generateStylesheet({ name, suffix: 'view' });
|
|
69
|
+
{{/if}}
|
|
62
70
|
appendToIndex({name, targetPath, suffix: 'view' });
|
|
63
71
|
} catch (err) {
|
|
64
72
|
handleError("Failed to generate view", err);
|
|
@@ -70,7 +78,7 @@ const creators = {
|
|
|
70
78
|
const create = creators[type.kebab];
|
|
71
79
|
|
|
72
80
|
if (create) {
|
|
73
|
-
create(name, targetPath);
|
|
81
|
+
{{#if stylinNutin}}await {{/if}}create(name, targetPath);
|
|
74
82
|
print.boldSuccess(`\n${type.capitalized} ${name.capitalized} has been generated.\n`)
|
|
75
83
|
} else {
|
|
76
84
|
showUsageAndExit(`Unsupported type: '${type.kebab}'`);
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { print } from '../utils/index.js';
|
|
3
|
+
import { print{{#if i18n}}, LANGUAGES{{/if}} } from '../utils/index.js';
|
|
4
4
|
{{#if i18n}}
|
|
5
|
-
import { LANGUAGES } from '../../src/app/languages.js';
|
|
6
5
|
import { jsonTemplate } from './templates/index.js';
|
|
7
6
|
{{/if}}
|
|
7
|
+
{{#if stylinNutin}}
|
|
8
|
+
import { scssTemplate } from './templates/index.js';
|
|
9
|
+
{{/if}}
|
|
8
10
|
|
|
9
11
|
export function generateFile({
|
|
10
12
|
name,
|
|
@@ -61,19 +63,24 @@ export function generateJson({ targetPath, name }) {
|
|
|
61
63
|
|
|
62
64
|
{{/if}}
|
|
63
65
|
{{#if stylinNutin}}
|
|
64
|
-
export function generateStylesheet(name) {
|
|
66
|
+
export function generateStylesheet({ name, suffix }) {
|
|
65
67
|
const template = scssTemplate();
|
|
66
|
-
const
|
|
67
|
-
const filePath = path.join(
|
|
68
|
-
const indexPath = path.join(
|
|
69
|
-
|
|
68
|
+
const stylesPath = path.join('src', 'styles', `${suffix}s`);
|
|
69
|
+
const filePath = path.join(stylesPath, `_${name.kebab}.${suffix}.scss`);
|
|
70
|
+
const indexPath = path.join(stylesPath, `_index.scss`);
|
|
71
|
+
|
|
72
|
+
if (!fs.existsSync(stylesPath) || !fs.existsSync(indexPath)) {
|
|
73
|
+
print.boldError('Non existing path');
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
70
76
|
if (fs.existsSync(filePath)) {
|
|
71
77
|
print.boldError('A file with this name already exists');
|
|
72
78
|
process.exit(1);
|
|
73
79
|
}
|
|
80
|
+
|
|
74
81
|
fs.writeFileSync(filePath, template);
|
|
75
82
|
|
|
76
|
-
const lineToAppend = `@forward "${name.kebab}";\n`;
|
|
83
|
+
const lineToAppend = `@forward "${name.kebab}.${suffix}";\n`;
|
|
77
84
|
|
|
78
85
|
try {
|
|
79
86
|
fs.appendFileSync(indexPath, lineToAppend, 'utf8');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import process from 'process';
|
|
2
|
+
import builderConfig from '../../builder.config.js';
|
|
2
3
|
|
|
3
4
|
export const isProd = process.env.npm_config_bundle || process.env.NODE_ENV === 'production';
|
|
4
5
|
|
|
5
|
-
export const isVerbose = process.env.npm_config_log;
|
|
6
|
+
export const isVerbose = process.env.npm_config_log || builderConfig.verbose;
|
|
@@ -4,4 +4,7 @@ export * from './get-files-recursive.js';
|
|
|
4
4
|
export * from './run-command.js';
|
|
5
5
|
export * from './run-script.js';
|
|
6
6
|
export * from './export-flags.js';
|
|
7
|
-
export * from './prompt-boolean.js';
|
|
7
|
+
export * from './prompt-boolean.js';
|
|
8
|
+
{{#if i18n}}
|
|
9
|
+
export * from './languages.js';
|
|
10
|
+
{{/if}}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
|
|
3
|
+
export function runCommand(command, args = []) {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
const child = spawn(command, args, { stdio: 'inherit', shell: true });
|
|
6
|
+
|
|
7
|
+
child.on('close', (code) => {
|
|
8
|
+
if (code === 0) resolve();
|
|
9
|
+
else reject(new Error(`${command} exited with code ${code}`));
|
|
10
|
+
});
|
|
4
11
|
});
|
|
5
12
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_LANGUAGE, LANGUAGES, Language, Translations } from '
|
|
1
|
+
import { DEFAULT_LANGUAGE, LANGUAGES, Language, Translations } from './languages.js';
|
|
2
2
|
import { Service } from '../../index.js';
|
|
3
3
|
|
|
4
4
|
export class I18n extends Service<I18n> {
|
|
@@ -11,6 +11,7 @@ export class I18n extends Service<I18n> {
|
|
|
11
11
|
constructor() {
|
|
12
12
|
super();
|
|
13
13
|
this.registerCleanup(this.resetTranslations);
|
|
14
|
+
this._currentLanguage = this.getBrowserLanguage();
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
private getBrowserLanguage() {
|
|
@@ -89,6 +90,14 @@ export class I18n extends Service<I18n> {
|
|
|
89
90
|
return this._currentLanguage;
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
public get defaultLanguage(): Language {
|
|
94
|
+
return this._DEFAULT_LANGUAGE;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public get languages(): Language[] {
|
|
98
|
+
return this._LANGUAGES;
|
|
99
|
+
}
|
|
100
|
+
|
|
92
101
|
public async initTranslations(): Promise<void> {
|
|
93
102
|
const browserLang = this.getBrowserLanguage();
|
|
94
103
|
const lang = this._LANGUAGES.includes(browserLang) ? browserLang : this._DEFAULT_LANGUAGE;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { CONFIG } from '../../config.js';
|
|
2
|
+
|
|
3
|
+
export const LANGUAGES = CONFIG.langs.languages;
|
|
4
|
+
export const DEFAULT_LANGUAGE = CONFIG.langs.defaultLanguage as (typeof LANGUAGES)[number];
|
|
5
|
+
|
|
6
|
+
export type Language = typeof LANGUAGES[number];
|
|
7
|
+
export type Translations = Record<string, any>;
|