@digitaldefiance/express-suite-starter 2.4.17 → 3.0.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.
@@ -0,0 +1,946 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.generateMonorepo = generateMonorepo;
37
+ const path = __importStar(require("path"));
38
+ const fs = __importStar(require("fs"));
39
+ const crypto = __importStar(require("crypto"));
40
+ const step_executor_1 = require("./step-executor");
41
+ const dry_run_executor_1 = require("./dry-run-executor");
42
+ const config_validator_1 = require("./validators/config-validator");
43
+ const project_generator_1 = require("./project-generator");
44
+ const logger_1 = require("../cli/logger");
45
+ const shell_utils_1 = require("../utils/shell-utils");
46
+ const template_renderer_1 = require("../utils/template-renderer");
47
+ const passwordObfuscator_1 = require("../../scripts/passwordObfuscator");
48
+ const nodeSetup_1 = require("../../scripts/nodeSetup");
49
+ const addScriptsToPackageJson_1 = require("../../scripts/addScriptsToPackageJson");
50
+ const templateUtils_1 = require("../../scripts/templateUtils");
51
+ const i18n_lib_1 = require("@digitaldefiance/i18n-lib");
52
+ const i18n_lib_2 = require("@digitaldefiance/i18n-lib");
53
+ const i18n_1 = require("../i18n");
54
+ const post_generation_validator_1 = require("./validators/post-generation-validator");
55
+ /**
56
+ * Run the monorepo generator programmatically without interactive prompts.
57
+ * Accepts a fully-formed configuration and executes the same step pipeline
58
+ * as the interactive CLI.
59
+ *
60
+ * @param options - Programmatic generation options
61
+ * @throws Error if configuration validation fails or a step fails
62
+ */
63
+ async function generateMonorepo(options) {
64
+ const { config, dryRun = false, hostname = `${config.workspace.name}.local`, language = i18n_lib_1.LanguageCodes.EN_US, siteTitle = 'Your Site Title', siteDescription = 'Your description here', siteTagline = 'Your tagline here', includeE2e = true, selectedGroups = [], devcontainer = { type: 'none' }, inMemoryDb = { enabled: false }, secrets = {}, createInitialCommit = false, pushToRemote = false, installPlaywright = false, skipSystemCheck: _skipSystemCheck = true, } = options;
65
+ (0, nodeSetup_1.checkAndUseNode)();
66
+ // Disable Yarn build scripts globally to avoid native module issues
67
+ process.env.YARN_ENABLE_SCRIPTS = 'false';
68
+ // Validate configuration
69
+ const validation = config_validator_1.ConfigValidator.validate(config);
70
+ if (!validation.valid) {
71
+ throw new Error(`Invalid configuration: ${validation.errors.join(', ')}`);
72
+ }
73
+ const { workspace, projects } = config;
74
+ const monorepoPath = path.join(workspace.parentDir, workspace.name);
75
+ // Generate secrets if not provided
76
+ const jwtSecret = secrets.jwtSecret ?? crypto.randomBytes(32).toString('hex');
77
+ const mnemonicEncryptionKey = secrets.mnemonicEncryptionKey ?? crypto.randomBytes(32).toString('hex');
78
+ const mnemonicHmacSecret = secrets.mnemonicHmacSecret ?? crypto.randomBytes(32).toString('hex');
79
+ const devcontainerChoice = devcontainer.type;
80
+ const mongoPassword = devcontainer.mongoPassword ?? '';
81
+ const useInMemoryDb = inMemoryDb.enabled;
82
+ const devDatabaseName = inMemoryDb.databaseName ?? 'test';
83
+ // Merge selected package groups
84
+ const packageGroupsPath = path.resolve(__dirname, '../../config/package-groups.json');
85
+ if (selectedGroups.length > 0 &&
86
+ fs.existsSync(packageGroupsPath)) {
87
+ const packageGroups = JSON.parse(fs.readFileSync(packageGroupsPath, 'utf-8')).groups;
88
+ const additionalPackages = [];
89
+ selectedGroups.forEach((groupName) => {
90
+ const group = packageGroups.find((g) => g.name === groupName);
91
+ if (group) {
92
+ additionalPackages.push(...group.packages);
93
+ }
94
+ });
95
+ if (additionalPackages.length > 0 && config.packages) {
96
+ config.packages = {
97
+ ...config.packages,
98
+ dev: [...(config.packages.dev || []), ...additionalPackages],
99
+ prod: config.packages.prod || [],
100
+ };
101
+ }
102
+ }
103
+ // Build state entries (same as interactive flow)
104
+ const stateEntries = [
105
+ ['monorepoPath', monorepoPath],
106
+ ['templatesDir', path.resolve(__dirname, '../../templates')],
107
+ ['scaffoldingDir', path.resolve(__dirname, '../../scaffolding')],
108
+ ];
109
+ projects.forEach((project) => {
110
+ stateEntries.push([
111
+ project.type === 'lib' ? 'libName' : `${project.type}Name`,
112
+ project.name,
113
+ ]);
114
+ });
115
+ const context = {
116
+ config,
117
+ state: new Map(stateEntries),
118
+ checkpointPath: path.join(workspace.parentDir, `.${workspace.name}.checkpoint`),
119
+ dryRun,
120
+ };
121
+ // Create executor
122
+ const executor = dryRun ? new dry_run_executor_1.DryRunExecutor() : new step_executor_1.StepExecutor();
123
+ if (dryRun) {
124
+ logger_1.Logger.warning((0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.GENERATION_DRY_RUN_MODE));
125
+ }
126
+ // Build the same step pipeline as the interactive CLI
127
+ buildSteps(executor, {
128
+ config,
129
+ context,
130
+ monorepoPath,
131
+ projects,
132
+ workspace,
133
+ hostname,
134
+ language,
135
+ siteTitle,
136
+ siteDescription,
137
+ siteTagline,
138
+ includeE2e,
139
+ devcontainerChoice,
140
+ mongoPassword,
141
+ useInMemoryDb,
142
+ devDatabaseName,
143
+ jwtSecret,
144
+ mnemonicEncryptionKey,
145
+ mnemonicHmacSecret,
146
+ createInitialCommit,
147
+ pushToRemote,
148
+ installPlaywright,
149
+ dryRun,
150
+ });
151
+ // Execute
152
+ await executor.execute(context);
153
+ }
154
+ /**
155
+ * Builds the step pipeline — same steps as the interactive CLI
156
+ * but without any interactive prompts.
157
+ */
158
+ function buildSteps(executor, params) {
159
+ const { config, context, monorepoPath, projects, workspace, hostname, language, siteTitle, siteDescription, siteTagline, includeE2e, devcontainerChoice, mongoPassword, useInMemoryDb, devDatabaseName, jwtSecret, mnemonicEncryptionKey, mnemonicHmacSecret, createInitialCommit, pushToRemote, installPlaywright, dryRun, } = params;
160
+ const workspaceName = workspace.name;
161
+ const projectPrefix = workspace.prefix;
162
+ const namespaceRoot = workspace.namespace;
163
+ const parentDir = workspace.parentDir;
164
+ const gitRepo = workspace.gitRepo ?? '';
165
+ executor.addStep({
166
+ name: 'checkTargetDir',
167
+ description: (0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.STEP_CHECK_TARGET_DIR),
168
+ execute: () => {
169
+ if (fs.existsSync(monorepoPath) &&
170
+ fs.readdirSync(monorepoPath).length > 0) {
171
+ throw new i18n_lib_2.TranslatableGenericError(i18n_1.StarterComponentId, i18n_1.StarterStringKey.ERROR_DIRECTORY_NOT_EMPTY, { path: monorepoPath });
172
+ }
173
+ },
174
+ });
175
+ executor.addStep({
176
+ name: 'createMonorepo',
177
+ description: (0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.STEP_CREATE_MONOREPO),
178
+ execute: (ctx) => {
179
+ (0, shell_utils_1.runCommand)(`npx create-nx-workspace@latest "${workspaceName}" --package-manager=yarn --preset=apps --ci=${config.nx?.ciProvider}`, { cwd: parentDir, dryRun: ctx.dryRun });
180
+ },
181
+ });
182
+ executor.addStep({
183
+ name: 'updateTsConfigBase',
184
+ description: (0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.STEP_UPDATE_TSCONFIG_BASE),
185
+ execute: () => {
186
+ const tsconfigBasePath = path.join(monorepoPath, 'tsconfig.base.json');
187
+ if (fs.existsSync(tsconfigBasePath)) {
188
+ const tsconfigBase = JSON.parse(fs.readFileSync(tsconfigBasePath, 'utf-8'));
189
+ tsconfigBase.compilerOptions = tsconfigBase.compilerOptions || {};
190
+ tsconfigBase.compilerOptions.esModuleInterop = true;
191
+ tsconfigBase.compilerOptions.allowSyntheticDefaultImports = true;
192
+ tsconfigBase.compilerOptions.allowImportingTsExtensions = true;
193
+ tsconfigBase.compilerOptions.target = 'es2020';
194
+ tsconfigBase.compilerOptions.skipLibCheck = true;
195
+ tsconfigBase.compilerOptions.skipDefaultLibCheck = true;
196
+ tsconfigBase.compilerOptions.noImplicitOverride = false;
197
+ fs.writeFileSync(tsconfigBasePath, JSON.stringify(tsconfigBase, null, 2) + '\n');
198
+ logger_1.Logger.info((0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.TSCONFIG_BASE_UPDATED));
199
+ }
200
+ },
201
+ });
202
+ executor.addStep({
203
+ name: 'setupGitOrigin',
204
+ description: (0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.STEP_SETUP_GIT_ORIGIN),
205
+ skip: () => !gitRepo,
206
+ execute: () => {
207
+ if (!fs.existsSync(path.join(monorepoPath, '.git'))) {
208
+ (0, shell_utils_1.runCommand)('git init', { cwd: monorepoPath, dryRun: context.dryRun });
209
+ }
210
+ try {
211
+ (0, shell_utils_1.runCommand)(`git remote add origin ${gitRepo}`, {
212
+ cwd: monorepoPath,
213
+ dryRun: context.dryRun,
214
+ });
215
+ }
216
+ catch (error) {
217
+ const errorMsg = error instanceof Error ? error.message : String(error);
218
+ if (errorMsg.includes('remote origin already exists') ||
219
+ (error !== null &&
220
+ typeof error === 'object' &&
221
+ 'status' in error &&
222
+ error.status === 3)) {
223
+ logger_1.Logger.info('Remote origin already exists, updating URL...');
224
+ (0, shell_utils_1.runCommand)(`git remote set-url origin ${gitRepo}`, {
225
+ cwd: monorepoPath,
226
+ dryRun: context.dryRun,
227
+ });
228
+ }
229
+ else {
230
+ throw error;
231
+ }
232
+ }
233
+ },
234
+ });
235
+ executor.addStep({
236
+ name: 'yarnBerrySetup',
237
+ description: (0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.STEP_YARN_BERRY_SETUP),
238
+ execute: () => {
239
+ (0, shell_utils_1.runCommand)('yarn set version berry', {
240
+ cwd: monorepoPath,
241
+ dryRun: context.dryRun,
242
+ });
243
+ (0, shell_utils_1.runCommand)('yarn config set nodeLinker node-modules', {
244
+ cwd: monorepoPath,
245
+ dryRun: context.dryRun,
246
+ });
247
+ (0, shell_utils_1.runCommand)('yarn', { cwd: monorepoPath, dryRun: context.dryRun });
248
+ },
249
+ });
250
+ executor.addStep({
251
+ name: 'addPackageResolutions',
252
+ description: (0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.STEP_ADD_PACKAGE_RESOLUTIONS),
253
+ execute: () => {
254
+ const packageJsonPath = path.join(monorepoPath, 'package.json');
255
+ if (fs.existsSync(packageJsonPath)) {
256
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
257
+ packageJson.resolutions = {
258
+ '@noble/curves': '1.4.2',
259
+ '@noble/hashes': '1.4.0',
260
+ '@scure/bip32': '1.4.0',
261
+ '@scure/bip39': '1.3.0',
262
+ };
263
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
264
+ logger_1.Logger.info((0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.PACKAGE_RESOLUTIONS_ADDED));
265
+ }
266
+ },
267
+ });
268
+ executor.addStep({
269
+ name: 'addNxPlugins',
270
+ description: (0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.STEP_ADD_NX_PLUGINS),
271
+ execute: () => {
272
+ (0, shell_utils_1.runCommand)('yarn add -D @nx/react @nx/node', {
273
+ cwd: monorepoPath,
274
+ dryRun: context.dryRun,
275
+ });
276
+ },
277
+ });
278
+ executor.addStep({
279
+ name: 'resetNxDaemon',
280
+ description: 'Resetting Nx daemon',
281
+ execute: () => {
282
+ (0, shell_utils_1.runCommand)('npx nx reset', {
283
+ cwd: monorepoPath,
284
+ dryRun: context.dryRun,
285
+ });
286
+ },
287
+ });
288
+ executor.addStep({
289
+ name: 'addYarnPackages',
290
+ description: (0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.STEP_ADD_YARN_PACKAGES),
291
+ execute: () => {
292
+ const devPkgs = config.packages?.dev || [];
293
+ const prodPkgs = config.packages?.prod || [];
294
+ if (devPkgs.length > 0) {
295
+ (0, shell_utils_1.runCommand)(`yarn add -D ${devPkgs.join(' ')}`, {
296
+ cwd: monorepoPath,
297
+ dryRun: context.dryRun,
298
+ });
299
+ }
300
+ if (prodPkgs.length > 0) {
301
+ (0, shell_utils_1.runCommand)(`yarn add ${prodPkgs.join(' ')}`, {
302
+ cwd: monorepoPath,
303
+ dryRun: context.dryRun,
304
+ });
305
+ }
306
+ },
307
+ });
308
+ executor.addStep({
309
+ name: 'generateProjects',
310
+ description: (0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.STEP_GENERATE_PROJECTS),
311
+ execute: () => {
312
+ projects.forEach((project) => {
313
+ if (!project.enabled)
314
+ return;
315
+ logger_1.Logger.info((0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.PROJECT_GENERATING, {
316
+ type: project.type,
317
+ name: project.name,
318
+ }));
319
+ switch (project.type) {
320
+ case 'react':
321
+ project_generator_1.ProjectGenerator.generateReact(project, monorepoPath, config.nx, context.dryRun);
322
+ break;
323
+ case 'react-lib':
324
+ project_generator_1.ProjectGenerator.generateReactLib(project, monorepoPath, config.nx, context.dryRun);
325
+ break;
326
+ case 'api':
327
+ project_generator_1.ProjectGenerator.generateApi(project, monorepoPath, config.nx, context.dryRun);
328
+ break;
329
+ case 'api-lib':
330
+ project_generator_1.ProjectGenerator.generateApiLib(project, monorepoPath, config.nx, context.dryRun);
331
+ break;
332
+ case 'lib':
333
+ project_generator_1.ProjectGenerator.generateLib(project, monorepoPath, config.nx, context.dryRun);
334
+ break;
335
+ case 'inituserdb':
336
+ project_generator_1.ProjectGenerator.generateInitUserDb(project, monorepoPath, context.dryRun);
337
+ break;
338
+ }
339
+ });
340
+ // Configure project.json targets for api and inituserdb
341
+ configureApiProjectJson(projects, monorepoPath);
342
+ configureInitUserDbProjectJson(projects, monorepoPath);
343
+ configureReactProjectJson(projects, monorepoPath);
344
+ },
345
+ });
346
+ executor.addStep({
347
+ name: 'updateTsConfigAppFiles',
348
+ description: 'Updating tsconfig.app.json files with skipLibCheck',
349
+ execute: () => {
350
+ const apiProject = projects.find((p) => p.type === 'api' && p.enabled);
351
+ const initUserDbProject = projects.find((p) => p.type === 'inituserdb' && p.enabled);
352
+ [apiProject, initUserDbProject].forEach((project) => {
353
+ if (project) {
354
+ const tsconfigAppPath = path.join(monorepoPath, project.name, 'tsconfig.app.json');
355
+ if (fs.existsSync(tsconfigAppPath)) {
356
+ const tsconfigApp = JSON.parse(fs.readFileSync(tsconfigAppPath, 'utf-8'));
357
+ tsconfigApp.compilerOptions = tsconfigApp.compilerOptions || {};
358
+ tsconfigApp.compilerOptions.skipLibCheck = true;
359
+ fs.writeFileSync(tsconfigAppPath, JSON.stringify(tsconfigApp, null, 2) + '\n');
360
+ logger_1.Logger.info(`Updated ${project.name}/tsconfig.app.json with skipLibCheck`);
361
+ }
362
+ }
363
+ });
364
+ },
365
+ });
366
+ executor.addStep({
367
+ name: 'installReactComponents',
368
+ description: (0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.STEP_INSTALL_REACT_COMPONENTS),
369
+ execute: () => {
370
+ const reactLibProject = projects.find((p) => p.type === 'react-lib' && p.enabled);
371
+ if (reactLibProject) {
372
+ const projectPackageJsonPath = path.join(monorepoPath, reactLibProject.name, 'package.json');
373
+ const projectPackageJson = JSON.parse(fs.readFileSync(projectPackageJsonPath, 'utf-8'));
374
+ projectPackageJson.dependencies =
375
+ projectPackageJson.dependencies || {};
376
+ projectPackageJson.dependencies['@digitaldefiance/express-suite-react-components'] = 'latest';
377
+ fs.writeFileSync(projectPackageJsonPath, JSON.stringify(projectPackageJson, null, 2) + '\n');
378
+ (0, shell_utils_1.runCommand)('yarn install', {
379
+ cwd: monorepoPath,
380
+ dryRun: context.dryRun,
381
+ });
382
+ }
383
+ },
384
+ });
385
+ executor.addStep({
386
+ name: 'renderTemplates',
387
+ description: (0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.STEP_RENDER_TEMPLATES),
388
+ execute: () => {
389
+ const variables = {
390
+ WORKSPACE_NAME: workspaceName,
391
+ PROJECT_PREFIX: projectPrefix,
392
+ NAMESPACE_ROOT: namespaceRoot,
393
+ HOSTNAME: hostname,
394
+ SITE_TITLE: siteTitle,
395
+ SITE_DESCRIPTION: siteDescription,
396
+ SITE_TAGLINE: siteTagline,
397
+ EXAMPLE_PASSWORD: (0, passwordObfuscator_1.obfuscatePassword)(projectPrefix),
398
+ EXAMPLE_JWT_SECRET: (0, passwordObfuscator_1.obfuscatePassword)(`${workspaceName}Secret`),
399
+ GIT_REPO: gitRepo,
400
+ NVM_USE_VERSION: config.node?.version,
401
+ YARN_VERSION: config.node?.yarnVersion,
402
+ };
403
+ projects.forEach((project) => {
404
+ const key = project.type === 'lib'
405
+ ? 'LIB_NAME'
406
+ : `${project.type.toUpperCase().replace(/-/g, '_')}_NAME`;
407
+ variables[key] = project.name;
408
+ });
409
+ (0, template_renderer_1.renderTemplates)(context.state.get('templatesDir'), monorepoPath, variables, config.templates?.engine, context.dryRun);
410
+ },
411
+ });
412
+ executor.addStep({
413
+ name: 'copyScaffolding',
414
+ description: (0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.STEP_COPY_SCAFFOLDING),
415
+ execute: () => {
416
+ const scaffoldingDir = context.state.get('scaffoldingDir');
417
+ const escapeForTypeScript = (str) => {
418
+ return str
419
+ .replace(/\\/g, '\\\\')
420
+ .replace(/'/g, "\\'")
421
+ .replace(/"/g, '\\"')
422
+ .replace(/\n/g, '\\n')
423
+ .replace(/\r/g, '\\r')
424
+ .replace(/\t/g, '\\t');
425
+ };
426
+ const scaffoldingVars = {
427
+ workspaceName,
428
+ WorkspaceName: workspaceName.charAt(0).toUpperCase() +
429
+ workspaceName
430
+ .slice(1)
431
+ .replace(/-([a-z])/g, (_, c) => c.toUpperCase()),
432
+ prefix: projectPrefix,
433
+ namespace: namespaceRoot,
434
+ hostname,
435
+ selectedLanguage: language,
436
+ siteTitle: escapeForTypeScript(siteTitle),
437
+ siteDescription: escapeForTypeScript(siteDescription),
438
+ siteTagline: escapeForTypeScript(siteTagline),
439
+ isEnUs: language === i18n_lib_1.LanguageCodes.EN_US,
440
+ isEnGb: language === i18n_lib_1.LanguageCodes.EN_GB,
441
+ isFr: language === i18n_lib_1.LanguageCodes.FR,
442
+ isEs: language === i18n_lib_1.LanguageCodes.ES,
443
+ isDe: language === i18n_lib_1.LanguageCodes.DE,
444
+ isZhCn: language === i18n_lib_1.LanguageCodes.ZH_CN,
445
+ isJa: language === i18n_lib_1.LanguageCodes.JA,
446
+ isUk: language === i18n_lib_1.LanguageCodes.UK,
447
+ };
448
+ // Copy root scaffolding
449
+ const rootSrc = path.join(scaffoldingDir, 'root');
450
+ if (fs.existsSync(rootSrc)) {
451
+ (0, template_renderer_1.copyDir)(rootSrc, monorepoPath, scaffoldingVars, 'mustache', context.dryRun);
452
+ }
453
+ // Copy devcontainer configuration
454
+ if (devcontainerChoice !== 'none') {
455
+ const devcontainerSrc = path.join(scaffoldingDir, `devcontainer-${devcontainerChoice}`);
456
+ if (fs.existsSync(devcontainerSrc)) {
457
+ logger_1.Logger.info(`Copying devcontainer configuration: ${devcontainerChoice}`);
458
+ (0, template_renderer_1.copyDir)(devcontainerSrc, monorepoPath, scaffoldingVars, 'handlebars', context.dryRun);
459
+ }
460
+ }
461
+ // Copy project-specific scaffolding
462
+ projects.forEach((project) => {
463
+ const projectSrc = path.join(scaffoldingDir, project.type);
464
+ if (fs.existsSync(projectSrc)) {
465
+ (0, template_renderer_1.copyDir)(projectSrc, path.join(monorepoPath, project.name), scaffoldingVars, 'mustache', context.dryRun);
466
+ }
467
+ });
468
+ },
469
+ });
470
+ executor.addStep({
471
+ name: 'fixPlaywrightPlugin',
472
+ description: 'Configuring Nx Playwright plugin to avoid daemon issues',
473
+ skip: () => !includeE2e,
474
+ execute: () => {
475
+ const nxJsonPath = path.join(monorepoPath, 'nx.json');
476
+ if (fs.existsSync(nxJsonPath)) {
477
+ const nxJson = JSON.parse(fs.readFileSync(nxJsonPath, 'utf-8'));
478
+ if (nxJson.plugins && Array.isArray(nxJson.plugins)) {
479
+ const playwrightPluginIndex = nxJson.plugins.findIndex((plugin) => (typeof plugin === 'string' && plugin.includes('playwright')) ||
480
+ (typeof plugin === 'object' &&
481
+ plugin.plugin?.includes('playwright')));
482
+ if (playwrightPluginIndex !== -1) {
483
+ nxJson.plugins.splice(playwrightPluginIndex, 1);
484
+ logger_1.Logger.info('Removed Playwright plugin from nx.json to avoid daemon worker issues');
485
+ }
486
+ }
487
+ fs.writeFileSync(nxJsonPath, JSON.stringify(nxJson, null, 2) + '\n');
488
+ }
489
+ },
490
+ });
491
+ executor.addStep({
492
+ name: 'createTypesDirectory',
493
+ description: 'Creating types directory with global.d.ts',
494
+ execute: () => {
495
+ const typesDir = path.join(monorepoPath, 'types');
496
+ const globalDtsPath = path.join(typesDir, 'global.d.ts');
497
+ if (!fs.existsSync(typesDir)) {
498
+ fs.mkdirSync(typesDir, { recursive: true });
499
+ }
500
+ const globalDtsContent = buildGlobalDtsContent();
501
+ fs.writeFileSync(globalDtsPath, globalDtsContent);
502
+ logger_1.Logger.info('Created types/global.d.ts with Error extensions');
503
+ const nodeModulesTypesDir = path.join(monorepoPath, 'node_modules', 'types');
504
+ if (!fs.existsSync(nodeModulesTypesDir)) {
505
+ fs.mkdirSync(nodeModulesTypesDir, { recursive: true });
506
+ }
507
+ fs.writeFileSync(path.join(nodeModulesTypesDir, 'global.d.ts'), globalDtsContent);
508
+ logger_1.Logger.info('Created node_modules/types/global.d.ts for library references');
509
+ },
510
+ });
511
+ // Skip license prompt in programmatic mode (no interactive prompt)
512
+ executor.addStep({
513
+ name: 'generateLicense',
514
+ description: (0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.STEP_GENERATE_LICENSE),
515
+ skip: () => true, // Skipped in programmatic mode — no interactive prompt
516
+ execute: () => {
517
+ // no-op
518
+ },
519
+ });
520
+ executor.addStep({
521
+ name: 'addScripts',
522
+ description: (0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.STEP_ADD_SCRIPTS),
523
+ execute: () => {
524
+ const packageJsonPath = path.join(monorepoPath, 'package.json');
525
+ const scriptContext = {
526
+ workspaceName,
527
+ projectPrefix,
528
+ namespaceRoot,
529
+ gitRepo,
530
+ };
531
+ projects.forEach((project) => {
532
+ scriptContext[`${project.type}Name`] = project.name;
533
+ });
534
+ const addScripts = {
535
+ build: 'NODE_ENV=production npx nx run-many --target=build --all --configuration=production',
536
+ 'build:dev': 'NODE_ENV=development npx nx run-many --target=build --all --configuration=development',
537
+ 'test:all': 'yarn test:jest && yarn test:e2e',
538
+ 'test:jest': 'NODE_ENV=development npx nx run-many --target=test --all --configuration=development',
539
+ 'lint:all': 'npx nx run-many --target=lint --all',
540
+ 'prettier:check': "prettier --check '**/*.{ts,tsx}'",
541
+ 'prettier:fix': "prettier --write '**/*.{ts,tsx}'",
542
+ };
543
+ const apiProject = projects.find((p) => p.type === 'api');
544
+ if (apiProject) {
545
+ addScripts['serve'] =
546
+ `npx nx serve ${apiProject.name} --configuration production`;
547
+ addScripts['serve:stream'] =
548
+ `npx nx serve ${apiProject.name} --configuration production --output-style=stream`;
549
+ addScripts['serve:dev'] =
550
+ `npx nx serve ${apiProject.name} --configuration development`;
551
+ addScripts['serve:dev:stream'] =
552
+ `npx nx serve ${apiProject.name} --configuration development --output-style=stream`;
553
+ addScripts['build:api'] = `npx nx build ${apiProject.name}`;
554
+ }
555
+ const reactProject = projects.find((p) => p.type === 'react');
556
+ if (reactProject) {
557
+ addScripts['build:react'] = `npx nx build ${reactProject.name}`;
558
+ }
559
+ const initUserDbProject = projects.find((p) => p.type === 'inituserdb');
560
+ if (initUserDbProject) {
561
+ addScripts['inituserdb'] =
562
+ `yarn build:dev && npx nx serve ${initUserDbProject.name} --output-style=stream`;
563
+ addScripts['inituserdb:drop'] =
564
+ `yarn build:dev && npx nx serve ${initUserDbProject.name} --output-style=stream --args=--drop`;
565
+ addScripts['inituserdb:setenv'] =
566
+ `yarn build:dev && npx nx serve ${initUserDbProject.name} --output-style=stream --args=--setEnv`;
567
+ addScripts['inituserdb:drop:setenv'] =
568
+ `yarn build:dev && npx nx serve ${initUserDbProject.name} --output-style=stream --args="--drop --setEnv"`;
569
+ }
570
+ const interpolatedScripts = {};
571
+ for (const [k, v] of Object.entries(addScripts)) {
572
+ interpolatedScripts[k] = (0, templateUtils_1.interpolateTemplateStrings)(v, scriptContext);
573
+ }
574
+ (0, addScriptsToPackageJson_1.addScriptsToPackageJson)(packageJsonPath, interpolatedScripts);
575
+ },
576
+ });
577
+ executor.addStep({
578
+ name: 'generateDocumentation',
579
+ description: (0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.STEP_GENERATE_DOCUMENTATION),
580
+ skip: () => true, // Disabled — README comes from scaffolding/root/
581
+ execute: async () => {
582
+ const { DocGenerator } = await Promise.resolve().then(() => __importStar(require('../utils/doc-generator')));
583
+ DocGenerator.generateProjectDocs(context);
584
+ },
585
+ });
586
+ executor.addStep({
587
+ name: 'setupEnvironment',
588
+ description: (0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.STEP_SETUP_ENVIRONMENT),
589
+ execute: () => {
590
+ setupEnvironmentFiles({
591
+ projects,
592
+ monorepoPath,
593
+ workspaceName,
594
+ devcontainerChoice,
595
+ mongoPassword,
596
+ useInMemoryDb,
597
+ devDatabaseName,
598
+ jwtSecret,
599
+ mnemonicEncryptionKey,
600
+ mnemonicHmacSecret,
601
+ });
602
+ },
603
+ });
604
+ executor.addStep({
605
+ name: 'rebuildNativeModules',
606
+ description: (0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.STEP_REBUILD_NATIVE_MODULES),
607
+ execute: () => {
608
+ logger_1.Logger.info((0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.COMMAND_REBUILDING_NATIVE));
609
+ (0, shell_utils_1.runCommand)('yarn config set enableScripts true', {
610
+ cwd: monorepoPath,
611
+ dryRun: context.dryRun,
612
+ });
613
+ (0, shell_utils_1.runCommand)('yarn rebuild', {
614
+ cwd: monorepoPath,
615
+ dryRun: context.dryRun,
616
+ });
617
+ },
618
+ });
619
+ executor.addStep({
620
+ name: 'validateGeneration',
621
+ description: (0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.STEP_VALIDATE_GENERATION),
622
+ skip: () => dryRun,
623
+ execute: async () => {
624
+ const report = await post_generation_validator_1.PostGenerationValidator.validate(context);
625
+ post_generation_validator_1.PostGenerationValidator.printReport(report);
626
+ if (!report.passed) {
627
+ logger_1.Logger.warning((0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.WARNING_VALIDATION_ERRORS));
628
+ }
629
+ },
630
+ });
631
+ executor.addStep({
632
+ name: 'initialCommit',
633
+ description: (0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.STEP_INITIAL_COMMIT),
634
+ skip: () => !createInitialCommit,
635
+ execute: () => {
636
+ if (!fs.existsSync(path.join(monorepoPath, '.git'))) {
637
+ (0, shell_utils_1.runCommand)('git init', { cwd: monorepoPath, dryRun: context.dryRun });
638
+ }
639
+ (0, shell_utils_1.runCommand)('git add -A', { cwd: monorepoPath, dryRun: context.dryRun });
640
+ (0, shell_utils_1.runCommand)('git commit -m "Initial commit"', {
641
+ cwd: monorepoPath,
642
+ dryRun: context.dryRun,
643
+ });
644
+ if (gitRepo && pushToRemote) {
645
+ (0, shell_utils_1.runCommand)('git push --set-upstream origin main', {
646
+ cwd: monorepoPath,
647
+ dryRun: context.dryRun,
648
+ });
649
+ }
650
+ },
651
+ });
652
+ executor.addStep({
653
+ name: 'installPlaywright',
654
+ description: (0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.STEP_INSTALL_PLAYWRIGHT),
655
+ skip: () => !includeE2e || !installPlaywright,
656
+ execute: () => {
657
+ logger_1.Logger.info((0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.COMMAND_INSTALLING_PLAYWRIGHT_BROWSERS));
658
+ (0, shell_utils_1.runCommand)('yarn playwright install --with-deps', {
659
+ cwd: monorepoPath,
660
+ dryRun: context.dryRun,
661
+ });
662
+ },
663
+ });
664
+ }
665
+ /** Configure api project.json targets */
666
+ function configureApiProjectJson(projects, monorepoPath) {
667
+ const apiProject = projects.find((p) => p.type === 'api' && p.enabled);
668
+ if (!apiProject)
669
+ return;
670
+ const projectJsonPath = path.join(monorepoPath, apiProject.name, 'project.json');
671
+ if (!fs.existsSync(projectJsonPath))
672
+ return;
673
+ const projectJson = JSON.parse(fs.readFileSync(projectJsonPath, 'utf-8'));
674
+ if (projectJson.targets?.build?.options) {
675
+ projectJson.targets.build.options.skipTypeCheck = true;
676
+ projectJson.targets.build.options.bundle = true;
677
+ const existingAssets = projectJson.targets.build.options.assets || [];
678
+ const hasObjectAssets = existingAssets.some((asset) => typeof asset === 'object' && asset.input);
679
+ if (!hasObjectAssets) {
680
+ projectJson.targets.build.options.assets = [
681
+ {
682
+ input: `${apiProject.name}/src/assets`,
683
+ glob: '**/*',
684
+ output: 'assets',
685
+ },
686
+ {
687
+ input: `${apiProject.name}/src/views`,
688
+ glob: '**/*',
689
+ output: 'views',
690
+ },
691
+ ];
692
+ }
693
+ }
694
+ projectJson.targets['copy-env'] = {
695
+ executor: 'nx:run-commands',
696
+ options: {
697
+ command: `cp ${apiProject.name}/.env dist/${apiProject.name}/.env`,
698
+ },
699
+ };
700
+ projectJson.targets['post-build'] = {
701
+ executor: 'nx:run-commands',
702
+ dependsOn: ['build'],
703
+ options: {
704
+ command: `cp ${apiProject.name}/.env dist/${apiProject.name}/.env`,
705
+ },
706
+ };
707
+ projectJson.targets.serve = {
708
+ continuous: true,
709
+ executor: 'nx:run-commands',
710
+ defaultConfiguration: 'development',
711
+ dependsOn: ['post-build'],
712
+ options: {
713
+ cwd: `dist/${apiProject.name}`,
714
+ command: 'node main.js',
715
+ },
716
+ configurations: {
717
+ development: {
718
+ cwd: `dist/${apiProject.name}`,
719
+ command: 'node main.js',
720
+ },
721
+ production: {
722
+ cwd: `dist/${apiProject.name}`,
723
+ command: 'node main.js',
724
+ },
725
+ },
726
+ };
727
+ fs.writeFileSync(projectJsonPath, JSON.stringify(projectJson, null, 2) + '\n');
728
+ logger_1.Logger.info((0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.PROJECT_ADDED_TARGETS, {
729
+ name: apiProject.name,
730
+ }));
731
+ }
732
+ /** Configure inituserdb project.json targets */
733
+ function configureInitUserDbProjectJson(projects, monorepoPath) {
734
+ const initUserDbProject = projects.find((p) => p.type === 'inituserdb' && p.enabled);
735
+ if (!initUserDbProject)
736
+ return;
737
+ const projectJsonPath = path.join(monorepoPath, initUserDbProject.name, 'project.json');
738
+ if (!fs.existsSync(projectJsonPath))
739
+ return;
740
+ const projectJson = JSON.parse(fs.readFileSync(projectJsonPath, 'utf-8'));
741
+ if (projectJson.targets?.build?.options) {
742
+ projectJson.targets.build.options.skipTypeCheck = true;
743
+ projectJson.targets.build.options.format = ['esm'];
744
+ delete projectJson.targets.build.options.platform;
745
+ projectJson.targets.build.options.bundle = true;
746
+ projectJson.targets.build.options.thirdParty = false;
747
+ projectJson.targets.build.options.generatePackageJson = false;
748
+ if (!projectJson.targets.build.options.esbuildOptions) {
749
+ projectJson.targets.build.options.esbuildOptions = {};
750
+ }
751
+ projectJson.targets.build.options.esbuildOptions.external = [
752
+ '@digitaldefiance/*',
753
+ ];
754
+ if (projectJson.targets.build.configurations?.production) {
755
+ if (!projectJson.targets.build.configurations.production.esbuildOptions) {
756
+ projectJson.targets.build.configurations.production.esbuildOptions =
757
+ {};
758
+ }
759
+ projectJson.targets.build.configurations.production.esbuildOptions.external =
760
+ ['@digitaldefiance/*'];
761
+ }
762
+ }
763
+ projectJson.targets['copy-env'] = {
764
+ executor: 'nx:run-commands',
765
+ options: {
766
+ command: `cp ${initUserDbProject.name}/.env dist/${initUserDbProject.name}/.env`,
767
+ },
768
+ };
769
+ projectJson.targets['post-build'] = {
770
+ executor: 'nx:run-commands',
771
+ dependsOn: ['build'],
772
+ options: {
773
+ command: `cp ${initUserDbProject.name}/.env dist/${initUserDbProject.name}/.env`,
774
+ },
775
+ };
776
+ projectJson.targets.serve = {
777
+ executor: 'nx:run-commands',
778
+ defaultConfiguration: 'development',
779
+ dependsOn: ['post-build'],
780
+ options: {
781
+ command: `node dist/${initUserDbProject.name}/main.js`,
782
+ cwd: '{workspaceRoot}',
783
+ },
784
+ configurations: {
785
+ development: {
786
+ command: `node dist/${initUserDbProject.name}/main.js`,
787
+ },
788
+ production: {
789
+ command: `node dist/${initUserDbProject.name}/main.js`,
790
+ },
791
+ },
792
+ };
793
+ fs.writeFileSync(projectJsonPath, JSON.stringify(projectJson, null, 2) + '\n');
794
+ logger_1.Logger.info((0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.PROJECT_ADDED_TARGETS, {
795
+ name: initUserDbProject.name,
796
+ }));
797
+ }
798
+ /** Configure react project.json targets */
799
+ function configureReactProjectJson(projects, monorepoPath) {
800
+ const reactProject = projects.find((p) => p.type === 'react' && p.enabled);
801
+ if (!reactProject)
802
+ return;
803
+ const projectJsonPath = path.join(monorepoPath, reactProject.name, 'project.json');
804
+ if (!fs.existsSync(projectJsonPath))
805
+ return;
806
+ const projectJson = JSON.parse(fs.readFileSync(projectJsonPath, 'utf-8'));
807
+ if (!projectJson.targets) {
808
+ projectJson.targets = {};
809
+ }
810
+ projectJson.targets['build'] = {
811
+ executor: 'nx:run-commands',
812
+ dependsOn: ['^build'],
813
+ cache: true,
814
+ outputs: [`{workspaceRoot}/dist/${reactProject.name}`],
815
+ options: {
816
+ cwd: reactProject.name,
817
+ command: 'vite build --mode development',
818
+ },
819
+ configurations: {
820
+ development: { command: 'vite build --mode development' },
821
+ production: { command: 'vite build --mode production' },
822
+ },
823
+ };
824
+ fs.writeFileSync(projectJsonPath, JSON.stringify(projectJson, null, 2) + '\n');
825
+ logger_1.Logger.info((0, i18n_1.getStarterTranslation)(i18n_1.StarterStringKey.PROJECT_ADDED_TARGETS, {
826
+ name: reactProject.name,
827
+ }));
828
+ // Update vite.config.ts to handle mode properly
829
+ const viteConfigPath = path.join(monorepoPath, reactProject.name, 'vite.config.ts');
830
+ if (fs.existsSync(viteConfigPath)) {
831
+ let viteConfig = fs.readFileSync(viteConfigPath, 'utf-8');
832
+ viteConfig = viteConfig.replace(/export default defineConfig\(\(\) => \(\{/, 'export default defineConfig(({ mode }) => ({');
833
+ if (!viteConfig.includes('mode:')) {
834
+ viteConfig = viteConfig.replace(/cacheDir: '[^']*',/, `cacheDir: '../node_modules/.vite/${reactProject.name}',\n mode: mode || process.env.NODE_ENV || 'production',`);
835
+ }
836
+ if (!viteConfig.includes('minify:')) {
837
+ viteConfig = viteConfig.replace(/commonjsOptions: \{[^}]*\},/, `commonjsOptions: {\n transformMixedEsModules: true,\n },\n minify: mode === 'production' ? ('esbuild' as const) : false,`);
838
+ }
839
+ viteConfig = viteConfig.replace(/\}\);(\s*)$/, '}));$1');
840
+ fs.writeFileSync(viteConfigPath, viteConfig);
841
+ }
842
+ }
843
+ /** Setup .env files for api, inituserdb, and devcontainer */
844
+ function setupEnvironmentFiles(params) {
845
+ const { projects, monorepoPath, workspaceName, devcontainerChoice, mongoPassword, useInMemoryDb, devDatabaseName, jwtSecret, mnemonicEncryptionKey, mnemonicHmacSecret, } = params;
846
+ const apiProject = projects.find((p) => p.type === 'api');
847
+ const initUserDbProject = projects.find((p) => p.type === 'inituserdb');
848
+ const escapeEnvValue = (value) => {
849
+ const escaped = value.replace(/'/g, "'\\''");
850
+ return `'${escaped}'`;
851
+ };
852
+ const buildMongoUri = (dbName) => {
853
+ const auth = mongoPassword
854
+ ? `admin:${encodeURIComponent(mongoPassword)}@`
855
+ : '';
856
+ const mongoParams = devcontainerChoice === 'mongodb-replicaset'
857
+ ? '?authSource=admin&replicaSet=rs0&directConnection=true'
858
+ : '?authSource=admin&directConnection=true';
859
+ return `mongodb://${auth}db:27017/${dbName}${mongoParams}`;
860
+ };
861
+ // Setup API .env
862
+ if (apiProject) {
863
+ const envExamplePath = path.join(monorepoPath, apiProject.name, '.env.example');
864
+ const envPath = path.join(monorepoPath, apiProject.name, '.env');
865
+ if (fs.existsSync(envExamplePath)) {
866
+ let envContent = fs.readFileSync(envExamplePath, 'utf-8');
867
+ if (useInMemoryDb) {
868
+ envContent = envContent.replace(/DEV_DATABASE=.*/g, `DEV_DATABASE=${devDatabaseName}`);
869
+ }
870
+ else {
871
+ envContent = envContent.replace(/DEV_DATABASE=.*/g, 'DEV_DATABASE=');
872
+ }
873
+ envContent = envContent.replace(/JWT_SECRET=.*/g, `JWT_SECRET=${jwtSecret}`);
874
+ envContent = envContent.replace(/MNEMONIC_ENCRYPTION_KEY=.*/g, `MNEMONIC_ENCRYPTION_KEY=${mnemonicEncryptionKey}`);
875
+ envContent = envContent.replace(/MNEMONIC_HMAC_SECRET=.*/g, `MNEMONIC_HMAC_SECRET=${mnemonicHmacSecret}`);
876
+ if (devcontainerChoice === 'mongodb' ||
877
+ devcontainerChoice === 'mongodb-replicaset') {
878
+ const mongoUri = buildMongoUri(workspaceName);
879
+ envContent = envContent.replace(/MONGO_URI=.*/g, `MONGO_URI=${mongoUri}`);
880
+ }
881
+ if (devcontainerChoice === 'mongodb-replicaset') {
882
+ envContent = envContent.replace(/MONGO_USE_TRANSACTIONS=.*/g, 'MONGO_USE_TRANSACTIONS=true');
883
+ }
884
+ else {
885
+ envContent = envContent.replace(/MONGO_USE_TRANSACTIONS=.*/g, 'MONGO_USE_TRANSACTIONS=false');
886
+ }
887
+ fs.writeFileSync(envPath, envContent);
888
+ logger_1.Logger.info(`Created ${apiProject.name}/.env with secrets`);
889
+ }
890
+ }
891
+ // Setup inituserdb .env
892
+ if (initUserDbProject && apiProject) {
893
+ const apiEnvPath = path.join(monorepoPath, apiProject.name, '.env');
894
+ const initEnvPath = path.join(monorepoPath, initUserDbProject.name, '.env');
895
+ if (fs.existsSync(apiEnvPath)) {
896
+ fs.copyFileSync(apiEnvPath, initEnvPath);
897
+ logger_1.Logger.info(`Created ${initUserDbProject.name}/.env from ${apiProject.name}/.env`);
898
+ }
899
+ }
900
+ // Setup devcontainer .env
901
+ if (devcontainerChoice === 'mongodb' ||
902
+ devcontainerChoice === 'mongodb-replicaset') {
903
+ const devcontainerEnvExamplePath = path.join(monorepoPath, '.devcontainer', '.env.example');
904
+ const devcontainerEnvPath = path.join(monorepoPath, '.devcontainer', '.env');
905
+ if (fs.existsSync(devcontainerEnvExamplePath)) {
906
+ let envContent = fs.readFileSync(devcontainerEnvExamplePath, 'utf-8');
907
+ envContent = envContent.replace(/MONGO_INITDB_ROOT_PASSWORD=.*/g, `MONGO_INITDB_ROOT_PASSWORD=${escapeEnvValue(mongoPassword)}`);
908
+ envContent = envContent.replace(/MONGO_INITDB_DATABASE=.*/g, `MONGO_INITDB_DATABASE=${workspaceName}`);
909
+ envContent = envContent.replace(/COMPOSE_PROJECT_NAME=.*/g, `COMPOSE_PROJECT_NAME=${workspaceName}_devcontainer`);
910
+ fs.writeFileSync(devcontainerEnvPath, envContent);
911
+ }
912
+ else {
913
+ const envContent = `MONGO_INITDB_ROOT_PASSWORD=${escapeEnvValue(mongoPassword)}\nMONGO_INITDB_DATABASE=${workspaceName}\nCOMPOSE_PROJECT_NAME=${workspaceName}_devcontainer\n`;
914
+ fs.writeFileSync(devcontainerEnvPath, envContent);
915
+ }
916
+ }
917
+ else if (devcontainerChoice === 'simple') {
918
+ const devcontainerEnvExamplePath = path.join(monorepoPath, '.devcontainer', '.env.example');
919
+ const devcontainerEnvPath = path.join(monorepoPath, '.devcontainer', '.env');
920
+ if (fs.existsSync(devcontainerEnvExamplePath)) {
921
+ fs.copyFileSync(devcontainerEnvExamplePath, devcontainerEnvPath);
922
+ }
923
+ }
924
+ }
925
+ /** Build the global.d.ts content for the generated project */
926
+ function buildGlobalDtsContent() {
927
+ return `/**
928
+ * Global ambient type declarations for Error and globalThis extensions
929
+ */
930
+ declare global {
931
+ interface ErrorConstructor {
932
+ captureStackTrace(targetObject: object, constructorOpt?: Function): void;
933
+ }
934
+ interface Error {
935
+ disposedAt?: string;
936
+ type?: string;
937
+ componentId?: string;
938
+ reasonMap?: Record<string, unknown>;
939
+ metadata?: Record<string, unknown>;
940
+ }
941
+ var GlobalActiveContext: any;
942
+ }
943
+ export {};
944
+ `;
945
+ }
946
+ //# sourceMappingURL=programmatic-generator.js.map