@nutin/cli 1.2.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +36 -1
  2. package/README.md +23 -15
  3. package/lib/index.mjs +7 -4
  4. package/lib/src/context-builder.mjs +35 -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/project-generator.mjs +53 -0
  10. package/lib/src/prompts.mjs +28 -15
  11. package/lib/src/template-compiler.mjs +1 -1
  12. package/lib/src/utils.mjs +9 -9
  13. package/lib/src/version.mjs +1 -0
  14. package/package.json +1 -1
  15. package/templates/base/.gitignore.hbs +1 -0
  16. package/templates/base/builder.config.js.hbs +7 -0
  17. package/templates/base/src/app/main.ts.hbs +2 -1
  18. package/templates/base/src/app/views/home/home.view.test.js.hbs +26 -0
  19. package/templates/base/src/app/views/home/home.view.ts.hbs +2 -2
  20. package/templates/base/src/app/views/not-found/not-found.view.ts.hbs +1 -1
  21. package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +23 -20
  22. package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +2 -2
  23. package/templates/base/src/core/base-classes/base-component/helpers/children.helper.ts.hbs +4 -4
  24. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +26 -13
  25. package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +9 -9
  26. package/templates/base/src/core/base-classes/base-component/helpers/security.helper.ts.hbs +40 -8
  27. package/templates/base/src/core/base-classes/base-component/helpers/token.helper.ts.hbs +16 -16
  28. package/templates/base/src/core/base-classes/component/component.ts.hbs +12 -12
  29. package/templates/base/src/core/base-classes/component/helpers/config.helper.ts.hbs +2 -2
  30. package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.ts.hbs +2 -2
  31. package/templates/base/src/core/base-classes/service/service.ts.hbs +35 -12
  32. package/templates/base/src/core/base-classes/view/view.ts.hbs +6 -6
  33. package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +13 -13
  34. package/templates/base/src/core/services/http-client/helpers/http-builder.helper.ts.hbs +18 -18
  35. package/templates/base/src/core/services/http-client/helpers/http-manager.helper.ts.hbs +24 -24
  36. package/templates/base/src/core/services/http-client/http-client.ts.hbs +19 -19
  37. package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +6 -6
  38. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +13 -13
  39. package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.ts.hbs +22 -22
  40. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +6 -5
  41. package/templates/base/src/core/services/router/router.ts.hbs +45 -45
  42. package/templates/base/src/core/services/store/store.ts.hbs +6 -6
  43. package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.ts.hbs +6 -8
  44. package/templates/base/src/core/utils/button-manager/button-manager.utils.ts.hbs +9 -11
  45. package/templates/base/src/core/utils/helpers/attributes.helper.ts.hbs +11 -5
  46. package/templates/base/src/libs/pipes/pipes.ts.hbs +1 -1
  47. package/templates/base/src/libs/popover/popover.ts.hbs +48 -48
  48. package/templates/base/src/libs/snackbar/snackbar.ts.hbs +1 -1
  49. package/templates/base/tools/builder/builder.js.hbs +9 -3
  50. package/templates/base/tools/builder/core/add-tags.js.hbs +46 -0
  51. package/templates/base/tools/builder/core/copy-static.js.hbs +35 -35
  52. package/templates/base/tools/builder/core/finalize-build.js.hbs +6 -6
  53. package/templates/base/tools/builder/core/hash-files.js.hbs +52 -0
  54. package/templates/base/tools/builder/core/validate-html.js.hbs +12 -32
  55. package/templates/base/tools/utils/get-files-recursive.js.hbs +17 -8
  56. package/templates/features/deployHelper/tools/builder/core/compress-files.js.hbs +56 -0
  57. package/templates/{base → features/deployHelper}/tools/deployment/Dockerfile.hbs +12 -4
  58. package/templates/{base → features/deployHelper}/tools/deployment/nginx.conf.hbs +30 -2
  59. package/templates/features/i18n/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +4 -6
  60. package/templates/features/i18n/src/core/services/i18n/i18n.ts.hbs +38 -47
  61. package/templates/features/stylinNutin/src/styles/core/_utilities.scss.hbs +17 -0
  62. package/templates/features/template/src/app/views/home/home.view.html.hbs +1 -1
  63. package/templates/features/template/tools/builder/core/merge-templates.js.hbs +2 -2
  64. package/templates/features/testinNutin/testin-nutin/core/src/utils/test-queue.js.hbs +2 -2
  65. package/templates/features/testinNutin/testin-nutin/runner.js.hbs +1 -0
  66. package/templates/features/testinNutin/testin-nutin.config.js.hbs +2 -2
  67. package/templates/base/tools/deployment/gzip.conf.hbs +0 -19
  68. package/templates/features/i18n/src/app/views/home/locales/fr.json.hbs +0 -9
  69. /package/templates/{base → features/deployHelper}/.dockerignore.hbs +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## V1.3.0
4
+
5
+ Version 1.3.0 marks a stability milestone with various improvements and refinements, making this the recommended version for new projects.
6
+
7
+ - Global
8
+ - `Service` abstract class now auto-binds methods with `this`, preventing `"this" is undefined` potential warning on initial page load.
9
+ - Added HTML template sanitization with trustLevel that can be passed to component's `super()`.
10
+ - trusted : no template sanitization (returns original)
11
+ - normal (default) : remove scripts and inline event handlers
12
+ - strict : remove iframe, object, embed, href (javascript), data: protocol, scripts and inline event handlers
13
+
14
+ - I18n
15
+ - Removed `data-i18n-params` pipe : Default values (fallback) must now be inlined text content in component's HTML template.
16
+
17
+ - CLI :
18
+ - Deployment helpers is now optional (default : disabled). Flags : `--deploy-helper` `--no-deploy-helper`
19
+ - Fixed CLI prompts (no longer overrides answers)
20
+
21
+ - Builder / Deployment Helper
22
+ - Now adds stylesheet & script tags in `index.html` on build time (`add-tags.js`)
23
+ - (Production) Added file hashing (js & css) (`hash-files.js`)
24
+ - (Production) Added Gzip and Brotli compression (`compress-files.js`). Configurable in `builder.config.js`
25
+ - **Note : default nginx alpine image does NOT support Brotli compression.** If you want to use it :
26
+ - uncomment brotli-related sections (`BROTLI OPTIONAL`) in `builder.config.js` and `tools/builder/core/compress-files.js`
27
+ - enable brotli in nginx.conf
28
+ - You'll also need to use an existing Brotli-enabled nginx image or build your own from source.
29
+ - Enabled gzip globally in `nginx.conf` and removed `gzip.conf` (compress during build).
30
+
31
+ - StylinNutin
32
+ - Added utility classes `u-text-center`, `u-text-right`, `u-text-left`, `u-font-primary`
33
+ - box-shadow variables now use `$primary-color`
34
+
35
+ - TestinNutin
36
+ - Now applies `setupJsdom()` beforeAll (was beforeEach) and `teardownJsdom()` afterAll (was afterEach) in `test-queue.js` (improved efficiency / speed)
37
+
3
38
  ## V1.2.3
4
39
 
5
40
  - `nginx.conf` : fixed multi-line CSP map warning (single line map)
@@ -69,7 +104,7 @@
69
104
  - Improved deployment tools Dockerfile (nginx conf + gzip)
70
105
 
71
106
  - stylin-nutin (generator)
72
- - When generating a component, prompts (boolean) to generate a `_component-name.scss` file in `styles/components` (forwarded by `stymes/components/_index.scss`).
107
+ - When generating a component, prompts (boolean) to generate a `_component-name.scss` file in `styles/components` (forwarded by `styles/components/_index.scss`).
73
108
 
74
109
  ## V1.1.0
75
110
 
package/README.md CHANGED
@@ -1,10 +1,11 @@
1
1
  # [nutin](https://www.nutin.org)
2
2
 
3
3
  **Build SPAs by understanding SPAs. A minimal frontend toolkit crafted for learning and lean applications.**
4
- Includes essential features out of the box, with zero runtime dependencies.
5
- *TypeScript / Javascript, SASS, HTML*
4
+ Includes essential features out of the box.
5
+ *Pure TypeScript, SASS, and DOM-driven components — zero runtime dependencies.*
6
6
 
7
- - Required : Node >=18 **If using testin-nutin: Node >=20.19.0 (JSDOM requirement : `{ node: '^20.19.0 || ^22.12.0 || >=24.0.0' }`)**
7
+ - Required : Node >=18
8
+ - **JSDOM requirement** (testin-nutin companion) : `{ node: '^20.19.0 || ^22.12.0 || >=24.0.0' }`
8
9
 
9
10
  ## Install
10
11
 
@@ -19,31 +20,38 @@ npm install -g @nutin/cli
19
20
  # create a new app
20
21
  create-nutin-app # or
21
22
  nutin-new
23
+
24
+ # without global installation
25
+ npx @nutin/cli
22
26
  ```
23
27
 
24
28
  - Flags
25
29
 
26
30
  ```bash
27
- # argument: project-name
31
+ # argument: project-name (prompt if not provided)
32
+
33
+ # Help
34
+ -h --help
28
35
 
36
+ # Defaults settings - see below
29
37
  -d --defaults
38
+
39
+ # Package manager
30
40
  -pm --package-manager <manager> # npm (Default), yarn, pnpm, bun
31
41
 
42
+ # Features
43
+ --template # (Default) Use external templates
32
44
  --no-template # Use inline templates
45
+ --stylin-nutin # (Default) Use built-in SCSS utility classes & mixins
33
46
  --no-stylin-nutin # Do not use built-in SCSS utility
34
47
  --testin-nutin # Use testin-nutin toolkit
35
- --no-i18n # Do not use i18n & json-based content
36
- --transition # Use animated view transitions. Note: May interfere with CSS `position: fixed`, `z-index`...
37
-
38
- # Defaults
39
- --template # (Default) Use external templates
40
- --stylin-nutin # (Default) Use built-in SCSS utility classes & mixins
41
- --i18n # (Default) Use i18n & json-based content
42
48
  --no-testin-nutin # (Default) Do not use testin-nutin toolkit
49
+ --i18n # Use i18n & json-based content
50
+ --no-i18n # (Default) Do not use i18n & json-based content
51
+ --deploy-helper # Use Docker & deployment helpers
52
+ --no-deploy-helper, # (Default) Do not use Docker & deployment helpers
53
+ --transition # Use animated view transitions. Note: May interfere with CSS `position: fixed`, `z-index`...
43
54
  --no-transition # (Default) Do not use animated view transitions
44
-
45
- # Help
46
- -h --help
47
55
  ```
48
56
 
49
57
  ## [First app tutorial](https://www.nutin.org/tutorial)
@@ -51,4 +59,4 @@ nutin-new
51
59
  ## 📄 Docs
52
60
 
53
61
  - Docs on [website](https://www.nutin.org).
54
- - Docs on [Project repository](https://github.com/LoisKOUNINEF/nutin/tree/main/docs).
62
+ - Docs on [repository](https://github.com/LoisKOUNINEF/nutin/tree/main/docs).
package/lib/index.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  import { program } from 'commander';
2
2
  import { print } from './src/print.mjs';
3
3
  import { promptUser } from './src/prompts.mjs';
4
- import { createProject } from './src/file-generator.mjs';
5
- import { displaySuccessMessage, packageVersion } from './src/utils.mjs';
4
+ import { createProject } from './src/project-generator.mjs';
5
+ import { displaySuccessMessage } from './src/utils.mjs';
6
+ import { packageVersion } from './src/version.mjs';
6
7
 
7
8
  export async function createApp() {
8
9
  program
@@ -14,8 +15,10 @@ export async function createApp() {
14
15
  .option('--no-template', 'Do not use external templates')
15
16
  .option('--stylin-nutin', '(Default) Use built-in SCSS utility classes & mixins')
16
17
  .option('--no-stylin-nutin', 'Do not use built-in SCSS utility')
17
- .option('--i18n', '(Default) Use i18n & json-based content')
18
- .option('--no-i18n', 'Do not use i18n & json-based content')
18
+ .option('--i18n', 'Use i18n & json-based content')
19
+ .option('--no-i18n', '(Default) Do not use i18n & json-based content')
20
+ .option('--deploy-helper', 'Use Docker & deployment helpers')
21
+ .option('--no-deploy-helper', '(Default) Do not use Docker & deployment helpers')
19
22
  .option('--testin-nutin', 'Use testin-nutin toolkit')
20
23
  .option('--no-testin-nutin', '(Default) Do not use testin-nutin toolkit')
21
24
  .option('--transition', 'Use animated view transitions\nNote: May interfere with CSS `position: fixed`, `z-index`...')
@@ -0,0 +1,35 @@
1
+ import { getCiCommand } from './package-manager.mjs';
2
+ import { packageVersion } from './version.mjs';
3
+
4
+ export const defaults = {
5
+ projectName: 'my-nutin-app',
6
+ packageManager: 'npm',
7
+ stylinNutin: true,
8
+ template: true,
9
+ i18n: false,
10
+ deployHelper: false,
11
+ testinNutin: false,
12
+ transition: false
13
+ };
14
+
15
+ export class ContextBuilder {
16
+ buildContext(answers) {
17
+ const version = packageVersion;
18
+ const ciCommand = getCiCommand(answers.packageManager);
19
+
20
+ return {
21
+ projectName: answers.projectName,
22
+ packageManager: answers.packageManager,
23
+
24
+ template: answers.template ?? defaults.template,
25
+ stylinNutin: answers.stylinNutin ?? defaults.stylinNutin,
26
+ i18n: answers.i18n ?? defaults.i18n,
27
+ deployHelper: answers.deployHelper ?? defaults.deployHelper,
28
+ testinNutin: answers.testinNutin ?? defaults.testinNutin,
29
+ transition: answers.transition ?? defaults.transition,
30
+
31
+ ciCommand: ciCommand,
32
+ version: version
33
+ };
34
+ }
35
+ }
@@ -3,9 +3,6 @@ import { fileURLToPath } from 'url';
3
3
  import * as fsExtra from 'fs-extra';
4
4
  import { TemplateCompiler } from './template-compiler.mjs';
5
5
  import { print } from './print.mjs';
6
- import { initializeGit } from './git-manager.mjs';
7
- import { installDependencies, generatePackageJson, generateTsconfigJson, getCiCommand } from './package-manager.mjs';
8
- import { packageVersion } from './utils.mjs';
9
6
 
10
7
  const fs = fsExtra.default;
11
8
  const __filename = fileURLToPath(import.meta.url);
@@ -18,36 +15,13 @@ const BINARY_EXTENSIONS = new Set([
18
15
  '.pdf', '.zip', '.tar', '.gz'
19
16
  ]);
20
17
 
21
- export class ProjectGenerator {
18
+ export class FileGenerator {
22
19
  constructor() {
23
20
  this.compiler = new TemplateCompiler();
24
21
  }
25
22
 
26
- async createProject(answers) {
27
- const { projectName } = answers;
28
- const projectPath = path.join(process.cwd(), projectName);
29
-
30
- await this.validateProjectPath(projectPath, projectName);
31
- await this.setupProjectStructure(projectPath, answers);
32
- await this.runPostSetupTasks(projectPath, answers);
33
- }
34
-
35
- async validateProjectPath(projectPath, projectName) {
36
- if (await fs.pathExists(projectPath)) {
37
- throw new Error(`Directory ${projectName} already exists`);
38
- }
39
- }
40
-
41
- async setupProjectStructure(projectPath, answers) {
42
- print.boldHead(`\n📁 Creating project in ${projectPath}...`);
43
-
44
- await fs.ensureDir(projectPath);
45
- await this.generateProjectFromTemplates(projectPath, answers);
46
- }
47
-
48
- async generateProjectFromTemplates(projectPath, answers) {
49
- const context = this.buildContext(answers);
50
- const templateDir = this.getTemplateDirectory(answers);
23
+ async generateProjectFromTemplates(projectPath, context) {
24
+ const templateDir = this.getTemplateDirectory();
51
25
 
52
26
  print.section('📝 Processing templates...');
53
27
  await this.processTemplateDirectory(templateDir, projectPath, context);
@@ -55,31 +29,8 @@ export class ProjectGenerator {
55
29
  await this.processFeatureTemplates(projectPath, context);
56
30
  }
57
31
 
58
- buildContext(answers) {
59
- const version = packageVersion;
60
- const ciCommand = getCiCommand(answers.packageManager);
61
-
62
- return {
63
- projectName: answers.projectName,
64
- description: answers.description || `A modern web application`,
65
-
66
- template: answers.template || false,
67
- stylinNutin: answers.stylinNutin || false,
68
- i18n: answers.i18n || false,
69
- transition: answers.transition || false,
70
- testinNutin: answers.testinNutin || false,
71
-
72
- hasFeatures: answers.template || answers.stylinNutin || answers.i18n || answers.transition || answers.testinNutin,
73
-
74
- year: new Date().getFullYear(),
75
- packageManager: answers.packageManager || 'npm',
76
- ciCommand: ciCommand,
77
- version: version
78
- };
79
- }
80
-
81
- getTemplateDirectory(answers) {
82
- return path.join(__dirname, '../../templates/base');
32
+ getTemplateDirectory() {
33
+ return path.join(__dirname, '..', '..', 'templates', 'base');
83
34
  }
84
35
 
85
36
  async processTemplateDirectory(templateDir, outputDir, context) {
@@ -104,6 +55,27 @@ export class ProjectGenerator {
104
55
  }
105
56
  }
106
57
 
58
+ async processFeatureTemplates(projectPath, context) {
59
+ const featuresDir = path.join(__dirname, '..', '..', 'templates', 'features');
60
+
61
+ if (!(await fs.pathExists(featuresDir))) {
62
+ return;
63
+ }
64
+
65
+ const features = [ 'template', 'i18n', 'stylinNutin', 'testinNutin', 'deployHelper' ];
66
+
67
+ for (const feature of features) {
68
+ if (context[feature]) {
69
+ const featureTemplateDir = path.join(featuresDir, feature);
70
+
71
+ if (await fs.pathExists(featureTemplateDir)) {
72
+ print.info(` 🔧 Adding ${feature} feature...`);
73
+ await this.processTemplateDirectory(featureTemplateDir, projectPath, context);
74
+ }
75
+ }
76
+ }
77
+ }
78
+
107
79
  async processTemplateFile(templatePath, outputDir, fileName, context) {
108
80
  if (fileName.includes("test.js") && !(context.testinNutin)) {
109
81
  return;
@@ -134,76 +106,4 @@ export class ProjectGenerator {
134
106
  // print.info(`📄 Copied: ${fileName}`);
135
107
  }
136
108
  }
137
-
138
- async processFeatureTemplates(projectPath, context) {
139
- const featuresDir = path.join(__dirname, '../../templates/features');
140
-
141
- if (!(await fs.pathExists(featuresDir))) {
142
- return;
143
- }
144
-
145
- const features = [ 'template', 'i18n', 'stylinNutin', 'testinNutin' ];
146
-
147
- for (const feature of features) {
148
- if (context[feature]) {
149
- const featureTemplateDir = path.join(featuresDir, feature);
150
-
151
- if (await fs.pathExists(featureTemplateDir)) {
152
- print.info(`🔧 Adding ${feature} feature...`);
153
- await this.processTemplateDirectory(featureTemplateDir, projectPath, context);
154
- }
155
- }
156
- }
157
- }
158
-
159
- async generateJsonFiles(projectPath, answers) {
160
- await generatePackageJson(projectPath, answers);
161
- await generateTsconfigJson(projectPath, answers);
162
- if (answers.i18n) this.generateConfigFiles(projectPath);
163
- }
164
-
165
- async generateConfigFiles(projectPath) {
166
- const configPath = path.join(projectPath, 'config');
167
- await fs.ensureDir(configPath);
168
- const languages = {
169
- "languages": ["en"],
170
- "defaultLanguage": "en"
171
- };
172
-
173
- await fs.writeJSON(path.join(configPath, 'languages.json'), languages, { spaces: 2 });
174
- }
175
-
176
- async runPostSetupTasks(projectPath, answers) {
177
- await this.generateJsonFiles(projectPath, answers);
178
- await initializeGit(projectPath);
179
-
180
- await installDependencies(projectPath, answers.packageManager);
181
-
182
- await this.runSetupScripts(projectPath, answers);
183
- }
184
-
185
- async runSetupScripts(projectPath, answers) {
186
- print.section('⚙️ Running setup scripts...');
187
-
188
- try {
189
- const setupScriptsTemplate = path.join(__dirname, '..', '..', 'templates', 'scripts');
190
-
191
- if (await fs.pathExists(setupScriptsTemplate)) {
192
- const scriptsDir = path.join(projectPath, 'scripts');
193
- const context = this.buildContext(answers);
194
-
195
- await fs.ensureDir(scriptsDir);
196
- await this.processTemplateDirectory(setupScriptsTemplate, scriptsDir, context);
197
- }
198
- } catch (error) {
199
- print.boldError('⚠️ Warning: Could not set up build scripts');
200
- console.error(error);
201
- }
202
- }
203
- }
204
-
205
- export const projectGenerator = new ProjectGenerator();
206
-
207
- export async function createProject(answers) {
208
- return projectGenerator.createProject(answers);
209
109
  }
@@ -2,6 +2,6 @@ import { print } from './print.mjs';
2
2
  import { promiseExec } from './utils.mjs';
3
3
 
4
4
  export async function initializeGit(projectPath) {
5
- print.section('\n🔧 Initializing Git repository...');
5
+ print.section('⚙️ Initializing Git repository...');
6
6
  await promiseExec('git init', { cwd: projectPath });
7
7
  }
@@ -0,0 +1,31 @@
1
+ import path from 'path';
2
+ import * as fsExtra from 'fs-extra';
3
+ import { generatePackageJson, generateTsconfigJson } from './package-manager.mjs';
4
+
5
+ const fs = fsExtra.default;
6
+
7
+ export class JsonGenerator {
8
+ async generateJsonFiles(projectPath, context) {
9
+ await generatePackageJson(projectPath, context);
10
+ await generateTsconfigJson(projectPath, context);
11
+ this.generateConfigFiles(projectPath, context);
12
+ }
13
+
14
+ async generateConfigFiles(projectPath, context) {
15
+ const configPath = path.join(projectPath, 'config');
16
+
17
+ if (context.i18n) {
18
+ await fs.ensureDir(configPath);
19
+ await this.generateLanguageConfig(configPath);
20
+ }
21
+ }
22
+
23
+ async generateLanguageConfig(configPath) {
24
+ const languages = {
25
+ "languages": [ "en" ],
26
+ "defaultLanguage": "en"
27
+ };
28
+
29
+ await fs.writeJSON(path.join(configPath, 'languages.json'), languages, { spaces: 2 });
30
+ }
31
+ }
@@ -6,7 +6,7 @@ import * as path from 'path';
6
6
  const fs = fsExtra.default;
7
7
 
8
8
  export async function installDependencies(projectPath, packageManager) {
9
- print.section(`\n📦 Installing dependencies with ${packageManager}...`);
9
+ print.section(`📦 Installing dependencies with ${packageManager}...`);
10
10
 
11
11
  const installCommand = getInstallCommand(packageManager);
12
12
  await promiseExec(installCommand, { cwd: projectPath });
@@ -38,8 +38,8 @@ function getInstallCommand(packageManager) {
38
38
  }
39
39
  }
40
40
 
41
- function getScripts(answers) {
42
- const { testinNutin, packageManager, projectName } = answers;
41
+ function getScripts(context) {
42
+ const { testinNutin, packageManager, projectName, deployHelper } = context;
43
43
 
44
44
  const baseScripts = {
45
45
  "build": "node tools/builder/builder.js",
@@ -47,30 +47,33 @@ function getScripts(answers) {
47
47
  "serve:only": "node tools/dev/serve.js",
48
48
  "serve": `${packageManager} run build && ${packageManager} run serve:only`,
49
49
  "dev": "node tools/dev/dev-serve.js",
50
- "generate": "node tools/generator/generator.js",
50
+ "generate": "node tools/generator/generator.js"
51
+ };
52
+
53
+ const deployHelperScripts = {
51
54
  "docker:build": `docker build -t ${projectName} -f tools/deployment/Dockerfile .`,
52
- "docker:run": `docker run -p 9090:9090 --name ${projectName}-container ${projectName}:latest`,
55
+ "docker:run": `docker run -p 9090:9090 ${projectName}:latest`,
53
56
  "patch": `${packageManager} version patch -m 'CI/CD: Bump version to %s'`,
54
57
  "minor": `${packageManager} version minor -m 'CI/CD: Bump version to %s'`,
55
58
  "major": `${packageManager} version major -m 'CI/CD: Bump version to %s'`
56
- };
59
+ }
57
60
 
58
61
  const testinNutinScripts = {
59
62
  "test": "node testin-nutin/runner.js",
60
63
  "test:rebuild": `${packageManager} run build && ${packageManager} run test`,
61
64
  "test:watch": `${packageManager} run build && node testin-nutin/watch-tests.js`
62
- }
63
-
64
- return testinNutin
65
- ? {
66
- ...baseScripts,
67
- ...testinNutinScripts
68
- }
69
- : baseScripts;
65
+ }
66
+
67
+ let scripts = { ...baseScripts };
68
+
69
+ if (testinNutin) scripts = { ...scripts, ...testinNutinScripts };
70
+ if (deployHelper) scripts = { ...scripts, ...deployHelperScripts };
71
+
72
+ return scripts;
70
73
  }
71
74
 
72
- export async function generatePackageJson(projectPath, answers) {
73
- const { testinNutin, projectName, packageManager } = answers;
75
+ export async function generatePackageJson(projectPath, context) {
76
+ const { testinNutin, projectName } = context;
74
77
 
75
78
  const devDependencies = {
76
79
  "chokidar": "^4.0.3",
@@ -84,7 +87,7 @@ export async function generatePackageJson(projectPath, answers) {
84
87
  })
85
88
  };
86
89
 
87
- const scripts = getScripts(answers);
90
+ const scripts = getScripts(context);
88
91
 
89
92
  const packageJson = {
90
93
  "name": projectName,
@@ -107,7 +110,7 @@ export async function generatePackageJson(projectPath, answers) {
107
110
  await fs.writeJSON(path.join(projectPath, 'package.json'), packageJson, { spaces: 2 });
108
111
  }
109
112
 
110
- export async function generateTsconfigJson(projectPath, answers) {
113
+ export async function generateTsconfigJson(projectPath, context) {
111
114
  const tsconfig = {
112
115
  "compilerOptions": {
113
116
  "baseUrl": "./",
@@ -0,0 +1,53 @@
1
+ import path from 'path';
2
+ import * as fsExtra from 'fs-extra';
3
+ import { print } from './print.mjs';
4
+ import { initializeGit } from './git-manager.mjs';
5
+ import { installDependencies } from './package-manager.mjs';
6
+ import { FileGenerator } from './file-generator.mjs';
7
+ import { JsonGenerator } from './json-generator.mjs';
8
+ import { ContextBuilder } from './context-builder.mjs';
9
+
10
+ const fs = fsExtra.default;
11
+
12
+ export class ProjectGenerator {
13
+ constructor() {
14
+ this.fileGenerator = new FileGenerator();
15
+ this.jsonGenerator = new JsonGenerator();
16
+ this.builder = new ContextBuilder();
17
+ }
18
+
19
+ async createProject(answers) {
20
+ const { projectName } = answers;
21
+ const projectPath = path.join(process.cwd(), projectName);
22
+ const context = this.builder.buildContext(answers);
23
+
24
+ await this.validateProjectPath(projectPath, projectName);
25
+ await this.setupProjectStructure(projectPath, context);
26
+ await this.runPostSetupTasks(projectPath, context);
27
+ }
28
+
29
+ async validateProjectPath(projectPath, projectName) {
30
+ if (await fs.pathExists(projectPath)) {
31
+ throw new Error(`Directory ${projectName} already exists`);
32
+ }
33
+ }
34
+
35
+ async setupProjectStructure(projectPath, context) {
36
+ print.boldHead(`\n📁 Creating project in ${projectPath}...\n`);
37
+
38
+ await fs.ensureDir(projectPath);
39
+ await this.fileGenerator.generateProjectFromTemplates(projectPath, context);
40
+ }
41
+
42
+ async runPostSetupTasks(projectPath, context) {
43
+ await this.jsonGenerator.generateJsonFiles(projectPath, context);
44
+ await initializeGit(projectPath);
45
+ await installDependencies(projectPath, context.packageManager);
46
+ }
47
+ }
48
+
49
+ export const projectGenerator = new ProjectGenerator();
50
+
51
+ export async function createProject(answers) {
52
+ return projectGenerator.createProject(answers);
53
+ }
@@ -1,6 +1,8 @@
1
1
  import inquirer from 'inquirer';
2
+ import { defaults } from './context-builder.mjs';
2
3
 
3
4
  export async function promptUser(initialName, cliOptions = {}) {
5
+
4
6
  let projectName = initialName;
5
7
  if (!projectName) {
6
8
  const nameAnswer = await inquirer.prompt([
@@ -8,7 +10,7 @@ export async function promptUser(initialName, cliOptions = {}) {
8
10
  type: 'input',
9
11
  name: 'projectName',
10
12
  message: 'Project name:',
11
- default: 'my-nutin-app',
13
+ default: defaults.projectName,
12
14
  validate: validateProjectName
13
15
  }
14
16
  ]);
@@ -23,7 +25,7 @@ export async function promptUser(initialName, cliOptions = {}) {
23
25
  name: 'packageManager',
24
26
  message: 'Package manager:',
25
27
  choices: ['npm', 'yarn', 'pnpm', 'bun'],
26
- default: 'npm'
28
+ default: defaults.packageManager
27
29
  }
28
30
  ]);
29
31
  packageManager = pmAnswer.packageManager;
@@ -33,11 +35,12 @@ export async function promptUser(initialName, cliOptions = {}) {
33
35
  return {
34
36
  projectName,
35
37
  packageManager,
36
- stylinNutin: cliOptions.stylinNutin || true,
37
- template: cliOptions.template || true,
38
- i18n: cliOptions.i18n || true,
39
- testinNutin: cliOptions.testinNutin || false,
40
- transition: cliOptions.transitions || false
38
+ stylinNutin: cliOptions.stylinNutin,
39
+ template: cliOptions.template,
40
+ i18n: cliOptions.i18n,
41
+ deployHelper: cliOptions.deployHelper,
42
+ testinNutin: cliOptions.testinNutin,
43
+ transition: cliOptions.transitions
41
44
  };
42
45
  }
43
46
 
@@ -48,7 +51,7 @@ export async function promptUser(initialName, cliOptions = {}) {
48
51
  type: 'confirm',
49
52
  name: 'stylinNutin',
50
53
  message: 'Use built-in SCSS utility classes ?',
51
- default: true
54
+ default: defaults.stylinNutin
52
55
  });
53
56
  }
54
57
 
@@ -57,7 +60,7 @@ export async function promptUser(initialName, cliOptions = {}) {
57
60
  type: 'confirm',
58
61
  name: 'template',
59
62
  message: 'Use external templates ?',
60
- default: true
63
+ default: defaults.template
61
64
  });
62
65
  }
63
66
 
@@ -66,16 +69,16 @@ export async function promptUser(initialName, cliOptions = {}) {
66
69
  type: 'confirm',
67
70
  name: 'i18n',
68
71
  message: 'Use i18n with json-based content ?',
69
- default: true
72
+ default: defaults.i18n
70
73
  });
71
74
  }
72
75
 
73
- if (cliOptions.transition === undefined) {
76
+ if (cliOptions.deployHelper === undefined) {
74
77
  questions.push({
75
78
  type: 'confirm',
76
- name: 'transition',
77
- message: 'Use animated view transitions?',
78
- default: false
79
+ name: 'deployHelper',
80
+ message: 'Use Docker & deployment helpers ?',
81
+ default: defaults.deployHelper
79
82
  });
80
83
  }
81
84
 
@@ -84,7 +87,16 @@ export async function promptUser(initialName, cliOptions = {}) {
84
87
  type: 'confirm',
85
88
  name: 'testinNutin',
86
89
  message: 'Use testin-nutin toolkit?',
87
- default: true
90
+ default: defaults.testinNutin
91
+ });
92
+ }
93
+
94
+ if (cliOptions.transition === undefined) {
95
+ questions.push({
96
+ type: 'confirm',
97
+ name: 'transition',
98
+ message: 'Use animated view transitions?',
99
+ default: defaults.transition
88
100
  });
89
101
  }
90
102
 
@@ -96,6 +108,7 @@ export async function promptUser(initialName, cliOptions = {}) {
96
108
  template: cliOptions.template ?? answers.template,
97
109
  stylinNutin: cliOptions.stylinNutin ?? answers.stylinNutin,
98
110
  i18n: cliOptions.i18n ?? answers.i18n,
111
+ deployHelper: cliOptions.deployHelper ?? answers.deployHelper,
99
112
  transition: cliOptions.transition ?? answers.transition,
100
113
  testinNutin: cliOptions.testinNutin ?? answers.testinNutin
101
114
  };
@@ -36,7 +36,7 @@ export class TemplateCompiler {
36
36
 
37
37
  Handlebars.registerHelper('hasAnyFeature', function(options) {
38
38
  const context = options.data.root;
39
- return context.i18n || context.template || context.testing
39
+ return context.i18n || context.template || context.testinNutin || context.stylinNutin
40
40
  });
41
41
  }
42
42