create-krispya 0.16.0 → 0.17.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.
@@ -277,6 +277,7 @@ async function detectTooling(root) {
277
277
 
278
278
  const DEFAULT_MINIMUM_RELEASE_AGE_MINUTES = 1440;
279
279
  const MINUTE_IN_MS = 60 * 1e3;
280
+ const SEMVER_PATTERN = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
280
281
  function getMinimumReleaseAgeMinutes(options) {
281
282
  return Math.max(0, options?.minimumReleaseAgeMinutes ?? DEFAULT_MINIMUM_RELEASE_AGE_MINUTES);
282
283
  }
@@ -292,6 +293,62 @@ function isVersionOldEnough(version, time, options) {
292
293
  function getInstallableVersions(versions, time, options) {
293
294
  return [...versions].filter((version) => isVersionOldEnough(version, time, options));
294
295
  }
296
+ function parseSemver(version) {
297
+ const match = version.match(SEMVER_PATTERN);
298
+ if (match == null) return void 0;
299
+ return {
300
+ major: Number.parseInt(match[1], 10),
301
+ minor: Number.parseInt(match[2], 10),
302
+ patch: Number.parseInt(match[3], 10),
303
+ prerelease: match[4]?.split(".") ?? []
304
+ };
305
+ }
306
+ function hasPrerelease(version) {
307
+ return (parseSemver(version)?.prerelease.length ?? 0) > 0;
308
+ }
309
+ function comparePrerelease(aParts, bParts) {
310
+ if (aParts.length === 0 && bParts.length === 0) return 0;
311
+ if (aParts.length === 0) return 1;
312
+ if (bParts.length === 0) return -1;
313
+ const maxLength = Math.max(aParts.length, bParts.length);
314
+ for (let index = 0; index < maxLength; index += 1) {
315
+ const aPart = aParts[index];
316
+ const bPart = bParts[index];
317
+ if (aPart == null) return -1;
318
+ if (bPart == null) return 1;
319
+ if (aPart === bPart) continue;
320
+ const aNumber = /^\d+$/.test(aPart) ? Number.parseInt(aPart, 10) : void 0;
321
+ const bNumber = /^\d+$/.test(bPart) ? Number.parseInt(bPart, 10) : void 0;
322
+ if (aNumber != null && bNumber != null) return aNumber - bNumber;
323
+ if (aNumber != null) return -1;
324
+ if (bNumber != null) return 1;
325
+ return aPart.localeCompare(bPart);
326
+ }
327
+ return 0;
328
+ }
329
+ function compareSemver(a, b) {
330
+ const aParsed = parseSemver(a);
331
+ const bParsed = parseSemver(b);
332
+ if (aParsed == null || bParsed == null) {
333
+ return compareNumericSemver(a, b);
334
+ }
335
+ const majorDifference = aParsed.major - bParsed.major;
336
+ if (majorDifference !== 0) return majorDifference;
337
+ const minorDifference = aParsed.minor - bParsed.minor;
338
+ if (minorDifference !== 0) return minorDifference;
339
+ const patchDifference = aParsed.patch - bParsed.patch;
340
+ if (patchDifference !== 0) return patchDifference;
341
+ return comparePrerelease(aParsed.prerelease, bParsed.prerelease);
342
+ }
343
+ function getLatestChannelVersions(versions, latestVersion) {
344
+ const availableVersions = [...versions];
345
+ if (latestVersion == null) return availableVersions.filter((version) => !hasPrerelease(version));
346
+ const latestIsPrerelease = hasPrerelease(latestVersion);
347
+ return availableVersions.filter((version) => {
348
+ if (!latestIsPrerelease && hasPrerelease(version)) return false;
349
+ return compareSemver(version, latestVersion) <= 0;
350
+ });
351
+ }
295
352
  async function fetchNpmPackageMetadata(packageName) {
296
353
  const response = await fetch(`https://registry.npmjs.org/${packageName}`);
297
354
  return await response.json();
@@ -309,10 +366,10 @@ async function getLatestNpmVersion(packageName, fallback, options) {
309
366
  return latestVersion;
310
367
  }
311
368
  const latestInstallableVersion = getInstallableVersions(
312
- Object.keys(data.versions ?? {}),
369
+ getLatestChannelVersions(Object.keys(data.versions ?? {}), latestVersion),
313
370
  data.time,
314
371
  options
315
- ).sort((a, b) => compareNumericSemver(b, a))[0];
372
+ ).sort((a, b) => compareSemver(b, a))[0];
316
373
  return latestInstallableVersion ?? fallback;
317
374
  } catch {
318
375
  return fallback;
@@ -339,7 +396,7 @@ function compareNumericSemver(a, b) {
339
396
  return 0;
340
397
  }
341
398
  function getLatestMatchingMajorVersion(versions, majorVersion, time, options) {
342
- return getInstallableVersions(versions, time, options).filter((version) => version.split(".")[0] === majorVersion).sort((a, b) => compareNumericSemver(b, a))[0];
399
+ return getInstallableVersions(versions, time, options).filter((version) => version.split(".")[0] === majorVersion).filter((version) => !hasPrerelease(version)).sort((a, b) => compareSemver(b, a))[0];
343
400
  }
344
401
  async function getLatestNpmMajorVersion(packageName, majorVersion, fallback, options) {
345
402
  try {
@@ -617,6 +674,8 @@ function getConfigPath() {
617
674
  }
618
675
 
619
676
  const ALL_AI_PLATFORMS = ["agents", "claude"];
677
+ const AI_FILE_MANAGED_BEGIN = "<!-- managed:start -->";
678
+ const AI_FILE_MANAGED_END = "<!-- managed:end -->";
620
679
  const AI_PLATFORM_LABELS = {
621
680
  agents: "AGENTS.md",
622
681
  claude: "CLAUDE.md"
@@ -628,12 +687,14 @@ const AI_PLATFORM_HINTS = {
628
687
  function renderAiFiles(files, params) {
629
688
  const { platforms, isMonorepo, configStrategy, hasTypecheck, ...rest } = params;
630
689
  if (platforms.length === 0) return;
631
- const content = generateWorkspace({
632
- ...rest,
633
- isMonorepo: !!isMonorepo,
634
- configStrategy: configStrategy ?? "stealth",
635
- hasTypecheck: hasTypecheck ?? false
636
- });
690
+ const content = renderManagedAiFileContent(
691
+ generateWorkspace({
692
+ ...rest,
693
+ isMonorepo: !!isMonorepo,
694
+ configStrategy: configStrategy ?? "stealth",
695
+ hasTypecheck: hasTypecheck ?? false
696
+ })
697
+ );
637
698
  const pointer = "See [`AGENTS.md`](./Agents.md) for agent context.\n";
638
699
  const hasAgents = platforms.includes("agents");
639
700
  const hasClaude = platforms.includes("claude");
@@ -647,18 +708,89 @@ function renderAiFiles(files, params) {
647
708
  }
648
709
  }
649
710
  }
711
+ function renderManagedAiFileContent(content) {
712
+ return [AI_FILE_MANAGED_BEGIN, content.trimEnd(), AI_FILE_MANAGED_END, ""].join("\n");
713
+ }
714
+ function getManagedBlockRange$1(content) {
715
+ const beginIndex = content.indexOf(AI_FILE_MANAGED_BEGIN);
716
+ const endIndex = content.indexOf(AI_FILE_MANAGED_END);
717
+ if (beginIndex === -1 && endIndex === -1) {
718
+ return { status: "missing" };
719
+ }
720
+ if (beginIndex === -1 || endIndex === -1 || endIndex < beginIndex) {
721
+ return { status: "conflicted" };
722
+ }
723
+ return {
724
+ status: "found",
725
+ beginIndex,
726
+ endIndex: endIndex + AI_FILE_MANAGED_END.length
727
+ };
728
+ }
729
+ function getManagedInnerContent(content) {
730
+ const range = getManagedBlockRange$1(content);
731
+ if (range.status !== "found") return void 0;
732
+ return content.slice(
733
+ range.beginIndex + AI_FILE_MANAGED_BEGIN.length,
734
+ range.endIndex - AI_FILE_MANAGED_END.length
735
+ ).replace(/^\r?\n/, "").replace(/\r?\n$/, "");
736
+ }
737
+ function mergeMissingManagedAiBlock(currentContent, expectedContent, legacyContent) {
738
+ if (currentContent.trim() === "") return expectedContent;
739
+ if (legacyContent != null && currentContent.startsWith(legacyContent)) {
740
+ const suffix = currentContent.slice(legacyContent.length);
741
+ return suffix === "" ? expectedContent : `${expectedContent.trimEnd()}${suffix}`;
742
+ }
743
+ return `${expectedContent.trimEnd()}
744
+
745
+ ${currentContent.trim()}
746
+ `;
747
+ }
748
+ function isManagedAiFilePath(path) {
749
+ return path === "AGENTS.md" || path === "CLAUDE.md";
750
+ }
751
+ function mergeAiFileContent(currentContent, expectedContent) {
752
+ if (!expectedContent.includes(AI_FILE_MANAGED_BEGIN)) {
753
+ return {
754
+ content: expectedContent,
755
+ mergeSafe: false
756
+ };
757
+ }
758
+ const range = getManagedBlockRange$1(currentContent);
759
+ if (range.status === "found") {
760
+ return {
761
+ content: `${currentContent.slice(0, range.beginIndex)}${expectedContent.trimEnd()}${currentContent.slice(
762
+ range.endIndex
763
+ )}`,
764
+ mergeSafe: true
765
+ };
766
+ }
767
+ if (range.status === "conflicted") {
768
+ return {
769
+ content: expectedContent,
770
+ mergeSafe: false
771
+ };
772
+ }
773
+ return {
774
+ content: mergeMissingManagedAiBlock(
775
+ currentContent,
776
+ expectedContent,
777
+ getManagedInnerContent(expectedContent)
778
+ ),
779
+ mergeSafe: true
780
+ };
781
+ }
650
782
  function generateWorkspace(ctx) {
651
783
  const { packageManager, linter, formatter, hasTypecheck } = ctx;
652
784
  const exampleFiles = "src/App.tsx src/core/systems/move-entity.ts";
653
785
  const commands = getAfterEditingCommands(ctx, exampleFiles);
654
786
  const sections = [
655
- "# Workspace Tools",
787
+ "## Workspace Tools",
656
788
  "",
657
789
  `- **Package Manager:** ${packageManager}`,
658
790
  `- **Linter:** ${linter}`,
659
791
  `- **Formatter:** ${formatter}`,
660
792
  "",
661
- "## After Editing",
793
+ "### After Editing",
662
794
  ""
663
795
  ];
664
796
  if (hasTypecheck) {
@@ -1114,6 +1246,54 @@ function isEnabledOption(option) {
1114
1246
  return option != null && option !== false;
1115
1247
  }
1116
1248
 
1249
+ function renderJson(value, options = {}) {
1250
+ const json = JSON.stringify(value, null, 2);
1251
+ const content = options.inlineArrays === false ? json : inlinePrimitiveArrays(json, options);
1252
+ return `${content}
1253
+ `;
1254
+ }
1255
+ function inlinePrimitiveArrays(json, options) {
1256
+ const printWidth = options.printWidth ?? 102;
1257
+ const lines = json.split("\n");
1258
+ const output = [];
1259
+ for (let index = 0; index < lines.length; index++) {
1260
+ const line = lines[index];
1261
+ const openIndex = line.indexOf("[");
1262
+ if (openIndex === -1 || line.slice(openIndex).trim() !== "[") {
1263
+ output.push(line);
1264
+ continue;
1265
+ }
1266
+ const items = [];
1267
+ let cursor = index + 1;
1268
+ let closingComma = "";
1269
+ for (; cursor < lines.length; cursor++) {
1270
+ const item = lines[cursor].trim();
1271
+ const closingMatch = item.match(/^\](,?)$/);
1272
+ if (closingMatch) {
1273
+ closingComma = closingMatch[1] ?? "";
1274
+ break;
1275
+ }
1276
+ if (!/^(?:"(?:[^"\\]|\\.)*"|-?\d+(?:\.\d+)?|true|false|null),?$/.test(item)) {
1277
+ items.length = 0;
1278
+ break;
1279
+ }
1280
+ items.push(item.replace(/,$/, ""));
1281
+ }
1282
+ if (items.length === 0 || cursor >= lines.length) {
1283
+ output.push(line);
1284
+ continue;
1285
+ }
1286
+ const nextLine = `${line.slice(0, openIndex)}[${items.join(", ")}]${closingComma}`;
1287
+ if (nextLine.length > printWidth) {
1288
+ output.push(line);
1289
+ continue;
1290
+ }
1291
+ output.push(nextLine);
1292
+ index = cursor;
1293
+ }
1294
+ return output.join("\n");
1295
+ }
1296
+
1117
1297
  function renderTypescriptConfig(baseTemplateOrParams) {
1118
1298
  const params = typeof baseTemplateOrParams === "string" ? { baseTemplate: baseTemplateOrParams } : baseTemplateOrParams;
1119
1299
  const {
@@ -1145,15 +1325,11 @@ function renderTypescriptConfig(baseTemplateOrParams) {
1145
1325
  const baseConfig = isReact || isR3f ? "@config/typescript/react.json" : "@config/typescript/app.json";
1146
1326
  files["tsconfig.json"] = {
1147
1327
  type: "text",
1148
- content: JSON.stringify(
1149
- {
1150
- $schema: "https://json.schemastore.org/tsconfig",
1151
- extends: baseConfig,
1152
- include: ["src/**/*", "tests/**/*"]
1153
- },
1154
- null,
1155
- 2
1156
- )
1328
+ content: renderJson({
1329
+ $schema: "https://json.schemastore.org/tsconfig",
1330
+ extends: baseConfig,
1331
+ include: ["src/**/*", "tests/**/*"]
1332
+ })
1157
1333
  };
1158
1334
  return { files, devDependencies };
1159
1335
  }
@@ -1165,7 +1341,7 @@ function renderTypescriptConfig(baseTemplateOrParams) {
1165
1341
  };
1166
1342
  files["tsconfig.json"] = {
1167
1343
  type: "text",
1168
- content: JSON.stringify(tsConfig, null, 2)
1344
+ content: renderJson(tsConfig)
1169
1345
  };
1170
1346
  const tsConfigApp = {
1171
1347
  $schema: "https://json.schemastore.org/tsconfig",
@@ -1188,7 +1364,7 @@ function renderTypescriptConfig(baseTemplateOrParams) {
1188
1364
  };
1189
1365
  files[".config/tsconfig.app.json"] = {
1190
1366
  type: "text",
1191
- content: JSON.stringify(tsConfigApp, null, 2)
1367
+ content: renderJson(tsConfigApp)
1192
1368
  };
1193
1369
  const tsConfigNode = {
1194
1370
  $schema: "https://json.schemastore.org/tsconfig",
@@ -1210,7 +1386,7 @@ function renderTypescriptConfig(baseTemplateOrParams) {
1210
1386
  };
1211
1387
  files[".config/tsconfig.node.json"] = {
1212
1388
  type: "text",
1213
- content: JSON.stringify(tsConfigNode, null, 2)
1389
+ content: renderJson(tsConfigNode)
1214
1390
  };
1215
1391
  } else {
1216
1392
  const tsConfig = {
@@ -1220,7 +1396,7 @@ function renderTypescriptConfig(baseTemplateOrParams) {
1220
1396
  };
1221
1397
  files["tsconfig.json"] = {
1222
1398
  type: "text",
1223
- content: JSON.stringify(tsConfig, null, 2)
1399
+ content: renderJson(tsConfig)
1224
1400
  };
1225
1401
  const tsConfigApp = {
1226
1402
  $schema: "https://json.schemastore.org/tsconfig",
@@ -1243,7 +1419,7 @@ function renderTypescriptConfig(baseTemplateOrParams) {
1243
1419
  };
1244
1420
  files["tsconfig.app.json"] = {
1245
1421
  type: "text",
1246
- content: JSON.stringify(tsConfigApp, null, 2)
1422
+ content: renderJson(tsConfigApp)
1247
1423
  };
1248
1424
  const tsConfigNode = {
1249
1425
  $schema: "https://json.schemastore.org/tsconfig",
@@ -1265,7 +1441,7 @@ function renderTypescriptConfig(baseTemplateOrParams) {
1265
1441
  };
1266
1442
  files["tsconfig.node.json"] = {
1267
1443
  type: "text",
1268
- content: JSON.stringify(tsConfigNode, null, 2)
1444
+ content: renderJson(tsConfigNode)
1269
1445
  };
1270
1446
  }
1271
1447
  return { files, devDependencies };
@@ -1453,7 +1629,7 @@ function renderPackageJson(params) {
1453
1629
  }
1454
1630
  files["package.json"] = {
1455
1631
  type: "text",
1456
- content: JSON.stringify(packageJson, null, 2)
1632
+ content: renderJson(packageJson, { inlineArrays: false })
1457
1633
  };
1458
1634
  if (isPnpm && !options.workspaceRoot) {
1459
1635
  files["pnpm-workspace.yaml"] = {
@@ -1825,6 +2001,8 @@ function renderTestFiles(params) {
1825
2001
  return files;
1826
2002
  }
1827
2003
 
2004
+ const GITIGNORE_MANAGED_BEGIN = "# managed:start";
2005
+ const GITIGNORE_MANAGED_END = "# managed:end";
1828
2006
  const COMMON_GITIGNORE_LINES = [
1829
2007
  "node_modules",
1830
2008
  "dist",
@@ -1834,13 +2012,77 @@ const COMMON_GITIGNORE_LINES = [
1834
2012
  "!.env.example",
1835
2013
  ".pnpm-store"
1836
2014
  ];
2015
+ function getCoreGitignoreLines(variant) {
2016
+ return variant === "workspace-root" ? [...COMMON_GITIGNORE_LINES, ".DS_Store"] : COMMON_GITIGNORE_LINES;
2017
+ }
2018
+ function renderManagedGitignoreBlock(variant) {
2019
+ return [GITIGNORE_MANAGED_BEGIN, ...getCoreGitignoreLines(variant), GITIGNORE_MANAGED_END].join(
2020
+ "\n"
2021
+ );
2022
+ }
1837
2023
  function renderGitignore(variant) {
1838
- const lines = variant === "workspace-root" ? [...COMMON_GITIGNORE_LINES, ".DS_Store"] : COMMON_GITIGNORE_LINES;
1839
2024
  return {
1840
2025
  type: "text",
1841
- content: lines.join("\n")
2026
+ content: renderManagedGitignoreBlock(variant)
1842
2027
  };
1843
2028
  }
2029
+ function getManagedBlockRange(content) {
2030
+ const beginIndex = content.indexOf(GITIGNORE_MANAGED_BEGIN);
2031
+ const endIndex = content.indexOf(GITIGNORE_MANAGED_END);
2032
+ if (beginIndex === -1 && endIndex === -1) {
2033
+ return { status: "missing" };
2034
+ }
2035
+ if (beginIndex === -1 || endIndex === -1 || endIndex < beginIndex) {
2036
+ return { status: "conflicted" };
2037
+ }
2038
+ return {
2039
+ status: "found",
2040
+ beginIndex,
2041
+ endIndex: endIndex + GITIGNORE_MANAGED_END.length
2042
+ };
2043
+ }
2044
+ function trimBlankEdges(lines) {
2045
+ let start = 0;
2046
+ let end = lines.length;
2047
+ while (start < end && lines[start]?.trim() === "") {
2048
+ start++;
2049
+ }
2050
+ while (end > start && lines[end - 1]?.trim() === "") {
2051
+ end--;
2052
+ }
2053
+ return lines.slice(start, end);
2054
+ }
2055
+ function mergeGitignoreContent(currentContent, variant) {
2056
+ const managedBlock = renderManagedGitignoreBlock(variant);
2057
+ const range = getManagedBlockRange(currentContent);
2058
+ if (range.status === "found") {
2059
+ return {
2060
+ content: `${currentContent.slice(0, range.beginIndex)}${managedBlock}${currentContent.slice(
2061
+ range.endIndex
2062
+ )}`,
2063
+ mergeSafe: true
2064
+ };
2065
+ }
2066
+ if (range.status === "conflicted") {
2067
+ return {
2068
+ content: managedBlock,
2069
+ mergeSafe: false
2070
+ };
2071
+ }
2072
+ const coreLines = new Set(getCoreGitignoreLines(variant));
2073
+ const customLines = trimBlankEdges(
2074
+ currentContent.split(/\r?\n/).filter((line) => !coreLines.has(line.trim()))
2075
+ );
2076
+ return {
2077
+ content: customLines.length > 0 ? `${managedBlock}
2078
+
2079
+ ${customLines.join("\n")}` : managedBlock,
2080
+ mergeSafe: true
2081
+ };
2082
+ }
2083
+ function detectGitignoreVariant(content) {
2084
+ return content.split(/\r?\n/).includes(".DS_Store") ? "workspace-root" : "standalone";
2085
+ }
1844
2086
 
1845
2087
  const defaultFormatterMetaConfig = {
1846
2088
  printWidth: 102,
@@ -2013,13 +2255,9 @@ function renderVscodeFiles$1(params) {
2013
2255
  const uniqueRecommendations = [...new Set(recommendations)];
2014
2256
  files[".vscode/extensions.json"] = {
2015
2257
  type: "text",
2016
- content: JSON.stringify(
2017
- {
2018
- recommendations: uniqueRecommendations
2019
- },
2020
- null,
2021
- 2
2022
- )
2258
+ content: renderJson({
2259
+ recommendations: uniqueRecommendations
2260
+ })
2023
2261
  };
2024
2262
  }
2025
2263
  const resolvedSettings = {
@@ -2032,7 +2270,7 @@ function renderVscodeFiles$1(params) {
2032
2270
  );
2033
2271
  files[".vscode/settings.json"] = {
2034
2272
  type: "text",
2035
- content: JSON.stringify(sortedSettings, null, 2)
2273
+ content: renderJson(sortedSettings)
2036
2274
  };
2037
2275
  }
2038
2276
  return files;
@@ -2207,15 +2445,14 @@ function renderTypescriptConfigPackage(files) {
2207
2445
  const basePath = ".config/typescript";
2208
2446
  files[`${basePath}/package.json`] = {
2209
2447
  type: "text",
2210
- content: JSON.stringify(
2448
+ content: renderJson(
2211
2449
  {
2212
2450
  name: "@config/typescript",
2213
2451
  version: "0.1.0",
2214
2452
  private: true,
2215
2453
  files: ["base.json", "app.json", "node.json", "react.json"]
2216
2454
  },
2217
- null,
2218
- 2
2455
+ { inlineArrays: false }
2219
2456
  )
2220
2457
  };
2221
2458
  files[`${basePath}/README.md`] = {
@@ -2245,82 +2482,65 @@ In your package's \`tsconfig.json\`:
2245
2482
  };
2246
2483
  files[`${basePath}/base.json`] = {
2247
2484
  type: "text",
2248
- content: JSON.stringify(
2249
- {
2250
- $schema: "https://json.schemastore.org/tsconfig",
2251
- compilerOptions: {
2252
- target: "ESNext",
2253
- module: "ESNext",
2254
- moduleResolution: "bundler",
2255
- esModuleInterop: true,
2256
- allowSyntheticDefaultImports: true,
2257
- strict: true,
2258
- skipLibCheck: true,
2259
- composite: true,
2260
- rewriteRelativeImportExtensions: true,
2261
- erasableSyntaxOnly: true
2262
- }
2263
- },
2264
- null,
2265
- 2
2266
- )
2485
+ content: renderJson({
2486
+ $schema: "https://json.schemastore.org/tsconfig",
2487
+ compilerOptions: {
2488
+ target: "ESNext",
2489
+ module: "ESNext",
2490
+ moduleResolution: "bundler",
2491
+ esModuleInterop: true,
2492
+ allowSyntheticDefaultImports: true,
2493
+ strict: true,
2494
+ skipLibCheck: true,
2495
+ composite: true,
2496
+ rewriteRelativeImportExtensions: true,
2497
+ erasableSyntaxOnly: true
2498
+ }
2499
+ })
2267
2500
  };
2268
2501
  files[`${basePath}/app.json`] = {
2269
2502
  type: "text",
2270
- content: JSON.stringify(
2271
- {
2272
- $schema: "https://json.schemastore.org/tsconfig",
2273
- extends: "./base.json",
2274
- compilerOptions: {
2275
- lib: ["DOM", "DOM.Iterable", "ESNext"]
2276
- }
2277
- },
2278
- null,
2279
- 2
2280
- )
2503
+ content: renderJson({
2504
+ $schema: "https://json.schemastore.org/tsconfig",
2505
+ extends: "./base.json",
2506
+ compilerOptions: {
2507
+ lib: ["DOM", "DOM.Iterable", "ESNext"]
2508
+ }
2509
+ })
2281
2510
  };
2282
2511
  files[`${basePath}/node.json`] = {
2283
2512
  type: "text",
2284
- content: JSON.stringify(
2285
- {
2286
- $schema: "https://json.schemastore.org/tsconfig",
2287
- extends: "./base.json",
2288
- compilerOptions: {
2289
- lib: ["ESNext"]
2290
- }
2291
- },
2292
- null,
2293
- 2
2294
- )
2513
+ content: renderJson({
2514
+ $schema: "https://json.schemastore.org/tsconfig",
2515
+ extends: "./base.json",
2516
+ compilerOptions: {
2517
+ lib: ["ESNext"]
2518
+ }
2519
+ })
2295
2520
  };
2296
2521
  files[`${basePath}/react.json`] = {
2297
2522
  type: "text",
2298
- content: JSON.stringify(
2299
- {
2300
- $schema: "https://json.schemastore.org/tsconfig",
2301
- extends: "./app.json",
2302
- compilerOptions: {
2303
- jsx: "react-jsx"
2304
- }
2305
- },
2306
- null,
2307
- 2
2308
- )
2523
+ content: renderJson({
2524
+ $schema: "https://json.schemastore.org/tsconfig",
2525
+ extends: "./app.json",
2526
+ compilerOptions: {
2527
+ jsx: "react-jsx"
2528
+ }
2529
+ })
2309
2530
  };
2310
2531
  }
2311
2532
  function renderOxlintConfigPackage(files) {
2312
2533
  const basePath = ".config/oxlint";
2313
2534
  files[`${basePath}/package.json`] = {
2314
2535
  type: "text",
2315
- content: JSON.stringify(
2536
+ content: renderJson(
2316
2537
  {
2317
2538
  name: "@config/oxlint",
2318
2539
  version: "0.1.0",
2319
2540
  private: true,
2320
2541
  files: ["base.json", "react.json"]
2321
2542
  },
2322
- null,
2323
- 2
2543
+ { inlineArrays: false }
2324
2544
  )
2325
2545
  };
2326
2546
  files[`${basePath}/README.md`] = {
@@ -2345,25 +2565,21 @@ oxlint -c node_modules/@config/oxlint/base.json
2345
2565
  };
2346
2566
  files[`${basePath}/base.json`] = {
2347
2567
  type: "text",
2348
- content: JSON.stringify(
2568
+ content: renderJson(
2349
2569
  renderOxlintConfig({
2350
2570
  schemaPath: "./node_modules/oxlint/configuration_schema.json",
2351
2571
  typescript: true
2352
- }),
2353
- null,
2354
- 2
2572
+ })
2355
2573
  )
2356
2574
  };
2357
2575
  files[`${basePath}/react.json`] = {
2358
2576
  type: "text",
2359
- content: JSON.stringify(
2577
+ content: renderJson(
2360
2578
  renderOxlintConfig({
2361
2579
  schemaPath: "./node_modules/oxlint/configuration_schema.json",
2362
2580
  react: true,
2363
2581
  typescript: true
2364
- }),
2365
- null,
2366
- 2
2582
+ })
2367
2583
  )
2368
2584
  };
2369
2585
  }
@@ -2371,7 +2587,7 @@ function renderEslintConfigPackage(files) {
2371
2587
  const basePath = ".config/eslint";
2372
2588
  files[`${basePath}/package.json`] = {
2373
2589
  type: "text",
2374
- content: JSON.stringify(
2590
+ content: renderJson(
2375
2591
  {
2376
2592
  name: "@config/eslint",
2377
2593
  version: "0.1.0",
@@ -2387,8 +2603,7 @@ function renderEslintConfigPackage(files) {
2387
2603
  "typescript-eslint": "^8.18.0"
2388
2604
  }
2389
2605
  },
2390
- null,
2391
- 2
2606
+ { inlineArrays: false }
2392
2607
  )
2393
2608
  };
2394
2609
  files[`${basePath}/README.md`] = {
@@ -2423,52 +2638,52 @@ export default [...react];
2423
2638
  };
2424
2639
  files[`${basePath}/base.js`] = {
2425
2640
  type: "text",
2426
- content: `import js from "@eslint/js";
2427
- import tseslint from "typescript-eslint";
2641
+ content: `import js from '@eslint/js';
2642
+ import tseslint from 'typescript-eslint';
2428
2643
 
2429
2644
  export default tseslint.config(
2430
2645
  js.configs.recommended,
2431
2646
  ...tseslint.configs.recommended,
2432
2647
  {
2433
2648
  rules: {
2434
- "@typescript-eslint/no-unused-vars": [
2435
- "error",
2649
+ '@typescript-eslint/no-unused-vars': [
2650
+ 'error',
2436
2651
  {
2437
- argsIgnorePattern: "^_",
2438
- varsIgnorePattern: "^_",
2439
- caughtErrorsIgnorePattern: "^_",
2652
+ argsIgnorePattern: '^_',
2653
+ varsIgnorePattern: '^_',
2654
+ caughtErrorsIgnorePattern: '^_',
2440
2655
  },
2441
2656
  ],
2442
2657
  },
2443
2658
  },
2444
2659
  {
2445
- ignores: ["dist/**", "node_modules/**"],
2660
+ ignores: ['dist/**', 'node_modules/**'],
2446
2661
  }
2447
2662
  );
2448
2663
  `
2449
2664
  };
2450
2665
  files[`${basePath}/react.js`] = {
2451
2666
  type: "text",
2452
- content: `import js from "@eslint/js";
2453
- import tseslint from "typescript-eslint";
2667
+ content: `import js from '@eslint/js';
2668
+ import tseslint from 'typescript-eslint';
2454
2669
 
2455
2670
  export default tseslint.config(
2456
2671
  js.configs.recommended,
2457
2672
  ...tseslint.configs.recommended,
2458
2673
  {
2459
2674
  rules: {
2460
- "@typescript-eslint/no-unused-vars": [
2461
- "error",
2675
+ '@typescript-eslint/no-unused-vars': [
2676
+ 'error',
2462
2677
  {
2463
- argsIgnorePattern: "^_",
2464
- varsIgnorePattern: "^_",
2465
- caughtErrorsIgnorePattern: "^_",
2678
+ argsIgnorePattern: '^_',
2679
+ varsIgnorePattern: '^_',
2680
+ caughtErrorsIgnorePattern: '^_',
2466
2681
  },
2467
2682
  ],
2468
2683
  },
2469
2684
  },
2470
2685
  {
2471
- ignores: ["dist/**", "node_modules/**"],
2686
+ ignores: ['dist/**', 'node_modules/**'],
2472
2687
  }
2473
2688
  );
2474
2689
  `
@@ -2478,7 +2693,7 @@ function renderPrettierConfigPackage(files) {
2478
2693
  const basePath = ".config/prettier";
2479
2694
  files[`${basePath}/package.json`] = {
2480
2695
  type: "text",
2481
- content: JSON.stringify(
2696
+ content: renderJson(
2482
2697
  {
2483
2698
  name: "@config/prettier",
2484
2699
  version: "0.1.0",
@@ -2489,8 +2704,7 @@ function renderPrettierConfigPackage(files) {
2489
2704
  },
2490
2705
  files: ["base.json", "prettierignore"]
2491
2706
  },
2492
- null,
2493
- 2
2707
+ { inlineArrays: false }
2494
2708
  )
2495
2709
  };
2496
2710
  files[`${basePath}/README.md`] = {
@@ -2522,7 +2736,7 @@ Or in \`.prettierrc.json\`:
2522
2736
  };
2523
2737
  files[`${basePath}/base.json`] = {
2524
2738
  type: "text",
2525
- content: JSON.stringify(toPrettierConfig(), null, 2)
2739
+ content: renderJson(toPrettierConfig())
2526
2740
  };
2527
2741
  files[`${basePath}/prettierignore`] = {
2528
2742
  type: "text",
@@ -2533,15 +2747,14 @@ function renderOxfmtConfigPackage(files) {
2533
2747
  const basePath = ".config/oxfmt";
2534
2748
  files[`${basePath}/package.json`] = {
2535
2749
  type: "text",
2536
- content: JSON.stringify(
2750
+ content: renderJson(
2537
2751
  {
2538
2752
  name: "@config/oxfmt",
2539
2753
  version: "0.1.0",
2540
2754
  private: true,
2541
2755
  files: ["base.json"]
2542
2756
  },
2543
- null,
2544
- 2
2757
+ { inlineArrays: false }
2545
2758
  )
2546
2759
  };
2547
2760
  files[`${basePath}/README.md`] = {
@@ -2565,7 +2778,7 @@ oxfmt -c node_modules/@config/oxfmt/base.json --write .
2565
2778
  };
2566
2779
  files[`${basePath}/base.json`] = {
2567
2780
  type: "text",
2568
- content: JSON.stringify(toOxfmtConfig(), null, 2)
2781
+ content: renderJson(toOxfmtConfig())
2569
2782
  };
2570
2783
  }
2571
2784
 
@@ -2625,7 +2838,7 @@ function renderMonorepo(params) {
2625
2838
  }
2626
2839
  files["package.json"] = {
2627
2840
  type: "text",
2628
- content: JSON.stringify(rootPackageJson, null, 2)
2841
+ content: renderJson(rootPackageJson, { inlineArrays: false })
2629
2842
  };
2630
2843
  if (isPnpm) {
2631
2844
  files["pnpm-workspace.yaml"] = {
@@ -2695,7 +2908,7 @@ export default [...base];
2695
2908
  };
2696
2909
  files["biome.json"] = {
2697
2910
  type: "text",
2698
- content: JSON.stringify(biomeConfig, null, 2)
2911
+ content: renderJson(biomeConfig)
2699
2912
  };
2700
2913
  }
2701
2914
  if (formatter === "oxfmt") {
@@ -2825,7 +3038,7 @@ function planBiome(builder, options) {
2825
3038
  if (isStealth) {
2826
3039
  builder.addFile(".config/biome.json", {
2827
3040
  type: "text",
2828
- content: JSON.stringify(biomeConfig, null, 2)
3041
+ content: renderJson(biomeConfig)
2829
3042
  });
2830
3043
  if (options.linter) {
2831
3044
  builder.addScripts(packageJsonScripts.lint.biome(".config"));
@@ -2836,7 +3049,7 @@ function planBiome(builder, options) {
2836
3049
  } else {
2837
3050
  builder.addFile("biome.json", {
2838
3051
  type: "text",
2839
- content: JSON.stringify(biomeConfig, null, 2)
3052
+ content: renderJson(biomeConfig)
2840
3053
  });
2841
3054
  if (options.linter) {
2842
3055
  builder.addScripts(packageJsonScripts.lint.biome());
@@ -3113,13 +3326,13 @@ function planOxfmt(builder, options) {
3113
3326
  if (isStealth) {
3114
3327
  builder.addFile(".config/oxfmt.json", {
3115
3328
  type: "text",
3116
- content: JSON.stringify(toOxfmtConfig(options.config), null, 2)
3329
+ content: renderJson(toOxfmtConfig(options.config))
3117
3330
  });
3118
3331
  builder.addScripts(packageJsonScripts.format.oxfmt(".config/oxfmt.json"));
3119
3332
  } else {
3120
3333
  builder.addFile("oxfmt.json", {
3121
3334
  type: "text",
3122
- content: JSON.stringify(toOxfmtConfig(options.config), null, 2)
3335
+ content: renderJson(toOxfmtConfig(options.config))
3123
3336
  });
3124
3337
  builder.addScripts(packageJsonScripts.format.oxfmt("oxfmt.json"));
3125
3338
  }
@@ -3164,13 +3377,13 @@ function planOxlint(builder, options) {
3164
3377
  if (isStealth) {
3165
3378
  builder.addFile(".config/oxlint.json", {
3166
3379
  type: "text",
3167
- content: JSON.stringify(oxlintConfig, null, 2)
3380
+ content: renderJson(oxlintConfig)
3168
3381
  });
3169
3382
  builder.addScripts(packageJsonScripts.lint.oxlint(".config/oxlint.json"));
3170
3383
  } else {
3171
3384
  builder.addFile("oxlint.json", {
3172
3385
  type: "text",
3173
- content: JSON.stringify(oxlintConfig, null, 2)
3386
+ content: renderJson(oxlintConfig)
3174
3387
  });
3175
3388
  builder.addScripts(packageJsonScripts.lint.oxlint());
3176
3389
  }
@@ -3209,7 +3422,7 @@ function planPrettier(builder, options) {
3209
3422
  if (isStealth) {
3210
3423
  builder.addFile(".config/prettier.json", {
3211
3424
  type: "text",
3212
- content: JSON.stringify(toPrettierConfig(options.config), null, 2)
3425
+ content: renderJson(toPrettierConfig(options.config))
3213
3426
  });
3214
3427
  builder.addFile(".config/prettierignore", {
3215
3428
  type: "text",
@@ -3221,7 +3434,7 @@ function planPrettier(builder, options) {
3221
3434
  } else {
3222
3435
  builder.addFile(".prettierrc", {
3223
3436
  type: "text",
3224
- content: JSON.stringify(toPrettierConfig(options.config), null, 2)
3437
+ content: renderJson(toPrettierConfig(options.config))
3225
3438
  });
3226
3439
  builder.addFile(".prettierignore", {
3227
3440
  type: "text",
@@ -4212,6 +4425,7 @@ exports.DEFAULT_MINIMUM_RELEASE_AGE_MINUTES = DEFAULT_MINIMUM_RELEASE_AGE_MINUTE
4212
4425
  exports.assignResolvedPackageVersion = assignResolvedPackageVersion;
4213
4426
  exports.clearConfig = clearConfig;
4214
4427
  exports.compareNumericSemver = compareNumericSemver;
4428
+ exports.detectGitignoreVariant = detectGitignoreVariant;
4215
4429
  exports.detectTooling = detectTooling;
4216
4430
  exports.formatEngine = formatEngine;
4217
4431
  exports.formatNodeTypesVersion = formatNodeTypesVersion;
@@ -4240,9 +4454,12 @@ exports.getPackageManagerSpec = getPackageManagerSpec;
4240
4454
  exports.getResolvedPackageVersion = getResolvedPackageVersion;
4241
4455
  exports.getSemverMajor = getSemverMajor;
4242
4456
  exports.getSemverMajorString = getSemverMajorString;
4457
+ exports.isManagedAiFilePath = isManagedAiFilePath;
4243
4458
  exports.isPackageManagerName = isPackageManagerName;
4244
4459
  exports.materializeJobs = materializeJobs;
4245
4460
  exports.merge = merge;
4461
+ exports.mergeAiFileContent = mergeAiFileContent;
4462
+ exports.mergeGitignoreContent = mergeGitignoreContent;
4246
4463
  exports.mergePackageJsonScripts = mergePackageJsonScripts;
4247
4464
  exports.packageJsonScripts = packageJsonScripts;
4248
4465
  exports.parseEngine = parseEngine;
@@ -4255,6 +4472,7 @@ exports.renderAiFiles = renderAiFiles;
4255
4472
  exports.renderEditorConfig = renderEditorConfig;
4256
4473
  exports.renderEslintConfigPackage = renderEslintConfigPackage;
4257
4474
  exports.renderGitignore = renderGitignore;
4475
+ exports.renderJson = renderJson;
4258
4476
  exports.renderOxfmtConfigPackage = renderOxfmtConfigPackage;
4259
4477
  exports.renderOxlintConfig = renderOxlintConfig;
4260
4478
  exports.renderOxlintConfigPackage = renderOxlintConfigPackage;