create-krispya 0.9.0 → 0.11.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
@@ -4,31 +4,31 @@
4
4
  const p = require('@clack/prompts');
5
5
  const color = require('chalk');
6
6
  const commander = require('commander');
7
- const node_fs = require('node:fs');
8
7
  const promises$1 = require('node:fs/promises');
9
8
  const node_module = require('node:module');
10
9
  const node_path = require('node:path');
11
10
  const node_process = require('node:process');
12
11
  const undici = require('undici');
13
- const index = require('./chunks/index.cjs');
12
+ const workspace = require('./shared/create-krispya.8KaGuRpu.cjs');
14
13
  const Conf = require('conf');
15
14
  const promises = require('fs/promises');
16
15
  const fs = require('fs');
17
16
  const path = require('path');
17
+ const node_fs = require('node:fs');
18
18
 
19
19
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
20
20
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
21
21
 
22
22
  function _interopNamespaceCompat(e) {
23
- if (e && typeof e === 'object' && 'default' in e) return e;
24
- const n = Object.create(null);
25
- if (e) {
26
- for (const k in e) {
27
- n[k] = e[k];
28
- }
23
+ if (e && typeof e === 'object' && 'default' in e) return e;
24
+ const n = Object.create(null);
25
+ if (e) {
26
+ for (const k in e) {
27
+ n[k] = e[k];
29
28
  }
30
- n.default = e;
31
- return n;
29
+ }
30
+ n.default = e;
31
+ return n;
32
32
  }
33
33
 
34
34
  const p__namespace = /*#__PURE__*/_interopNamespaceCompat(p);
@@ -49,13 +49,13 @@ function formatConfigSummary(options, inherited) {
49
49
  return lang === "typescript" ? "TypeScript" : lang === "javascript" ? "JavaScript" : lang;
50
50
  };
51
51
  const projectType = options.projectType ?? "app";
52
- const baseTemplate = options.template ? index.getBaseTemplate(options.template) : "vanilla";
52
+ const baseTemplate = options.template ? workspace.getBaseTemplate(options.template) : "vanilla";
53
53
  if (baseTemplate === "react") {
54
54
  lines.push(formatRow("Framework", "React"));
55
55
  } else if (baseTemplate === "r3f") {
56
56
  lines.push(formatRow("Framework", "React Three Fiber"));
57
57
  }
58
- const language = options.template ? index.getLanguageFromTemplate(options.template) : "typescript";
58
+ const language = options.template ? workspace.getLanguageFromTemplate(options.template) : "typescript";
59
59
  lines.push(formatRow("Language", formatLanguage(language)));
60
60
  if (projectType === "library") {
61
61
  lines.push(formatRow("Bundler", options.libraryBundler ?? "unbuild"));
@@ -66,15 +66,15 @@ function formatConfigSummary(options, inherited) {
66
66
  lines.push(
67
67
  formatRow(
68
68
  "Engine",
69
- `${index.getEngineName(options.engine)}@${options.engine?.version ?? "latest"}`,
69
+ `${workspace.getEngineName(options.engine)}@${options.engine?.version ?? "latest"}`,
70
70
  engineInherited
71
71
  )
72
72
  );
73
73
  const pmInherited = inherited?.packageManager !== void 0;
74
74
  lines.push(
75
- formatRow("Package manager", index.getPackageManagerName(options.packageManager), pmInherited)
75
+ formatRow("Package manager", workspace.getPackageManagerName(options.packageManager), pmInherited)
76
76
  );
77
- if (index.getPackageManagerName(options.packageManager) === "pnpm") {
77
+ if (workspace.getPackageManagerName(options.packageManager) === "pnpm") {
78
78
  const versionManaged = options.pnpmManageVersions ? "yes" : "no";
79
79
  const pnpmVersionInherited = inherited?.pnpmManageVersions !== void 0;
80
80
  lines.push(formatRow("\u21B3 Version managed", versionManaged, pnpmVersionInherited, ""));
@@ -90,10 +90,11 @@ function formatConfigSummary(options, inherited) {
90
90
  const testing = options.testing ?? (projectType === "library" ? "vitest" : "none");
91
91
  lines.push(formatRow("Testing", testing));
92
92
  if (!inherited) {
93
+ lines.push(formatRow("Editor config", options.ide === "none" ? "generic" : "generic + vscode"));
93
94
  const configStrategy = options.configStrategy ?? "stealth";
94
95
  lines.push(formatRow("Config strategy", configStrategy));
95
96
  }
96
- if (options.template && index.getBaseTemplate(options.template) === "r3f") {
97
+ if (options.template && workspace.getBaseTemplate(options.template) === "r3f") {
97
98
  const integrationNames = [
98
99
  options.drei && "drei",
99
100
  options.handle && "handle",
@@ -109,7 +110,7 @@ function formatConfigSummary(options, inherited) {
109
110
  options.viverse && "viverse"
110
111
  ].filter(Boolean);
111
112
  lines.push("");
112
- lines.push(color__default.dim("Integrations"));
113
+ lines.push(color__default.dim("Features"));
113
114
  for (let i = 0; i < integrationNames.length; i += 2) {
114
115
  const left = `${color__default.green("\u25CF")} ${integrationNames[i]}`;
115
116
  const right = integrationNames[i + 1] ? `${color__default.green("\u25CF")} ${integrationNames[i + 1]}` : "";
@@ -129,7 +130,7 @@ function formatMonorepoConfigSummary(options) {
129
130
  return `${indent}${label} ${dots} ${value}`;
130
131
  };
131
132
  lines.push(
132
- formatRow("Engine", `${index.getEngineName(options.engine)}@${options.engine.version ?? "latest"}`)
133
+ formatRow("Engine", `${workspace.getEngineName(options.engine)}@${options.engine.version ?? "latest"}`)
133
134
  );
134
135
  lines.push(formatRow("Package manager", options.packageManager));
135
136
  if (options.packageManager === "pnpm") {
@@ -138,6 +139,7 @@ function formatMonorepoConfigSummary(options) {
138
139
  }
139
140
  lines.push(formatRow("Linter", options.linter));
140
141
  lines.push(formatRow("Formatter", options.formatter));
142
+ lines.push(formatRow("Editor config", options.ide === "none" ? "generic" : "generic + vscode"));
141
143
  return lines.join("\n");
142
144
  }
143
145
 
@@ -156,12 +158,58 @@ function clearConfig() {
156
158
  function getConfigPath() {
157
159
  return config.path;
158
160
  }
159
- function getCustomTemplates() {
160
- return config.get("customTemplates") ?? {};
161
- }
162
161
 
163
- function getDefaultOptions(template, name, projectType = "app", libraryBundler, integrations, inheritedSettings) {
164
- const baseTemplate = index.getBaseTemplate(template);
162
+ const R3F_INTEGRATION_OPTIONS = [
163
+ { value: "drei", label: "Drei" },
164
+ { value: "handle", label: "Handle" },
165
+ { value: "leva", label: "Leva" },
166
+ { value: "postprocessing", label: "Postprocessing" },
167
+ { value: "rapier", label: "Rapier" },
168
+ { value: "xr", label: "XR" },
169
+ { value: "uikit", label: "UIKit" },
170
+ { value: "offscreen", label: "Offscreen" },
171
+ { value: "zustand", label: "Zustand" },
172
+ { value: "koota", label: "Koota" },
173
+ { value: "triplex", label: "Triplex" },
174
+ { value: "viverse", label: "Viverse" }
175
+ ];
176
+ function getR3fIntegrationFlags(features) {
177
+ if (!features) return {};
178
+ return {
179
+ drei: features.includes("drei") ? {} : void 0,
180
+ handle: features.includes("handle") ? {} : void 0,
181
+ leva: features.includes("leva") ? {} : void 0,
182
+ postprocessing: features.includes("postprocessing") ? {} : void 0,
183
+ rapier: features.includes("rapier") ? {} : void 0,
184
+ xr: features.includes("xr") ? {} : void 0,
185
+ uikit: features.includes("uikit") ? {} : void 0,
186
+ offscreen: features.includes("offscreen") ? {} : void 0,
187
+ zustand: features.includes("zustand") ? {} : void 0,
188
+ koota: features.includes("koota") ? {} : void 0,
189
+ triplex: features.includes("triplex") ? {} : void 0,
190
+ viverse: features.includes("viverse") ? {} : void 0
191
+ };
192
+ }
193
+ function getInitialR3fIntegrations(presets) {
194
+ if (!presets) return ["drei"];
195
+ const initialValues = R3F_INTEGRATION_OPTIONS.filter(({ value }) => presets[value]).map(
196
+ ({ value }) => value
197
+ );
198
+ return initialValues.length > 0 ? initialValues : ["drei"];
199
+ }
200
+ async function promptForProceed() {
201
+ const proceed = await p__namespace.confirm({
202
+ message: "Proceed with these settings?",
203
+ initialValue: true
204
+ });
205
+ if (p__namespace.isCancel(proceed)) {
206
+ p__namespace.cancel("Operation cancelled.");
207
+ process.exit(0);
208
+ }
209
+ return proceed;
210
+ }
211
+ function getDefaultOptions(template, name, projectType = "app", libraryBundler, features, inheritedSettings) {
212
+ const baseTemplate = workspace.getBaseTemplate(template);
165
213
  const base = {
166
214
  name,
167
215
  template,
@@ -174,71 +222,30 @@ function getDefaultOptions(template, name, projectType = "app", libraryBundler,
174
222
  formatter: inheritedSettings?.formatter ?? "prettier",
175
223
  // Libraries get vitest by default, apps don't
176
224
  testing: projectType === "library" ? "vitest" : "none",
177
- configStrategy: getConfigStrategy()
225
+ configStrategy: getConfigStrategy(),
226
+ ide: "vscode"
227
+ };
228
+ return {
229
+ ...base,
230
+ ...baseTemplate === "r3f" ? getR3fIntegrationFlags(features) : {}
178
231
  };
179
- if (baseTemplate === "r3f" && integrations) {
180
- return {
181
- ...base,
182
- drei: integrations.includes("drei") ? {} : void 0,
183
- handle: integrations.includes("handle") ? {} : void 0,
184
- leva: integrations.includes("leva") ? {} : void 0,
185
- postprocessing: integrations.includes("postprocessing") ? {} : void 0,
186
- rapier: integrations.includes("rapier") ? {} : void 0,
187
- xr: integrations.includes("xr") ? {} : void 0,
188
- uikit: integrations.includes("uikit") ? {} : void 0,
189
- offscreen: integrations.includes("offscreen") ? {} : void 0,
190
- zustand: integrations.includes("zustand") ? {} : void 0,
191
- koota: integrations.includes("koota") ? {} : void 0,
192
- triplex: integrations.includes("triplex") ? {} : void 0,
193
- viverse: integrations.includes("viverse") ? {} : void 0
194
- };
195
- }
196
- return base;
197
232
  }
198
233
  function getDefaultProjectName(template) {
199
- const base = index.getBaseTemplate(template);
234
+ const base = workspace.getBaseTemplate(template);
200
235
  switch (base) {
201
236
  case "vanilla":
202
- return `vanilla-${index.generateRandomName()}`;
237
+ return `vanilla-${workspace.generateRandomName()}`;
203
238
  case "react":
204
- return `react-${index.generateRandomName()}`;
239
+ return `react-${workspace.generateRandomName()}`;
205
240
  case "r3f":
206
- return `react-three-${index.generateRandomName()}`;
241
+ return `react-three-${workspace.generateRandomName()}`;
207
242
  }
208
243
  }
209
244
  async function promptForR3fIntegrations(presets) {
210
- const initialValues = [];
211
- if (presets) {
212
- if (presets.drei) initialValues.push("drei");
213
- if (presets.handle) initialValues.push("handle");
214
- if (presets.leva) initialValues.push("leva");
215
- if (presets.postprocessing) initialValues.push("postprocessing");
216
- if (presets.rapier) initialValues.push("rapier");
217
- if (presets.xr) initialValues.push("xr");
218
- if (presets.uikit) initialValues.push("uikit");
219
- if (presets.offscreen) initialValues.push("offscreen");
220
- if (presets.zustand) initialValues.push("zustand");
221
- if (presets.koota) initialValues.push("koota");
222
- if (presets.triplex) initialValues.push("triplex");
223
- if (presets.viverse) initialValues.push("viverse");
224
- }
225
245
  const selected = await p__namespace.multiselect({
226
- message: "R3F integrations",
227
- options: [
228
- { value: "drei", label: "Drei" },
229
- { value: "handle", label: "Handle" },
230
- { value: "leva", label: "Leva" },
231
- { value: "postprocessing", label: "Postprocessing" },
232
- { value: "rapier", label: "Rapier" },
233
- { value: "xr", label: "XR" },
234
- { value: "uikit", label: "UIKit" },
235
- { value: "offscreen", label: "Offscreen" },
236
- { value: "zustand", label: "Zustand" },
237
- { value: "koota", label: "Koota" },
238
- { value: "triplex", label: "Triplex" },
239
- { value: "viverse", label: "Viverse" }
240
- ],
241
- initialValues: initialValues.length > 0 ? initialValues : ["drei"],
246
+ message: "R3F features",
247
+ options: [...R3F_INTEGRATION_OPTIONS],
248
+ initialValues: getInitialR3fIntegrations(presets),
242
249
  required: false
243
250
  });
244
251
  if (p__namespace.isCancel(selected)) {
@@ -247,7 +254,7 @@ async function promptForR3fIntegrations(presets) {
247
254
  }
248
255
  return selected;
249
256
  }
250
- async function promptForCustomization(template, name, projectType, integrations, inheritedSettings, presets) {
257
+ async function promptForCustomization(template, name, projectType, features, inheritedSettings, presets) {
251
258
  let libraryBundler;
252
259
  if (projectType === "library") {
253
260
  const bundler = await p__namespace.select({
@@ -382,7 +389,19 @@ async function promptForCustomization(template, name, projectType, integrations,
382
389
  p__namespace.cancel("Operation cancelled.");
383
390
  process.exit(0);
384
391
  }
385
- const baseTemplate = index.getBaseTemplate(template);
392
+ const ideChoice = await p__namespace.select({
393
+ message: "IDE config",
394
+ options: [
395
+ { value: "vscode", label: "vscode" },
396
+ { value: "none", label: "None" }
397
+ ],
398
+ initialValue: presets?.ide ?? "vscode"
399
+ });
400
+ if (p__namespace.isCancel(ideChoice)) {
401
+ p__namespace.cancel("Operation cancelled.");
402
+ process.exit(0);
403
+ }
404
+ const baseTemplate = workspace.getBaseTemplate(template);
386
405
  const finalTemplate = language === "javascript" ? `${baseTemplate}-js` : baseTemplate;
387
406
  const base = {
388
407
  name,
@@ -395,26 +414,13 @@ async function promptForCustomization(template, name, projectType, integrations,
395
414
  linter,
396
415
  formatter,
397
416
  testing,
398
- configStrategy: configStrategyChoice
417
+ configStrategy: configStrategyChoice,
418
+ ide: ideChoice
419
+ };
420
+ return {
421
+ ...base,
422
+ ...baseTemplate === "r3f" ? getR3fIntegrationFlags(features) : {}
399
423
  };
400
- if (baseTemplate === "r3f" && integrations) {
401
- return {
402
- ...base,
403
- drei: integrations.includes("drei") ? {} : void 0,
404
- handle: integrations.includes("handle") ? {} : void 0,
405
- leva: integrations.includes("leva") ? {} : void 0,
406
- postprocessing: integrations.includes("postprocessing") ? {} : void 0,
407
- rapier: integrations.includes("rapier") ? {} : void 0,
408
- xr: integrations.includes("xr") ? {} : void 0,
409
- uikit: integrations.includes("uikit") ? {} : void 0,
410
- offscreen: integrations.includes("offscreen") ? {} : void 0,
411
- zustand: integrations.includes("zustand") ? {} : void 0,
412
- koota: integrations.includes("koota") ? {} : void 0,
413
- triplex: integrations.includes("triplex") ? {} : void 0,
414
- viverse: integrations.includes("viverse") ? {} : void 0
415
- };
416
- }
417
- return base;
418
424
  }
419
425
  async function promptForInitialPackage() {
420
426
  const choice = await p__namespace.select({
@@ -440,7 +446,8 @@ function getDefaultMonorepoOptions(name) {
440
446
  pnpmManageVersions: true,
441
447
  engine: { name: "node", version: "latest" },
442
448
  linter: "oxlint",
443
- formatter: "prettier"
449
+ formatter: "prettier",
450
+ ide: "vscode"
444
451
  };
445
452
  }
446
453
  async function promptForMonorepoCustomization(name, presets) {
@@ -493,6 +500,18 @@ async function promptForMonorepoCustomization(name, presets) {
493
500
  p__namespace.cancel("Operation cancelled.");
494
501
  process.exit(0);
495
502
  }
503
+ const ide = await p__namespace.select({
504
+ message: "IDE config",
505
+ options: [
506
+ { value: "vscode", label: "vscode" },
507
+ { value: "none", label: "None" }
508
+ ],
509
+ initialValue: presets?.ide ?? "vscode"
510
+ });
511
+ if (p__namespace.isCancel(ide)) {
512
+ p__namespace.cancel("Operation cancelled.");
513
+ process.exit(0);
514
+ }
496
515
  return {
497
516
  name,
498
517
  projectType: "monorepo",
@@ -500,7 +519,8 @@ async function promptForMonorepoCustomization(name, presets) {
500
519
  packageManager: { name: "pnpm" },
501
520
  pnpmManageVersions: managePnpm,
502
521
  linter,
503
- formatter
522
+ formatter,
523
+ ide
504
524
  };
505
525
  }
506
526
  async function promptForMonorepo(workspaceName, presets) {
@@ -508,6 +528,7 @@ async function promptForMonorepo(workspaceName, presets) {
508
528
  if (presets) {
509
529
  if (presets.linter) defaultOptions.linter = presets.linter;
510
530
  if (presets.formatter) defaultOptions.formatter = presets.formatter;
531
+ if (presets.ide) defaultOptions.ide = presets.ide;
511
532
  if (presets.engine) defaultOptions.engine = presets.engine;
512
533
  if (presets.pnpmManageVersions !== void 0)
513
534
  defaultOptions.pnpmManageVersions = presets.pnpmManageVersions;
@@ -516,26 +537,15 @@ async function promptForMonorepo(workspaceName, presets) {
516
537
  formatMonorepoConfigSummary({
517
538
  name: defaultOptions.name,
518
539
  engine: defaultOptions.engine ?? { name: "node", version: "latest" },
519
- packageManager: index.getPackageManagerName(defaultOptions.packageManager),
540
+ packageManager: workspace.getPackageManagerName(defaultOptions.packageManager),
520
541
  pnpmManageVersions: defaultOptions.pnpmManageVersions,
521
542
  linter: defaultOptions.linter ?? "oxlint",
522
- formatter: defaultOptions.formatter ?? "prettier"
543
+ formatter: defaultOptions.formatter ?? "prettier",
544
+ ide: defaultOptions.ide ?? "vscode"
523
545
  }),
524
546
  "Workspace Configuration"
525
547
  );
526
- const proceed = await p__namespace.select({
527
- message: "Proceed with these settings?",
528
- options: [
529
- { value: "continue", label: "Yes, continue" },
530
- { value: "customize", label: "No, customize settings" }
531
- ],
532
- initialValue: "continue"
533
- });
534
- if (p__namespace.isCancel(proceed)) {
535
- p__namespace.cancel("Operation cancelled.");
536
- process.exit(0);
537
- }
538
- if (proceed === "continue") {
548
+ if (await promptForProceed()) {
539
549
  return defaultOptions;
540
550
  }
541
551
  return promptForMonorepoCustomization(workspaceName, presets);
@@ -544,9 +554,9 @@ async function promptForOptions(name, presets) {
544
554
  let projectName = name;
545
555
  if (!projectName) {
546
556
  const nameResult = await p__namespace.text({
547
- message: "What is your project named?",
548
- placeholder: index.generateRandomName(),
549
- defaultValue: index.generateRandomName(),
557
+ message: "Project name:",
558
+ placeholder: workspace.generateRandomName(),
559
+ defaultValue: workspace.generateRandomName(),
550
560
  validate: (value) => {
551
561
  if (!value.length) return "Project name is required";
552
562
  }
@@ -558,7 +568,7 @@ async function promptForOptions(name, presets) {
558
568
  projectName = nameResult;
559
569
  }
560
570
  const projectType = await p__namespace.select({
561
- message: "Project type",
571
+ message: "Project type:",
562
572
  options: [
563
573
  { value: "app", label: "Application" },
564
574
  { value: "library", label: "Library" },
@@ -575,41 +585,6 @@ async function promptForOptions(name, presets) {
575
585
  }
576
586
  return promptForPackageOptions(projectName, projectType, void 0, presets);
577
587
  }
578
- function customTemplateToOptions(customTemplate, name, projectType, inheritedSettings) {
579
- const baseTemplate = customTemplate.baseTemplate;
580
- const template = baseTemplate;
581
- const base = {
582
- name,
583
- template,
584
- projectType,
585
- packageManager: inheritedSettings?.packageManager ?? { name: "pnpm" },
586
- pnpmManageVersions: inheritedSettings?.pnpmManageVersions ?? true,
587
- engine: inheritedSettings?.engine ?? { name: "node", version: "latest" },
588
- linter: inheritedSettings?.linter ?? customTemplate.linter,
589
- formatter: inheritedSettings?.formatter ?? customTemplate.formatter,
590
- testing: customTemplate.testing,
591
- configStrategy: customTemplate.configStrategy ?? getConfigStrategy()
592
- };
593
- if (baseTemplate === "r3f" && customTemplate.integrations) {
594
- const integrations = customTemplate.integrations;
595
- return {
596
- ...base,
597
- drei: integrations.includes("drei") ? {} : void 0,
598
- handle: integrations.includes("handle") ? {} : void 0,
599
- leva: integrations.includes("leva") ? {} : void 0,
600
- postprocessing: integrations.includes("postprocessing") ? {} : void 0,
601
- rapier: integrations.includes("rapier") ? {} : void 0,
602
- xr: integrations.includes("xr") ? {} : void 0,
603
- uikit: integrations.includes("uikit") ? {} : void 0,
604
- offscreen: integrations.includes("offscreen") ? {} : void 0,
605
- zustand: integrations.includes("zustand") ? {} : void 0,
606
- koota: integrations.includes("koota") ? {} : void 0,
607
- triplex: integrations.includes("triplex") ? {} : void 0,
608
- viverse: integrations.includes("viverse") ? {} : void 0
609
- };
610
- }
611
- return base;
612
- }
613
588
  function presetsToInheritedSettings(presets) {
614
589
  if (!presets) return void 0;
615
590
  return {
@@ -621,798 +596,138 @@ function presetsToInheritedSettings(presets) {
621
596
  };
622
597
  }
623
598
  async function promptForPackageOptions(projectName, projectType, inheritedSettings, presets) {
624
- const builtInOptions = [
625
- { value: "vanilla", label: "Vanilla" },
626
- { value: "react", label: "React", hint: "experimental" },
627
- { value: "r3f", label: "React Three Fiber", hint: "experimental" }
628
- ];
629
- const customTemplates = getCustomTemplates();
630
- const customOptions = Object.keys(customTemplates).map((name) => ({
631
- value: `custom:${name}`,
632
- label: name,
633
- hint: "saved template"
634
- }));
635
- const allOptions = [...builtInOptions, ...customOptions];
636
599
  const templateSelection = await p__namespace.select({
637
- message: "Select a template",
638
- options: allOptions,
639
- initialValue: presets?.template ?? "vanilla"
600
+ message: "Select a template:",
601
+ options: [
602
+ { value: "vanilla", label: color__default.yellow("Vanilla") },
603
+ { value: "react", label: color__default.cyan("React"), hint: "experimental" },
604
+ { value: "r3f", label: color__default.magenta("React Three Fiber"), hint: "experimental" }
605
+ ],
606
+ initialValue: presets?.template ? workspace.getBaseTemplate(presets.template) : "vanilla"
640
607
  });
641
608
  if (p__namespace.isCancel(templateSelection)) {
642
609
  p__namespace.cancel("Operation cancelled.");
643
610
  process.exit(0);
644
611
  }
645
- const selection = templateSelection;
646
- if (selection.startsWith("custom:")) {
647
- const customName = selection.slice(7);
648
- const customTemplate = customTemplates[customName];
649
- const defaultOptions2 = customTemplateToOptions(
650
- customTemplate,
651
- projectName,
652
- projectType,
653
- inheritedSettings
654
- );
655
- const configTitle2 = inheritedSettings ? `Template: ${customName} (using workspace settings)` : `Template: ${customName}`;
656
- p__namespace.note(formatConfigSummary(defaultOptions2, inheritedSettings), configTitle2);
657
- const proceed2 = await p__namespace.select({
658
- message: "Proceed with these settings?",
659
- options: [
660
- { value: "continue", label: "Yes, continue" },
661
- { value: "customize", label: "No, customize settings" }
662
- ],
663
- initialValue: "continue"
664
- });
665
- if (p__namespace.isCancel(proceed2)) {
666
- p__namespace.cancel("Operation cancelled.");
667
- process.exit(0);
668
- }
669
- if (proceed2 === "continue") {
670
- return defaultOptions2;
671
- }
672
- return promptForCustomization(
673
- customTemplate.baseTemplate,
674
- projectName,
675
- projectType,
676
- customTemplate.integrations,
677
- inheritedSettings
678
- );
679
- }
680
- const template = selection;
681
- const baseTemplate = index.getBaseTemplate(template);
682
- let integrations;
612
+ const template = templateSelection;
613
+ const baseTemplate = workspace.getBaseTemplate(template);
614
+ let features;
683
615
  if (baseTemplate === "r3f") {
684
- integrations = await promptForR3fIntegrations(presets);
616
+ features = await promptForR3fIntegrations(presets);
685
617
  }
686
618
  const defaultOptions = getDefaultOptions(
687
619
  template,
688
620
  projectName,
689
621
  projectType,
690
622
  presets?.bundler,
691
- integrations,
623
+ features,
692
624
  inheritedSettings ?? presetsToInheritedSettings(presets)
693
625
  );
626
+ if (presets?.ide && !inheritedSettings) {
627
+ defaultOptions.ide = presets.ide;
628
+ }
694
629
  const configTitle = inheritedSettings ? "Template Configuration (using workspace settings)" : "Template Configuration";
695
630
  p__namespace.note(formatConfigSummary(defaultOptions, inheritedSettings), configTitle);
696
- const proceed = await p__namespace.select({
697
- message: "Proceed with these settings?",
698
- options: [
699
- { value: "continue", label: "Yes, continue" },
700
- { value: "customize", label: "No, customize settings" }
701
- ],
702
- initialValue: "continue"
703
- });
704
- if (p__namespace.isCancel(proceed)) {
705
- p__namespace.cancel("Operation cancelled.");
706
- process.exit(0);
707
- }
708
- if (proceed === "continue") {
631
+ if (await promptForProceed()) {
709
632
  return defaultOptions;
710
633
  }
711
634
  return promptForCustomization(
712
635
  template,
713
636
  projectName,
714
637
  projectType,
715
- integrations,
638
+ features,
716
639
  inheritedSettings,
717
640
  presets
718
641
  );
719
642
  }
720
643
 
721
- async function detectCurrentConfig(root, isMonorepo = true) {
722
- let name = root.split(/[/\\]/).pop() ?? "workspace";
723
- let packageManager = "pnpm";
724
- try {
725
- const pkgPath = path.join(root, "package.json");
726
- const content = await promises.readFile(pkgPath, "utf-8");
727
- const pkgJson = JSON.parse(content);
728
- if (pkgJson.name) {
729
- name = pkgJson.name.replace(/^@/, "").replace(/\/.*$/, "");
644
+ async function promptForAiAgentPlatforms(isNonInteractive) {
645
+ const savedPlatforms = getAiPlatforms();
646
+ if (isNonInteractive) {
647
+ return savedPlatforms ?? workspace.ALL_AI_PLATFORMS;
648
+ }
649
+ if (savedPlatforms && savedPlatforms.length > 0) {
650
+ const savedLabels = savedPlatforms.map((platform) => workspace.AI_PLATFORM_LABELS[platform]).join(", ");
651
+ const useDefault = await p__namespace.confirm({
652
+ message: `Add AI rules? ${color__default.dim(`(${savedLabels})`)}`,
653
+ initialValue: true
654
+ });
655
+ if (p__namespace.isCancel(useDefault)) {
656
+ return [];
730
657
  }
731
- if (pkgJson.packageManager) {
732
- packageManager = pkgJson.packageManager.split("@")[0] ?? packageManager;
658
+ if (useDefault) {
659
+ return savedPlatforms;
733
660
  }
734
- } catch {
735
661
  }
736
- const tooling = await index.detectTooling(root);
737
- const configStrategy = isMonorepo ? void 0 : await detectStandaloneConfigStrategy(root);
738
- return {
739
- name,
740
- linter: tooling.linter ?? "oxlint",
741
- formatter: tooling.formatter ?? "prettier",
742
- packageManager,
743
- isMonorepo,
744
- configStrategy
745
- };
746
- }
747
- async function detectStandaloneConfigStrategy(root) {
748
- const hasStealthConfig = await Promise.all([
749
- fileExists$1(path.join(root, ".config/tsconfig.app.json")),
750
- fileExists$1(path.join(root, ".config/tsconfig.node.json")),
751
- fileExists$1(path.join(root, ".config/prettier.json")),
752
- fileExists$1(path.join(root, ".config/oxlint.json"))
753
- ]).then((matches) => matches.some(Boolean));
754
- return hasStealthConfig ? "stealth" : "root";
755
- }
756
- async function generateExpectedFiles(config) {
757
- const { name, linter, formatter, packageManager, isMonorepo, configStrategy } = config;
758
- const versions = linter === "biome" || formatter === "biome" ? await index.resolveMonorepoRootPackageVersions({ linter, formatter }) : {};
759
- const aiFilesMap = {};
760
- index.generateAiFiles(aiFilesMap, {
761
- name,
762
- packageManager,
763
- linter,
764
- formatter,
765
- isMonorepo,
766
- configStrategy,
767
- platforms: index.ALL_AI_PLATFORMS
662
+ const selected = await p__namespace.multiselect({
663
+ message: "Add AI rules?",
664
+ options: workspace.ALL_AI_PLATFORMS.map((platform) => ({
665
+ value: platform,
666
+ label: workspace.AI_PLATFORM_LABELS[platform],
667
+ hint: workspace.AI_PLATFORM_HINTS[platform]
668
+ })),
669
+ initialValues: ["agents"],
670
+ required: false
768
671
  });
769
- const vscodeFiles = {};
770
- index.generateVscodeFiles(vscodeFiles, linter, formatter);
771
- const configPackages = {};
772
- if (isMonorepo) {
773
- index.generateTypescriptConfigPackage(configPackages);
774
- if (linter === "oxlint") {
775
- index.generateOxlintConfigPackage(configPackages);
776
- } else if (linter === "eslint") {
777
- index.generateEslintConfigPackage(configPackages);
778
- }
779
- if (formatter === "oxfmt") {
780
- index.generateOxfmtConfigPackage(configPackages);
781
- } else if (formatter === "prettier") {
782
- index.generatePrettierConfigPackage(configPackages);
672
+ if (p__namespace.isCancel(selected)) {
673
+ return [];
674
+ }
675
+ return selected;
676
+ }
677
+
678
+ async function checkAnyExists(paths) {
679
+ for (const path of paths) {
680
+ try {
681
+ await promises.access(path, promises.constants.F_OK);
682
+ return true;
683
+ } catch {
783
684
  }
784
685
  }
785
- const workspaceConfig = {};
786
- const rootConfig = {};
787
- rootConfig[".gitignore"] = index.generateGitignore(isMonorepo ? "workspace-root" : "standalone");
788
- rootConfig[".gitattributes"] = {
789
- type: "text",
790
- content: `* text=auto eol=lf
791
- *.{cmd,[cC][mM][dD]} text eol=crlf
792
- *.{bat,[bB][aA][tT]} text eol=crlf
793
- `
794
- };
795
- if (linter === "biome" || formatter === "biome") {
796
- const biomeVersion = index.getResolvedPackageVersion(versions, "@biomejs/biome");
797
- const biomeConfig = {
798
- $schema: `https://biomejs.dev/schemas/${biomeVersion}/schema.json`,
799
- vcs: {
800
- enabled: true,
801
- clientKind: "git",
802
- useIgnoreFile: true
803
- },
804
- linter: {
805
- enabled: linter === "biome",
806
- rules: {
807
- recommended: true
808
- }
809
- },
810
- formatter: {
811
- enabled: formatter === "biome"
812
- }
813
- };
814
- rootConfig["biome.json"] = {
815
- type: "text",
816
- content: JSON.stringify(biomeConfig, null, 2)
817
- };
686
+ return false;
687
+ }
688
+ async function validateWorkspace(monorepoRoot) {
689
+ const errors = [];
690
+ const tsConfigPath = path.join(monorepoRoot, ".config/typescript/package.json");
691
+ try {
692
+ await promises.access(tsConfigPath, promises.constants.F_OK);
693
+ } catch {
694
+ errors.push("Missing .config/typescript package");
818
695
  }
819
- return {
820
- "ai-files": aiFilesMap,
821
- vscode: vscodeFiles,
822
- "config-packages": configPackages,
823
- "workspace-config": workspaceConfig,
824
- "root-config": rootConfig
825
- };
696
+ const linterPaths = [
697
+ path.join(monorepoRoot, ".config/oxlint/package.json"),
698
+ path.join(monorepoRoot, ".config/eslint/package.json"),
699
+ path.join(monorepoRoot, "eslint.config.js"),
700
+ path.join(monorepoRoot, "biome.json")
701
+ ];
702
+ const hasLinter = await checkAnyExists(linterPaths);
703
+ if (!hasLinter) {
704
+ errors.push(
705
+ "Missing linter config (.config/oxlint, .config/eslint, eslint.config.js, or biome.json)"
706
+ );
707
+ }
708
+ const formatterPaths = [
709
+ path.join(monorepoRoot, ".config/oxfmt/package.json"),
710
+ path.join(monorepoRoot, ".config/prettier/package.json"),
711
+ path.join(monorepoRoot, ".prettierrc.json"),
712
+ path.join(monorepoRoot, "biome.json")
713
+ ];
714
+ const hasFormatter = await checkAnyExists(formatterPaths);
715
+ if (!hasFormatter) {
716
+ errors.push(
717
+ "Missing formatter config (.config/oxfmt, .config/prettier, .prettierrc.json, or biome.json)"
718
+ );
719
+ }
720
+ return { valid: errors.length === 0, errors };
826
721
  }
722
+
827
723
  async function fileExists$1(path) {
828
724
  try {
829
- await promises.access(path, fs.constants.F_OK);
725
+ await promises$1.access(path, node_fs.constants.F_OK);
830
726
  return true;
831
727
  } catch {
832
728
  return false;
833
729
  }
834
730
  }
835
- async function compareWithDisk(expected, root) {
836
- const categoryLabels = {
837
- "ai-files": "AI Files",
838
- vscode: "VS Code",
839
- "config-packages": "Config Packages",
840
- "workspace-config": "Workspace Config",
841
- "root-config": "Root Config"
842
- };
843
- const categories = [];
844
- for (const [category, files] of Object.entries(expected)) {
845
- const changes = [];
846
- for (const [filePath, file] of Object.entries(files)) {
847
- if (file.type !== "text") continue;
848
- const fullPath = path.join(root, filePath);
849
- const newContent = file.content;
850
- if (await fileExists$1(fullPath)) {
851
- const currentContent = await promises.readFile(fullPath, "utf-8");
852
- if (currentContent === newContent) {
853
- changes.push({
854
- path: filePath,
855
- status: "unchanged",
856
- currentContent,
857
- newContent
858
- });
859
- } else {
860
- changes.push({
861
- path: filePath,
862
- status: "modified",
863
- currentContent,
864
- newContent
865
- });
866
- }
867
- } else {
868
- changes.push({
869
- path: filePath,
870
- status: "added",
871
- newContent
872
- });
873
- }
874
- }
875
- if (changes.length === 0) continue;
876
- const hasUserModifications = changes.some((c) => c.status === "modified");
877
- categories.push({
878
- category,
879
- label: categoryLabels[category],
880
- changes,
881
- hasUserModifications
882
- });
883
- }
884
- return categories;
885
- }
886
- async function getWorkspaceConfigUpdates(root) {
887
- const workspacePath = path.join(root, "pnpm-workspace.yaml");
888
- const changes = [];
889
- let currentContent = "";
890
- let exists = false;
891
- try {
892
- currentContent = await promises.readFile(workspacePath, "utf-8");
893
- exists = true;
894
- } catch {
895
- }
896
- if (!exists) {
897
- const newContent = `manage-package-manager-versions: true
898
-
899
- packages:
900
- - ".config/*"
901
- - "apps/*"
902
- - "packages/*"
903
-
904
- onlyBuiltDependencies:
905
- - esbuild
906
- `;
907
- changes.push({
908
- path: "pnpm-workspace.yaml",
909
- status: "added",
910
- newContent
911
- });
912
- return changes;
913
- }
914
- let updatedContent = currentContent;
915
- let needsUpdate = false;
916
- if (!currentContent.includes("manage-package-manager-versions")) {
917
- updatedContent = `manage-package-manager-versions: true
918
-
919
- ${updatedContent}`;
920
- needsUpdate = true;
921
- }
922
- if (!currentContent.includes("onlyBuiltDependencies")) {
923
- updatedContent = `${updatedContent.trimEnd()}
924
-
925
- onlyBuiltDependencies:
926
- - esbuild
927
- `;
928
- needsUpdate = true;
929
- }
930
- if (!currentContent.includes(".config/*") && !currentContent.includes('".config/*"')) {
931
- const lines = updatedContent.split("\n");
932
- const packagesIndex = lines.findIndex((line) => line.trim().startsWith("packages:"));
933
- if (packagesIndex !== -1) {
934
- lines.splice(packagesIndex + 1, 0, ' - ".config/*"');
935
- updatedContent = lines.join("\n");
936
- needsUpdate = true;
937
- }
938
- }
939
- if (needsUpdate) {
940
- changes.push({
941
- path: "pnpm-workspace.yaml",
942
- status: "modified",
943
- currentContent,
944
- newContent: updatedContent
945
- });
946
- } else {
947
- changes.push({
948
- path: "pnpm-workspace.yaml",
949
- status: "unchanged",
950
- currentContent,
951
- newContent: currentContent
952
- });
953
- }
954
- return changes;
955
- }
956
- async function applyUpdates(changes, root) {
957
- for (const change of changes) {
958
- if (change.status === "unchanged") continue;
959
- const fullPath = path.join(root, change.path);
960
- await promises.mkdir(path.dirname(fullPath), { recursive: true });
961
- await promises.writeFile(fullPath, change.newContent);
962
- }
963
- }
964
- function formatFileChange(change) {
965
- const icon = change.status === "added" ? "+" : change.status === "modified" ? "~" : "=";
966
- return ` ${icon} ${change.path}`;
967
- }
968
- const LINTER_DEPS = {
969
- oxlint: "oxlint",
970
- eslint: "eslint",
971
- biome: "@biomejs/biome"
972
- };
973
- const FORMATTER_DEPS = {
974
- oxfmt: "oxfmt",
975
- prettier: "prettier",
976
- biome: "@biomejs/biome"
977
- };
978
- const LINTER_CONFIG_PACKAGES = {
979
- oxlint: "@config/oxlint",
980
- eslint: "@config/eslint",
981
- biome: null
982
- // biome uses root biome.json
983
- };
984
- const FORMATTER_CONFIG_PACKAGES = {
985
- oxfmt: "@config/oxfmt",
986
- prettier: "@config/prettier",
987
- biome: null
988
- // biome uses root biome.json
989
- };
990
- function needsMigration(current, target) {
991
- const linterChange = target.linter && target.linter !== current.linter;
992
- const formatterChange = target.formatter && target.formatter !== current.formatter;
993
- return linterChange || formatterChange || false;
994
- }
995
- async function getMigrationPlan(current, target, root) {
996
- const toLinter = target.linter ?? current.linter;
997
- const toFormatter = target.formatter ?? current.formatter;
998
- const targetVersions = toLinter === "biome" || toFormatter === "biome" ? await index.resolveMonorepoRootPackageVersions({
999
- linter: toLinter,
1000
- formatter: toFormatter
1001
- }) : {};
1002
- const biomeSchemaUrl = toLinter === "biome" || toFormatter === "biome" ? `https://biomejs.dev/schemas/${index.getResolvedPackageVersion(
1003
- targetVersions,
1004
- "@biomejs/biome"
1005
- )}/schema.json` : "";
1006
- const changes = [];
1007
- if (toLinter !== current.linter) {
1008
- if (current.linter !== "biome") {
1009
- changes.push({
1010
- type: "remove-dir",
1011
- path: `.config/${current.linter}`,
1012
- description: `Remove @config/${current.linter} package`
1013
- });
1014
- }
1015
- if (toLinter !== "biome") {
1016
- const files = {};
1017
- if (toLinter === "oxlint") {
1018
- index.generateOxlintConfigPackage(files);
1019
- } else if (toLinter === "eslint") {
1020
- index.generateEslintConfigPackage(files);
1021
- }
1022
- for (const [path, file] of Object.entries(files)) {
1023
- if (file.type === "text") {
1024
- changes.push({
1025
- type: "add-file",
1026
- path,
1027
- description: `Add ${path}`,
1028
- content: file.content
1029
- });
1030
- }
1031
- }
1032
- }
1033
- if (toLinter === "biome" && toFormatter === "biome") {
1034
- changes.push({
1035
- type: "add-file",
1036
- path: "biome.json",
1037
- description: "Add biome.json config",
1038
- content: JSON.stringify(
1039
- {
1040
- $schema: biomeSchemaUrl,
1041
- vcs: { enabled: true, clientKind: "git", useIgnoreFile: true },
1042
- linter: { enabled: true, rules: { recommended: true } },
1043
- formatter: { enabled: true }
1044
- },
1045
- null,
1046
- 2
1047
- )
1048
- });
1049
- } else if (toLinter === "biome" && toFormatter !== "biome") {
1050
- changes.push({
1051
- type: "add-file",
1052
- path: "biome.json",
1053
- description: "Add biome.json config (linter only)",
1054
- content: JSON.stringify(
1055
- {
1056
- $schema: biomeSchemaUrl,
1057
- vcs: { enabled: true, clientKind: "git", useIgnoreFile: true },
1058
- linter: { enabled: true, rules: { recommended: true } },
1059
- formatter: { enabled: false }
1060
- },
1061
- null,
1062
- 2
1063
- )
1064
- });
1065
- }
1066
- if (current.linter === "biome" && toLinter !== "biome" && current.formatter !== "biome" && toFormatter !== "biome") {
1067
- changes.push({
1068
- type: "remove-file",
1069
- path: "biome.json",
1070
- description: "Remove biome.json"
1071
- });
1072
- }
1073
- }
1074
- if (toFormatter !== current.formatter) {
1075
- const formatterSameAsLinter = current.formatter === current.linter;
1076
- if (current.formatter !== "biome" && !formatterSameAsLinter) {
1077
- changes.push({
1078
- type: "remove-dir",
1079
- path: `.config/${current.formatter}`,
1080
- description: `Remove @config/${current.formatter} package`
1081
- });
1082
- }
1083
- const newFormatterSameAsLinter = toFormatter === toLinter;
1084
- if (toFormatter !== "biome" && !newFormatterSameAsLinter) {
1085
- const files = {};
1086
- if (toFormatter === "oxfmt") {
1087
- index.generateOxfmtConfigPackage(files);
1088
- } else if (toFormatter === "prettier") {
1089
- index.generatePrettierConfigPackage(files);
1090
- }
1091
- for (const [path, file] of Object.entries(files)) {
1092
- if (file.type === "text") {
1093
- changes.push({
1094
- type: "add-file",
1095
- path,
1096
- description: `Add ${path}`,
1097
- content: file.content
1098
- });
1099
- }
1100
- }
1101
- }
1102
- if (toFormatter === "biome" && toLinter !== "biome") {
1103
- changes.push({
1104
- type: "add-file",
1105
- path: "biome.json",
1106
- description: "Add biome.json config (formatter only)",
1107
- content: JSON.stringify(
1108
- {
1109
- $schema: biomeSchemaUrl,
1110
- vcs: { enabled: true, clientKind: "git", useIgnoreFile: true },
1111
- linter: { enabled: false },
1112
- formatter: { enabled: true }
1113
- },
1114
- null,
1115
- 2
1116
- )
1117
- });
1118
- }
1119
- if (current.formatter === "biome" && toFormatter !== "biome" && current.linter !== "biome" && toLinter !== "biome") {
1120
- changes.push({
1121
- type: "remove-file",
1122
- path: "biome.json",
1123
- description: "Remove biome.json"
1124
- });
1125
- }
1126
- }
1127
- changes.push({
1128
- type: "update-package-json",
1129
- path: "package.json",
1130
- description: "Update root package.json (devDependencies, scripts)"
1131
- });
1132
- const subPackageUpdates = await getSubPackageUpdates(root, current, toLinter, toFormatter);
1133
- return {
1134
- fromLinter: current.linter,
1135
- toLinter,
1136
- fromFormatter: current.formatter,
1137
- toFormatter,
1138
- changes,
1139
- subPackageUpdates
1140
- };
1141
- }
1142
- async function getSubPackageUpdates(root, current, toLinter, toFormatter) {
1143
- const updates = [];
1144
- const workspacePath = path.join(root, "pnpm-workspace.yaml");
1145
- let workspaceContent;
1146
- try {
1147
- workspaceContent = await promises.readFile(workspacePath, "utf-8");
1148
- } catch {
1149
- return updates;
1150
- }
1151
- const packageGlobs = index.parseWorkspaceYamlContent(workspaceContent);
1152
- for (const glob of packageGlobs) {
1153
- if (glob.includes(".config")) continue;
1154
- const baseDir = glob.replace(/\/\*$/, "").replace(/^["']|["']$/g, "");
1155
- const basePath = path.join(root, baseDir);
1156
- try {
1157
- const entries = await promises.readdir(basePath, { withFileTypes: true });
1158
- for (const entry of entries) {
1159
- if (!entry.isDirectory()) continue;
1160
- const pkgJsonPath = path.join(basePath, entry.name, "package.json");
1161
- try {
1162
- const content = await promises.readFile(pkgJsonPath, "utf-8");
1163
- const pkg = JSON.parse(content);
1164
- const devDeps = pkg.devDependencies ?? {};
1165
- const remove = [];
1166
- const add = [];
1167
- const oldLinterPkg = LINTER_CONFIG_PACKAGES[current.linter];
1168
- const newLinterPkg = LINTER_CONFIG_PACKAGES[toLinter];
1169
- if (oldLinterPkg && oldLinterPkg !== newLinterPkg && devDeps[oldLinterPkg]) {
1170
- remove.push(oldLinterPkg);
1171
- }
1172
- if (newLinterPkg && newLinterPkg !== oldLinterPkg && oldLinterPkg && devDeps[oldLinterPkg]) {
1173
- add.push(newLinterPkg);
1174
- }
1175
- if (current.formatter !== current.linter) {
1176
- const oldFormatterPkg = FORMATTER_CONFIG_PACKAGES[current.formatter];
1177
- const newFormatterPkg = FORMATTER_CONFIG_PACKAGES[toFormatter];
1178
- if (oldFormatterPkg && oldFormatterPkg !== newFormatterPkg && devDeps[oldFormatterPkg]) {
1179
- remove.push(oldFormatterPkg);
1180
- }
1181
- if (newFormatterPkg && newFormatterPkg !== oldFormatterPkg && oldFormatterPkg && devDeps[oldFormatterPkg]) {
1182
- add.push(newFormatterPkg);
1183
- }
1184
- }
1185
- if (remove.length > 0 || add.length > 0) {
1186
- updates.push({
1187
- path: path.join(baseDir, entry.name, "package.json"),
1188
- remove,
1189
- add
1190
- });
1191
- }
1192
- } catch {
1193
- }
1194
- }
1195
- } catch {
1196
- }
1197
- }
1198
- return updates;
1199
- }
1200
- async function applyMigration(plan, root) {
1201
- for (const change of plan.changes) {
1202
- if (change.type === "remove-dir") {
1203
- const fullPath = path.join(root, change.path);
1204
- try {
1205
- await promises.rm(fullPath, { recursive: true });
1206
- } catch {
1207
- }
1208
- }
1209
- }
1210
- for (const change of plan.changes) {
1211
- if (change.type === "remove-file") {
1212
- const fullPath = path.join(root, change.path);
1213
- try {
1214
- await promises.rm(fullPath);
1215
- } catch {
1216
- }
1217
- }
1218
- }
1219
- for (const change of plan.changes) {
1220
- if (change.type === "add-file" && change.content) {
1221
- const fullPath = path.join(root, change.path);
1222
- await promises.mkdir(path.dirname(fullPath), { recursive: true });
1223
- await promises.writeFile(fullPath, change.content);
1224
- }
1225
- }
1226
- await updateRootPackageJson(root, plan);
1227
- for (const update of plan.subPackageUpdates) {
1228
- await updateSubPackageJson(root, update);
1229
- }
1230
- }
1231
- async function updateRootPackageJson(root, plan) {
1232
- const pkgPath = path.join(root, "package.json");
1233
- const content = await promises.readFile(pkgPath, "utf-8");
1234
- const pkg = JSON.parse(content);
1235
- const devDeps = pkg.devDependencies ?? {};
1236
- const oldLinterDep = LINTER_DEPS[plan.fromLinter];
1237
- delete devDeps[oldLinterDep];
1238
- if (plan.fromFormatter !== plan.fromLinter) {
1239
- const oldFormatterDep = FORMATTER_DEPS[plan.fromFormatter];
1240
- delete devDeps[oldFormatterDep];
1241
- }
1242
- const resolvedVersions = await index.resolveMonorepoRootPackageVersions({
1243
- linter: plan.toLinter,
1244
- formatter: plan.toFormatter
1245
- });
1246
- const newLinterDep = LINTER_DEPS[plan.toLinter];
1247
- devDeps[newLinterDep] = index.formatResolvedPackageVersion(resolvedVersions, newLinterDep);
1248
- if (plan.toFormatter !== plan.toLinter) {
1249
- const newFormatterDep = FORMATTER_DEPS[plan.toFormatter];
1250
- devDeps[newFormatterDep] = index.formatResolvedPackageVersion(resolvedVersions, newFormatterDep);
1251
- }
1252
- pkg.devDependencies = Object.fromEntries(
1253
- Object.entries(devDeps).sort(([a], [b]) => a.localeCompare(b))
1254
- );
1255
- const scripts = pkg.scripts ?? {};
1256
- if (plan.toLinter === "oxlint") {
1257
- scripts.lint = "oxlint .";
1258
- } else if (plan.toLinter === "eslint") {
1259
- scripts.lint = "eslint .";
1260
- } else if (plan.toLinter === "biome") {
1261
- scripts.lint = "biome check .";
1262
- }
1263
- if (plan.toFormatter === "oxfmt") {
1264
- scripts.format = "oxfmt .";
1265
- } else if (plan.toFormatter === "prettier") {
1266
- scripts.format = "prettier --write .";
1267
- } else if (plan.toFormatter === "biome") {
1268
- scripts.format = "biome format . --write";
1269
- }
1270
- pkg.scripts = scripts;
1271
- await promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
1272
- }
1273
- async function updateSubPackageJson(root, update) {
1274
- const pkgPath = path.join(root, update.path);
1275
- const content = await promises.readFile(pkgPath, "utf-8");
1276
- const pkg = JSON.parse(content);
1277
- const devDeps = pkg.devDependencies ?? {};
1278
- for (const dep of update.remove) {
1279
- delete devDeps[dep];
1280
- }
1281
- for (const dep of update.add) {
1282
- devDeps[dep] = "workspace:*";
1283
- }
1284
- pkg.devDependencies = Object.fromEntries(
1285
- Object.entries(devDeps).sort(([a], [b]) => a.localeCompare(b))
1286
- );
1287
- await promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
1288
- }
1289
- function formatMigrationChange(change) {
1290
- const icon = change.type === "remove-dir" || change.type === "remove-file" ? "-" : change.type === "add-file" ? "+" : "~";
1291
- return ` ${icon} ${change.description}`;
1292
- }
1293
-
1294
- async function checkAnyExists(paths) {
1295
- for (const path of paths) {
1296
- try {
1297
- await promises.access(path, promises.constants.F_OK);
1298
- return true;
1299
- } catch {
1300
- }
1301
- }
1302
- return false;
1303
- }
1304
- async function validateWorkspace(monorepoRoot) {
1305
- const errors = [];
1306
- const tsConfigPath = path.join(monorepoRoot, ".config/typescript/package.json");
1307
- try {
1308
- await promises.access(tsConfigPath, promises.constants.F_OK);
1309
- } catch {
1310
- errors.push("Missing .config/typescript package");
1311
- }
1312
- const linterPaths = [
1313
- path.join(monorepoRoot, ".config/oxlint/package.json"),
1314
- path.join(monorepoRoot, ".config/eslint/package.json"),
1315
- path.join(monorepoRoot, "eslint.config.js"),
1316
- path.join(monorepoRoot, "biome.json")
1317
- ];
1318
- const hasLinter = await checkAnyExists(linterPaths);
1319
- if (!hasLinter) {
1320
- errors.push(
1321
- "Missing linter config (.config/oxlint, .config/eslint, eslint.config.js, or biome.json)"
1322
- );
1323
- }
1324
- const formatterPaths = [
1325
- path.join(monorepoRoot, ".config/oxfmt/package.json"),
1326
- path.join(monorepoRoot, ".config/prettier/package.json"),
1327
- path.join(monorepoRoot, ".prettierrc.json"),
1328
- path.join(monorepoRoot, "biome.json")
1329
- ];
1330
- const hasFormatter = await checkAnyExists(formatterPaths);
1331
- if (!hasFormatter) {
1332
- errors.push(
1333
- "Missing formatter config (.config/oxfmt, .config/prettier, .prettierrc.json, or biome.json)"
1334
- );
1335
- }
1336
- return { valid: errors.length === 0, errors };
1337
- }
1338
-
1339
- const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href)));
1340
- const pkg = require$1("../package.json");
1341
- const META_OPTIONS = [
1342
- "clearConfig",
1343
- "configPath",
1344
- "check",
1345
- "fix",
1346
- "update",
1347
- "yes",
1348
- "workspace",
1349
- "path",
1350
- "dir"
1351
- ];
1352
- function hasConfigOptions(options) {
1353
- return Object.keys(options).some(
1354
- (key) => !META_OPTIONS.includes(key)
1355
- );
1356
- }
1357
- async function fileExists(path) {
1358
- try {
1359
- await promises$1.access(path, node_fs.constants.F_OK);
1360
- return true;
1361
- } catch {
1362
- return false;
1363
- }
1364
- }
1365
- async function promptForAiPlatforms(isNonInteractive) {
1366
- const savedPlatforms = getAiPlatforms();
1367
- if (isNonInteractive) {
1368
- return savedPlatforms ?? index.ALL_AI_PLATFORMS;
1369
- }
1370
- if (savedPlatforms && savedPlatforms.length > 0) {
1371
- const savedLabels = savedPlatforms.map((plat) => index.AI_PLATFORM_LABELS[plat]).join(", ");
1372
- const useDefault = await p__namespace.confirm({
1373
- message: `Add AI rules? ${color__default.dim(`(${savedLabels})`)}`,
1374
- initialValue: true
1375
- });
1376
- if (p__namespace.isCancel(useDefault)) {
1377
- return [];
1378
- }
1379
- if (useDefault) {
1380
- return savedPlatforms;
1381
- }
1382
- }
1383
- const selected = await p__namespace.multiselect({
1384
- message: "Add AI rules?",
1385
- options: index.ALL_AI_PLATFORMS.map((platform) => ({
1386
- value: platform,
1387
- label: index.AI_PLATFORM_LABELS[platform],
1388
- hint: index.AI_PLATFORM_HINTS[platform]
1389
- })),
1390
- initialValues: ["agents"],
1391
- required: false
1392
- });
1393
- if (p__namespace.isCancel(selected)) {
1394
- return [];
1395
- }
1396
- const platforms = selected;
1397
- if (platforms.length === 0) {
1398
- return [];
1399
- }
1400
- return platforms;
1401
- }
1402
- async function writeGeneratedFiles(basePath, files) {
1403
- const filePaths = Object.keys(files).sort();
1404
- for (const filePath of filePaths) {
1405
- const fullFilePath = node_path.join(basePath, filePath);
1406
- await promises$1.mkdir(node_path.dirname(fullFilePath), { recursive: true });
1407
- const file = files[filePath];
1408
- if (file.type === "text") {
1409
- await promises$1.writeFile(fullFilePath, file.content);
1410
- } else {
1411
- const response = await undici.fetch(file.url);
1412
- await promises$1.writeFile(fullFilePath, response.body);
1413
- }
1414
- }
1415
- }
1416
731
  function calculateWorkspaceRoot(packagePath) {
1417
732
  const segments = packagePath.split(/[/\\]/).filter(Boolean);
1418
733
  return segments.map(() => "..").join("/");
@@ -1438,30 +753,30 @@ async function detectPackageRoot() {
1438
753
  let currentDir = node_process.cwd();
1439
754
  const root = node_path.resolve("/");
1440
755
  while (currentDir !== root) {
1441
- if (await fileExists(node_path.join(currentDir, "package.json"))) {
756
+ if (await fileExists$1(node_path.join(currentDir, "package.json"))) {
1442
757
  return currentDir;
1443
758
  }
1444
759
  currentDir = node_path.dirname(currentDir);
1445
760
  }
1446
- return await fileExists(node_path.join(root, "package.json")) ? root : null;
761
+ return await fileExists$1(node_path.join(root, "package.json")) ? root : null;
1447
762
  }
1448
763
  async function parseWorkspaceDirectories(monorepoRoot) {
1449
764
  try {
1450
765
  const workspaceFile = node_path.join(monorepoRoot, "pnpm-workspace.yaml");
1451
766
  const content = await promises$1.readFile(workspaceFile, "utf-8");
1452
- return index.parseWorkspaceYamlContent(content);
767
+ return workspace.parseWorkspaceYamlContent(content);
1453
768
  } catch {
1454
769
  return [];
1455
770
  }
1456
771
  }
1457
772
  async function detectWorkspaceSettings(monorepoRoot) {
1458
773
  try {
1459
- const tooling = await index.detectTooling(monorepoRoot);
774
+ const tooling = await workspace.detectTooling(monorepoRoot);
1460
775
  const pkgPath = node_path.join(monorepoRoot, "package.json");
1461
776
  const content = await promises$1.readFile(pkgPath, "utf-8");
1462
777
  const pkgJson = JSON.parse(content);
1463
- const packageManager = index.parsePackageManager(pkgJson.packageManager);
1464
- const engine = index.parseEngine(pkgJson.engines);
778
+ const packageManager = workspace.parsePackageManager(pkgJson.packageManager);
779
+ const engine = workspace.parseEngine(pkgJson.engines);
1465
780
  let pnpmManageVersions;
1466
781
  try {
1467
782
  const workspaceFile = node_path.join(monorepoRoot, "pnpm-workspace.yaml");
@@ -1483,17 +798,17 @@ async function detectWorkspaceSettings(monorepoRoot) {
1483
798
  async function detectExistingConfigs(monorepoRoot) {
1484
799
  const configs = {};
1485
800
  const eslintPath = node_path.join(monorepoRoot, "eslint.config.js");
1486
- if (await fileExists(eslintPath)) {
801
+ if (await fileExists$1(eslintPath)) {
1487
802
  configs.linter = "eslint";
1488
803
  configs.eslintConfigPath = eslintPath;
1489
804
  }
1490
805
  const prettierPath = node_path.join(monorepoRoot, ".prettierrc.json");
1491
- if (await fileExists(prettierPath)) {
806
+ if (await fileExists$1(prettierPath)) {
1492
807
  configs.formatter = "prettier";
1493
808
  configs.prettierConfigPath = prettierPath;
1494
809
  }
1495
810
  const biomePath = node_path.join(monorepoRoot, "biome.json");
1496
- if (await fileExists(biomePath)) {
811
+ if (await fileExists$1(biomePath)) {
1497
812
  configs.biomeConfigPath = biomePath;
1498
813
  if (!configs.linter) configs.linter = "biome";
1499
814
  if (!configs.formatter) configs.formatter = "biome";
@@ -1515,18 +830,14 @@ async function getMonorepoScope(monorepoRoot) {
1515
830
  async function getWorkspacePackages(monorepoRoot) {
1516
831
  const packagesDir = node_path.join(monorepoRoot, "packages");
1517
832
  try {
1518
- const { readdir } = await import('fs/promises');
1519
- const entries = await readdir(packagesDir, { withFileTypes: true });
833
+ const entries = await promises$1.readdir(packagesDir, { withFileTypes: true });
1520
834
  const names = [];
1521
835
  for (const entry of entries) {
1522
836
  if (!entry.isDirectory()) continue;
1523
837
  try {
1524
- const content = await promises$1.readFile(
1525
- node_path.join(packagesDir, entry.name, "package.json"),
1526
- "utf-8"
1527
- );
1528
- const pkg2 = JSON.parse(content);
1529
- if (pkg2.name) names.push(pkg2.name);
838
+ const content = await promises$1.readFile(node_path.join(packagesDir, entry.name, "package.json"), "utf-8");
839
+ const pkg = JSON.parse(content);
840
+ if (pkg.name) names.push(pkg.name);
1530
841
  } catch {
1531
842
  }
1532
843
  }
@@ -1542,27 +853,48 @@ async function ensureConfigInWorkspace(monorepoRoot) {
1542
853
  content = await promises$1.readFile(workspacePath, "utf-8");
1543
854
  } catch {
1544
855
  content = `packages:
1545
- - ".config/*"
1546
- - "packages/*"
856
+ - '.config/*'
857
+ - 'packages/*'
1547
858
  `;
1548
859
  await promises$1.writeFile(workspacePath, content);
1549
860
  return;
1550
861
  }
1551
- if (content.includes(".config/*") || content.includes('".config/*"')) {
862
+ if (content.includes(".config/*")) {
1552
863
  return;
1553
864
  }
1554
865
  const lines = content.split("\n");
1555
866
  const packagesIndex = lines.findIndex((line) => line.trim().startsWith("packages:"));
1556
867
  if (packagesIndex === -1) {
1557
868
  content = `packages:
1558
- - ".config/*"
869
+ - '.config/*'
1559
870
  ${content}`;
1560
871
  } else {
1561
- lines.splice(packagesIndex + 1, 0, ' - ".config/*"');
872
+ lines.splice(packagesIndex + 1, 0, " - '.config/*'");
1562
873
  content = lines.join("\n");
1563
874
  }
1564
875
  await promises$1.writeFile(workspacePath, content);
1565
876
  }
877
+
878
+ async function handleCheckCommand() {
879
+ const monorepoRoot = await detectMonorepoRoot();
880
+ if (!monorepoRoot) {
881
+ console.log(color__default.red("\u2717") + " Not a monorepo workspace");
882
+ process.exit(1);
883
+ }
884
+ const { valid, errors } = await validateWorkspace(monorepoRoot);
885
+ if (valid) {
886
+ console.log(color__default.green("\u2713") + " Valid monorepo workspace");
887
+ console.log(color__default.dim(` ${monorepoRoot}`));
888
+ } else {
889
+ console.log(color__default.red("\u2717") + " Invalid monorepo workspace");
890
+ console.log(color__default.dim(` ${monorepoRoot}`));
891
+ for (const error of errors) {
892
+ console.log(color__default.red(` \u2022 ${error}`));
893
+ }
894
+ }
895
+ process.exit(valid ? 0 : 1);
896
+ }
897
+
1566
898
  async function migrateEslintConfig(monorepoRoot, files) {
1567
899
  const configBasePath = ".config/eslint";
1568
900
  const existingConfigPath = node_path.join(monorepoRoot, "eslint.config.js");
@@ -1570,7 +902,7 @@ async function migrateEslintConfig(monorepoRoot, files) {
1570
902
  try {
1571
903
  existingContent = await promises$1.readFile(existingConfigPath, "utf-8");
1572
904
  } catch {
1573
- index.generateEslintConfigPackage(files);
905
+ workspace.renderEslintConfigPackage(files);
1574
906
  return;
1575
907
  }
1576
908
  files[`${configBasePath}/package.json`] = {
@@ -1649,7 +981,7 @@ async function migratePrettierConfig(monorepoRoot, files) {
1649
981
  try {
1650
982
  existingContent = await promises$1.readFile(existingConfigPath, "utf-8");
1651
983
  } catch {
1652
- index.generatePrettierConfigPackage(files);
984
+ workspace.renderPrettierConfigPackage(files);
1653
985
  return;
1654
986
  }
1655
987
  files[`${configBasePath}/package.json`] = {
@@ -1669,151 +1001,35 @@ async function migratePrettierConfig(monorepoRoot, files) {
1669
1001
  };
1670
1002
  files[`${configBasePath}/README.md`] = {
1671
1003
  type: "text",
1672
- content: `# \`@config/prettier\`
1673
-
1674
- Shared Prettier configurations.
1675
-
1676
- ## Usage
1677
-
1678
- In your package's \`.prettierrc\`:
1679
-
1680
- \`\`\`json
1681
- "@config/prettier/base"
1682
- \`\`\`
1683
-
1684
- Or in \`package.json\`:
1685
-
1686
- \`\`\`json
1687
- {
1688
- "prettier": "@config/prettier/base"
1689
- }
1690
- \`\`\`
1691
-
1692
- ## Available Configs
1693
-
1694
- - \`base\` - Base Prettier rules (migrated from root)
1695
- `
1696
- };
1697
- files[`${configBasePath}/base.json`] = {
1698
- type: "text",
1699
- content: existingContent
1700
- };
1701
- }
1702
- async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedSettings, scope) {
1703
- const workspaceDirectories = await parseWorkspaceDirectories(monorepoRoot);
1704
- const defaultDirectories = ["apps", "packages"];
1705
- const hasCustomDirectories = workspaceDirectories.length > 0 && !workspaceDirectories.every((dir) => defaultDirectories.includes(dir));
1706
- const packageType = await promptForInitialPackage();
1707
- if (packageType === "skip") {
1708
- return false;
1709
- }
1710
- const defaultDir = packageType === "app" ? "apps" : "packages";
1711
- const packageNameInput = await p__namespace.text({
1712
- message: "Package name?",
1713
- initialValue: `@${scope}/`,
1714
- validate: (value) => {
1715
- const validationError = index.validatePackageName(value);
1716
- if (validationError) return validationError;
1717
- const dirName = value.includes("/") ? value.split("/").pop() : value;
1718
- if (!dirName) return "Package name is required";
1719
- if (!hasCustomDirectories) {
1720
- const targetPath = node_path.join(monorepoRoot, defaultDir, dirName);
1721
- try {
1722
- const { statSync } = require$1("fs");
1723
- statSync(targetPath);
1724
- return `Directory ${defaultDir}/${dirName} already exists`;
1725
- } catch {
1726
- }
1727
- }
1728
- }
1729
- });
1730
- if (p__namespace.isCancel(packageNameInput)) {
1731
- return false;
1732
- }
1733
- const scopedName = packageNameInput;
1734
- const shortName = scopedName.includes("/") ? scopedName.split("/").pop() : scopedName;
1735
- const packageOptions = await promptForPackageOptions(scopedName, packageType, inheritedSettings);
1736
- let targetDir = defaultDir;
1737
- if (hasCustomDirectories && workspaceDirectories.length > 0) {
1738
- const dirChoice = await p__namespace.select({
1739
- message: "Target directory",
1740
- options: workspaceDirectories.map((dir) => ({
1741
- value: dir,
1742
- label: dir
1743
- })),
1744
- initialValue: workspaceDirectories.includes(defaultDir) ? defaultDir : workspaceDirectories[0]
1745
- });
1746
- if (p__namespace.isCancel(dirChoice)) {
1747
- return false;
1748
- }
1749
- targetDir = dirChoice;
1750
- const targetPath = node_path.join(monorepoRoot, targetDir, shortName);
1751
- try {
1752
- const { statSync } = require$1("fs");
1753
- statSync(targetPath);
1754
- p__namespace.log.error(`Directory ${targetDir}/${shortName} already exists`);
1755
- return false;
1756
- } catch {
1757
- }
1758
- }
1759
- const relativePkgPath = node_path.join(targetDir, shortName);
1760
- const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
1761
- packageOptions.workspaceRoot = workspaceRoot;
1762
- packageOptions.name = scopedName;
1763
- packageOptions.packageManager = await index.resolvePackageManager(packageOptions);
1764
- packageOptions.engine = await index.resolveEngine(packageOptions);
1765
- packageOptions.versions = await index.resolveProjectPackageVersions(packageOptions);
1766
- const workspacePackages = packageType === "app" ? await getWorkspacePackages(monorepoRoot) : [];
1767
- if (workspacePackages.length > 0) {
1768
- const selectedDeps = await p__namespace.multiselect({
1769
- message: "Add workspace dependencies?",
1770
- options: workspacePackages.map((name) => ({ value: name, label: name })),
1771
- required: false
1772
- });
1773
- if (!p__namespace.isCancel(selectedDeps) && selectedDeps.length > 0) {
1774
- packageOptions.workspaceDependencies = selectedDeps;
1775
- }
1776
- }
1777
- const outputPath = node_path.join(monorepoRoot, relativePkgPath);
1778
- const spinner = p__namespace.spinner();
1779
- spinner.start("Creating package...");
1780
- try {
1781
- const files = index.generate(packageOptions);
1782
- await writeGeneratedFiles(outputPath, files);
1783
- spinner.stop(color__default.green.inverse(` \u2713 Package created at ${relativePkgPath}! `));
1784
- const addAnother = await p__namespace.select({
1785
- message: "Add another package?",
1786
- options: [
1787
- { value: "no", label: "No, I'm done" },
1788
- { value: "yes", label: "Yes, add another" }
1789
- ],
1790
- initialValue: "no"
1791
- });
1792
- return !p__namespace.isCancel(addAnother) && addAnother === "yes";
1793
- } catch (error) {
1794
- spinner.stop("Failed to create package");
1795
- p__namespace.log.error(String(error));
1796
- return false;
1797
- }
1004
+ content: `# \`@config/prettier\`
1005
+
1006
+ Shared Prettier configurations.
1007
+
1008
+ ## Usage
1009
+
1010
+ In your package's \`.prettierrc\`:
1011
+
1012
+ \`\`\`json
1013
+ "@config/prettier/base"
1014
+ \`\`\`
1015
+
1016
+ Or in \`package.json\`:
1017
+
1018
+ \`\`\`json
1019
+ {
1020
+ "prettier": "@config/prettier/base"
1798
1021
  }
1799
- async function handleCheckCommand() {
1800
- const monorepoRoot = await detectMonorepoRoot();
1801
- if (!monorepoRoot) {
1802
- console.log(color__default.red("\u2717") + " Not a monorepo workspace");
1803
- process.exit(1);
1804
- }
1805
- const { valid, errors } = await validateWorkspace(monorepoRoot);
1806
- if (valid) {
1807
- console.log(color__default.green("\u2713") + " Valid monorepo workspace");
1808
- console.log(color__default.dim(` ${monorepoRoot}`));
1809
- } else {
1810
- console.log(color__default.red("\u2717") + " Invalid monorepo workspace");
1811
- console.log(color__default.dim(` ${monorepoRoot}`));
1812
- for (const error of errors) {
1813
- console.log(color__default.red(` \u2022 ${error}`));
1814
- }
1815
- }
1816
- process.exit(valid ? 0 : 1);
1022
+ \`\`\`
1023
+
1024
+ ## Available Configs
1025
+
1026
+ - \`base\` - Base Prettier rules (migrated from root)
1027
+ `
1028
+ };
1029
+ files[`${configBasePath}/base.json`] = {
1030
+ type: "text",
1031
+ content: existingContent
1032
+ };
1817
1033
  }
1818
1034
  async function handleFixCommand(options) {
1819
1035
  const monorepoRoot = await detectMonorepoRoot();
@@ -1896,48 +1112,42 @@ async function handleFixCommand(options) {
1896
1112
  spinner.start("Fixing workspace...");
1897
1113
  try {
1898
1114
  const files = {};
1899
- const tsConfigExists = await fileExists(
1900
- node_path.join(monorepoRoot, ".config/typescript/package.json")
1901
- );
1115
+ const tsConfigExists = await fileExists$1(node_path.join(monorepoRoot, ".config/typescript/package.json"));
1902
1116
  if (!tsConfigExists) {
1903
- index.generateTypescriptConfigPackage(files);
1117
+ workspace.renderTypescriptConfigPackage(files);
1904
1118
  }
1905
1119
  if (linter === "oxlint") {
1906
- const oxlintExists = await fileExists(node_path.join(monorepoRoot, ".config/oxlint/package.json"));
1907
- if (!oxlintExists) index.generateOxlintConfigPackage(files);
1120
+ const oxlintExists = await fileExists$1(node_path.join(monorepoRoot, ".config/oxlint/package.json"));
1121
+ if (!oxlintExists) workspace.renderOxlintConfigPackage(files);
1908
1122
  } else if (linter === "eslint") {
1909
- const eslintPkgExists = await fileExists(
1910
- node_path.join(monorepoRoot, ".config/eslint/package.json")
1911
- );
1123
+ const eslintPkgExists = await fileExists$1(node_path.join(monorepoRoot, ".config/eslint/package.json"));
1912
1124
  if (!eslintPkgExists) {
1913
1125
  if (existingConfigs.eslintConfigPath) {
1914
1126
  await migrateEslintConfig(monorepoRoot, files);
1915
1127
  } else {
1916
- index.generateEslintConfigPackage(files);
1128
+ workspace.renderEslintConfigPackage(files);
1917
1129
  }
1918
1130
  }
1919
1131
  }
1920
1132
  if (formatter === "oxfmt") {
1921
- const oxfmtExists = await fileExists(node_path.join(monorepoRoot, ".config/oxfmt/package.json"));
1922
- if (!oxfmtExists) index.generateOxfmtConfigPackage(files);
1133
+ const oxfmtExists = await fileExists$1(node_path.join(monorepoRoot, ".config/oxfmt/package.json"));
1134
+ if (!oxfmtExists) workspace.renderOxfmtConfigPackage(files);
1923
1135
  } else if (formatter === "prettier") {
1924
- const prettierPkgExists = await fileExists(
1925
- node_path.join(monorepoRoot, ".config/prettier/package.json")
1926
- );
1136
+ const prettierPkgExists = await fileExists$1(node_path.join(monorepoRoot, ".config/prettier/package.json"));
1927
1137
  if (!prettierPkgExists) {
1928
1138
  if (existingConfigs.prettierConfigPath) {
1929
1139
  await migratePrettierConfig(monorepoRoot, files);
1930
1140
  } else {
1931
- index.generatePrettierConfigPackage(files);
1141
+ workspace.renderPrettierConfigPackage(files);
1932
1142
  }
1933
1143
  }
1934
1144
  }
1935
1145
  if ((linter === "biome" || formatter === "biome") && !existingConfigs.biomeConfigPath) {
1936
- const versions = await index.resolveMonorepoRootPackageVersions({
1146
+ const versions = await workspace.resolveMonorepoRootPackageVersions({
1937
1147
  linter,
1938
1148
  formatter
1939
1149
  });
1940
- const biomeVersion = index.getResolvedPackageVersion(versions, "@biomejs/biome");
1150
+ const biomeVersion = workspace.getResolvedPackageVersion(versions, "@biomejs/biome");
1941
1151
  const biomeConfig = {
1942
1152
  $schema: `https://biomejs.dev/schemas/${biomeVersion}/schema.json`,
1943
1153
  vcs: {
@@ -1979,15 +1189,13 @@ async function handleFixCommand(options) {
1979
1189
  }
1980
1190
  }
1981
1191
  spinner.stop(color__default.green("\u2713") + " Workspace fixed!");
1982
- const generated = Object.keys(files).filter((f) => f.endsWith("package.json"));
1192
+ const generated = Object.keys(files).filter((file) => file.endsWith("package.json"));
1983
1193
  for (const pkgFile of generated) {
1984
1194
  const pkgName = pkgFile.replace("/package.json", "");
1985
1195
  console.log(color__default.dim(` Generated ${pkgName}`));
1986
1196
  }
1987
- const vscodeSettingsExists = await fileExists(node_path.join(monorepoRoot, ".vscode/settings.json"));
1988
- const vscodeExtensionsExists = await fileExists(
1989
- node_path.join(monorepoRoot, ".vscode/extensions.json")
1990
- );
1197
+ const vscodeSettingsExists = await fileExists$1(node_path.join(monorepoRoot, ".vscode/settings.json"));
1198
+ const vscodeExtensionsExists = await fileExists$1(node_path.join(monorepoRoot, ".vscode/extensions.json"));
1991
1199
  const vscodeExists = vscodeSettingsExists && vscodeExtensionsExists;
1992
1200
  if (!vscodeExists) {
1993
1201
  let addVscode = false;
@@ -2002,7 +1210,7 @@ async function handleFixCommand(options) {
2002
1210
  }
2003
1211
  if (addVscode) {
2004
1212
  const vscodeFiles = {};
2005
- index.generateVscodeFiles(vscodeFiles, linter, formatter);
1213
+ workspace.renderVscodeFiles(vscodeFiles, linter, formatter);
2006
1214
  for (const [filePath, file] of Object.entries(vscodeFiles)) {
2007
1215
  const fullPath = node_path.join(monorepoRoot, filePath);
2008
1216
  await promises$1.mkdir(node_path.dirname(fullPath), { recursive: true });
@@ -2012,319 +1220,830 @@ async function handleFixCommand(options) {
2012
1220
  console.log(color__default.dim(" Generated .vscode/extensions.json"));
2013
1221
  }
2014
1222
  }
2015
- const aiRulesExist = await fileExists(node_path.join(monorepoRoot, ".ai/workspace.md"));
2016
- if (!aiRulesExist) {
2017
- const platforms = await promptForAiPlatforms(isNonInteractive);
2018
- if (platforms.length > 0) {
2019
- const scope = await getMonorepoScope(monorepoRoot);
2020
- const aiFilesOutput = {};
2021
- index.generateAiFiles(aiFilesOutput, {
2022
- name: scope,
2023
- packageManager: "pnpm",
2024
- linter,
2025
- formatter,
2026
- isMonorepo: true,
2027
- platforms
1223
+ const aiRulesExist = await fileExists$1(node_path.join(monorepoRoot, ".ai/workspace.md"));
1224
+ if (!aiRulesExist) {
1225
+ const platforms = await promptForAiAgentPlatforms(isNonInteractive);
1226
+ if (platforms.length > 0) {
1227
+ const scope = await getMonorepoScope(monorepoRoot);
1228
+ const aiFilesOutput = {};
1229
+ workspace.renderAiFiles(aiFilesOutput, {
1230
+ name: scope,
1231
+ packageManager: "pnpm",
1232
+ linter,
1233
+ formatter,
1234
+ isMonorepo: true,
1235
+ hasTypecheck: false,
1236
+ platforms
1237
+ });
1238
+ for (const [filePath, file] of Object.entries(aiFilesOutput)) {
1239
+ const fullPath = node_path.join(monorepoRoot, filePath);
1240
+ await promises$1.mkdir(node_path.dirname(fullPath), { recursive: true });
1241
+ await promises$1.writeFile(fullPath, file.content);
1242
+ console.log(color__default.dim(` Generated ${filePath}`));
1243
+ }
1244
+ }
1245
+ }
1246
+ process.exit(0);
1247
+ } catch (error) {
1248
+ spinner.stop(color__default.red("\u2717") + " Failed to fix workspace");
1249
+ console.error(error);
1250
+ process.exit(1);
1251
+ }
1252
+ }
1253
+
1254
+ function detectViteTemplate(pkg) {
1255
+ if (!hasPackage(pkg, "vite")) return void 0;
1256
+ if (hasPackage(pkg, "@react-three/fiber")) return "r3f";
1257
+ if (hasPackage(pkg, "react") || hasPackage(pkg, "@vitejs/plugin-react")) return "react";
1258
+ return "vanilla";
1259
+ }
1260
+ function renderExpectedViteConfig(template) {
1261
+ const isReact = template === "react" || template === "r3f";
1262
+ const codeSnippets = isReact ? { "vite-config-import": ["import react from '@vitejs/plugin-react';"] } : {};
1263
+ const viteConfig = {
1264
+ base: "./"
1265
+ };
1266
+ if (isReact) {
1267
+ viteConfig.plugins = ["$raw:react()"];
1268
+ }
1269
+ if (template === "r3f") {
1270
+ viteConfig.resolve = { dedupe: ["three"] };
1271
+ }
1272
+ return workspace.renderViteConfig({ viteConfig, codeSnippets });
1273
+ }
1274
+ async function detectCurrentConfig(root, isMonorepo = true) {
1275
+ let name = root.split(/[/\\]/).pop() ?? "workspace";
1276
+ let packageManager = "pnpm";
1277
+ let hasTypecheck = false;
1278
+ let viteTemplate;
1279
+ try {
1280
+ const pkgPath = path.join(root, "package.json");
1281
+ const content = await promises.readFile(pkgPath, "utf-8");
1282
+ const pkgJson = JSON.parse(content);
1283
+ if (pkgJson.name) {
1284
+ name = pkgJson.name.replace(/^@/, "").replace(/\/.*$/, "");
1285
+ }
1286
+ if (pkgJson.packageManager) {
1287
+ packageManager = pkgJson.packageManager.split("@")[0] ?? packageManager;
1288
+ }
1289
+ hasTypecheck = pkgJson.scripts?.typecheck != null;
1290
+ viteTemplate = detectViteTemplate(pkgJson);
1291
+ } catch {
1292
+ }
1293
+ const tooling = await workspace.detectTooling(root);
1294
+ const configStrategy = isMonorepo ? void 0 : await detectSinglePackageConfigStrategy(root);
1295
+ return {
1296
+ name,
1297
+ linter: tooling.linter ?? "oxlint",
1298
+ formatter: tooling.formatter ?? "prettier",
1299
+ packageManager,
1300
+ isMonorepo,
1301
+ configStrategy,
1302
+ hasTypecheck,
1303
+ viteTemplate
1304
+ };
1305
+ }
1306
+ async function detectSinglePackageConfigStrategy(root) {
1307
+ const hasStealthConfig = await Promise.all([
1308
+ fileExists(path.join(root, ".config/tsconfig.app.json")),
1309
+ fileExists(path.join(root, ".config/tsconfig.node.json")),
1310
+ fileExists(path.join(root, ".config/prettier.json")),
1311
+ fileExists(path.join(root, ".config/oxlint.json"))
1312
+ ]).then((matches) => matches.some(Boolean));
1313
+ return hasStealthConfig ? "stealth" : "root";
1314
+ }
1315
+ async function planExpectedFiles(config) {
1316
+ const { name, linter, formatter, packageManager, isMonorepo, configStrategy, hasTypecheck } = config;
1317
+ const versions = linter === "biome" || formatter === "biome" ? await workspace.resolveMonorepoRootPackageVersions({ linter, formatter }) : {};
1318
+ const aiFilesMap = {};
1319
+ workspace.renderAiFiles(aiFilesMap, {
1320
+ name,
1321
+ packageManager,
1322
+ linter,
1323
+ formatter,
1324
+ isMonorepo,
1325
+ configStrategy,
1326
+ hasTypecheck,
1327
+ platforms: workspace.ALL_AI_PLATFORMS
1328
+ });
1329
+ const vscodeFiles = workspace.renderVscodeFiles$1({
1330
+ linter,
1331
+ formatter,
1332
+ configStrategy,
1333
+ isMonorepo,
1334
+ packageManager: isPackageManagerName(packageManager) ? packageManager : void 0
1335
+ });
1336
+ const configPackages = {};
1337
+ if (isMonorepo) {
1338
+ workspace.renderTypescriptConfigPackage(configPackages);
1339
+ if (linter === "oxlint") {
1340
+ workspace.renderOxlintConfigPackage(configPackages);
1341
+ } else if (linter === "eslint") {
1342
+ workspace.renderEslintConfigPackage(configPackages);
1343
+ }
1344
+ if (formatter === "oxfmt") {
1345
+ workspace.renderOxfmtConfigPackage(configPackages);
1346
+ } else if (formatter === "prettier") {
1347
+ workspace.renderPrettierConfigPackage(configPackages);
1348
+ }
1349
+ }
1350
+ const workspaceConfig = {};
1351
+ const rootConfig = {};
1352
+ rootConfig[".editorconfig"] = workspace.renderEditorConfig();
1353
+ rootConfig[".gitignore"] = workspace.renderGitignore(isMonorepo ? "workspace-root" : "standalone");
1354
+ rootConfig[".gitattributes"] = {
1355
+ type: "text",
1356
+ content: `* text=auto eol=lf
1357
+ *.{cmd,[cC][mM][dD]} text eol=crlf
1358
+ *.{bat,[bB][aA][tT]} text eol=crlf
1359
+ `
1360
+ };
1361
+ if (!isMonorepo && formatter === "prettier") {
1362
+ rootConfig[configStrategy === "root" ? ".prettierignore" : ".config/prettierignore"] = {
1363
+ type: "text",
1364
+ content: workspace.toPrettierIgnoreContent()
1365
+ };
1366
+ }
1367
+ if (!isMonorepo && config.viteTemplate != null) {
1368
+ rootConfig["vite.config.ts"] = renderExpectedViteConfig(config.viteTemplate);
1369
+ }
1370
+ if (linter === "biome" || formatter === "biome") {
1371
+ const biomeVersion = workspace.getResolvedPackageVersion(versions, "@biomejs/biome");
1372
+ const biomeConfig = {
1373
+ $schema: `https://biomejs.dev/schemas/${biomeVersion}/schema.json`,
1374
+ vcs: {
1375
+ enabled: true,
1376
+ clientKind: "git",
1377
+ useIgnoreFile: true
1378
+ },
1379
+ linter: {
1380
+ enabled: linter === "biome",
1381
+ rules: {
1382
+ recommended: true
1383
+ }
1384
+ },
1385
+ formatter: {
1386
+ enabled: formatter === "biome"
1387
+ }
1388
+ };
1389
+ rootConfig["biome.json"] = {
1390
+ type: "text",
1391
+ content: JSON.stringify(biomeConfig, null, 2)
1392
+ };
1393
+ }
1394
+ return {
1395
+ "ai-files": aiFilesMap,
1396
+ vscode: vscodeFiles,
1397
+ "package-json": {},
1398
+ "config-packages": configPackages,
1399
+ "tooling-config": {},
1400
+ "workspace-config": workspaceConfig,
1401
+ "root-config": rootConfig
1402
+ };
1403
+ }
1404
+ async function fileExists(path) {
1405
+ try {
1406
+ await promises.access(path, fs.constants.F_OK);
1407
+ return true;
1408
+ } catch {
1409
+ return false;
1410
+ }
1411
+ }
1412
+ function stripJsonComments(content) {
1413
+ let output = "";
1414
+ let inString = false;
1415
+ let inLineComment = false;
1416
+ let inBlockComment = false;
1417
+ let escaped = false;
1418
+ for (let index = 0; index < content.length; index++) {
1419
+ const char = content[index];
1420
+ const next = content[index + 1];
1421
+ if (inLineComment) {
1422
+ if (char === "\n" || char === "\r") {
1423
+ inLineComment = false;
1424
+ output += char;
1425
+ }
1426
+ continue;
1427
+ }
1428
+ if (inBlockComment) {
1429
+ if (char === "*" && next === "/") {
1430
+ inBlockComment = false;
1431
+ index++;
1432
+ }
1433
+ continue;
1434
+ }
1435
+ if (inString) {
1436
+ output += char;
1437
+ if (escaped) {
1438
+ escaped = false;
1439
+ } else if (char === "\\") {
1440
+ escaped = true;
1441
+ } else if (char === '"') {
1442
+ inString = false;
1443
+ }
1444
+ continue;
1445
+ }
1446
+ if (char === '"') {
1447
+ inString = true;
1448
+ output += char;
1449
+ continue;
1450
+ }
1451
+ if (char === "/" && next === "/") {
1452
+ inLineComment = true;
1453
+ index++;
1454
+ continue;
1455
+ }
1456
+ if (char === "/" && next === "*") {
1457
+ inBlockComment = true;
1458
+ index++;
1459
+ continue;
1460
+ }
1461
+ output += char;
1462
+ }
1463
+ return output;
1464
+ }
1465
+ function stripTrailingJsonCommas(content) {
1466
+ let output = "";
1467
+ let inString = false;
1468
+ let escaped = false;
1469
+ for (let index = 0; index < content.length; index++) {
1470
+ const char = content[index];
1471
+ if (inString) {
1472
+ output += char;
1473
+ if (escaped) {
1474
+ escaped = false;
1475
+ } else if (char === "\\") {
1476
+ escaped = true;
1477
+ } else if (char === '"') {
1478
+ inString = false;
1479
+ }
1480
+ continue;
1481
+ }
1482
+ if (char === '"') {
1483
+ inString = true;
1484
+ output += char;
1485
+ continue;
1486
+ }
1487
+ if (char === ",") {
1488
+ let lookahead = index + 1;
1489
+ while (/\s/.test(content[lookahead] ?? "")) lookahead++;
1490
+ if (content[lookahead] === "}" || content[lookahead] === "]") {
1491
+ continue;
1492
+ }
1493
+ }
1494
+ output += char;
1495
+ }
1496
+ return output;
1497
+ }
1498
+ function parseJsonValue(content) {
1499
+ return JSON.parse(stripTrailingJsonCommas(stripJsonComments(content)));
1500
+ }
1501
+ function stableJsonValue(value) {
1502
+ if (Array.isArray(value)) {
1503
+ return value.map(stableJsonValue);
1504
+ }
1505
+ if (value != null && typeof value === "object") {
1506
+ return Object.fromEntries(
1507
+ Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entryValue]) => [key, stableJsonValue(entryValue)])
1508
+ );
1509
+ }
1510
+ return value;
1511
+ }
1512
+ function jsonValuesEqual(currentContent, newContent) {
1513
+ try {
1514
+ return JSON.stringify(stableJsonValue(parseJsonValue(currentContent))) === JSON.stringify(stableJsonValue(parseJsonValue(newContent)));
1515
+ } catch {
1516
+ return false;
1517
+ }
1518
+ }
1519
+ function shouldCompareJsonValues(filePath) {
1520
+ return filePath.endsWith(".json") || filePath.endsWith(".jsonc");
1521
+ }
1522
+ function fileContentsEqual(filePath, currentContent, newContent) {
1523
+ if (shouldCompareJsonValues(filePath) && jsonValuesEqual(currentContent, newContent)) {
1524
+ return true;
1525
+ }
1526
+ return currentContent === newContent;
1527
+ }
1528
+ async function compareWithDisk(expected, root) {
1529
+ const categoryLabels = {
1530
+ "ai-files": "AI Files",
1531
+ "ai-files-install": "Install More AI Files",
1532
+ "ai-files-update": "Update Existing AI Files",
1533
+ vscode: "VS Code",
1534
+ "package-json": "package.json Scripts",
1535
+ "config-packages": "Config Packages",
1536
+ "tooling-config": "Tooling Config",
1537
+ "workspace-config": "Workspace Config",
1538
+ "root-config": "Root Config"
1539
+ };
1540
+ const categories = [];
1541
+ for (const [category, files] of Object.entries(expected)) {
1542
+ const changes = [];
1543
+ for (const [filePath, file] of Object.entries(files)) {
1544
+ if (file.type !== "text") continue;
1545
+ const fullPath = path.join(root, filePath);
1546
+ const newContent = file.content;
1547
+ if (await fileExists(fullPath)) {
1548
+ const currentContent = await promises.readFile(fullPath, "utf-8");
1549
+ if (fileContentsEqual(filePath, currentContent, newContent)) {
1550
+ changes.push({
1551
+ path: filePath,
1552
+ status: "unchanged",
1553
+ currentContent,
1554
+ newContent
1555
+ });
1556
+ } else {
1557
+ changes.push({
1558
+ path: filePath,
1559
+ status: "modified",
1560
+ currentContent,
1561
+ newContent
1562
+ });
1563
+ }
1564
+ } else {
1565
+ changes.push({
1566
+ path: filePath,
1567
+ status: "added",
1568
+ newContent
1569
+ });
1570
+ }
1571
+ }
1572
+ if (category === "ai-files") {
1573
+ const newAiFiles = changes.filter((change) => change.status === "added");
1574
+ const modifiedAiFiles = changes.filter((change) => change.status === "modified");
1575
+ if (newAiFiles.length > 0) {
1576
+ categories.push({
1577
+ category: "ai-files-install",
1578
+ label: categoryLabels["ai-files-install"],
1579
+ changes: newAiFiles,
1580
+ hasUserModifications: false
1581
+ });
1582
+ }
1583
+ if (modifiedAiFiles.length > 0) {
1584
+ categories.push({
1585
+ category: "ai-files-update",
1586
+ label: categoryLabels["ai-files-update"],
1587
+ changes: modifiedAiFiles,
1588
+ hasUserModifications: true
2028
1589
  });
2029
- for (const [filePath, file] of Object.entries(aiFilesOutput)) {
2030
- const fullPath = node_path.join(monorepoRoot, filePath);
2031
- await promises$1.mkdir(node_path.dirname(fullPath), { recursive: true });
2032
- await promises$1.writeFile(fullPath, file.content);
2033
- console.log(color__default.dim(` Generated ${filePath}`));
2034
- }
2035
1590
  }
1591
+ continue;
2036
1592
  }
2037
- process.exit(0);
2038
- } catch (error) {
2039
- spinner.stop(color__default.red("\u2717") + " Failed to fix workspace");
2040
- console.error(error);
2041
- process.exit(1);
1593
+ if (changes.length === 0) continue;
1594
+ const hasUserModifications = changes.some((c) => c.status === "modified");
1595
+ categories.push({
1596
+ category,
1597
+ label: categoryLabels[category],
1598
+ changes,
1599
+ hasUserModifications
1600
+ });
2042
1601
  }
1602
+ return categories;
1603
+ }
1604
+ function isPackageManagerName(value) {
1605
+ return value === "pnpm" || value === "npm" || value === "yarn";
2043
1606
  }
2044
- async function handleMigration(config, target, root, options) {
2045
- const plan = await getMigrationPlan(config, target, root);
2046
- console.log(color__default.cyan("Migration:"));
2047
- if (plan.fromLinter !== plan.toLinter) {
2048
- console.log(` Linter: ${color__default.dim(plan.fromLinter)} \u2192 ${color__default.green(plan.toLinter)}`);
1607
+ function hasPackage(pkg, name) {
1608
+ return pkg.dependencies?.[name] != null || pkg.devDependencies?.[name] != null || pkg.peerDependencies?.[name] != null;
1609
+ }
1610
+ function sortPackageMap(packageMap) {
1611
+ return Object.fromEntries(Object.entries(packageMap).sort(([a], [b]) => a.localeCompare(b)));
1612
+ }
1613
+ async function detectTypeScriptPackage(root, pkg) {
1614
+ if (hasPackage(pkg, "typescript")) return true;
1615
+ return await fileExists(path.join(root, "tsconfig.json")) || await fileExists(path.join(root, "tsconfig.app.json")) || await fileExists(path.join(root, ".config/tsconfig.app.json"));
1616
+ }
1617
+ function detectLibraryPackage(pkg) {
1618
+ return pkg.exports != null || pkg.main?.includes("dist") === true || pkg.module?.includes("dist") === true || Array.isArray(pkg.files) && pkg.files.includes("dist");
1619
+ }
1620
+ function getPackageManagerForScripts(config, pkg) {
1621
+ const packageManager = pkg.packageManager?.split("@")[0] ?? config.packageManager;
1622
+ return isPackageManagerName(packageManager) ? packageManager : "pnpm";
1623
+ }
1624
+ function getSinglePackageToolScripts(config) {
1625
+ const isStealth = (config.configStrategy ?? "stealth") === "stealth";
1626
+ const linterScripts = config.linter === "oxlint" ? workspace.packageJsonScripts.lint.oxlint(isStealth ? ".config/oxlint.json" : void 0) : config.linter === "eslint" ? workspace.packageJsonScripts.lint.eslint(isStealth ? ".config/eslint.config.js" : void 0) : workspace.packageJsonScripts.lint.biome(isStealth ? ".config" : void 0);
1627
+ const formatterScripts = config.formatter === "prettier" ? workspace.packageJsonScripts.format.prettier(
1628
+ isStealth ? ".config/prettier.json" : void 0,
1629
+ isStealth ? ".config/prettierignore" : void 0
1630
+ ) : config.formatter === "oxfmt" ? workspace.packageJsonScripts.format.oxfmt(isStealth ? ".config/oxfmt.json" : "oxfmt.json") : workspace.packageJsonScripts.format.biome(isStealth ? ".config" : void 0);
1631
+ return workspace.mergePackageJsonScripts(linterScripts, formatterScripts);
1632
+ }
1633
+ function getLibraryBuildScripts(pkg) {
1634
+ if (!detectLibraryPackage(pkg)) return void 0;
1635
+ if (hasPackage(pkg, "tsdown") || pkg.scripts?.build === "tsdown") {
1636
+ return workspace.packageJsonScripts.build.tsdown;
2049
1637
  }
2050
- if (plan.fromFormatter !== plan.toFormatter) {
2051
- console.log(
2052
- ` Formatter: ${color__default.dim(plan.fromFormatter)} \u2192 ${color__default.green(plan.toFormatter)}`
2053
- );
1638
+ return workspace.packageJsonScripts.build.unbuild();
1639
+ }
1640
+ function getTestingScripts(pkg) {
1641
+ if (hasPackage(pkg, "vitest") || pkg.scripts?.test === "vitest") {
1642
+ return workspace.packageJsonScripts.test.vitest;
2054
1643
  }
2055
- console.log();
2056
- console.log(color__default.cyan("Changes:"));
2057
- for (const change of plan.changes) {
2058
- console.log(formatMigrationChange(change));
1644
+ return void 0;
1645
+ }
1646
+ function scriptsEqual(left, right) {
1647
+ const leftEntries = Object.entries(left);
1648
+ if (leftEntries.length !== Object.keys(right).length) return false;
1649
+ return leftEntries.every(([key, value]) => right[key] === value);
1650
+ }
1651
+ async function getExpectedPackageScripts(root, config, pkg) {
1652
+ if (config.isMonorepo) {
1653
+ return workspace.packageJsonScripts.monorepoRoot(config.linter, config.formatter);
1654
+ }
1655
+ const language = await detectTypeScriptPackage(root, pkg) ? "typescript" : "javascript";
1656
+ const isLibrary = detectLibraryPackage(pkg);
1657
+ const packageManagerName = getPackageManagerForScripts(config, pkg);
1658
+ return workspace.mergePackageJsonScripts(
1659
+ workspace.resolveDefaultPackageJsonScripts({
1660
+ language,
1661
+ isLibrary,
1662
+ packageManagerName
1663
+ }),
1664
+ getLibraryBuildScripts(pkg),
1665
+ getTestingScripts(pkg),
1666
+ getSinglePackageToolScripts(config)
1667
+ );
1668
+ }
1669
+ async function getExpectedPackageDevDependencies(root, config, pkg) {
1670
+ const nextDevDependencies = { ...pkg.devDependencies };
1671
+ const shouldAddOxlintTypeAwareBackend = config.linter === "oxlint" && (config.isMonorepo || await detectTypeScriptPackage(root, pkg)) && !hasPackage(pkg, "oxlint-tsgolint");
1672
+ if (shouldAddOxlintTypeAwareBackend) {
1673
+ nextDevDependencies["oxlint-tsgolint"] = workspace.formatResolvedPackageVersion({}, "oxlint-tsgolint");
2059
1674
  }
2060
- if (plan.subPackageUpdates.length > 0) {
2061
- console.log();
2062
- console.log(color__default.cyan(`Sub-packages (${plan.subPackageUpdates.length}):`));
2063
- for (const update of plan.subPackageUpdates) {
2064
- const changes = [
2065
- ...update.remove.map((d) => `-${d}`),
2066
- ...update.add.map((d) => `+${d}`)
2067
- ].join(", ");
2068
- console.log(` ~ ${update.path} (${changes})`);
1675
+ return sortPackageMap(nextDevDependencies);
1676
+ }
1677
+ async function getPackageJsonScriptUpdates(root, config) {
1678
+ const packageJsonPath = path.join(root, "package.json");
1679
+ let currentContent;
1680
+ try {
1681
+ currentContent = await promises.readFile(packageJsonPath, "utf-8");
1682
+ } catch {
1683
+ return [];
1684
+ }
1685
+ const pkg = JSON.parse(currentContent);
1686
+ const currentScripts = pkg.scripts ?? {};
1687
+ const expectedScripts = await getExpectedPackageScripts(root, config, pkg);
1688
+ const nextScripts = workspace.mergePackageJsonScripts(currentScripts, expectedScripts);
1689
+ const currentDevDependencies = pkg.devDependencies ?? {};
1690
+ const nextDevDependencies = await getExpectedPackageDevDependencies(root, config, pkg);
1691
+ if (scriptsEqual(currentScripts, nextScripts) && scriptsEqual(currentDevDependencies, nextDevDependencies)) {
1692
+ return [
1693
+ {
1694
+ path: "package.json",
1695
+ status: "unchanged",
1696
+ currentContent,
1697
+ newContent: currentContent
1698
+ }
1699
+ ];
1700
+ }
1701
+ const nextPackageJson = {
1702
+ ...pkg,
1703
+ scripts: nextScripts
1704
+ };
1705
+ if (Object.keys(nextDevDependencies).length > 0 || pkg.devDependencies != null) {
1706
+ nextPackageJson.devDependencies = nextDevDependencies;
1707
+ }
1708
+ const newContent = `${JSON.stringify(nextPackageJson, null, 2)}
1709
+ `;
1710
+ return [
1711
+ {
1712
+ path: "package.json",
1713
+ status: "modified",
1714
+ currentContent,
1715
+ newContent
2069
1716
  }
1717
+ ];
1718
+ }
1719
+ function planSinglePackageOxlintConfig(config) {
1720
+ if (config.linter !== "oxlint" || config.isMonorepo) return void 0;
1721
+ const isStealth = (config.configStrategy ?? "stealth") === "stealth";
1722
+ const path = isStealth ? ".config/oxlint.json" : "oxlint.json";
1723
+ const oxlintConfig = workspace.renderOxlintConfig({
1724
+ schemaPath: isStealth ? "../node_modules/oxlint/configuration_schema.json" : "./node_modules/oxlint/configuration_schema.json",
1725
+ typescript: true
1726
+ });
1727
+ return {
1728
+ path,
1729
+ status: "added",
1730
+ newContent: `${JSON.stringify(oxlintConfig, null, 2)}
1731
+ `
1732
+ };
1733
+ }
1734
+ async function getOxlintConfigReplacementUpdates(root, config) {
1735
+ const expected = planSinglePackageOxlintConfig(config);
1736
+ if (expected == null) return [];
1737
+ const fullPath = path.join(root, expected.path);
1738
+ let currentContent;
1739
+ try {
1740
+ currentContent = await promises.readFile(fullPath, "utf-8");
1741
+ } catch {
1742
+ return [expected];
2070
1743
  }
2071
- console.log();
2072
- if (!options.yes) {
2073
- const confirm = await p__namespace.confirm({
2074
- message: "Apply migration?",
2075
- initialValue: true
2076
- });
2077
- if (p__namespace.isCancel(confirm) || !confirm) {
2078
- console.log(color__default.dim(" Migration cancelled"));
2079
- process.exit(0);
1744
+ if (fileContentsEqual(expected.path, currentContent, expected.newContent)) {
1745
+ return [
1746
+ {
1747
+ ...expected,
1748
+ status: "unchanged",
1749
+ currentContent,
1750
+ newContent: currentContent
1751
+ }
1752
+ ];
1753
+ }
1754
+ return [
1755
+ {
1756
+ ...expected,
1757
+ status: "modified",
1758
+ currentContent
2080
1759
  }
1760
+ ];
1761
+ }
1762
+ async function getWorkspaceConfigUpdates(root) {
1763
+ const workspacePath = path.join(root, "pnpm-workspace.yaml");
1764
+ const changes = [];
1765
+ let currentContent = "";
1766
+ let exists = false;
1767
+ try {
1768
+ currentContent = await promises.readFile(workspacePath, "utf-8");
1769
+ exists = true;
1770
+ } catch {
2081
1771
  }
2082
- await applyMigration(plan, root);
2083
- const aiWorkspacePath = node_path.join(root, ".ai/workspace.md");
2084
- const aiRulesExist = await fileExists(aiWorkspacePath);
2085
- if (aiRulesExist) {
2086
- console.log();
2087
- console.log(color__default.cyan("Updating AI rules..."));
2088
- const scope = await getMonorepoScope(root);
2089
- const existingPlatforms = [];
2090
- if (await fileExists(node_path.join(root, "AGENTS.md"))) {
2091
- existingPlatforms.push("agents");
2092
- }
2093
- if (await fileExists(node_path.join(root, "CLAUDE.md"))) {
2094
- existingPlatforms.push("claude");
2095
- }
2096
- const aiFilesOutput = {};
2097
- index.generateAiFiles(aiFilesOutput, {
2098
- name: scope,
2099
- packageManager: "pnpm",
2100
- linter: plan.toLinter,
2101
- formatter: plan.toFormatter,
2102
- isMonorepo: true,
2103
- platforms: existingPlatforms.length > 0 ? existingPlatforms : ["agents"]
1772
+ if (!exists) {
1773
+ const newContent = `manage-package-manager-versions: true
1774
+
1775
+ packages:
1776
+ - '.config/*'
1777
+ - 'apps/*'
1778
+ - 'packages/*'
1779
+
1780
+ onlyBuiltDependencies:
1781
+ - esbuild
1782
+ `;
1783
+ changes.push({
1784
+ path: "pnpm-workspace.yaml",
1785
+ status: "added",
1786
+ newContent
2104
1787
  });
2105
- for (const [filePath, file] of Object.entries(aiFilesOutput)) {
2106
- const fullPath = node_path.join(root, filePath);
2107
- await promises$1.mkdir(node_path.dirname(fullPath), { recursive: true });
2108
- await promises$1.writeFile(fullPath, file.content);
2109
- console.log(color__default.dim(` ${filePath}`));
1788
+ return changes;
1789
+ }
1790
+ let updatedContent = currentContent;
1791
+ let needsUpdate = false;
1792
+ if (!currentContent.includes("manage-package-manager-versions")) {
1793
+ updatedContent = `manage-package-manager-versions: true
1794
+
1795
+ ${updatedContent}`;
1796
+ needsUpdate = true;
1797
+ }
1798
+ if (!currentContent.includes("onlyBuiltDependencies")) {
1799
+ updatedContent = `${updatedContent.trimEnd()}
1800
+
1801
+ onlyBuiltDependencies:
1802
+ - esbuild
1803
+ `;
1804
+ needsUpdate = true;
1805
+ }
1806
+ if (!currentContent.includes(".config/*")) {
1807
+ const lines = updatedContent.split("\n");
1808
+ const packagesIndex = lines.findIndex((line) => line.trim().startsWith("packages:"));
1809
+ if (packagesIndex !== -1) {
1810
+ lines.splice(packagesIndex + 1, 0, " - '.config/*'");
1811
+ updatedContent = lines.join("\n");
1812
+ needsUpdate = true;
2110
1813
  }
2111
1814
  }
2112
- console.log();
2113
- console.log(color__default.green("\u2713") + ` Migrated to ${plan.toLinter}/${plan.toFormatter}`);
2114
- console.log(color__default.dim(" Run `pnpm install` to update dependencies"));
2115
- process.exit(0);
1815
+ if (needsUpdate) {
1816
+ changes.push({
1817
+ path: "pnpm-workspace.yaml",
1818
+ status: "modified",
1819
+ currentContent,
1820
+ newContent: updatedContent
1821
+ });
1822
+ } else {
1823
+ changes.push({
1824
+ path: "pnpm-workspace.yaml",
1825
+ status: "unchanged",
1826
+ currentContent,
1827
+ newContent: currentContent
1828
+ });
1829
+ }
1830
+ return changes;
2116
1831
  }
2117
- async function handleUpdateCommand(options) {
2118
- const monorepoRoot = await detectMonorepoRoot();
2119
- const projectRoot = monorepoRoot ?? await detectPackageRoot();
2120
- if (!projectRoot) {
2121
- console.log(color__default.red("\u2717") + " Could not find a project root");
2122
- console.log(color__default.dim(" Run this command from inside a generated project"));
2123
- process.exit(1);
1832
+ async function applyUpdates(changes, root) {
1833
+ for (const change of changes) {
1834
+ if (change.status === "unchanged") continue;
1835
+ const fullPath = path.join(root, change.path);
1836
+ await promises.mkdir(path.dirname(fullPath), { recursive: true });
1837
+ await promises.writeFile(fullPath, change.newContent);
2124
1838
  }
2125
- const isMonorepo = monorepoRoot != null;
2126
- if (isMonorepo) {
2127
- const { valid, errors } = await validateWorkspace(projectRoot);
2128
- if (!valid) {
2129
- console.log(color__default.yellow("!") + " Workspace has issues:");
2130
- for (const error of errors) {
2131
- console.log(color__default.dim(` \u2022 ${error}`));
2132
- }
2133
- console.log();
2134
- const shouldFix = options.yes || await p__namespace.confirm({
2135
- message: "Run fix first to resolve these issues?",
2136
- initialValue: true
2137
- });
2138
- if (p__namespace.isCancel(shouldFix) || !shouldFix) {
2139
- console.log(color__default.dim(" Run `pnpm create krispya --fix` to fix manually"));
2140
- process.exit(1);
2141
- }
2142
- const preFixConfig = await detectCurrentConfig(projectRoot);
2143
- const fixOptions = {
2144
- ...options,
2145
- linter: options.linter ?? preFixConfig.linter,
2146
- formatter: options.formatter ?? preFixConfig.formatter
2147
- };
2148
- await handleFixCommand(fixOptions);
2149
- }
2150
- } else if (options.linter || options.formatter) {
2151
- console.log(
2152
- color__default.yellow("!") + " Linter/formatter migrations in --update are currently monorepo-only"
2153
- );
2154
- console.log(color__default.dim(" Continuing with standalone shared config updates"));
2155
- console.log();
1839
+ }
1840
+ function formatFileChange(change) {
1841
+ const icon = change.status === "added" ? "+" : change.status === "modified" ? "~" : "=";
1842
+ return ` ${icon} ${change.path}`;
1843
+ }
1844
+
1845
+ const UPDATE_CATEGORY_ORDER = [
1846
+ "root-config",
1847
+ "config-packages",
1848
+ "tooling-config",
1849
+ "workspace-config",
1850
+ "vscode",
1851
+ "package-json",
1852
+ "ai-files",
1853
+ "ai-files-install",
1854
+ "ai-files-update"
1855
+ ];
1856
+ function isMergeUpdateCategory(category) {
1857
+ return category === "workspace-config" || category === "package-json";
1858
+ }
1859
+ function getUpdateHint(category, status) {
1860
+ if (status === "added") return "new file";
1861
+ if (category === "package-json") return "merge update";
1862
+ if (category === "workspace-config") return "merge update";
1863
+ return "changed; overwrites if selected";
1864
+ }
1865
+ function isSelectableFileChange(change) {
1866
+ return change.status === "added" || change.status === "modified";
1867
+ }
1868
+ function getInitialUpdateSelections(category) {
1869
+ return category.changes.filter(
1870
+ (change) => change.status === "added" || change.status === "modified" && isMergeUpdateCategory(category.category)
1871
+ ).map((change) => change.path);
1872
+ }
1873
+ async function promptForUpdateSelections(category) {
1874
+ const selectableChanges = category.changes.filter(isSelectableFileChange);
1875
+ const selectedFiles = await p__namespace.multiselect({
1876
+ message: category.label,
1877
+ options: selectableChanges.map((change) => ({
1878
+ value: change.path,
1879
+ label: change.path,
1880
+ hint: getUpdateHint(category.category, change.status)
1881
+ })),
1882
+ initialValues: getInitialUpdateSelections(category),
1883
+ required: false
1884
+ });
1885
+ if (p__namespace.isCancel(selectedFiles)) {
1886
+ p__namespace.cancel("Operation cancelled.");
1887
+ process.exit(0);
2156
1888
  }
2157
- const config = await detectCurrentConfig(projectRoot, isMonorepo);
2158
- const targetLinter = options.linter;
2159
- const targetFormatter = options.formatter;
2160
- const migrationTarget = { linter: targetLinter, formatter: targetFormatter };
2161
- if (isMonorepo && needsMigration(config, migrationTarget)) {
2162
- await handleMigration(config, migrationTarget, projectRoot, options);
2163
- return;
1889
+ return selectableChanges.filter((change) => selectedFiles.includes(change.path));
1890
+ }
1891
+ async function promptForAiFileInstall(category) {
1892
+ const newChanges = category.changes.filter((change) => change.status === "added");
1893
+ const fileList = newChanges.map((change) => change.path).join(", ");
1894
+ const shouldInstall = await p__namespace.confirm({
1895
+ message: fileList ? `Install more AI files? (${fileList})` : "Install more AI files?",
1896
+ initialValue: true
1897
+ });
1898
+ if (p__namespace.isCancel(shouldInstall)) {
1899
+ p__namespace.cancel("Operation cancelled.");
1900
+ process.exit(0);
2164
1901
  }
2165
- console.log(
2166
- color__default.cyan("Checking for updates...") + color__default.dim(` (${config.linter}/${config.formatter})`)
1902
+ return shouldInstall ? newChanges : [];
1903
+ }
1904
+ function getCategoryOrder(category) {
1905
+ const index = UPDATE_CATEGORY_ORDER.indexOf(category);
1906
+ return index === -1 ? UPDATE_CATEGORY_ORDER.length : index;
1907
+ }
1908
+ function orderUpdateCategories(categories) {
1909
+ return [...categories].sort(
1910
+ (left, right) => getCategoryOrder(left.category) - getCategoryOrder(right.category)
2167
1911
  );
2168
- console.log();
2169
- const expected = await generateExpectedFiles(config);
1912
+ }
1913
+ async function collectUpdateCategories(projectRoot, config, isMonorepo) {
1914
+ const expected = await planExpectedFiles(config);
2170
1915
  const categories = await compareWithDisk(expected, projectRoot);
2171
- const allCategories = categories.filter((c) => c.category !== "workspace-config");
1916
+ const allCategories = categories.filter((category) => category.category !== "workspace-config");
1917
+ const packageJsonScriptChanges = await getPackageJsonScriptUpdates(projectRoot, config);
1918
+ if (packageJsonScriptChanges.length > 0) {
1919
+ allCategories.push({
1920
+ category: "package-json",
1921
+ label: "package.json",
1922
+ changes: packageJsonScriptChanges,
1923
+ hasUserModifications: packageJsonScriptChanges.some((change) => change.status === "modified")
1924
+ });
1925
+ }
1926
+ const oxlintConfigChanges = await getOxlintConfigReplacementUpdates(projectRoot, config);
1927
+ if (oxlintConfigChanges.length > 0) {
1928
+ allCategories.push({
1929
+ category: "tooling-config",
1930
+ label: "Tooling Config",
1931
+ changes: oxlintConfigChanges,
1932
+ hasUserModifications: oxlintConfigChanges.some((change) => change.status === "modified")
1933
+ });
1934
+ }
2172
1935
  if (isMonorepo) {
2173
1936
  const workspaceConfigChanges = await getWorkspaceConfigUpdates(projectRoot);
2174
- const workspaceCategory = {
2175
- category: "workspace-config",
2176
- label: "Workspace Config",
2177
- changes: workspaceConfigChanges,
2178
- hasUserModifications: workspaceConfigChanges.some((c) => c.status === "modified")
2179
- };
2180
1937
  if (workspaceConfigChanges.length > 0) {
2181
- const configPkgIndex = allCategories.findIndex((c) => c.category === "config-packages");
2182
- if (configPkgIndex !== -1) {
2183
- allCategories.splice(configPkgIndex + 1, 0, workspaceCategory);
2184
- } else {
2185
- allCategories.push(workspaceCategory);
2186
- }
1938
+ allCategories.push({
1939
+ category: "workspace-config",
1940
+ label: "Workspace Config",
1941
+ changes: workspaceConfigChanges,
1942
+ hasUserModifications: workspaceConfigChanges.some((change) => change.status === "modified")
1943
+ });
2187
1944
  }
2188
1945
  }
2189
- let updatedCount = 0;
2190
- let skippedCount = 0;
2191
- for (const category of allCategories) {
2192
- const newChanges = category.changes.filter((c) => c.status === "added");
2193
- const modifiedChanges = category.changes.filter((c) => c.status === "modified");
2194
- const hasNew = newChanges.length > 0;
2195
- const hasModified = modifiedChanges.length > 0;
2196
- const hasChanges = hasNew || hasModified;
2197
- if (!hasChanges) {
2198
- console.log(color__default.green("\u2713") + ` ${category.label}: Up to date`);
2199
- continue;
2200
- }
2201
- if (category.category === "ai-files") {
2202
- if (hasNew) {
2203
- console.log(color__default.cyan(category.label + ":"));
2204
- console.log(color__default.dim(` ${newChanges.length} AI file(s) can be added`));
2205
- console.log();
2206
- const applyAi = options.yes ? true : await p__namespace.confirm({
2207
- message: "Add AI rules?",
2208
- initialValue: true
2209
- });
2210
- if (!p__namespace.isCancel(applyAi) && applyAi) {
2211
- await applyUpdates(newChanges, projectRoot);
2212
- console.log(color__default.green("\u2713") + ` Added ${newChanges.length} AI file(s)`);
2213
- updatedCount++;
2214
- } else {
2215
- console.log(color__default.dim(` Skipped ${category.label}`));
2216
- skippedCount++;
2217
- }
2218
- }
2219
- if (hasModified) {
2220
- console.log(color__default.cyan("AI Files (existing):"));
2221
- for (const change of modifiedChanges) {
2222
- console.log(formatFileChange(change));
2223
- }
2224
- console.log();
2225
- if (options.yes) {
2226
- console.log(color__default.dim(" (--yes mode: keeping existing AI files)"));
2227
- } else {
2228
- const updateExisting = await p__namespace.confirm({
2229
- message: "Update existing AI files to latest template?",
2230
- initialValue: false
2231
- });
2232
- if (!p__namespace.isCancel(updateExisting) && updateExisting) {
2233
- await applyUpdates(modifiedChanges, projectRoot);
2234
- console.log(color__default.green("\u2713") + " Updated existing AI files");
2235
- }
2236
- }
2237
- }
2238
- console.log();
2239
- continue;
2240
- }
2241
- let changesToApply = [];
2242
- if (options.yes) {
2243
- console.log(color__default.cyan(category.label + ":"));
2244
- for (const change of [...newChanges, ...modifiedChanges]) {
2245
- console.log(formatFileChange(change));
2246
- }
2247
- console.log();
2248
- if (category.category === "workspace-config") {
2249
- changesToApply = [...newChanges, ...modifiedChanges];
2250
- if (changesToApply.length > 0) {
2251
- console.log(color__default.dim(" (--yes mode: applying merge updates)"));
2252
- }
2253
- } else {
2254
- changesToApply = newChanges;
2255
- if (newChanges.length > 0) {
2256
- console.log(color__default.dim(" (--yes mode: adding new files only)"));
2257
- }
2258
- }
2259
- } else if (hasNew && hasModified) {
2260
- const allChanges = [...newChanges, ...modifiedChanges];
2261
- const selectedFiles = await p__namespace.multiselect({
2262
- message: `${category.label} (+ new, ~ changed)`,
2263
- options: allChanges.map((change) => ({
2264
- value: change.path,
2265
- label: change.status === "added" ? `+ ${change.path}` : `~ ${change.path}`
2266
- })),
2267
- initialValues: newChanges.map((c) => c.path),
2268
- // Pre-select new files
2269
- required: false
2270
- });
2271
- if (p__namespace.isCancel(selectedFiles)) {
2272
- p__namespace.cancel("Operation cancelled.");
2273
- process.exit(0);
1946
+ return orderUpdateCategories(allCategories);
1947
+ }
1948
+ async function processUpdateCategory(category, projectRoot, options) {
1949
+ const newChanges = category.changes.filter((change) => change.status === "added");
1950
+ const modifiedChanges = category.changes.filter((change) => change.status === "modified");
1951
+ const hasNew = newChanges.length > 0;
1952
+ const hasModified = modifiedChanges.length > 0;
1953
+ const hasChanges = hasNew || hasModified;
1954
+ if (!hasChanges) {
1955
+ console.log(color__default.green("\u2713") + ` ${category.label}: Up to date`);
1956
+ return "unchanged";
1957
+ }
1958
+ let changesToApply = [];
1959
+ if (options.yes) {
1960
+ console.log(color__default.cyan(`${category.label}:`));
1961
+ for (const change of [...newChanges, ...modifiedChanges]) {
1962
+ console.log(formatFileChange(change));
1963
+ }
1964
+ console.log();
1965
+ if (isMergeUpdateCategory(category.category)) {
1966
+ changesToApply = [...newChanges, ...modifiedChanges];
1967
+ if (changesToApply.length > 0) {
1968
+ console.log(color__default.dim(" (--yes mode: applying merge updates)"));
2274
1969
  }
2275
- if (selectedFiles.length > 0) {
2276
- changesToApply = allChanges.filter((c) => selectedFiles.includes(c.path));
1970
+ } else {
1971
+ changesToApply = newChanges;
1972
+ if (newChanges.length > 0) {
1973
+ console.log(color__default.dim(" (--yes mode: adding new files only)"));
2277
1974
  }
2278
- } else if (hasNew) {
2279
- console.log(color__default.cyan(category.label + ":"));
2280
- for (const change of newChanges) {
2281
- console.log(formatFileChange(change));
1975
+ }
1976
+ } else {
1977
+ changesToApply = category.category === "ai-files-install" ? await promptForAiFileInstall(category) : await promptForUpdateSelections(category);
1978
+ }
1979
+ if (changesToApply.length > 0) {
1980
+ await applyUpdates(changesToApply, projectRoot);
1981
+ const addedCount = changesToApply.filter((change) => change.status === "added").length;
1982
+ const updatedFilesCount = changesToApply.filter((change) => change.status === "modified").length;
1983
+ const parts = [];
1984
+ if (addedCount > 0) parts.push(`added ${addedCount}`);
1985
+ if (updatedFilesCount > 0) parts.push(`updated ${updatedFilesCount}`);
1986
+ console.log(color__default.green("\u2713") + ` ${category.label}: ${parts.join(", ")}`);
1987
+ console.log();
1988
+ return "updated";
1989
+ }
1990
+ console.log(color__default.dim(` Skipped ${category.label}`));
1991
+ console.log();
1992
+ return "skipped";
1993
+ }
1994
+ async function handleUpdateCommand(options, handleFixCommand) {
1995
+ const monorepoRoot = await detectMonorepoRoot();
1996
+ const projectRoot = monorepoRoot ?? await detectPackageRoot();
1997
+ if (!projectRoot) {
1998
+ console.log(color__default.red("\u2717") + " Could not find a project root");
1999
+ console.log(color__default.dim(" Run this command from inside a generated project"));
2000
+ process.exit(1);
2001
+ }
2002
+ const isMonorepo = monorepoRoot != null;
2003
+ if (isMonorepo) {
2004
+ const { valid, errors } = await validateWorkspace(projectRoot);
2005
+ if (!valid) {
2006
+ console.log(color__default.yellow("!") + " Workspace has issues:");
2007
+ for (const error of errors) {
2008
+ console.log(color__default.dim(` \u2022 ${error}`));
2282
2009
  }
2283
2010
  console.log();
2284
- const shouldAdd = await p__namespace.confirm({
2285
- message: `Add ${newChanges.length} new file(s)?`,
2011
+ const shouldFix = options.yes || await p__namespace.confirm({
2012
+ message: "Run fix first to resolve these issues?",
2286
2013
  initialValue: true
2287
2014
  });
2288
- if (p__namespace.isCancel(shouldAdd)) {
2289
- p__namespace.cancel("Operation cancelled.");
2290
- process.exit(0);
2291
- }
2292
- if (shouldAdd) {
2293
- changesToApply = newChanges;
2294
- }
2295
- } else if (hasModified) {
2296
- console.log(color__default.cyan(category.label + ":"));
2297
- for (const change of modifiedChanges) {
2298
- console.log(formatFileChange(change));
2015
+ if (p__namespace.isCancel(shouldFix) || !shouldFix) {
2016
+ console.log(color__default.dim(" Run `pnpm create krispya --fix` to fix manually"));
2017
+ process.exit(1);
2299
2018
  }
2300
- console.log();
2301
- const shouldUpdate = await p__namespace.confirm({
2302
- message: `Update ${modifiedChanges.length} file(s)? (will overwrite)`,
2303
- initialValue: false
2019
+ const preFixConfig = await detectCurrentConfig(projectRoot);
2020
+ await handleFixCommand({
2021
+ ...options,
2022
+ linter: options.linter ?? preFixConfig.linter,
2023
+ formatter: options.formatter ?? preFixConfig.formatter
2304
2024
  });
2305
- if (p__namespace.isCancel(shouldUpdate)) {
2306
- p__namespace.cancel("Operation cancelled.");
2307
- process.exit(0);
2308
- }
2309
- if (shouldUpdate) {
2310
- changesToApply = modifiedChanges;
2311
- }
2312
- }
2313
- if (changesToApply.length > 0) {
2314
- await applyUpdates(changesToApply, projectRoot);
2315
- const addedCount = changesToApply.filter((c) => c.status === "added").length;
2316
- const updatedFilesCount = changesToApply.filter((c) => c.status === "modified").length;
2317
- const parts = [];
2318
- if (addedCount > 0) parts.push(`added ${addedCount}`);
2319
- if (updatedFilesCount > 0) parts.push(`updated ${updatedFilesCount}`);
2320
- console.log(color__default.green("\u2713") + ` ${category.label}: ${parts.join(", ")}`);
2321
- updatedCount++;
2322
- } else {
2323
- console.log(color__default.dim(` Skipped ${category.label}`));
2324
- skippedCount++;
2325
2025
  }
2026
+ }
2027
+ const config = await detectCurrentConfig(projectRoot, isMonorepo);
2028
+ if (options.linter || options.formatter) {
2029
+ console.log(
2030
+ color__default.yellow("!") + " Linter/formatter migration is not part of --update in this architecture pass"
2031
+ );
2032
+ console.log(color__default.dim(" Continuing with updates for the detected current tooling"));
2326
2033
  console.log();
2327
2034
  }
2035
+ console.log(
2036
+ color__default.cyan("Checking for updates...") + color__default.dim(` (${config.linter}/${config.formatter})`)
2037
+ );
2038
+ console.log();
2039
+ const categories = await collectUpdateCategories(projectRoot, config, isMonorepo);
2040
+ let updatedCount = 0;
2041
+ let skippedCount = 0;
2042
+ for (const category of categories) {
2043
+ const result = await processUpdateCategory(category, projectRoot, options);
2044
+ if (result === "updated") updatedCount++;
2045
+ if (result === "skipped") skippedCount++;
2046
+ }
2328
2047
  if (updatedCount === 0 && skippedCount === 0) {
2329
2048
  console.log(color__default.green("\u2713") + " Everything is up to date!");
2330
2049
  } else if (updatedCount > 0) {
@@ -2337,7 +2056,103 @@ async function handleUpdateCommand(options) {
2337
2056
  }
2338
2057
  process.exit(0);
2339
2058
  }
2340
- async function handleWorkspaceCommand(name, options) {
2059
+
2060
+ const require$2 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href)));
2061
+ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedSettings, scope, writeGeneratedFiles) {
2062
+ const workspaceDirectories = await parseWorkspaceDirectories(monorepoRoot);
2063
+ const defaultDirectories = ["apps", "packages"];
2064
+ const hasCustomDirectories = workspaceDirectories.length > 0 && !workspaceDirectories.every((dir) => defaultDirectories.includes(dir));
2065
+ const packageType = await promptForInitialPackage();
2066
+ if (packageType === "skip") {
2067
+ return false;
2068
+ }
2069
+ const defaultDir = packageType === "app" ? "apps" : "packages";
2070
+ const packageNameInput = await p__namespace.text({
2071
+ message: "Package name?",
2072
+ initialValue: `@${scope}/`,
2073
+ validate: (value) => {
2074
+ const validationError = workspace.validatePackageName(value);
2075
+ if (validationError) return validationError;
2076
+ const dirName = value.includes("/") ? value.split("/").pop() : value;
2077
+ if (!dirName) return "Package name is required";
2078
+ if (!hasCustomDirectories) {
2079
+ const targetPath = node_path.join(monorepoRoot, defaultDir, dirName);
2080
+ try {
2081
+ const { statSync } = require$2("fs");
2082
+ statSync(targetPath);
2083
+ return `Directory ${defaultDir}/${dirName} already exists`;
2084
+ } catch {
2085
+ }
2086
+ }
2087
+ }
2088
+ });
2089
+ if (p__namespace.isCancel(packageNameInput)) {
2090
+ return false;
2091
+ }
2092
+ const scopedName = packageNameInput;
2093
+ const shortName = scopedName.includes("/") ? scopedName.split("/").pop() : scopedName;
2094
+ const packageOptions = await promptForPackageOptions(scopedName, packageType, inheritedSettings);
2095
+ let targetDir = defaultDir;
2096
+ if (hasCustomDirectories && workspaceDirectories.length > 0) {
2097
+ const dirChoice = await p__namespace.select({
2098
+ message: "Target directory",
2099
+ options: workspaceDirectories.map((dir) => ({
2100
+ value: dir,
2101
+ label: dir
2102
+ })),
2103
+ initialValue: workspaceDirectories.includes(defaultDir) ? defaultDir : workspaceDirectories[0]
2104
+ });
2105
+ if (p__namespace.isCancel(dirChoice)) {
2106
+ return false;
2107
+ }
2108
+ targetDir = dirChoice;
2109
+ const targetPath = node_path.join(monorepoRoot, targetDir, shortName);
2110
+ try {
2111
+ const { statSync } = require$2("fs");
2112
+ statSync(targetPath);
2113
+ p__namespace.log.error(`Directory ${targetDir}/${shortName} already exists`);
2114
+ return false;
2115
+ } catch {
2116
+ }
2117
+ }
2118
+ const relativePkgPath = node_path.join(targetDir, shortName);
2119
+ const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
2120
+ packageOptions.workspaceRoot = workspaceRoot;
2121
+ packageOptions.name = scopedName;
2122
+ const workspacePackages = packageType === "app" ? await getWorkspacePackages(monorepoRoot) : [];
2123
+ if (workspacePackages.length > 0) {
2124
+ const selectedDeps = await p__namespace.multiselect({
2125
+ message: "Add workspace dependencies?",
2126
+ options: workspacePackages.map((name) => ({ value: name, label: name })),
2127
+ required: false
2128
+ });
2129
+ if (!p__namespace.isCancel(selectedDeps) && selectedDeps.length > 0) {
2130
+ packageOptions.workspaceDependencies = selectedDeps;
2131
+ }
2132
+ }
2133
+ const outputPath = node_path.join(monorepoRoot, relativePkgPath);
2134
+ const spinner = p__namespace.spinner();
2135
+ spinner.start("Creating package...");
2136
+ try {
2137
+ const { files } = await workspace.planProject(workspace.resolveProjectPlanInput(packageOptions));
2138
+ await writeGeneratedFiles(outputPath, files);
2139
+ spinner.stop(color__default.green.inverse(` \u2713 Package created at ${relativePkgPath}! `));
2140
+ const addAnother = await p__namespace.select({
2141
+ message: "Add another package?",
2142
+ options: [
2143
+ { value: "no", label: "No, I'm done" },
2144
+ { value: "yes", label: "Yes, add another" }
2145
+ ],
2146
+ initialValue: "no"
2147
+ });
2148
+ return !p__namespace.isCancel(addAnother) && addAnother === "yes";
2149
+ } catch (error) {
2150
+ spinner.stop("Failed to create package");
2151
+ p__namespace.log.error(String(error));
2152
+ return false;
2153
+ }
2154
+ }
2155
+ async function handleWorkspaceCommand(name, options, writeGeneratedFiles) {
2341
2156
  const monorepoRoot = await detectMonorepoRoot();
2342
2157
  if (!monorepoRoot) {
2343
2158
  console.error(color__default.red("Error:") + " --workspace flag requires being inside a monorepo");
@@ -2354,7 +2169,7 @@ async function handleWorkspaceCommand(name, options) {
2354
2169
  const defaultDir = projectType === "library" ? "packages" : "apps";
2355
2170
  const targetDir = options.dir ?? defaultDir;
2356
2171
  const template = options.template ?? "vanilla";
2357
- const baseTemplate = index.getBaseTemplate(template);
2172
+ const baseTemplate = workspace.getBaseTemplate(template);
2358
2173
  const scopedName = name.startsWith("@") ? name : `@${scope}/${name}`;
2359
2174
  const fullPackagePath = node_path.join(monorepoRoot, targetDir, name);
2360
2175
  try {
@@ -2374,7 +2189,7 @@ async function handleWorkspaceCommand(name, options) {
2374
2189
  const isLibrary = projectType === "library";
2375
2190
  const relativePkgPath = node_path.join(targetDir, name);
2376
2191
  const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
2377
- const generateOptions = {
2192
+ const projectOptions = {
2378
2193
  name: scopedName,
2379
2194
  projectType,
2380
2195
  libraryBundler: isLibrary ? options.bundler ?? "unbuild" : void 0,
@@ -2400,12 +2215,9 @@ async function handleWorkspaceCommand(name, options) {
2400
2215
  triplex: options.triplex ? {} : void 0
2401
2216
  }
2402
2217
  };
2403
- generateOptions.packageManager = await index.resolvePackageManager(generateOptions);
2404
- generateOptions.engine = await index.resolveEngine(generateOptions);
2405
- generateOptions.versions = await index.resolveProjectPackageVersions(generateOptions);
2406
2218
  console.log(color__default.cyan("Creating") + ` ${scopedName} in ${targetDir}/${name}...`);
2407
2219
  try {
2408
- const files = index.generate(generateOptions);
2220
+ const { files } = await workspace.planProject(workspace.resolveProjectPlanInput(projectOptions));
2409
2221
  await writeGeneratedFiles(fullPackagePath, files);
2410
2222
  console.log(color__default.green("\u2713") + ` Created ${scopedName} at ${targetDir}/${name}`);
2411
2223
  process.exit(0);
@@ -2415,68 +2227,126 @@ async function handleWorkspaceCommand(name, options) {
2415
2227
  process.exit(1);
2416
2228
  }
2417
2229
  }
2418
- async function handleMonorepoCreation(generateOptions, isNonInteractive) {
2419
- const { generateMonorepo } = await import('./chunks/index.cjs').then(function (n) { return n.monorepo; });
2420
- const packageManager = index.getPackageManagerName(generateOptions.packageManager);
2421
- generateOptions.packageManager = await index.resolvePackageManager(generateOptions);
2422
- generateOptions.engine = await index.resolveEngine(generateOptions);
2423
- generateOptions.versions = await index.resolveMonorepoRootPackageVersions({
2424
- linter: generateOptions.linter ?? "oxlint",
2425
- formatter: generateOptions.formatter ?? "prettier",
2426
- engine: generateOptions.engine,
2427
- versions: generateOptions.versions
2230
+ async function handleInteractiveMonorepoMode(monorepoRoot, writeGeneratedFiles) {
2231
+ const choice = await p__namespace.select({
2232
+ message: "Detected monorepo workspace",
2233
+ options: [
2234
+ { value: "add", label: "Add new package to this workspace" },
2235
+ { value: "standalone", label: "Create single-package workspace" }
2236
+ ],
2237
+ initialValue: "add"
2428
2238
  });
2429
- const aiPlatforms = await promptForAiPlatforms(isNonInteractive);
2430
- const projectPath = node_path.join(node_process.cwd(), generateOptions.name);
2239
+ if (p__namespace.isCancel(choice)) {
2240
+ p__namespace.cancel("Operation cancelled.");
2241
+ process.exit(0);
2242
+ }
2243
+ if (choice === "add") {
2244
+ const inheritedSettings = await detectWorkspaceSettings(monorepoRoot);
2245
+ const hasSettings = Object.values(inheritedSettings).some(Boolean);
2246
+ if (hasSettings) {
2247
+ const settingsInfo = [
2248
+ inheritedSettings.linter && `linter: ${inheritedSettings.linter}`,
2249
+ inheritedSettings.formatter && `formatter: ${inheritedSettings.formatter}`,
2250
+ inheritedSettings.packageManager && `pm: ${inheritedSettings.packageManager.name}`
2251
+ ].filter(Boolean).join(", ");
2252
+ p__namespace.log.info(`Using workspace settings (${settingsInfo})`);
2253
+ }
2254
+ const scope = await getMonorepoScope(monorepoRoot);
2255
+ let addMore = true;
2256
+ while (addMore) {
2257
+ addMore = await createPackageInWorkspace(
2258
+ monorepoRoot,
2259
+ inheritedSettings.packageManager?.name ?? "pnpm",
2260
+ inheritedSettings,
2261
+ scope,
2262
+ writeGeneratedFiles
2263
+ );
2264
+ }
2265
+ p__namespace.note([`cd ${monorepoRoot}`, "pnpm install", "pnpm run dev"].join("\n"), "Next steps");
2266
+ p__namespace.outro(color__default.green("Happy coding! \u2728"));
2267
+ process.exit(0);
2268
+ }
2269
+ }
2270
+
2271
+ const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href)));
2272
+ const pkg = require$1("../package.json");
2273
+ const META_OPTIONS = [
2274
+ "clearConfig",
2275
+ "configPath",
2276
+ "check",
2277
+ "fix",
2278
+ "update",
2279
+ "yes",
2280
+ "workspace",
2281
+ "path",
2282
+ "dir"
2283
+ ];
2284
+ function hasConfigOptions(options) {
2285
+ return Object.keys(options).some(
2286
+ (key) => !META_OPTIONS.includes(key)
2287
+ );
2288
+ }
2289
+ async function writeGeneratedFiles(basePath, files) {
2290
+ const filePaths = Object.keys(files).sort();
2291
+ for (const filePath of filePaths) {
2292
+ const fullFilePath = node_path.join(basePath, filePath);
2293
+ await promises$1.mkdir(node_path.dirname(fullFilePath), { recursive: true });
2294
+ const file = files[filePath];
2295
+ if (file.type === "text") {
2296
+ await promises$1.writeFile(fullFilePath, file.content);
2297
+ } else {
2298
+ const response = await undici.fetch(file.url);
2299
+ await promises$1.writeFile(fullFilePath, response.body);
2300
+ }
2301
+ }
2302
+ }
2303
+ async function handleMonorepoCreation(projectOptions, isNonInteractive) {
2304
+ const packageManager = workspace.getPackageManagerName(projectOptions.packageManager);
2305
+ const projectPath = node_path.join(node_process.cwd(), projectOptions.name);
2431
2306
  const spinner = p__namespace.spinner();
2432
2307
  spinner.start("Creating monorepo workspace...");
2433
2308
  try {
2434
- const { files } = generateMonorepo({
2435
- name: generateOptions.name,
2436
- linter: generateOptions.linter ?? "oxlint",
2437
- formatter: generateOptions.formatter ?? "prettier",
2438
- packageManager: generateOptions.packageManager ?? {
2309
+ const planInput = workspace.resolveWorkspacePlanInput({
2310
+ name: projectOptions.name,
2311
+ linter: projectOptions.linter ?? "oxlint",
2312
+ formatter: projectOptions.formatter ?? "prettier",
2313
+ packageManager: projectOptions.packageManager ?? {
2439
2314
  name: packageManager
2440
2315
  },
2441
- pnpmManageVersions: generateOptions.pnpmManageVersions,
2442
- engine: generateOptions.engine,
2443
- versions: generateOptions.versions,
2444
- aiPlatforms: aiPlatforms.length > 0 ? aiPlatforms : void 0
2316
+ pnpmManageVersions: projectOptions.pnpmManageVersions,
2317
+ engine: projectOptions.engine,
2318
+ versions: projectOptions.versions,
2319
+ aiPlatforms: projectOptions.aiPlatforms,
2320
+ ide: projectOptions.ide ?? "vscode"
2445
2321
  });
2446
- const filePaths = Object.keys(files).sort();
2447
- for (const filePath of filePaths) {
2448
- const fullFilePath = node_path.join(projectPath, filePath);
2449
- await promises$1.mkdir(node_path.dirname(fullFilePath), { recursive: true });
2450
- const file = files[filePath];
2451
- if (file.type === "text") {
2452
- await promises$1.writeFile(fullFilePath, file.content);
2453
- }
2454
- }
2322
+ const { files } = await workspace.planWorkspace(planInput);
2323
+ await writeGeneratedFiles(projectPath, files);
2455
2324
  spinner.stop(color__default.green.inverse(" \u2713 Monorepo workspace created! "));
2456
2325
  if (isNonInteractive) {
2457
2326
  process.exit(0);
2458
2327
  }
2459
2328
  const newWorkspaceSettings = {
2460
- linter: generateOptions.linter,
2461
- formatter: generateOptions.formatter,
2462
- packageManager: generateOptions.packageManager ?? {
2329
+ linter: projectOptions.linter,
2330
+ formatter: projectOptions.formatter,
2331
+ packageManager: projectOptions.packageManager ?? {
2463
2332
  name: packageManager
2464
2333
  },
2465
- engine: generateOptions.engine,
2466
- pnpmManageVersions: generateOptions.pnpmManageVersions
2334
+ engine: projectOptions.engine,
2335
+ pnpmManageVersions: projectOptions.pnpmManageVersions
2467
2336
  };
2468
- const scope = generateOptions.name;
2337
+ const scope = projectOptions.name;
2469
2338
  let addMore = true;
2470
2339
  while (addMore) {
2471
2340
  addMore = await createPackageInWorkspace(
2472
2341
  projectPath,
2473
2342
  packageManager,
2474
2343
  newWorkspaceSettings,
2475
- scope
2344
+ scope,
2345
+ writeGeneratedFiles
2476
2346
  );
2477
2347
  }
2478
2348
  const nextSteps = [
2479
- `cd ${generateOptions.name}`,
2349
+ `cd ${projectOptions.name}`,
2480
2350
  `${packageManager} install`,
2481
2351
  `${packageManager} run dev`
2482
2352
  ].join("\n");
@@ -2489,33 +2359,26 @@ async function handleMonorepoCreation(generateOptions, isNonInteractive) {
2489
2359
  process.exit(1);
2490
2360
  }
2491
2361
  }
2492
- async function handleStandaloneProjectCreation(generateOptions, isNonInteractive) {
2493
- const base = generateOptions.template ? index.getBaseTemplate(generateOptions.template) : "vanilla";
2362
+ async function handleSingleWorkspaceCreation(projectOptions, isNonInteractive) {
2363
+ const base = projectOptions.template ? workspace.getBaseTemplate(projectOptions.template) : "vanilla";
2494
2364
  const defaultFallbackName = base === "vanilla" ? "vanilla-app" : base === "react" ? "react-app" : "react-three-app";
2495
- generateOptions.name ??= defaultFallbackName;
2496
- const aiPlatforms = await promptForAiPlatforms(isNonInteractive);
2497
- if (aiPlatforms.length > 0) {
2498
- generateOptions.aiPlatforms = aiPlatforms;
2499
- }
2500
- const packageManager = index.getPackageManagerName(generateOptions.packageManager);
2501
- const isLibrary = generateOptions.projectType === "library";
2502
- generateOptions.packageManager = await index.resolvePackageManager(generateOptions);
2503
- generateOptions.engine = await index.resolveEngine(generateOptions);
2504
- generateOptions.versions = await index.resolveProjectPackageVersions(generateOptions);
2505
- const projectPath = node_path.join(node_process.cwd(), generateOptions.name);
2365
+ projectOptions.name ??= defaultFallbackName;
2366
+ const packageManager = workspace.getPackageManagerName(projectOptions.packageManager);
2367
+ const projectPath = node_path.join(node_process.cwd(), projectOptions.name);
2506
2368
  const spinner = p__namespace.spinner();
2507
2369
  spinner.start("Creating project...");
2508
2370
  try {
2509
- const files = index.generate(generateOptions);
2371
+ const planInput = workspace.resolveProjectPlanInput(projectOptions);
2372
+ const { files } = await workspace.planProject(planInput);
2510
2373
  await writeGeneratedFiles(projectPath, files);
2511
2374
  spinner.stop(color__default.green.inverse(" \u2713 Project created! "));
2512
2375
  if (isNonInteractive) process.exit(0);
2513
- const nextSteps = isLibrary ? [
2514
- `cd ${generateOptions.name}`,
2376
+ const nextSteps = projectOptions.projectType === "library" ? [
2377
+ `cd ${projectOptions.name}`,
2515
2378
  `${packageManager} install`,
2516
2379
  `${packageManager} run build`
2517
2380
  ].join("\n") : [
2518
- `cd ${generateOptions.name}`,
2381
+ `cd ${projectOptions.name}`,
2519
2382
  `${packageManager} install`,
2520
2383
  `${packageManager} run dev`
2521
2384
  ].join("\n");
@@ -2527,45 +2390,6 @@ async function handleStandaloneProjectCreation(generateOptions, isNonInteractive
2527
2390
  process.exit(1);
2528
2391
  }
2529
2392
  }
2530
- async function handleInteractiveMonorepoMode(monorepoRoot) {
2531
- const choice = await p__namespace.select({
2532
- message: "Detected monorepo workspace",
2533
- options: [
2534
- { value: "add", label: "Add new package to this workspace" },
2535
- { value: "standalone", label: "Create standalone project" }
2536
- ],
2537
- initialValue: "add"
2538
- });
2539
- if (p__namespace.isCancel(choice)) {
2540
- p__namespace.cancel("Operation cancelled.");
2541
- process.exit(0);
2542
- }
2543
- if (choice === "add") {
2544
- const inheritedSettings = await detectWorkspaceSettings(monorepoRoot);
2545
- const hasSettings = Object.values(inheritedSettings).some(Boolean);
2546
- if (hasSettings) {
2547
- const settingsInfo = [
2548
- inheritedSettings.linter && `linter: ${inheritedSettings.linter}`,
2549
- inheritedSettings.formatter && `formatter: ${inheritedSettings.formatter}`,
2550
- inheritedSettings.packageManager && `pm: ${inheritedSettings.packageManager.name}`
2551
- ].filter(Boolean).join(", ");
2552
- p__namespace.log.info(`Using workspace settings (${settingsInfo})`);
2553
- }
2554
- const scope = await getMonorepoScope(monorepoRoot);
2555
- let addMore = true;
2556
- while (addMore) {
2557
- addMore = await createPackageInWorkspace(
2558
- monorepoRoot,
2559
- inheritedSettings.packageManager?.name ?? "pnpm",
2560
- inheritedSettings,
2561
- scope
2562
- );
2563
- }
2564
- p__namespace.note([`cd ${monorepoRoot}`, "pnpm install", "pnpm run dev"].join("\n"), "Next steps");
2565
- p__namespace.outro(color__default.green("Happy coding! \u2728"));
2566
- process.exit(0);
2567
- }
2568
- }
2569
2393
  async function main() {
2570
2394
  const program = new commander.Command().name("create-krispya").description("CLI for creating Vanilla, React, and React Three Fiber projects").argument("[name]", "name for the project").option("--type <type>", "project type: app or library (default: app)").option(
2571
2395
  "--bundler <bundler>",
@@ -2573,7 +2397,7 @@ async function main() {
2573
2397
  ).option(
2574
2398
  "--template <type>",
2575
2399
  "project template: vanilla, vanilla-js, react, react-js, r3f, r3f-js (default: vanilla)"
2576
- ).option("--linter <type>", "linter: eslint, oxlint, or biome (default: oxlint)").option("--formatter <type>", "formatter: prettier, oxfmt, or biome (default: prettier)").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("--package-manager <manager>", "specify package manager (e.g. npm, yarn, pnpm)").option(
2400
+ ).option("--linter <type>", "linter: eslint, oxlint, or biome (default: oxlint)").option("--formatter <type>", "formatter: prettier, oxfmt, or biome (default: prettier)").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("--package-manager <manager>", "specify package manager (e.g. npm, yarn, pnpm)").option("--ide <ide>", "IDE files: vscode or none (default: vscode)").option(
2577
2401
  "--pnpm-manage-versions",
2578
2402
  "enable manage-package-manager-versions in pnpm-workspace.yaml (default: true)"
2579
2403
  ).option(
@@ -2582,10 +2406,7 @@ async function main() {
2582
2406
  ).option(
2583
2407
  "--node-version <version>",
2584
2408
  'set Node.js version for engines.node field (default: "latest")'
2585
- ).option("--workspace", "Add package to current monorepo workspace (non-interactive)").option("--dir <directory>", "Target directory for --workspace (default: apps/ or packages/)").option("--clear-config", "Clear saved preferences").option("--config-path", "Print the path to the config file").option("--check", "Check if current directory is in a valid monorepo workspace").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(
2586
- "--path <directory>",
2587
- "Run in specified directory instead of current working directory"
2588
- ).action(async (name, options) => {
2409
+ ).option("--workspace", "Add package to current monorepo workspace (non-interactive)").option("--dir <directory>", "Target directory for --workspace (default: apps/ or packages/)").option("--clear-config", "Clear saved preferences").option("--config-path", "Print the path to the config file").option("--check", "Check if current directory is in a valid monorepo workspace").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("--path <directory>", "Run in specified directory instead of current working directory").action(async (name, options) => {
2589
2410
  if (options.path) {
2590
2411
  process.chdir(options.path);
2591
2412
  }
@@ -2598,6 +2419,10 @@ async function main() {
2598
2419
  console.log(getConfigPath());
2599
2420
  process.exit(0);
2600
2421
  }
2422
+ if (options.ide && !["vscode", "none"].includes(options.ide)) {
2423
+ console.error(color__default.red("Error:") + ' --ide must be "vscode" or "none"');
2424
+ process.exit(1);
2425
+ }
2601
2426
  if (name?.startsWith("-")) {
2602
2427
  switch (name) {
2603
2428
  case "--version":
@@ -2639,37 +2464,36 @@ async function main() {
2639
2464
  await handleFixCommand(options);
2640
2465
  }
2641
2466
  if (options.update) {
2642
- await handleUpdateCommand(options);
2467
+ await handleUpdateCommand(options, handleFixCommand);
2643
2468
  }
2644
2469
  if (options.dir && !options.workspace) {
2645
2470
  console.error(color__default.red("Error:") + " --dir requires --workspace flag");
2646
- console.log(
2647
- color__default.dim(" Example: pnpm create krispya my-lib --workspace --dir examples")
2648
- );
2471
+ console.log(color__default.dim(" Example: pnpm create krispya my-lib --workspace --dir examples"));
2649
2472
  process.exit(1);
2650
2473
  }
2651
2474
  if (options.workspace) {
2652
- await handleWorkspaceCommand(name, options);
2475
+ await handleWorkspaceCommand(name, options, writeGeneratedFiles);
2653
2476
  }
2654
2477
  console.clear();
2655
2478
  p__namespace.intro(color__default.bgCyan(color__default.black(` create-krispya v${pkg.version} `)));
2656
2479
  const monorepoRoot = await detectMonorepoRoot();
2657
2480
  if (monorepoRoot && !hasConfigOptions(options)) {
2658
- await handleInteractiveMonorepoMode(monorepoRoot);
2481
+ await handleInteractiveMonorepoMode(monorepoRoot, writeGeneratedFiles);
2659
2482
  }
2660
- let generateOptions;
2483
+ let projectOptions;
2661
2484
  if (options.yes) {
2662
2485
  const template = options.template ?? "vanilla";
2663
- const baseTemplate = index.getBaseTemplate(template);
2486
+ const baseTemplate = workspace.getBaseTemplate(template);
2664
2487
  const defaultName = getDefaultProjectName(template);
2665
2488
  const projectType = options.type ?? "app";
2666
- generateOptions = {
2489
+ projectOptions = {
2667
2490
  name: name || defaultName,
2668
2491
  projectType,
2669
2492
  libraryBundler: projectType === "library" ? options.bundler ?? "unbuild" : void 0,
2670
2493
  template,
2671
2494
  linter: options.linter ?? "oxlint",
2672
2495
  formatter: options.formatter ?? "prettier",
2496
+ ide: options.ide ?? "vscode",
2673
2497
  ...baseTemplate === "r3f" && {
2674
2498
  drei: options.drei ? {} : void 0,
2675
2499
  handle: options.handle ? {} : void 0,
@@ -2696,6 +2520,7 @@ async function main() {
2696
2520
  linter: options.linter,
2697
2521
  formatter: options.formatter,
2698
2522
  packageManager: options.packageManager,
2523
+ ide: options.ide,
2699
2524
  engine: options.nodeVersion ? { name: "node", version: options.nodeVersion } : void 0,
2700
2525
  pnpmManageVersions: options.pnpmManageVersions,
2701
2526
  drei: options.drei,
@@ -2711,13 +2536,17 @@ async function main() {
2711
2536
  triplex: options.triplex,
2712
2537
  viverse: options.viverse
2713
2538
  } : void 0;
2714
- generateOptions = await promptForOptions(name, presets);
2539
+ projectOptions = await promptForOptions(name, presets);
2715
2540
  }
2716
2541
  const isNonInteractive = options.yes ?? false;
2717
- if (generateOptions.projectType === "monorepo") {
2718
- await handleMonorepoCreation(generateOptions, isNonInteractive);
2542
+ const aiAgentPlatforms = await promptForAiAgentPlatforms(isNonInteractive);
2543
+ if (aiAgentPlatforms.length > 0) {
2544
+ projectOptions.aiPlatforms = aiAgentPlatforms;
2545
+ }
2546
+ if (projectOptions.projectType === "monorepo") {
2547
+ await handleMonorepoCreation(projectOptions, isNonInteractive);
2719
2548
  } else {
2720
- await handleStandaloneProjectCreation(generateOptions, isNonInteractive);
2549
+ await handleSingleWorkspaceCreation(projectOptions, isNonInteractive);
2721
2550
  }
2722
2551
  });
2723
2552
  await program.parseAsync();