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