@nutin/cli 1.2.3 → 1.3.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 (80) hide show
  1. package/CHANGELOG.md +70 -1
  2. package/README.md +25 -18
  3. package/lib/index.mjs +23 -20
  4. package/lib/src/context-builder.mjs +40 -0
  5. package/lib/src/file-generator.mjs +26 -126
  6. package/lib/src/git-manager.mjs +1 -1
  7. package/lib/src/json-generator.mjs +31 -0
  8. package/lib/src/package-manager.mjs +21 -18
  9. package/lib/src/print.mjs +1 -0
  10. package/lib/src/project-generator.mjs +53 -0
  11. package/lib/src/prompts.mjs +16 -75
  12. package/lib/src/template-compiler.mjs +1 -1
  13. package/lib/src/utils.mjs +9 -9
  14. package/lib/src/version.mjs +1 -0
  15. package/package.json +1 -1
  16. package/templates/base/.gitignore.hbs +1 -0
  17. package/templates/base/builder.config.js.hbs +8 -0
  18. package/templates/base/src/app/globals.d.ts.hbs +12 -8
  19. package/templates/base/src/app/main.ts.hbs +2 -1
  20. package/templates/base/src/app/views/home/home.view.test.js.hbs +26 -0
  21. package/templates/base/src/app/views/home/home.view.ts.hbs +2 -2
  22. package/templates/base/src/app/views/not-found/not-found.view.ts.hbs +1 -1
  23. package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +46 -26
  24. package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +36 -22
  25. package/templates/base/src/core/base-classes/base-component/helpers/children.helper.ts.hbs +4 -4
  26. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +60 -13
  27. package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +16 -17
  28. package/templates/base/src/core/base-classes/base-component/helpers/security.helper.ts.hbs +40 -8
  29. package/templates/base/src/core/base-classes/base-component/helpers/token.helper.ts.hbs +16 -16
  30. package/templates/base/src/core/base-classes/component/component.ts.hbs +20 -24
  31. package/templates/base/src/core/base-classes/component/helpers/config.helper.ts.hbs +19 -2
  32. package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.ts.hbs +2 -2
  33. package/templates/base/src/core/base-classes/service/service.ts.hbs +35 -12
  34. package/templates/base/src/core/base-classes/view/view.ts.hbs +6 -6
  35. package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +13 -13
  36. package/templates/base/src/core/services/http-client/helpers/http-builder.helper.ts.hbs +18 -18
  37. package/templates/base/src/core/services/http-client/helpers/http-manager.helper.ts.hbs +24 -24
  38. package/templates/base/src/core/services/http-client/http-client.ts.hbs +19 -19
  39. package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +6 -6
  40. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +15 -14
  41. package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.ts.hbs +22 -22
  42. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +4 -36
  43. package/templates/base/src/core/services/router/router.ts.hbs +45 -46
  44. package/templates/base/src/core/services/store/store.ts.hbs +6 -6
  45. package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.ts.hbs +41 -15
  46. package/templates/base/src/core/utils/button-manager/button-manager.utils.ts.hbs +40 -19
  47. package/templates/base/src/core/utils/helpers/attributes.helper.ts.hbs +11 -5
  48. package/templates/base/src/libs/pipes/pipes.ts.hbs +1 -1
  49. package/templates/base/src/libs/popover/helpers/focus-trap.helper.ts.hbs +197 -0
  50. package/templates/base/src/libs/popover/helpers/popover-dom.helper.ts.hbs +75 -0
  51. package/templates/base/src/libs/popover/popover.ts.hbs +89 -96
  52. package/templates/base/src/libs/snackbar/snackbar.ts.hbs +7 -4
  53. package/templates/base/src/styles/base/_variables.scss.hbs +1 -0
  54. package/templates/base/src/styles/core/_anchors.scss.hbs +10 -0
  55. package/templates/base/src/styles/core/_index.scss.hbs +6 -0
  56. package/templates/base/src/styles/core/_popover.scss.hbs +96 -0
  57. package/templates/base/src/styles/core/_snackbar.scss.hbs +50 -0
  58. package/templates/base/src/styles/main.scss.hbs +1 -1
  59. package/templates/base/tools/builder/builder.js.hbs +9 -3
  60. package/templates/base/tools/builder/core/add-tags.js.hbs +46 -0
  61. package/templates/base/tools/builder/core/copy-static.js.hbs +35 -35
  62. package/templates/base/tools/builder/core/finalize-build.js.hbs +6 -6
  63. package/templates/base/tools/builder/core/hash-files.js.hbs +52 -0
  64. package/templates/base/tools/builder/core/validate-html.js.hbs +12 -32
  65. package/templates/base/tools/utils/get-files-recursive.js.hbs +17 -8
  66. package/templates/features/deployHelper/tools/builder/core/compress-files.js.hbs +56 -0
  67. package/templates/{base → features/deployHelper}/tools/deployment/Dockerfile.hbs +12 -4
  68. package/templates/{base → features/deployHelper}/tools/deployment/nginx.conf.hbs +30 -2
  69. package/templates/features/i18n/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +4 -6
  70. package/templates/features/i18n/src/core/services/i18n/i18n.ts.hbs +76 -52
  71. package/templates/features/stylinNutin/src/styles/core/_utilities.scss.hbs +31 -0
  72. package/templates/features/template/src/app/views/home/home.view.html.hbs +1 -1
  73. package/templates/features/template/tools/builder/core/merge-templates.js.hbs +2 -2
  74. package/templates/features/testinNutin/testin-nutin/core/src/utils/test-queue.js.hbs +2 -2
  75. package/templates/features/testinNutin/testin-nutin/runner.js.hbs +1 -0
  76. package/templates/features/testinNutin/testin-nutin.config.js.hbs +2 -2
  77. package/templates/base/src/styles/core/_libs.scss.hbs +0 -132
  78. package/templates/base/tools/deployment/gzip.conf.hbs +0 -19
  79. package/templates/features/i18n/src/app/views/home/locales/fr.json.hbs +0 -9
  80. /package/templates/{base → features/deployHelper}/.dockerignore.hbs +0 -0
@@ -1,5 +1,5 @@
1
1
  import { DEFAULT_LANGUAGE, LANGUAGES, Language, Translations } from './languages.js';
2
- import { Service } from '../../index.js';
2
+ import { AppEventBus, Service } from '../../index.js';
3
3
 
4
4
  export class I18n extends Service<I18n> {
5
5
  private readonly _DEFAULT_LANGUAGE: Language = DEFAULT_LANGUAGE;
@@ -7,41 +7,35 @@ export class I18n extends Service<I18n> {
7
7
  private _currentLanguage: Language = this._DEFAULT_LANGUAGE;
8
8
  private _translations: Translations = {};
9
9
  private _defaultTranslations: Translations = {};
10
+ private readonly _localStorageKey = 'nutin-fav-lang';
10
11
 
11
12
  constructor() {
12
13
  super();
13
14
  this.registerCleanup(this.resetTranslations);
14
- this._currentLanguage = this.getBrowserLanguage();
15
+ this._currentLanguage = this.getPreferredLanguage();
15
16
  }
16
17
 
17
- private getBrowserLanguage() {
18
- return navigator.language.split('-')[0] as Language;
19
- };
18
+ public get currentLanguage(): Language {
19
+ return this._currentLanguage;
20
+ }
20
21
 
21
- private async loadDefaultTranslations(): Promise<void> {
22
- try {
23
- const response = await fetch(`/locales/${this._DEFAULT_LANGUAGE}.json`);
24
-
25
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
26
-
27
- this._defaultTranslations = await response.json();
28
- } catch (error) {
29
- console.error('Default translation load error:', error);
30
- }
22
+ public get defaultLanguage(): Language {
23
+ return this._DEFAULT_LANGUAGE;
31
24
  }
32
25
 
33
- private getNestedValue(obj: any, keys: string[]): any {
34
- let value = obj;
35
- for (const key of keys) {
36
- value = value?.[key];
37
- if (!value) break;
38
- }
39
- return value;
26
+ public get languages(): Language[] {
27
+ return this._LANGUAGES;
40
28
  }
41
29
 
42
- protected onDestroy(): void | Promise<void> {
43
- this.resetTranslations();
44
- this.dispose();
30
+ public get localStorageKey(): string {
31
+ return this._localStorageKey;
32
+ }
33
+
34
+ public async setCurrentLanguage(lang: Language): Promise<void> {
35
+ this._currentLanguage = lang;
36
+ this.savePreferences();
37
+ await this.loadTranslations(lang);
38
+ AppEventBus.emit('language-changed');
45
39
  }
46
40
 
47
41
  public async loadTranslations(lang: Language): Promise<void> {
@@ -57,13 +51,14 @@ export class I18n extends Service<I18n> {
57
51
  await this.loadDefaultTranslations();
58
52
  }
59
53
 
60
- document.documentElement.lang = lang;
54
+ document.documentElement.lang = lang;
55
+ this.savePreferences();
61
56
  } catch (error) {
62
57
  console.error('Translation load error:', error);
63
- }
58
+ }
64
59
  }
65
60
 
66
- public translate(key: string, params?: Record<string, string>): string {
61
+ public translate(key: string, textContent?: string | null): string {
67
62
  const keys = key.split('.');
68
63
 
69
64
  // Try to get value from current language translations
@@ -74,33 +69,12 @@ export class I18n extends Service<I18n> {
74
69
  value = this.getNestedValue(this._defaultTranslations, keys);
75
70
  }
76
71
 
77
- // If still not found, return the key as fallback
78
- let result = value || key;
79
-
80
- if (params) {
81
- Object.entries(params).forEach(([k, v]) => {
82
- result = result.replace(`{${k}}`, v);
83
- });
84
- }
85
-
86
- return result;
87
- }
88
-
89
- public get currentLanguage(): Language {
90
- return this._currentLanguage;
91
- }
92
-
93
- public get defaultLanguage(): Language {
94
- return this._DEFAULT_LANGUAGE;
95
- }
96
-
97
- public get languages(): Language[] {
98
- return this._LANGUAGES;
72
+ return value || textContent || key;
99
73
  }
100
74
 
101
75
  public async initTranslations(): Promise<void> {
102
- const browserLang = this.getBrowserLanguage();
103
- const lang = this._LANGUAGES.includes(browserLang) ? browserLang : this._DEFAULT_LANGUAGE;
76
+ const preferredLang = this.getPreferredLanguage();
77
+ const lang = this._LANGUAGES.includes(preferredLang) ? preferredLang : this._DEFAULT_LANGUAGE;
104
78
 
105
79
  // Always load default translations first
106
80
  if (lang !== this._DEFAULT_LANGUAGE) {
@@ -110,11 +84,61 @@ export class I18n extends Service<I18n> {
110
84
  await this.loadTranslations(lang);
111
85
  }
112
86
 
87
+ public getTranslationObject<T = any>(key: string): T | null {
88
+ const keys = key.split('.');
89
+
90
+ let value = this.getNestedValue(this._translations, keys);
91
+
92
+ if (!value && this._currentLanguage !== this._DEFAULT_LANGUAGE) {
93
+ value = this.getNestedValue(this._defaultTranslations, keys);
94
+ }
95
+
96
+ return value || null;
97
+ }
98
+
113
99
  public resetTranslations(): void {
114
100
  this._translations = {};
115
101
  this._defaultTranslations = {};
116
102
  this._currentLanguage = this._DEFAULT_LANGUAGE;
117
103
  }
104
+
105
+ protected onDestroy(): void | Promise<void> {
106
+ this.resetTranslations();
107
+ this.dispose();
108
+ }
109
+
110
+ private getPreferredLanguage() {
111
+ return this.getPreferences() || navigator.language.split('-')[0] as Language;
112
+ };
113
+
114
+ private async loadDefaultTranslations(): Promise<void> {
115
+ try {
116
+ const response = await fetch(`/locales/${this._DEFAULT_LANGUAGE}.json`);
117
+
118
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
119
+
120
+ this._defaultTranslations = await response.json();
121
+ } catch (error) {
122
+ console.error('Default translation load error:', error);
123
+ }
124
+ }
125
+
126
+ private getNestedValue(obj: any, keys: string[]): any {
127
+ let value = obj;
128
+ for (const key of keys) {
129
+ value = value?.[key];
130
+ if (!value) break;
131
+ }
132
+ return value;
133
+ }
134
+
135
+ private savePreferences(): void {
136
+ localStorage.setItem(this._localStorageKey, this._currentLanguage);
137
+ }
138
+
139
+ private getPreferences(): string | null {
140
+ return localStorage.getItem(this._localStorageKey);
141
+ }
118
142
  }
119
143
 
120
144
  export const I18nService = I18n.getInstance();
@@ -60,6 +60,16 @@
60
60
  }
61
61
  }
62
62
 
63
+ .u-hide-on-small {
64
+ @include mixins.respond-to(medium) {
65
+ display: none;
66
+ }
67
+ }
68
+
69
+ .u-hide {
70
+ display: none;
71
+ }
72
+
63
73
  // color
64
74
  .u-color-base {
65
75
  color: $text-color;
@@ -73,6 +83,10 @@
73
83
  color: $secondary-color;
74
84
  }
75
85
 
86
+ .u-color-acc {
87
+ color: $accent-color;
88
+ }
89
+
76
90
  .u-color-bg {
77
91
  color: $background-color;
78
92
  }
@@ -90,6 +104,23 @@
90
104
  font-family: $secondary-font;
91
105
  }
92
106
 
107
+ .u-font-primary {
108
+ font-family: $primary-font;
109
+ }
110
+
111
+ // text
112
+ .u-text-center {
113
+ text-align: center;
114
+ }
115
+
116
+ .u-text-right {
117
+ text-align: right;
118
+ }
119
+
120
+ .u-text-left {
121
+ text-align: left;
122
+ }
123
+
93
124
  // flex
94
125
  .u-flex-center {
95
126
  @include mixins.flex-center;
@@ -2,7 +2,7 @@
2
2
  {{#if stylinNutin}}
3
3
  class="u-flex-center u-flex-column u-padd-y-large"
4
4
  {{else}}
5
- style="display:flex;justify-content:center;align-items:center;flex-direction:column"
5
+ style="display:flex;justify-content:center;align-items:center;flex-direction:column;padding-top:1rem;padding-bottom:1rem;"
6
6
  {{/if}}>
7
7
  <h1
8
8
  {{#if i18n}}
@@ -19,7 +19,7 @@ async function mergeTemplates() {
19
19
  try {
20
20
  const htmlContent = await fs.readFile(htmlPath, 'utf-8');
21
21
  const minifiedHtml = await minifyHTML(htmlContent);
22
- if (isVerbose) print.info(`Minified HTML: ${file}`);
22
+ if (isVerbose) print.info(`Minified HTML: ${htmlPath}`);
23
23
 
24
24
  try {
25
25
  let jsContent = await fs.readFile(jsPath, 'utf-8');
@@ -32,7 +32,7 @@ async function mergeTemplates() {
32
32
  jsContent = jsContent.replace(PLACEHOLDER, minifiedHtml);
33
33
  await fs.writeFile(jsPath, jsContent);
34
34
  await fs.unlink(htmlPath);
35
- if (isVerbose) print.info(`✅ Updated ${jsFilename} with template from ${htmlFilename}.`);
35
+ if (isVerbose) print.info(`Updated ${jsFilename} with template from ${htmlFilename}.`);
36
36
  } catch (err) {
37
37
  print.boldError(`ERROR: Cannot update ${jsFilename}. ${err.message}`);
38
38
  }
@@ -24,9 +24,11 @@ export async function runQueuedTests() {
24
24
  test = testQueue.deque();
25
25
 
26
26
  if (test.suiteName !== previousSuite) {
27
+ teardownJsdom();
27
28
  if (previousSuiteAfterAll) {
28
29
  await previousSuiteAfterAll();
29
30
  }
31
+ setupJsdom();
30
32
  if (test.beforeAll) {
31
33
  await test.beforeAll();
32
34
  }
@@ -34,11 +36,9 @@ export async function runQueuedTests() {
34
36
  previousSuiteAfterAll = test.afterAll;
35
37
  }
36
38
 
37
- setupJsdom();
38
39
  if (test.beforeEach) await test.beforeEach();
39
40
  await test.testFn();
40
41
  if (test.afterEach) await test.afterEach();
41
- teardownJsdom();
42
42
 
43
43
  passed++;
44
44
  results.push({
@@ -1,4 +1,5 @@
1
1
  import { registerTestGlobals, getTestFiles, runQueuedTests, print } from './core/index.js';
2
+ import path from 'path';
2
3
  import config from '#root/testin-nutin.config.js';
3
4
 
4
5
  registerTestGlobals();
@@ -4,11 +4,11 @@ export default {
4
4
  // 'src/core', // toolkit core logic
5
5
  // 'testin-nutin/core/tests', // test globals
6
6
  ],
7
- verbose: false,
8
7
  jsdomOptions: {
9
8
  runScripts: false, // or true ("dangerously") if needed
10
9
  resources: false, // or true ("usable") if needed
11
10
  freezeGlobals: false,
12
11
  pretendToBeVisual: true,
13
- }
12
+ },
13
+ verbose: false,
14
14
  }
@@ -1,132 +0,0 @@
1
- @use "sass:map";
2
- @use "variables" as *;
3
-
4
- // snackbar
5
- .app-snackbar {
6
- position: fixed;
7
- left: 50%;
8
- transform: translateX(-50%);
9
- background: $snack-default;
10
- color: white;
11
- padding: 0.75rem 1.5rem;
12
- border-radius: 0.5rem;
13
- box-shadow: 0 2px 6px rgba(0,0,0,0.3);
14
- display: flex;
15
- gap: 1rem;
16
- align-items: center;
17
- // z-index: 1000;
18
- animation: fade-in 0.3s ease;
19
- }
20
-
21
- .app-snackbar--bottom { bottom: 1rem; }
22
- .app-snackbar--top { top: 1rem; }
23
-
24
- .app-snackbar--success { background: $snack-success; }
25
- .app-snackbar--error { background: $snack-error; }
26
- .app-snackbar--info { background: $snack-info; }
27
-
28
- .app-snackbar button {
29
- background: transparent;
30
- border: none;
31
- color: #fff;
32
- font-weight: bold;
33
- cursor: pointer;
34
- }
35
-
36
- @keyframes fade-in {
37
- from { opacity: 0; transform: translateX(-50%) translateY(10px); }
38
- to { opacity: 1; transform: translateX(-50%) translateY(0); }
39
- }
40
-
41
- // popover
42
- /* Prevent scroll when popover is active */
43
- .no-scroll {
44
- overflow: hidden;
45
- }
46
-
47
- .popover-overlay {
48
- background: linear-gradient(to right, rgba($primary-color, .7), rgba($secondary-color, .7));
49
- position: fixed;
50
- inset: 0;
51
- backdrop-filter: blur(5px);
52
- display: flex;
53
- align-items: center;
54
- justify-content: center;
55
- // z-index: 9999;
56
- opacity: 0;
57
- transition: opacity 0.25s ease;
58
- &.show {
59
- opacity: 1;
60
- }
61
- }
62
-
63
- .popover-wrapper {
64
- position: relative;
65
- background-color: $background-color;
66
- border-radius: 10px transparent;
67
- max-width: 600px;
68
- width: 90%;
69
- max-height: 90%;
70
- overflow: auto;
71
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
72
- transform: scale(0.95);
73
- opacity: 0;
74
- transition: opacity 1s ease, transform 1s ease;
75
- &.show {
76
- transform: scale(1);
77
- opacity: 1;
78
- }
79
- }
80
-
81
- .popover-content {
82
- position: relative;
83
- }
84
-
85
- .popover-close-button {
86
- position: absolute;
87
- top: 0.5rem;
88
- right: 0.75rem;
89
- background: none;
90
- border: none;
91
- font-size: 1.5rem;
92
- cursor: pointer;
93
- }
94
-
95
- .popover-buttons {
96
- display: flex;
97
- justify-content: flex-end;
98
- gap: 1rem;
99
- margin-top: 1.5rem;
100
- }
101
-
102
- .popover-buttons button {
103
- background-color: inherit;
104
- transition: ease-in-out 0.3s;
105
- padding: 0.5rem 1rem;
106
- font-size: 1rem;
107
- cursor: pointer;
108
- &:hover {
109
- background-color: rgba($secondary-color, .7);
110
- color: $background-color;
111
- }
112
- }
113
-
114
- {{#if transition}}
115
- // view transition
116
- .app-view-transition {
117
- opacity: 0;
118
- transform: scale(0.95);
119
- transition: all 300ms ease;
120
-
121
- &.enter-active {
122
- opacity: 1;
123
- transform: scale(1);
124
- }
125
-
126
- &.exit-active {
127
- opacity: 0;
128
- transform: scale(0.98);
129
- }
130
- }
131
-
132
- {{/if}}
@@ -1,19 +0,0 @@
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;
@@ -1,9 +0,0 @@
1
- {
2
- "meta" : {
3
- "title": "Accueil",
4
- "description": "Page d'accueil"
5
- },
6
- "title": "{{projectName}}",
7
- "ready": "Tout est prêt !",
8
- "enjoy": "Happy Coding 🎉"
9
- }