@nutin/cli 1.2.0 → 1.2.2

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 (80) hide show
  1. package/CHANGELOG.md +67 -30
  2. package/README.md +12 -9
  3. package/lib/index.mjs +6 -6
  4. package/lib/src/file-generator.mjs +19 -6
  5. package/lib/src/package-manager.mjs +36 -24
  6. package/lib/src/prompts.mjs +1 -1
  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 -5
  23. package/templates/base/tools/builder/core/copy-static.js.hbs +27 -2
  24. package/templates/base/tools/builder/core/esbuild.js.hbs +4 -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 +108 -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/views/_index.scss.hbs +0 -0
  48. package/templates/features/template/tools/builder/core/merge-templates.js.hbs +1 -1
  49. package/templates/features/testinNutin/{test → testin-nutin}/core/index.js.hbs +1 -1
  50. package/templates/features/testinNutin/testin-nutin/core/src/globals/assertion-lib.js.hbs +136 -0
  51. package/templates/features/testinNutin/testin-nutin/core/src/globals/jsdom-setup.js.hbs +101 -0
  52. package/templates/features/testinNutin/{test → testin-nutin}/core/src/globals/register-test-globals.js.hbs +15 -1
  53. package/templates/features/testinNutin/testin-nutin/core/src/globals/spyon.js.hbs +59 -0
  54. package/templates/features/testinNutin/{test → testin-nutin}/core/src/utils/print.js.hbs +3 -1
  55. package/templates/features/testinNutin/{test → testin-nutin}/core/src/utils/test-discovery.js.hbs +5 -1
  56. package/templates/features/testinNutin/{test → testin-nutin}/core/src/utils/test-queue.js.hbs +3 -1
  57. package/templates/features/testinNutin/{test → testin-nutin}/core/tests/assertions.test.js.hbs +26 -2
  58. package/templates/features/testinNutin/testin-nutin/runner.js.hbs +35 -0
  59. package/templates/features/testinNutin/{test → testin-nutin}/watch-tests.js.hbs +1 -1
  60. package/templates/features/testinNutin/testin-nutin.config.js.hbs +14 -0
  61. package/templates/base/Dockerfile.hbs +0 -33
  62. package/templates/base/nginx.conf.hbs +0 -43
  63. package/templates/base/tools/builder/docs.md.hbs +0 -9
  64. package/templates/base/tools/dev.js.hbs +0 -43
  65. package/templates/features/i18n/src/app/languages.js.hbs +0 -10
  66. package/templates/features/i18n/src/app/languages.ts.hbs +0 -7
  67. package/templates/features/testinNutin/test/core/src/globals/assertion-lib.js.hbs +0 -118
  68. package/templates/features/testinNutin/test/core/src/globals/spyon.js.hbs +0 -22
  69. package/templates/features/testinNutin/test/core/src/jsdom-setup.js.hbs +0 -12
  70. package/templates/features/testinNutin/test/docs.md.hbs +0 -39
  71. package/templates/features/testinNutin/test/runner.js.hbs +0 -36
  72. /package/templates/base/tools/{serve.js.hbs → dev/serve.js.hbs} +0 -0
  73. /package/templates/features/testinNutin/{test → testin-nutin}/core/src/utils/queue.js.hbs +0 -0
  74. /package/templates/features/testinNutin/{test → testin-nutin}/core/tests/globals-each-all.test.js.hbs +0 -0
  75. /package/templates/features/testinNutin/{test → testin-nutin}/mocks/create-mock-method.js.hbs +0 -0
  76. /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-event-bus.js.hbs +0 -0
  77. /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-http-client.js.hbs +0 -0
  78. /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-i18n.js.hbs +0 -0
  79. /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-router.js.hbs +0 -0
  80. /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-store.js.hbs +0 -0
@@ -3,23 +3,28 @@
3
3
  import path from 'path';
4
4
  import { print, isProd, runScript } from '../utils/index.js';
5
5
 
6
- const scriptsDir = path.resolve(process.cwd(),'tools', 'builder', 'core');
6
+ const scriptsDir = path.join(process.cwd(),'tools', 'builder', 'core');
7
7
 
8
- print.boldHead(`Starting build...`);
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 JavaScript files...');
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
- runScript(path.join(scriptsDir, 'sass.config.js'), 'Compiling styles from main.scss...');
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`);
@@ -20,20 +20,43 @@ async function copyFavicon() {
20
20
 
21
21
  try {
22
22
  await copyFile(srcPath, destPath);
23
- if (isVerbose) print.info('✅ Copied: favicon.ico');
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,33 +1,22 @@
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.info(`ESBuild complete.`);
19
+ if (isVerbose) print.boldInfo(`ESBuild complete.\n`);
31
20
  }
32
21
 
33
22
  build().catch((err) => {
@@ -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.info('Build finalized.');
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.info('Inline templates minified.');
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
- 'base',
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.info('Global styles compiled.');
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.info('TypeScript compiled.');
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 dom = new JSDOM(htmlContent);
17
- const document = dom.window.document;
15
+ const scriptSrc = isProd ? "bundle.js" : "app/main.js";
16
+ const scriptTag = `<script type="module" src="/${scriptSrc}"></script>`;
18
17
 
19
- const newScript = document.createElement("script");
20
- newScript.type = "module";
21
- newScript.src = isProd ? "/bundle.js" : "/app/main.js";
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
- document.body.appendChild(newScript);
24
- const serialized = dom.serialize().replace(/>\s+</g, '><')
25
+ const minified = modifiedHtml.replace(/>\s+</g, '><');
25
26
 
26
- writeFile(filePath, serialized, "utf8");
27
+ writeFile(filePath, minified, "utf8");
27
28
 
28
- return htmlContent;
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.info('HTML validation passed');
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,108 @@
1
+ # ---------------------------
2
+ # HEADER VARIABLES (define outside server block)
3
+ # ---------------------------
4
+ map $uri $frame_options {
5
+ default "SAMEORIGIN";
6
+ }
7
+
8
+ map $uri $content_type_options {
9
+ default "nosniff";
10
+ }
11
+
12
+ map $uri $xss_protection {
13
+ default "1; mode=block";
14
+ }
15
+
16
+ map $uri $referrer_policy {
17
+ default "strict-origin-when-cross-origin";
18
+ }
19
+
20
+ map $uri $permissions_policy {
21
+ default "geolocation=(), microphone=(), camera=()";
22
+ }
23
+
24
+ map $uri $csp_policy {
25
+ default "default-src 'self'; \
26
+ script-src 'self'; \
27
+ style-src 'self' 'unsafe-inline'; \
28
+ img-src 'self' data: https:; \
29
+ font-src 'self'; \
30
+ connect-src 'self'; \
31
+ object-src 'none'; \
32
+ base-uri 'self'; \
33
+ form-action 'self'; \
34
+ frame-ancestors 'none';";
35
+ }
36
+
37
+ server {
38
+ # ---------------------------
39
+ # LISTEN PORTS
40
+ # ---------------------------
41
+ listen 9090;
42
+ listen 9091;
43
+ server_name _;
44
+
45
+ # ---------------------------
46
+ # ROOT + ROUTING
47
+ # ---------------------------
48
+ root /usr/share/nginx/html;
49
+ index index.html;
50
+
51
+ # Hide nginx version
52
+ server_tokens off;
53
+
54
+ # SPA routing: serve index.html for unknown paths
55
+ location / {
56
+ try_files $uri $uri/ /index.html;
57
+
58
+ # Security headers
59
+ add_header X-Frame-Options $frame_options always;
60
+ add_header X-Content-Type-Options $content_type_options always;
61
+ add_header X-XSS-Protection $xss_protection always;
62
+ add_header Referrer-Policy $referrer_policy always;
63
+ add_header Permissions-Policy $permissions_policy always;
64
+ add_header Content-Security-Policy $csp_policy always;
65
+ }
66
+
67
+ # ---------------------------
68
+ # STATIC ASSETS CACHING
69
+ # ---------------------------
70
+
71
+ # Immutable cache for static assets
72
+ location ~* \.(js|css|png|jpg|jpeg|gif|avif|ico|svg|woff|woff2|ttf|eot)$ {
73
+ expires 1y;
74
+ add_header Cache-Control "public, immutable" always;
75
+
76
+ # Security headers
77
+ add_header X-Frame-Options $frame_options always;
78
+ add_header X-Content-Type-Options $content_type_options always;
79
+ add_header X-XSS-Protection $xss_protection always;
80
+ add_header Referrer-Policy $referrer_policy always;
81
+ add_header Permissions-Policy $permissions_policy always;
82
+ add_header Content-Security-Policy $csp_policy always;
83
+ }
84
+
85
+ # No caching for HTML files (SPA shell)
86
+ location ~* \.html$ {
87
+ expires -1;
88
+ add_header Cache-Control "no-cache, no-store, must-revalidate" always;
89
+ add_header Pragma "no-cache" always;
90
+
91
+ # Security headers
92
+ add_header X-Frame-Options $frame_options always;
93
+ add_header X-Content-Type-Options $content_type_options always;
94
+ add_header X-XSS-Protection $xss_protection always;
95
+ add_header Referrer-Policy $referrer_policy always;
96
+ add_header Permissions-Policy $permissions_policy always;
97
+ add_header Content-Security-Policy $csp_policy always;
98
+ }
99
+
100
+ # ---------------------------
101
+ # HEALTH ENDPOINT
102
+ # ---------------------------
103
+ location /health {
104
+ access_log off;
105
+ add_header Content-Type text/plain always;
106
+ return 200 "healthy\n";
107
+ }
108
+ }
@@ -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('{{packageManager}}', ['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,7 +1,7 @@
1
1
  import chokidar from 'chokidar';
2
2
  import { exec } from 'child_process';
3
3
  import path from 'path';
4
- import { print } from './utils/index.js'
4
+ import { print } from '../utils/index.js'
5
5
 
6
6
  const watcher = chokidar.watch(['src'], {
7
7
  ignored: /(^|[/\\])\../,
@@ -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 = `src/app/${type.kebab}s/${fullPath.kebab}`;
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 componentsStylePath = path.join('src', 'styles', 'components');
67
- const filePath = path.join(componentsStylePath, `_${name.kebab}.scss`);
68
- const indexPath = path.join(componentsStylePath, `_index.scss`);
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
- export function runCommand(cmd) {
2
- import('child_process').then(({ execSync }) => {
3
- execSync(cmd, { stdio: 'inherit' });
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
  }
@@ -4,7 +4,7 @@ import process from 'process';
4
4
  import { print } from './index.js';
5
5
 
6
6
  export function runScript(path, message) {
7
- print.boldSection(`\n${message}`);
7
+ print.boldSection(message);
8
8
 
9
9
  const args = [resolve(path)];
10
10
 
@@ -0,0 +1,3 @@
1
+ import langs from '../../config/languages.json' with { type: 'json' };
2
+
3
+ export const CONFIG = { langs } as const;
@@ -65,6 +65,8 @@ describe('i18n module', async () => {
65
65
  configurable: true,
66
66
  value: "en-US"
67
67
  });
68
+ I18nService['_LANGUAGES'] = [ 'en', 'fr' ];
69
+ I18nService['_DEFAULT_LANGUAGE'] = 'en';
68
70
  });
69
71
  afterAll(() => {
70
72
  Object.defineProperty(navigator, 'language', {
@@ -1,4 +1,4 @@
1
- import { DEFAULT_LANGUAGE, LANGUAGES, Language, Translations } from '../../../app/languages.js';
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;