create-krispya 0.8.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,18 +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 child_process = require('child_process');
14
- const index = require('./chunks/index.cjs');
12
+ const workspace = require('./shared/create-krispya.DTHeUlq4.cjs');
15
13
  const Conf = require('conf');
16
14
  const promises = require('fs/promises');
17
15
  const fs = require('fs');
18
16
  const path = require('path');
17
+ const node_fs = require('node:fs');
19
18
 
20
19
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
21
20
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
@@ -36,31 +35,6 @@ const p__namespace = /*#__PURE__*/_interopNamespaceCompat(p);
36
35
  const color__default = /*#__PURE__*/_interopDefaultCompat(color);
37
36
  const Conf__default = /*#__PURE__*/_interopDefaultCompat(Conf);
38
37
 
39
- const editorNames = {
40
- cursor: "Cursor",
41
- code: "VS Code",
42
- webstorm: "WebStorm",
43
- skip: "Skip"
44
- };
45
- function openInEditor(editor, path, reuseWindow) {
46
- return new Promise((resolve, reject) => {
47
- const isWindows = process.platform === "win32";
48
- const useReuseFlag = reuseWindow && (editor === "cursor" || editor === "code");
49
- const args = useReuseFlag ? ["-r", path] : [path];
50
- const child = isWindows ? child_process.spawn(`${editor} ${useReuseFlag ? "-r " : ""}"${path}"`, {
51
- detached: true,
52
- stdio: "ignore",
53
- shell: true
54
- }) : child_process.spawn(editor, args, {
55
- detached: true,
56
- stdio: "ignore"
57
- });
58
- child.on("error", reject);
59
- child.unref();
60
- setTimeout(resolve, 100);
61
- });
62
- }
63
-
64
38
  function formatConfigSummary(options, inherited) {
65
39
  const lines = [];
66
40
  const VALUE_COL = 27;
@@ -75,13 +49,13 @@ function formatConfigSummary(options, inherited) {
75
49
  return lang === "typescript" ? "TypeScript" : lang === "javascript" ? "JavaScript" : lang;
76
50
  };
77
51
  const projectType = options.projectType ?? "app";
78
- const baseTemplate = options.template ? index.getBaseTemplate(options.template) : "vanilla";
52
+ const baseTemplate = options.template ? workspace.getBaseTemplate(options.template) : "vanilla";
79
53
  if (baseTemplate === "react") {
80
54
  lines.push(formatRow("Framework", "React"));
81
55
  } else if (baseTemplate === "r3f") {
82
56
  lines.push(formatRow("Framework", "React Three Fiber"));
83
57
  }
84
- const language = options.template ? index.getLanguageFromTemplate(options.template) : "typescript";
58
+ const language = options.template ? workspace.getLanguageFromTemplate(options.template) : "typescript";
85
59
  lines.push(formatRow("Language", formatLanguage(language)));
86
60
  if (projectType === "library") {
87
61
  lines.push(formatRow("Bundler", options.libraryBundler ?? "unbuild"));
@@ -92,15 +66,15 @@ function formatConfigSummary(options, inherited) {
92
66
  lines.push(
93
67
  formatRow(
94
68
  "Engine",
95
- `${index.getEngineName(options.engine)}@${options.engine?.version ?? "latest"}`,
69
+ `${workspace.getEngineName(options.engine)}@${options.engine?.version ?? "latest"}`,
96
70
  engineInherited
97
71
  )
98
72
  );
99
73
  const pmInherited = inherited?.packageManager !== void 0;
100
74
  lines.push(
101
- formatRow("Package manager", index.getPackageManagerName(options.packageManager), pmInherited)
75
+ formatRow("Package manager", workspace.getPackageManagerName(options.packageManager), pmInherited)
102
76
  );
103
- if (index.getPackageManagerName(options.packageManager) === "pnpm") {
77
+ if (workspace.getPackageManagerName(options.packageManager) === "pnpm") {
104
78
  const versionManaged = options.pnpmManageVersions ? "yes" : "no";
105
79
  const pnpmVersionInherited = inherited?.pnpmManageVersions !== void 0;
106
80
  lines.push(formatRow("\u21B3 Version managed", versionManaged, pnpmVersionInherited, ""));
@@ -116,10 +90,13 @@ function formatConfigSummary(options, inherited) {
116
90
  const testing = options.testing ?? (projectType === "library" ? "vitest" : "none");
117
91
  lines.push(formatRow("Testing", testing));
118
92
  if (!inherited) {
93
+ lines.push(
94
+ formatRow("Editor config", options.ide === "none" ? "generic" : "generic + vscode")
95
+ );
119
96
  const configStrategy = options.configStrategy ?? "stealth";
120
97
  lines.push(formatRow("Config strategy", configStrategy));
121
98
  }
122
- if (options.template && index.getBaseTemplate(options.template) === "r3f") {
99
+ if (options.template && workspace.getBaseTemplate(options.template) === "r3f") {
123
100
  const integrationNames = [
124
101
  options.drei && "drei",
125
102
  options.handle && "handle",
@@ -135,7 +112,7 @@ function formatConfigSummary(options, inherited) {
135
112
  options.viverse && "viverse"
136
113
  ].filter(Boolean);
137
114
  lines.push("");
138
- lines.push(color__default.dim("Integrations"));
115
+ lines.push(color__default.dim("Features"));
139
116
  for (let i = 0; i < integrationNames.length; i += 2) {
140
117
  const left = `${color__default.green("\u25CF")} ${integrationNames[i]}`;
141
118
  const right = integrationNames[i + 1] ? `${color__default.green("\u25CF")} ${integrationNames[i + 1]}` : "";
@@ -155,7 +132,7 @@ function formatMonorepoConfigSummary(options) {
155
132
  return `${indent}${label} ${dots} ${value}`;
156
133
  };
157
134
  lines.push(
158
- formatRow("Engine", `${index.getEngineName(options.engine)}@${options.engine.version ?? "latest"}`)
135
+ formatRow("Engine", `${workspace.getEngineName(options.engine)}@${options.engine.version ?? "latest"}`)
159
136
  );
160
137
  lines.push(formatRow("Package manager", options.packageManager));
161
138
  if (options.packageManager === "pnpm") {
@@ -164,24 +141,13 @@ function formatMonorepoConfigSummary(options) {
164
141
  }
165
142
  lines.push(formatRow("Linter", options.linter));
166
143
  lines.push(formatRow("Formatter", options.formatter));
144
+ lines.push(formatRow("Editor config", options.ide === "none" ? "generic" : "generic + vscode"));
167
145
  return lines.join("\n");
168
146
  }
169
147
 
170
148
  const config = new Conf__default({
171
149
  projectName: "create-krispya"
172
150
  });
173
- function getPreferredEditor() {
174
- return config.get("preferredEditor");
175
- }
176
- function setPreferredEditor(editor) {
177
- config.set("preferredEditor", editor);
178
- }
179
- function getReuseWindow() {
180
- return config.get("reuseWindow") ?? false;
181
- }
182
- function setReuseWindow(reuse) {
183
- config.set("reuseWindow", reuse);
184
- }
185
151
  function getAiPlatforms() {
186
152
  return config.get("aiPlatforms");
187
153
  }
@@ -194,12 +160,58 @@ function clearConfig() {
194
160
  function getConfigPath() {
195
161
  return config.path;
196
162
  }
197
- function getCustomTemplates() {
198
- return config.get("customTemplates") ?? {};
199
- }
200
163
 
201
- function getDefaultOptions(template, name, projectType = "app", libraryBundler, integrations, inheritedSettings) {
202
- 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);
203
215
  const base = {
204
216
  name,
205
217
  template,
@@ -212,71 +224,30 @@ function getDefaultOptions(template, name, projectType = "app", libraryBundler,
212
224
  formatter: inheritedSettings?.formatter ?? "prettier",
213
225
  // Libraries get vitest by default, apps don't
214
226
  testing: projectType === "library" ? "vitest" : "none",
215
- configStrategy: getConfigStrategy()
227
+ configStrategy: getConfigStrategy(),
228
+ ide: "vscode"
229
+ };
230
+ return {
231
+ ...base,
232
+ ...baseTemplate === "r3f" ? getR3fIntegrationFlags(features) : {}
216
233
  };
217
- if (baseTemplate === "r3f" && integrations) {
218
- return {
219
- ...base,
220
- drei: integrations.includes("drei") ? {} : void 0,
221
- handle: integrations.includes("handle") ? {} : void 0,
222
- leva: integrations.includes("leva") ? {} : void 0,
223
- postprocessing: integrations.includes("postprocessing") ? {} : void 0,
224
- rapier: integrations.includes("rapier") ? {} : void 0,
225
- xr: integrations.includes("xr") ? {} : void 0,
226
- uikit: integrations.includes("uikit") ? {} : void 0,
227
- offscreen: integrations.includes("offscreen") ? {} : void 0,
228
- zustand: integrations.includes("zustand") ? {} : void 0,
229
- koota: integrations.includes("koota") ? {} : void 0,
230
- triplex: integrations.includes("triplex") ? {} : void 0,
231
- viverse: integrations.includes("viverse") ? {} : void 0
232
- };
233
- }
234
- return base;
235
234
  }
236
235
  function getDefaultProjectName(template) {
237
- const base = index.getBaseTemplate(template);
236
+ const base = workspace.getBaseTemplate(template);
238
237
  switch (base) {
239
238
  case "vanilla":
240
- return `vanilla-${index.generateRandomName()}`;
239
+ return `vanilla-${workspace.generateRandomName()}`;
241
240
  case "react":
242
- return `react-${index.generateRandomName()}`;
241
+ return `react-${workspace.generateRandomName()}`;
243
242
  case "r3f":
244
- return `react-three-${index.generateRandomName()}`;
243
+ return `react-three-${workspace.generateRandomName()}`;
245
244
  }
246
245
  }
247
246
  async function promptForR3fIntegrations(presets) {
248
- const initialValues = [];
249
- if (presets) {
250
- if (presets.drei) initialValues.push("drei");
251
- if (presets.handle) initialValues.push("handle");
252
- if (presets.leva) initialValues.push("leva");
253
- if (presets.postprocessing) initialValues.push("postprocessing");
254
- if (presets.rapier) initialValues.push("rapier");
255
- if (presets.xr) initialValues.push("xr");
256
- if (presets.uikit) initialValues.push("uikit");
257
- if (presets.offscreen) initialValues.push("offscreen");
258
- if (presets.zustand) initialValues.push("zustand");
259
- if (presets.koota) initialValues.push("koota");
260
- if (presets.triplex) initialValues.push("triplex");
261
- if (presets.viverse) initialValues.push("viverse");
262
- }
263
247
  const selected = await p__namespace.multiselect({
264
- message: "R3F integrations",
265
- options: [
266
- { value: "drei", label: "Drei" },
267
- { value: "handle", label: "Handle" },
268
- { value: "leva", label: "Leva" },
269
- { value: "postprocessing", label: "Postprocessing" },
270
- { value: "rapier", label: "Rapier" },
271
- { value: "xr", label: "XR" },
272
- { value: "uikit", label: "UIKit" },
273
- { value: "offscreen", label: "Offscreen" },
274
- { value: "zustand", label: "Zustand" },
275
- { value: "koota", label: "Koota" },
276
- { value: "triplex", label: "Triplex" },
277
- { value: "viverse", label: "Viverse" }
278
- ],
279
- initialValues: initialValues.length > 0 ? initialValues : ["drei"],
248
+ message: "R3F features",
249
+ options: [...R3F_INTEGRATION_OPTIONS],
250
+ initialValues: getInitialR3fIntegrations(presets),
280
251
  required: false
281
252
  });
282
253
  if (p__namespace.isCancel(selected)) {
@@ -285,7 +256,7 @@ async function promptForR3fIntegrations(presets) {
285
256
  }
286
257
  return selected;
287
258
  }
288
- async function promptForCustomization(template, name, projectType, integrations, inheritedSettings, presets) {
259
+ async function promptForCustomization(template, name, projectType, features, inheritedSettings, presets) {
289
260
  let libraryBundler;
290
261
  if (projectType === "library") {
291
262
  const bundler = await p__namespace.select({
@@ -420,7 +391,19 @@ async function promptForCustomization(template, name, projectType, integrations,
420
391
  p__namespace.cancel("Operation cancelled.");
421
392
  process.exit(0);
422
393
  }
423
- 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);
424
407
  const finalTemplate = language === "javascript" ? `${baseTemplate}-js` : baseTemplate;
425
408
  const base = {
426
409
  name,
@@ -433,26 +416,13 @@ async function promptForCustomization(template, name, projectType, integrations,
433
416
  linter,
434
417
  formatter,
435
418
  testing,
436
- configStrategy: configStrategyChoice
419
+ configStrategy: configStrategyChoice,
420
+ ide: ideChoice
421
+ };
422
+ return {
423
+ ...base,
424
+ ...baseTemplate === "r3f" ? getR3fIntegrationFlags(features) : {}
437
425
  };
438
- if (baseTemplate === "r3f" && integrations) {
439
- return {
440
- ...base,
441
- drei: integrations.includes("drei") ? {} : void 0,
442
- handle: integrations.includes("handle") ? {} : void 0,
443
- leva: integrations.includes("leva") ? {} : void 0,
444
- postprocessing: integrations.includes("postprocessing") ? {} : void 0,
445
- rapier: integrations.includes("rapier") ? {} : void 0,
446
- xr: integrations.includes("xr") ? {} : void 0,
447
- uikit: integrations.includes("uikit") ? {} : void 0,
448
- offscreen: integrations.includes("offscreen") ? {} : void 0,
449
- zustand: integrations.includes("zustand") ? {} : void 0,
450
- koota: integrations.includes("koota") ? {} : void 0,
451
- triplex: integrations.includes("triplex") ? {} : void 0,
452
- viverse: integrations.includes("viverse") ? {} : void 0
453
- };
454
- }
455
- return base;
456
426
  }
457
427
  async function promptForInitialPackage() {
458
428
  const choice = await p__namespace.select({
@@ -478,7 +448,8 @@ function getDefaultMonorepoOptions(name) {
478
448
  pnpmManageVersions: true,
479
449
  engine: { name: "node", version: "latest" },
480
450
  linter: "oxlint",
481
- formatter: "prettier"
451
+ formatter: "prettier",
452
+ ide: "vscode"
482
453
  };
483
454
  }
484
455
  async function promptForMonorepoCustomization(name, presets) {
@@ -531,6 +502,18 @@ async function promptForMonorepoCustomization(name, presets) {
531
502
  p__namespace.cancel("Operation cancelled.");
532
503
  process.exit(0);
533
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
+ }
534
517
  return {
535
518
  name,
536
519
  projectType: "monorepo",
@@ -538,7 +521,8 @@ async function promptForMonorepoCustomization(name, presets) {
538
521
  packageManager: { name: "pnpm" },
539
522
  pnpmManageVersions: managePnpm,
540
523
  linter,
541
- formatter
524
+ formatter,
525
+ ide
542
526
  };
543
527
  }
544
528
  async function promptForMonorepo(workspaceName, presets) {
@@ -546,6 +530,7 @@ async function promptForMonorepo(workspaceName, presets) {
546
530
  if (presets) {
547
531
  if (presets.linter) defaultOptions.linter = presets.linter;
548
532
  if (presets.formatter) defaultOptions.formatter = presets.formatter;
533
+ if (presets.ide) defaultOptions.ide = presets.ide;
549
534
  if (presets.engine) defaultOptions.engine = presets.engine;
550
535
  if (presets.pnpmManageVersions !== void 0)
551
536
  defaultOptions.pnpmManageVersions = presets.pnpmManageVersions;
@@ -554,26 +539,15 @@ async function promptForMonorepo(workspaceName, presets) {
554
539
  formatMonorepoConfigSummary({
555
540
  name: defaultOptions.name,
556
541
  engine: defaultOptions.engine ?? { name: "node", version: "latest" },
557
- packageManager: index.getPackageManagerName(defaultOptions.packageManager),
542
+ packageManager: workspace.getPackageManagerName(defaultOptions.packageManager),
558
543
  pnpmManageVersions: defaultOptions.pnpmManageVersions,
559
544
  linter: defaultOptions.linter ?? "oxlint",
560
- formatter: defaultOptions.formatter ?? "prettier"
545
+ formatter: defaultOptions.formatter ?? "prettier",
546
+ ide: defaultOptions.ide ?? "vscode"
561
547
  }),
562
548
  "Workspace Configuration"
563
549
  );
564
- const proceed = await p__namespace.select({
565
- message: "Proceed with these settings?",
566
- options: [
567
- { value: "continue", label: "Yes, continue" },
568
- { value: "customize", label: "No, customize settings" }
569
- ],
570
- initialValue: "continue"
571
- });
572
- if (p__namespace.isCancel(proceed)) {
573
- p__namespace.cancel("Operation cancelled.");
574
- process.exit(0);
575
- }
576
- if (proceed === "continue") {
550
+ if (await promptForProceed()) {
577
551
  return defaultOptions;
578
552
  }
579
553
  return promptForMonorepoCustomization(workspaceName, presets);
@@ -583,8 +557,8 @@ async function promptForOptions(name, presets) {
583
557
  if (!projectName) {
584
558
  const nameResult = await p__namespace.text({
585
559
  message: "What is your project named?",
586
- placeholder: index.generateRandomName(),
587
- defaultValue: index.generateRandomName(),
560
+ placeholder: workspace.generateRandomName(),
561
+ defaultValue: workspace.generateRandomName(),
588
562
  validate: (value) => {
589
563
  if (!value.length) return "Project name is required";
590
564
  }
@@ -600,7 +574,7 @@ async function promptForOptions(name, presets) {
600
574
  options: [
601
575
  { value: "app", label: "Application" },
602
576
  { value: "library", label: "Library" },
603
- { value: "monorepo", label: "Monorepo" }
577
+ { value: "monorepo", label: "Monorepo", hint: "experimental" }
604
578
  ],
605
579
  initialValue: presets?.type ?? "app"
606
580
  });
@@ -613,41 +587,6 @@ async function promptForOptions(name, presets) {
613
587
  }
614
588
  return promptForPackageOptions(projectName, projectType, void 0, presets);
615
589
  }
616
- function customTemplateToOptions(customTemplate, name, projectType, inheritedSettings) {
617
- const baseTemplate = customTemplate.baseTemplate;
618
- const template = baseTemplate;
619
- const base = {
620
- name,
621
- template,
622
- projectType,
623
- packageManager: inheritedSettings?.packageManager ?? { name: "pnpm" },
624
- pnpmManageVersions: inheritedSettings?.pnpmManageVersions ?? true,
625
- engine: inheritedSettings?.engine ?? { name: "node", version: "latest" },
626
- linter: inheritedSettings?.linter ?? customTemplate.linter,
627
- formatter: inheritedSettings?.formatter ?? customTemplate.formatter,
628
- testing: customTemplate.testing,
629
- configStrategy: customTemplate.configStrategy ?? getConfigStrategy()
630
- };
631
- if (baseTemplate === "r3f" && customTemplate.integrations) {
632
- const integrations = customTemplate.integrations;
633
- return {
634
- ...base,
635
- drei: integrations.includes("drei") ? {} : void 0,
636
- handle: integrations.includes("handle") ? {} : void 0,
637
- leva: integrations.includes("leva") ? {} : void 0,
638
- postprocessing: integrations.includes("postprocessing") ? {} : void 0,
639
- rapier: integrations.includes("rapier") ? {} : void 0,
640
- xr: integrations.includes("xr") ? {} : void 0,
641
- uikit: integrations.includes("uikit") ? {} : void 0,
642
- offscreen: integrations.includes("offscreen") ? {} : void 0,
643
- zustand: integrations.includes("zustand") ? {} : void 0,
644
- koota: integrations.includes("koota") ? {} : void 0,
645
- triplex: integrations.includes("triplex") ? {} : void 0,
646
- viverse: integrations.includes("viverse") ? {} : void 0
647
- };
648
- }
649
- return base;
650
- }
651
590
  function presetsToInheritedSettings(presets) {
652
591
  if (!presets) return void 0;
653
592
  return {
@@ -659,798 +598,138 @@ function presetsToInheritedSettings(presets) {
659
598
  };
660
599
  }
661
600
  async function promptForPackageOptions(projectName, projectType, inheritedSettings, presets) {
662
- const builtInOptions = [
663
- { value: "vanilla", label: "Vanilla" },
664
- { value: "react", label: "React" },
665
- { value: "r3f", label: "React Three Fiber" }
666
- ];
667
- const customTemplates = getCustomTemplates();
668
- const customOptions = Object.keys(customTemplates).map((name) => ({
669
- value: `custom:${name}`,
670
- label: name,
671
- hint: "saved template"
672
- }));
673
- const allOptions = [...builtInOptions, ...customOptions];
674
601
  const templateSelection = await p__namespace.select({
675
602
  message: "Select a template",
676
- options: allOptions,
677
- 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"
678
609
  });
679
610
  if (p__namespace.isCancel(templateSelection)) {
680
611
  p__namespace.cancel("Operation cancelled.");
681
612
  process.exit(0);
682
613
  }
683
- const selection = templateSelection;
684
- if (selection.startsWith("custom:")) {
685
- const customName = selection.slice(7);
686
- const customTemplate = customTemplates[customName];
687
- const defaultOptions2 = customTemplateToOptions(
688
- customTemplate,
689
- projectName,
690
- projectType,
691
- inheritedSettings
692
- );
693
- const configTitle2 = inheritedSettings ? `Template: ${customName} (using workspace settings)` : `Template: ${customName}`;
694
- p__namespace.note(formatConfigSummary(defaultOptions2, inheritedSettings), configTitle2);
695
- const proceed2 = await p__namespace.select({
696
- message: "Proceed with these settings?",
697
- options: [
698
- { value: "continue", label: "Yes, continue" },
699
- { value: "customize", label: "No, customize settings" }
700
- ],
701
- initialValue: "continue"
702
- });
703
- if (p__namespace.isCancel(proceed2)) {
704
- p__namespace.cancel("Operation cancelled.");
705
- process.exit(0);
706
- }
707
- if (proceed2 === "continue") {
708
- return defaultOptions2;
709
- }
710
- return promptForCustomization(
711
- customTemplate.baseTemplate,
712
- projectName,
713
- projectType,
714
- customTemplate.integrations,
715
- inheritedSettings
716
- );
717
- }
718
- const template = selection;
719
- const baseTemplate = index.getBaseTemplate(template);
720
- let integrations;
614
+ const template = templateSelection;
615
+ const baseTemplate = workspace.getBaseTemplate(template);
616
+ let features;
721
617
  if (baseTemplate === "r3f") {
722
- integrations = await promptForR3fIntegrations(presets);
618
+ features = await promptForR3fIntegrations(presets);
723
619
  }
724
620
  const defaultOptions = getDefaultOptions(
725
621
  template,
726
622
  projectName,
727
623
  projectType,
728
624
  presets?.bundler,
729
- integrations,
625
+ features,
730
626
  inheritedSettings ?? presetsToInheritedSettings(presets)
731
627
  );
628
+ if (presets?.ide && !inheritedSettings) {
629
+ defaultOptions.ide = presets.ide;
630
+ }
732
631
  const configTitle = inheritedSettings ? "Template Configuration (using workspace settings)" : "Template Configuration";
733
632
  p__namespace.note(formatConfigSummary(defaultOptions, inheritedSettings), configTitle);
734
- const proceed = await p__namespace.select({
735
- message: "Proceed with these settings?",
736
- options: [
737
- { value: "continue", label: "Yes, continue" },
738
- { value: "customize", label: "No, customize settings" }
739
- ],
740
- initialValue: "continue"
741
- });
742
- if (p__namespace.isCancel(proceed)) {
743
- p__namespace.cancel("Operation cancelled.");
744
- process.exit(0);
745
- }
746
- if (proceed === "continue") {
633
+ if (await promptForProceed()) {
747
634
  return defaultOptions;
748
635
  }
749
636
  return promptForCustomization(
750
637
  template,
751
638
  projectName,
752
639
  projectType,
753
- integrations,
640
+ features,
754
641
  inheritedSettings,
755
642
  presets
756
643
  );
757
644
  }
758
645
 
759
- async function detectCurrentConfig(root, isMonorepo = true) {
760
- let name = root.split(/[/\\]/).pop() ?? "workspace";
761
- let packageManager = "pnpm";
762
- try {
763
- const pkgPath = path.join(root, "package.json");
764
- const content = await promises.readFile(pkgPath, "utf-8");
765
- const pkgJson = JSON.parse(content);
766
- if (pkgJson.name) {
767
- 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 [];
768
659
  }
769
- if (pkgJson.packageManager) {
770
- packageManager = pkgJson.packageManager.split("@")[0] ?? packageManager;
660
+ if (useDefault) {
661
+ return savedPlatforms;
771
662
  }
772
- } catch {
773
663
  }
774
- const tooling = await index.detectTooling(root);
775
- const configStrategy = isMonorepo ? void 0 : await detectStandaloneConfigStrategy(root);
776
- return {
777
- name,
778
- linter: tooling.linter ?? "oxlint",
779
- formatter: tooling.formatter ?? "prettier",
780
- packageManager,
781
- isMonorepo,
782
- configStrategy
783
- };
784
- }
785
- async function detectStandaloneConfigStrategy(root) {
786
- const hasStealthConfig = await Promise.all([
787
- fileExists$1(path.join(root, ".config/tsconfig.app.json")),
788
- fileExists$1(path.join(root, ".config/tsconfig.node.json")),
789
- fileExists$1(path.join(root, ".config/prettier.json")),
790
- fileExists$1(path.join(root, ".config/oxlint.json"))
791
- ]).then((matches) => matches.some(Boolean));
792
- return hasStealthConfig ? "stealth" : "root";
793
- }
794
- async function generateExpectedFiles(config) {
795
- const { name, linter, formatter, packageManager, isMonorepo, configStrategy } = config;
796
- const versions = linter === "biome" || formatter === "biome" ? await index.resolveMonorepoRootPackageVersions({ linter, formatter }) : {};
797
- const aiFilesMap = {};
798
- index.generateAiFiles(aiFilesMap, {
799
- name,
800
- packageManager,
801
- linter,
802
- formatter,
803
- isMonorepo,
804
- configStrategy,
805
- 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
806
673
  });
807
- const vscodeFiles = {};
808
- index.generateVscodeFiles(vscodeFiles, linter, formatter);
809
- const configPackages = {};
810
- if (isMonorepo) {
811
- index.generateTypescriptConfigPackage(configPackages);
812
- if (linter === "oxlint") {
813
- index.generateOxlintConfigPackage(configPackages);
814
- } else if (linter === "eslint") {
815
- index.generateEslintConfigPackage(configPackages);
816
- }
817
- if (formatter === "oxfmt") {
818
- index.generateOxfmtConfigPackage(configPackages);
819
- } else if (formatter === "prettier") {
820
- 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 {
821
686
  }
822
687
  }
823
- const workspaceConfig = {};
824
- const rootConfig = {};
825
- rootConfig[".gitignore"] = index.generateGitignore(isMonorepo ? "workspace-root" : "standalone");
826
- rootConfig[".gitattributes"] = {
827
- type: "text",
828
- content: `* text=auto eol=lf
829
- *.{cmd,[cC][mM][dD]} text eol=crlf
830
- *.{bat,[bB][aA][tT]} text eol=crlf
831
- `
832
- };
833
- if (linter === "biome" || formatter === "biome") {
834
- const biomeVersion = index.getResolvedPackageVersion(versions, "@biomejs/biome");
835
- const biomeConfig = {
836
- $schema: `https://biomejs.dev/schemas/${biomeVersion}/schema.json`,
837
- vcs: {
838
- enabled: true,
839
- clientKind: "git",
840
- useIgnoreFile: true
841
- },
842
- linter: {
843
- enabled: linter === "biome",
844
- rules: {
845
- recommended: true
846
- }
847
- },
848
- formatter: {
849
- enabled: formatter === "biome"
850
- }
851
- };
852
- rootConfig["biome.json"] = {
853
- type: "text",
854
- content: JSON.stringify(biomeConfig, null, 2)
855
- };
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");
856
697
  }
857
- return {
858
- "ai-files": aiFilesMap,
859
- vscode: vscodeFiles,
860
- "config-packages": configPackages,
861
- "workspace-config": workspaceConfig,
862
- "root-config": rootConfig
863
- };
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 };
864
723
  }
724
+
865
725
  async function fileExists$1(path) {
866
726
  try {
867
- await promises.access(path, fs.constants.F_OK);
727
+ await promises$1.access(path, node_fs.constants.F_OK);
868
728
  return true;
869
729
  } catch {
870
730
  return false;
871
731
  }
872
732
  }
873
- async function compareWithDisk(expected, root) {
874
- const categoryLabels = {
875
- "ai-files": "AI Files",
876
- vscode: "VS Code",
877
- "config-packages": "Config Packages",
878
- "workspace-config": "Workspace Config",
879
- "root-config": "Root Config"
880
- };
881
- const categories = [];
882
- for (const [category, files] of Object.entries(expected)) {
883
- const changes = [];
884
- for (const [filePath, file] of Object.entries(files)) {
885
- if (file.type !== "text") continue;
886
- const fullPath = path.join(root, filePath);
887
- const newContent = file.content;
888
- if (await fileExists$1(fullPath)) {
889
- const currentContent = await promises.readFile(fullPath, "utf-8");
890
- if (currentContent === newContent) {
891
- changes.push({
892
- path: filePath,
893
- status: "unchanged",
894
- currentContent,
895
- newContent
896
- });
897
- } else {
898
- changes.push({
899
- path: filePath,
900
- status: "modified",
901
- currentContent,
902
- newContent
903
- });
904
- }
905
- } else {
906
- changes.push({
907
- path: filePath,
908
- status: "added",
909
- newContent
910
- });
911
- }
912
- }
913
- if (changes.length === 0) continue;
914
- const hasUserModifications = changes.some((c) => c.status === "modified");
915
- categories.push({
916
- category,
917
- label: categoryLabels[category],
918
- changes,
919
- hasUserModifications
920
- });
921
- }
922
- return categories;
923
- }
924
- async function getWorkspaceConfigUpdates(root) {
925
- const workspacePath = path.join(root, "pnpm-workspace.yaml");
926
- const changes = [];
927
- let currentContent = "";
928
- let exists = false;
929
- try {
930
- currentContent = await promises.readFile(workspacePath, "utf-8");
931
- exists = true;
932
- } catch {
933
- }
934
- if (!exists) {
935
- const newContent = `manage-package-manager-versions: true
936
-
937
- packages:
938
- - ".config/*"
939
- - "apps/*"
940
- - "packages/*"
941
-
942
- onlyBuiltDependencies:
943
- - esbuild
944
- `;
945
- changes.push({
946
- path: "pnpm-workspace.yaml",
947
- status: "added",
948
- newContent
949
- });
950
- return changes;
951
- }
952
- let updatedContent = currentContent;
953
- let needsUpdate = false;
954
- if (!currentContent.includes("manage-package-manager-versions")) {
955
- updatedContent = `manage-package-manager-versions: true
956
-
957
- ${updatedContent}`;
958
- needsUpdate = true;
959
- }
960
- if (!currentContent.includes("onlyBuiltDependencies")) {
961
- updatedContent = `${updatedContent.trimEnd()}
962
-
963
- onlyBuiltDependencies:
964
- - esbuild
965
- `;
966
- needsUpdate = true;
967
- }
968
- if (!currentContent.includes(".config/*") && !currentContent.includes('".config/*"')) {
969
- const lines = updatedContent.split("\n");
970
- const packagesIndex = lines.findIndex((line) => line.trim().startsWith("packages:"));
971
- if (packagesIndex !== -1) {
972
- lines.splice(packagesIndex + 1, 0, ' - ".config/*"');
973
- updatedContent = lines.join("\n");
974
- needsUpdate = true;
975
- }
976
- }
977
- if (needsUpdate) {
978
- changes.push({
979
- path: "pnpm-workspace.yaml",
980
- status: "modified",
981
- currentContent,
982
- newContent: updatedContent
983
- });
984
- } else {
985
- changes.push({
986
- path: "pnpm-workspace.yaml",
987
- status: "unchanged",
988
- currentContent,
989
- newContent: currentContent
990
- });
991
- }
992
- return changes;
993
- }
994
- async function applyUpdates(changes, root) {
995
- for (const change of changes) {
996
- if (change.status === "unchanged") continue;
997
- const fullPath = path.join(root, change.path);
998
- await promises.mkdir(path.dirname(fullPath), { recursive: true });
999
- await promises.writeFile(fullPath, change.newContent);
1000
- }
1001
- }
1002
- function formatFileChange(change) {
1003
- const icon = change.status === "added" ? "+" : change.status === "modified" ? "~" : "=";
1004
- return ` ${icon} ${change.path}`;
1005
- }
1006
- const LINTER_DEPS = {
1007
- oxlint: "oxlint",
1008
- eslint: "eslint",
1009
- biome: "@biomejs/biome"
1010
- };
1011
- const FORMATTER_DEPS = {
1012
- oxfmt: "oxfmt",
1013
- prettier: "prettier",
1014
- biome: "@biomejs/biome"
1015
- };
1016
- const LINTER_CONFIG_PACKAGES = {
1017
- oxlint: "@config/oxlint",
1018
- eslint: "@config/eslint",
1019
- biome: null
1020
- // biome uses root biome.json
1021
- };
1022
- const FORMATTER_CONFIG_PACKAGES = {
1023
- oxfmt: "@config/oxfmt",
1024
- prettier: "@config/prettier",
1025
- biome: null
1026
- // biome uses root biome.json
1027
- };
1028
- function needsMigration(current, target) {
1029
- const linterChange = target.linter && target.linter !== current.linter;
1030
- const formatterChange = target.formatter && target.formatter !== current.formatter;
1031
- return linterChange || formatterChange || false;
1032
- }
1033
- async function getMigrationPlan(current, target, root) {
1034
- const toLinter = target.linter ?? current.linter;
1035
- const toFormatter = target.formatter ?? current.formatter;
1036
- const targetVersions = toLinter === "biome" || toFormatter === "biome" ? await index.resolveMonorepoRootPackageVersions({
1037
- linter: toLinter,
1038
- formatter: toFormatter
1039
- }) : {};
1040
- const biomeSchemaUrl = toLinter === "biome" || toFormatter === "biome" ? `https://biomejs.dev/schemas/${index.getResolvedPackageVersion(
1041
- targetVersions,
1042
- "@biomejs/biome"
1043
- )}/schema.json` : "";
1044
- const changes = [];
1045
- if (toLinter !== current.linter) {
1046
- if (current.linter !== "biome") {
1047
- changes.push({
1048
- type: "remove-dir",
1049
- path: `.config/${current.linter}`,
1050
- description: `Remove @config/${current.linter} package`
1051
- });
1052
- }
1053
- if (toLinter !== "biome") {
1054
- const files = {};
1055
- if (toLinter === "oxlint") {
1056
- index.generateOxlintConfigPackage(files);
1057
- } else if (toLinter === "eslint") {
1058
- index.generateEslintConfigPackage(files);
1059
- }
1060
- for (const [path, file] of Object.entries(files)) {
1061
- if (file.type === "text") {
1062
- changes.push({
1063
- type: "add-file",
1064
- path,
1065
- description: `Add ${path}`,
1066
- content: file.content
1067
- });
1068
- }
1069
- }
1070
- }
1071
- if (toLinter === "biome" && toFormatter === "biome") {
1072
- changes.push({
1073
- type: "add-file",
1074
- path: "biome.json",
1075
- description: "Add biome.json config",
1076
- content: JSON.stringify(
1077
- {
1078
- $schema: biomeSchemaUrl,
1079
- vcs: { enabled: true, clientKind: "git", useIgnoreFile: true },
1080
- linter: { enabled: true, rules: { recommended: true } },
1081
- formatter: { enabled: true }
1082
- },
1083
- null,
1084
- 2
1085
- )
1086
- });
1087
- } else if (toLinter === "biome" && toFormatter !== "biome") {
1088
- changes.push({
1089
- type: "add-file",
1090
- path: "biome.json",
1091
- description: "Add biome.json config (linter only)",
1092
- content: JSON.stringify(
1093
- {
1094
- $schema: biomeSchemaUrl,
1095
- vcs: { enabled: true, clientKind: "git", useIgnoreFile: true },
1096
- linter: { enabled: true, rules: { recommended: true } },
1097
- formatter: { enabled: false }
1098
- },
1099
- null,
1100
- 2
1101
- )
1102
- });
1103
- }
1104
- if (current.linter === "biome" && toLinter !== "biome" && current.formatter !== "biome" && toFormatter !== "biome") {
1105
- changes.push({
1106
- type: "remove-file",
1107
- path: "biome.json",
1108
- description: "Remove biome.json"
1109
- });
1110
- }
1111
- }
1112
- if (toFormatter !== current.formatter) {
1113
- const formatterSameAsLinter = current.formatter === current.linter;
1114
- if (current.formatter !== "biome" && !formatterSameAsLinter) {
1115
- changes.push({
1116
- type: "remove-dir",
1117
- path: `.config/${current.formatter}`,
1118
- description: `Remove @config/${current.formatter} package`
1119
- });
1120
- }
1121
- const newFormatterSameAsLinter = toFormatter === toLinter;
1122
- if (toFormatter !== "biome" && !newFormatterSameAsLinter) {
1123
- const files = {};
1124
- if (toFormatter === "oxfmt") {
1125
- index.generateOxfmtConfigPackage(files);
1126
- } else if (toFormatter === "prettier") {
1127
- index.generatePrettierConfigPackage(files);
1128
- }
1129
- for (const [path, file] of Object.entries(files)) {
1130
- if (file.type === "text") {
1131
- changes.push({
1132
- type: "add-file",
1133
- path,
1134
- description: `Add ${path}`,
1135
- content: file.content
1136
- });
1137
- }
1138
- }
1139
- }
1140
- if (toFormatter === "biome" && toLinter !== "biome") {
1141
- changes.push({
1142
- type: "add-file",
1143
- path: "biome.json",
1144
- description: "Add biome.json config (formatter only)",
1145
- content: JSON.stringify(
1146
- {
1147
- $schema: biomeSchemaUrl,
1148
- vcs: { enabled: true, clientKind: "git", useIgnoreFile: true },
1149
- linter: { enabled: false },
1150
- formatter: { enabled: true }
1151
- },
1152
- null,
1153
- 2
1154
- )
1155
- });
1156
- }
1157
- if (current.formatter === "biome" && toFormatter !== "biome" && current.linter !== "biome" && toLinter !== "biome") {
1158
- changes.push({
1159
- type: "remove-file",
1160
- path: "biome.json",
1161
- description: "Remove biome.json"
1162
- });
1163
- }
1164
- }
1165
- changes.push({
1166
- type: "update-package-json",
1167
- path: "package.json",
1168
- description: "Update root package.json (devDependencies, scripts)"
1169
- });
1170
- const subPackageUpdates = await getSubPackageUpdates(root, current, toLinter, toFormatter);
1171
- return {
1172
- fromLinter: current.linter,
1173
- toLinter,
1174
- fromFormatter: current.formatter,
1175
- toFormatter,
1176
- changes,
1177
- subPackageUpdates
1178
- };
1179
- }
1180
- async function getSubPackageUpdates(root, current, toLinter, toFormatter) {
1181
- const updates = [];
1182
- const workspacePath = path.join(root, "pnpm-workspace.yaml");
1183
- let workspaceContent;
1184
- try {
1185
- workspaceContent = await promises.readFile(workspacePath, "utf-8");
1186
- } catch {
1187
- return updates;
1188
- }
1189
- const packageGlobs = index.parseWorkspaceYamlContent(workspaceContent);
1190
- for (const glob of packageGlobs) {
1191
- if (glob.includes(".config")) continue;
1192
- const baseDir = glob.replace(/\/\*$/, "").replace(/^["']|["']$/g, "");
1193
- const basePath = path.join(root, baseDir);
1194
- try {
1195
- const entries = await promises.readdir(basePath, { withFileTypes: true });
1196
- for (const entry of entries) {
1197
- if (!entry.isDirectory()) continue;
1198
- const pkgJsonPath = path.join(basePath, entry.name, "package.json");
1199
- try {
1200
- const content = await promises.readFile(pkgJsonPath, "utf-8");
1201
- const pkg = JSON.parse(content);
1202
- const devDeps = pkg.devDependencies ?? {};
1203
- const remove = [];
1204
- const add = [];
1205
- const oldLinterPkg = LINTER_CONFIG_PACKAGES[current.linter];
1206
- const newLinterPkg = LINTER_CONFIG_PACKAGES[toLinter];
1207
- if (oldLinterPkg && oldLinterPkg !== newLinterPkg && devDeps[oldLinterPkg]) {
1208
- remove.push(oldLinterPkg);
1209
- }
1210
- if (newLinterPkg && newLinterPkg !== oldLinterPkg && oldLinterPkg && devDeps[oldLinterPkg]) {
1211
- add.push(newLinterPkg);
1212
- }
1213
- if (current.formatter !== current.linter) {
1214
- const oldFormatterPkg = FORMATTER_CONFIG_PACKAGES[current.formatter];
1215
- const newFormatterPkg = FORMATTER_CONFIG_PACKAGES[toFormatter];
1216
- if (oldFormatterPkg && oldFormatterPkg !== newFormatterPkg && devDeps[oldFormatterPkg]) {
1217
- remove.push(oldFormatterPkg);
1218
- }
1219
- if (newFormatterPkg && newFormatterPkg !== oldFormatterPkg && oldFormatterPkg && devDeps[oldFormatterPkg]) {
1220
- add.push(newFormatterPkg);
1221
- }
1222
- }
1223
- if (remove.length > 0 || add.length > 0) {
1224
- updates.push({
1225
- path: path.join(baseDir, entry.name, "package.json"),
1226
- remove,
1227
- add
1228
- });
1229
- }
1230
- } catch {
1231
- }
1232
- }
1233
- } catch {
1234
- }
1235
- }
1236
- return updates;
1237
- }
1238
- async function applyMigration(plan, root) {
1239
- for (const change of plan.changes) {
1240
- if (change.type === "remove-dir") {
1241
- const fullPath = path.join(root, change.path);
1242
- try {
1243
- await promises.rm(fullPath, { recursive: true });
1244
- } catch {
1245
- }
1246
- }
1247
- }
1248
- for (const change of plan.changes) {
1249
- if (change.type === "remove-file") {
1250
- const fullPath = path.join(root, change.path);
1251
- try {
1252
- await promises.rm(fullPath);
1253
- } catch {
1254
- }
1255
- }
1256
- }
1257
- for (const change of plan.changes) {
1258
- if (change.type === "add-file" && change.content) {
1259
- const fullPath = path.join(root, change.path);
1260
- await promises.mkdir(path.dirname(fullPath), { recursive: true });
1261
- await promises.writeFile(fullPath, change.content);
1262
- }
1263
- }
1264
- await updateRootPackageJson(root, plan);
1265
- for (const update of plan.subPackageUpdates) {
1266
- await updateSubPackageJson(root, update);
1267
- }
1268
- }
1269
- async function updateRootPackageJson(root, plan) {
1270
- const pkgPath = path.join(root, "package.json");
1271
- const content = await promises.readFile(pkgPath, "utf-8");
1272
- const pkg = JSON.parse(content);
1273
- const devDeps = pkg.devDependencies ?? {};
1274
- const oldLinterDep = LINTER_DEPS[plan.fromLinter];
1275
- delete devDeps[oldLinterDep];
1276
- if (plan.fromFormatter !== plan.fromLinter) {
1277
- const oldFormatterDep = FORMATTER_DEPS[plan.fromFormatter];
1278
- delete devDeps[oldFormatterDep];
1279
- }
1280
- const resolvedVersions = await index.resolveMonorepoRootPackageVersions({
1281
- linter: plan.toLinter,
1282
- formatter: plan.toFormatter
1283
- });
1284
- const newLinterDep = LINTER_DEPS[plan.toLinter];
1285
- devDeps[newLinterDep] = index.formatResolvedPackageVersion(resolvedVersions, newLinterDep);
1286
- if (plan.toFormatter !== plan.toLinter) {
1287
- const newFormatterDep = FORMATTER_DEPS[plan.toFormatter];
1288
- devDeps[newFormatterDep] = index.formatResolvedPackageVersion(resolvedVersions, newFormatterDep);
1289
- }
1290
- pkg.devDependencies = Object.fromEntries(
1291
- Object.entries(devDeps).sort(([a], [b]) => a.localeCompare(b))
1292
- );
1293
- const scripts = pkg.scripts ?? {};
1294
- if (plan.toLinter === "oxlint") {
1295
- scripts.lint = "oxlint .";
1296
- } else if (plan.toLinter === "eslint") {
1297
- scripts.lint = "eslint .";
1298
- } else if (plan.toLinter === "biome") {
1299
- scripts.lint = "biome check .";
1300
- }
1301
- if (plan.toFormatter === "oxfmt") {
1302
- scripts.format = "oxfmt .";
1303
- } else if (plan.toFormatter === "prettier") {
1304
- scripts.format = "prettier --write .";
1305
- } else if (plan.toFormatter === "biome") {
1306
- scripts.format = "biome format . --write";
1307
- }
1308
- pkg.scripts = scripts;
1309
- await promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
1310
- }
1311
- async function updateSubPackageJson(root, update) {
1312
- const pkgPath = path.join(root, update.path);
1313
- const content = await promises.readFile(pkgPath, "utf-8");
1314
- const pkg = JSON.parse(content);
1315
- const devDeps = pkg.devDependencies ?? {};
1316
- for (const dep of update.remove) {
1317
- delete devDeps[dep];
1318
- }
1319
- for (const dep of update.add) {
1320
- devDeps[dep] = "workspace:*";
1321
- }
1322
- pkg.devDependencies = Object.fromEntries(
1323
- Object.entries(devDeps).sort(([a], [b]) => a.localeCompare(b))
1324
- );
1325
- await promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
1326
- }
1327
- function formatMigrationChange(change) {
1328
- const icon = change.type === "remove-dir" || change.type === "remove-file" ? "-" : change.type === "add-file" ? "+" : "~";
1329
- return ` ${icon} ${change.description}`;
1330
- }
1331
-
1332
- async function checkAnyExists(paths) {
1333
- for (const path of paths) {
1334
- try {
1335
- await promises.access(path, promises.constants.F_OK);
1336
- return true;
1337
- } catch {
1338
- }
1339
- }
1340
- return false;
1341
- }
1342
- async function validateWorkspace(monorepoRoot) {
1343
- const errors = [];
1344
- const tsConfigPath = path.join(monorepoRoot, ".config/typescript/package.json");
1345
- try {
1346
- await promises.access(tsConfigPath, promises.constants.F_OK);
1347
- } catch {
1348
- errors.push("Missing .config/typescript package");
1349
- }
1350
- const linterPaths = [
1351
- path.join(monorepoRoot, ".config/oxlint/package.json"),
1352
- path.join(monorepoRoot, ".config/eslint/package.json"),
1353
- path.join(monorepoRoot, "eslint.config.js"),
1354
- path.join(monorepoRoot, "biome.json")
1355
- ];
1356
- const hasLinter = await checkAnyExists(linterPaths);
1357
- if (!hasLinter) {
1358
- errors.push(
1359
- "Missing linter config (.config/oxlint, .config/eslint, eslint.config.js, or biome.json)"
1360
- );
1361
- }
1362
- const formatterPaths = [
1363
- path.join(monorepoRoot, ".config/oxfmt/package.json"),
1364
- path.join(monorepoRoot, ".config/prettier/package.json"),
1365
- path.join(monorepoRoot, ".prettierrc.json"),
1366
- path.join(monorepoRoot, "biome.json")
1367
- ];
1368
- const hasFormatter = await checkAnyExists(formatterPaths);
1369
- if (!hasFormatter) {
1370
- errors.push(
1371
- "Missing formatter config (.config/oxfmt, .config/prettier, .prettierrc.json, or biome.json)"
1372
- );
1373
- }
1374
- return { valid: errors.length === 0, errors };
1375
- }
1376
-
1377
- 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)));
1378
- const pkg = require$1("../package.json");
1379
- const META_OPTIONS = [
1380
- "clearConfig",
1381
- "configPath",
1382
- "check",
1383
- "fix",
1384
- "update",
1385
- "yes",
1386
- "workspace",
1387
- "path",
1388
- "dir"
1389
- ];
1390
- function hasConfigOptions(options) {
1391
- return Object.keys(options).some(
1392
- (key) => !META_OPTIONS.includes(key)
1393
- );
1394
- }
1395
- async function fileExists(path) {
1396
- try {
1397
- await promises$1.access(path, node_fs.constants.F_OK);
1398
- return true;
1399
- } catch {
1400
- return false;
1401
- }
1402
- }
1403
- async function promptForAiPlatforms(isNonInteractive) {
1404
- const savedPlatforms = getAiPlatforms();
1405
- if (isNonInteractive) {
1406
- return savedPlatforms ?? index.ALL_AI_PLATFORMS;
1407
- }
1408
- if (savedPlatforms && savedPlatforms.length > 0) {
1409
- const savedLabels = savedPlatforms.map((plat) => index.AI_PLATFORM_LABELS[plat]).join(", ");
1410
- const useDefault = await p__namespace.confirm({
1411
- message: `Add AI rules? ${color__default.dim(`(${savedLabels})`)}`,
1412
- initialValue: true
1413
- });
1414
- if (p__namespace.isCancel(useDefault)) {
1415
- return [];
1416
- }
1417
- if (useDefault) {
1418
- return savedPlatforms;
1419
- }
1420
- }
1421
- const selected = await p__namespace.multiselect({
1422
- message: "Add AI rules?",
1423
- options: index.ALL_AI_PLATFORMS.map((platform) => ({
1424
- value: platform,
1425
- label: index.AI_PLATFORM_LABELS[platform],
1426
- hint: index.AI_PLATFORM_HINTS[platform]
1427
- })),
1428
- initialValues: ["agents"],
1429
- required: false
1430
- });
1431
- if (p__namespace.isCancel(selected)) {
1432
- return [];
1433
- }
1434
- const platforms = selected;
1435
- if (platforms.length === 0) {
1436
- return [];
1437
- }
1438
- return platforms;
1439
- }
1440
- async function writeGeneratedFiles(basePath, files) {
1441
- const filePaths = Object.keys(files).sort();
1442
- for (const filePath of filePaths) {
1443
- const fullFilePath = node_path.join(basePath, filePath);
1444
- await promises$1.mkdir(node_path.dirname(fullFilePath), { recursive: true });
1445
- const file = files[filePath];
1446
- if (file.type === "text") {
1447
- await promises$1.writeFile(fullFilePath, file.content);
1448
- } else {
1449
- const response = await undici.fetch(file.url);
1450
- await promises$1.writeFile(fullFilePath, response.body);
1451
- }
1452
- }
1453
- }
1454
733
  function calculateWorkspaceRoot(packagePath) {
1455
734
  const segments = packagePath.split(/[/\\]/).filter(Boolean);
1456
735
  return segments.map(() => "..").join("/");
@@ -1476,30 +755,30 @@ async function detectPackageRoot() {
1476
755
  let currentDir = node_process.cwd();
1477
756
  const root = node_path.resolve("/");
1478
757
  while (currentDir !== root) {
1479
- if (await fileExists(node_path.join(currentDir, "package.json"))) {
758
+ if (await fileExists$1(node_path.join(currentDir, "package.json"))) {
1480
759
  return currentDir;
1481
760
  }
1482
761
  currentDir = node_path.dirname(currentDir);
1483
762
  }
1484
- return await fileExists(node_path.join(root, "package.json")) ? root : null;
763
+ return await fileExists$1(node_path.join(root, "package.json")) ? root : null;
1485
764
  }
1486
765
  async function parseWorkspaceDirectories(monorepoRoot) {
1487
766
  try {
1488
767
  const workspaceFile = node_path.join(monorepoRoot, "pnpm-workspace.yaml");
1489
768
  const content = await promises$1.readFile(workspaceFile, "utf-8");
1490
- return index.parseWorkspaceYamlContent(content);
769
+ return workspace.parseWorkspaceYamlContent(content);
1491
770
  } catch {
1492
771
  return [];
1493
772
  }
1494
773
  }
1495
774
  async function detectWorkspaceSettings(monorepoRoot) {
1496
775
  try {
1497
- const tooling = await index.detectTooling(monorepoRoot);
776
+ const tooling = await workspace.detectTooling(monorepoRoot);
1498
777
  const pkgPath = node_path.join(monorepoRoot, "package.json");
1499
778
  const content = await promises$1.readFile(pkgPath, "utf-8");
1500
779
  const pkgJson = JSON.parse(content);
1501
- const packageManager = index.parsePackageManager(pkgJson.packageManager);
1502
- const engine = index.parseEngine(pkgJson.engines);
780
+ const packageManager = workspace.parsePackageManager(pkgJson.packageManager);
781
+ const engine = workspace.parseEngine(pkgJson.engines);
1503
782
  let pnpmManageVersions;
1504
783
  try {
1505
784
  const workspaceFile = node_path.join(monorepoRoot, "pnpm-workspace.yaml");
@@ -1521,17 +800,17 @@ async function detectWorkspaceSettings(monorepoRoot) {
1521
800
  async function detectExistingConfigs(monorepoRoot) {
1522
801
  const configs = {};
1523
802
  const eslintPath = node_path.join(monorepoRoot, "eslint.config.js");
1524
- if (await fileExists(eslintPath)) {
803
+ if (await fileExists$1(eslintPath)) {
1525
804
  configs.linter = "eslint";
1526
805
  configs.eslintConfigPath = eslintPath;
1527
806
  }
1528
807
  const prettierPath = node_path.join(monorepoRoot, ".prettierrc.json");
1529
- if (await fileExists(prettierPath)) {
808
+ if (await fileExists$1(prettierPath)) {
1530
809
  configs.formatter = "prettier";
1531
810
  configs.prettierConfigPath = prettierPath;
1532
811
  }
1533
812
  const biomePath = node_path.join(monorepoRoot, "biome.json");
1534
- if (await fileExists(biomePath)) {
813
+ if (await fileExists$1(biomePath)) {
1535
814
  configs.biomeConfigPath = biomePath;
1536
815
  if (!configs.linter) configs.linter = "biome";
1537
816
  if (!configs.formatter) configs.formatter = "biome";
@@ -1553,8 +832,7 @@ async function getMonorepoScope(monorepoRoot) {
1553
832
  async function getWorkspacePackages(monorepoRoot) {
1554
833
  const packagesDir = node_path.join(monorepoRoot, "packages");
1555
834
  try {
1556
- const { readdir } = await import('fs/promises');
1557
- const entries = await readdir(packagesDir, { withFileTypes: true });
835
+ const entries = await promises$1.readdir(packagesDir, { withFileTypes: true });
1558
836
  const names = [];
1559
837
  for (const entry of entries) {
1560
838
  if (!entry.isDirectory()) continue;
@@ -1563,8 +841,8 @@ async function getWorkspacePackages(monorepoRoot) {
1563
841
  node_path.join(packagesDir, entry.name, "package.json"),
1564
842
  "utf-8"
1565
843
  );
1566
- const pkg2 = JSON.parse(content);
1567
- if (pkg2.name) names.push(pkg2.name);
844
+ const pkg = JSON.parse(content);
845
+ if (pkg.name) names.push(pkg.name);
1568
846
  } catch {
1569
847
  }
1570
848
  }
@@ -1580,27 +858,48 @@ async function ensureConfigInWorkspace(monorepoRoot) {
1580
858
  content = await promises$1.readFile(workspacePath, "utf-8");
1581
859
  } catch {
1582
860
  content = `packages:
1583
- - ".config/*"
1584
- - "packages/*"
861
+ - '.config/*'
862
+ - 'packages/*'
1585
863
  `;
1586
864
  await promises$1.writeFile(workspacePath, content);
1587
865
  return;
1588
866
  }
1589
- if (content.includes(".config/*") || content.includes('".config/*"')) {
867
+ if (content.includes(".config/*")) {
1590
868
  return;
1591
869
  }
1592
870
  const lines = content.split("\n");
1593
871
  const packagesIndex = lines.findIndex((line) => line.trim().startsWith("packages:"));
1594
872
  if (packagesIndex === -1) {
1595
873
  content = `packages:
1596
- - ".config/*"
874
+ - '.config/*'
1597
875
  ${content}`;
1598
876
  } else {
1599
- lines.splice(packagesIndex + 1, 0, ' - ".config/*"');
877
+ lines.splice(packagesIndex + 1, 0, " - '.config/*'");
1600
878
  content = lines.join("\n");
1601
879
  }
1602
880
  await promises$1.writeFile(workspacePath, content);
1603
881
  }
882
+
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);
888
+ }
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}`));
893
+ } else {
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
+ }
899
+ }
900
+ process.exit(valid ? 0 : 1);
901
+ }
902
+
1604
903
  async function migrateEslintConfig(monorepoRoot, files) {
1605
904
  const configBasePath = ".config/eslint";
1606
905
  const existingConfigPath = node_path.join(monorepoRoot, "eslint.config.js");
@@ -1608,7 +907,7 @@ async function migrateEslintConfig(monorepoRoot, files) {
1608
907
  try {
1609
908
  existingContent = await promises$1.readFile(existingConfigPath, "utf-8");
1610
909
  } catch {
1611
- index.generateEslintConfigPackage(files);
910
+ workspace.renderEslintConfigPackage(files);
1612
911
  return;
1613
912
  }
1614
913
  files[`${configBasePath}/package.json`] = {
@@ -1687,7 +986,7 @@ async function migratePrettierConfig(monorepoRoot, files) {
1687
986
  try {
1688
987
  existingContent = await promises$1.readFile(existingConfigPath, "utf-8");
1689
988
  } catch {
1690
- index.generatePrettierConfigPackage(files);
989
+ workspace.renderPrettierConfigPackage(files);
1691
990
  return;
1692
991
  }
1693
992
  files[`${configBasePath}/package.json`] = {
@@ -1708,211 +1007,34 @@ async function migratePrettierConfig(monorepoRoot, files) {
1708
1007
  files[`${configBasePath}/README.md`] = {
1709
1008
  type: "text",
1710
1009
  content: `# \`@config/prettier\`
1711
-
1712
- Shared Prettier configurations.
1713
-
1714
- ## Usage
1715
-
1716
- In your package's \`.prettierrc\`:
1717
-
1718
- \`\`\`json
1719
- "@config/prettier/base"
1720
- \`\`\`
1721
-
1722
- Or in \`package.json\`:
1723
-
1724
- \`\`\`json
1725
- {
1726
- "prettier": "@config/prettier/base"
1727
- }
1728
- \`\`\`
1729
-
1730
- ## Available Configs
1731
-
1732
- - \`base\` - Base Prettier rules (migrated from root)
1733
- `
1734
- };
1735
- files[`${configBasePath}/base.json`] = {
1736
- type: "text",
1737
- content: existingContent
1738
- };
1739
- }
1740
- async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedSettings, scope) {
1741
- const workspaceDirectories = await parseWorkspaceDirectories(monorepoRoot);
1742
- const defaultDirectories = ["apps", "packages"];
1743
- const hasCustomDirectories = workspaceDirectories.length > 0 && !workspaceDirectories.every((dir) => defaultDirectories.includes(dir));
1744
- const packageType = await promptForInitialPackage();
1745
- if (packageType === "skip") {
1746
- return false;
1747
- }
1748
- const defaultDir = packageType === "app" ? "apps" : "packages";
1749
- const packageNameInput = await p__namespace.text({
1750
- message: "Package name?",
1751
- initialValue: `@${scope}/`,
1752
- validate: (value) => {
1753
- const validationError = index.validatePackageName(value);
1754
- if (validationError) return validationError;
1755
- const dirName = value.includes("/") ? value.split("/").pop() : value;
1756
- if (!dirName) return "Package name is required";
1757
- if (!hasCustomDirectories) {
1758
- const targetPath = node_path.join(monorepoRoot, defaultDir, dirName);
1759
- try {
1760
- const { statSync } = require$1("fs");
1761
- statSync(targetPath);
1762
- return `Directory ${defaultDir}/${dirName} already exists`;
1763
- } catch {
1764
- }
1765
- }
1766
- }
1767
- });
1768
- if (p__namespace.isCancel(packageNameInput)) {
1769
- return false;
1770
- }
1771
- const scopedName = packageNameInput;
1772
- const shortName = scopedName.includes("/") ? scopedName.split("/").pop() : scopedName;
1773
- const packageOptions = await promptForPackageOptions(scopedName, packageType, inheritedSettings);
1774
- let targetDir = defaultDir;
1775
- if (hasCustomDirectories && workspaceDirectories.length > 0) {
1776
- const dirChoice = await p__namespace.select({
1777
- message: "Target directory",
1778
- options: workspaceDirectories.map((dir) => ({
1779
- value: dir,
1780
- label: dir
1781
- })),
1782
- initialValue: workspaceDirectories.includes(defaultDir) ? defaultDir : workspaceDirectories[0]
1783
- });
1784
- if (p__namespace.isCancel(dirChoice)) {
1785
- return false;
1786
- }
1787
- targetDir = dirChoice;
1788
- const targetPath = node_path.join(monorepoRoot, targetDir, shortName);
1789
- try {
1790
- const { statSync } = require$1("fs");
1791
- statSync(targetPath);
1792
- p__namespace.log.error(`Directory ${targetDir}/${shortName} already exists`);
1793
- return false;
1794
- } catch {
1795
- }
1796
- }
1797
- const relativePkgPath = node_path.join(targetDir, shortName);
1798
- const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
1799
- packageOptions.workspaceRoot = workspaceRoot;
1800
- packageOptions.name = scopedName;
1801
- packageOptions.packageManager = await index.resolvePackageManager(packageOptions);
1802
- packageOptions.engine = await index.resolveEngine(packageOptions);
1803
- packageOptions.versions = await index.resolveProjectPackageVersions(packageOptions);
1804
- const workspacePackages = packageType === "app" ? await getWorkspacePackages(monorepoRoot) : [];
1805
- if (workspacePackages.length > 0) {
1806
- const selectedDeps = await p__namespace.multiselect({
1807
- message: "Add workspace dependencies?",
1808
- options: workspacePackages.map((name) => ({ value: name, label: name })),
1809
- required: false
1810
- });
1811
- if (!p__namespace.isCancel(selectedDeps) && selectedDeps.length > 0) {
1812
- packageOptions.workspaceDependencies = selectedDeps;
1813
- }
1814
- }
1815
- const outputPath = node_path.join(monorepoRoot, relativePkgPath);
1816
- const spinner = p__namespace.spinner();
1817
- spinner.start("Creating package...");
1818
- try {
1819
- const files = index.generate(packageOptions);
1820
- await writeGeneratedFiles(outputPath, files);
1821
- spinner.stop(color__default.green.inverse(` \u2713 Package created at ${relativePkgPath}! `));
1822
- const addAnother = await p__namespace.select({
1823
- message: "Add another package?",
1824
- options: [
1825
- { value: "no", label: "No, I'm done" },
1826
- { value: "yes", label: "Yes, add another" }
1827
- ],
1828
- initialValue: "no"
1829
- });
1830
- return !p__namespace.isCancel(addAnother) && addAnother === "yes";
1831
- } catch (error) {
1832
- spinner.stop("Failed to create package");
1833
- p__namespace.log.error(String(error));
1834
- return false;
1835
- }
1836
- }
1837
- async function promptAndOpenEditor(projectPath) {
1838
- const savedEditor = getPreferredEditor();
1839
- let selectedEditor;
1840
- if (savedEditor && savedEditor !== "skip") {
1841
- const useDefault = await p__namespace.confirm({
1842
- message: `Open in editor? ${color__default.dim(`(${editorNames[savedEditor]})`)}`,
1843
- initialValue: true
1844
- });
1845
- if (p__namespace.isCancel(useDefault)) {
1846
- selectedEditor = void 0;
1847
- } else if (useDefault) {
1848
- selectedEditor = savedEditor;
1849
- } else {
1850
- selectedEditor = "skip";
1851
- }
1852
- } else {
1853
- const openEditor = await p__namespace.select({
1854
- message: "Open project in editor?",
1855
- options: [
1856
- { value: "skip", label: "Skip" },
1857
- { value: "cursor", label: "Cursor" },
1858
- { value: "code", label: "VS Code" },
1859
- { value: "webstorm", label: "WebStorm" }
1860
- ],
1861
- initialValue: "skip"
1862
- });
1863
- if (!p__namespace.isCancel(openEditor)) {
1864
- selectedEditor = openEditor;
1865
- const saveChoice = await p__namespace.confirm({
1866
- message: `Save ${editorNames[selectedEditor] ?? "Skip"} as default editor?`,
1867
- initialValue: true
1868
- });
1869
- if (!p__namespace.isCancel(saveChoice) && saveChoice) {
1870
- setPreferredEditor(selectedEditor);
1871
- if (selectedEditor === "cursor" || selectedEditor === "code") {
1872
- const reuseChoice = await p__namespace.confirm({
1873
- message: "Reuse current window when opening projects?",
1874
- initialValue: false
1875
- });
1876
- if (!p__namespace.isCancel(reuseChoice)) {
1877
- setReuseWindow(reuseChoice);
1878
- }
1879
- }
1880
- }
1881
- }
1882
- }
1883
- if (selectedEditor && selectedEditor !== "skip") {
1884
- try {
1885
- await openInEditor(
1886
- selectedEditor,
1887
- projectPath,
1888
- getReuseWindow()
1889
- );
1890
- p__namespace.log.success(`Opening in ${editorNames[selectedEditor]}...`);
1891
- } catch {
1892
- p__namespace.log.warn(
1893
- `Could not open ${editorNames[selectedEditor]}. Make sure the CLI command is in your PATH.`
1894
- );
1895
- }
1896
- }
1010
+
1011
+ Shared Prettier configurations.
1012
+
1013
+ ## Usage
1014
+
1015
+ In your package's \`.prettierrc\`:
1016
+
1017
+ \`\`\`json
1018
+ "@config/prettier/base"
1019
+ \`\`\`
1020
+
1021
+ Or in \`package.json\`:
1022
+
1023
+ \`\`\`json
1024
+ {
1025
+ "prettier": "@config/prettier/base"
1897
1026
  }
1898
- async function handleCheckCommand() {
1899
- const monorepoRoot = await detectMonorepoRoot();
1900
- if (!monorepoRoot) {
1901
- console.log(color__default.red("\u2717") + " Not a monorepo workspace");
1902
- process.exit(1);
1903
- }
1904
- const { valid, errors } = await validateWorkspace(monorepoRoot);
1905
- if (valid) {
1906
- console.log(color__default.green("\u2713") + " Valid monorepo workspace");
1907
- console.log(color__default.dim(` ${monorepoRoot}`));
1908
- } else {
1909
- console.log(color__default.red("\u2717") + " Invalid monorepo workspace");
1910
- console.log(color__default.dim(` ${monorepoRoot}`));
1911
- for (const error of errors) {
1912
- console.log(color__default.red(` \u2022 ${error}`));
1913
- }
1914
- }
1915
- process.exit(valid ? 0 : 1);
1027
+ \`\`\`
1028
+
1029
+ ## Available Configs
1030
+
1031
+ - \`base\` - Base Prettier rules (migrated from root)
1032
+ `
1033
+ };
1034
+ files[`${configBasePath}/base.json`] = {
1035
+ type: "text",
1036
+ content: existingContent
1037
+ };
1916
1038
  }
1917
1039
  async function handleFixCommand(options) {
1918
1040
  const monorepoRoot = await detectMonorepoRoot();
@@ -1995,48 +1117,48 @@ async function handleFixCommand(options) {
1995
1117
  spinner.start("Fixing workspace...");
1996
1118
  try {
1997
1119
  const files = {};
1998
- const tsConfigExists = await fileExists(
1120
+ const tsConfigExists = await fileExists$1(
1999
1121
  node_path.join(monorepoRoot, ".config/typescript/package.json")
2000
1122
  );
2001
1123
  if (!tsConfigExists) {
2002
- index.generateTypescriptConfigPackage(files);
1124
+ workspace.renderTypescriptConfigPackage(files);
2003
1125
  }
2004
1126
  if (linter === "oxlint") {
2005
- const oxlintExists = await fileExists(node_path.join(monorepoRoot, ".config/oxlint/package.json"));
2006
- 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);
2007
1129
  } else if (linter === "eslint") {
2008
- const eslintPkgExists = await fileExists(
1130
+ const eslintPkgExists = await fileExists$1(
2009
1131
  node_path.join(monorepoRoot, ".config/eslint/package.json")
2010
1132
  );
2011
1133
  if (!eslintPkgExists) {
2012
1134
  if (existingConfigs.eslintConfigPath) {
2013
1135
  await migrateEslintConfig(monorepoRoot, files);
2014
1136
  } else {
2015
- index.generateEslintConfigPackage(files);
1137
+ workspace.renderEslintConfigPackage(files);
2016
1138
  }
2017
1139
  }
2018
1140
  }
2019
1141
  if (formatter === "oxfmt") {
2020
- const oxfmtExists = await fileExists(node_path.join(monorepoRoot, ".config/oxfmt/package.json"));
2021
- 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);
2022
1144
  } else if (formatter === "prettier") {
2023
- const prettierPkgExists = await fileExists(
1145
+ const prettierPkgExists = await fileExists$1(
2024
1146
  node_path.join(monorepoRoot, ".config/prettier/package.json")
2025
1147
  );
2026
1148
  if (!prettierPkgExists) {
2027
1149
  if (existingConfigs.prettierConfigPath) {
2028
1150
  await migratePrettierConfig(monorepoRoot, files);
2029
1151
  } else {
2030
- index.generatePrettierConfigPackage(files);
1152
+ workspace.renderPrettierConfigPackage(files);
2031
1153
  }
2032
1154
  }
2033
1155
  }
2034
1156
  if ((linter === "biome" || formatter === "biome") && !existingConfigs.biomeConfigPath) {
2035
- const versions = await index.resolveMonorepoRootPackageVersions({
1157
+ const versions = await workspace.resolveMonorepoRootPackageVersions({
2036
1158
  linter,
2037
1159
  formatter
2038
1160
  });
2039
- const biomeVersion = index.getResolvedPackageVersion(versions, "@biomejs/biome");
1161
+ const biomeVersion = workspace.getResolvedPackageVersion(versions, "@biomejs/biome");
2040
1162
  const biomeConfig = {
2041
1163
  $schema: `https://biomejs.dev/schemas/${biomeVersion}/schema.json`,
2042
1164
  vcs: {
@@ -2078,13 +1200,13 @@ async function handleFixCommand(options) {
2078
1200
  }
2079
1201
  }
2080
1202
  spinner.stop(color__default.green("\u2713") + " Workspace fixed!");
2081
- const generated = Object.keys(files).filter((f) => f.endsWith("package.json"));
1203
+ const generated = Object.keys(files).filter((file) => file.endsWith("package.json"));
2082
1204
  for (const pkgFile of generated) {
2083
1205
  const pkgName = pkgFile.replace("/package.json", "");
2084
1206
  console.log(color__default.dim(` Generated ${pkgName}`));
2085
1207
  }
2086
- const vscodeSettingsExists = await fileExists(node_path.join(monorepoRoot, ".vscode/settings.json"));
2087
- const vscodeExtensionsExists = await fileExists(
1208
+ const vscodeSettingsExists = await fileExists$1(node_path.join(monorepoRoot, ".vscode/settings.json"));
1209
+ const vscodeExtensionsExists = await fileExists$1(
2088
1210
  node_path.join(monorepoRoot, ".vscode/extensions.json")
2089
1211
  );
2090
1212
  const vscodeExists = vscodeSettingsExists && vscodeExtensionsExists;
@@ -2093,337 +1215,828 @@ async function handleFixCommand(options) {
2093
1215
  if (isNonInteractive) {
2094
1216
  addVscode = true;
2095
1217
  } else {
2096
- const vscodeChoice = await p__namespace.confirm({
2097
- message: "Generate VS Code settings?",
2098
- initialValue: true
1218
+ const vscodeChoice = await p__namespace.confirm({
1219
+ message: "Generate VS Code settings?",
1220
+ initialValue: true
1221
+ });
1222
+ addVscode = !p__namespace.isCancel(vscodeChoice) && vscodeChoice;
1223
+ }
1224
+ if (addVscode) {
1225
+ const vscodeFiles = {};
1226
+ workspace.renderVscodeFiles(vscodeFiles, linter, formatter);
1227
+ for (const [filePath, file] of Object.entries(vscodeFiles)) {
1228
+ const fullPath = node_path.join(monorepoRoot, filePath);
1229
+ await promises$1.mkdir(node_path.dirname(fullPath), { recursive: true });
1230
+ await promises$1.writeFile(fullPath, file.content);
1231
+ }
1232
+ console.log(color__default.dim(" Generated .vscode/settings.json"));
1233
+ console.log(color__default.dim(" Generated .vscode/extensions.json"));
1234
+ }
1235
+ }
1236
+ const aiRulesExist = await fileExists$1(node_path.join(monorepoRoot, ".ai/workspace.md"));
1237
+ if (!aiRulesExist) {
1238
+ const platforms = await promptForAiAgentPlatforms(isNonInteractive);
1239
+ if (platforms.length > 0) {
1240
+ const scope = await getMonorepoScope(monorepoRoot);
1241
+ const aiFilesOutput = {};
1242
+ workspace.renderAiFiles(aiFilesOutput, {
1243
+ name: scope,
1244
+ packageManager: "pnpm",
1245
+ linter,
1246
+ formatter,
1247
+ isMonorepo: true,
1248
+ hasTypecheck: false,
1249
+ platforms
1250
+ });
1251
+ for (const [filePath, file] of Object.entries(aiFilesOutput)) {
1252
+ const fullPath = node_path.join(monorepoRoot, filePath);
1253
+ await promises$1.mkdir(node_path.dirname(fullPath), { recursive: true });
1254
+ await promises$1.writeFile(fullPath, file.content);
1255
+ console.log(color__default.dim(` Generated ${filePath}`));
1256
+ }
1257
+ }
1258
+ }
1259
+ process.exit(0);
1260
+ } catch (error) {
1261
+ spinner.stop(color__default.red("\u2717") + " Failed to fix workspace");
1262
+ console.error(error);
1263
+ process.exit(1);
1264
+ }
1265
+ }
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(/\/.*$/, "");
1277
+ }
1278
+ if (pkgJson.packageManager) {
1279
+ packageManager = pkgJson.packageManager.split("@")[0] ?? packageManager;
1280
+ }
1281
+ hasTypecheck = pkgJson.scripts?.typecheck != null;
1282
+ } catch {
1283
+ }
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);
1338
+ }
1339
+ }
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
+ };
1390
+ }
1391
+ async function fileExists(path) {
1392
+ try {
1393
+ await promises.access(path, fs.constants.F_OK);
1394
+ return true;
1395
+ } catch {
1396
+ return false;
1397
+ }
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;
1412
+ }
1413
+ continue;
1414
+ }
1415
+ if (inBlockComment) {
1416
+ if (char === "*" && next === "/") {
1417
+ inBlockComment = false;
1418
+ index++;
1419
+ }
1420
+ continue;
1421
+ }
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;
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;
1447
+ }
1448
+ output += char;
1449
+ }
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
+ }
1467
+ continue;
1468
+ }
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;
1479
+ }
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
+ });
1543
+ } else {
1544
+ changes.push({
1545
+ path: filePath,
1546
+ status: "modified",
1547
+ currentContent,
1548
+ newContent
1549
+ });
1550
+ }
1551
+ } else {
1552
+ changes.push({
1553
+ path: filePath,
1554
+ status: "added",
1555
+ newContent
2099
1556
  });
2100
- addVscode = !p__namespace.isCancel(vscodeChoice) && vscodeChoice;
2101
- }
2102
- if (addVscode) {
2103
- const vscodeFiles = {};
2104
- index.generateVscodeFiles(vscodeFiles, linter, formatter);
2105
- for (const [filePath, file] of Object.entries(vscodeFiles)) {
2106
- const fullPath = node_path.join(monorepoRoot, filePath);
2107
- await promises$1.mkdir(node_path.dirname(fullPath), { recursive: true });
2108
- await promises$1.writeFile(fullPath, file.content);
2109
- }
2110
- console.log(color__default.dim(" Generated .vscode/settings.json"));
2111
- console.log(color__default.dim(" Generated .vscode/extensions.json"));
2112
1557
  }
2113
1558
  }
2114
- const aiRulesExist = await fileExists(node_path.join(monorepoRoot, ".ai/workspace.md"));
2115
- if (!aiRulesExist) {
2116
- const platforms = await promptForAiPlatforms(isNonInteractive);
2117
- if (platforms.length > 0) {
2118
- const scope = await getMonorepoScope(monorepoRoot);
2119
- const aiFilesOutput = {};
2120
- index.generateAiFiles(aiFilesOutput, {
2121
- name: scope,
2122
- packageManager: "pnpm",
2123
- linter,
2124
- formatter,
2125
- isMonorepo: true,
2126
- platforms
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
2127
1576
  });
2128
- for (const [filePath, file] of Object.entries(aiFilesOutput)) {
2129
- const fullPath = node_path.join(monorepoRoot, filePath);
2130
- await promises$1.mkdir(node_path.dirname(fullPath), { recursive: true });
2131
- await promises$1.writeFile(fullPath, file.content);
2132
- console.log(color__default.dim(` Generated ${filePath}`));
2133
- }
2134
1577
  }
1578
+ continue;
2135
1579
  }
2136
- process.exit(0);
2137
- } catch (error) {
2138
- spinner.stop(color__default.red("\u2717") + " Failed to fix workspace");
2139
- console.error(error);
2140
- process.exit(1);
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
+ });
2141
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
+ );
2142
1655
  }
2143
- async function handleMigration(config, target, root, options) {
2144
- const plan = await getMigrationPlan(config, target, root);
2145
- console.log(color__default.cyan("Migration:"));
2146
- if (plan.fromLinter !== plan.toLinter) {
2147
- console.log(` Linter: ${color__default.dim(plan.fromLinter)} \u2192 ${color__default.green(plan.toLinter)}`);
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");
2148
1661
  }
2149
- if (plan.fromFormatter !== plan.toFormatter) {
2150
- console.log(
2151
- ` Formatter: ${color__default.dim(plan.fromFormatter)} \u2192 ${color__default.green(plan.toFormatter)}`
2152
- );
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 [];
2153
1671
  }
2154
- console.log();
2155
- console.log(color__default.cyan("Changes:"));
2156
- for (const change of plan.changes) {
2157
- console.log(formatMigrationChange(change));
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
1685
+ }
1686
+ ];
2158
1687
  }
2159
- if (plan.subPackageUpdates.length > 0) {
2160
- console.log();
2161
- console.log(color__default.cyan(`Sub-packages (${plan.subPackageUpdates.length}):`));
2162
- for (const update of plan.subPackageUpdates) {
2163
- const changes = [
2164
- ...update.remove.map((d) => `-${d}`),
2165
- ...update.add.map((d) => `+${d}`)
2166
- ].join(", ");
2167
- console.log(` ~ ${update.path} (${changes})`);
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
2168
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];
2169
1730
  }
2170
- console.log();
2171
- if (!options.yes) {
2172
- const confirm = await p__namespace.confirm({
2173
- message: "Apply migration?",
2174
- initialValue: true
2175
- });
2176
- if (p__namespace.isCancel(confirm) || !confirm) {
2177
- console.log(color__default.dim(" Migration cancelled"));
2178
- process.exit(0);
1731
+ if (fileContentsEqual(expected.path, currentContent, expected.newContent)) {
1732
+ return [
1733
+ {
1734
+ ...expected,
1735
+ status: "unchanged",
1736
+ currentContent,
1737
+ newContent: currentContent
1738
+ }
1739
+ ];
1740
+ }
1741
+ return [
1742
+ {
1743
+ ...expected,
1744
+ status: "modified",
1745
+ currentContent
2179
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 {
2180
1758
  }
2181
- await applyMigration(plan, root);
2182
- const aiWorkspacePath = node_path.join(root, ".ai/workspace.md");
2183
- const aiRulesExist = await fileExists(aiWorkspacePath);
2184
- if (aiRulesExist) {
2185
- console.log();
2186
- console.log(color__default.cyan("Updating AI rules..."));
2187
- const scope = await getMonorepoScope(root);
2188
- const existingPlatforms = [];
2189
- if (await fileExists(node_path.join(root, "AGENTS.md"))) {
2190
- existingPlatforms.push("agents");
2191
- }
2192
- if (await fileExists(node_path.join(root, "CLAUDE.md"))) {
2193
- existingPlatforms.push("claude");
2194
- }
2195
- const aiFilesOutput = {};
2196
- index.generateAiFiles(aiFilesOutput, {
2197
- name: scope,
2198
- packageManager: "pnpm",
2199
- linter: plan.toLinter,
2200
- formatter: plan.toFormatter,
2201
- isMonorepo: true,
2202
- platforms: existingPlatforms.length > 0 ? existingPlatforms : ["agents"]
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
2203
1774
  });
2204
- for (const [filePath, file] of Object.entries(aiFilesOutput)) {
2205
- const fullPath = node_path.join(root, filePath);
2206
- await promises$1.mkdir(node_path.dirname(fullPath), { recursive: true });
2207
- await promises$1.writeFile(fullPath, file.content);
2208
- console.log(color__default.dim(` ${filePath}`));
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;
2209
1800
  }
2210
1801
  }
2211
- console.log();
2212
- console.log(color__default.green("\u2713") + ` Migrated to ${plan.toLinter}/${plan.toFormatter}`);
2213
- console.log(color__default.dim(" Run `pnpm install` to update dependencies"));
2214
- process.exit(0);
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;
2215
1818
  }
2216
- async function handleUpdateCommand(options) {
2217
- const monorepoRoot = await detectMonorepoRoot();
2218
- const projectRoot = monorepoRoot ?? await detectPackageRoot();
2219
- if (!projectRoot) {
2220
- console.log(color__default.red("\u2717") + " Could not find a project root");
2221
- console.log(color__default.dim(" Run this command from inside a generated project"));
2222
- process.exit(1);
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);
2223
1825
  }
2224
- const isMonorepo = monorepoRoot != null;
2225
- if (isMonorepo) {
2226
- const { valid, errors } = await validateWorkspace(projectRoot);
2227
- if (!valid) {
2228
- console.log(color__default.yellow("!") + " Workspace has issues:");
2229
- for (const error of errors) {
2230
- console.log(color__default.dim(` \u2022 ${error}`));
2231
- }
2232
- console.log();
2233
- const shouldFix = options.yes || await p__namespace.confirm({
2234
- message: "Run fix first to resolve these issues?",
2235
- initialValue: true
2236
- });
2237
- if (p__namespace.isCancel(shouldFix) || !shouldFix) {
2238
- console.log(color__default.dim(" Run `pnpm create krispya --fix` to fix manually"));
2239
- process.exit(1);
2240
- }
2241
- const preFixConfig = await detectCurrentConfig(projectRoot);
2242
- const fixOptions = {
2243
- ...options,
2244
- linter: options.linter ?? preFixConfig.linter,
2245
- formatter: options.formatter ?? preFixConfig.formatter
2246
- };
2247
- await handleFixCommand(fixOptions);
2248
- }
2249
- } else if (options.linter || options.formatter) {
2250
- console.log(
2251
- color__default.yellow("!") + " Linter/formatter migrations in --update are currently monorepo-only"
2252
- );
2253
- console.log(color__default.dim(" Continuing with standalone shared config updates"));
2254
- console.log();
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);
2255
1875
  }
2256
- const config = await detectCurrentConfig(projectRoot, isMonorepo);
2257
- const targetLinter = options.linter;
2258
- const targetFormatter = options.formatter;
2259
- const migrationTarget = { linter: targetLinter, formatter: targetFormatter };
2260
- if (isMonorepo && needsMigration(config, migrationTarget)) {
2261
- await handleMigration(config, migrationTarget, projectRoot, options);
2262
- return;
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);
2263
1888
  }
2264
- console.log(
2265
- color__default.cyan("Checking for updates...") + color__default.dim(` (${config.linter}/${config.formatter})`)
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)
2266
1898
  );
2267
- console.log();
2268
- const expected = await generateExpectedFiles(config);
1899
+ }
1900
+ async function collectUpdateCategories(projectRoot, config, isMonorepo) {
1901
+ const expected = await planExpectedFiles(config);
2269
1902
  const categories = await compareWithDisk(expected, projectRoot);
2270
- const allCategories = categories.filter((c) => c.category !== "workspace-config");
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
+ }
2271
1924
  if (isMonorepo) {
2272
1925
  const workspaceConfigChanges = await getWorkspaceConfigUpdates(projectRoot);
2273
- const workspaceCategory = {
2274
- category: "workspace-config",
2275
- label: "Workspace Config",
2276
- changes: workspaceConfigChanges,
2277
- hasUserModifications: workspaceConfigChanges.some((c) => c.status === "modified")
2278
- };
2279
1926
  if (workspaceConfigChanges.length > 0) {
2280
- const configPkgIndex = allCategories.findIndex((c) => c.category === "config-packages");
2281
- if (configPkgIndex !== -1) {
2282
- allCategories.splice(configPkgIndex + 1, 0, workspaceCategory);
2283
- } else {
2284
- allCategories.push(workspaceCategory);
2285
- }
1927
+ allCategories.push({
1928
+ category: "workspace-config",
1929
+ label: "Workspace Config",
1930
+ changes: workspaceConfigChanges,
1931
+ hasUserModifications: workspaceConfigChanges.some(
1932
+ (change) => change.status === "modified"
1933
+ )
1934
+ });
2286
1935
  }
2287
1936
  }
2288
- let updatedCount = 0;
2289
- let skippedCount = 0;
2290
- for (const category of allCategories) {
2291
- const newChanges = category.changes.filter((c) => c.status === "added");
2292
- const modifiedChanges = category.changes.filter((c) => c.status === "modified");
2293
- const hasNew = newChanges.length > 0;
2294
- const hasModified = modifiedChanges.length > 0;
2295
- const hasChanges = hasNew || hasModified;
2296
- if (!hasChanges) {
2297
- console.log(color__default.green("\u2713") + ` ${category.label}: Up to date`);
2298
- continue;
2299
- }
2300
- if (category.category === "ai-files") {
2301
- if (hasNew) {
2302
- console.log(color__default.cyan(category.label + ":"));
2303
- console.log(color__default.dim(` ${newChanges.length} AI file(s) can be added`));
2304
- console.log();
2305
- const applyAi = options.yes ? true : await p__namespace.confirm({
2306
- message: "Add AI rules?",
2307
- initialValue: true
2308
- });
2309
- if (!p__namespace.isCancel(applyAi) && applyAi) {
2310
- await applyUpdates(newChanges, projectRoot);
2311
- console.log(color__default.green("\u2713") + ` Added ${newChanges.length} AI file(s)`);
2312
- updatedCount++;
2313
- } else {
2314
- console.log(color__default.dim(` Skipped ${category.label}`));
2315
- skippedCount++;
2316
- }
2317
- }
2318
- if (hasModified) {
2319
- console.log(color__default.cyan("AI Files (existing):"));
2320
- for (const change of modifiedChanges) {
2321
- console.log(formatFileChange(change));
2322
- }
2323
- console.log();
2324
- if (options.yes) {
2325
- console.log(color__default.dim(" (--yes mode: keeping existing AI files)"));
2326
- } else {
2327
- const updateExisting = await p__namespace.confirm({
2328
- message: "Update existing AI files to latest template?",
2329
- initialValue: false
2330
- });
2331
- if (!p__namespace.isCancel(updateExisting) && updateExisting) {
2332
- await applyUpdates(modifiedChanges, projectRoot);
2333
- console.log(color__default.green("\u2713") + " Updated existing AI files");
2334
- }
2335
- }
2336
- }
2337
- console.log();
2338
- continue;
2339
- }
2340
- let changesToApply = [];
2341
- if (options.yes) {
2342
- console.log(color__default.cyan(category.label + ":"));
2343
- for (const change of [...newChanges, ...modifiedChanges]) {
2344
- console.log(formatFileChange(change));
2345
- }
2346
- console.log();
2347
- if (category.category === "workspace-config") {
2348
- changesToApply = [...newChanges, ...modifiedChanges];
2349
- if (changesToApply.length > 0) {
2350
- console.log(color__default.dim(" (--yes mode: applying merge updates)"));
2351
- }
2352
- } else {
2353
- changesToApply = newChanges;
2354
- if (newChanges.length > 0) {
2355
- console.log(color__default.dim(" (--yes mode: adding new files only)"));
2356
- }
2357
- }
2358
- } else if (hasNew && hasModified) {
2359
- const allChanges = [...newChanges, ...modifiedChanges];
2360
- const selectedFiles = await p__namespace.multiselect({
2361
- message: `${category.label} (+ new, ~ changed)`,
2362
- options: allChanges.map((change) => ({
2363
- value: change.path,
2364
- label: change.status === "added" ? `+ ${change.path}` : `~ ${change.path}`
2365
- })),
2366
- initialValues: newChanges.map((c) => c.path),
2367
- // Pre-select new files
2368
- required: false
2369
- });
2370
- if (p__namespace.isCancel(selectedFiles)) {
2371
- p__namespace.cancel("Operation cancelled.");
2372
- process.exit(0);
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)"));
2373
1960
  }
2374
- if (selectedFiles.length > 0) {
2375
- 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)"));
2376
1965
  }
2377
- } else if (hasNew) {
2378
- console.log(color__default.cyan(category.label + ":"));
2379
- for (const change of newChanges) {
2380
- 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}`));
2381
2002
  }
2382
2003
  console.log();
2383
- const shouldAdd = await p__namespace.confirm({
2384
- 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?",
2385
2006
  initialValue: true
2386
2007
  });
2387
- if (p__namespace.isCancel(shouldAdd)) {
2388
- p__namespace.cancel("Operation cancelled.");
2389
- process.exit(0);
2390
- }
2391
- if (shouldAdd) {
2392
- changesToApply = newChanges;
2393
- }
2394
- } else if (hasModified) {
2395
- console.log(color__default.cyan(category.label + ":"));
2396
- for (const change of modifiedChanges) {
2397
- 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);
2398
2011
  }
2399
- console.log();
2400
- const shouldUpdate = await p__namespace.confirm({
2401
- message: `Update ${modifiedChanges.length} file(s)? (will overwrite)`,
2402
- 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
2403
2017
  });
2404
- if (p__namespace.isCancel(shouldUpdate)) {
2405
- p__namespace.cancel("Operation cancelled.");
2406
- process.exit(0);
2407
- }
2408
- if (shouldUpdate) {
2409
- changesToApply = modifiedChanges;
2410
- }
2411
- }
2412
- if (changesToApply.length > 0) {
2413
- await applyUpdates(changesToApply, projectRoot);
2414
- const addedCount = changesToApply.filter((c) => c.status === "added").length;
2415
- const updatedFilesCount = changesToApply.filter((c) => c.status === "modified").length;
2416
- const parts = [];
2417
- if (addedCount > 0) parts.push(`added ${addedCount}`);
2418
- if (updatedFilesCount > 0) parts.push(`updated ${updatedFilesCount}`);
2419
- console.log(color__default.green("\u2713") + ` ${category.label}: ${parts.join(", ")}`);
2420
- updatedCount++;
2421
- } else {
2422
- console.log(color__default.dim(` Skipped ${category.label}`));
2423
- skippedCount++;
2424
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"));
2425
2026
  console.log();
2426
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
+ }
2427
2040
  if (updatedCount === 0 && skippedCount === 0) {
2428
2041
  console.log(color__default.green("\u2713") + " Everything is up to date!");
2429
2042
  } else if (updatedCount > 0) {
@@ -2436,7 +2049,103 @@ async function handleUpdateCommand(options) {
2436
2049
  }
2437
2050
  process.exit(0);
2438
2051
  }
2439
- 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) {
2440
2149
  const monorepoRoot = await detectMonorepoRoot();
2441
2150
  if (!monorepoRoot) {
2442
2151
  console.error(color__default.red("Error:") + " --workspace flag requires being inside a monorepo");
@@ -2453,7 +2162,7 @@ async function handleWorkspaceCommand(name, options) {
2453
2162
  const defaultDir = projectType === "library" ? "packages" : "apps";
2454
2163
  const targetDir = options.dir ?? defaultDir;
2455
2164
  const template = options.template ?? "vanilla";
2456
- const baseTemplate = index.getBaseTemplate(template);
2165
+ const baseTemplate = workspace.getBaseTemplate(template);
2457
2166
  const scopedName = name.startsWith("@") ? name : `@${scope}/${name}`;
2458
2167
  const fullPackagePath = node_path.join(monorepoRoot, targetDir, name);
2459
2168
  try {
@@ -2473,7 +2182,7 @@ async function handleWorkspaceCommand(name, options) {
2473
2182
  const isLibrary = projectType === "library";
2474
2183
  const relativePkgPath = node_path.join(targetDir, name);
2475
2184
  const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
2476
- const generateOptions = {
2185
+ const projectOptions = {
2477
2186
  name: scopedName,
2478
2187
  projectType,
2479
2188
  libraryBundler: isLibrary ? options.bundler ?? "unbuild" : void 0,
@@ -2499,12 +2208,9 @@ async function handleWorkspaceCommand(name, options) {
2499
2208
  triplex: options.triplex ? {} : void 0
2500
2209
  }
2501
2210
  };
2502
- generateOptions.packageManager = await index.resolvePackageManager(generateOptions);
2503
- generateOptions.engine = await index.resolveEngine(generateOptions);
2504
- generateOptions.versions = await index.resolveProjectPackageVersions(generateOptions);
2505
2211
  console.log(color__default.cyan("Creating") + ` ${scopedName} in ${targetDir}/${name}...`);
2506
2212
  try {
2507
- const files = index.generate(generateOptions);
2213
+ const { files } = await workspace.planProject(workspace.resolveProjectPlanInput(projectOptions));
2508
2214
  await writeGeneratedFiles(fullPackagePath, files);
2509
2215
  console.log(color__default.green("\u2713") + ` Created ${scopedName} at ${targetDir}/${name}`);
2510
2216
  process.exit(0);
@@ -2514,72 +2220,130 @@ async function handleWorkspaceCommand(name, options) {
2514
2220
  process.exit(1);
2515
2221
  }
2516
2222
  }
2517
- async function handleMonorepoCreation(generateOptions, isNonInteractive) {
2518
- const { generateMonorepo } = await import('./chunks/index.cjs').then(function (n) { return n.monorepo; });
2519
- const packageManager = index.getPackageManagerName(generateOptions.packageManager);
2520
- generateOptions.packageManager = await index.resolvePackageManager(generateOptions);
2521
- generateOptions.engine = await index.resolveEngine(generateOptions);
2522
- generateOptions.versions = await index.resolveMonorepoRootPackageVersions({
2523
- linter: generateOptions.linter ?? "oxlint",
2524
- formatter: generateOptions.formatter ?? "prettier",
2525
- 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"
2526
2231
  });
2527
- const aiPlatforms = await promptForAiPlatforms(isNonInteractive);
2528
- 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);
2529
2299
  const spinner = p__namespace.spinner();
2530
2300
  spinner.start("Creating monorepo workspace...");
2531
2301
  try {
2532
- const { files } = generateMonorepo({
2533
- name: generateOptions.name,
2534
- linter: generateOptions.linter ?? "oxlint",
2535
- formatter: generateOptions.formatter ?? "prettier",
2536
- 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 ?? {
2537
2307
  name: packageManager
2538
2308
  },
2539
- pnpmManageVersions: generateOptions.pnpmManageVersions,
2540
- engine: generateOptions.engine,
2541
- versions: generateOptions.versions,
2542
- 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"
2543
2314
  });
2544
- const filePaths = Object.keys(files).sort();
2545
- for (const filePath of filePaths) {
2546
- const fullFilePath = node_path.join(projectPath, filePath);
2547
- await promises$1.mkdir(node_path.dirname(fullFilePath), { recursive: true });
2548
- const file = files[filePath];
2549
- if (file.type === "text") {
2550
- await promises$1.writeFile(fullFilePath, file.content);
2551
- }
2552
- }
2315
+ const { files } = await workspace.planWorkspace(planInput);
2316
+ await writeGeneratedFiles(projectPath, files);
2553
2317
  spinner.stop(color__default.green.inverse(" \u2713 Monorepo workspace created! "));
2554
2318
  if (isNonInteractive) {
2555
2319
  process.exit(0);
2556
2320
  }
2557
2321
  const newWorkspaceSettings = {
2558
- linter: generateOptions.linter,
2559
- formatter: generateOptions.formatter,
2560
- packageManager: generateOptions.packageManager ?? {
2322
+ linter: projectOptions.linter,
2323
+ formatter: projectOptions.formatter,
2324
+ packageManager: projectOptions.packageManager ?? {
2561
2325
  name: packageManager
2562
2326
  },
2563
- engine: generateOptions.engine,
2564
- pnpmManageVersions: generateOptions.pnpmManageVersions
2327
+ engine: projectOptions.engine,
2328
+ pnpmManageVersions: projectOptions.pnpmManageVersions
2565
2329
  };
2566
- const scope = generateOptions.name;
2330
+ const scope = projectOptions.name;
2567
2331
  let addMore = true;
2568
2332
  while (addMore) {
2569
2333
  addMore = await createPackageInWorkspace(
2570
2334
  projectPath,
2571
2335
  packageManager,
2572
2336
  newWorkspaceSettings,
2573
- scope
2337
+ scope,
2338
+ writeGeneratedFiles
2574
2339
  );
2575
2340
  }
2576
2341
  const nextSteps = [
2577
- `cd ${generateOptions.name}`,
2342
+ `cd ${projectOptions.name}`,
2578
2343
  `${packageManager} install`,
2579
2344
  `${packageManager} run dev`
2580
2345
  ].join("\n");
2581
2346
  p__namespace.note(nextSteps, "Next steps");
2582
- await promptAndOpenEditor(projectPath);
2583
2347
  p__namespace.outro(color__default.green("Happy coding! \u2728"));
2584
2348
  process.exit(0);
2585
2349
  } catch (error) {
@@ -2588,38 +2352,30 @@ async function handleMonorepoCreation(generateOptions, isNonInteractive) {
2588
2352
  process.exit(1);
2589
2353
  }
2590
2354
  }
2591
- async function handleStandaloneProjectCreation(generateOptions, isNonInteractive) {
2592
- 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";
2593
2357
  const defaultFallbackName = base === "vanilla" ? "vanilla-app" : base === "react" ? "react-app" : "react-three-app";
2594
- generateOptions.name ??= defaultFallbackName;
2595
- const aiPlatforms = await promptForAiPlatforms(isNonInteractive);
2596
- if (aiPlatforms.length > 0) {
2597
- generateOptions.aiPlatforms = aiPlatforms;
2598
- }
2599
- const packageManager = index.getPackageManagerName(generateOptions.packageManager);
2600
- const isLibrary = generateOptions.projectType === "library";
2601
- generateOptions.packageManager = await index.resolvePackageManager(generateOptions);
2602
- generateOptions.engine = await index.resolveEngine(generateOptions);
2603
- generateOptions.versions = await index.resolveProjectPackageVersions(generateOptions);
2604
- 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);
2605
2361
  const spinner = p__namespace.spinner();
2606
2362
  spinner.start("Creating project...");
2607
2363
  try {
2608
- const files = index.generate(generateOptions);
2364
+ const planInput = workspace.resolveProjectPlanInput(projectOptions);
2365
+ const { files } = await workspace.planProject(planInput);
2609
2366
  await writeGeneratedFiles(projectPath, files);
2610
2367
  spinner.stop(color__default.green.inverse(" \u2713 Project created! "));
2611
2368
  if (isNonInteractive) process.exit(0);
2612
- const nextSteps = isLibrary ? [
2613
- `cd ${generateOptions.name}`,
2369
+ const nextSteps = projectOptions.projectType === "library" ? [
2370
+ `cd ${projectOptions.name}`,
2614
2371
  `${packageManager} install`,
2615
2372
  `${packageManager} run build`
2616
2373
  ].join("\n") : [
2617
- `cd ${generateOptions.name}`,
2374
+ `cd ${projectOptions.name}`,
2618
2375
  `${packageManager} install`,
2619
2376
  `${packageManager} run dev`
2620
2377
  ].join("\n");
2621
2378
  p__namespace.note(nextSteps, "Next steps");
2622
- await promptAndOpenEditor(projectPath);
2623
2379
  p__namespace.outro(color__default.green("Happy coding! \u2728"));
2624
2380
  } catch (error) {
2625
2381
  spinner.stop("Failed to create project");
@@ -2627,46 +2383,6 @@ async function handleStandaloneProjectCreation(generateOptions, isNonInteractive
2627
2383
  process.exit(1);
2628
2384
  }
2629
2385
  }
2630
- async function handleInteractiveMonorepoMode(monorepoRoot) {
2631
- const choice = await p__namespace.select({
2632
- message: "Detected monorepo workspace",
2633
- options: [
2634
- { value: "add", label: "Add new package to this workspace" },
2635
- { value: "standalone", label: "Create standalone project" }
2636
- ],
2637
- initialValue: "add"
2638
- });
2639
- if (p__namespace.isCancel(choice)) {
2640
- p__namespace.cancel("Operation cancelled.");
2641
- process.exit(0);
2642
- }
2643
- if (choice === "add") {
2644
- const inheritedSettings = await detectWorkspaceSettings(monorepoRoot);
2645
- const hasSettings = Object.values(inheritedSettings).some(Boolean);
2646
- if (hasSettings) {
2647
- const settingsInfo = [
2648
- inheritedSettings.linter && `linter: ${inheritedSettings.linter}`,
2649
- inheritedSettings.formatter && `formatter: ${inheritedSettings.formatter}`,
2650
- inheritedSettings.packageManager && `pm: ${inheritedSettings.packageManager.name}`
2651
- ].filter(Boolean).join(", ");
2652
- p__namespace.log.info(`Using workspace settings (${settingsInfo})`);
2653
- }
2654
- const scope = await getMonorepoScope(monorepoRoot);
2655
- let addMore = true;
2656
- while (addMore) {
2657
- addMore = await createPackageInWorkspace(
2658
- monorepoRoot,
2659
- inheritedSettings.packageManager?.name ?? "pnpm",
2660
- inheritedSettings,
2661
- scope
2662
- );
2663
- }
2664
- p__namespace.note([`cd ${monorepoRoot}`, "pnpm install", "pnpm run dev"].join("\n"), "Next steps");
2665
- await promptAndOpenEditor(monorepoRoot);
2666
- p__namespace.outro(color__default.green("Happy coding! \u2728"));
2667
- process.exit(0);
2668
- }
2669
- }
2670
2386
  async function main() {
2671
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(
2672
2388
  "--bundler <bundler>",
@@ -2674,7 +2390,7 @@ async function main() {
2674
2390
  ).option(
2675
2391
  "--template <type>",
2676
2392
  "project template: vanilla, vanilla-js, react, react-js, r3f, r3f-js (default: vanilla)"
2677
- ).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(
2678
2394
  "--pnpm-manage-versions",
2679
2395
  "enable manage-package-manager-versions in pnpm-workspace.yaml (default: true)"
2680
2396
  ).option(
@@ -2683,7 +2399,7 @@ async function main() {
2683
2399
  ).option(
2684
2400
  "--node-version <version>",
2685
2401
  'set Node.js version for engines.node field (default: "latest")'
2686
- ).option("--workspace", "Add package to current monorepo workspace (non-interactive)").option("--dir <directory>", "Target directory for --workspace (default: apps/ or packages/)").option("--clear-config", "Clear saved preferences (e.g. editor choice)").option("--config-path", "Print the path to the config file").option("--check", "Check if current directory is in a valid monorepo workspace").option("--fix", "Fix monorepo by generating missing .config packages").option("--update", "Update monorepo workspace to latest configuration").option("-y, --yes", "Non-interactive mode - accept all prompts").option(
2402
+ ).option("--workspace", "Add package to current monorepo workspace (non-interactive)").option("--dir <directory>", "Target directory for --workspace (default: apps/ or packages/)").option("--clear-config", "Clear saved preferences").option("--config-path", "Print the path to the config file").option("--check", "Check if current directory is in a valid monorepo workspace").option("--fix", "Fix monorepo by generating missing .config packages").option("--update", "Update monorepo workspace to latest configuration").option("-y, --yes", "Non-interactive mode - accept all prompts").option(
2687
2403
  "--path <directory>",
2688
2404
  "Run in specified directory instead of current working directory"
2689
2405
  ).action(async (name, options) => {
@@ -2699,6 +2415,10 @@ async function main() {
2699
2415
  console.log(getConfigPath());
2700
2416
  process.exit(0);
2701
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
+ }
2702
2422
  if (name?.startsWith("-")) {
2703
2423
  switch (name) {
2704
2424
  case "--version":
@@ -2740,7 +2460,7 @@ async function main() {
2740
2460
  await handleFixCommand(options);
2741
2461
  }
2742
2462
  if (options.update) {
2743
- await handleUpdateCommand(options);
2463
+ await handleUpdateCommand(options, handleFixCommand);
2744
2464
  }
2745
2465
  if (options.dir && !options.workspace) {
2746
2466
  console.error(color__default.red("Error:") + " --dir requires --workspace flag");
@@ -2750,27 +2470,28 @@ async function main() {
2750
2470
  process.exit(1);
2751
2471
  }
2752
2472
  if (options.workspace) {
2753
- await handleWorkspaceCommand(name, options);
2473
+ await handleWorkspaceCommand(name, options, writeGeneratedFiles);
2754
2474
  }
2755
2475
  console.clear();
2756
2476
  p__namespace.intro(color__default.bgCyan(color__default.black(` create-krispya v${pkg.version} `)));
2757
2477
  const monorepoRoot = await detectMonorepoRoot();
2758
2478
  if (monorepoRoot && !hasConfigOptions(options)) {
2759
- await handleInteractiveMonorepoMode(monorepoRoot);
2479
+ await handleInteractiveMonorepoMode(monorepoRoot, writeGeneratedFiles);
2760
2480
  }
2761
- let generateOptions;
2481
+ let projectOptions;
2762
2482
  if (options.yes) {
2763
2483
  const template = options.template ?? "vanilla";
2764
- const baseTemplate = index.getBaseTemplate(template);
2484
+ const baseTemplate = workspace.getBaseTemplate(template);
2765
2485
  const defaultName = getDefaultProjectName(template);
2766
2486
  const projectType = options.type ?? "app";
2767
- generateOptions = {
2487
+ projectOptions = {
2768
2488
  name: name || defaultName,
2769
2489
  projectType,
2770
2490
  libraryBundler: projectType === "library" ? options.bundler ?? "unbuild" : void 0,
2771
2491
  template,
2772
2492
  linter: options.linter ?? "oxlint",
2773
2493
  formatter: options.formatter ?? "prettier",
2494
+ ide: options.ide ?? "vscode",
2774
2495
  ...baseTemplate === "r3f" && {
2775
2496
  drei: options.drei ? {} : void 0,
2776
2497
  handle: options.handle ? {} : void 0,
@@ -2797,6 +2518,7 @@ async function main() {
2797
2518
  linter: options.linter,
2798
2519
  formatter: options.formatter,
2799
2520
  packageManager: options.packageManager,
2521
+ ide: options.ide,
2800
2522
  engine: options.nodeVersion ? { name: "node", version: options.nodeVersion } : void 0,
2801
2523
  pnpmManageVersions: options.pnpmManageVersions,
2802
2524
  drei: options.drei,
@@ -2812,13 +2534,17 @@ async function main() {
2812
2534
  triplex: options.triplex,
2813
2535
  viverse: options.viverse
2814
2536
  } : void 0;
2815
- generateOptions = await promptForOptions(name, presets);
2537
+ projectOptions = await promptForOptions(name, presets);
2816
2538
  }
2817
2539
  const isNonInteractive = options.yes ?? false;
2818
- if (generateOptions.projectType === "monorepo") {
2819
- 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);
2820
2546
  } else {
2821
- await handleStandaloneProjectCreation(generateOptions, isNonInteractive);
2547
+ await handleSingleWorkspaceCreation(projectOptions, isNonInteractive);
2822
2548
  }
2823
2549
  });
2824
2550
  await program.parseAsync();