@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
@@ -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.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`);
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('✅ 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,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.info(`ESBuild complete.`);
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.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,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,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;
@@ -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>;