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.cjs CHANGED
@@ -58,14 +58,15 @@ function openInEditor(editor, path, reuseWindow) {
58
58
  });
59
59
  }
60
60
 
61
- function formatConfigSummary(options) {
61
+ function formatConfigSummary(options, inherited) {
62
62
  const lines = [];
63
63
  const VALUE_COL = 27;
64
- const formatRow = (label, value, indent = "") => {
64
+ const formatRow = (label, value, isInherited = false, indent = "") => {
65
65
  const fullLabel = indent + label;
66
66
  const dotCount = Math.max(1, VALUE_COL - fullLabel.length - 1);
67
67
  const dots = color__default.gray(".".repeat(dotCount));
68
- return `${indent}${label} ${dots} ${value}`;
68
+ const displayValue = isInherited ? `${value} \u{1F512}` : value;
69
+ return `${indent}${label} ${dots} ${displayValue}`;
69
70
  };
70
71
  const formatLanguage = (lang) => {
71
72
  return lang === "typescript" ? "TypeScript" : lang === "javascript" ? "JavaScript" : lang;
@@ -84,20 +85,29 @@ function formatConfigSummary(options) {
84
85
  } else {
85
86
  lines.push(formatRow("Bundler", "vite"));
86
87
  }
87
- lines.push(formatRow("Node version", options.nodeVersion || "latest"));
88
- lines.push(formatRow("Package manager", options.packageManager || "pnpm"));
88
+ const nodeVersionInherited = inherited?.nodeVersion !== void 0;
89
+ lines.push(formatRow("Node version", options.nodeVersion || "latest", nodeVersionInherited));
90
+ const pmInherited = inherited?.packageManager !== void 0;
91
+ lines.push(formatRow("Package manager", options.packageManager || "pnpm", pmInherited));
89
92
  if (options.packageManager === "pnpm") {
90
93
  const versionManaged = options.pnpmManageVersions ? "yes" : "no";
91
- lines.push(formatRow("\u21B3 Version managed", versionManaged, ""));
94
+ const pnpmVersionInherited = inherited?.pnpmManageVersions !== void 0;
95
+ lines.push(formatRow("\u21B3 Version managed", versionManaged, pnpmVersionInherited, ""));
92
96
  }
93
97
  if (options.linter) {
94
- lines.push(formatRow("Linter", options.linter));
98
+ const linterInherited = inherited?.linter !== void 0;
99
+ lines.push(formatRow("Linter", options.linter, linterInherited));
95
100
  }
96
101
  if (options.formatter) {
97
- lines.push(formatRow("Formatter", options.formatter));
102
+ const formatterInherited = inherited?.formatter !== void 0;
103
+ lines.push(formatRow("Formatter", options.formatter, formatterInherited));
98
104
  }
99
105
  const testing = options.testing ?? (projectType === "library" ? "vitest" : "none");
100
106
  lines.push(formatRow("Testing", testing));
107
+ if (!inherited) {
108
+ const configStrategy = options.configStrategy ?? "stealth";
109
+ lines.push(formatRow("Config strategy", configStrategy));
110
+ }
101
111
  if (options.template && index.getBaseTemplate(options.template) === "r3f") {
102
112
  const integrationNames = [
103
113
  options.drei && "drei",
@@ -159,11 +169,14 @@ function getReuseWindow() {
159
169
  function setReuseWindow(reuse) {
160
170
  config.set("reuseWindow", reuse);
161
171
  }
162
- function getAiFiles() {
163
- return config.get("aiFiles");
172
+ function getAiPlatforms() {
173
+ return config.get("aiPlatforms");
174
+ }
175
+ function setAiPlatforms(platforms) {
176
+ config.set("aiPlatforms", platforms);
164
177
  }
165
- function setAiFiles(files) {
166
- config.set("aiFiles", files);
178
+ function getConfigStrategy() {
179
+ return config.get("configStrategy") ?? "stealth";
167
180
  }
168
181
  function clearConfig() {
169
182
  config.clear();
@@ -175,20 +188,21 @@ function getCustomTemplates() {
175
188
  return config.get("customTemplates") ?? {};
176
189
  }
177
190
 
178
- function getDefaultOptions(template, name, projectType = "app", libraryBundler, integrations, inheritedTooling) {
191
+ function getDefaultOptions(template, name, projectType = "app", libraryBundler, integrations, inheritedSettings) {
179
192
  const baseTemplate = index.getBaseTemplate(template);
180
193
  const base = {
181
194
  name,
182
195
  template,
183
196
  projectType,
184
197
  libraryBundler: projectType === "library" ? libraryBundler ?? "unbuild" : void 0,
185
- packageManager: "pnpm",
186
- pnpmManageVersions: true,
187
- nodeVersion: "latest",
188
- linter: inheritedTooling?.linter ?? "oxlint",
189
- formatter: inheritedTooling?.formatter ?? "oxfmt",
198
+ packageManager: inheritedSettings?.packageManager ?? "pnpm",
199
+ pnpmManageVersions: inheritedSettings?.pnpmManageVersions ?? true,
200
+ nodeVersion: inheritedSettings?.nodeVersion ?? "latest",
201
+ linter: inheritedSettings?.linter ?? "oxlint",
202
+ formatter: inheritedSettings?.formatter ?? "prettier",
190
203
  // Libraries get vitest by default, apps don't
191
- testing: projectType === "library" ? "vitest" : "none"
204
+ testing: projectType === "library" ? "vitest" : "none",
205
+ configStrategy: getConfigStrategy()
192
206
  };
193
207
  if (baseTemplate === "r3f" && integrations) {
194
208
  return {
@@ -220,7 +234,22 @@ function getDefaultProjectName(template) {
220
234
  return `react-three-${index.generateRandomName()}`;
221
235
  }
222
236
  }
223
- async function promptForR3fIntegrations() {
237
+ async function promptForR3fIntegrations(presets) {
238
+ const initialValues = [];
239
+ if (presets) {
240
+ if (presets.drei) initialValues.push("drei");
241
+ if (presets.handle) initialValues.push("handle");
242
+ if (presets.leva) initialValues.push("leva");
243
+ if (presets.postprocessing) initialValues.push("postprocessing");
244
+ if (presets.rapier) initialValues.push("rapier");
245
+ if (presets.xr) initialValues.push("xr");
246
+ if (presets.uikit) initialValues.push("uikit");
247
+ if (presets.offscreen) initialValues.push("offscreen");
248
+ if (presets.zustand) initialValues.push("zustand");
249
+ if (presets.koota) initialValues.push("koota");
250
+ if (presets.triplex) initialValues.push("triplex");
251
+ if (presets.viverse) initialValues.push("viverse");
252
+ }
224
253
  const selected = await p__namespace.multiselect({
225
254
  message: "R3F integrations",
226
255
  options: [
@@ -237,7 +266,7 @@ async function promptForR3fIntegrations() {
237
266
  { value: "triplex", label: "Triplex" },
238
267
  { value: "viverse", label: "Viverse" }
239
268
  ],
240
- initialValues: ["drei"],
269
+ initialValues: initialValues.length > 0 ? initialValues : ["drei"],
241
270
  required: false
242
271
  });
243
272
  if (p__namespace.isCancel(selected)) {
@@ -246,7 +275,7 @@ async function promptForR3fIntegrations() {
246
275
  }
247
276
  return selected;
248
277
  }
249
- async function promptForCustomization(template, name, projectType, integrations, inheritedTooling) {
278
+ async function promptForCustomization(template, name, projectType, integrations, inheritedSettings, presets) {
250
279
  let libraryBundler;
251
280
  if (projectType === "library") {
252
281
  const bundler = await p__namespace.select({
@@ -255,7 +284,7 @@ async function promptForCustomization(template, name, projectType, integrations,
255
284
  { value: "unbuild", label: "unbuild", hint: "unjs, simple config" },
256
285
  { value: "tsdown", label: "tsdown", hint: "fast, esbuild-based" }
257
286
  ],
258
- initialValue: "unbuild"
287
+ initialValue: presets?.bundler ?? "unbuild"
259
288
  });
260
289
  if (p__namespace.isCancel(bundler)) {
261
290
  p__namespace.cancel("Operation cancelled.");
@@ -263,64 +292,57 @@ async function promptForCustomization(template, name, projectType, integrations,
263
292
  }
264
293
  libraryBundler = bundler;
265
294
  }
266
- const nodeVersion = await p__namespace.text({
267
- message: "Node.js version",
268
- placeholder: "latest",
269
- defaultValue: "latest",
270
- validate: (value) => {
271
- if (!value.length) return "Required";
272
- if (value !== "latest" && !/^\d+(\.\d+(\.\d+)?)?$/.test(value)) {
273
- return 'Must be "latest" or a valid semver (e.g., "22" or "22.13.0")';
274
- }
275
- }
276
- });
277
- if (p__namespace.isCancel(nodeVersion)) {
278
- p__namespace.cancel("Operation cancelled.");
279
- process.exit(0);
280
- }
281
- const packageManager = await p__namespace.select({
282
- message: "Package manager",
283
- options: [
284
- { value: "pnpm", label: "pnpm" },
285
- { value: "npm", label: "npm" },
286
- { value: "yarn", label: "yarn" },
287
- { value: "custom", label: "Other (custom)" }
288
- ],
289
- initialValue: "pnpm"
290
- });
291
- if (p__namespace.isCancel(packageManager)) {
292
- p__namespace.cancel("Operation cancelled.");
293
- process.exit(0);
294
- }
295
- let finalPackageManager = packageManager;
296
- if (packageManager === "custom") {
297
- const customPm = await p__namespace.text({
298
- message: "Enter package manager command",
295
+ let nodeVersion = inheritedSettings?.nodeVersion ?? presets?.nodeVersion ?? "latest";
296
+ let finalPackageManager = inheritedSettings?.packageManager ?? presets?.packageManager ?? "pnpm";
297
+ let pnpmManageVersions = inheritedSettings?.pnpmManageVersions ?? presets?.pnpmManageVersions ?? true;
298
+ if (!inheritedSettings?.nodeVersion) {
299
+ const nodeVersionInput = await p__namespace.text({
300
+ message: "Node.js version",
301
+ placeholder: presets?.nodeVersion ?? "latest",
302
+ defaultValue: presets?.nodeVersion ?? "latest",
299
303
  validate: (value) => {
300
304
  if (!value.length) return "Required";
305
+ if (value !== "latest" && !/^\d+(\.\d+(\.\d+)?)?$/.test(value)) {
306
+ return 'Must be "latest" or a valid semver (e.g., "22" or "22.13.0")';
307
+ }
301
308
  }
302
309
  });
303
- if (p__namespace.isCancel(customPm)) {
310
+ if (p__namespace.isCancel(nodeVersionInput)) {
304
311
  p__namespace.cancel("Operation cancelled.");
305
312
  process.exit(0);
306
313
  }
307
- finalPackageManager = customPm;
314
+ nodeVersion = nodeVersionInput;
308
315
  }
309
- let pnpmManageVersions = true;
310
- if (packageManager === "pnpm") {
311
- const managePnpm = await p__namespace.confirm({
312
- message: "Enable manage-package-manager-versions?",
313
- initialValue: true
316
+ if (!inheritedSettings?.packageManager) {
317
+ const packageManager = await p__namespace.select({
318
+ message: "Package manager",
319
+ options: [
320
+ { value: "pnpm", label: "pnpm" },
321
+ { value: "npm", label: "npm" },
322
+ { value: "yarn", label: "yarn" }
323
+ ],
324
+ initialValue: presets?.packageManager ?? "pnpm"
314
325
  });
315
- if (p__namespace.isCancel(managePnpm)) {
326
+ if (p__namespace.isCancel(packageManager)) {
316
327
  p__namespace.cancel("Operation cancelled.");
317
328
  process.exit(0);
318
329
  }
319
- pnpmManageVersions = managePnpm;
330
+ finalPackageManager = packageManager;
331
+ if (packageManager === "pnpm") {
332
+ const managePnpm = await p__namespace.confirm({
333
+ message: "Enable manage-package-manager-versions?",
334
+ initialValue: presets?.pnpmManageVersions ?? true
335
+ });
336
+ if (p__namespace.isCancel(managePnpm)) {
337
+ p__namespace.cancel("Operation cancelled.");
338
+ process.exit(0);
339
+ }
340
+ pnpmManageVersions = managePnpm;
341
+ }
320
342
  }
321
- let linter = inheritedTooling?.linter ?? "oxlint";
322
- let formatter = inheritedTooling?.formatter ?? "oxfmt";
323
- if (!inheritedTooling?.linter) {
343
+ let linter = inheritedSettings?.linter ?? presets?.linter ?? "oxlint";
344
+ let formatter = inheritedSettings?.formatter ?? presets?.formatter ?? "prettier";
345
+ if (!inheritedSettings?.linter) {
324
346
  const linterChoice = await p__namespace.select({
325
347
  message: "Linter",
326
348
  options: [
@@ -328,7 +350,7 @@ async function promptForCustomization(template, name, projectType, integrations,
328
350
  { value: "eslint", label: "ESLint", hint: "classic" },
329
351
  { value: "biome", label: "Biome", hint: "all-in-one" }
330
352
  ],
331
- initialValue: "oxlint"
353
+ initialValue: presets?.linter ?? "oxlint"
332
354
  });
333
355
  if (p__namespace.isCancel(linterChoice)) {
334
356
  p__namespace.cancel("Operation cancelled.");
@@ -336,15 +358,15 @@ async function promptForCustomization(template, name, projectType, integrations,
336
358
  }
337
359
  linter = linterChoice;
338
360
  }
339
- if (!inheritedTooling?.formatter) {
361
+ if (!inheritedSettings?.formatter) {
340
362
  const formatterChoice = await p__namespace.select({
341
363
  message: "Formatter",
342
364
  options: [
365
+ { value: "prettier", label: "Prettier", hint: "widely adopted" },
343
366
  { value: "oxfmt", label: "Oxfmt", hint: "fast, Prettier-compatible" },
344
- { value: "prettier", label: "Prettier", hint: "classic" },
345
367
  { value: "biome", label: "Biome", hint: "all-in-one" }
346
368
  ],
347
- initialValue: "oxfmt"
369
+ initialValue: presets?.formatter ?? "prettier"
348
370
  });
349
371
  if (p__namespace.isCancel(formatterChoice)) {
350
372
  p__namespace.cancel("Operation cancelled.");
@@ -376,6 +398,18 @@ async function promptForCustomization(template, name, projectType, integrations,
376
398
  p__namespace.cancel("Operation cancelled.");
377
399
  process.exit(0);
378
400
  }
401
+ const configStrategyChoice = await p__namespace.select({
402
+ message: "Config strategy",
403
+ options: [
404
+ { value: "stealth", label: "stealth", hint: "configs in .config/" },
405
+ { value: "root", label: "root", hint: "configs at project root" }
406
+ ],
407
+ initialValue: getConfigStrategy()
408
+ });
409
+ if (p__namespace.isCancel(configStrategyChoice)) {
410
+ p__namespace.cancel("Operation cancelled.");
411
+ process.exit(0);
412
+ }
379
413
  const baseTemplate = index.getBaseTemplate(template);
380
414
  const finalTemplate = language === "javascript" ? `${baseTemplate}-js` : baseTemplate;
381
415
  const base = {
@@ -388,7 +422,8 @@ async function promptForCustomization(template, name, projectType, integrations,
388
422
  pnpmManageVersions,
389
423
  linter,
390
424
  formatter,
391
- testing
425
+ testing,
426
+ configStrategy: configStrategyChoice
392
427
  };
393
428
  if (baseTemplate === "r3f" && integrations) {
394
429
  return {
@@ -433,14 +468,14 @@ function getDefaultMonorepoOptions(name) {
433
468
  pnpmManageVersions: true,
434
469
  nodeVersion: "latest",
435
470
  linter: "oxlint",
436
- formatter: "oxfmt"
471
+ formatter: "prettier"
437
472
  };
438
473
  }
439
- async function promptForMonorepoCustomization(name) {
474
+ async function promptForMonorepoCustomization(name, presets) {
440
475
  const nodeVersion = await p__namespace.text({
441
476
  message: "Node.js version",
442
- placeholder: "latest",
443
- defaultValue: "latest",
477
+ placeholder: presets?.nodeVersion ?? "latest",
478
+ defaultValue: presets?.nodeVersion ?? "latest",
444
479
  validate: (value) => {
445
480
  if (!value.length) return "Required";
446
481
  if (value !== "latest" && !/^\d+(\.\d+(\.\d+)?)?$/.test(value)) {
@@ -454,7 +489,7 @@ async function promptForMonorepoCustomization(name) {
454
489
  }
455
490
  const managePnpm = await p__namespace.confirm({
456
491
  message: "Enable manage-package-manager-versions?",
457
- initialValue: true
492
+ initialValue: presets?.pnpmManageVersions ?? true
458
493
  });
459
494
  if (p__namespace.isCancel(managePnpm)) {
460
495
  p__namespace.cancel("Operation cancelled.");
@@ -467,7 +502,7 @@ async function promptForMonorepoCustomization(name) {
467
502
  { value: "eslint", label: "ESLint", hint: "classic" },
468
503
  { value: "biome", label: "Biome", hint: "all-in-one" }
469
504
  ],
470
- initialValue: "oxlint"
505
+ initialValue: presets?.linter ?? "oxlint"
471
506
  });
472
507
  if (p__namespace.isCancel(linter)) {
473
508
  p__namespace.cancel("Operation cancelled.");
@@ -476,11 +511,11 @@ async function promptForMonorepoCustomization(name) {
476
511
  const formatter = await p__namespace.select({
477
512
  message: "Formatter",
478
513
  options: [
514
+ { value: "prettier", label: "Prettier", hint: "widely adopted" },
479
515
  { value: "oxfmt", label: "Oxfmt", hint: "fast, Prettier-compatible" },
480
- { value: "prettier", label: "Prettier", hint: "classic" },
481
516
  { value: "biome", label: "Biome", hint: "all-in-one" }
482
517
  ],
483
- initialValue: "oxfmt"
518
+ initialValue: presets?.formatter ?? "prettier"
484
519
  });
485
520
  if (p__namespace.isCancel(formatter)) {
486
521
  p__namespace.cancel("Operation cancelled.");
@@ -496,8 +531,15 @@ async function promptForMonorepoCustomization(name) {
496
531
  formatter
497
532
  };
498
533
  }
499
- async function promptForMonorepo(workspaceName) {
534
+ async function promptForMonorepo(workspaceName, presets) {
500
535
  const defaultOptions = getDefaultMonorepoOptions(workspaceName);
536
+ if (presets) {
537
+ if (presets.linter) defaultOptions.linter = presets.linter;
538
+ if (presets.formatter) defaultOptions.formatter = presets.formatter;
539
+ if (presets.nodeVersion) defaultOptions.nodeVersion = presets.nodeVersion;
540
+ if (presets.pnpmManageVersions !== void 0)
541
+ defaultOptions.pnpmManageVersions = presets.pnpmManageVersions;
542
+ }
501
543
  p__namespace.note(
502
544
  formatMonorepoConfigSummary({
503
545
  name: defaultOptions.name,
@@ -505,24 +547,28 @@ async function promptForMonorepo(workspaceName) {
505
547
  packageManager: defaultOptions.packageManager ?? "pnpm",
506
548
  pnpmManageVersions: defaultOptions.pnpmManageVersions,
507
549
  linter: defaultOptions.linter ?? "oxlint",
508
- formatter: defaultOptions.formatter ?? "oxfmt"
550
+ formatter: defaultOptions.formatter ?? "prettier"
509
551
  }),
510
552
  "Workspace Configuration"
511
553
  );
512
- const proceed = await p__namespace.confirm({
554
+ const proceed = await p__namespace.select({
513
555
  message: "Proceed with these settings?",
514
- initialValue: true
556
+ options: [
557
+ { value: "continue", label: "Yes, continue" },
558
+ { value: "customize", label: "No, customize settings" }
559
+ ],
560
+ initialValue: "continue"
515
561
  });
516
562
  if (p__namespace.isCancel(proceed)) {
517
563
  p__namespace.cancel("Operation cancelled.");
518
564
  process.exit(0);
519
565
  }
520
- if (proceed) {
566
+ if (proceed === "continue") {
521
567
  return defaultOptions;
522
568
  }
523
- return promptForMonorepoCustomization(workspaceName);
569
+ return promptForMonorepoCustomization(workspaceName, presets);
524
570
  }
525
- async function promptForOptions(name) {
571
+ async function promptForOptions(name, presets) {
526
572
  let projectName = name;
527
573
  if (!projectName) {
528
574
  const nameResult = await p__namespace.text({
@@ -546,30 +592,36 @@ async function promptForOptions(name) {
546
592
  { value: "library", label: "Library" },
547
593
  { value: "monorepo", label: "Monorepo" }
548
594
  ],
549
- initialValue: "app"
595
+ initialValue: presets?.type ?? "app"
550
596
  });
551
597
  if (p__namespace.isCancel(projectType)) {
552
598
  p__namespace.cancel("Operation cancelled.");
553
599
  process.exit(0);
554
600
  }
555
601
  if (projectType === "monorepo") {
556
- return promptForMonorepo(projectName);
602
+ return promptForMonorepo(projectName, presets);
557
603
  }
558
- return promptForPackageOptions(projectName, projectType);
604
+ return promptForPackageOptions(
605
+ projectName,
606
+ projectType,
607
+ void 0,
608
+ presets
609
+ );
559
610
  }
560
- function customTemplateToOptions(customTemplate, name, projectType) {
611
+ function customTemplateToOptions(customTemplate, name, projectType, inheritedSettings) {
561
612
  const baseTemplate = customTemplate.baseTemplate;
562
613
  const template = baseTemplate;
563
614
  const base = {
564
615
  name,
565
616
  template,
566
617
  projectType,
567
- packageManager: "pnpm",
568
- pnpmManageVersions: true,
569
- nodeVersion: "latest",
570
- linter: customTemplate.linter,
571
- formatter: customTemplate.formatter,
572
- testing: customTemplate.testing
618
+ packageManager: inheritedSettings?.packageManager ?? "pnpm",
619
+ pnpmManageVersions: inheritedSettings?.pnpmManageVersions ?? true,
620
+ nodeVersion: inheritedSettings?.nodeVersion ?? "latest",
621
+ linter: inheritedSettings?.linter ?? customTemplate.linter,
622
+ formatter: inheritedSettings?.formatter ?? customTemplate.formatter,
623
+ testing: customTemplate.testing,
624
+ configStrategy: customTemplate.configStrategy ?? getConfigStrategy()
573
625
  };
574
626
  if (baseTemplate === "r3f" && customTemplate.integrations) {
575
627
  const integrations = customTemplate.integrations;
@@ -591,7 +643,17 @@ function customTemplateToOptions(customTemplate, name, projectType) {
591
643
  }
592
644
  return base;
593
645
  }
594
- async function promptForPackageOptions(projectName, projectType, inheritedTooling) {
646
+ function presetsToInheritedSettings(presets) {
647
+ if (!presets) return void 0;
648
+ return {
649
+ linter: presets.linter,
650
+ formatter: presets.formatter,
651
+ packageManager: presets.packageManager,
652
+ nodeVersion: presets.nodeVersion,
653
+ pnpmManageVersions: presets.pnpmManageVersions
654
+ };
655
+ }
656
+ async function promptForPackageOptions(projectName, projectType, inheritedSettings, presets) {
595
657
  const builtInOptions = [
596
658
  { value: "vanilla", label: "Vanilla" },
597
659
  { value: "react", label: "React" },
@@ -607,7 +669,7 @@ async function promptForPackageOptions(projectName, projectType, inheritedToolin
607
669
  const templateSelection = await p__namespace.select({
608
670
  message: "Select a template",
609
671
  options: allOptions,
610
- initialValue: "vanilla"
672
+ initialValue: presets?.template ?? "vanilla"
611
673
  });
612
674
  if (p__namespace.isCancel(templateSelection)) {
613
675
  p__namespace.cancel("Operation cancelled.");
@@ -617,24 +679,27 @@ async function promptForPackageOptions(projectName, projectType, inheritedToolin
617
679
  if (selection.startsWith("custom:")) {
618
680
  const customName = selection.slice(7);
619
681
  const customTemplate = customTemplates[customName];
620
- const defaultOptions2 = customTemplateToOptions(customTemplate, projectName, projectType);
621
- if (inheritedTooling?.linter) {
622
- defaultOptions2.linter = inheritedTooling.linter;
623
- }
624
- if (inheritedTooling?.formatter) {
625
- defaultOptions2.formatter = inheritedTooling.formatter;
626
- }
627
- const configTitle2 = inheritedTooling ? `Template: ${customName} (using workspace tooling)` : `Template: ${customName}`;
628
- p__namespace.note(formatConfigSummary(defaultOptions2), configTitle2);
629
- const proceed2 = await p__namespace.confirm({
682
+ const defaultOptions2 = customTemplateToOptions(
683
+ customTemplate,
684
+ projectName,
685
+ projectType,
686
+ inheritedSettings
687
+ );
688
+ const configTitle2 = inheritedSettings ? `Template: ${customName} (using workspace settings)` : `Template: ${customName}`;
689
+ p__namespace.note(formatConfigSummary(defaultOptions2, inheritedSettings), configTitle2);
690
+ const proceed2 = await p__namespace.select({
630
691
  message: "Proceed with these settings?",
631
- initialValue: true
692
+ options: [
693
+ { value: "continue", label: "Yes, continue" },
694
+ { value: "customize", label: "No, customize settings" }
695
+ ],
696
+ initialValue: "continue"
632
697
  });
633
698
  if (p__namespace.isCancel(proceed2)) {
634
699
  p__namespace.cancel("Operation cancelled.");
635
700
  process.exit(0);
636
701
  }
637
- if (proceed2) {
702
+ if (proceed2 === "continue") {
638
703
  return defaultOptions2;
639
704
  }
640
705
  return promptForCustomization(
@@ -642,37 +707,48 @@ async function promptForPackageOptions(projectName, projectType, inheritedToolin
642
707
  projectName,
643
708
  projectType,
644
709
  customTemplate.integrations,
645
- inheritedTooling
710
+ inheritedSettings
646
711
  );
647
712
  }
648
713
  const template = selection;
649
714
  const baseTemplate = index.getBaseTemplate(template);
650
715
  let integrations;
651
716
  if (baseTemplate === "r3f") {
652
- integrations = await promptForR3fIntegrations();
717
+ integrations = await promptForR3fIntegrations(presets);
653
718
  }
654
719
  const defaultOptions = getDefaultOptions(
655
720
  template,
656
721
  projectName,
657
722
  projectType,
658
- void 0,
723
+ presets?.bundler,
659
724
  integrations,
660
- inheritedTooling
725
+ inheritedSettings ?? presetsToInheritedSettings(presets)
661
726
  );
662
- const configTitle = inheritedTooling ? "Template Configuration (using workspace tooling)" : "Template Configuration";
663
- p__namespace.note(formatConfigSummary(defaultOptions), configTitle);
664
- const proceed = await p__namespace.confirm({
727
+ const configTitle = inheritedSettings ? "Template Configuration (using workspace settings)" : "Template Configuration";
728
+ p__namespace.note(formatConfigSummary(defaultOptions, inheritedSettings), configTitle);
729
+ const proceed = await p__namespace.select({
665
730
  message: "Proceed with these settings?",
666
- initialValue: true
731
+ options: [
732
+ { value: "continue", label: "Yes, continue" },
733
+ { value: "customize", label: "No, customize settings" }
734
+ ],
735
+ initialValue: "continue"
667
736
  });
668
737
  if (p__namespace.isCancel(proceed)) {
669
738
  p__namespace.cancel("Operation cancelled.");
670
739
  process.exit(0);
671
740
  }
672
- if (proceed) {
741
+ if (proceed === "continue") {
673
742
  return defaultOptions;
674
743
  }
675
- return promptForCustomization(template, projectName, projectType, integrations, inheritedTooling);
744
+ return promptForCustomization(
745
+ template,
746
+ projectName,
747
+ projectType,
748
+ integrations,
749
+ inheritedSettings,
750
+ presets
751
+ );
676
752
  }
677
753
 
678
754
  async function checkAnyExists(paths) {
@@ -720,8 +796,586 @@ async function validateWorkspace(monorepoRoot) {
720
796
  return { valid: errors.length === 0, errors };
721
797
  }
722
798
 
799
+ async function detectCurrentConfig(root) {
800
+ let name = root.split(/[/\\]/).pop() ?? "workspace";
801
+ try {
802
+ const pkgPath = path.join(root, "package.json");
803
+ const content = await promises.readFile(pkgPath, "utf-8");
804
+ const pkgJson = JSON.parse(content);
805
+ if (pkgJson.name) {
806
+ name = pkgJson.name.replace(/^@/, "").replace(/\/.*$/, "");
807
+ }
808
+ } catch {
809
+ }
810
+ const tooling = await index.detectTooling(root);
811
+ return {
812
+ name,
813
+ linter: tooling.linter ?? "oxlint",
814
+ formatter: tooling.formatter ?? "prettier",
815
+ packageManager: "pnpm"
816
+ };
817
+ }
818
+ function generateExpectedFiles(config) {
819
+ const { name, linter, formatter, packageManager } = config;
820
+ const aiFilesMap = {};
821
+ index.generateAiFiles(aiFilesMap, {
822
+ name,
823
+ packageManager,
824
+ linter,
825
+ formatter,
826
+ isMonorepo: true,
827
+ platforms: index.ALL_AI_PLATFORMS
828
+ });
829
+ const vscodeFiles = {};
830
+ index.generateVscodeFiles(vscodeFiles, linter, formatter);
831
+ const configPackages = {};
832
+ index.generateTypescriptConfigPackage(configPackages);
833
+ if (linter === "oxlint") {
834
+ index.generateOxlintConfigPackage(configPackages);
835
+ } else if (linter === "eslint") {
836
+ index.generateEslintConfigPackage(configPackages);
837
+ }
838
+ if (formatter === "oxfmt") {
839
+ index.generateOxfmtConfigPackage(configPackages);
840
+ } else if (formatter === "prettier") {
841
+ index.generatePrettierConfigPackage(configPackages);
842
+ }
843
+ const workspaceConfig = {};
844
+ const rootConfig = {};
845
+ rootConfig[".gitignore"] = {
846
+ type: "text",
847
+ content: ["node_modules", "dist", "*.tsbuildinfo", ".DS_Store"].join("\n")
848
+ };
849
+ rootConfig[".gitattributes"] = {
850
+ type: "text",
851
+ content: `* text=auto eol=lf
852
+ *.{cmd,[cC][mM][dD]} text eol=crlf
853
+ *.{bat,[bB][aA][tT]} text eol=crlf
854
+ `
855
+ };
856
+ if (linter === "biome" || formatter === "biome") {
857
+ const biomeConfig = {
858
+ $schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
859
+ vcs: {
860
+ enabled: true,
861
+ clientKind: "git",
862
+ useIgnoreFile: true
863
+ },
864
+ linter: {
865
+ enabled: linter === "biome",
866
+ rules: {
867
+ recommended: true
868
+ }
869
+ },
870
+ formatter: {
871
+ enabled: formatter === "biome"
872
+ }
873
+ };
874
+ rootConfig["biome.json"] = {
875
+ type: "text",
876
+ content: JSON.stringify(biomeConfig, null, 2)
877
+ };
878
+ }
879
+ return {
880
+ "ai-files": aiFilesMap,
881
+ vscode: vscodeFiles,
882
+ "config-packages": configPackages,
883
+ "workspace-config": workspaceConfig,
884
+ "root-config": rootConfig
885
+ };
886
+ }
887
+ async function fileExists$1(path) {
888
+ try {
889
+ await promises.access(path, fs.constants.F_OK);
890
+ return true;
891
+ } catch {
892
+ return false;
893
+ }
894
+ }
895
+ async function compareWithDisk(expected, root) {
896
+ const categoryLabels = {
897
+ "ai-files": "AI Files",
898
+ vscode: "VS Code",
899
+ "config-packages": "Config Packages",
900
+ "workspace-config": "Workspace Config",
901
+ "root-config": "Root Config"
902
+ };
903
+ const categories = [];
904
+ for (const [category, files] of Object.entries(expected)) {
905
+ const changes = [];
906
+ for (const [filePath, file] of Object.entries(files)) {
907
+ if (file.type !== "text") continue;
908
+ const fullPath = path.join(root, filePath);
909
+ const newContent = file.content;
910
+ if (await fileExists$1(fullPath)) {
911
+ const currentContent = await promises.readFile(fullPath, "utf-8");
912
+ if (currentContent === newContent) {
913
+ changes.push({
914
+ path: filePath,
915
+ status: "unchanged",
916
+ currentContent,
917
+ newContent
918
+ });
919
+ } else {
920
+ changes.push({
921
+ path: filePath,
922
+ status: "modified",
923
+ currentContent,
924
+ newContent
925
+ });
926
+ }
927
+ } else {
928
+ changes.push({
929
+ path: filePath,
930
+ status: "added",
931
+ newContent
932
+ });
933
+ }
934
+ }
935
+ if (changes.length === 0) continue;
936
+ const hasUserModifications = changes.some((c) => c.status === "modified");
937
+ categories.push({
938
+ category,
939
+ label: categoryLabels[category],
940
+ changes,
941
+ hasUserModifications
942
+ });
943
+ }
944
+ return categories;
945
+ }
946
+ async function getWorkspaceConfigUpdates(root) {
947
+ const workspacePath = path.join(root, "pnpm-workspace.yaml");
948
+ const changes = [];
949
+ let currentContent = "";
950
+ let exists = false;
951
+ try {
952
+ currentContent = await promises.readFile(workspacePath, "utf-8");
953
+ exists = true;
954
+ } catch {
955
+ }
956
+ if (!exists) {
957
+ const newContent = `manage-package-manager-versions: true
958
+
959
+ packages:
960
+ - ".config/*"
961
+ - "apps/*"
962
+ - "packages/*"
963
+
964
+ onlyBuiltDependencies:
965
+ - esbuild
966
+ `;
967
+ changes.push({
968
+ path: "pnpm-workspace.yaml",
969
+ status: "added",
970
+ newContent
971
+ });
972
+ return changes;
973
+ }
974
+ let updatedContent = currentContent;
975
+ let needsUpdate = false;
976
+ if (!currentContent.includes("manage-package-manager-versions")) {
977
+ updatedContent = `manage-package-manager-versions: true
978
+
979
+ ${updatedContent}`;
980
+ needsUpdate = true;
981
+ }
982
+ if (!currentContent.includes("onlyBuiltDependencies")) {
983
+ updatedContent = `${updatedContent.trimEnd()}
984
+
985
+ onlyBuiltDependencies:
986
+ - esbuild
987
+ `;
988
+ needsUpdate = true;
989
+ }
990
+ if (!currentContent.includes(".config/*") && !currentContent.includes('".config/*"')) {
991
+ const lines = updatedContent.split("\n");
992
+ const packagesIndex = lines.findIndex(
993
+ (line) => line.trim().startsWith("packages:")
994
+ );
995
+ if (packagesIndex !== -1) {
996
+ lines.splice(packagesIndex + 1, 0, ' - ".config/*"');
997
+ updatedContent = lines.join("\n");
998
+ needsUpdate = true;
999
+ }
1000
+ }
1001
+ if (needsUpdate) {
1002
+ changes.push({
1003
+ path: "pnpm-workspace.yaml",
1004
+ status: "modified",
1005
+ currentContent,
1006
+ newContent: updatedContent
1007
+ });
1008
+ } else {
1009
+ changes.push({
1010
+ path: "pnpm-workspace.yaml",
1011
+ status: "unchanged",
1012
+ currentContent,
1013
+ newContent: currentContent
1014
+ });
1015
+ }
1016
+ return changes;
1017
+ }
1018
+ async function applyUpdates(changes, root) {
1019
+ for (const change of changes) {
1020
+ if (change.status === "unchanged") continue;
1021
+ const fullPath = path.join(root, change.path);
1022
+ await promises.mkdir(path.dirname(fullPath), { recursive: true });
1023
+ await promises.writeFile(fullPath, change.newContent);
1024
+ }
1025
+ }
1026
+ function formatFileChange(change) {
1027
+ const icon = change.status === "added" ? "+" : change.status === "modified" ? "~" : "=";
1028
+ return ` ${icon} ${change.path}`;
1029
+ }
1030
+ const LINTER_DEPS = {
1031
+ oxlint: "oxlint",
1032
+ eslint: "eslint",
1033
+ biome: "@biomejs/biome"
1034
+ };
1035
+ const FORMATTER_DEPS = {
1036
+ oxfmt: "oxfmt",
1037
+ prettier: "prettier",
1038
+ biome: "@biomejs/biome"
1039
+ };
1040
+ const LINTER_CONFIG_PACKAGES = {
1041
+ oxlint: "@config/oxlint",
1042
+ eslint: "@config/eslint",
1043
+ biome: null
1044
+ // biome uses root biome.json
1045
+ };
1046
+ const FORMATTER_CONFIG_PACKAGES = {
1047
+ oxfmt: "@config/oxfmt",
1048
+ prettier: "@config/prettier",
1049
+ biome: null
1050
+ // biome uses root biome.json
1051
+ };
1052
+ function needsMigration(current, target) {
1053
+ const linterChange = target.linter && target.linter !== current.linter;
1054
+ const formatterChange = target.formatter && target.formatter !== current.formatter;
1055
+ return linterChange || formatterChange || false;
1056
+ }
1057
+ async function getMigrationPlan(current, target, root) {
1058
+ const toLinter = target.linter ?? current.linter;
1059
+ const toFormatter = target.formatter ?? current.formatter;
1060
+ const changes = [];
1061
+ if (toLinter !== current.linter) {
1062
+ if (current.linter !== "biome") {
1063
+ changes.push({
1064
+ type: "remove-dir",
1065
+ path: `.config/${current.linter}`,
1066
+ description: `Remove @config/${current.linter} package`
1067
+ });
1068
+ }
1069
+ if (toLinter !== "biome") {
1070
+ const files = {};
1071
+ if (toLinter === "oxlint") {
1072
+ index.generateOxlintConfigPackage(files);
1073
+ } else if (toLinter === "eslint") {
1074
+ index.generateEslintConfigPackage(files);
1075
+ }
1076
+ for (const [path, file] of Object.entries(files)) {
1077
+ if (file.type === "text") {
1078
+ changes.push({
1079
+ type: "add-file",
1080
+ path,
1081
+ description: `Add ${path}`,
1082
+ content: file.content
1083
+ });
1084
+ }
1085
+ }
1086
+ }
1087
+ if (toLinter === "biome" && toFormatter === "biome") {
1088
+ changes.push({
1089
+ type: "add-file",
1090
+ path: "biome.json",
1091
+ description: "Add biome.json config",
1092
+ content: JSON.stringify(
1093
+ {
1094
+ $schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
1095
+ vcs: { enabled: true, clientKind: "git", useIgnoreFile: true },
1096
+ linter: { enabled: true, rules: { recommended: true } },
1097
+ formatter: { enabled: true }
1098
+ },
1099
+ null,
1100
+ 2
1101
+ )
1102
+ });
1103
+ } else if (toLinter === "biome" && toFormatter !== "biome") {
1104
+ changes.push({
1105
+ type: "add-file",
1106
+ path: "biome.json",
1107
+ description: "Add biome.json config (linter only)",
1108
+ content: JSON.stringify(
1109
+ {
1110
+ $schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
1111
+ vcs: { enabled: true, clientKind: "git", useIgnoreFile: true },
1112
+ linter: { enabled: true, rules: { recommended: true } },
1113
+ formatter: { enabled: false }
1114
+ },
1115
+ null,
1116
+ 2
1117
+ )
1118
+ });
1119
+ }
1120
+ if (current.linter === "biome" && toLinter !== "biome" && current.formatter !== "biome" && toFormatter !== "biome") {
1121
+ changes.push({
1122
+ type: "remove-file",
1123
+ path: "biome.json",
1124
+ description: "Remove biome.json"
1125
+ });
1126
+ }
1127
+ }
1128
+ if (toFormatter !== current.formatter) {
1129
+ const formatterSameAsLinter = current.formatter === current.linter;
1130
+ if (current.formatter !== "biome" && !formatterSameAsLinter) {
1131
+ changes.push({
1132
+ type: "remove-dir",
1133
+ path: `.config/${current.formatter}`,
1134
+ description: `Remove @config/${current.formatter} package`
1135
+ });
1136
+ }
1137
+ const newFormatterSameAsLinter = toFormatter === toLinter;
1138
+ if (toFormatter !== "biome" && !newFormatterSameAsLinter) {
1139
+ const files = {};
1140
+ if (toFormatter === "oxfmt") {
1141
+ index.generateOxfmtConfigPackage(files);
1142
+ } else if (toFormatter === "prettier") {
1143
+ index.generatePrettierConfigPackage(files);
1144
+ }
1145
+ for (const [path, file] of Object.entries(files)) {
1146
+ if (file.type === "text") {
1147
+ changes.push({
1148
+ type: "add-file",
1149
+ path,
1150
+ description: `Add ${path}`,
1151
+ content: file.content
1152
+ });
1153
+ }
1154
+ }
1155
+ }
1156
+ if (toFormatter === "biome" && toLinter !== "biome") {
1157
+ changes.push({
1158
+ type: "add-file",
1159
+ path: "biome.json",
1160
+ description: "Add biome.json config (formatter only)",
1161
+ content: JSON.stringify(
1162
+ {
1163
+ $schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
1164
+ vcs: { enabled: true, clientKind: "git", useIgnoreFile: true },
1165
+ linter: { enabled: false },
1166
+ formatter: { enabled: true }
1167
+ },
1168
+ null,
1169
+ 2
1170
+ )
1171
+ });
1172
+ }
1173
+ if (current.formatter === "biome" && toFormatter !== "biome" && current.linter !== "biome" && toLinter !== "biome") {
1174
+ changes.push({
1175
+ type: "remove-file",
1176
+ path: "biome.json",
1177
+ description: "Remove biome.json"
1178
+ });
1179
+ }
1180
+ }
1181
+ changes.push({
1182
+ type: "update-package-json",
1183
+ path: "package.json",
1184
+ description: "Update root package.json (devDependencies, scripts)"
1185
+ });
1186
+ const subPackageUpdates = await getSubPackageUpdates(
1187
+ root,
1188
+ current,
1189
+ toLinter,
1190
+ toFormatter
1191
+ );
1192
+ return {
1193
+ fromLinter: current.linter,
1194
+ toLinter,
1195
+ fromFormatter: current.formatter,
1196
+ toFormatter,
1197
+ changes,
1198
+ subPackageUpdates
1199
+ };
1200
+ }
1201
+ async function getSubPackageUpdates(root, current, toLinter, toFormatter) {
1202
+ const updates = [];
1203
+ const workspacePath = path.join(root, "pnpm-workspace.yaml");
1204
+ let workspaceContent;
1205
+ try {
1206
+ workspaceContent = await promises.readFile(workspacePath, "utf-8");
1207
+ } catch {
1208
+ return updates;
1209
+ }
1210
+ const packageGlobs = index.parseWorkspaceYamlContent(workspaceContent);
1211
+ for (const glob of packageGlobs) {
1212
+ if (glob.includes(".config")) continue;
1213
+ const baseDir = glob.replace(/\/\*$/, "").replace(/^["']|["']$/g, "");
1214
+ const basePath = path.join(root, baseDir);
1215
+ try {
1216
+ const entries = await promises.readdir(basePath, { withFileTypes: true });
1217
+ for (const entry of entries) {
1218
+ if (!entry.isDirectory()) continue;
1219
+ const pkgJsonPath = path.join(basePath, entry.name, "package.json");
1220
+ try {
1221
+ const content = await promises.readFile(pkgJsonPath, "utf-8");
1222
+ const pkg = JSON.parse(content);
1223
+ const devDeps = pkg.devDependencies ?? {};
1224
+ const remove = [];
1225
+ const add = [];
1226
+ const oldLinterPkg = LINTER_CONFIG_PACKAGES[current.linter];
1227
+ const newLinterPkg = LINTER_CONFIG_PACKAGES[toLinter];
1228
+ if (oldLinterPkg && oldLinterPkg !== newLinterPkg && devDeps[oldLinterPkg]) {
1229
+ remove.push(oldLinterPkg);
1230
+ }
1231
+ if (newLinterPkg && newLinterPkg !== oldLinterPkg && oldLinterPkg && devDeps[oldLinterPkg]) {
1232
+ add.push(newLinterPkg);
1233
+ }
1234
+ if (current.formatter !== current.linter) {
1235
+ const oldFormatterPkg = FORMATTER_CONFIG_PACKAGES[current.formatter];
1236
+ const newFormatterPkg = FORMATTER_CONFIG_PACKAGES[toFormatter];
1237
+ if (oldFormatterPkg && oldFormatterPkg !== newFormatterPkg && devDeps[oldFormatterPkg]) {
1238
+ remove.push(oldFormatterPkg);
1239
+ }
1240
+ if (newFormatterPkg && newFormatterPkg !== oldFormatterPkg && oldFormatterPkg && devDeps[oldFormatterPkg]) {
1241
+ add.push(newFormatterPkg);
1242
+ }
1243
+ }
1244
+ if (remove.length > 0 || add.length > 0) {
1245
+ updates.push({
1246
+ path: path.join(baseDir, entry.name, "package.json"),
1247
+ remove,
1248
+ add
1249
+ });
1250
+ }
1251
+ } catch {
1252
+ }
1253
+ }
1254
+ } catch {
1255
+ }
1256
+ }
1257
+ return updates;
1258
+ }
1259
+ async function applyMigration(plan, root) {
1260
+ for (const change of plan.changes) {
1261
+ if (change.type === "remove-dir") {
1262
+ const fullPath = path.join(root, change.path);
1263
+ try {
1264
+ await promises.rm(fullPath, { recursive: true });
1265
+ } catch {
1266
+ }
1267
+ }
1268
+ }
1269
+ for (const change of plan.changes) {
1270
+ if (change.type === "remove-file") {
1271
+ const fullPath = path.join(root, change.path);
1272
+ try {
1273
+ await promises.rm(fullPath);
1274
+ } catch {
1275
+ }
1276
+ }
1277
+ }
1278
+ for (const change of plan.changes) {
1279
+ if (change.type === "add-file" && change.content) {
1280
+ const fullPath = path.join(root, change.path);
1281
+ await promises.mkdir(path.dirname(fullPath), { recursive: true });
1282
+ await promises.writeFile(fullPath, change.content);
1283
+ }
1284
+ }
1285
+ await updateRootPackageJson(root, plan);
1286
+ for (const update of plan.subPackageUpdates) {
1287
+ await updateSubPackageJson(root, update);
1288
+ }
1289
+ }
1290
+ async function updateRootPackageJson(root, plan) {
1291
+ const pkgPath = path.join(root, "package.json");
1292
+ const content = await promises.readFile(pkgPath, "utf-8");
1293
+ const pkg = JSON.parse(content);
1294
+ const devDeps = pkg.devDependencies ?? {};
1295
+ const oldLinterDep = LINTER_DEPS[plan.fromLinter];
1296
+ delete devDeps[oldLinterDep];
1297
+ if (plan.fromFormatter !== plan.fromLinter) {
1298
+ const oldFormatterDep = FORMATTER_DEPS[plan.fromFormatter];
1299
+ delete devDeps[oldFormatterDep];
1300
+ }
1301
+ const newLinterDep = LINTER_DEPS[plan.toLinter];
1302
+ if (plan.toLinter === "oxlint") {
1303
+ devDeps[newLinterDep] = "^1.36.0";
1304
+ } else if (plan.toLinter === "eslint") {
1305
+ devDeps[newLinterDep] = "^9.17.0";
1306
+ } else if (plan.toLinter === "biome") {
1307
+ devDeps[newLinterDep] = "^1.9.4";
1308
+ }
1309
+ if (plan.toFormatter !== plan.toLinter) {
1310
+ const newFormatterDep = FORMATTER_DEPS[plan.toFormatter];
1311
+ if (plan.toFormatter === "oxfmt") {
1312
+ devDeps[newFormatterDep] = "^0.21.0";
1313
+ } else if (plan.toFormatter === "prettier") {
1314
+ devDeps[newFormatterDep] = "^3.4.2";
1315
+ } else if (plan.toFormatter === "biome") {
1316
+ devDeps[newFormatterDep] = "^1.9.4";
1317
+ }
1318
+ }
1319
+ pkg.devDependencies = Object.fromEntries(
1320
+ Object.entries(devDeps).sort(([a], [b]) => a.localeCompare(b))
1321
+ );
1322
+ const scripts = pkg.scripts ?? {};
1323
+ if (plan.toLinter === "oxlint") {
1324
+ scripts.lint = "oxlint .";
1325
+ } else if (plan.toLinter === "eslint") {
1326
+ scripts.lint = "eslint .";
1327
+ } else if (plan.toLinter === "biome") {
1328
+ scripts.lint = "biome check .";
1329
+ }
1330
+ if (plan.toFormatter === "oxfmt") {
1331
+ scripts.format = "oxfmt .";
1332
+ } else if (plan.toFormatter === "prettier") {
1333
+ scripts.format = "prettier --write .";
1334
+ } else if (plan.toFormatter === "biome") {
1335
+ scripts.format = "biome format . --write";
1336
+ }
1337
+ pkg.scripts = scripts;
1338
+ await promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
1339
+ }
1340
+ async function updateSubPackageJson(root, update) {
1341
+ const pkgPath = path.join(root, update.path);
1342
+ const content = await promises.readFile(pkgPath, "utf-8");
1343
+ const pkg = JSON.parse(content);
1344
+ const devDeps = pkg.devDependencies ?? {};
1345
+ for (const dep of update.remove) {
1346
+ delete devDeps[dep];
1347
+ }
1348
+ for (const dep of update.add) {
1349
+ devDeps[dep] = "workspace:*";
1350
+ }
1351
+ pkg.devDependencies = Object.fromEntries(
1352
+ Object.entries(devDeps).sort(([a], [b]) => a.localeCompare(b))
1353
+ );
1354
+ await promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
1355
+ }
1356
+ function formatMigrationChange(change) {
1357
+ const icon = change.type === "remove-dir" || change.type === "remove-file" ? "-" : change.type === "add-file" ? "+" : "~";
1358
+ return ` ${icon} ${change.description}`;
1359
+ }
1360
+
723
1361
  const require$1 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href)));
724
1362
  const pkg = require$1("../package.json");
1363
+ const META_OPTIONS = [
1364
+ "clearConfig",
1365
+ "configPath",
1366
+ "check",
1367
+ "fix",
1368
+ "update",
1369
+ "yes",
1370
+ "workspace",
1371
+ "path",
1372
+ "dir"
1373
+ ];
1374
+ function hasConfigOptions(options) {
1375
+ return Object.keys(options).some(
1376
+ (key) => !META_OPTIONS.includes(key)
1377
+ );
1378
+ }
725
1379
  async function fileExists(path) {
726
1380
  try {
727
1381
  await promises.access(path, fs.constants.F_OK);
@@ -730,6 +1384,50 @@ async function fileExists(path) {
730
1384
  return false;
731
1385
  }
732
1386
  }
1387
+ async function promptForAiPlatforms(isNonInteractive) {
1388
+ const savedPlatforms = getAiPlatforms();
1389
+ if (isNonInteractive) {
1390
+ return savedPlatforms ?? index.ALL_AI_PLATFORMS;
1391
+ }
1392
+ if (savedPlatforms && savedPlatforms.length > 0) {
1393
+ const savedLabels = savedPlatforms.map((plat) => index.AI_PLATFORM_LABELS[plat]).join(", ");
1394
+ const useDefault = await p__namespace.confirm({
1395
+ message: `Add AI rules? ${color__default.dim(`(${savedLabels})`)}`,
1396
+ initialValue: true
1397
+ });
1398
+ if (p__namespace.isCancel(useDefault)) {
1399
+ return [];
1400
+ }
1401
+ if (useDefault) {
1402
+ return savedPlatforms;
1403
+ }
1404
+ }
1405
+ const selected = await p__namespace.multiselect({
1406
+ message: "Add AI rules?",
1407
+ options: index.ALL_AI_PLATFORMS.map((platform) => ({
1408
+ value: platform,
1409
+ label: index.AI_PLATFORM_LABELS[platform],
1410
+ hint: index.AI_PLATFORM_HINTS[platform]
1411
+ })),
1412
+ initialValues: [],
1413
+ required: false
1414
+ });
1415
+ if (p__namespace.isCancel(selected)) {
1416
+ return [];
1417
+ }
1418
+ const platforms = selected;
1419
+ if (platforms.length === 0) {
1420
+ return [];
1421
+ }
1422
+ const saveChoice = await p__namespace.confirm({
1423
+ message: "Save selection for future projects?",
1424
+ initialValue: true
1425
+ });
1426
+ if (!p__namespace.isCancel(saveChoice) && saveChoice) {
1427
+ setAiPlatforms(platforms);
1428
+ }
1429
+ return platforms;
1430
+ }
733
1431
  async function writeGeneratedFiles(basePath, files) {
734
1432
  const filePaths = Object.keys(files).sort();
735
1433
  for (const filePath of filePaths) {
@@ -774,15 +1472,39 @@ async function parseWorkspaceDirectories(monorepoRoot) {
774
1472
  return [];
775
1473
  }
776
1474
  }
777
- async function detectWorkspaceTooling(monorepoRoot) {
1475
+ async function detectWorkspaceSettings(monorepoRoot) {
778
1476
  try {
1477
+ const tooling = await index.detectTooling(monorepoRoot);
779
1478
  const pkgPath = path.join(monorepoRoot, "package.json");
780
1479
  const content = await promises.readFile(pkgPath, "utf-8");
781
1480
  const pkgJson = JSON.parse(content);
782
- const devDeps = pkgJson.devDependencies ?? {};
783
- const linter = devDeps.oxlint ? "oxlint" : devDeps.eslint ? "eslint" : devDeps["@biomejs/biome"] ? "biome" : void 0;
784
- const formatter = devDeps.oxfmt ? "oxfmt" : devDeps.prettier ? "prettier" : devDeps["@biomejs/biome"] ? "biome" : void 0;
785
- return { linter, formatter };
1481
+ let packageManager;
1482
+ if (pkgJson.packageManager) {
1483
+ packageManager = pkgJson.packageManager.split("@")[0];
1484
+ }
1485
+ let nodeVersion;
1486
+ if (pkgJson.engines?.node) {
1487
+ const match = pkgJson.engines.node.match(/(\d+)/);
1488
+ if (match) {
1489
+ nodeVersion = match[1];
1490
+ }
1491
+ }
1492
+ let pnpmManageVersions;
1493
+ try {
1494
+ const workspaceFile = path.join(monorepoRoot, "pnpm-workspace.yaml");
1495
+ const workspaceContent = await promises.readFile(workspaceFile, "utf-8");
1496
+ pnpmManageVersions = workspaceContent.includes(
1497
+ "manage-package-manager-versions: true"
1498
+ );
1499
+ } catch {
1500
+ }
1501
+ return {
1502
+ linter: tooling.linter,
1503
+ formatter: tooling.formatter,
1504
+ packageManager,
1505
+ nodeVersion,
1506
+ pnpmManageVersions
1507
+ };
786
1508
  } catch {
787
1509
  return {};
788
1510
  }
@@ -1008,7 +1730,7 @@ Or in \`package.json\`:
1008
1730
  content: existingContent
1009
1731
  };
1010
1732
  }
1011
- async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedTooling, scope) {
1733
+ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedSettings, scope) {
1012
1734
  const workspaceDirectories = await parseWorkspaceDirectories(monorepoRoot);
1013
1735
  const defaultDirectories = ["apps", "packages"];
1014
1736
  const hasCustomDirectories = workspaceDirectories.length > 0 && !workspaceDirectories.every((dir) => defaultDirectories.includes(dir));
@@ -1044,7 +1766,7 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedT
1044
1766
  const packageOptions = await promptForPackageOptions(
1045
1767
  scopedName,
1046
1768
  packageType,
1047
- inheritedTooling
1769
+ inheritedSettings
1048
1770
  );
1049
1771
  let targetDir = defaultDir;
1050
1772
  if (hasCustomDirectories && workspaceDirectories.length > 0) {
@@ -1122,7 +1844,7 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedT
1122
1844
  })
1123
1845
  );
1124
1846
  }
1125
- const formatter = packageOptions.formatter ?? "oxfmt";
1847
+ const formatter = packageOptions.formatter ?? "prettier";
1126
1848
  if (formatter === "prettier") {
1127
1849
  versionPromises.push(
1128
1850
  index.getLatestNpmVersion("prettier", "3.4.2").then((v) => {
@@ -1277,11 +1999,11 @@ async function handleFixCommand(options) {
1277
1999
  console.log(color__default.dim(` \u2022 ${error}`));
1278
2000
  }
1279
2001
  console.log();
1280
- const tooling = await detectWorkspaceTooling(monorepoRoot);
2002
+ const tooling = await detectWorkspaceSettings(monorepoRoot);
1281
2003
  const existingConfigs = await detectExistingConfigs(monorepoRoot);
1282
2004
  const detectedLinter = tooling.linter ?? existingConfigs.linter ?? "oxlint";
1283
- const detectedFormatter = tooling.formatter ?? existingConfigs.formatter ?? "oxfmt";
1284
- const isNonInteractive = options.linter && options.formatter;
2005
+ const detectedFormatter = tooling.formatter ?? existingConfigs.formatter ?? "prettier";
2006
+ const isNonInteractive = Boolean(options.linter && options.formatter);
1285
2007
  let linter;
1286
2008
  let formatter;
1287
2009
  if (isNonInteractive) {
@@ -1459,85 +2181,346 @@ async function handleFixCommand(options) {
1459
2181
  console.log(color__default.dim(" Generated .vscode/extensions.json"));
1460
2182
  }
1461
2183
  }
1462
- const aiFilePaths = {
1463
- "cursor-rules": ".cursor/rules",
1464
- "agents-md": "AGENTS.md",
1465
- "claude-md": "CLAUDE.md",
1466
- "copilot-md": ".github/copilot-instructions.md"
1467
- };
1468
- const existingAiFiles = [];
1469
- for (const [choice, path$1] of Object.entries(aiFilePaths)) {
1470
- if (await fileExists(path.join(monorepoRoot, path$1))) {
1471
- existingAiFiles.push(choice);
2184
+ const aiRulesExist = await fileExists(
2185
+ path.join(monorepoRoot, ".ai/workspace.md")
2186
+ );
2187
+ if (!aiRulesExist) {
2188
+ const platforms = await promptForAiPlatforms(isNonInteractive);
2189
+ if (platforms.length > 0) {
2190
+ const scope = await getMonorepoScope(monorepoRoot);
2191
+ const aiFilesOutput = {};
2192
+ index.generateAiFiles(aiFilesOutput, {
2193
+ name: scope,
2194
+ packageManager: "pnpm",
2195
+ linter,
2196
+ formatter,
2197
+ isMonorepo: true,
2198
+ platforms
2199
+ });
2200
+ for (const [filePath, file] of Object.entries(aiFilesOutput)) {
2201
+ const fullPath = path.join(monorepoRoot, filePath);
2202
+ await promises.mkdir(path.dirname(fullPath), { recursive: true });
2203
+ await promises.writeFile(fullPath, file.content);
2204
+ console.log(color__default.dim(` Generated ${filePath}`));
2205
+ }
1472
2206
  }
1473
2207
  }
1474
- let selectedAiFiles = [];
1475
- const savedAiFiles = getAiFiles();
1476
- const availableChoices = ["cursor-rules", "agents-md", "claude-md", "copilot-md"].filter((c) => !existingAiFiles.includes(c));
1477
- if (availableChoices.length === 0) {
1478
- } else if (isNonInteractive) {
1479
- const preferred = savedAiFiles ?? ["cursor-rules"];
1480
- selectedAiFiles = preferred.filter((f) => availableChoices.includes(f));
1481
- } else if (savedAiFiles && savedAiFiles.length > 0) {
1482
- const availableSaved = savedAiFiles.filter(
1483
- (f) => availableChoices.includes(f)
2208
+ process.exit(0);
2209
+ } catch (error) {
2210
+ spinner.stop(color__default.red("\u2717") + " Failed to fix workspace");
2211
+ console.error(error);
2212
+ process.exit(1);
2213
+ }
2214
+ }
2215
+ async function handleMigration(config, target, root, options) {
2216
+ const plan = await getMigrationPlan(config, target, root);
2217
+ console.log(color__default.cyan("Migration:"));
2218
+ if (plan.fromLinter !== plan.toLinter) {
2219
+ console.log(
2220
+ ` Linter: ${color__default.dim(plan.fromLinter)} \u2192 ${color__default.green(plan.toLinter)}`
2221
+ );
2222
+ }
2223
+ if (plan.fromFormatter !== plan.toFormatter) {
2224
+ console.log(
2225
+ ` Formatter: ${color__default.dim(plan.fromFormatter)} \u2192 ${color__default.green(
2226
+ plan.toFormatter
2227
+ )}`
2228
+ );
2229
+ }
2230
+ console.log();
2231
+ console.log(color__default.cyan("Changes:"));
2232
+ for (const change of plan.changes) {
2233
+ console.log(formatMigrationChange(change));
2234
+ }
2235
+ if (plan.subPackageUpdates.length > 0) {
2236
+ console.log();
2237
+ console.log(color__default.cyan(`Sub-packages (${plan.subPackageUpdates.length}):`));
2238
+ for (const update of plan.subPackageUpdates) {
2239
+ const changes = [
2240
+ ...update.remove.map((d) => `-${d}`),
2241
+ ...update.add.map((d) => `+${d}`)
2242
+ ].join(", ");
2243
+ console.log(` ~ ${update.path} (${changes})`);
2244
+ }
2245
+ }
2246
+ console.log();
2247
+ if (!options.yes) {
2248
+ const confirm = await p__namespace.confirm({
2249
+ message: "Apply migration?",
2250
+ initialValue: true
2251
+ });
2252
+ if (p__namespace.isCancel(confirm) || !confirm) {
2253
+ console.log(color__default.dim(" Migration cancelled"));
2254
+ process.exit(0);
2255
+ }
2256
+ }
2257
+ await applyMigration(plan, root);
2258
+ const aiWorkspacePath = path.join(root, ".ai/workspace.md");
2259
+ const aiRulesExist = await fileExists(aiWorkspacePath);
2260
+ if (aiRulesExist) {
2261
+ console.log();
2262
+ console.log(color__default.cyan("Updating AI rules..."));
2263
+ const scope = await getMonorepoScope(root);
2264
+ const existingPlatforms = [];
2265
+ if (await fileExists(path.join(root, "AGENTS.md"))) {
2266
+ existingPlatforms.push("agents");
2267
+ }
2268
+ if (await fileExists(path.join(root, "CLAUDE.md"))) {
2269
+ existingPlatforms.push("claude");
2270
+ }
2271
+ const aiFilesOutput = {};
2272
+ index.generateAiFiles(aiFilesOutput, {
2273
+ name: scope,
2274
+ packageManager: "pnpm",
2275
+ linter: plan.toLinter,
2276
+ formatter: plan.toFormatter,
2277
+ isMonorepo: true,
2278
+ platforms: existingPlatforms.length > 0 ? existingPlatforms : ["agents"]
2279
+ });
2280
+ for (const [filePath, file] of Object.entries(aiFilesOutput)) {
2281
+ const fullPath = path.join(root, filePath);
2282
+ await promises.mkdir(path.dirname(fullPath), { recursive: true });
2283
+ await promises.writeFile(fullPath, file.content);
2284
+ console.log(color__default.dim(` ${filePath}`));
2285
+ }
2286
+ }
2287
+ console.log();
2288
+ console.log(
2289
+ color__default.green("\u2713") + ` Migrated to ${plan.toLinter}/${plan.toFormatter}`
2290
+ );
2291
+ console.log(color__default.dim(" Run `pnpm install` to update dependencies"));
2292
+ process.exit(0);
2293
+ }
2294
+ async function handleUpdateCommand(options) {
2295
+ const monorepoRoot = await detectMonorepoRoot();
2296
+ if (!monorepoRoot) {
2297
+ console.log(color__default.red("\u2717") + " Not a monorepo workspace");
2298
+ console.log(color__default.dim(" --update only supports pnpm monorepos"));
2299
+ process.exit(1);
2300
+ }
2301
+ const { valid, errors } = await validateWorkspace(monorepoRoot);
2302
+ if (!valid) {
2303
+ console.log(color__default.yellow("!") + " Workspace has issues:");
2304
+ for (const error of errors) {
2305
+ console.log(color__default.dim(` \u2022 ${error}`));
2306
+ }
2307
+ console.log();
2308
+ const shouldFix = options.yes || await p__namespace.confirm({
2309
+ message: "Run fix first to resolve these issues?",
2310
+ initialValue: true
2311
+ });
2312
+ if (p__namespace.isCancel(shouldFix) || !shouldFix) {
2313
+ console.log(
2314
+ color__default.dim(" Run `pnpm create krispya --fix` to fix manually")
1484
2315
  );
1485
- if (availableSaved.length > 0) {
1486
- const savedLabels = availableSaved.map((f) => aiFilePaths[f]).join(", ");
1487
- const useDefault = await p__namespace.confirm({
1488
- message: `Generate AI instruction files? ${color__default.dim(
1489
- `(${savedLabels})`
1490
- )}`,
2316
+ process.exit(1);
2317
+ }
2318
+ const preFixConfig = await detectCurrentConfig(monorepoRoot);
2319
+ const fixOptions = {
2320
+ ...options,
2321
+ linter: options.linter ?? preFixConfig.linter,
2322
+ formatter: options.formatter ?? preFixConfig.formatter
2323
+ };
2324
+ await handleFixCommand(fixOptions);
2325
+ }
2326
+ const config = await detectCurrentConfig(monorepoRoot);
2327
+ const targetLinter = options.linter;
2328
+ const targetFormatter = options.formatter;
2329
+ const migrationTarget = { linter: targetLinter, formatter: targetFormatter };
2330
+ if (needsMigration(config, migrationTarget)) {
2331
+ await handleMigration(config, migrationTarget, monorepoRoot, options);
2332
+ return;
2333
+ }
2334
+ console.log(
2335
+ color__default.cyan("Checking for updates...") + color__default.dim(` (${config.linter}/${config.formatter})`)
2336
+ );
2337
+ console.log();
2338
+ const expected = generateExpectedFiles(config);
2339
+ const categories = await compareWithDisk(expected, monorepoRoot);
2340
+ const workspaceConfigChanges = await getWorkspaceConfigUpdates(monorepoRoot);
2341
+ const workspaceCategory = {
2342
+ category: "workspace-config",
2343
+ label: "Workspace Config",
2344
+ changes: workspaceConfigChanges,
2345
+ hasUserModifications: workspaceConfigChanges.some(
2346
+ (c) => c.status === "modified"
2347
+ )
2348
+ };
2349
+ const allCategories = categories.filter(
2350
+ (c) => c.category !== "workspace-config"
2351
+ );
2352
+ if (workspaceConfigChanges.length > 0) {
2353
+ const configPkgIndex = allCategories.findIndex(
2354
+ (c) => c.category === "config-packages"
2355
+ );
2356
+ if (configPkgIndex !== -1) {
2357
+ allCategories.splice(configPkgIndex + 1, 0, workspaceCategory);
2358
+ } else {
2359
+ allCategories.push(workspaceCategory);
2360
+ }
2361
+ }
2362
+ let updatedCount = 0;
2363
+ let skippedCount = 0;
2364
+ for (const category of allCategories) {
2365
+ const newChanges = category.changes.filter((c) => c.status === "added");
2366
+ const modifiedChanges = category.changes.filter(
2367
+ (c) => c.status === "modified"
2368
+ );
2369
+ const hasNew = newChanges.length > 0;
2370
+ const hasModified = modifiedChanges.length > 0;
2371
+ const hasChanges = hasNew || hasModified;
2372
+ if (!hasChanges) {
2373
+ console.log(color__default.green("\u2713") + ` ${category.label}: Up to date`);
2374
+ continue;
2375
+ }
2376
+ if (category.category === "ai-files") {
2377
+ if (hasNew) {
2378
+ console.log(color__default.cyan(category.label + ":"));
2379
+ console.log(
2380
+ color__default.dim(` ${newChanges.length} AI file(s) can be added`)
2381
+ );
2382
+ console.log();
2383
+ const applyAi = options.yes ? true : await p__namespace.confirm({
2384
+ message: "Add AI rules?",
1491
2385
  initialValue: true
1492
2386
  });
1493
- if (!p__namespace.isCancel(useDefault) && useDefault) {
1494
- selectedAiFiles = availableSaved;
2387
+ if (!p__namespace.isCancel(applyAi) && applyAi) {
2388
+ await applyUpdates(newChanges, monorepoRoot);
2389
+ console.log(
2390
+ color__default.green("\u2713") + ` Added ${newChanges.length} AI file(s)`
2391
+ );
2392
+ updatedCount++;
2393
+ } else {
2394
+ console.log(color__default.dim(` Skipped ${category.label}`));
2395
+ skippedCount++;
1495
2396
  }
1496
2397
  }
1497
- } else {
1498
- const aiFilesChoice = await p__namespace.multiselect({
1499
- message: "Generate AI instruction files?",
1500
- options: availableChoices.map((c) => ({
1501
- value: c,
1502
- label: aiFilePaths[c],
1503
- hint: c === "cursor-rules" ? "Cursor AI" : c === "agents-md" ? "GitHub Copilot, general" : c === "claude-md" ? "Claude" : "GitHub Copilot"
2398
+ if (hasModified) {
2399
+ console.log(color__default.cyan("AI Files (existing):"));
2400
+ for (const change of modifiedChanges) {
2401
+ console.log(formatFileChange(change));
2402
+ }
2403
+ console.log();
2404
+ if (options.yes) {
2405
+ console.log(color__default.dim(" (--yes mode: keeping existing AI files)"));
2406
+ } else {
2407
+ const updateExisting = await p__namespace.confirm({
2408
+ message: "Update existing AI files to latest template?",
2409
+ initialValue: false
2410
+ });
2411
+ if (!p__namespace.isCancel(updateExisting) && updateExisting) {
2412
+ await applyUpdates(modifiedChanges, monorepoRoot);
2413
+ console.log(color__default.green("\u2713") + " Updated existing AI files");
2414
+ }
2415
+ }
2416
+ }
2417
+ console.log();
2418
+ continue;
2419
+ }
2420
+ let changesToApply = [];
2421
+ if (options.yes) {
2422
+ console.log(color__default.cyan(category.label + ":"));
2423
+ for (const change of [...newChanges, ...modifiedChanges]) {
2424
+ console.log(formatFileChange(change));
2425
+ }
2426
+ console.log();
2427
+ if (category.category === "workspace-config") {
2428
+ changesToApply = [...newChanges, ...modifiedChanges];
2429
+ if (changesToApply.length > 0) {
2430
+ console.log(color__default.dim(" (--yes mode: applying merge updates)"));
2431
+ }
2432
+ } else {
2433
+ changesToApply = newChanges;
2434
+ if (newChanges.length > 0) {
2435
+ console.log(color__default.dim(" (--yes mode: adding new files only)"));
2436
+ }
2437
+ }
2438
+ } else if (hasNew && hasModified) {
2439
+ const allChanges = [...newChanges, ...modifiedChanges];
2440
+ const selectedFiles = await p__namespace.multiselect({
2441
+ message: `${category.label} (+ new, ~ changed)`,
2442
+ options: allChanges.map((change) => ({
2443
+ value: change.path,
2444
+ label: change.status === "added" ? `+ ${change.path}` : `~ ${change.path}`
1504
2445
  })),
2446
+ initialValues: newChanges.map((c) => c.path),
2447
+ // Pre-select new files
1505
2448
  required: false
1506
2449
  });
1507
- if (!p__namespace.isCancel(aiFilesChoice) && aiFilesChoice.length > 0) {
1508
- selectedAiFiles = aiFilesChoice;
1509
- const saveChoice = await p__namespace.confirm({
1510
- message: "Save as default for future?",
1511
- initialValue: true
1512
- });
1513
- if (!p__namespace.isCancel(saveChoice) && saveChoice) {
1514
- setAiFiles(selectedAiFiles);
1515
- }
2450
+ if (p__namespace.isCancel(selectedFiles)) {
2451
+ p__namespace.cancel("Operation cancelled.");
2452
+ process.exit(0);
1516
2453
  }
1517
- }
1518
- if (selectedAiFiles.length > 0) {
1519
- const scope = await getMonorepoScope(monorepoRoot);
1520
- const aiFilesOutput = {};
1521
- index.generateAiFiles(aiFilesOutput, {
1522
- name: scope,
1523
- packageManager: "pnpm",
1524
- linter,
1525
- formatter,
1526
- aiFiles: selectedAiFiles
2454
+ if (selectedFiles.length > 0) {
2455
+ changesToApply = allChanges.filter(
2456
+ (c) => selectedFiles.includes(c.path)
2457
+ );
2458
+ }
2459
+ } else if (hasNew) {
2460
+ console.log(color__default.cyan(category.label + ":"));
2461
+ for (const change of newChanges) {
2462
+ console.log(formatFileChange(change));
2463
+ }
2464
+ console.log();
2465
+ const shouldAdd = await p__namespace.confirm({
2466
+ message: `Add ${newChanges.length} new file(s)?`,
2467
+ initialValue: true
1527
2468
  });
1528
- for (const [filePath, file] of Object.entries(aiFilesOutput)) {
1529
- const fullPath = path.join(monorepoRoot, filePath);
1530
- await promises.mkdir(path.dirname(fullPath), { recursive: true });
1531
- await promises.writeFile(fullPath, file.content);
1532
- console.log(color__default.dim(` Generated ${filePath}`));
2469
+ if (p__namespace.isCancel(shouldAdd)) {
2470
+ p__namespace.cancel("Operation cancelled.");
2471
+ process.exit(0);
2472
+ }
2473
+ if (shouldAdd) {
2474
+ changesToApply = newChanges;
2475
+ }
2476
+ } else if (hasModified) {
2477
+ console.log(color__default.cyan(category.label + ":"));
2478
+ for (const change of modifiedChanges) {
2479
+ console.log(formatFileChange(change));
2480
+ }
2481
+ console.log();
2482
+ const shouldUpdate = await p__namespace.confirm({
2483
+ message: `Update ${modifiedChanges.length} file(s)? (will overwrite)`,
2484
+ initialValue: false
2485
+ });
2486
+ if (p__namespace.isCancel(shouldUpdate)) {
2487
+ p__namespace.cancel("Operation cancelled.");
2488
+ process.exit(0);
2489
+ }
2490
+ if (shouldUpdate) {
2491
+ changesToApply = modifiedChanges;
1533
2492
  }
1534
2493
  }
1535
- process.exit(0);
1536
- } catch (error) {
1537
- spinner.stop(color__default.red("\u2717") + " Failed to fix workspace");
1538
- console.error(error);
1539
- process.exit(1);
2494
+ if (changesToApply.length > 0) {
2495
+ await applyUpdates(changesToApply, monorepoRoot);
2496
+ const addedCount = changesToApply.filter(
2497
+ (c) => c.status === "added"
2498
+ ).length;
2499
+ const updatedFilesCount = changesToApply.filter(
2500
+ (c) => c.status === "modified"
2501
+ ).length;
2502
+ const parts = [];
2503
+ if (addedCount > 0) parts.push(`added ${addedCount}`);
2504
+ if (updatedFilesCount > 0) parts.push(`updated ${updatedFilesCount}`);
2505
+ console.log(color__default.green("\u2713") + ` ${category.label}: ${parts.join(", ")}`);
2506
+ updatedCount++;
2507
+ } else {
2508
+ console.log(color__default.dim(` Skipped ${category.label}`));
2509
+ skippedCount++;
2510
+ }
2511
+ console.log();
1540
2512
  }
2513
+ if (updatedCount === 0 && skippedCount === 0) {
2514
+ console.log(color__default.green("\u2713") + " Everything is up to date!");
2515
+ } else if (updatedCount > 0) {
2516
+ console.log(
2517
+ color__default.green("\u2713") + ` Updated ${updatedCount} ${updatedCount === 1 ? "category" : "categories"}`
2518
+ );
2519
+ if (skippedCount > 0) {
2520
+ console.log(color__default.dim(` Skipped ${skippedCount}`));
2521
+ }
2522
+ }
2523
+ process.exit(0);
1541
2524
  }
1542
2525
  async function handleWorkspaceCommand(name, options) {
1543
2526
  const monorepoRoot = await detectMonorepoRoot();
@@ -1559,7 +2542,7 @@ async function handleWorkspaceCommand(name, options) {
1559
2542
  process.exit(1);
1560
2543
  }
1561
2544
  const scope = await getMonorepoScope(monorepoRoot);
1562
- const inheritedTooling = await detectWorkspaceTooling(monorepoRoot);
2545
+ const inheritedSettings = await detectWorkspaceSettings(monorepoRoot);
1563
2546
  const projectType = options.type ?? "app";
1564
2547
  const defaultDir = projectType === "library" ? "packages" : "apps";
1565
2548
  const targetDir = options.dir ?? defaultDir;
@@ -1585,8 +2568,11 @@ async function handleWorkspaceCommand(name, options) {
1585
2568
  })
1586
2569
  );
1587
2570
  }
1588
- const linter = inheritedTooling.linter ?? options.linter ?? "oxlint";
1589
- const formatter = inheritedTooling.formatter ?? options.formatter ?? "oxfmt";
2571
+ const linter = inheritedSettings.linter ?? options.linter ?? "oxlint";
2572
+ const formatter = inheritedSettings.formatter ?? options.formatter ?? "prettier";
2573
+ const packageManager = inheritedSettings.packageManager ?? "pnpm";
2574
+ const nodeVersion = inheritedSettings.nodeVersion ?? "latest";
2575
+ const pnpmManageVersions = inheritedSettings.pnpmManageVersions ?? true;
1590
2576
  await Promise.all(versionPromises);
1591
2577
  const relativePkgPath = path.join(targetDir, name);
1592
2578
  const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
@@ -1597,6 +2583,9 @@ async function handleWorkspaceCommand(name, options) {
1597
2583
  template,
1598
2584
  linter,
1599
2585
  formatter,
2586
+ packageManager,
2587
+ nodeVersion,
2588
+ pnpmManageVersions,
1600
2589
  workspaceRoot,
1601
2590
  versions,
1602
2591
  ...baseTemplate === "r3f" && {
@@ -1630,7 +2619,7 @@ async function handleWorkspaceCommand(name, options) {
1630
2619
  process.exit(1);
1631
2620
  }
1632
2621
  }
1633
- async function handleMonorepoCreation(generateOptions) {
2622
+ async function handleMonorepoCreation(generateOptions, isNonInteractive) {
1634
2623
  const { generateMonorepo } = await import('./chunks/index.cjs').then(function (n) { return n.monorepo; });
1635
2624
  const packageManager = generateOptions.packageManager || "pnpm";
1636
2625
  if (packageManager === "pnpm") {
@@ -1644,55 +2633,7 @@ async function handleMonorepoCreation(generateOptions) {
1644
2633
  if (nodeVersion === "latest") {
1645
2634
  generateOptions.nodeVersion = await index.getLatestNodeVersion();
1646
2635
  }
1647
- const savedAiFiles = getAiFiles();
1648
- let selectedAiFiles = [];
1649
- if (savedAiFiles && savedAiFiles.length > 0) {
1650
- const aiFileLabels = {
1651
- "cursor-rules": ".cursor/rules",
1652
- "agents-md": "AGENTS.md",
1653
- "claude-md": "CLAUDE.md",
1654
- "copilot-md": ".github/copilot-instructions.md"
1655
- };
1656
- const savedLabels = savedAiFiles.map((f) => aiFileLabels[f]).join(", ");
1657
- const useDefault = await p__namespace.confirm({
1658
- message: `Generate AI instruction files? ${color__default.dim(
1659
- `(${savedLabels})`
1660
- )}`,
1661
- initialValue: true
1662
- });
1663
- if (!p__namespace.isCancel(useDefault) && useDefault) {
1664
- selectedAiFiles = savedAiFiles;
1665
- }
1666
- } else {
1667
- const aiFilesChoice = await p__namespace.multiselect({
1668
- message: "Generate AI instruction files?",
1669
- options: [
1670
- { value: "cursor-rules", label: ".cursor/rules", hint: "Cursor AI" },
1671
- {
1672
- value: "agents-md",
1673
- label: "AGENTS.md",
1674
- hint: "GitHub Copilot, general"
1675
- },
1676
- { value: "claude-md", label: "CLAUDE.md", hint: "Claude" },
1677
- {
1678
- value: "copilot-md",
1679
- label: ".github/copilot-instructions.md",
1680
- hint: "GitHub Copilot"
1681
- }
1682
- ],
1683
- required: false
1684
- });
1685
- if (!p__namespace.isCancel(aiFilesChoice) && aiFilesChoice.length > 0) {
1686
- selectedAiFiles = aiFilesChoice;
1687
- const saveChoice = await p__namespace.confirm({
1688
- message: "Save as default for future monorepos?",
1689
- initialValue: true
1690
- });
1691
- if (!p__namespace.isCancel(saveChoice) && saveChoice) {
1692
- setAiFiles(selectedAiFiles);
1693
- }
1694
- }
1695
- }
2636
+ const aiPlatforms = await promptForAiPlatforms(isNonInteractive);
1696
2637
  const projectPath = path.join(process$1.cwd(), generateOptions.name);
1697
2638
  const spinner = p__namespace.spinner();
1698
2639
  spinner.start("Creating monorepo workspace...");
@@ -1700,12 +2641,12 @@ async function handleMonorepoCreation(generateOptions) {
1700
2641
  const { files } = generateMonorepo({
1701
2642
  name: generateOptions.name,
1702
2643
  linter: generateOptions.linter ?? "oxlint",
1703
- formatter: generateOptions.formatter ?? "oxfmt",
2644
+ formatter: generateOptions.formatter ?? "prettier",
1704
2645
  packageManager,
1705
2646
  pnpmVersion: generateOptions.pnpmVersion,
1706
2647
  pnpmManageVersions: generateOptions.pnpmManageVersions,
1707
2648
  nodeVersion: generateOptions.nodeVersion,
1708
- aiFiles: selectedAiFiles.length > 0 ? selectedAiFiles : void 0
2649
+ aiPlatforms: aiPlatforms.length > 0 ? aiPlatforms : void 0
1709
2650
  });
1710
2651
  const filePaths = Object.keys(files).sort();
1711
2652
  for (const filePath of filePaths) {
@@ -1717,9 +2658,15 @@ async function handleMonorepoCreation(generateOptions) {
1717
2658
  }
1718
2659
  }
1719
2660
  spinner.stop(color__default.green.inverse(" \u2713 Monorepo workspace created! "));
1720
- const newMonorepoTooling = {
2661
+ if (isNonInteractive) {
2662
+ process.exit(0);
2663
+ }
2664
+ const newWorkspaceSettings = {
1721
2665
  linter: generateOptions.linter,
1722
- formatter: generateOptions.formatter
2666
+ formatter: generateOptions.formatter,
2667
+ packageManager,
2668
+ nodeVersion: generateOptions.nodeVersion,
2669
+ pnpmManageVersions: generateOptions.pnpmManageVersions
1723
2670
  };
1724
2671
  const scope = generateOptions.name;
1725
2672
  let addMore = true;
@@ -1727,7 +2674,7 @@ async function handleMonorepoCreation(generateOptions) {
1727
2674
  addMore = await createPackageInWorkspace(
1728
2675
  projectPath,
1729
2676
  packageManager,
1730
- newMonorepoTooling,
2677
+ newWorkspaceSettings,
1731
2678
  scope
1732
2679
  );
1733
2680
  }
@@ -1746,10 +2693,14 @@ async function handleMonorepoCreation(generateOptions) {
1746
2693
  process.exit(1);
1747
2694
  }
1748
2695
  }
1749
- async function handleStandaloneProjectCreation(generateOptions) {
2696
+ async function handleStandaloneProjectCreation(generateOptions, isNonInteractive) {
1750
2697
  const base = generateOptions.template ? index.getBaseTemplate(generateOptions.template) : "vanilla";
1751
2698
  const defaultFallbackName = base === "vanilla" ? "vanilla-app" : base === "react" ? "react-app" : "react-three-app";
1752
2699
  generateOptions.name ??= defaultFallbackName;
2700
+ const aiPlatforms = await promptForAiPlatforms(isNonInteractive);
2701
+ if (aiPlatforms.length > 0) {
2702
+ generateOptions.aiPlatforms = aiPlatforms;
2703
+ }
1753
2704
  const packageManager = generateOptions.packageManager || "pnpm";
1754
2705
  if (packageManager === "pnpm") {
1755
2706
  generateOptions.pnpmVersion = await index.getLatestPnpmVersion();
@@ -1800,7 +2751,7 @@ async function handleStandaloneProjectCreation(generateOptions) {
1800
2751
  })
1801
2752
  );
1802
2753
  }
1803
- const formatter = generateOptions.formatter ?? "oxfmt";
2754
+ const formatter = generateOptions.formatter ?? "prettier";
1804
2755
  if (formatter === "prettier") {
1805
2756
  versionPromises.push(
1806
2757
  index.getLatestNpmVersion("prettier", "3.4.2").then((v) => {
@@ -1829,6 +2780,9 @@ async function handleStandaloneProjectCreation(generateOptions) {
1829
2780
  const files = index.generate(generateOptions);
1830
2781
  await writeGeneratedFiles(projectPath, files);
1831
2782
  spinner.stop(color__default.green.inverse(" \u2713 Project created! "));
2783
+ if (isNonInteractive) {
2784
+ process.exit(0);
2785
+ }
1832
2786
  const nextSteps = isLibrary ? [
1833
2787
  `cd ${generateOptions.name}`,
1834
2788
  `${packageManager} install`,
@@ -1861,21 +2815,23 @@ async function handleInteractiveMonorepoMode(monorepoRoot) {
1861
2815
  process.exit(0);
1862
2816
  }
1863
2817
  if (choice === "add") {
1864
- const inheritedTooling = await detectWorkspaceTooling(monorepoRoot);
1865
- if (inheritedTooling.linter || inheritedTooling.formatter) {
1866
- const toolingInfo = [
1867
- inheritedTooling.linter && `linter: ${inheritedTooling.linter}`,
1868
- inheritedTooling.formatter && `formatter: ${inheritedTooling.formatter}`
2818
+ const inheritedSettings = await detectWorkspaceSettings(monorepoRoot);
2819
+ const hasSettings = Object.values(inheritedSettings).some(Boolean);
2820
+ if (hasSettings) {
2821
+ const settingsInfo = [
2822
+ inheritedSettings.linter && `linter: ${inheritedSettings.linter}`,
2823
+ inheritedSettings.formatter && `formatter: ${inheritedSettings.formatter}`,
2824
+ inheritedSettings.packageManager && `pm: ${inheritedSettings.packageManager}`
1869
2825
  ].filter(Boolean).join(", ");
1870
- p__namespace.log.info(`Using workspace tooling (${toolingInfo})`);
2826
+ p__namespace.log.info(`Using workspace settings (${settingsInfo})`);
1871
2827
  }
1872
2828
  const scope = await getMonorepoScope(monorepoRoot);
1873
2829
  let addMore = true;
1874
2830
  while (addMore) {
1875
2831
  addMore = await createPackageInWorkspace(
1876
2832
  monorepoRoot,
1877
- "pnpm",
1878
- inheritedTooling,
2833
+ inheritedSettings.packageManager ?? "pnpm",
2834
+ inheritedSettings,
1879
2835
  scope
1880
2836
  );
1881
2837
  }
@@ -1902,7 +2858,7 @@ async function main() {
1902
2858
  "linter: eslint, oxlint, or biome (default: oxlint)"
1903
2859
  ).option(
1904
2860
  "--formatter <type>",
1905
- "formatter: prettier, oxfmt, or biome (default: oxfmt)"
2861
+ "formatter: prettier, oxfmt, or biome (default: prettier)"
1906
2862
  ).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(
1907
2863
  "--package-manager <manager>",
1908
2864
  "specify package manager (e.g. npm, yarn, pnpm)"
@@ -1924,7 +2880,7 @@ async function main() {
1924
2880
  ).option("--clear-config", "Clear saved preferences (e.g. editor choice)").option("--config-path", "Print the path to the config file").option(
1925
2881
  "--check",
1926
2882
  "Check if current directory is in a valid monorepo workspace"
1927
- ).option("--fix", "Fix monorepo by generating missing .config packages").option(
2883
+ ).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(
1928
2884
  "--path <directory>",
1929
2885
  "Run in specified directory instead of current working directory"
1930
2886
  ).action(async (name, options) => {
@@ -1963,6 +2919,12 @@ async function main() {
1963
2919
  case "--fix":
1964
2920
  options.fix = true;
1965
2921
  break;
2922
+ case "--update":
2923
+ options.update = true;
2924
+ break;
2925
+ case "--yes":
2926
+ options.yes = true;
2927
+ break;
1966
2928
  default:
1967
2929
  console.error(color__default.red(`Unknown option: ${name}`));
1968
2930
  process.exit(1);
@@ -1974,6 +2936,9 @@ async function main() {
1974
2936
  if (options.fix) {
1975
2937
  await handleFixCommand(options);
1976
2938
  }
2939
+ if (options.update) {
2940
+ await handleUpdateCommand(options);
2941
+ }
1977
2942
  if (options.dir && !options.workspace) {
1978
2943
  console.error(color__default.red("Error:") + " --dir requires --workspace flag");
1979
2944
  console.log(
@@ -1989,11 +2954,11 @@ async function main() {
1989
2954
  console.clear();
1990
2955
  p__namespace.intro(color__default.bgCyan(color__default.black(` create-krispya v${pkg.version} `)));
1991
2956
  const monorepoRoot = await detectMonorepoRoot();
1992
- if (monorepoRoot && Object.keys(options).length === 0) {
2957
+ if (monorepoRoot && !hasConfigOptions(options)) {
1993
2958
  await handleInteractiveMonorepoMode(monorepoRoot);
1994
2959
  }
1995
2960
  let generateOptions;
1996
- if (Object.keys(options).length > 0) {
2961
+ if (options.yes) {
1997
2962
  const template = options.template ?? "vanilla";
1998
2963
  const baseTemplate = index.getBaseTemplate(template);
1999
2964
  const defaultName = getDefaultProjectName(template);
@@ -2004,7 +2969,7 @@ async function main() {
2004
2969
  libraryBundler: projectType === "library" ? options.bundler ?? "unbuild" : void 0,
2005
2970
  template,
2006
2971
  linter: options.linter ?? "oxlint",
2007
- formatter: options.formatter ?? "oxfmt",
2972
+ formatter: options.formatter ?? "prettier",
2008
2973
  ...baseTemplate === "r3f" && {
2009
2974
  drei: options.drei ? {} : void 0,
2010
2975
  handle: options.handle ? {} : void 0,
@@ -2024,12 +2989,35 @@ async function main() {
2024
2989
  nodeVersion: options.nodeVersion ?? "latest"
2025
2990
  };
2026
2991
  } else {
2027
- generateOptions = await promptForOptions(name);
2992
+ const presets = hasConfigOptions(options) ? {
2993
+ type: options.type,
2994
+ template: options.template,
2995
+ bundler: options.bundler,
2996
+ linter: options.linter,
2997
+ formatter: options.formatter,
2998
+ packageManager: options.packageManager,
2999
+ nodeVersion: options.nodeVersion,
3000
+ pnpmManageVersions: options.pnpmManageVersions,
3001
+ drei: options.drei,
3002
+ handle: options.handle,
3003
+ leva: options.leva,
3004
+ postprocessing: options.postprocessing,
3005
+ rapier: options.rapier,
3006
+ xr: options.xr,
3007
+ uikit: options.uikit,
3008
+ offscreen: options.offscreen,
3009
+ zustand: options.zustand,
3010
+ koota: options.koota,
3011
+ triplex: options.triplex,
3012
+ viverse: options.viverse
3013
+ } : void 0;
3014
+ generateOptions = await promptForOptions(name, presets);
2028
3015
  }
3016
+ const isNonInteractive = options.yes ?? false;
2029
3017
  if (generateOptions.projectType === "monorepo") {
2030
- await handleMonorepoCreation(generateOptions);
3018
+ await handleMonorepoCreation(generateOptions, isNonInteractive);
2031
3019
  } else {
2032
- await handleStandaloneProjectCreation(generateOptions);
3020
+ await handleStandaloneProjectCreation(generateOptions, isNonInteractive);
2033
3021
  }
2034
3022
  });
2035
3023
  await program.parseAsync();