create-krispya 0.5.3 → 0.6.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.
package/dist/cli.mjs CHANGED
@@ -2,14 +2,14 @@
2
2
  import { createRequire } from 'module';
3
3
  import { cwd } from 'process';
4
4
  import { join, dirname, resolve } from 'path';
5
- import { access, constants, mkdir, writeFile, unlink, readFile } from 'fs/promises';
5
+ import { access, constants, readFile, mkdir, writeFile, rm, readdir, unlink } from 'fs/promises';
6
6
  import { constants as constants$1 } from 'fs';
7
7
  import { Command } from 'commander';
8
8
  import * as p from '@clack/prompts';
9
9
  import color from 'chalk';
10
10
  import { fetch } from 'undici';
11
11
  import { spawn } from 'child_process';
12
- import { g as getBaseTemplate, a as getLanguageFromTemplate, b as generateRandomName, c as generateTypescriptConfigPackage, d as generateOxlintConfigPackage, e as generateEslintConfigPackage, f as generateOxfmtConfigPackage, h as generatePrettierConfigPackage, i as generateVscodeFiles, j as generateAiFiles, k as getLatestNpmVersion, l as generate, m as getLatestPnpmVersion, n as getLatestYarnVersion, o as getLatestNpmCliVersion, p as getLatestNodeVersion, v as validatePackageName, q as parseWorkspaceYamlContent } from './chunks/index.mjs';
12
+ import { g as getBaseTemplate, a as getLanguageFromTemplate, b as generateRandomName, d as detectTooling, c as generateAiFiles, A as ALL_AI_PLATFORMS, e as generateVscodeFiles, f as generateTypescriptConfigPackage, h as generateOxlintConfigPackage, i as generateEslintConfigPackage, j as generateOxfmtConfigPackage, k as generatePrettierConfigPackage, p as parseWorkspaceYamlContent, l as getLatestNpmVersion, m as generate, n as getLatestPnpmVersion, o as getLatestYarnVersion, q as getLatestNpmCliVersion, r as getLatestNodeVersion, s as AI_PLATFORM_LABELS, t as AI_PLATFORM_HINTS, v as validatePackageName } from './chunks/index.mjs';
13
13
  import Conf from 'conf';
14
14
 
15
15
  const editorNames = {
@@ -37,14 +37,15 @@ function openInEditor(editor, path, reuseWindow) {
37
37
  });
38
38
  }
39
39
 
40
- function formatConfigSummary(options) {
40
+ function formatConfigSummary(options, inherited) {
41
41
  const lines = [];
42
42
  const VALUE_COL = 27;
43
- const formatRow = (label, value, indent = "") => {
43
+ const formatRow = (label, value, isInherited = false, indent = "") => {
44
44
  const fullLabel = indent + label;
45
45
  const dotCount = Math.max(1, VALUE_COL - fullLabel.length - 1);
46
46
  const dots = color.gray(".".repeat(dotCount));
47
- return `${indent}${label} ${dots} ${value}`;
47
+ const displayValue = isInherited ? `${value} \u{1F512}` : value;
48
+ return `${indent}${label} ${dots} ${displayValue}`;
48
49
  };
49
50
  const formatLanguage = (lang) => {
50
51
  return lang === "typescript" ? "TypeScript" : lang === "javascript" ? "JavaScript" : lang;
@@ -63,20 +64,29 @@ function formatConfigSummary(options) {
63
64
  } else {
64
65
  lines.push(formatRow("Bundler", "vite"));
65
66
  }
66
- lines.push(formatRow("Node version", options.nodeVersion || "latest"));
67
- lines.push(formatRow("Package manager", options.packageManager || "pnpm"));
67
+ const nodeVersionInherited = inherited?.nodeVersion !== void 0;
68
+ lines.push(formatRow("Node version", options.nodeVersion || "latest", nodeVersionInherited));
69
+ const pmInherited = inherited?.packageManager !== void 0;
70
+ lines.push(formatRow("Package manager", options.packageManager || "pnpm", pmInherited));
68
71
  if (options.packageManager === "pnpm") {
69
72
  const versionManaged = options.pnpmManageVersions ? "yes" : "no";
70
- lines.push(formatRow("\u21B3 Version managed", versionManaged, ""));
73
+ const pnpmVersionInherited = inherited?.pnpmManageVersions !== void 0;
74
+ lines.push(formatRow("\u21B3 Version managed", versionManaged, pnpmVersionInherited, ""));
71
75
  }
72
76
  if (options.linter) {
73
- lines.push(formatRow("Linter", options.linter));
77
+ const linterInherited = inherited?.linter !== void 0;
78
+ lines.push(formatRow("Linter", options.linter, linterInherited));
74
79
  }
75
80
  if (options.formatter) {
76
- lines.push(formatRow("Formatter", options.formatter));
81
+ const formatterInherited = inherited?.formatter !== void 0;
82
+ lines.push(formatRow("Formatter", options.formatter, formatterInherited));
77
83
  }
78
84
  const testing = options.testing ?? (projectType === "library" ? "vitest" : "none");
79
85
  lines.push(formatRow("Testing", testing));
86
+ if (!inherited) {
87
+ const configStrategy = options.configStrategy ?? "stealth";
88
+ lines.push(formatRow("Config strategy", configStrategy));
89
+ }
80
90
  if (options.template && getBaseTemplate(options.template) === "r3f") {
81
91
  const integrationNames = [
82
92
  options.drei && "drei",
@@ -138,11 +148,14 @@ function getReuseWindow() {
138
148
  function setReuseWindow(reuse) {
139
149
  config.set("reuseWindow", reuse);
140
150
  }
141
- function getAiFiles() {
142
- return config.get("aiFiles");
151
+ function getAiPlatforms() {
152
+ return config.get("aiPlatforms");
153
+ }
154
+ function setAiPlatforms(platforms) {
155
+ config.set("aiPlatforms", platforms);
143
156
  }
144
- function setAiFiles(files) {
145
- config.set("aiFiles", files);
157
+ function getConfigStrategy() {
158
+ return config.get("configStrategy") ?? "stealth";
146
159
  }
147
160
  function clearConfig() {
148
161
  config.clear();
@@ -154,20 +167,21 @@ function getCustomTemplates() {
154
167
  return config.get("customTemplates") ?? {};
155
168
  }
156
169
 
157
- function getDefaultOptions(template, name, projectType = "app", libraryBundler, integrations, inheritedTooling) {
170
+ function getDefaultOptions(template, name, projectType = "app", libraryBundler, integrations, inheritedSettings) {
158
171
  const baseTemplate = getBaseTemplate(template);
159
172
  const base = {
160
173
  name,
161
174
  template,
162
175
  projectType,
163
176
  libraryBundler: projectType === "library" ? libraryBundler ?? "unbuild" : void 0,
164
- packageManager: "pnpm",
165
- pnpmManageVersions: true,
166
- nodeVersion: "latest",
167
- linter: inheritedTooling?.linter ?? "oxlint",
168
- formatter: inheritedTooling?.formatter ?? "oxfmt",
177
+ packageManager: inheritedSettings?.packageManager ?? "pnpm",
178
+ pnpmManageVersions: inheritedSettings?.pnpmManageVersions ?? true,
179
+ nodeVersion: inheritedSettings?.nodeVersion ?? "latest",
180
+ linter: inheritedSettings?.linter ?? "oxlint",
181
+ formatter: inheritedSettings?.formatter ?? "prettier",
169
182
  // Libraries get vitest by default, apps don't
170
- testing: projectType === "library" ? "vitest" : "none"
183
+ testing: projectType === "library" ? "vitest" : "none",
184
+ configStrategy: getConfigStrategy()
171
185
  };
172
186
  if (baseTemplate === "r3f" && integrations) {
173
187
  return {
@@ -199,7 +213,22 @@ function getDefaultProjectName(template) {
199
213
  return `react-three-${generateRandomName()}`;
200
214
  }
201
215
  }
202
- async function promptForR3fIntegrations() {
216
+ async function promptForR3fIntegrations(presets) {
217
+ const initialValues = [];
218
+ if (presets) {
219
+ if (presets.drei) initialValues.push("drei");
220
+ if (presets.handle) initialValues.push("handle");
221
+ if (presets.leva) initialValues.push("leva");
222
+ if (presets.postprocessing) initialValues.push("postprocessing");
223
+ if (presets.rapier) initialValues.push("rapier");
224
+ if (presets.xr) initialValues.push("xr");
225
+ if (presets.uikit) initialValues.push("uikit");
226
+ if (presets.offscreen) initialValues.push("offscreen");
227
+ if (presets.zustand) initialValues.push("zustand");
228
+ if (presets.koota) initialValues.push("koota");
229
+ if (presets.triplex) initialValues.push("triplex");
230
+ if (presets.viverse) initialValues.push("viverse");
231
+ }
203
232
  const selected = await p.multiselect({
204
233
  message: "R3F integrations",
205
234
  options: [
@@ -216,7 +245,7 @@ async function promptForR3fIntegrations() {
216
245
  { value: "triplex", label: "Triplex" },
217
246
  { value: "viverse", label: "Viverse" }
218
247
  ],
219
- initialValues: ["drei"],
248
+ initialValues: initialValues.length > 0 ? initialValues : ["drei"],
220
249
  required: false
221
250
  });
222
251
  if (p.isCancel(selected)) {
@@ -225,7 +254,7 @@ async function promptForR3fIntegrations() {
225
254
  }
226
255
  return selected;
227
256
  }
228
- async function promptForCustomization(template, name, projectType, integrations, inheritedTooling) {
257
+ async function promptForCustomization(template, name, projectType, integrations, inheritedSettings, presets) {
229
258
  let libraryBundler;
230
259
  if (projectType === "library") {
231
260
  const bundler = await p.select({
@@ -234,7 +263,7 @@ async function promptForCustomization(template, name, projectType, integrations,
234
263
  { value: "unbuild", label: "unbuild", hint: "unjs, simple config" },
235
264
  { value: "tsdown", label: "tsdown", hint: "fast, esbuild-based" }
236
265
  ],
237
- initialValue: "unbuild"
266
+ initialValue: presets?.bundler ?? "unbuild"
238
267
  });
239
268
  if (p.isCancel(bundler)) {
240
269
  p.cancel("Operation cancelled.");
@@ -242,64 +271,57 @@ async function promptForCustomization(template, name, projectType, integrations,
242
271
  }
243
272
  libraryBundler = bundler;
244
273
  }
245
- const nodeVersion = await p.text({
246
- message: "Node.js version",
247
- placeholder: "latest",
248
- defaultValue: "latest",
249
- validate: (value) => {
250
- if (!value.length) return "Required";
251
- if (value !== "latest" && !/^\d+(\.\d+(\.\d+)?)?$/.test(value)) {
252
- return 'Must be "latest" or a valid semver (e.g., "22" or "22.13.0")';
253
- }
254
- }
255
- });
256
- if (p.isCancel(nodeVersion)) {
257
- p.cancel("Operation cancelled.");
258
- process.exit(0);
259
- }
260
- const packageManager = await p.select({
261
- message: "Package manager",
262
- options: [
263
- { value: "pnpm", label: "pnpm" },
264
- { value: "npm", label: "npm" },
265
- { value: "yarn", label: "yarn" },
266
- { value: "custom", label: "Other (custom)" }
267
- ],
268
- initialValue: "pnpm"
269
- });
270
- if (p.isCancel(packageManager)) {
271
- p.cancel("Operation cancelled.");
272
- process.exit(0);
273
- }
274
- let finalPackageManager = packageManager;
275
- if (packageManager === "custom") {
276
- const customPm = await p.text({
277
- message: "Enter package manager command",
274
+ let nodeVersion = inheritedSettings?.nodeVersion ?? presets?.nodeVersion ?? "latest";
275
+ let finalPackageManager = inheritedSettings?.packageManager ?? presets?.packageManager ?? "pnpm";
276
+ let pnpmManageVersions = inheritedSettings?.pnpmManageVersions ?? presets?.pnpmManageVersions ?? true;
277
+ if (!inheritedSettings?.nodeVersion) {
278
+ const nodeVersionInput = await p.text({
279
+ message: "Node.js version",
280
+ placeholder: presets?.nodeVersion ?? "latest",
281
+ defaultValue: presets?.nodeVersion ?? "latest",
278
282
  validate: (value) => {
279
283
  if (!value.length) return "Required";
284
+ if (value !== "latest" && !/^\d+(\.\d+(\.\d+)?)?$/.test(value)) {
285
+ return 'Must be "latest" or a valid semver (e.g., "22" or "22.13.0")';
286
+ }
280
287
  }
281
288
  });
282
- if (p.isCancel(customPm)) {
289
+ if (p.isCancel(nodeVersionInput)) {
283
290
  p.cancel("Operation cancelled.");
284
291
  process.exit(0);
285
292
  }
286
- finalPackageManager = customPm;
293
+ nodeVersion = nodeVersionInput;
287
294
  }
288
- let pnpmManageVersions = true;
289
- if (packageManager === "pnpm") {
290
- const managePnpm = await p.confirm({
291
- message: "Enable manage-package-manager-versions?",
292
- initialValue: true
295
+ if (!inheritedSettings?.packageManager) {
296
+ const packageManager = await p.select({
297
+ message: "Package manager",
298
+ options: [
299
+ { value: "pnpm", label: "pnpm" },
300
+ { value: "npm", label: "npm" },
301
+ { value: "yarn", label: "yarn" }
302
+ ],
303
+ initialValue: presets?.packageManager ?? "pnpm"
293
304
  });
294
- if (p.isCancel(managePnpm)) {
305
+ if (p.isCancel(packageManager)) {
295
306
  p.cancel("Operation cancelled.");
296
307
  process.exit(0);
297
308
  }
298
- pnpmManageVersions = managePnpm;
309
+ finalPackageManager = packageManager;
310
+ if (packageManager === "pnpm") {
311
+ const managePnpm = await p.confirm({
312
+ message: "Enable manage-package-manager-versions?",
313
+ initialValue: presets?.pnpmManageVersions ?? true
314
+ });
315
+ if (p.isCancel(managePnpm)) {
316
+ p.cancel("Operation cancelled.");
317
+ process.exit(0);
318
+ }
319
+ pnpmManageVersions = managePnpm;
320
+ }
299
321
  }
300
- let linter = inheritedTooling?.linter ?? "oxlint";
301
- let formatter = inheritedTooling?.formatter ?? "oxfmt";
302
- if (!inheritedTooling?.linter) {
322
+ let linter = inheritedSettings?.linter ?? presets?.linter ?? "oxlint";
323
+ let formatter = inheritedSettings?.formatter ?? presets?.formatter ?? "prettier";
324
+ if (!inheritedSettings?.linter) {
303
325
  const linterChoice = await p.select({
304
326
  message: "Linter",
305
327
  options: [
@@ -307,7 +329,7 @@ async function promptForCustomization(template, name, projectType, integrations,
307
329
  { value: "eslint", label: "ESLint", hint: "classic" },
308
330
  { value: "biome", label: "Biome", hint: "all-in-one" }
309
331
  ],
310
- initialValue: "oxlint"
332
+ initialValue: presets?.linter ?? "oxlint"
311
333
  });
312
334
  if (p.isCancel(linterChoice)) {
313
335
  p.cancel("Operation cancelled.");
@@ -315,15 +337,15 @@ async function promptForCustomization(template, name, projectType, integrations,
315
337
  }
316
338
  linter = linterChoice;
317
339
  }
318
- if (!inheritedTooling?.formatter) {
340
+ if (!inheritedSettings?.formatter) {
319
341
  const formatterChoice = await p.select({
320
342
  message: "Formatter",
321
343
  options: [
344
+ { value: "prettier", label: "Prettier", hint: "widely adopted" },
322
345
  { value: "oxfmt", label: "Oxfmt", hint: "fast, Prettier-compatible" },
323
- { value: "prettier", label: "Prettier", hint: "classic" },
324
346
  { value: "biome", label: "Biome", hint: "all-in-one" }
325
347
  ],
326
- initialValue: "oxfmt"
348
+ initialValue: presets?.formatter ?? "prettier"
327
349
  });
328
350
  if (p.isCancel(formatterChoice)) {
329
351
  p.cancel("Operation cancelled.");
@@ -355,6 +377,18 @@ async function promptForCustomization(template, name, projectType, integrations,
355
377
  p.cancel("Operation cancelled.");
356
378
  process.exit(0);
357
379
  }
380
+ const configStrategyChoice = await p.select({
381
+ message: "Config strategy",
382
+ options: [
383
+ { value: "stealth", label: "stealth", hint: "configs in .config/" },
384
+ { value: "root", label: "root", hint: "configs at project root" }
385
+ ],
386
+ initialValue: getConfigStrategy()
387
+ });
388
+ if (p.isCancel(configStrategyChoice)) {
389
+ p.cancel("Operation cancelled.");
390
+ process.exit(0);
391
+ }
358
392
  const baseTemplate = getBaseTemplate(template);
359
393
  const finalTemplate = language === "javascript" ? `${baseTemplate}-js` : baseTemplate;
360
394
  const base = {
@@ -367,7 +401,8 @@ async function promptForCustomization(template, name, projectType, integrations,
367
401
  pnpmManageVersions,
368
402
  linter,
369
403
  formatter,
370
- testing
404
+ testing,
405
+ configStrategy: configStrategyChoice
371
406
  };
372
407
  if (baseTemplate === "r3f" && integrations) {
373
408
  return {
@@ -412,14 +447,14 @@ function getDefaultMonorepoOptions(name) {
412
447
  pnpmManageVersions: true,
413
448
  nodeVersion: "latest",
414
449
  linter: "oxlint",
415
- formatter: "oxfmt"
450
+ formatter: "prettier"
416
451
  };
417
452
  }
418
- async function promptForMonorepoCustomization(name) {
453
+ async function promptForMonorepoCustomization(name, presets) {
419
454
  const nodeVersion = await p.text({
420
455
  message: "Node.js version",
421
- placeholder: "latest",
422
- defaultValue: "latest",
456
+ placeholder: presets?.nodeVersion ?? "latest",
457
+ defaultValue: presets?.nodeVersion ?? "latest",
423
458
  validate: (value) => {
424
459
  if (!value.length) return "Required";
425
460
  if (value !== "latest" && !/^\d+(\.\d+(\.\d+)?)?$/.test(value)) {
@@ -433,7 +468,7 @@ async function promptForMonorepoCustomization(name) {
433
468
  }
434
469
  const managePnpm = await p.confirm({
435
470
  message: "Enable manage-package-manager-versions?",
436
- initialValue: true
471
+ initialValue: presets?.pnpmManageVersions ?? true
437
472
  });
438
473
  if (p.isCancel(managePnpm)) {
439
474
  p.cancel("Operation cancelled.");
@@ -446,7 +481,7 @@ async function promptForMonorepoCustomization(name) {
446
481
  { value: "eslint", label: "ESLint", hint: "classic" },
447
482
  { value: "biome", label: "Biome", hint: "all-in-one" }
448
483
  ],
449
- initialValue: "oxlint"
484
+ initialValue: presets?.linter ?? "oxlint"
450
485
  });
451
486
  if (p.isCancel(linter)) {
452
487
  p.cancel("Operation cancelled.");
@@ -455,11 +490,11 @@ async function promptForMonorepoCustomization(name) {
455
490
  const formatter = await p.select({
456
491
  message: "Formatter",
457
492
  options: [
493
+ { value: "prettier", label: "Prettier", hint: "widely adopted" },
458
494
  { value: "oxfmt", label: "Oxfmt", hint: "fast, Prettier-compatible" },
459
- { value: "prettier", label: "Prettier", hint: "classic" },
460
495
  { value: "biome", label: "Biome", hint: "all-in-one" }
461
496
  ],
462
- initialValue: "oxfmt"
497
+ initialValue: presets?.formatter ?? "prettier"
463
498
  });
464
499
  if (p.isCancel(formatter)) {
465
500
  p.cancel("Operation cancelled.");
@@ -475,8 +510,15 @@ async function promptForMonorepoCustomization(name) {
475
510
  formatter
476
511
  };
477
512
  }
478
- async function promptForMonorepo(workspaceName) {
513
+ async function promptForMonorepo(workspaceName, presets) {
479
514
  const defaultOptions = getDefaultMonorepoOptions(workspaceName);
515
+ if (presets) {
516
+ if (presets.linter) defaultOptions.linter = presets.linter;
517
+ if (presets.formatter) defaultOptions.formatter = presets.formatter;
518
+ if (presets.nodeVersion) defaultOptions.nodeVersion = presets.nodeVersion;
519
+ if (presets.pnpmManageVersions !== void 0)
520
+ defaultOptions.pnpmManageVersions = presets.pnpmManageVersions;
521
+ }
480
522
  p.note(
481
523
  formatMonorepoConfigSummary({
482
524
  name: defaultOptions.name,
@@ -484,24 +526,28 @@ async function promptForMonorepo(workspaceName) {
484
526
  packageManager: defaultOptions.packageManager ?? "pnpm",
485
527
  pnpmManageVersions: defaultOptions.pnpmManageVersions,
486
528
  linter: defaultOptions.linter ?? "oxlint",
487
- formatter: defaultOptions.formatter ?? "oxfmt"
529
+ formatter: defaultOptions.formatter ?? "prettier"
488
530
  }),
489
531
  "Workspace Configuration"
490
532
  );
491
- const proceed = await p.confirm({
533
+ const proceed = await p.select({
492
534
  message: "Proceed with these settings?",
493
- initialValue: true
535
+ options: [
536
+ { value: "continue", label: "Yes, continue" },
537
+ { value: "customize", label: "No, customize settings" }
538
+ ],
539
+ initialValue: "continue"
494
540
  });
495
541
  if (p.isCancel(proceed)) {
496
542
  p.cancel("Operation cancelled.");
497
543
  process.exit(0);
498
544
  }
499
- if (proceed) {
545
+ if (proceed === "continue") {
500
546
  return defaultOptions;
501
547
  }
502
- return promptForMonorepoCustomization(workspaceName);
548
+ return promptForMonorepoCustomization(workspaceName, presets);
503
549
  }
504
- async function promptForOptions(name) {
550
+ async function promptForOptions(name, presets) {
505
551
  let projectName = name;
506
552
  if (!projectName) {
507
553
  const nameResult = await p.text({
@@ -525,30 +571,36 @@ async function promptForOptions(name) {
525
571
  { value: "library", label: "Library" },
526
572
  { value: "monorepo", label: "Monorepo" }
527
573
  ],
528
- initialValue: "app"
574
+ initialValue: presets?.type ?? "app"
529
575
  });
530
576
  if (p.isCancel(projectType)) {
531
577
  p.cancel("Operation cancelled.");
532
578
  process.exit(0);
533
579
  }
534
580
  if (projectType === "monorepo") {
535
- return promptForMonorepo(projectName);
581
+ return promptForMonorepo(projectName, presets);
536
582
  }
537
- return promptForPackageOptions(projectName, projectType);
583
+ return promptForPackageOptions(
584
+ projectName,
585
+ projectType,
586
+ void 0,
587
+ presets
588
+ );
538
589
  }
539
- function customTemplateToOptions(customTemplate, name, projectType) {
590
+ function customTemplateToOptions(customTemplate, name, projectType, inheritedSettings) {
540
591
  const baseTemplate = customTemplate.baseTemplate;
541
592
  const template = baseTemplate;
542
593
  const base = {
543
594
  name,
544
595
  template,
545
596
  projectType,
546
- packageManager: "pnpm",
547
- pnpmManageVersions: true,
548
- nodeVersion: "latest",
549
- linter: customTemplate.linter,
550
- formatter: customTemplate.formatter,
551
- testing: customTemplate.testing
597
+ packageManager: inheritedSettings?.packageManager ?? "pnpm",
598
+ pnpmManageVersions: inheritedSettings?.pnpmManageVersions ?? true,
599
+ nodeVersion: inheritedSettings?.nodeVersion ?? "latest",
600
+ linter: inheritedSettings?.linter ?? customTemplate.linter,
601
+ formatter: inheritedSettings?.formatter ?? customTemplate.formatter,
602
+ testing: customTemplate.testing,
603
+ configStrategy: customTemplate.configStrategy ?? getConfigStrategy()
552
604
  };
553
605
  if (baseTemplate === "r3f" && customTemplate.integrations) {
554
606
  const integrations = customTemplate.integrations;
@@ -570,7 +622,17 @@ function customTemplateToOptions(customTemplate, name, projectType) {
570
622
  }
571
623
  return base;
572
624
  }
573
- async function promptForPackageOptions(projectName, projectType, inheritedTooling) {
625
+ function presetsToInheritedSettings(presets) {
626
+ if (!presets) return void 0;
627
+ return {
628
+ linter: presets.linter,
629
+ formatter: presets.formatter,
630
+ packageManager: presets.packageManager,
631
+ nodeVersion: presets.nodeVersion,
632
+ pnpmManageVersions: presets.pnpmManageVersions
633
+ };
634
+ }
635
+ async function promptForPackageOptions(projectName, projectType, inheritedSettings, presets) {
574
636
  const builtInOptions = [
575
637
  { value: "vanilla", label: "Vanilla" },
576
638
  { value: "react", label: "React" },
@@ -586,7 +648,7 @@ async function promptForPackageOptions(projectName, projectType, inheritedToolin
586
648
  const templateSelection = await p.select({
587
649
  message: "Select a template",
588
650
  options: allOptions,
589
- initialValue: "vanilla"
651
+ initialValue: presets?.template ?? "vanilla"
590
652
  });
591
653
  if (p.isCancel(templateSelection)) {
592
654
  p.cancel("Operation cancelled.");
@@ -596,24 +658,27 @@ async function promptForPackageOptions(projectName, projectType, inheritedToolin
596
658
  if (selection.startsWith("custom:")) {
597
659
  const customName = selection.slice(7);
598
660
  const customTemplate = customTemplates[customName];
599
- const defaultOptions2 = customTemplateToOptions(customTemplate, projectName, projectType);
600
- if (inheritedTooling?.linter) {
601
- defaultOptions2.linter = inheritedTooling.linter;
602
- }
603
- if (inheritedTooling?.formatter) {
604
- defaultOptions2.formatter = inheritedTooling.formatter;
605
- }
606
- const configTitle2 = inheritedTooling ? `Template: ${customName} (using workspace tooling)` : `Template: ${customName}`;
607
- p.note(formatConfigSummary(defaultOptions2), configTitle2);
608
- const proceed2 = await p.confirm({
661
+ const defaultOptions2 = customTemplateToOptions(
662
+ customTemplate,
663
+ projectName,
664
+ projectType,
665
+ inheritedSettings
666
+ );
667
+ const configTitle2 = inheritedSettings ? `Template: ${customName} (using workspace settings)` : `Template: ${customName}`;
668
+ p.note(formatConfigSummary(defaultOptions2, inheritedSettings), configTitle2);
669
+ const proceed2 = await p.select({
609
670
  message: "Proceed with these settings?",
610
- initialValue: true
671
+ options: [
672
+ { value: "continue", label: "Yes, continue" },
673
+ { value: "customize", label: "No, customize settings" }
674
+ ],
675
+ initialValue: "continue"
611
676
  });
612
677
  if (p.isCancel(proceed2)) {
613
678
  p.cancel("Operation cancelled.");
614
679
  process.exit(0);
615
680
  }
616
- if (proceed2) {
681
+ if (proceed2 === "continue") {
617
682
  return defaultOptions2;
618
683
  }
619
684
  return promptForCustomization(
@@ -621,37 +686,48 @@ async function promptForPackageOptions(projectName, projectType, inheritedToolin
621
686
  projectName,
622
687
  projectType,
623
688
  customTemplate.integrations,
624
- inheritedTooling
689
+ inheritedSettings
625
690
  );
626
691
  }
627
692
  const template = selection;
628
693
  const baseTemplate = getBaseTemplate(template);
629
694
  let integrations;
630
695
  if (baseTemplate === "r3f") {
631
- integrations = await promptForR3fIntegrations();
696
+ integrations = await promptForR3fIntegrations(presets);
632
697
  }
633
698
  const defaultOptions = getDefaultOptions(
634
699
  template,
635
700
  projectName,
636
701
  projectType,
637
- void 0,
702
+ presets?.bundler,
638
703
  integrations,
639
- inheritedTooling
704
+ inheritedSettings ?? presetsToInheritedSettings(presets)
640
705
  );
641
- const configTitle = inheritedTooling ? "Template Configuration (using workspace tooling)" : "Template Configuration";
642
- p.note(formatConfigSummary(defaultOptions), configTitle);
643
- const proceed = await p.confirm({
706
+ const configTitle = inheritedSettings ? "Template Configuration (using workspace settings)" : "Template Configuration";
707
+ p.note(formatConfigSummary(defaultOptions, inheritedSettings), configTitle);
708
+ const proceed = await p.select({
644
709
  message: "Proceed with these settings?",
645
- initialValue: true
710
+ options: [
711
+ { value: "continue", label: "Yes, continue" },
712
+ { value: "customize", label: "No, customize settings" }
713
+ ],
714
+ initialValue: "continue"
646
715
  });
647
716
  if (p.isCancel(proceed)) {
648
717
  p.cancel("Operation cancelled.");
649
718
  process.exit(0);
650
719
  }
651
- if (proceed) {
720
+ if (proceed === "continue") {
652
721
  return defaultOptions;
653
722
  }
654
- return promptForCustomization(template, projectName, projectType, integrations, inheritedTooling);
723
+ return promptForCustomization(
724
+ template,
725
+ projectName,
726
+ projectType,
727
+ integrations,
728
+ inheritedSettings,
729
+ presets
730
+ );
655
731
  }
656
732
 
657
733
  async function checkAnyExists(paths) {
@@ -699,8 +775,586 @@ async function validateWorkspace(monorepoRoot) {
699
775
  return { valid: errors.length === 0, errors };
700
776
  }
701
777
 
778
+ async function detectCurrentConfig(root) {
779
+ let name = root.split(/[/\\]/).pop() ?? "workspace";
780
+ try {
781
+ const pkgPath = join(root, "package.json");
782
+ const content = await readFile(pkgPath, "utf-8");
783
+ const pkgJson = JSON.parse(content);
784
+ if (pkgJson.name) {
785
+ name = pkgJson.name.replace(/^@/, "").replace(/\/.*$/, "");
786
+ }
787
+ } catch {
788
+ }
789
+ const tooling = await detectTooling(root);
790
+ return {
791
+ name,
792
+ linter: tooling.linter ?? "oxlint",
793
+ formatter: tooling.formatter ?? "prettier",
794
+ packageManager: "pnpm"
795
+ };
796
+ }
797
+ function generateExpectedFiles(config) {
798
+ const { name, linter, formatter, packageManager } = config;
799
+ const aiFilesMap = {};
800
+ generateAiFiles(aiFilesMap, {
801
+ name,
802
+ packageManager,
803
+ linter,
804
+ formatter,
805
+ isMonorepo: true,
806
+ platforms: ALL_AI_PLATFORMS
807
+ });
808
+ const vscodeFiles = {};
809
+ generateVscodeFiles(vscodeFiles, linter, formatter);
810
+ const configPackages = {};
811
+ generateTypescriptConfigPackage(configPackages);
812
+ if (linter === "oxlint") {
813
+ generateOxlintConfigPackage(configPackages);
814
+ } else if (linter === "eslint") {
815
+ generateEslintConfigPackage(configPackages);
816
+ }
817
+ if (formatter === "oxfmt") {
818
+ generateOxfmtConfigPackage(configPackages);
819
+ } else if (formatter === "prettier") {
820
+ generatePrettierConfigPackage(configPackages);
821
+ }
822
+ const workspaceConfig = {};
823
+ const rootConfig = {};
824
+ rootConfig[".gitignore"] = {
825
+ type: "text",
826
+ content: ["node_modules", "dist", "*.tsbuildinfo", ".DS_Store"].join("\n")
827
+ };
828
+ rootConfig[".gitattributes"] = {
829
+ type: "text",
830
+ content: `* text=auto eol=lf
831
+ *.{cmd,[cC][mM][dD]} text eol=crlf
832
+ *.{bat,[bB][aA][tT]} text eol=crlf
833
+ `
834
+ };
835
+ if (linter === "biome" || formatter === "biome") {
836
+ const biomeConfig = {
837
+ $schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
838
+ vcs: {
839
+ enabled: true,
840
+ clientKind: "git",
841
+ useIgnoreFile: true
842
+ },
843
+ linter: {
844
+ enabled: linter === "biome",
845
+ rules: {
846
+ recommended: true
847
+ }
848
+ },
849
+ formatter: {
850
+ enabled: formatter === "biome"
851
+ }
852
+ };
853
+ rootConfig["biome.json"] = {
854
+ type: "text",
855
+ content: JSON.stringify(biomeConfig, null, 2)
856
+ };
857
+ }
858
+ return {
859
+ "ai-files": aiFilesMap,
860
+ vscode: vscodeFiles,
861
+ "config-packages": configPackages,
862
+ "workspace-config": workspaceConfig,
863
+ "root-config": rootConfig
864
+ };
865
+ }
866
+ async function fileExists$1(path) {
867
+ try {
868
+ await access(path, constants$1.F_OK);
869
+ return true;
870
+ } catch {
871
+ return false;
872
+ }
873
+ }
874
+ async function compareWithDisk(expected, root) {
875
+ const categoryLabels = {
876
+ "ai-files": "AI Files",
877
+ vscode: "VS Code",
878
+ "config-packages": "Config Packages",
879
+ "workspace-config": "Workspace Config",
880
+ "root-config": "Root Config"
881
+ };
882
+ const categories = [];
883
+ for (const [category, files] of Object.entries(expected)) {
884
+ const changes = [];
885
+ for (const [filePath, file] of Object.entries(files)) {
886
+ if (file.type !== "text") continue;
887
+ const fullPath = join(root, filePath);
888
+ const newContent = file.content;
889
+ if (await fileExists$1(fullPath)) {
890
+ const currentContent = await readFile(fullPath, "utf-8");
891
+ if (currentContent === newContent) {
892
+ changes.push({
893
+ path: filePath,
894
+ status: "unchanged",
895
+ currentContent,
896
+ newContent
897
+ });
898
+ } else {
899
+ changes.push({
900
+ path: filePath,
901
+ status: "modified",
902
+ currentContent,
903
+ newContent
904
+ });
905
+ }
906
+ } else {
907
+ changes.push({
908
+ path: filePath,
909
+ status: "added",
910
+ newContent
911
+ });
912
+ }
913
+ }
914
+ if (changes.length === 0) continue;
915
+ const hasUserModifications = changes.some((c) => c.status === "modified");
916
+ categories.push({
917
+ category,
918
+ label: categoryLabels[category],
919
+ changes,
920
+ hasUserModifications
921
+ });
922
+ }
923
+ return categories;
924
+ }
925
+ async function getWorkspaceConfigUpdates(root) {
926
+ const workspacePath = join(root, "pnpm-workspace.yaml");
927
+ const changes = [];
928
+ let currentContent = "";
929
+ let exists = false;
930
+ try {
931
+ currentContent = await readFile(workspacePath, "utf-8");
932
+ exists = true;
933
+ } catch {
934
+ }
935
+ if (!exists) {
936
+ const newContent = `manage-package-manager-versions: true
937
+
938
+ packages:
939
+ - ".config/*"
940
+ - "apps/*"
941
+ - "packages/*"
942
+
943
+ onlyBuiltDependencies:
944
+ - esbuild
945
+ `;
946
+ changes.push({
947
+ path: "pnpm-workspace.yaml",
948
+ status: "added",
949
+ newContent
950
+ });
951
+ return changes;
952
+ }
953
+ let updatedContent = currentContent;
954
+ let needsUpdate = false;
955
+ if (!currentContent.includes("manage-package-manager-versions")) {
956
+ updatedContent = `manage-package-manager-versions: true
957
+
958
+ ${updatedContent}`;
959
+ needsUpdate = true;
960
+ }
961
+ if (!currentContent.includes("onlyBuiltDependencies")) {
962
+ updatedContent = `${updatedContent.trimEnd()}
963
+
964
+ onlyBuiltDependencies:
965
+ - esbuild
966
+ `;
967
+ needsUpdate = true;
968
+ }
969
+ if (!currentContent.includes(".config/*") && !currentContent.includes('".config/*"')) {
970
+ const lines = updatedContent.split("\n");
971
+ const packagesIndex = lines.findIndex(
972
+ (line) => line.trim().startsWith("packages:")
973
+ );
974
+ if (packagesIndex !== -1) {
975
+ lines.splice(packagesIndex + 1, 0, ' - ".config/*"');
976
+ updatedContent = lines.join("\n");
977
+ needsUpdate = true;
978
+ }
979
+ }
980
+ if (needsUpdate) {
981
+ changes.push({
982
+ path: "pnpm-workspace.yaml",
983
+ status: "modified",
984
+ currentContent,
985
+ newContent: updatedContent
986
+ });
987
+ } else {
988
+ changes.push({
989
+ path: "pnpm-workspace.yaml",
990
+ status: "unchanged",
991
+ currentContent,
992
+ newContent: currentContent
993
+ });
994
+ }
995
+ return changes;
996
+ }
997
+ async function applyUpdates(changes, root) {
998
+ for (const change of changes) {
999
+ if (change.status === "unchanged") continue;
1000
+ const fullPath = join(root, change.path);
1001
+ await mkdir(dirname(fullPath), { recursive: true });
1002
+ await writeFile(fullPath, change.newContent);
1003
+ }
1004
+ }
1005
+ function formatFileChange(change) {
1006
+ const icon = change.status === "added" ? "+" : change.status === "modified" ? "~" : "=";
1007
+ return ` ${icon} ${change.path}`;
1008
+ }
1009
+ const LINTER_DEPS = {
1010
+ oxlint: "oxlint",
1011
+ eslint: "eslint",
1012
+ biome: "@biomejs/biome"
1013
+ };
1014
+ const FORMATTER_DEPS = {
1015
+ oxfmt: "oxfmt",
1016
+ prettier: "prettier",
1017
+ biome: "@biomejs/biome"
1018
+ };
1019
+ const LINTER_CONFIG_PACKAGES = {
1020
+ oxlint: "@config/oxlint",
1021
+ eslint: "@config/eslint",
1022
+ biome: null
1023
+ // biome uses root biome.json
1024
+ };
1025
+ const FORMATTER_CONFIG_PACKAGES = {
1026
+ oxfmt: "@config/oxfmt",
1027
+ prettier: "@config/prettier",
1028
+ biome: null
1029
+ // biome uses root biome.json
1030
+ };
1031
+ function needsMigration(current, target) {
1032
+ const linterChange = target.linter && target.linter !== current.linter;
1033
+ const formatterChange = target.formatter && target.formatter !== current.formatter;
1034
+ return linterChange || formatterChange || false;
1035
+ }
1036
+ async function getMigrationPlan(current, target, root) {
1037
+ const toLinter = target.linter ?? current.linter;
1038
+ const toFormatter = target.formatter ?? current.formatter;
1039
+ const changes = [];
1040
+ if (toLinter !== current.linter) {
1041
+ if (current.linter !== "biome") {
1042
+ changes.push({
1043
+ type: "remove-dir",
1044
+ path: `.config/${current.linter}`,
1045
+ description: `Remove @config/${current.linter} package`
1046
+ });
1047
+ }
1048
+ if (toLinter !== "biome") {
1049
+ const files = {};
1050
+ if (toLinter === "oxlint") {
1051
+ generateOxlintConfigPackage(files);
1052
+ } else if (toLinter === "eslint") {
1053
+ generateEslintConfigPackage(files);
1054
+ }
1055
+ for (const [path, file] of Object.entries(files)) {
1056
+ if (file.type === "text") {
1057
+ changes.push({
1058
+ type: "add-file",
1059
+ path,
1060
+ description: `Add ${path}`,
1061
+ content: file.content
1062
+ });
1063
+ }
1064
+ }
1065
+ }
1066
+ if (toLinter === "biome" && toFormatter === "biome") {
1067
+ changes.push({
1068
+ type: "add-file",
1069
+ path: "biome.json",
1070
+ description: "Add biome.json config",
1071
+ content: JSON.stringify(
1072
+ {
1073
+ $schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
1074
+ vcs: { enabled: true, clientKind: "git", useIgnoreFile: true },
1075
+ linter: { enabled: true, rules: { recommended: true } },
1076
+ formatter: { enabled: true }
1077
+ },
1078
+ null,
1079
+ 2
1080
+ )
1081
+ });
1082
+ } else if (toLinter === "biome" && toFormatter !== "biome") {
1083
+ changes.push({
1084
+ type: "add-file",
1085
+ path: "biome.json",
1086
+ description: "Add biome.json config (linter only)",
1087
+ content: JSON.stringify(
1088
+ {
1089
+ $schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
1090
+ vcs: { enabled: true, clientKind: "git", useIgnoreFile: true },
1091
+ linter: { enabled: true, rules: { recommended: true } },
1092
+ formatter: { enabled: false }
1093
+ },
1094
+ null,
1095
+ 2
1096
+ )
1097
+ });
1098
+ }
1099
+ if (current.linter === "biome" && toLinter !== "biome" && current.formatter !== "biome" && toFormatter !== "biome") {
1100
+ changes.push({
1101
+ type: "remove-file",
1102
+ path: "biome.json",
1103
+ description: "Remove biome.json"
1104
+ });
1105
+ }
1106
+ }
1107
+ if (toFormatter !== current.formatter) {
1108
+ const formatterSameAsLinter = current.formatter === current.linter;
1109
+ if (current.formatter !== "biome" && !formatterSameAsLinter) {
1110
+ changes.push({
1111
+ type: "remove-dir",
1112
+ path: `.config/${current.formatter}`,
1113
+ description: `Remove @config/${current.formatter} package`
1114
+ });
1115
+ }
1116
+ const newFormatterSameAsLinter = toFormatter === toLinter;
1117
+ if (toFormatter !== "biome" && !newFormatterSameAsLinter) {
1118
+ const files = {};
1119
+ if (toFormatter === "oxfmt") {
1120
+ generateOxfmtConfigPackage(files);
1121
+ } else if (toFormatter === "prettier") {
1122
+ generatePrettierConfigPackage(files);
1123
+ }
1124
+ for (const [path, file] of Object.entries(files)) {
1125
+ if (file.type === "text") {
1126
+ changes.push({
1127
+ type: "add-file",
1128
+ path,
1129
+ description: `Add ${path}`,
1130
+ content: file.content
1131
+ });
1132
+ }
1133
+ }
1134
+ }
1135
+ if (toFormatter === "biome" && toLinter !== "biome") {
1136
+ changes.push({
1137
+ type: "add-file",
1138
+ path: "biome.json",
1139
+ description: "Add biome.json config (formatter only)",
1140
+ content: JSON.stringify(
1141
+ {
1142
+ $schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
1143
+ vcs: { enabled: true, clientKind: "git", useIgnoreFile: true },
1144
+ linter: { enabled: false },
1145
+ formatter: { enabled: true }
1146
+ },
1147
+ null,
1148
+ 2
1149
+ )
1150
+ });
1151
+ }
1152
+ if (current.formatter === "biome" && toFormatter !== "biome" && current.linter !== "biome" && toLinter !== "biome") {
1153
+ changes.push({
1154
+ type: "remove-file",
1155
+ path: "biome.json",
1156
+ description: "Remove biome.json"
1157
+ });
1158
+ }
1159
+ }
1160
+ changes.push({
1161
+ type: "update-package-json",
1162
+ path: "package.json",
1163
+ description: "Update root package.json (devDependencies, scripts)"
1164
+ });
1165
+ const subPackageUpdates = await getSubPackageUpdates(
1166
+ root,
1167
+ current,
1168
+ toLinter,
1169
+ toFormatter
1170
+ );
1171
+ return {
1172
+ fromLinter: current.linter,
1173
+ toLinter,
1174
+ fromFormatter: current.formatter,
1175
+ toFormatter,
1176
+ changes,
1177
+ subPackageUpdates
1178
+ };
1179
+ }
1180
+ async function getSubPackageUpdates(root, current, toLinter, toFormatter) {
1181
+ const updates = [];
1182
+ const workspacePath = join(root, "pnpm-workspace.yaml");
1183
+ let workspaceContent;
1184
+ try {
1185
+ workspaceContent = await readFile(workspacePath, "utf-8");
1186
+ } catch {
1187
+ return updates;
1188
+ }
1189
+ const packageGlobs = parseWorkspaceYamlContent(workspaceContent);
1190
+ for (const glob of packageGlobs) {
1191
+ if (glob.includes(".config")) continue;
1192
+ const baseDir = glob.replace(/\/\*$/, "").replace(/^["']|["']$/g, "");
1193
+ const basePath = join(root, baseDir);
1194
+ try {
1195
+ const entries = await readdir(basePath, { withFileTypes: true });
1196
+ for (const entry of entries) {
1197
+ if (!entry.isDirectory()) continue;
1198
+ const pkgJsonPath = join(basePath, entry.name, "package.json");
1199
+ try {
1200
+ const content = await readFile(pkgJsonPath, "utf-8");
1201
+ const pkg = JSON.parse(content);
1202
+ const devDeps = pkg.devDependencies ?? {};
1203
+ const remove = [];
1204
+ const add = [];
1205
+ const oldLinterPkg = LINTER_CONFIG_PACKAGES[current.linter];
1206
+ const newLinterPkg = LINTER_CONFIG_PACKAGES[toLinter];
1207
+ if (oldLinterPkg && oldLinterPkg !== newLinterPkg && devDeps[oldLinterPkg]) {
1208
+ remove.push(oldLinterPkg);
1209
+ }
1210
+ if (newLinterPkg && newLinterPkg !== oldLinterPkg && oldLinterPkg && devDeps[oldLinterPkg]) {
1211
+ add.push(newLinterPkg);
1212
+ }
1213
+ if (current.formatter !== current.linter) {
1214
+ const oldFormatterPkg = FORMATTER_CONFIG_PACKAGES[current.formatter];
1215
+ const newFormatterPkg = FORMATTER_CONFIG_PACKAGES[toFormatter];
1216
+ if (oldFormatterPkg && oldFormatterPkg !== newFormatterPkg && devDeps[oldFormatterPkg]) {
1217
+ remove.push(oldFormatterPkg);
1218
+ }
1219
+ if (newFormatterPkg && newFormatterPkg !== oldFormatterPkg && oldFormatterPkg && devDeps[oldFormatterPkg]) {
1220
+ add.push(newFormatterPkg);
1221
+ }
1222
+ }
1223
+ if (remove.length > 0 || add.length > 0) {
1224
+ updates.push({
1225
+ path: join(baseDir, entry.name, "package.json"),
1226
+ remove,
1227
+ add
1228
+ });
1229
+ }
1230
+ } catch {
1231
+ }
1232
+ }
1233
+ } catch {
1234
+ }
1235
+ }
1236
+ return updates;
1237
+ }
1238
+ async function applyMigration(plan, root) {
1239
+ for (const change of plan.changes) {
1240
+ if (change.type === "remove-dir") {
1241
+ const fullPath = join(root, change.path);
1242
+ try {
1243
+ await rm(fullPath, { recursive: true });
1244
+ } catch {
1245
+ }
1246
+ }
1247
+ }
1248
+ for (const change of plan.changes) {
1249
+ if (change.type === "remove-file") {
1250
+ const fullPath = join(root, change.path);
1251
+ try {
1252
+ await rm(fullPath);
1253
+ } catch {
1254
+ }
1255
+ }
1256
+ }
1257
+ for (const change of plan.changes) {
1258
+ if (change.type === "add-file" && change.content) {
1259
+ const fullPath = join(root, change.path);
1260
+ await mkdir(dirname(fullPath), { recursive: true });
1261
+ await writeFile(fullPath, change.content);
1262
+ }
1263
+ }
1264
+ await updateRootPackageJson(root, plan);
1265
+ for (const update of plan.subPackageUpdates) {
1266
+ await updateSubPackageJson(root, update);
1267
+ }
1268
+ }
1269
+ async function updateRootPackageJson(root, plan) {
1270
+ const pkgPath = join(root, "package.json");
1271
+ const content = await readFile(pkgPath, "utf-8");
1272
+ const pkg = JSON.parse(content);
1273
+ const devDeps = pkg.devDependencies ?? {};
1274
+ const oldLinterDep = LINTER_DEPS[plan.fromLinter];
1275
+ delete devDeps[oldLinterDep];
1276
+ if (plan.fromFormatter !== plan.fromLinter) {
1277
+ const oldFormatterDep = FORMATTER_DEPS[plan.fromFormatter];
1278
+ delete devDeps[oldFormatterDep];
1279
+ }
1280
+ const newLinterDep = LINTER_DEPS[plan.toLinter];
1281
+ if (plan.toLinter === "oxlint") {
1282
+ devDeps[newLinterDep] = "^1.36.0";
1283
+ } else if (plan.toLinter === "eslint") {
1284
+ devDeps[newLinterDep] = "^9.17.0";
1285
+ } else if (plan.toLinter === "biome") {
1286
+ devDeps[newLinterDep] = "^1.9.4";
1287
+ }
1288
+ if (plan.toFormatter !== plan.toLinter) {
1289
+ const newFormatterDep = FORMATTER_DEPS[plan.toFormatter];
1290
+ if (plan.toFormatter === "oxfmt") {
1291
+ devDeps[newFormatterDep] = "^0.21.0";
1292
+ } else if (plan.toFormatter === "prettier") {
1293
+ devDeps[newFormatterDep] = "^3.4.2";
1294
+ } else if (plan.toFormatter === "biome") {
1295
+ devDeps[newFormatterDep] = "^1.9.4";
1296
+ }
1297
+ }
1298
+ pkg.devDependencies = Object.fromEntries(
1299
+ Object.entries(devDeps).sort(([a], [b]) => a.localeCompare(b))
1300
+ );
1301
+ const scripts = pkg.scripts ?? {};
1302
+ if (plan.toLinter === "oxlint") {
1303
+ scripts.lint = "oxlint .";
1304
+ } else if (plan.toLinter === "eslint") {
1305
+ scripts.lint = "eslint .";
1306
+ } else if (plan.toLinter === "biome") {
1307
+ scripts.lint = "biome check .";
1308
+ }
1309
+ if (plan.toFormatter === "oxfmt") {
1310
+ scripts.format = "oxfmt .";
1311
+ } else if (plan.toFormatter === "prettier") {
1312
+ scripts.format = "prettier --write .";
1313
+ } else if (plan.toFormatter === "biome") {
1314
+ scripts.format = "biome format . --write";
1315
+ }
1316
+ pkg.scripts = scripts;
1317
+ await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
1318
+ }
1319
+ async function updateSubPackageJson(root, update) {
1320
+ const pkgPath = join(root, update.path);
1321
+ const content = await readFile(pkgPath, "utf-8");
1322
+ const pkg = JSON.parse(content);
1323
+ const devDeps = pkg.devDependencies ?? {};
1324
+ for (const dep of update.remove) {
1325
+ delete devDeps[dep];
1326
+ }
1327
+ for (const dep of update.add) {
1328
+ devDeps[dep] = "workspace:*";
1329
+ }
1330
+ pkg.devDependencies = Object.fromEntries(
1331
+ Object.entries(devDeps).sort(([a], [b]) => a.localeCompare(b))
1332
+ );
1333
+ await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
1334
+ }
1335
+ function formatMigrationChange(change) {
1336
+ const icon = change.type === "remove-dir" || change.type === "remove-file" ? "-" : change.type === "add-file" ? "+" : "~";
1337
+ return ` ${icon} ${change.description}`;
1338
+ }
1339
+
702
1340
  const require$1 = createRequire(import.meta.url);
703
1341
  const pkg = require$1("../package.json");
1342
+ const META_OPTIONS = [
1343
+ "clearConfig",
1344
+ "configPath",
1345
+ "check",
1346
+ "fix",
1347
+ "update",
1348
+ "yes",
1349
+ "workspace",
1350
+ "path",
1351
+ "dir"
1352
+ ];
1353
+ function hasConfigOptions(options) {
1354
+ return Object.keys(options).some(
1355
+ (key) => !META_OPTIONS.includes(key)
1356
+ );
1357
+ }
704
1358
  async function fileExists(path) {
705
1359
  try {
706
1360
  await access(path, constants$1.F_OK);
@@ -709,6 +1363,50 @@ async function fileExists(path) {
709
1363
  return false;
710
1364
  }
711
1365
  }
1366
+ async function promptForAiPlatforms(isNonInteractive) {
1367
+ const savedPlatforms = getAiPlatforms();
1368
+ if (isNonInteractive) {
1369
+ return savedPlatforms ?? ALL_AI_PLATFORMS;
1370
+ }
1371
+ if (savedPlatforms && savedPlatforms.length > 0) {
1372
+ const savedLabels = savedPlatforms.map((plat) => AI_PLATFORM_LABELS[plat]).join(", ");
1373
+ const useDefault = await p.confirm({
1374
+ message: `Add AI rules? ${color.dim(`(${savedLabels})`)}`,
1375
+ initialValue: true
1376
+ });
1377
+ if (p.isCancel(useDefault)) {
1378
+ return [];
1379
+ }
1380
+ if (useDefault) {
1381
+ return savedPlatforms;
1382
+ }
1383
+ }
1384
+ const selected = await p.multiselect({
1385
+ message: "Add AI rules?",
1386
+ options: ALL_AI_PLATFORMS.map((platform) => ({
1387
+ value: platform,
1388
+ label: AI_PLATFORM_LABELS[platform],
1389
+ hint: AI_PLATFORM_HINTS[platform]
1390
+ })),
1391
+ initialValues: [],
1392
+ required: false
1393
+ });
1394
+ if (p.isCancel(selected)) {
1395
+ return [];
1396
+ }
1397
+ const platforms = selected;
1398
+ if (platforms.length === 0) {
1399
+ return [];
1400
+ }
1401
+ const saveChoice = await p.confirm({
1402
+ message: "Save selection for future projects?",
1403
+ initialValue: true
1404
+ });
1405
+ if (!p.isCancel(saveChoice) && saveChoice) {
1406
+ setAiPlatforms(platforms);
1407
+ }
1408
+ return platforms;
1409
+ }
712
1410
  async function writeGeneratedFiles(basePath, files) {
713
1411
  const filePaths = Object.keys(files).sort();
714
1412
  for (const filePath of filePaths) {
@@ -753,15 +1451,39 @@ async function parseWorkspaceDirectories(monorepoRoot) {
753
1451
  return [];
754
1452
  }
755
1453
  }
756
- async function detectWorkspaceTooling(monorepoRoot) {
1454
+ async function detectWorkspaceSettings(monorepoRoot) {
757
1455
  try {
1456
+ const tooling = await detectTooling(monorepoRoot);
758
1457
  const pkgPath = join(monorepoRoot, "package.json");
759
1458
  const content = await readFile(pkgPath, "utf-8");
760
1459
  const pkgJson = JSON.parse(content);
761
- const devDeps = pkgJson.devDependencies ?? {};
762
- const linter = devDeps.oxlint ? "oxlint" : devDeps.eslint ? "eslint" : devDeps["@biomejs/biome"] ? "biome" : void 0;
763
- const formatter = devDeps.oxfmt ? "oxfmt" : devDeps.prettier ? "prettier" : devDeps["@biomejs/biome"] ? "biome" : void 0;
764
- return { linter, formatter };
1460
+ let packageManager;
1461
+ if (pkgJson.packageManager) {
1462
+ packageManager = pkgJson.packageManager.split("@")[0];
1463
+ }
1464
+ let nodeVersion;
1465
+ if (pkgJson.engines?.node) {
1466
+ const match = pkgJson.engines.node.match(/(\d+)/);
1467
+ if (match) {
1468
+ nodeVersion = match[1];
1469
+ }
1470
+ }
1471
+ let pnpmManageVersions;
1472
+ try {
1473
+ const workspaceFile = join(monorepoRoot, "pnpm-workspace.yaml");
1474
+ const workspaceContent = await readFile(workspaceFile, "utf-8");
1475
+ pnpmManageVersions = workspaceContent.includes(
1476
+ "manage-package-manager-versions: true"
1477
+ );
1478
+ } catch {
1479
+ }
1480
+ return {
1481
+ linter: tooling.linter,
1482
+ formatter: tooling.formatter,
1483
+ packageManager,
1484
+ nodeVersion,
1485
+ pnpmManageVersions
1486
+ };
765
1487
  } catch {
766
1488
  return {};
767
1489
  }
@@ -987,7 +1709,7 @@ Or in \`package.json\`:
987
1709
  content: existingContent
988
1710
  };
989
1711
  }
990
- async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedTooling, scope) {
1712
+ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedSettings, scope) {
991
1713
  const workspaceDirectories = await parseWorkspaceDirectories(monorepoRoot);
992
1714
  const defaultDirectories = ["apps", "packages"];
993
1715
  const hasCustomDirectories = workspaceDirectories.length > 0 && !workspaceDirectories.every((dir) => defaultDirectories.includes(dir));
@@ -1023,7 +1745,7 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedT
1023
1745
  const packageOptions = await promptForPackageOptions(
1024
1746
  scopedName,
1025
1747
  packageType,
1026
- inheritedTooling
1748
+ inheritedSettings
1027
1749
  );
1028
1750
  let targetDir = defaultDir;
1029
1751
  if (hasCustomDirectories && workspaceDirectories.length > 0) {
@@ -1101,7 +1823,7 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedT
1101
1823
  })
1102
1824
  );
1103
1825
  }
1104
- const formatter = packageOptions.formatter ?? "oxfmt";
1826
+ const formatter = packageOptions.formatter ?? "prettier";
1105
1827
  if (formatter === "prettier") {
1106
1828
  versionPromises.push(
1107
1829
  getLatestNpmVersion("prettier", "3.4.2").then((v) => {
@@ -1256,11 +1978,11 @@ async function handleFixCommand(options) {
1256
1978
  console.log(color.dim(` \u2022 ${error}`));
1257
1979
  }
1258
1980
  console.log();
1259
- const tooling = await detectWorkspaceTooling(monorepoRoot);
1981
+ const tooling = await detectWorkspaceSettings(monorepoRoot);
1260
1982
  const existingConfigs = await detectExistingConfigs(monorepoRoot);
1261
1983
  const detectedLinter = tooling.linter ?? existingConfigs.linter ?? "oxlint";
1262
- const detectedFormatter = tooling.formatter ?? existingConfigs.formatter ?? "oxfmt";
1263
- const isNonInteractive = options.linter && options.formatter;
1984
+ const detectedFormatter = tooling.formatter ?? existingConfigs.formatter ?? "prettier";
1985
+ const isNonInteractive = Boolean(options.linter && options.formatter);
1264
1986
  let linter;
1265
1987
  let formatter;
1266
1988
  if (isNonInteractive) {
@@ -1438,85 +2160,346 @@ async function handleFixCommand(options) {
1438
2160
  console.log(color.dim(" Generated .vscode/extensions.json"));
1439
2161
  }
1440
2162
  }
1441
- const aiFilePaths = {
1442
- "cursor-rules": ".cursor/rules",
1443
- "agents-md": "AGENTS.md",
1444
- "claude-md": "CLAUDE.md",
1445
- "copilot-md": ".github/copilot-instructions.md"
1446
- };
1447
- const existingAiFiles = [];
1448
- for (const [choice, path] of Object.entries(aiFilePaths)) {
1449
- if (await fileExists(join(monorepoRoot, path))) {
1450
- existingAiFiles.push(choice);
2163
+ const aiRulesExist = await fileExists(
2164
+ join(monorepoRoot, ".ai/workspace.md")
2165
+ );
2166
+ if (!aiRulesExist) {
2167
+ const platforms = await promptForAiPlatforms(isNonInteractive);
2168
+ if (platforms.length > 0) {
2169
+ const scope = await getMonorepoScope(monorepoRoot);
2170
+ const aiFilesOutput = {};
2171
+ generateAiFiles(aiFilesOutput, {
2172
+ name: scope,
2173
+ packageManager: "pnpm",
2174
+ linter,
2175
+ formatter,
2176
+ isMonorepo: true,
2177
+ platforms
2178
+ });
2179
+ for (const [filePath, file] of Object.entries(aiFilesOutput)) {
2180
+ const fullPath = join(monorepoRoot, filePath);
2181
+ await mkdir(dirname(fullPath), { recursive: true });
2182
+ await writeFile(fullPath, file.content);
2183
+ console.log(color.dim(` Generated ${filePath}`));
2184
+ }
1451
2185
  }
1452
2186
  }
1453
- let selectedAiFiles = [];
1454
- const savedAiFiles = getAiFiles();
1455
- const availableChoices = ["cursor-rules", "agents-md", "claude-md", "copilot-md"].filter((c) => !existingAiFiles.includes(c));
1456
- if (availableChoices.length === 0) {
1457
- } else if (isNonInteractive) {
1458
- const preferred = savedAiFiles ?? ["cursor-rules"];
1459
- selectedAiFiles = preferred.filter((f) => availableChoices.includes(f));
1460
- } else if (savedAiFiles && savedAiFiles.length > 0) {
1461
- const availableSaved = savedAiFiles.filter(
1462
- (f) => availableChoices.includes(f)
2187
+ process.exit(0);
2188
+ } catch (error) {
2189
+ spinner.stop(color.red("\u2717") + " Failed to fix workspace");
2190
+ console.error(error);
2191
+ process.exit(1);
2192
+ }
2193
+ }
2194
+ async function handleMigration(config, target, root, options) {
2195
+ const plan = await getMigrationPlan(config, target, root);
2196
+ console.log(color.cyan("Migration:"));
2197
+ if (plan.fromLinter !== plan.toLinter) {
2198
+ console.log(
2199
+ ` Linter: ${color.dim(plan.fromLinter)} \u2192 ${color.green(plan.toLinter)}`
2200
+ );
2201
+ }
2202
+ if (plan.fromFormatter !== plan.toFormatter) {
2203
+ console.log(
2204
+ ` Formatter: ${color.dim(plan.fromFormatter)} \u2192 ${color.green(
2205
+ plan.toFormatter
2206
+ )}`
2207
+ );
2208
+ }
2209
+ console.log();
2210
+ console.log(color.cyan("Changes:"));
2211
+ for (const change of plan.changes) {
2212
+ console.log(formatMigrationChange(change));
2213
+ }
2214
+ if (plan.subPackageUpdates.length > 0) {
2215
+ console.log();
2216
+ console.log(color.cyan(`Sub-packages (${plan.subPackageUpdates.length}):`));
2217
+ for (const update of plan.subPackageUpdates) {
2218
+ const changes = [
2219
+ ...update.remove.map((d) => `-${d}`),
2220
+ ...update.add.map((d) => `+${d}`)
2221
+ ].join(", ");
2222
+ console.log(` ~ ${update.path} (${changes})`);
2223
+ }
2224
+ }
2225
+ console.log();
2226
+ if (!options.yes) {
2227
+ const confirm = await p.confirm({
2228
+ message: "Apply migration?",
2229
+ initialValue: true
2230
+ });
2231
+ if (p.isCancel(confirm) || !confirm) {
2232
+ console.log(color.dim(" Migration cancelled"));
2233
+ process.exit(0);
2234
+ }
2235
+ }
2236
+ await applyMigration(plan, root);
2237
+ const aiWorkspacePath = join(root, ".ai/workspace.md");
2238
+ const aiRulesExist = await fileExists(aiWorkspacePath);
2239
+ if (aiRulesExist) {
2240
+ console.log();
2241
+ console.log(color.cyan("Updating AI rules..."));
2242
+ const scope = await getMonorepoScope(root);
2243
+ const existingPlatforms = [];
2244
+ if (await fileExists(join(root, "AGENTS.md"))) {
2245
+ existingPlatforms.push("agents");
2246
+ }
2247
+ if (await fileExists(join(root, "CLAUDE.md"))) {
2248
+ existingPlatforms.push("claude");
2249
+ }
2250
+ const aiFilesOutput = {};
2251
+ generateAiFiles(aiFilesOutput, {
2252
+ name: scope,
2253
+ packageManager: "pnpm",
2254
+ linter: plan.toLinter,
2255
+ formatter: plan.toFormatter,
2256
+ isMonorepo: true,
2257
+ platforms: existingPlatforms.length > 0 ? existingPlatforms : ["agents"]
2258
+ });
2259
+ for (const [filePath, file] of Object.entries(aiFilesOutput)) {
2260
+ const fullPath = join(root, filePath);
2261
+ await mkdir(dirname(fullPath), { recursive: true });
2262
+ await writeFile(fullPath, file.content);
2263
+ console.log(color.dim(` ${filePath}`));
2264
+ }
2265
+ }
2266
+ console.log();
2267
+ console.log(
2268
+ color.green("\u2713") + ` Migrated to ${plan.toLinter}/${plan.toFormatter}`
2269
+ );
2270
+ console.log(color.dim(" Run `pnpm install` to update dependencies"));
2271
+ process.exit(0);
2272
+ }
2273
+ async function handleUpdateCommand(options) {
2274
+ const monorepoRoot = await detectMonorepoRoot();
2275
+ if (!monorepoRoot) {
2276
+ console.log(color.red("\u2717") + " Not a monorepo workspace");
2277
+ console.log(color.dim(" --update only supports pnpm monorepos"));
2278
+ process.exit(1);
2279
+ }
2280
+ const { valid, errors } = await validateWorkspace(monorepoRoot);
2281
+ if (!valid) {
2282
+ console.log(color.yellow("!") + " Workspace has issues:");
2283
+ for (const error of errors) {
2284
+ console.log(color.dim(` \u2022 ${error}`));
2285
+ }
2286
+ console.log();
2287
+ const shouldFix = options.yes || await p.confirm({
2288
+ message: "Run fix first to resolve these issues?",
2289
+ initialValue: true
2290
+ });
2291
+ if (p.isCancel(shouldFix) || !shouldFix) {
2292
+ console.log(
2293
+ color.dim(" Run `pnpm create krispya --fix` to fix manually")
1463
2294
  );
1464
- if (availableSaved.length > 0) {
1465
- const savedLabels = availableSaved.map((f) => aiFilePaths[f]).join(", ");
1466
- const useDefault = await p.confirm({
1467
- message: `Generate AI instruction files? ${color.dim(
1468
- `(${savedLabels})`
1469
- )}`,
2295
+ process.exit(1);
2296
+ }
2297
+ const preFixConfig = await detectCurrentConfig(monorepoRoot);
2298
+ const fixOptions = {
2299
+ ...options,
2300
+ linter: options.linter ?? preFixConfig.linter,
2301
+ formatter: options.formatter ?? preFixConfig.formatter
2302
+ };
2303
+ await handleFixCommand(fixOptions);
2304
+ }
2305
+ const config = await detectCurrentConfig(monorepoRoot);
2306
+ const targetLinter = options.linter;
2307
+ const targetFormatter = options.formatter;
2308
+ const migrationTarget = { linter: targetLinter, formatter: targetFormatter };
2309
+ if (needsMigration(config, migrationTarget)) {
2310
+ await handleMigration(config, migrationTarget, monorepoRoot, options);
2311
+ return;
2312
+ }
2313
+ console.log(
2314
+ color.cyan("Checking for updates...") + color.dim(` (${config.linter}/${config.formatter})`)
2315
+ );
2316
+ console.log();
2317
+ const expected = generateExpectedFiles(config);
2318
+ const categories = await compareWithDisk(expected, monorepoRoot);
2319
+ const workspaceConfigChanges = await getWorkspaceConfigUpdates(monorepoRoot);
2320
+ const workspaceCategory = {
2321
+ category: "workspace-config",
2322
+ label: "Workspace Config",
2323
+ changes: workspaceConfigChanges,
2324
+ hasUserModifications: workspaceConfigChanges.some(
2325
+ (c) => c.status === "modified"
2326
+ )
2327
+ };
2328
+ const allCategories = categories.filter(
2329
+ (c) => c.category !== "workspace-config"
2330
+ );
2331
+ if (workspaceConfigChanges.length > 0) {
2332
+ const configPkgIndex = allCategories.findIndex(
2333
+ (c) => c.category === "config-packages"
2334
+ );
2335
+ if (configPkgIndex !== -1) {
2336
+ allCategories.splice(configPkgIndex + 1, 0, workspaceCategory);
2337
+ } else {
2338
+ allCategories.push(workspaceCategory);
2339
+ }
2340
+ }
2341
+ let updatedCount = 0;
2342
+ let skippedCount = 0;
2343
+ for (const category of allCategories) {
2344
+ const newChanges = category.changes.filter((c) => c.status === "added");
2345
+ const modifiedChanges = category.changes.filter(
2346
+ (c) => c.status === "modified"
2347
+ );
2348
+ const hasNew = newChanges.length > 0;
2349
+ const hasModified = modifiedChanges.length > 0;
2350
+ const hasChanges = hasNew || hasModified;
2351
+ if (!hasChanges) {
2352
+ console.log(color.green("\u2713") + ` ${category.label}: Up to date`);
2353
+ continue;
2354
+ }
2355
+ if (category.category === "ai-files") {
2356
+ if (hasNew) {
2357
+ console.log(color.cyan(category.label + ":"));
2358
+ console.log(
2359
+ color.dim(` ${newChanges.length} AI file(s) can be added`)
2360
+ );
2361
+ console.log();
2362
+ const applyAi = options.yes ? true : await p.confirm({
2363
+ message: "Add AI rules?",
1470
2364
  initialValue: true
1471
2365
  });
1472
- if (!p.isCancel(useDefault) && useDefault) {
1473
- selectedAiFiles = availableSaved;
2366
+ if (!p.isCancel(applyAi) && applyAi) {
2367
+ await applyUpdates(newChanges, monorepoRoot);
2368
+ console.log(
2369
+ color.green("\u2713") + ` Added ${newChanges.length} AI file(s)`
2370
+ );
2371
+ updatedCount++;
2372
+ } else {
2373
+ console.log(color.dim(` Skipped ${category.label}`));
2374
+ skippedCount++;
1474
2375
  }
1475
2376
  }
1476
- } else {
1477
- const aiFilesChoice = await p.multiselect({
1478
- message: "Generate AI instruction files?",
1479
- options: availableChoices.map((c) => ({
1480
- value: c,
1481
- label: aiFilePaths[c],
1482
- hint: c === "cursor-rules" ? "Cursor AI" : c === "agents-md" ? "GitHub Copilot, general" : c === "claude-md" ? "Claude" : "GitHub Copilot"
2377
+ if (hasModified) {
2378
+ console.log(color.cyan("AI Files (existing):"));
2379
+ for (const change of modifiedChanges) {
2380
+ console.log(formatFileChange(change));
2381
+ }
2382
+ console.log();
2383
+ if (options.yes) {
2384
+ console.log(color.dim(" (--yes mode: keeping existing AI files)"));
2385
+ } else {
2386
+ const updateExisting = await p.confirm({
2387
+ message: "Update existing AI files to latest template?",
2388
+ initialValue: false
2389
+ });
2390
+ if (!p.isCancel(updateExisting) && updateExisting) {
2391
+ await applyUpdates(modifiedChanges, monorepoRoot);
2392
+ console.log(color.green("\u2713") + " Updated existing AI files");
2393
+ }
2394
+ }
2395
+ }
2396
+ console.log();
2397
+ continue;
2398
+ }
2399
+ let changesToApply = [];
2400
+ if (options.yes) {
2401
+ console.log(color.cyan(category.label + ":"));
2402
+ for (const change of [...newChanges, ...modifiedChanges]) {
2403
+ console.log(formatFileChange(change));
2404
+ }
2405
+ console.log();
2406
+ if (category.category === "workspace-config") {
2407
+ changesToApply = [...newChanges, ...modifiedChanges];
2408
+ if (changesToApply.length > 0) {
2409
+ console.log(color.dim(" (--yes mode: applying merge updates)"));
2410
+ }
2411
+ } else {
2412
+ changesToApply = newChanges;
2413
+ if (newChanges.length > 0) {
2414
+ console.log(color.dim(" (--yes mode: adding new files only)"));
2415
+ }
2416
+ }
2417
+ } else if (hasNew && hasModified) {
2418
+ const allChanges = [...newChanges, ...modifiedChanges];
2419
+ const selectedFiles = await p.multiselect({
2420
+ message: `${category.label} (+ new, ~ changed)`,
2421
+ options: allChanges.map((change) => ({
2422
+ value: change.path,
2423
+ label: change.status === "added" ? `+ ${change.path}` : `~ ${change.path}`
1483
2424
  })),
2425
+ initialValues: newChanges.map((c) => c.path),
2426
+ // Pre-select new files
1484
2427
  required: false
1485
2428
  });
1486
- if (!p.isCancel(aiFilesChoice) && aiFilesChoice.length > 0) {
1487
- selectedAiFiles = aiFilesChoice;
1488
- const saveChoice = await p.confirm({
1489
- message: "Save as default for future?",
1490
- initialValue: true
1491
- });
1492
- if (!p.isCancel(saveChoice) && saveChoice) {
1493
- setAiFiles(selectedAiFiles);
1494
- }
2429
+ if (p.isCancel(selectedFiles)) {
2430
+ p.cancel("Operation cancelled.");
2431
+ process.exit(0);
1495
2432
  }
1496
- }
1497
- if (selectedAiFiles.length > 0) {
1498
- const scope = await getMonorepoScope(monorepoRoot);
1499
- const aiFilesOutput = {};
1500
- generateAiFiles(aiFilesOutput, {
1501
- name: scope,
1502
- packageManager: "pnpm",
1503
- linter,
1504
- formatter,
1505
- aiFiles: selectedAiFiles
2433
+ if (selectedFiles.length > 0) {
2434
+ changesToApply = allChanges.filter(
2435
+ (c) => selectedFiles.includes(c.path)
2436
+ );
2437
+ }
2438
+ } else if (hasNew) {
2439
+ console.log(color.cyan(category.label + ":"));
2440
+ for (const change of newChanges) {
2441
+ console.log(formatFileChange(change));
2442
+ }
2443
+ console.log();
2444
+ const shouldAdd = await p.confirm({
2445
+ message: `Add ${newChanges.length} new file(s)?`,
2446
+ initialValue: true
1506
2447
  });
1507
- for (const [filePath, file] of Object.entries(aiFilesOutput)) {
1508
- const fullPath = join(monorepoRoot, filePath);
1509
- await mkdir(dirname(fullPath), { recursive: true });
1510
- await writeFile(fullPath, file.content);
1511
- console.log(color.dim(` Generated ${filePath}`));
2448
+ if (p.isCancel(shouldAdd)) {
2449
+ p.cancel("Operation cancelled.");
2450
+ process.exit(0);
2451
+ }
2452
+ if (shouldAdd) {
2453
+ changesToApply = newChanges;
2454
+ }
2455
+ } else if (hasModified) {
2456
+ console.log(color.cyan(category.label + ":"));
2457
+ for (const change of modifiedChanges) {
2458
+ console.log(formatFileChange(change));
2459
+ }
2460
+ console.log();
2461
+ const shouldUpdate = await p.confirm({
2462
+ message: `Update ${modifiedChanges.length} file(s)? (will overwrite)`,
2463
+ initialValue: false
2464
+ });
2465
+ if (p.isCancel(shouldUpdate)) {
2466
+ p.cancel("Operation cancelled.");
2467
+ process.exit(0);
2468
+ }
2469
+ if (shouldUpdate) {
2470
+ changesToApply = modifiedChanges;
1512
2471
  }
1513
2472
  }
1514
- process.exit(0);
1515
- } catch (error) {
1516
- spinner.stop(color.red("\u2717") + " Failed to fix workspace");
1517
- console.error(error);
1518
- process.exit(1);
2473
+ if (changesToApply.length > 0) {
2474
+ await applyUpdates(changesToApply, monorepoRoot);
2475
+ const addedCount = changesToApply.filter(
2476
+ (c) => c.status === "added"
2477
+ ).length;
2478
+ const updatedFilesCount = changesToApply.filter(
2479
+ (c) => c.status === "modified"
2480
+ ).length;
2481
+ const parts = [];
2482
+ if (addedCount > 0) parts.push(`added ${addedCount}`);
2483
+ if (updatedFilesCount > 0) parts.push(`updated ${updatedFilesCount}`);
2484
+ console.log(color.green("\u2713") + ` ${category.label}: ${parts.join(", ")}`);
2485
+ updatedCount++;
2486
+ } else {
2487
+ console.log(color.dim(` Skipped ${category.label}`));
2488
+ skippedCount++;
2489
+ }
2490
+ console.log();
1519
2491
  }
2492
+ if (updatedCount === 0 && skippedCount === 0) {
2493
+ console.log(color.green("\u2713") + " Everything is up to date!");
2494
+ } else if (updatedCount > 0) {
2495
+ console.log(
2496
+ color.green("\u2713") + ` Updated ${updatedCount} ${updatedCount === 1 ? "category" : "categories"}`
2497
+ );
2498
+ if (skippedCount > 0) {
2499
+ console.log(color.dim(` Skipped ${skippedCount}`));
2500
+ }
2501
+ }
2502
+ process.exit(0);
1520
2503
  }
1521
2504
  async function handleWorkspaceCommand(name, options) {
1522
2505
  const monorepoRoot = await detectMonorepoRoot();
@@ -1538,7 +2521,7 @@ async function handleWorkspaceCommand(name, options) {
1538
2521
  process.exit(1);
1539
2522
  }
1540
2523
  const scope = await getMonorepoScope(monorepoRoot);
1541
- const inheritedTooling = await detectWorkspaceTooling(monorepoRoot);
2524
+ const inheritedSettings = await detectWorkspaceSettings(monorepoRoot);
1542
2525
  const projectType = options.type ?? "app";
1543
2526
  const defaultDir = projectType === "library" ? "packages" : "apps";
1544
2527
  const targetDir = options.dir ?? defaultDir;
@@ -1564,8 +2547,11 @@ async function handleWorkspaceCommand(name, options) {
1564
2547
  })
1565
2548
  );
1566
2549
  }
1567
- const linter = inheritedTooling.linter ?? options.linter ?? "oxlint";
1568
- const formatter = inheritedTooling.formatter ?? options.formatter ?? "oxfmt";
2550
+ const linter = inheritedSettings.linter ?? options.linter ?? "oxlint";
2551
+ const formatter = inheritedSettings.formatter ?? options.formatter ?? "prettier";
2552
+ const packageManager = inheritedSettings.packageManager ?? "pnpm";
2553
+ const nodeVersion = inheritedSettings.nodeVersion ?? "latest";
2554
+ const pnpmManageVersions = inheritedSettings.pnpmManageVersions ?? true;
1569
2555
  await Promise.all(versionPromises);
1570
2556
  const relativePkgPath = join(targetDir, name);
1571
2557
  const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
@@ -1576,6 +2562,9 @@ async function handleWorkspaceCommand(name, options) {
1576
2562
  template,
1577
2563
  linter,
1578
2564
  formatter,
2565
+ packageManager,
2566
+ nodeVersion,
2567
+ pnpmManageVersions,
1579
2568
  workspaceRoot,
1580
2569
  versions,
1581
2570
  ...baseTemplate === "r3f" && {
@@ -1609,8 +2598,8 @@ async function handleWorkspaceCommand(name, options) {
1609
2598
  process.exit(1);
1610
2599
  }
1611
2600
  }
1612
- async function handleMonorepoCreation(generateOptions) {
1613
- const { generateMonorepo } = await import('./chunks/index.mjs').then(function (n) { return n.s; });
2601
+ async function handleMonorepoCreation(generateOptions, isNonInteractive) {
2602
+ const { generateMonorepo } = await import('./chunks/index.mjs').then(function (n) { return n.w; });
1614
2603
  const packageManager = generateOptions.packageManager || "pnpm";
1615
2604
  if (packageManager === "pnpm") {
1616
2605
  generateOptions.pnpmVersion = await getLatestPnpmVersion();
@@ -1623,55 +2612,7 @@ async function handleMonorepoCreation(generateOptions) {
1623
2612
  if (nodeVersion === "latest") {
1624
2613
  generateOptions.nodeVersion = await getLatestNodeVersion();
1625
2614
  }
1626
- const savedAiFiles = getAiFiles();
1627
- let selectedAiFiles = [];
1628
- if (savedAiFiles && savedAiFiles.length > 0) {
1629
- const aiFileLabels = {
1630
- "cursor-rules": ".cursor/rules",
1631
- "agents-md": "AGENTS.md",
1632
- "claude-md": "CLAUDE.md",
1633
- "copilot-md": ".github/copilot-instructions.md"
1634
- };
1635
- const savedLabels = savedAiFiles.map((f) => aiFileLabels[f]).join(", ");
1636
- const useDefault = await p.confirm({
1637
- message: `Generate AI instruction files? ${color.dim(
1638
- `(${savedLabels})`
1639
- )}`,
1640
- initialValue: true
1641
- });
1642
- if (!p.isCancel(useDefault) && useDefault) {
1643
- selectedAiFiles = savedAiFiles;
1644
- }
1645
- } else {
1646
- const aiFilesChoice = await p.multiselect({
1647
- message: "Generate AI instruction files?",
1648
- options: [
1649
- { value: "cursor-rules", label: ".cursor/rules", hint: "Cursor AI" },
1650
- {
1651
- value: "agents-md",
1652
- label: "AGENTS.md",
1653
- hint: "GitHub Copilot, general"
1654
- },
1655
- { value: "claude-md", label: "CLAUDE.md", hint: "Claude" },
1656
- {
1657
- value: "copilot-md",
1658
- label: ".github/copilot-instructions.md",
1659
- hint: "GitHub Copilot"
1660
- }
1661
- ],
1662
- required: false
1663
- });
1664
- if (!p.isCancel(aiFilesChoice) && aiFilesChoice.length > 0) {
1665
- selectedAiFiles = aiFilesChoice;
1666
- const saveChoice = await p.confirm({
1667
- message: "Save as default for future monorepos?",
1668
- initialValue: true
1669
- });
1670
- if (!p.isCancel(saveChoice) && saveChoice) {
1671
- setAiFiles(selectedAiFiles);
1672
- }
1673
- }
1674
- }
2615
+ const aiPlatforms = await promptForAiPlatforms(isNonInteractive);
1675
2616
  const projectPath = join(cwd(), generateOptions.name);
1676
2617
  const spinner = p.spinner();
1677
2618
  spinner.start("Creating monorepo workspace...");
@@ -1679,12 +2620,12 @@ async function handleMonorepoCreation(generateOptions) {
1679
2620
  const { files } = generateMonorepo({
1680
2621
  name: generateOptions.name,
1681
2622
  linter: generateOptions.linter ?? "oxlint",
1682
- formatter: generateOptions.formatter ?? "oxfmt",
2623
+ formatter: generateOptions.formatter ?? "prettier",
1683
2624
  packageManager,
1684
2625
  pnpmVersion: generateOptions.pnpmVersion,
1685
2626
  pnpmManageVersions: generateOptions.pnpmManageVersions,
1686
2627
  nodeVersion: generateOptions.nodeVersion,
1687
- aiFiles: selectedAiFiles.length > 0 ? selectedAiFiles : void 0
2628
+ aiPlatforms: aiPlatforms.length > 0 ? aiPlatforms : void 0
1688
2629
  });
1689
2630
  const filePaths = Object.keys(files).sort();
1690
2631
  for (const filePath of filePaths) {
@@ -1696,9 +2637,15 @@ async function handleMonorepoCreation(generateOptions) {
1696
2637
  }
1697
2638
  }
1698
2639
  spinner.stop(color.green.inverse(" \u2713 Monorepo workspace created! "));
1699
- const newMonorepoTooling = {
2640
+ if (isNonInteractive) {
2641
+ process.exit(0);
2642
+ }
2643
+ const newWorkspaceSettings = {
1700
2644
  linter: generateOptions.linter,
1701
- formatter: generateOptions.formatter
2645
+ formatter: generateOptions.formatter,
2646
+ packageManager,
2647
+ nodeVersion: generateOptions.nodeVersion,
2648
+ pnpmManageVersions: generateOptions.pnpmManageVersions
1702
2649
  };
1703
2650
  const scope = generateOptions.name;
1704
2651
  let addMore = true;
@@ -1706,7 +2653,7 @@ async function handleMonorepoCreation(generateOptions) {
1706
2653
  addMore = await createPackageInWorkspace(
1707
2654
  projectPath,
1708
2655
  packageManager,
1709
- newMonorepoTooling,
2656
+ newWorkspaceSettings,
1710
2657
  scope
1711
2658
  );
1712
2659
  }
@@ -1725,10 +2672,14 @@ async function handleMonorepoCreation(generateOptions) {
1725
2672
  process.exit(1);
1726
2673
  }
1727
2674
  }
1728
- async function handleStandaloneProjectCreation(generateOptions) {
2675
+ async function handleStandaloneProjectCreation(generateOptions, isNonInteractive) {
1729
2676
  const base = generateOptions.template ? getBaseTemplate(generateOptions.template) : "vanilla";
1730
2677
  const defaultFallbackName = base === "vanilla" ? "vanilla-app" : base === "react" ? "react-app" : "react-three-app";
1731
2678
  generateOptions.name ??= defaultFallbackName;
2679
+ const aiPlatforms = await promptForAiPlatforms(isNonInteractive);
2680
+ if (aiPlatforms.length > 0) {
2681
+ generateOptions.aiPlatforms = aiPlatforms;
2682
+ }
1732
2683
  const packageManager = generateOptions.packageManager || "pnpm";
1733
2684
  if (packageManager === "pnpm") {
1734
2685
  generateOptions.pnpmVersion = await getLatestPnpmVersion();
@@ -1779,7 +2730,7 @@ async function handleStandaloneProjectCreation(generateOptions) {
1779
2730
  })
1780
2731
  );
1781
2732
  }
1782
- const formatter = generateOptions.formatter ?? "oxfmt";
2733
+ const formatter = generateOptions.formatter ?? "prettier";
1783
2734
  if (formatter === "prettier") {
1784
2735
  versionPromises.push(
1785
2736
  getLatestNpmVersion("prettier", "3.4.2").then((v) => {
@@ -1808,6 +2759,9 @@ async function handleStandaloneProjectCreation(generateOptions) {
1808
2759
  const files = generate(generateOptions);
1809
2760
  await writeGeneratedFiles(projectPath, files);
1810
2761
  spinner.stop(color.green.inverse(" \u2713 Project created! "));
2762
+ if (isNonInteractive) {
2763
+ process.exit(0);
2764
+ }
1811
2765
  const nextSteps = isLibrary ? [
1812
2766
  `cd ${generateOptions.name}`,
1813
2767
  `${packageManager} install`,
@@ -1840,21 +2794,23 @@ async function handleInteractiveMonorepoMode(monorepoRoot) {
1840
2794
  process.exit(0);
1841
2795
  }
1842
2796
  if (choice === "add") {
1843
- const inheritedTooling = await detectWorkspaceTooling(monorepoRoot);
1844
- if (inheritedTooling.linter || inheritedTooling.formatter) {
1845
- const toolingInfo = [
1846
- inheritedTooling.linter && `linter: ${inheritedTooling.linter}`,
1847
- inheritedTooling.formatter && `formatter: ${inheritedTooling.formatter}`
2797
+ const inheritedSettings = await detectWorkspaceSettings(monorepoRoot);
2798
+ const hasSettings = Object.values(inheritedSettings).some(Boolean);
2799
+ if (hasSettings) {
2800
+ const settingsInfo = [
2801
+ inheritedSettings.linter && `linter: ${inheritedSettings.linter}`,
2802
+ inheritedSettings.formatter && `formatter: ${inheritedSettings.formatter}`,
2803
+ inheritedSettings.packageManager && `pm: ${inheritedSettings.packageManager}`
1848
2804
  ].filter(Boolean).join(", ");
1849
- p.log.info(`Using workspace tooling (${toolingInfo})`);
2805
+ p.log.info(`Using workspace settings (${settingsInfo})`);
1850
2806
  }
1851
2807
  const scope = await getMonorepoScope(monorepoRoot);
1852
2808
  let addMore = true;
1853
2809
  while (addMore) {
1854
2810
  addMore = await createPackageInWorkspace(
1855
2811
  monorepoRoot,
1856
- "pnpm",
1857
- inheritedTooling,
2812
+ inheritedSettings.packageManager ?? "pnpm",
2813
+ inheritedSettings,
1858
2814
  scope
1859
2815
  );
1860
2816
  }
@@ -1881,7 +2837,7 @@ async function main() {
1881
2837
  "linter: eslint, oxlint, or biome (default: oxlint)"
1882
2838
  ).option(
1883
2839
  "--formatter <type>",
1884
- "formatter: prettier, oxfmt, or biome (default: oxfmt)"
2840
+ "formatter: prettier, oxfmt, or biome (default: prettier)"
1885
2841
  ).option("--drei", "add @react-three/drei (r3f only)").option("--handle", "add @react-three/handle (r3f only)").option("--leva", "add leva (r3f only)").option("--postprocessing", "add @react-three/postprocessing (r3f only)").option("--rapier", "add @react-three/rapier (r3f only)").option("--xr", "add @react-three/xr (r3f only)").option("--uikit", "add @react-three/uikit (r3f only)").option("--offscreen", "add @react-three/offscreen (r3f only)").option("--zustand", "add zustand (r3f only)").option("--koota", "add koota (r3f only)").option("--triplex", "set up triplex development environment (r3f only)").option("--viverse", "set up viverse deployment (r3f only)").option(
1886
2842
  "--package-manager <manager>",
1887
2843
  "specify package manager (e.g. npm, yarn, pnpm)"
@@ -1903,7 +2859,7 @@ async function main() {
1903
2859
  ).option("--clear-config", "Clear saved preferences (e.g. editor choice)").option("--config-path", "Print the path to the config file").option(
1904
2860
  "--check",
1905
2861
  "Check if current directory is in a valid monorepo workspace"
1906
- ).option("--fix", "Fix monorepo by generating missing .config packages").option(
2862
+ ).option("--fix", "Fix monorepo by generating missing .config packages").option("--update", "Update monorepo workspace to latest configuration").option("-y, --yes", "Non-interactive mode - accept all prompts").option(
1907
2863
  "--path <directory>",
1908
2864
  "Run in specified directory instead of current working directory"
1909
2865
  ).action(async (name, options) => {
@@ -1942,6 +2898,12 @@ async function main() {
1942
2898
  case "--fix":
1943
2899
  options.fix = true;
1944
2900
  break;
2901
+ case "--update":
2902
+ options.update = true;
2903
+ break;
2904
+ case "--yes":
2905
+ options.yes = true;
2906
+ break;
1945
2907
  default:
1946
2908
  console.error(color.red(`Unknown option: ${name}`));
1947
2909
  process.exit(1);
@@ -1953,6 +2915,9 @@ async function main() {
1953
2915
  if (options.fix) {
1954
2916
  await handleFixCommand(options);
1955
2917
  }
2918
+ if (options.update) {
2919
+ await handleUpdateCommand(options);
2920
+ }
1956
2921
  if (options.dir && !options.workspace) {
1957
2922
  console.error(color.red("Error:") + " --dir requires --workspace flag");
1958
2923
  console.log(
@@ -1968,11 +2933,11 @@ async function main() {
1968
2933
  console.clear();
1969
2934
  p.intro(color.bgCyan(color.black(` create-krispya v${pkg.version} `)));
1970
2935
  const monorepoRoot = await detectMonorepoRoot();
1971
- if (monorepoRoot && Object.keys(options).length === 0) {
2936
+ if (monorepoRoot && !hasConfigOptions(options)) {
1972
2937
  await handleInteractiveMonorepoMode(monorepoRoot);
1973
2938
  }
1974
2939
  let generateOptions;
1975
- if (Object.keys(options).length > 0) {
2940
+ if (options.yes) {
1976
2941
  const template = options.template ?? "vanilla";
1977
2942
  const baseTemplate = getBaseTemplate(template);
1978
2943
  const defaultName = getDefaultProjectName(template);
@@ -1983,7 +2948,7 @@ async function main() {
1983
2948
  libraryBundler: projectType === "library" ? options.bundler ?? "unbuild" : void 0,
1984
2949
  template,
1985
2950
  linter: options.linter ?? "oxlint",
1986
- formatter: options.formatter ?? "oxfmt",
2951
+ formatter: options.formatter ?? "prettier",
1987
2952
  ...baseTemplate === "r3f" && {
1988
2953
  drei: options.drei ? {} : void 0,
1989
2954
  handle: options.handle ? {} : void 0,
@@ -2003,12 +2968,35 @@ async function main() {
2003
2968
  nodeVersion: options.nodeVersion ?? "latest"
2004
2969
  };
2005
2970
  } else {
2006
- generateOptions = await promptForOptions(name);
2971
+ const presets = hasConfigOptions(options) ? {
2972
+ type: options.type,
2973
+ template: options.template,
2974
+ bundler: options.bundler,
2975
+ linter: options.linter,
2976
+ formatter: options.formatter,
2977
+ packageManager: options.packageManager,
2978
+ nodeVersion: options.nodeVersion,
2979
+ pnpmManageVersions: options.pnpmManageVersions,
2980
+ drei: options.drei,
2981
+ handle: options.handle,
2982
+ leva: options.leva,
2983
+ postprocessing: options.postprocessing,
2984
+ rapier: options.rapier,
2985
+ xr: options.xr,
2986
+ uikit: options.uikit,
2987
+ offscreen: options.offscreen,
2988
+ zustand: options.zustand,
2989
+ koota: options.koota,
2990
+ triplex: options.triplex,
2991
+ viverse: options.viverse
2992
+ } : void 0;
2993
+ generateOptions = await promptForOptions(name, presets);
2007
2994
  }
2995
+ const isNonInteractive = options.yes ?? false;
2008
2996
  if (generateOptions.projectType === "monorepo") {
2009
- await handleMonorepoCreation(generateOptions);
2997
+ await handleMonorepoCreation(generateOptions, isNonInteractive);
2010
2998
  } else {
2011
- await handleStandaloneProjectCreation(generateOptions);
2999
+ await handleStandaloneProjectCreation(generateOptions, isNonInteractive);
2012
3000
  }
2013
3001
  });
2014
3002
  await program.parseAsync();