create-krispya 0.5.2 → 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
  }
@@ -821,29 +1543,26 @@ async function getMonorepoScope(monorepoRoot) {
821
1543
  }
822
1544
  async function getWorkspacePackages(monorepoRoot) {
823
1545
  const packagesDir = path.join(monorepoRoot, "packages");
824
- const packages = [];
825
1546
  try {
826
1547
  const { readdir } = await import('fs/promises');
827
1548
  const entries = await readdir(packagesDir, { withFileTypes: true });
1549
+ const names = [];
828
1550
  for (const entry of entries) {
829
- if (entry.isDirectory()) {
830
- try {
831
- const pkgJsonPath = path.join(packagesDir, entry.name, "package.json");
832
- const content = await promises.readFile(pkgJsonPath, "utf-8");
833
- const pkgJson = JSON.parse(content);
834
- if (pkgJson.name) {
835
- packages.push({
836
- name: pkgJson.name,
837
- path: `packages/${entry.name}`
838
- });
839
- }
840
- } catch {
841
- }
1551
+ if (!entry.isDirectory()) continue;
1552
+ try {
1553
+ const content = await promises.readFile(
1554
+ path.join(packagesDir, entry.name, "package.json"),
1555
+ "utf-8"
1556
+ );
1557
+ const pkg2 = JSON.parse(content);
1558
+ if (pkg2.name) names.push(pkg2.name);
1559
+ } catch {
842
1560
  }
843
1561
  }
1562
+ return names;
844
1563
  } catch {
1564
+ return [];
845
1565
  }
846
- return packages;
847
1566
  }
848
1567
  async function ensureConfigInWorkspace(monorepoRoot) {
849
1568
  const workspacePath = path.join(monorepoRoot, "pnpm-workspace.yaml");
@@ -1011,7 +1730,7 @@ Or in \`package.json\`:
1011
1730
  content: existingContent
1012
1731
  };
1013
1732
  }
1014
- async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedTooling, scope) {
1733
+ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedSettings, scope) {
1015
1734
  const workspaceDirectories = await parseWorkspaceDirectories(monorepoRoot);
1016
1735
  const defaultDirectories = ["apps", "packages"];
1017
1736
  const hasCustomDirectories = workspaceDirectories.length > 0 && !workspaceDirectories.every((dir) => defaultDirectories.includes(dir));
@@ -1047,7 +1766,7 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedT
1047
1766
  const packageOptions = await promptForPackageOptions(
1048
1767
  scopedName,
1049
1768
  packageType,
1050
- inheritedTooling
1769
+ inheritedSettings
1051
1770
  );
1052
1771
  let targetDir = defaultDir;
1053
1772
  if (hasCustomDirectories && workspaceDirectories.length > 0) {
@@ -1125,7 +1844,7 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedT
1125
1844
  })
1126
1845
  );
1127
1846
  }
1128
- const formatter = packageOptions.formatter ?? "oxfmt";
1847
+ const formatter = packageOptions.formatter ?? "prettier";
1129
1848
  if (formatter === "prettier") {
1130
1849
  versionPromises.push(
1131
1850
  index.getLatestNpmVersion("prettier", "3.4.2").then((v) => {
@@ -1147,20 +1866,15 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedT
1147
1866
  }
1148
1867
  await Promise.all(versionPromises);
1149
1868
  packageOptions.versions = versions;
1150
- if (packageType === "app") {
1151
- const workspacePackages = await getWorkspacePackages(monorepoRoot);
1152
- if (workspacePackages.length > 0) {
1153
- const selectedDeps = await p__namespace.multiselect({
1154
- message: "Add workspace dependencies?",
1155
- options: workspacePackages.map((pkgInfo) => ({
1156
- value: pkgInfo.name,
1157
- label: pkgInfo.name.replace(/^@[^/]+\//, "")
1158
- })),
1159
- required: false
1160
- });
1161
- if (!p__namespace.isCancel(selectedDeps) && selectedDeps.length > 0) {
1162
- packageOptions.workspaceDependencies = selectedDeps;
1163
- }
1869
+ const workspacePackages = packageType === "app" ? await getWorkspacePackages(monorepoRoot) : [];
1870
+ if (workspacePackages.length > 0) {
1871
+ const selectedDeps = await p__namespace.multiselect({
1872
+ message: "Add workspace dependencies?",
1873
+ options: workspacePackages.map((name) => ({ value: name, label: name })),
1874
+ required: false
1875
+ });
1876
+ if (!p__namespace.isCancel(selectedDeps) && selectedDeps.length > 0) {
1877
+ packageOptions.workspaceDependencies = selectedDeps;
1164
1878
  }
1165
1879
  }
1166
1880
  const outputPath = path.join(monorepoRoot, relativePkgPath);
@@ -1285,11 +1999,11 @@ async function handleFixCommand(options) {
1285
1999
  console.log(color__default.dim(` \u2022 ${error}`));
1286
2000
  }
1287
2001
  console.log();
1288
- const tooling = await detectWorkspaceTooling(monorepoRoot);
2002
+ const tooling = await detectWorkspaceSettings(monorepoRoot);
1289
2003
  const existingConfigs = await detectExistingConfigs(monorepoRoot);
1290
2004
  const detectedLinter = tooling.linter ?? existingConfigs.linter ?? "oxlint";
1291
- const detectedFormatter = tooling.formatter ?? existingConfigs.formatter ?? "oxfmt";
1292
- const isNonInteractive = options.linter && options.formatter;
2005
+ const detectedFormatter = tooling.formatter ?? existingConfigs.formatter ?? "prettier";
2006
+ const isNonInteractive = Boolean(options.linter && options.formatter);
1293
2007
  let linter;
1294
2008
  let formatter;
1295
2009
  if (isNonInteractive) {
@@ -1467,85 +2181,346 @@ async function handleFixCommand(options) {
1467
2181
  console.log(color__default.dim(" Generated .vscode/extensions.json"));
1468
2182
  }
1469
2183
  }
1470
- const aiFilePaths = {
1471
- "cursor-rules": ".cursor/rules",
1472
- "agents-md": "AGENTS.md",
1473
- "claude-md": "CLAUDE.md",
1474
- "copilot-md": ".github/copilot-instructions.md"
1475
- };
1476
- const existingAiFiles = [];
1477
- for (const [choice, path$1] of Object.entries(aiFilePaths)) {
1478
- if (await fileExists(path.join(monorepoRoot, path$1))) {
1479
- 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
+ }
1480
2206
  }
1481
2207
  }
1482
- let selectedAiFiles = [];
1483
- const savedAiFiles = getAiFiles();
1484
- const availableChoices = ["cursor-rules", "agents-md", "claude-md", "copilot-md"].filter((c) => !existingAiFiles.includes(c));
1485
- if (availableChoices.length === 0) {
1486
- } else if (isNonInteractive) {
1487
- const preferred = savedAiFiles ?? ["cursor-rules"];
1488
- selectedAiFiles = preferred.filter((f) => availableChoices.includes(f));
1489
- } else if (savedAiFiles && savedAiFiles.length > 0) {
1490
- const availableSaved = savedAiFiles.filter(
1491
- (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")
1492
2315
  );
1493
- if (availableSaved.length > 0) {
1494
- const savedLabels = availableSaved.map((f) => aiFilePaths[f]).join(", ");
1495
- const useDefault = await p__namespace.confirm({
1496
- message: `Generate AI instruction files? ${color__default.dim(
1497
- `(${savedLabels})`
1498
- )}`,
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?",
1499
2385
  initialValue: true
1500
2386
  });
1501
- if (!p__namespace.isCancel(useDefault) && useDefault) {
1502
- 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++;
1503
2396
  }
1504
2397
  }
1505
- } else {
1506
- const aiFilesChoice = await p__namespace.multiselect({
1507
- message: "Generate AI instruction files?",
1508
- options: availableChoices.map((c) => ({
1509
- value: c,
1510
- label: aiFilePaths[c],
1511
- 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}`
1512
2445
  })),
2446
+ initialValues: newChanges.map((c) => c.path),
2447
+ // Pre-select new files
1513
2448
  required: false
1514
2449
  });
1515
- if (!p__namespace.isCancel(aiFilesChoice) && aiFilesChoice.length > 0) {
1516
- selectedAiFiles = aiFilesChoice;
1517
- const saveChoice = await p__namespace.confirm({
1518
- message: "Save as default for future?",
1519
- initialValue: true
1520
- });
1521
- if (!p__namespace.isCancel(saveChoice) && saveChoice) {
1522
- setAiFiles(selectedAiFiles);
1523
- }
2450
+ if (p__namespace.isCancel(selectedFiles)) {
2451
+ p__namespace.cancel("Operation cancelled.");
2452
+ process.exit(0);
1524
2453
  }
1525
- }
1526
- if (selectedAiFiles.length > 0) {
1527
- const scope = await getMonorepoScope(monorepoRoot);
1528
- const aiFilesOutput = {};
1529
- index.generateAiFiles(aiFilesOutput, {
1530
- name: scope,
1531
- packageManager: "pnpm",
1532
- linter,
1533
- formatter,
1534
- 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
1535
2468
  });
1536
- for (const [filePath, file] of Object.entries(aiFilesOutput)) {
1537
- const fullPath = path.join(monorepoRoot, filePath);
1538
- await promises.mkdir(path.dirname(fullPath), { recursive: true });
1539
- await promises.writeFile(fullPath, file.content);
1540
- 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;
1541
2492
  }
1542
2493
  }
1543
- process.exit(0);
1544
- } catch (error) {
1545
- spinner.stop(color__default.red("\u2717") + " Failed to fix workspace");
1546
- console.error(error);
1547
- 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();
1548
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);
1549
2524
  }
1550
2525
  async function handleWorkspaceCommand(name, options) {
1551
2526
  const monorepoRoot = await detectMonorepoRoot();
@@ -1567,7 +2542,7 @@ async function handleWorkspaceCommand(name, options) {
1567
2542
  process.exit(1);
1568
2543
  }
1569
2544
  const scope = await getMonorepoScope(monorepoRoot);
1570
- const inheritedTooling = await detectWorkspaceTooling(monorepoRoot);
2545
+ const inheritedSettings = await detectWorkspaceSettings(monorepoRoot);
1571
2546
  const projectType = options.type ?? "app";
1572
2547
  const defaultDir = projectType === "library" ? "packages" : "apps";
1573
2548
  const targetDir = options.dir ?? defaultDir;
@@ -1593,8 +2568,11 @@ async function handleWorkspaceCommand(name, options) {
1593
2568
  })
1594
2569
  );
1595
2570
  }
1596
- const linter = inheritedTooling.linter ?? options.linter ?? "oxlint";
1597
- 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;
1598
2576
  await Promise.all(versionPromises);
1599
2577
  const relativePkgPath = path.join(targetDir, name);
1600
2578
  const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
@@ -1605,6 +2583,9 @@ async function handleWorkspaceCommand(name, options) {
1605
2583
  template,
1606
2584
  linter,
1607
2585
  formatter,
2586
+ packageManager,
2587
+ nodeVersion,
2588
+ pnpmManageVersions,
1608
2589
  workspaceRoot,
1609
2590
  versions,
1610
2591
  ...baseTemplate === "r3f" && {
@@ -1638,7 +2619,7 @@ async function handleWorkspaceCommand(name, options) {
1638
2619
  process.exit(1);
1639
2620
  }
1640
2621
  }
1641
- async function handleMonorepoCreation(generateOptions) {
2622
+ async function handleMonorepoCreation(generateOptions, isNonInteractive) {
1642
2623
  const { generateMonorepo } = await import('./chunks/index.cjs').then(function (n) { return n.monorepo; });
1643
2624
  const packageManager = generateOptions.packageManager || "pnpm";
1644
2625
  if (packageManager === "pnpm") {
@@ -1652,55 +2633,7 @@ async function handleMonorepoCreation(generateOptions) {
1652
2633
  if (nodeVersion === "latest") {
1653
2634
  generateOptions.nodeVersion = await index.getLatestNodeVersion();
1654
2635
  }
1655
- const savedAiFiles = getAiFiles();
1656
- let selectedAiFiles = [];
1657
- if (savedAiFiles && savedAiFiles.length > 0) {
1658
- const aiFileLabels = {
1659
- "cursor-rules": ".cursor/rules",
1660
- "agents-md": "AGENTS.md",
1661
- "claude-md": "CLAUDE.md",
1662
- "copilot-md": ".github/copilot-instructions.md"
1663
- };
1664
- const savedLabels = savedAiFiles.map((f) => aiFileLabels[f]).join(", ");
1665
- const useDefault = await p__namespace.confirm({
1666
- message: `Generate AI instruction files? ${color__default.dim(
1667
- `(${savedLabels})`
1668
- )}`,
1669
- initialValue: true
1670
- });
1671
- if (!p__namespace.isCancel(useDefault) && useDefault) {
1672
- selectedAiFiles = savedAiFiles;
1673
- }
1674
- } else {
1675
- const aiFilesChoice = await p__namespace.multiselect({
1676
- message: "Generate AI instruction files?",
1677
- options: [
1678
- { value: "cursor-rules", label: ".cursor/rules", hint: "Cursor AI" },
1679
- {
1680
- value: "agents-md",
1681
- label: "AGENTS.md",
1682
- hint: "GitHub Copilot, general"
1683
- },
1684
- { value: "claude-md", label: "CLAUDE.md", hint: "Claude" },
1685
- {
1686
- value: "copilot-md",
1687
- label: ".github/copilot-instructions.md",
1688
- hint: "GitHub Copilot"
1689
- }
1690
- ],
1691
- required: false
1692
- });
1693
- if (!p__namespace.isCancel(aiFilesChoice) && aiFilesChoice.length > 0) {
1694
- selectedAiFiles = aiFilesChoice;
1695
- const saveChoice = await p__namespace.confirm({
1696
- message: "Save as default for future monorepos?",
1697
- initialValue: true
1698
- });
1699
- if (!p__namespace.isCancel(saveChoice) && saveChoice) {
1700
- setAiFiles(selectedAiFiles);
1701
- }
1702
- }
1703
- }
2636
+ const aiPlatforms = await promptForAiPlatforms(isNonInteractive);
1704
2637
  const projectPath = path.join(process$1.cwd(), generateOptions.name);
1705
2638
  const spinner = p__namespace.spinner();
1706
2639
  spinner.start("Creating monorepo workspace...");
@@ -1708,12 +2641,12 @@ async function handleMonorepoCreation(generateOptions) {
1708
2641
  const { files } = generateMonorepo({
1709
2642
  name: generateOptions.name,
1710
2643
  linter: generateOptions.linter ?? "oxlint",
1711
- formatter: generateOptions.formatter ?? "oxfmt",
2644
+ formatter: generateOptions.formatter ?? "prettier",
1712
2645
  packageManager,
1713
2646
  pnpmVersion: generateOptions.pnpmVersion,
1714
2647
  pnpmManageVersions: generateOptions.pnpmManageVersions,
1715
2648
  nodeVersion: generateOptions.nodeVersion,
1716
- aiFiles: selectedAiFiles.length > 0 ? selectedAiFiles : void 0
2649
+ aiPlatforms: aiPlatforms.length > 0 ? aiPlatforms : void 0
1717
2650
  });
1718
2651
  const filePaths = Object.keys(files).sort();
1719
2652
  for (const filePath of filePaths) {
@@ -1725,9 +2658,15 @@ async function handleMonorepoCreation(generateOptions) {
1725
2658
  }
1726
2659
  }
1727
2660
  spinner.stop(color__default.green.inverse(" \u2713 Monorepo workspace created! "));
1728
- const newMonorepoTooling = {
2661
+ if (isNonInteractive) {
2662
+ process.exit(0);
2663
+ }
2664
+ const newWorkspaceSettings = {
1729
2665
  linter: generateOptions.linter,
1730
- formatter: generateOptions.formatter
2666
+ formatter: generateOptions.formatter,
2667
+ packageManager,
2668
+ nodeVersion: generateOptions.nodeVersion,
2669
+ pnpmManageVersions: generateOptions.pnpmManageVersions
1731
2670
  };
1732
2671
  const scope = generateOptions.name;
1733
2672
  let addMore = true;
@@ -1735,7 +2674,7 @@ async function handleMonorepoCreation(generateOptions) {
1735
2674
  addMore = await createPackageInWorkspace(
1736
2675
  projectPath,
1737
2676
  packageManager,
1738
- newMonorepoTooling,
2677
+ newWorkspaceSettings,
1739
2678
  scope
1740
2679
  );
1741
2680
  }
@@ -1754,10 +2693,14 @@ async function handleMonorepoCreation(generateOptions) {
1754
2693
  process.exit(1);
1755
2694
  }
1756
2695
  }
1757
- async function handleStandaloneProjectCreation(generateOptions) {
2696
+ async function handleStandaloneProjectCreation(generateOptions, isNonInteractive) {
1758
2697
  const base = generateOptions.template ? index.getBaseTemplate(generateOptions.template) : "vanilla";
1759
2698
  const defaultFallbackName = base === "vanilla" ? "vanilla-app" : base === "react" ? "react-app" : "react-three-app";
1760
2699
  generateOptions.name ??= defaultFallbackName;
2700
+ const aiPlatforms = await promptForAiPlatforms(isNonInteractive);
2701
+ if (aiPlatforms.length > 0) {
2702
+ generateOptions.aiPlatforms = aiPlatforms;
2703
+ }
1761
2704
  const packageManager = generateOptions.packageManager || "pnpm";
1762
2705
  if (packageManager === "pnpm") {
1763
2706
  generateOptions.pnpmVersion = await index.getLatestPnpmVersion();
@@ -1808,7 +2751,7 @@ async function handleStandaloneProjectCreation(generateOptions) {
1808
2751
  })
1809
2752
  );
1810
2753
  }
1811
- const formatter = generateOptions.formatter ?? "oxfmt";
2754
+ const formatter = generateOptions.formatter ?? "prettier";
1812
2755
  if (formatter === "prettier") {
1813
2756
  versionPromises.push(
1814
2757
  index.getLatestNpmVersion("prettier", "3.4.2").then((v) => {
@@ -1837,6 +2780,9 @@ async function handleStandaloneProjectCreation(generateOptions) {
1837
2780
  const files = index.generate(generateOptions);
1838
2781
  await writeGeneratedFiles(projectPath, files);
1839
2782
  spinner.stop(color__default.green.inverse(" \u2713 Project created! "));
2783
+ if (isNonInteractive) {
2784
+ process.exit(0);
2785
+ }
1840
2786
  const nextSteps = isLibrary ? [
1841
2787
  `cd ${generateOptions.name}`,
1842
2788
  `${packageManager} install`,
@@ -1869,21 +2815,23 @@ async function handleInteractiveMonorepoMode(monorepoRoot) {
1869
2815
  process.exit(0);
1870
2816
  }
1871
2817
  if (choice === "add") {
1872
- const inheritedTooling = await detectWorkspaceTooling(monorepoRoot);
1873
- if (inheritedTooling.linter || inheritedTooling.formatter) {
1874
- const toolingInfo = [
1875
- inheritedTooling.linter && `linter: ${inheritedTooling.linter}`,
1876
- 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}`
1877
2825
  ].filter(Boolean).join(", ");
1878
- p__namespace.log.info(`Using workspace tooling (${toolingInfo})`);
2826
+ p__namespace.log.info(`Using workspace settings (${settingsInfo})`);
1879
2827
  }
1880
2828
  const scope = await getMonorepoScope(monorepoRoot);
1881
2829
  let addMore = true;
1882
2830
  while (addMore) {
1883
2831
  addMore = await createPackageInWorkspace(
1884
2832
  monorepoRoot,
1885
- "pnpm",
1886
- inheritedTooling,
2833
+ inheritedSettings.packageManager ?? "pnpm",
2834
+ inheritedSettings,
1887
2835
  scope
1888
2836
  );
1889
2837
  }
@@ -1910,7 +2858,7 @@ async function main() {
1910
2858
  "linter: eslint, oxlint, or biome (default: oxlint)"
1911
2859
  ).option(
1912
2860
  "--formatter <type>",
1913
- "formatter: prettier, oxfmt, or biome (default: oxfmt)"
2861
+ "formatter: prettier, oxfmt, or biome (default: prettier)"
1914
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(
1915
2863
  "--package-manager <manager>",
1916
2864
  "specify package manager (e.g. npm, yarn, pnpm)"
@@ -1932,7 +2880,7 @@ async function main() {
1932
2880
  ).option("--clear-config", "Clear saved preferences (e.g. editor choice)").option("--config-path", "Print the path to the config file").option(
1933
2881
  "--check",
1934
2882
  "Check if current directory is in a valid monorepo workspace"
1935
- ).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(
1936
2884
  "--path <directory>",
1937
2885
  "Run in specified directory instead of current working directory"
1938
2886
  ).action(async (name, options) => {
@@ -1971,6 +2919,12 @@ async function main() {
1971
2919
  case "--fix":
1972
2920
  options.fix = true;
1973
2921
  break;
2922
+ case "--update":
2923
+ options.update = true;
2924
+ break;
2925
+ case "--yes":
2926
+ options.yes = true;
2927
+ break;
1974
2928
  default:
1975
2929
  console.error(color__default.red(`Unknown option: ${name}`));
1976
2930
  process.exit(1);
@@ -1982,6 +2936,9 @@ async function main() {
1982
2936
  if (options.fix) {
1983
2937
  await handleFixCommand(options);
1984
2938
  }
2939
+ if (options.update) {
2940
+ await handleUpdateCommand(options);
2941
+ }
1985
2942
  if (options.dir && !options.workspace) {
1986
2943
  console.error(color__default.red("Error:") + " --dir requires --workspace flag");
1987
2944
  console.log(
@@ -1997,11 +2954,11 @@ async function main() {
1997
2954
  console.clear();
1998
2955
  p__namespace.intro(color__default.bgCyan(color__default.black(` create-krispya v${pkg.version} `)));
1999
2956
  const monorepoRoot = await detectMonorepoRoot();
2000
- if (monorepoRoot && Object.keys(options).length === 0) {
2957
+ if (monorepoRoot && !hasConfigOptions(options)) {
2001
2958
  await handleInteractiveMonorepoMode(monorepoRoot);
2002
2959
  }
2003
2960
  let generateOptions;
2004
- if (Object.keys(options).length > 0) {
2961
+ if (options.yes) {
2005
2962
  const template = options.template ?? "vanilla";
2006
2963
  const baseTemplate = index.getBaseTemplate(template);
2007
2964
  const defaultName = getDefaultProjectName(template);
@@ -2012,7 +2969,7 @@ async function main() {
2012
2969
  libraryBundler: projectType === "library" ? options.bundler ?? "unbuild" : void 0,
2013
2970
  template,
2014
2971
  linter: options.linter ?? "oxlint",
2015
- formatter: options.formatter ?? "oxfmt",
2972
+ formatter: options.formatter ?? "prettier",
2016
2973
  ...baseTemplate === "r3f" && {
2017
2974
  drei: options.drei ? {} : void 0,
2018
2975
  handle: options.handle ? {} : void 0,
@@ -2032,12 +2989,35 @@ async function main() {
2032
2989
  nodeVersion: options.nodeVersion ?? "latest"
2033
2990
  };
2034
2991
  } else {
2035
- 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);
2036
3015
  }
3016
+ const isNonInteractive = options.yes ?? false;
2037
3017
  if (generateOptions.projectType === "monorepo") {
2038
- await handleMonorepoCreation(generateOptions);
3018
+ await handleMonorepoCreation(generateOptions, isNonInteractive);
2039
3019
  } else {
2040
- await handleStandaloneProjectCreation(generateOptions);
3020
+ await handleStandaloneProjectCreation(generateOptions, isNonInteractive);
2041
3021
  }
2042
3022
  });
2043
3023
  await program.parseAsync();