@acorex/platform-generator 21.0.0-next.3 → 21.0.0-next.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -6
- package/package.json +9 -3
- package/src/bin/create-acorex-app.js +116 -0
- package/src/bin/standalone-scaffold.js +533 -0
- package/src/generators/app-module/files/project.json.template +194 -48
- package/src/generators/app-module/files/src/app/app.config.api.ts.template +145 -0
- package/src/generators/app-module/files/src/app/app.config.full.ts.template +216 -0
- package/src/generators/app-module/files/src/app/app.config.minimal.ts.template +162 -0
- package/src/generators/app-module/files/src/app/app.routes.ts.template +1 -1
- package/src/generators/app-module/files/src/app/modules/common/default-settings.providers.ts.template +16 -8
- package/src/generators/app-module/files/src/app/modules/layout/layout-root.module.ts.template +3 -6
- package/src/generators/app-module/files/src/app/modules/root/root.module.ts.template +2 -2
- package/src/generators/app-module/files/src/assets/i18n/en-US/activity-log.json +10 -1
- package/src/generators/app-module/files/src/assets/i18n/en-US/auth.json +12 -0
- package/src/generators/app-module/files/src/assets/i18n/en-US/data-management.json +4 -0
- package/src/generators/app-module/files/src/assets/i18n/en-US/general.json +5 -0
- package/src/generators/app-module/files/src/assets/i18n/en-US/organization-management.json +0 -8
- package/src/generators/app-module/files/src/assets/i18n/en-US/questionnaire.json +77 -0
- package/src/generators/app-module/files/src/assets/i18n/fa-IR/activity-log.json +10 -1
- package/src/generators/app-module/files/src/assets/i18n/fa-IR/auth.json +12 -0
- package/src/generators/app-module/files/src/assets/i18n/fa-IR/data-management.json +5 -1
- package/src/generators/app-module/files/src/assets/i18n/fa-IR/general.json +7 -3
- package/src/generators/app-module/files/src/assets/i18n/fa-IR/human-capital-management.json +4 -7
- package/src/generators/app-module/files/src/assets/i18n/fa-IR/organization-management.json +0 -8
- package/src/generators/app-module/files/src/assets/i18n/fa-IR/questionnaire.json +77 -0
- package/src/generators/app-module/files/src/environments/environment.dev.ts.template +45 -16
- package/src/generators/app-module/files/src/environments/environment.prod.ts.template +33 -0
- package/src/generators/app-module/files/src/environments/environment.ts.template +23 -7
- package/src/generators/app-module/files/src/main.ts.template +42 -2
- package/src/generators/app-module/generator.d.ts +10 -1
- package/src/generators/app-module/generator.js +100 -12
- package/src/generators/app-module/generator.js.map +1 -1
- package/src/generators/app-module/schema.d.ts +28 -0
- package/src/generators/app-module/schema.json +74 -5
- package/src/generators/standalone-workspace/versions.json +53 -0
- package/src/generators/app-module/files/src/app/app.config.ts.template +0 -158
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Scaffolds a new Nx + Angular workspace with an ACoreX Platform app from bundled templates.
|
|
4
|
+
* Templates live under generator/src/generators (app-module/files + standalone-workspace).
|
|
5
|
+
* Requires published @acorex/* packages (see generator/src/generators/standalone-workspace/versions.json).
|
|
6
|
+
*/
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { spawnSync } = require('child_process');
|
|
10
|
+
const ejs = require('ejs');
|
|
11
|
+
|
|
12
|
+
const BIN_DIR = __dirname;
|
|
13
|
+
const GENERATORS_ROOT = path.join(BIN_DIR, '..', 'generators');
|
|
14
|
+
const APP_MODULE_FILES = path.join(GENERATORS_ROOT, 'app-module', 'files');
|
|
15
|
+
const VERSIONS_JSON = path.join(GENERATORS_ROOT, 'standalone-workspace', 'versions.json');
|
|
16
|
+
const ROOT_TEMPLATES = path.join(GENERATORS_ROOT, 'standalone-workspace', 'root');
|
|
17
|
+
|
|
18
|
+
// Office / archives and fonts must never be read as UTF-8: random bytes can match "<%" and break EJS.
|
|
19
|
+
const BINARY_EXT =
|
|
20
|
+
/\.(ico|png|jpe?g|gif|webp|woff2?|ttf|otf|eot|pdf|svg|docx?|pptx?|xlsx?|xls|zip|7z|mp4|webm|mp3|wasm)$/i;
|
|
21
|
+
|
|
22
|
+
const MINIMAL_I18N_BASENAMES = new Set([
|
|
23
|
+
'module.json',
|
|
24
|
+
'acorex.json',
|
|
25
|
+
'auth.json',
|
|
26
|
+
'general.json',
|
|
27
|
+
'layout.json',
|
|
28
|
+
'settings.json',
|
|
29
|
+
'regional.json',
|
|
30
|
+
'help-desk.json',
|
|
31
|
+
'dashboard.json',
|
|
32
|
+
'notification-management.json',
|
|
33
|
+
'security-management.json',
|
|
34
|
+
'organization-management.json',
|
|
35
|
+
'platform-management.json',
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
function upperCase(text) {
|
|
39
|
+
return String(text).toUpperCase();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function toPascalCase(text) {
|
|
43
|
+
return String(text)
|
|
44
|
+
.split(/[-_\s.]+/)
|
|
45
|
+
.filter(Boolean)
|
|
46
|
+
.map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
|
|
47
|
+
.join('');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function toConstPrefix(text) {
|
|
51
|
+
return String(text)
|
|
52
|
+
.split(/[-_\s.]+/)
|
|
53
|
+
.filter(Boolean)
|
|
54
|
+
.join('_')
|
|
55
|
+
.toUpperCase();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function normalizeOptions(raw) {
|
|
59
|
+
const preset = raw.preset ?? 'full';
|
|
60
|
+
const entityStorage = raw.entityStorage ?? 'dexie';
|
|
61
|
+
const connectivity = raw.connectivity ?? 'mock';
|
|
62
|
+
if (connectivity === 'none') {
|
|
63
|
+
console.warn(
|
|
64
|
+
'[acorex-platform] connectivity=none is not fully supported yet; using mock (IndexedDB / mock auth).',
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const connectivityMode = connectivity === 'api' ? 'api' : 'mock';
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
name: raw.name,
|
|
71
|
+
title: raw.title,
|
|
72
|
+
copyright: raw.copyright ?? '© 2025',
|
|
73
|
+
baseUrl: raw.baseUrl ?? 'https://localhost:44320/api',
|
|
74
|
+
preset,
|
|
75
|
+
entityStorage,
|
|
76
|
+
entityStorageBackend: entityStorage,
|
|
77
|
+
connectivityMode,
|
|
78
|
+
includeApiConfig: connectivityMode === 'api',
|
|
79
|
+
includeConsoleLogCapture: raw.includeConsoleLogCapture ?? false,
|
|
80
|
+
firebaseApiKey: raw.firebaseApiKey ?? 'YOUR_FIREBASE_API_KEY',
|
|
81
|
+
firebaseAuthDomain: raw.firebaseAuthDomain ?? 'your-project.firebaseapp.com',
|
|
82
|
+
firebaseProjectId: raw.firebaseProjectId ?? 'your-project-id',
|
|
83
|
+
firebaseStorageBucket: raw.firebaseStorageBucket ?? 'your-project.appspot.com',
|
|
84
|
+
firebaseMessagingSenderId: raw.firebaseMessagingSenderId ?? '000000000000',
|
|
85
|
+
firebaseAppId: raw.firebaseAppId ?? '1:000000000000:web:000000000000000000000',
|
|
86
|
+
upperCase,
|
|
87
|
+
className: toPascalCase(raw.name),
|
|
88
|
+
constPrefix: toConstPrefix(raw.name),
|
|
89
|
+
isFullPreset: preset === 'full',
|
|
90
|
+
isMinimalPreset: preset === 'minimal',
|
|
91
|
+
includeConnectivityPackage: connectivityMode === 'mock' || connectivityMode === 'api',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function loadVersions() {
|
|
96
|
+
const raw = fs.readFileSync(VERSIONS_JSON, 'utf8');
|
|
97
|
+
return JSON.parse(raw);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function isBinaryFile(filePath) {
|
|
101
|
+
return BINARY_EXT.test(filePath);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function renderTemplateString(content, vars) {
|
|
105
|
+
return ejs.render(content, vars, { async: false });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Copies app-module/files to projectRoot (e.g. apps/myapp) with EJS rendering for .template files.
|
|
110
|
+
*/
|
|
111
|
+
function copyAppModuleTemplates(projectRoot, vars) {
|
|
112
|
+
function walk(srcRel, destRel) {
|
|
113
|
+
const srcAbs = path.join(APP_MODULE_FILES, srcRel);
|
|
114
|
+
const st = fs.statSync(srcAbs);
|
|
115
|
+
if (st.isDirectory()) {
|
|
116
|
+
fs.mkdirSync(path.join(projectRoot, destRel), { recursive: true });
|
|
117
|
+
for (const ent of fs.readdirSync(srcAbs, { withFileTypes: true })) {
|
|
118
|
+
walk(path.join(srcRel, ent.name), path.join(destRel, ent.name));
|
|
119
|
+
}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const base = path.basename(srcAbs);
|
|
123
|
+
if (isBinaryFile(srcAbs)) {
|
|
124
|
+
const destAbs = path.join(projectRoot, destRel);
|
|
125
|
+
fs.mkdirSync(path.dirname(destAbs), { recursive: true });
|
|
126
|
+
fs.copyFileSync(srcAbs, destAbs);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const rawBuf = fs.readFileSync(srcAbs);
|
|
130
|
+
// Unknown extension but clearly binary (e.g. mis-tagged assets): never pass to EJS.
|
|
131
|
+
if (rawBuf.includes(0)) {
|
|
132
|
+
const destAbs = path.join(projectRoot, destRel);
|
|
133
|
+
fs.mkdirSync(path.dirname(destAbs), { recursive: true });
|
|
134
|
+
fs.writeFileSync(destAbs, rawBuf);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
let buf = rawBuf.toString('utf8');
|
|
138
|
+
if (base.endsWith('.template')) {
|
|
139
|
+
buf = renderTemplateString(buf, vars);
|
|
140
|
+
const outName = base.replace(/\.template$/, '');
|
|
141
|
+
const outPath = path.join(projectRoot, path.dirname(destRel), outName);
|
|
142
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
143
|
+
fs.writeFileSync(outPath, buf, 'utf8');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const destAbs = path.join(projectRoot, destRel);
|
|
147
|
+
fs.mkdirSync(path.dirname(destAbs), { recursive: true });
|
|
148
|
+
fs.writeFileSync(destAbs, buf, 'utf8');
|
|
149
|
+
}
|
|
150
|
+
walk('', '');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function mergeAppConfigFromPreset(projectRoot, preset) {
|
|
154
|
+
const src = path.join(projectRoot, 'src', 'app');
|
|
155
|
+
const fullPath = path.join(src, 'app.config.full.ts');
|
|
156
|
+
const minPath = path.join(src, 'app.config.minimal.ts');
|
|
157
|
+
const chosen = preset === 'minimal' ? minPath : fullPath;
|
|
158
|
+
if (!fs.existsSync(chosen)) {
|
|
159
|
+
throw new Error(`[standalone] Missing ${chosen}`);
|
|
160
|
+
}
|
|
161
|
+
const body = fs.readFileSync(chosen, 'utf8');
|
|
162
|
+
fs.writeFileSync(path.join(src, 'app.config.ts'), body, 'utf8');
|
|
163
|
+
try {
|
|
164
|
+
fs.unlinkSync(fullPath);
|
|
165
|
+
} catch (_) {}
|
|
166
|
+
try {
|
|
167
|
+
fs.unlinkSync(minPath);
|
|
168
|
+
} catch (_) {}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function applyApiAppConfig(projectRoot) {
|
|
172
|
+
const src = path.join(projectRoot, 'src', 'app');
|
|
173
|
+
const apiPath = path.join(src, 'app.config.api.ts');
|
|
174
|
+
if (!fs.existsSync(apiPath)) return;
|
|
175
|
+
const body = fs.readFileSync(apiPath, 'utf8');
|
|
176
|
+
fs.writeFileSync(path.join(src, 'app.config.ts'), body, 'utf8');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function deleteIfExists(p) {
|
|
180
|
+
try {
|
|
181
|
+
fs.unlinkSync(p);
|
|
182
|
+
} catch (_) {}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function pruneMinimalI18n(projectRoot, appName) {
|
|
186
|
+
const locales = ['en-US', 'fa-IR'];
|
|
187
|
+
for (const loc of locales) {
|
|
188
|
+
const dir = path.join(projectRoot, 'src', 'assets', 'i18n', loc);
|
|
189
|
+
if (!fs.existsSync(dir)) continue;
|
|
190
|
+
for (const file of fs.readdirSync(dir)) {
|
|
191
|
+
if (!file.endsWith('.json')) continue;
|
|
192
|
+
const moduleFile = `${appName}-module.json`;
|
|
193
|
+
if (file === moduleFile) continue;
|
|
194
|
+
if (MINIMAL_I18N_BASENAMES.has(file)) continue;
|
|
195
|
+
fs.unlinkSync(path.join(dir, file));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function renameRootModule(projectRoot, appName) {
|
|
201
|
+
const rootDir = path.join(projectRoot, 'src', 'app', 'modules', 'root');
|
|
202
|
+
const targetDir = path.join(projectRoot, 'src', 'app', 'modules', appName);
|
|
203
|
+
// Rename the folder first; pre-creating targetDir would block directory rename on all OSes.
|
|
204
|
+
fs.renameSync(rootDir, targetDir);
|
|
205
|
+
const oldFile = path.join(targetDir, 'root.module.ts');
|
|
206
|
+
const newFile = path.join(targetDir, `${appName}-root.module.ts`);
|
|
207
|
+
fs.renameSync(oldFile, newFile);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function renameModuleI18n(projectRoot, appName) {
|
|
211
|
+
const en = path.join(projectRoot, 'src', 'assets', 'i18n', 'en-US', 'module.json');
|
|
212
|
+
const fa = path.join(projectRoot, 'src', 'assets', 'i18n', 'fa-IR', 'module.json');
|
|
213
|
+
const enTo = path.join(projectRoot, 'src', 'assets', 'i18n', 'en-US', `${appName}-module.json`);
|
|
214
|
+
const faTo = path.join(projectRoot, 'src', 'assets', 'i18n', 'fa-IR', `${appName}-module.json`);
|
|
215
|
+
if (fs.existsSync(en)) fs.renameSync(en, enTo);
|
|
216
|
+
if (fs.existsSync(fa)) fs.renameSync(fa, faTo);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function writeRootWorkspace(outRoot, vars, versions) {
|
|
220
|
+
fs.mkdirSync(outRoot, { recursive: true });
|
|
221
|
+
const nxJson = {
|
|
222
|
+
$schema: './node_modules/nx/schemas/nx-schema.json',
|
|
223
|
+
namedInputs: {
|
|
224
|
+
default: ['{projectRoot}/**/*', 'sharedGlobals'],
|
|
225
|
+
production: ['default'],
|
|
226
|
+
sharedGlobals: [],
|
|
227
|
+
},
|
|
228
|
+
targetDefaults: {
|
|
229
|
+
build: {
|
|
230
|
+
dependsOn: ['^build'],
|
|
231
|
+
inputs: ['production', '^production'],
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
plugins: [
|
|
235
|
+
{
|
|
236
|
+
plugin: '@nx/angular/plugin',
|
|
237
|
+
options: {
|
|
238
|
+
buildTargetName: 'build',
|
|
239
|
+
serveTargetName: 'serve',
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
],
|
|
243
|
+
};
|
|
244
|
+
fs.writeFileSync(path.join(outRoot, 'nx.json'), JSON.stringify(nxJson, null, 2), 'utf8');
|
|
245
|
+
|
|
246
|
+
const tsconfigBase = {
|
|
247
|
+
compileOnSave: false,
|
|
248
|
+
compilerOptions: {
|
|
249
|
+
rootDir: '.',
|
|
250
|
+
sourceMap: true,
|
|
251
|
+
declaration: false,
|
|
252
|
+
moduleResolution: 'bundler',
|
|
253
|
+
experimentalDecorators: true,
|
|
254
|
+
importHelpers: true,
|
|
255
|
+
target: 'es2022',
|
|
256
|
+
module: 'esnext',
|
|
257
|
+
lib: ['es2020', 'dom'],
|
|
258
|
+
skipLibCheck: true,
|
|
259
|
+
baseUrl: '.',
|
|
260
|
+
paths: {},
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
fs.writeFileSync(path.join(outRoot, 'tsconfig.base.json'), JSON.stringify(tsconfigBase, null, 2), 'utf8');
|
|
264
|
+
|
|
265
|
+
const pkg = {
|
|
266
|
+
name: vars.workspacePackageName ?? vars.name,
|
|
267
|
+
version: '0.0.1',
|
|
268
|
+
license: 'MIT',
|
|
269
|
+
private: true,
|
|
270
|
+
scripts: {
|
|
271
|
+
start: `nx serve ${vars.name}`,
|
|
272
|
+
build: `nx build ${vars.name}`,
|
|
273
|
+
'build:prod': `nx run ${vars.name}:build-esbuild:build-prod`,
|
|
274
|
+
},
|
|
275
|
+
dependencies: {},
|
|
276
|
+
devDependencies: {},
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const deps = { ...versions };
|
|
280
|
+
delete deps.$comment;
|
|
281
|
+
const runtimeKeys = [
|
|
282
|
+
'@acorex/cdk',
|
|
283
|
+
'@acorex/charts',
|
|
284
|
+
'@acorex/components',
|
|
285
|
+
'@acorex/core',
|
|
286
|
+
'@acorex/styles',
|
|
287
|
+
'@acorex/platform',
|
|
288
|
+
'@acorex/modules',
|
|
289
|
+
'@angular/animations',
|
|
290
|
+
'@angular/cdk',
|
|
291
|
+
'@angular/common',
|
|
292
|
+
'@angular/compiler',
|
|
293
|
+
'@angular/core',
|
|
294
|
+
'@angular/forms',
|
|
295
|
+
'@angular/platform-browser',
|
|
296
|
+
'@angular/platform-browser-dynamic',
|
|
297
|
+
'@angular/router',
|
|
298
|
+
'@angular/service-worker',
|
|
299
|
+
'@ngrx/signals',
|
|
300
|
+
'angular-oauth2-oidc',
|
|
301
|
+
'dexie',
|
|
302
|
+
'dom-to-image',
|
|
303
|
+
'firebase',
|
|
304
|
+
'lodash-es',
|
|
305
|
+
'memoizee',
|
|
306
|
+
'rxjs',
|
|
307
|
+
'tailwindcss-animate',
|
|
308
|
+
'tslib',
|
|
309
|
+
'zone.js',
|
|
310
|
+
];
|
|
311
|
+
if (vars.includeConnectivityPackage) {
|
|
312
|
+
runtimeKeys.push('@acorex/connectivity');
|
|
313
|
+
}
|
|
314
|
+
for (const k of runtimeKeys) {
|
|
315
|
+
if (deps[k] != null) pkg.dependencies[k] = deps[k];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const devKeys = [
|
|
319
|
+
'@angular-devkit/build-angular',
|
|
320
|
+
'@angular/cli',
|
|
321
|
+
'@angular/compiler-cli',
|
|
322
|
+
'@angular/language-service',
|
|
323
|
+
'@nx/angular',
|
|
324
|
+
'@nx/eslint-plugin',
|
|
325
|
+
'@nx/jest',
|
|
326
|
+
'@nx/js',
|
|
327
|
+
'@nx/workspace',
|
|
328
|
+
'@schematics/angular',
|
|
329
|
+
'angular-eslint',
|
|
330
|
+
'eslint',
|
|
331
|
+
'jest',
|
|
332
|
+
'jest-environment-jsdom',
|
|
333
|
+
'jest-preset-angular',
|
|
334
|
+
'ng-packagr',
|
|
335
|
+
'nx',
|
|
336
|
+
'prettier',
|
|
337
|
+
'sass',
|
|
338
|
+
'tailwindcss',
|
|
339
|
+
'typescript',
|
|
340
|
+
];
|
|
341
|
+
for (const k of devKeys) {
|
|
342
|
+
const v = versions[k] ?? pickDevVersion(k);
|
|
343
|
+
if (v) pkg.devDependencies[k] = v;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
fs.writeFileSync(path.join(outRoot, 'package.json'), JSON.stringify(pkg, null, 2), 'utf8');
|
|
347
|
+
|
|
348
|
+
const gitignore = [
|
|
349
|
+
'node_modules',
|
|
350
|
+
'dist',
|
|
351
|
+
'.angular',
|
|
352
|
+
'.nx/cache',
|
|
353
|
+
'coverage',
|
|
354
|
+
'*.log',
|
|
355
|
+
'.env',
|
|
356
|
+
'',
|
|
357
|
+
].join('\n');
|
|
358
|
+
fs.writeFileSync(path.join(outRoot, '.gitignore'), gitignore, 'utf8');
|
|
359
|
+
|
|
360
|
+
const readme = renderTemplateString(
|
|
361
|
+
`# <%= title %>\n\nGenerated by @acorex/platform-generator.\n\n## Scripts\n\n- npm start — dev server\n- npm run build — production build\n\nInstall dependencies: npm install\n`,
|
|
362
|
+
vars,
|
|
363
|
+
);
|
|
364
|
+
fs.writeFileSync(path.join(outRoot, 'README.md'), readme, 'utf8');
|
|
365
|
+
|
|
366
|
+
const jestPreset = "const { getJestProjectsAsync } = require('@nx/jest');\nmodule.exports = async () => ({\n projects: await getJestProjectsAsync(),\n});\n";
|
|
367
|
+
fs.writeFileSync(path.join(outRoot, 'jest.preset.js'), jestPreset, 'utf8');
|
|
368
|
+
|
|
369
|
+
fs.writeFileSync(path.join(outRoot, 'eslint.config.mjs'), `export default [];\n`, 'utf8');
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function pickDevVersion(key) {
|
|
373
|
+
const map = {
|
|
374
|
+
'@angular-devkit/build-angular': '~20.3.0',
|
|
375
|
+
'@angular/cli': '~20.3.0',
|
|
376
|
+
'@angular/compiler-cli': '~20.3.0',
|
|
377
|
+
'@angular/language-service': '~20.3.0',
|
|
378
|
+
'@nx/angular': '21.6.3',
|
|
379
|
+
'@nx/eslint-plugin': '21.6.3',
|
|
380
|
+
'@nx/jest': '21.6.3',
|
|
381
|
+
'@nx/js': '21.6.3',
|
|
382
|
+
'@nx/workspace': '21.6.3',
|
|
383
|
+
'@schematics/angular': '20.3.4',
|
|
384
|
+
'angular-eslint': '^20.3.0',
|
|
385
|
+
eslint: '9.26.0',
|
|
386
|
+
jest: '^29.7.0',
|
|
387
|
+
'jest-environment-jsdom': '^29.7.0',
|
|
388
|
+
'jest-preset-angular': '^14.6.0',
|
|
389
|
+
'ng-packagr': '20.3.0',
|
|
390
|
+
nx: '21.6.3',
|
|
391
|
+
prettier: '^3.5.3',
|
|
392
|
+
sass: '^1.83.4',
|
|
393
|
+
tailwindcss: '^3.4.17',
|
|
394
|
+
typescript: '5.9.2',
|
|
395
|
+
};
|
|
396
|
+
return map[key];
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function parseArgs(argv) {
|
|
400
|
+
const flags = {};
|
|
401
|
+
const positional = [];
|
|
402
|
+
for (let i = 0; i < argv.length; i++) {
|
|
403
|
+
const a = argv[i];
|
|
404
|
+
if (a === '--workspace' && argv[i + 1]) {
|
|
405
|
+
flags.workspace = argv[++i];
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
if (a.startsWith('--')) {
|
|
409
|
+
const eq = a.indexOf('=');
|
|
410
|
+
if (eq !== -1) {
|
|
411
|
+
flags[a.slice(2, eq)] = a.slice(eq + 1);
|
|
412
|
+
} else {
|
|
413
|
+
const key = a.slice(2);
|
|
414
|
+
const next = argv[i + 1];
|
|
415
|
+
if (next && !next.startsWith('--')) {
|
|
416
|
+
flags[key] = next;
|
|
417
|
+
i++;
|
|
418
|
+
} else {
|
|
419
|
+
flags[key] = true;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
positional.push(a);
|
|
425
|
+
}
|
|
426
|
+
return { flags, positional };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function runStandalone(argv) {
|
|
430
|
+
const { flags, positional } = parseArgs(argv);
|
|
431
|
+
if (flags.help || flags.h) {
|
|
432
|
+
console.log(`
|
|
433
|
+
Usage:
|
|
434
|
+
acorex-platform <project-directory> "<app title>" [options]
|
|
435
|
+
|
|
436
|
+
Options:
|
|
437
|
+
--preset=full|minimal Full module set vs minimal (default: full)
|
|
438
|
+
--connectivity=mock|api|none Mock vs API app config; none warns and falls back to mock (default: mock)
|
|
439
|
+
--copyright="..." Copyright line in platform config
|
|
440
|
+
--baseUrl=... API base URL in environment
|
|
441
|
+
--skip-install Do not run npm install
|
|
442
|
+
--dry-run Print actions only
|
|
443
|
+
|
|
444
|
+
Examples:
|
|
445
|
+
npx @acorex/platform-generator@latest my-app "My App"
|
|
446
|
+
npx @acorex/platform-generator@latest my-app "My App" --preset=minimal --connectivity=api
|
|
447
|
+
`);
|
|
448
|
+
process.exit(0);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const dryRun = Boolean(flags['dry-run']);
|
|
452
|
+
const skipInstall = Boolean(flags['skip-install']);
|
|
453
|
+
const outDir = positional[0];
|
|
454
|
+
const title = positional[1] ?? 'ACoreX App';
|
|
455
|
+
|
|
456
|
+
if (!outDir) {
|
|
457
|
+
console.error('[acorex-platform] Missing project directory. Example: acorex-platform my-app "My App"');
|
|
458
|
+
process.exit(1);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const resolvedOut = path.resolve(process.cwd(), outDir);
|
|
462
|
+
const name = path.basename(resolvedOut);
|
|
463
|
+
|
|
464
|
+
if (!dryRun && fs.existsSync(resolvedOut) && fs.readdirSync(resolvedOut).length > 0) {
|
|
465
|
+
console.error(`[acorex-platform] Directory is not empty: ${resolvedOut}`);
|
|
466
|
+
process.exit(1);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const versions = loadVersions();
|
|
470
|
+
const vars = normalizeOptions({
|
|
471
|
+
name,
|
|
472
|
+
title,
|
|
473
|
+
preset: flags.preset,
|
|
474
|
+
connectivity: flags.connectivity,
|
|
475
|
+
copyright: flags.copyright,
|
|
476
|
+
baseUrl: flags.baseUrl,
|
|
477
|
+
includeConsoleLogCapture: flags['include-console-log-capture'] === true || flags['include-console-log-capture'] === 'true',
|
|
478
|
+
});
|
|
479
|
+
vars.workspacePackageName =
|
|
480
|
+
name
|
|
481
|
+
.replace(/[^a-z0-9-]/gi, '-')
|
|
482
|
+
.toLowerCase()
|
|
483
|
+
.replace(/-+/g, '-')
|
|
484
|
+
.replace(/^-+|-+$/g, '') || 'acorex-app';
|
|
485
|
+
|
|
486
|
+
if (dryRun) {
|
|
487
|
+
console.log('[dry-run] Would create:', resolvedOut);
|
|
488
|
+
console.log('[dry-run] Options:', vars);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
fs.mkdirSync(resolvedOut, { recursive: true });
|
|
493
|
+
|
|
494
|
+
writeRootWorkspace(resolvedOut, vars, versions);
|
|
495
|
+
|
|
496
|
+
const appRoot = path.join(resolvedOut, 'apps', name);
|
|
497
|
+
fs.mkdirSync(appRoot, { recursive: true });
|
|
498
|
+
|
|
499
|
+
copyAppModuleTemplates(appRoot, vars);
|
|
500
|
+
|
|
501
|
+
mergeAppConfigFromPreset(appRoot, vars.preset);
|
|
502
|
+
|
|
503
|
+
if (vars.connectivityMode === 'api') {
|
|
504
|
+
applyApiAppConfig(appRoot);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (!vars.includeApiConfig) {
|
|
508
|
+
deleteIfExists(path.join(appRoot, 'src', 'app', 'app.config.api.ts'));
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (vars.isMinimalPreset) {
|
|
512
|
+
pruneMinimalI18n(appRoot, name);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
renameRootModule(appRoot, name);
|
|
516
|
+
renameModuleI18n(appRoot, name);
|
|
517
|
+
|
|
518
|
+
console.log(`\nCreated workspace at ${resolvedOut}`);
|
|
519
|
+
console.log('Next: cd ' + path.basename(resolvedOut) + ' && npm install && npm start\n');
|
|
520
|
+
|
|
521
|
+
if (!skipInstall) {
|
|
522
|
+
const isWindows = process.platform === 'win32';
|
|
523
|
+
const npmCmd = isWindows ? 'npm.cmd' : 'npm';
|
|
524
|
+
const r = spawnSync(npmCmd, ['install'], { cwd: resolvedOut, stdio: 'inherit', shell: isWindows });
|
|
525
|
+
process.exit(r.status === null ? 1 : r.status);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
module.exports = { runStandalone, parseArgs, normalizeOptions };
|
|
530
|
+
|
|
531
|
+
if (require.main === module) {
|
|
532
|
+
runStandalone(process.argv.slice(2));
|
|
533
|
+
}
|