openapi-typesafe-migrate 1.0.0-rc.6 → 1.0.0-rc.8

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/main.mjs CHANGED
@@ -609,15 +609,17 @@ function columnAt(text, offset) {
609
609
  return offset - text.lastIndexOf("\n", offset - 1);
610
610
  }
611
611
 
612
+ class MigrationBugError extends Error {
613
+ name = "MigrationBugError";
614
+ }
615
+
612
616
  function applyTextEdits(text, edits) {
613
617
  const sorted = [...edits].sort((left, right) => left.start - right.start);
614
618
  let edited = "";
615
619
  let copiedUpTo = 0;
616
620
  for (const edit of sorted) {
617
621
  if (edit.start < copiedUpTo) {
618
- throw new Error(
619
- `Edits overlap at offset ${edit.start}; the file was not changed (a migration bug).`
620
- );
622
+ throw new MigrationBugError(`edits overlap at offset ${edit.start}`);
621
623
  }
622
624
  edited += `${text.slice(copiedUpTo, edit.start)}${edit.text}`;
623
625
  copiedUpTo = edit.end;
@@ -625,6 +627,80 @@ function applyTextEdits(text, edits) {
625
627
  return edited + text.slice(copiedUpTo);
626
628
  }
627
629
 
630
+ const TYPE_TEXT = "string";
631
+ const TYPESCRIPT_MODULES$1 = /* @__PURE__ */ new Set(["typescript", "@typescript/typescript6"]);
632
+ function isTypescriptReference(node, bindings) {
633
+ const name = node.typeName;
634
+ if (ts.isIdentifier(name)) {
635
+ return bindings.bindingOf(name)?.kind === "typescript-import";
636
+ }
637
+ return ts.isIdentifier(name.left) && bindings.bindingOf(name.left)?.kind === "typescript-module";
638
+ }
639
+ function isTypescriptImportType(node) {
640
+ const { argument } = node;
641
+ return !node.isTypeOf && ts.isLiteralTypeNode(argument) && ts.isStringLiteral(argument.literal) && TYPESCRIPT_MODULES$1.has(argument.literal.text);
642
+ }
643
+ function joined$1(found) {
644
+ const edits = [];
645
+ for (const part of found) {
646
+ if (part === void 0) {
647
+ return void 0;
648
+ }
649
+ edits.push(...part);
650
+ }
651
+ return edits;
652
+ }
653
+ const memberEdits = (node, bindings) => joined$1(
654
+ node.members.map(
655
+ (member) => ts.isPropertySignature(member) ? typeEdits(member.type, bindings) : void 0
656
+ )
657
+ );
658
+ const replaced = (node) => [
659
+ { start: node.getStart(), end: node.getEnd(), text: TYPE_TEXT }
660
+ ];
661
+ const isKeyword = (node) => node.kind >= ts.SyntaxKind.FirstKeyword && node.kind <= ts.SyntaxKind.LastKeyword;
662
+ function typeEdits(node, bindings) {
663
+ if (node === void 0) {
664
+ return void 0;
665
+ }
666
+ if (ts.isLiteralTypeNode(node) || isKeyword(node)) {
667
+ return [];
668
+ }
669
+ if (ts.isUnionTypeNode(node) || ts.isIntersectionTypeNode(node)) {
670
+ return joined$1(node.types.map((part) => typeEdits(part, bindings)));
671
+ }
672
+ if (ts.isParenthesizedTypeNode(node)) {
673
+ return typeEdits(node.type, bindings);
674
+ }
675
+ if (ts.isTypeLiteralNode(node)) {
676
+ return memberEdits(node, bindings);
677
+ }
678
+ const isNodeType = ts.isTypeReferenceNode(node) ? isTypescriptReference(node, bindings) : ts.isImportTypeNode(node) && isTypescriptImportType(node);
679
+ return isNodeType ? replaced(node) : void 0;
680
+ }
681
+ function functionReturnType(node) {
682
+ return ts.isFunctionTypeNode(node) || ts.isJSDocFunctionType(node) ? node.type : void 0;
683
+ }
684
+ function declaredType(node) {
685
+ const annotation = ts.isVariableDeclaration(node) ? node.type : void 0;
686
+ const isJavaScript = (node.flags & ts.NodeFlags.JavaScriptFile) !== 0;
687
+ return annotation ?? (isJavaScript ? ts.getJSDocType(node) : void 0);
688
+ }
689
+ function annotationEdits(type, bindings) {
690
+ const returned = functionReturnType(type);
691
+ if (returned !== void 0) {
692
+ return typeEdits(returned, bindings);
693
+ }
694
+ return ts.isIndexedAccessTypeNode(type) || isKeyword(type) ? [] : void 0;
695
+ }
696
+ function callbackTypeEdits(annotations, callback, bindings) {
697
+ const returnsTag = ts.getJSDocReturnTag(callback)?.typeExpression?.type;
698
+ return joined$1([
699
+ ...annotations.map((type) => annotationEdits(type, bindings)),
700
+ returnsTag === void 0 ? [] : typeEdits(returnsTag, bindings)
701
+ ]);
702
+ }
703
+
628
704
  const hasExportModifier = (declaration) => (ts.getCombinedModifierFlags(declaration) & ts.ModifierFlags.Export) !== 0;
629
705
  const GENERATOR_MODULES = /* @__PURE__ */ new Set([
630
706
  "openapi-typesafe",
@@ -737,7 +813,9 @@ function indexUses(file, bindings) {
737
813
  if (declaration !== void 0 && !isDeclaredName(node, declaration)) {
738
814
  uses.set(declaration, [...uses.get(declaration) ?? [], node]);
739
815
  }
740
- ts.forEachChild(node, visit);
816
+ for (const child of node.getChildren(file)) {
817
+ visit(child);
818
+ }
741
819
  };
742
820
  visit(file);
743
821
  return uses;
@@ -812,18 +890,23 @@ function constantOptions(name, bindings, outerTypes) {
812
890
  return { kind: "shared", options: found.value };
813
891
  }
814
892
  const declared = declaration.type;
815
- const annotations = [
816
- ...outerTypes,
893
+ const ownTypes = [
817
894
  ...found.types,
818
895
  ...declared === void 0 ? [] : [declared],
819
896
  ...jsDocTypes(declaration)
820
897
  ];
821
- return { kind: "own", options: found.value, annotations };
898
+ const annotations = [...outerTypes, ...ownTypes];
899
+ return {
900
+ kind: "own",
901
+ options: found.value,
902
+ annotations,
903
+ isContextuallyTyped: ownTypes.length > 0
904
+ };
822
905
  }
823
906
  function optionsOf(argument, bindings) {
824
907
  const { value, types } = unwrapped(argument);
825
908
  if (ts.isObjectLiteralExpression(value)) {
826
- return { kind: "own", options: value, annotations: types };
909
+ return { kind: "own", options: value, annotations: types, isContextuallyTyped: true };
827
910
  }
828
911
  return ts.isIdentifier(value) ? constantOptions(value, bindings, types) : { kind: "unknown", expression: argument };
829
912
  }
@@ -841,6 +924,22 @@ function generatorCalls(file, bindings) {
841
924
  return calls;
842
925
  }
843
926
 
927
+ const TRANSFORM = "transform";
928
+ const LEGACY_ONLY_CALLBACKS = ["postTransform", "transformProperty"];
929
+ const IR_CALLBACKS_OPTION = "irCallbacks";
930
+ function propertyNamed(options, name) {
931
+ return options.properties.find((property) => {
932
+ const key = property.name;
933
+ return key !== void 0 && (ts.isIdentifier(key) || ts.isStringLiteral(key)) && key.text === name;
934
+ });
935
+ }
936
+ function legacyCallbackSteps(options, text) {
937
+ return LEGACY_ONLY_CALLBACKS.flatMap((callback) => {
938
+ const property = propertyNamed(options, callback);
939
+ return property === void 0 ? [] : [{ kind: "legacy-callback", line: lineAt(text, property.getStart()), callback }];
940
+ });
941
+ }
942
+
844
943
  const TS_NODE_OPTIONS = "OpenAPITSOptions";
845
944
  const IR_OPTIONS = "OpenAPITSIrOptions";
846
945
  const TYPE_MODIFIER = "type ";
@@ -1073,6 +1172,41 @@ function scriptRegions(text, path) {
1073
1172
  return componentRegions(text, extension === ASTRO_EXTENSION);
1074
1173
  }
1075
1174
 
1175
+ function spreadObject(expression, bindings) {
1176
+ const { value } = unwrapped(expression);
1177
+ if (ts.isObjectLiteralExpression(value)) {
1178
+ return value;
1179
+ }
1180
+ const binding = ts.isIdentifier(value) ? bindings.bindingOf(value) : void 0;
1181
+ const initializer = binding?.kind === "constant" ? unwrapped(binding.initializer).value : void 0;
1182
+ return initializer !== void 0 && ts.isObjectLiteralExpression(initializer) ? initializer : void 0;
1183
+ }
1184
+ function spreadObjectSteps(source, text, bindings, seen) {
1185
+ if (seen.has(source) || propertyNamed(source, IR_CALLBACKS_OPTION) !== void 0) {
1186
+ return [];
1187
+ }
1188
+ seen.add(source);
1189
+ const transform = propertyNamed(source, TRANSFORM);
1190
+ const kept = transform === void 0 ? [] : [
1191
+ {
1192
+ kind: "transform-not-rewritten",
1193
+ line: lineAt(text, transform.getStart()),
1194
+ reason: "in-spread"
1195
+ }
1196
+ ];
1197
+ return [
1198
+ ...legacyCallbackSteps(source, text),
1199
+ ...kept,
1200
+ ...spreadSteps(source, text, bindings, seen)
1201
+ ];
1202
+ }
1203
+ function spreadSteps(options, text, bindings, seen = /* @__PURE__ */ new Set()) {
1204
+ return options.properties.filter(ts.isSpreadAssignment).flatMap((spread) => {
1205
+ const source = spreadObject(spread.expression, bindings);
1206
+ return source === void 0 ? [{ kind: "options-spread-not-inspected", line: lineAt(text, spread.getStart()) }] : spreadObjectSteps(source, text, bindings, seen);
1207
+ });
1208
+ }
1209
+
1076
1210
  function namedFunction(name, names) {
1077
1211
  const binding = names.bindings.bindingOf(name);
1078
1212
  if (binding?.kind === "function") {
@@ -1101,7 +1235,7 @@ function callbackFunction(property, names) {
1101
1235
  function callbackAnnotations(property, names) {
1102
1236
  const name = namedValue(property);
1103
1237
  const declaration = name === void 0 ? void 0 : names.bindings.declarationOf(name);
1104
- const type = declaration !== void 0 && ts.isVariableDeclaration(declaration) ? declaration.type : void 0;
1238
+ const type = declaredType(declaration ?? property);
1105
1239
  return type === void 0 ? [] : [type];
1106
1240
  }
1107
1241
  function isSharedFunction(property, names) {
@@ -1431,21 +1565,15 @@ function removalEdits(removed, text) {
1431
1565
  return [...statements].flatMap((statement) => statementEdits(statement, isRemoved, text));
1432
1566
  }
1433
1567
 
1434
- const TRANSFORM = "transform";
1435
- const LEGACY_ONLY_CALLBACKS = ["postTransform", "transformProperty"];
1436
- const IR_CALLBACKS_OPTION = "irCallbacks";
1568
+ const IR_CALLBACKS_TRUE = "true";
1569
+ const IR_CALLBACKS_TRUE_CONST = "true as const";
1570
+ const IR_CALLBACKS_TRUE_CONST_JS = "/** @type {const} */ (true)";
1437
1571
  const noFindings = () => ({
1438
1572
  edits: [],
1439
1573
  changes: [],
1440
1574
  manualSteps: [],
1441
1575
  typeReferences: []
1442
1576
  });
1443
- function propertyNamed(options, name) {
1444
- return options.properties.find((property) => {
1445
- const key = property.name;
1446
- return key !== void 0 && (ts.isIdentifier(key) || ts.isStringLiteral(key)) && key.text === name;
1447
- });
1448
- }
1449
1577
  function returnTypeEdits(callback, text) {
1450
1578
  const type = callback.type;
1451
1579
  if (type === void 0) {
@@ -1462,7 +1590,15 @@ function transformEdits(property, names, text) {
1462
1590
  if (isSharedFunction(property, names)) {
1463
1591
  return "shared-function";
1464
1592
  }
1465
- const edits = returnTypeEdits(callback, text);
1593
+ const declared = callbackTypeEdits(
1594
+ callbackAnnotations(property, names),
1595
+ callback,
1596
+ names.bindings
1597
+ );
1598
+ if (declared === void 0) {
1599
+ return "declared-type";
1600
+ }
1601
+ const edits = [...returnTypeEdits(callback, text), ...declared];
1466
1602
  for (const returned of returnedExpressions(callback.body)) {
1467
1603
  const returnEditList = returnEdits(returned, names);
1468
1604
  if (returnEditList === void 0) {
@@ -1472,10 +1608,17 @@ function transformEdits(property, names, text) {
1472
1608
  }
1473
1609
  return edits;
1474
1610
  }
1475
- function irCallbacksOption(text, transformStart) {
1611
+ function irCallbacksValue(options) {
1612
+ if (options.isContextuallyTyped) {
1613
+ return IR_CALLBACKS_TRUE;
1614
+ }
1615
+ const isJavaScript = (options.options.flags & ts.NodeFlags.JavaScriptFile) !== 0;
1616
+ return isJavaScript ? IR_CALLBACKS_TRUE_CONST_JS : IR_CALLBACKS_TRUE_CONST;
1617
+ }
1618
+ function irCallbacksOption(text, transformStart, value) {
1476
1619
  const lineStart = text.lastIndexOf("\n", transformStart - 1) + 1;
1477
1620
  const before = text.slice(lineStart, transformStart);
1478
- const option = `${IR_CALLBACKS_OPTION}: true,`;
1621
+ const option = `${IR_CALLBACKS_OPTION}: ${value},`;
1479
1622
  return /^\s*$/.test(before) ? `${option}
1480
1623
  ${before}` : `${option} `;
1481
1624
  }
@@ -1486,12 +1629,6 @@ function keepReason(options) {
1486
1629
  }
1487
1630
  return options.properties.some(ts.isSpreadAssignment) ? "spread-options" : void 0;
1488
1631
  }
1489
- function legacyCallbackSteps(options, text) {
1490
- return LEGACY_ONLY_CALLBACKS.flatMap((callback) => {
1491
- const property = propertyNamed(options, callback);
1492
- return property === void 0 ? [] : [{ kind: "legacy-callback", line: lineAt(text, property.getStart()), callback }];
1493
- });
1494
- }
1495
1632
  function transformRewrite(transform, options, names, text) {
1496
1633
  const edits = keepReason(options.options) ?? transformEdits(transform, names, text);
1497
1634
  if (typeof edits === "string") {
@@ -1507,6 +1644,7 @@ function migrateOptions(options, text, names) {
1507
1644
  }
1508
1645
  const findings = noFindings();
1509
1646
  findings.manualSteps.push(...legacyCallbackSteps(options.options, text));
1647
+ findings.manualSteps.push(...spreadSteps(options.options, text, names.bindings));
1510
1648
  const transform = propertyNamed(options.options, TRANSFORM);
1511
1649
  if (transform === void 0) {
1512
1650
  return findings;
@@ -1518,17 +1656,20 @@ function migrateOptions(options, text, names) {
1518
1656
  findings.manualSteps.push({ kind: "transform-not-rewritten", line, reason: result });
1519
1657
  return findings;
1520
1658
  }
1521
- findings.edits.push({ start, end: start, text: irCallbacksOption(text, start) }, ...result.edits);
1659
+ const at = transform.getStart(void 0, true);
1660
+ const option = irCallbacksOption(text, at, irCallbacksValue(options));
1661
+ findings.edits.push({ start: at, end: at, text: option }, ...result.edits);
1522
1662
  findings.typeReferences.push(...result.typeReferences);
1523
1663
  findings.changes.push({ kind: "transform-rewritten", line });
1524
1664
  return findings;
1525
1665
  }
1526
- function sharedOptionsFindings(options, text) {
1666
+ function sharedOptionsFindings(options, text, bindings) {
1527
1667
  const findings = noFindings();
1528
1668
  if (propertyNamed(options, IR_CALLBACKS_OPTION) !== void 0) {
1529
1669
  return findings;
1530
1670
  }
1531
1671
  findings.manualSteps.push(...legacyCallbackSteps(options, text));
1672
+ findings.manualSteps.push(...spreadSteps(options, text, bindings));
1532
1673
  const transform = propertyNamed(options, TRANSFORM);
1533
1674
  if (transform !== void 0) {
1534
1675
  const line = lineAt(text, transform.getStart());
@@ -1544,7 +1685,7 @@ function callFindings(found, text, names) {
1544
1685
  case "own":
1545
1686
  return migrateOptions(options, text, names);
1546
1687
  case "shared":
1547
- return sharedOptionsFindings(options.options, text);
1688
+ return sharedOptionsFindings(options.options, text, names.bindings);
1548
1689
  case "unknown": {
1549
1690
  const line = lineAt(text, options.expression.getStart());
1550
1691
  return { ...noFindings(), manualSteps: [{ kind: "options-not-inspected", line }] };
@@ -1667,7 +1808,7 @@ function isModuleSpecifier(literal) {
1667
1808
  }
1668
1809
  return ts.isExternalModuleReference(parent);
1669
1810
  }
1670
- function stringValue$1(node, file) {
1811
+ function stringValue(node, file) {
1671
1812
  return ts.isTemplateExpression(node) ? file.text.slice(node.getStart(file) + 1, node.getEnd() - 1) : node.text;
1672
1813
  }
1673
1814
  function collectStrings(root, file, offset, found) {
@@ -1678,7 +1819,7 @@ function collectStrings(root, file, offset, found) {
1678
1819
  found.set(start, {
1679
1820
  start,
1680
1821
  end,
1681
- value: stringValue$1(node, file),
1822
+ value: stringValue(node, file),
1682
1823
  isModuleSpecifier: isModuleSpecifier(node)
1683
1824
  });
1684
1825
  }
@@ -1887,6 +2028,153 @@ function createYamlTextReader() {
1887
2028
  return { read };
1888
2029
  }
1889
2030
 
2031
+ const PACKAGE_RENAMES = /* @__PURE__ */ new Map([
2032
+ ["openapi-typescript", "openapi-typesafe"],
2033
+ ["openapi-fetch", "openapi-typesafe-fetch"],
2034
+ ["openapi-react-query", "openapi-typesafe-react-query"],
2035
+ ["openapi-typescript-helpers", "openapi-typesafe-helpers"]
2036
+ ]);
2037
+ function namesOriginalPackage(text) {
2038
+ return [...PACKAGE_RENAMES.keys()].some((name) => text.includes(name));
2039
+ }
2040
+ const GENERATOR_COMMAND = "openapi-typescript";
2041
+ const MIGRATED_GENERATOR_COMMAND = "openapi-typesafe";
2042
+ const FLAG_RENAMES = /* @__PURE__ */ new Map([
2043
+ ["--immutable-types", "--immutable"],
2044
+ ["-it", "--immutable"],
2045
+ ["--support-array-length", "--array-length"],
2046
+ ["--redoc", "--redocly"],
2047
+ ["-ap", "--additional-properties"]
2048
+ ]);
2049
+ const DEPENDENCY_FIELDS = [
2050
+ "dependencies",
2051
+ "devDependencies",
2052
+ "peerDependencies",
2053
+ "optionalDependencies"
2054
+ ];
2055
+ const NAME_KEYED_FIELDS = ["peerDependenciesMeta", "dependenciesMeta"];
2056
+ const NAME_LIST_FIELDS = ["bundleDependencies", "bundledDependencies"];
2057
+ const CATALOG_FIELD = "catalog";
2058
+ const NAMED_CATALOGS_FIELD = "catalogs";
2059
+ const WORKSPACES_FIELD = "workspaces";
2060
+ const OVERRIDE_FIELDS = ["overrides", "resolutions"];
2061
+ const PNPM_SETTINGS_FIELD = "pnpm";
2062
+ const OLD_PEER_DEPENDENTS = /* @__PURE__ */ new Map([
2063
+ ["swr-openapi", { peer: "openapi-fetch", range: ">=0.15" }]
2064
+ ]);
2065
+ const PATH_SEPARATOR$9 = "/";
2066
+ const QUERY_START = "?";
2067
+ function renamedSpecifier(value) {
2068
+ const ends = [value.indexOf(PATH_SEPARATOR$9), value.indexOf(QUERY_START)].filter(
2069
+ (at) => at !== -1
2070
+ );
2071
+ const name = value.slice(0, ends.length === 0 ? value.length : Math.min(...ends));
2072
+ const renamed = PACKAGE_RENAMES.get(name);
2073
+ if (renamed === void 0) {
2074
+ return void 0;
2075
+ }
2076
+ const rest = value.slice(name.length);
2077
+ const subpath = rest.split(QUERY_START, 1)[0] ?? rest;
2078
+ return { to: `${renamed}${rest}`, isDeep: subpath !== "" && subpath !== PATH_SEPARATOR$9 };
2079
+ }
2080
+ const NODE_MODULES_FOLDER = "node_modules/";
2081
+ function oldPackageInNodeModules(path) {
2082
+ return [...PACKAGE_RENAMES.keys()].find(
2083
+ (name) => path.includes(`${NODE_MODULES_FOLDER}${name}${PATH_SEPARATOR$9}`) || path.endsWith(`${NODE_MODULES_FOLDER}${name}`)
2084
+ );
2085
+ }
2086
+
2087
+ const isMap = (node) => node?.kind === "map";
2088
+ const isScalar = (node) => node.kind === "scalar";
2089
+ function textEntries(map) {
2090
+ return map.entries.flatMap(({ key, value }) => isScalar(key) ? [{ key, value }] : []);
2091
+ }
2092
+ function valuesAt(map, key) {
2093
+ return textEntries(map).filter((entry) => entry.key.value === key).map((entry) => entry.value);
2094
+ }
2095
+ function mapsAt(map, key) {
2096
+ return valuesAt(map, key).filter(isMap);
2097
+ }
2098
+ function stringsBelow(node, path) {
2099
+ switch (node.kind) {
2100
+ case "scalar":
2101
+ return [{ ...node, path }];
2102
+ case "list":
2103
+ return node.items.flatMap((item, index) => stringsBelow(item, [...path, index]));
2104
+ case "map":
2105
+ return textEntries(node).flatMap(
2106
+ ({ key, value }) => stringsBelow(value, [...path, key.value])
2107
+ );
2108
+ default:
2109
+ return [];
2110
+ }
2111
+ }
2112
+ function documentStrings(documents) {
2113
+ return documents.flatMap((document) => stringsBelow(document, []));
2114
+ }
2115
+ function ownerKey(path) {
2116
+ return path.findLast((part) => typeof part === "string");
2117
+ }
2118
+ function unreadableConfig(text, offset) {
2119
+ if (!namesOriginalPackage(text)) {
2120
+ return unchanged(text);
2121
+ }
2122
+ const position = offset === void 0 ? void 0 : { line: lineAt(text, offset), column: columnAt(text, offset) };
2123
+ return { ...unchanged(text), manualSteps: [{ kind: "config-unreadable", position }] };
2124
+ }
2125
+
2126
+ const SEPARATOR = "/";
2127
+ const CURRENT_FOLDER = ".";
2128
+ const PARENT_FOLDER = "..";
2129
+ const URL_SCHEME = /^[a-z][a-z\d+.-]*:/i;
2130
+ function resolvedPath(fromPath, reference) {
2131
+ const parts = fromPath.split(SEPARATOR).slice(0, -1);
2132
+ for (const part of reference.split(SEPARATOR)) {
2133
+ if (part === PARENT_FOLDER) {
2134
+ if (parts.pop() === void 0) {
2135
+ return void 0;
2136
+ }
2137
+ } else if (part !== CURRENT_FOLDER && part !== "") {
2138
+ parts.push(part);
2139
+ }
2140
+ }
2141
+ return parts.join(SEPARATOR);
2142
+ }
2143
+ function referencedProjectPath(fromPath, reference) {
2144
+ const isOutside = URL_SCHEME.test(reference) || reference.startsWith(SEPARATOR);
2145
+ const path = isOutside ? void 0 : resolvedPath(fromPath, reference);
2146
+ return path === "" ? void 0 : path;
2147
+ }
2148
+
2149
+ const API_DESCRIPTION_KEYS = [
2150
+ "openapi",
2151
+ "swagger",
2152
+ "asyncapi",
2153
+ "overlay",
2154
+ "arazzo"
2155
+ ];
2156
+ const API_DESCRIPTION_KEY_SET = new Set(API_DESCRIPTION_KEYS);
2157
+ const DESCRIPTION_KEY_LINE_RE = new RegExp(
2158
+ `^[ \\t{]*["']?(?:${API_DESCRIPTION_KEYS.join("|")})["']?[ \\t]*:`,
2159
+ "m"
2160
+ );
2161
+ const mayBeApiDescription = (text) => DESCRIPTION_KEY_LINE_RE.test(text);
2162
+ const SPEC_VERSION_RE = /^\d+\.\d+(?:\.\d+)?(?:-[0-9A-Za-z.-]+)?$/;
2163
+ const isApiDescription = (documents) => documents.some(
2164
+ (root) => isMap(root) && textEntries(root).some(
2165
+ ({ key, value }) => API_DESCRIPTION_KEY_SET.has(key.value) && isScalar(value) && SPEC_VERSION_RE.test(value.value)
2166
+ )
2167
+ );
2168
+ const REF_KEY = "$ref";
2169
+ const FRAGMENT_START = "#";
2170
+ function referencedFiles(path, documents) {
2171
+ return documentStrings(documents).filter((string) => string.path.at(-1) === REF_KEY).flatMap(({ value }) => {
2172
+ const file = value.split(FRAGMENT_START)[0] ?? "";
2173
+ const referenced = referencedProjectPath(path, file);
2174
+ return referenced === void 0 ? [] : [referenced];
2175
+ });
2176
+ }
2177
+
1890
2178
  const NONE = /* @__PURE__ */ new Set();
1891
2179
  const PACKAGE_OPTIONS = /* @__PURE__ */ new Set(["-p", "--package"]);
1892
2180
  const PACKAGE_MANAGER_OPTIONS = /* @__PURE__ */ new Set([
@@ -2030,8 +2318,8 @@ const COMMAND_SEPARATORS = /* @__PURE__ */ new Set([
2030
2318
  "\n"
2031
2319
  ]);
2032
2320
  const WORD_BREAKS = /* @__PURE__ */ new Set([" ", " ", "\r", "<", ">"]);
2033
- const BACKSLASH$1 = "\\";
2034
- const DOUBLE_QUOTE_ESCAPES = /* @__PURE__ */ new Set(["$", "`", '"', BACKSLASH$1, "\n"]);
2321
+ const BACKSLASH$3 = "\\";
2322
+ const DOUBLE_QUOTE_ESCAPES = /* @__PURE__ */ new Set(["$", "`", '"', BACKSLASH$3, "\n"]);
2035
2323
  function singleQuoted(text, start) {
2036
2324
  const close = text.indexOf("'", start + 1);
2037
2325
  const end = close === -1 ? text.length : close + 1;
@@ -2042,13 +2330,13 @@ function doubleQuoted(text, start) {
2042
2330
  let index = start + 1;
2043
2331
  while (index < text.length && text[index] !== '"') {
2044
2332
  const next = text[index + 1] ?? "";
2045
- const isEscape = text[index] === BACKSLASH$1 && DOUBLE_QUOTE_ESCAPES.has(next);
2333
+ const isEscape = text[index] === BACKSLASH$3 && DOUBLE_QUOTE_ESCAPES.has(next);
2046
2334
  value += isEscape ? next === "\n" ? "" : next : text[index];
2047
2335
  index += isEscape ? 2 : 1;
2048
2336
  }
2049
2337
  return { end: Math.min(index + 1, text.length), value };
2050
2338
  }
2051
- const isLineContinuation = (text, index) => text[index] === BACKSLASH$1 && (text[index + 1] === "\n" || text.startsWith("\r\n", index + 1));
2339
+ const isLineContinuation = (text, index) => text[index] === BACKSLASH$3 && (text[index + 1] === "\n" || text.startsWith("\r\n", index + 1));
2052
2340
  const ARITHMETIC_OPEN = "$((";
2053
2341
  const NESTING_OPEN = "(";
2054
2342
  const NESTING_CLOSE = ")";
@@ -2063,11 +2351,48 @@ function arithmeticExpansion(text, start) {
2063
2351
  }
2064
2352
  return void 0;
2065
2353
  }
2354
+ const PARAMETER_OPEN = "${";
2355
+ const PARAMETER_CLOSE = "}";
2356
+ function parameterExpansionEnd(text, start) {
2357
+ let depth = 1;
2358
+ for (let index = start + PARAMETER_OPEN.length; index < text.length; ) {
2359
+ if (text.startsWith(PARAMETER_OPEN, index)) {
2360
+ depth += 1;
2361
+ index += PARAMETER_OPEN.length;
2362
+ } else if (text[index] === PARAMETER_CLOSE) {
2363
+ depth -= 1;
2364
+ index += 1;
2365
+ if (depth === 0) {
2366
+ return index;
2367
+ }
2368
+ } else {
2369
+ index = quotedOrEscapedEnd(text, index) ?? index + 1;
2370
+ }
2371
+ }
2372
+ return void 0;
2373
+ }
2374
+ function quotedOrEscapedEnd(text, index) {
2375
+ const char = text[index];
2376
+ if (char === "'") {
2377
+ return singleQuoted(text, index).end;
2378
+ }
2379
+ if (char === '"') {
2380
+ return doubleQuoted(text, index).end;
2381
+ }
2382
+ return char === BACKSLASH$3 ? index + 2 : void 0;
2383
+ }
2384
+ function expansionPart(text, index) {
2385
+ if (text.startsWith(ARITHMETIC_OPEN, index)) {
2386
+ return arithmeticExpansion(text, index);
2387
+ }
2388
+ const end = text.startsWith(PARAMETER_OPEN, index) ? parameterExpansionEnd(text, index) : void 0;
2389
+ return end === void 0 ? void 0 : { end, value: text.slice(index, end) };
2390
+ }
2066
2391
  function wordPart(text, index) {
2067
2392
  const char = text[index];
2068
- const arithmetic = text.startsWith(ARITHMETIC_OPEN, index) ? arithmeticExpansion(text, index) : void 0;
2069
- if (arithmetic !== void 0) {
2070
- return arithmetic;
2393
+ const expansion = expansionPart(text, index);
2394
+ if (expansion !== void 0) {
2395
+ return expansion;
2071
2396
  }
2072
2397
  if (char === "'") {
2073
2398
  return singleQuoted(text, index);
@@ -2075,11 +2400,23 @@ function wordPart(text, index) {
2075
2400
  if (char === '"') {
2076
2401
  return doubleQuoted(text, index);
2077
2402
  }
2078
- if (char === BACKSLASH$1 && index + 1 < text.length) {
2403
+ if (char === BACKSLASH$3 && index + 1 < text.length) {
2079
2404
  return { end: index + 2, value: text[index + 1] };
2080
2405
  }
2081
2406
  return { end: index + 1, value: char };
2082
2407
  }
2408
+ const ARRAY_HEAD_RE = /^[A-Za-z_][A-Za-z0-9_]*\+?=$/;
2409
+ function arrayListEnd(text, index) {
2410
+ let depth = 0;
2411
+ for (let at = index; at < text.length; ) {
2412
+ depth += text[at] === NESTING_OPEN ? 1 : text[at] === NESTING_CLOSE ? -1 : 0;
2413
+ if (depth === 0) {
2414
+ return at + 1;
2415
+ }
2416
+ at = quotedOrEscapedEnd(text, at) ?? at + 1;
2417
+ }
2418
+ return void 0;
2419
+ }
2083
2420
  const endsWord = (text, index) => {
2084
2421
  const char = text[index];
2085
2422
  return WORD_BREAKS.has(char) || COMMAND_SEPARATORS.has(char) || isLineContinuation(text, index);
@@ -2087,8 +2424,13 @@ const endsWord = (text, index) => {
2087
2424
  function readWord(text, start) {
2088
2425
  let value = "";
2089
2426
  let index = start;
2090
- while (index < text.length && !endsWord(text, index)) {
2091
- const part = wordPart(text, index);
2427
+ while (index < text.length) {
2428
+ const isArrayList = text[index] === NESTING_OPEN && ARRAY_HEAD_RE.test(text.slice(start, index));
2429
+ const list = isArrayList ? arrayListEnd(text, index) : void 0;
2430
+ if (list === void 0 && endsWord(text, index)) {
2431
+ break;
2432
+ }
2433
+ const part = list === void 0 ? wordPart(text, index) : { end: list, value: text.slice(index, list) };
2092
2434
  value += part.value;
2093
2435
  index = part.end;
2094
2436
  }
@@ -2119,11 +2461,17 @@ function shellAssignments(command) {
2119
2461
  return name === void 0 ? [] : [{ name, value: word.value.slice(name.length + 1), word }];
2120
2462
  });
2121
2463
  }
2464
+ const ARRAY_OPEN = "(";
2465
+ const ARRAY_CLOSE = ")";
2466
+ const isArrayAssignment = (assignment) => assignment.word.raw.startsWith(ARRAY_OPEN, assignment.name.length + 1);
2122
2467
  function assignmentValueText(assignment) {
2123
2468
  const at = assignment.name.length + 1;
2124
2469
  const raw = assignment.word.raw.slice(at);
2125
2470
  const start = assignment.word.start + at;
2126
2471
  const quote = raw[0] ?? "";
2472
+ if (isArrayAssignment(assignment)) {
2473
+ return raw.endsWith(ARRAY_CLOSE) ? { text: raw.slice(1, -1), start: start + 1 } : void 0;
2474
+ }
2127
2475
  if (QUOTES$3.has(quote)) {
2128
2476
  const isOneString = raw.length > 1 && raw.endsWith(quote) && wordPart(raw, 0).end === raw.length;
2129
2477
  return isOneString ? { text: raw.slice(1, -1), start: start + 1 } : void 0;
@@ -2140,9 +2488,11 @@ function skipAssignments(words, start) {
2140
2488
  }
2141
2489
  return index;
2142
2490
  }
2491
+ const WINDOWS_PROGRAM_SUFFIX_RE = /\.(?:cmd|exe|ps1)$/i;
2492
+ const entryWord = (word) => word?.value.replace(WINDOWS_PROGRAM_SUFFIX_RE, "");
2143
2493
  function matchingPrefix(entries, words, index) {
2144
2494
  return entries.find(
2145
- (entry) => entry.words.every((name, offset) => words[index + offset]?.value === name)
2495
+ (entry) => entry.words.every((name, offset) => entryWord(words[index + offset]) === name)
2146
2496
  );
2147
2497
  }
2148
2498
  function inlineOptionPackage(word, launcher) {
@@ -2192,62 +2542,6 @@ function commandRoles(words) {
2192
2542
  return { program: void 0, packages };
2193
2543
  }
2194
2544
 
2195
- const PACKAGE_RENAMES = /* @__PURE__ */ new Map([
2196
- ["openapi-typescript", "openapi-typesafe"],
2197
- ["openapi-fetch", "openapi-typesafe-fetch"],
2198
- ["openapi-react-query", "openapi-typesafe-react-query"],
2199
- ["openapi-typescript-helpers", "openapi-typesafe-helpers"]
2200
- ]);
2201
- function namesOriginalPackage(text) {
2202
- return [...PACKAGE_RENAMES.keys()].some((name) => text.includes(name));
2203
- }
2204
- const GENERATOR_COMMAND = "openapi-typescript";
2205
- const MIGRATED_GENERATOR_COMMAND = "openapi-typesafe";
2206
- const FLAG_RENAMES = /* @__PURE__ */ new Map([
2207
- ["--immutable-types", "--immutable"],
2208
- ["-it", "--immutable"],
2209
- ["--support-array-length", "--array-length"],
2210
- ["--redoc", "--redocly"],
2211
- ["-ap", "--additional-properties"]
2212
- ]);
2213
- const DEPENDENCY_FIELDS = [
2214
- "dependencies",
2215
- "devDependencies",
2216
- "peerDependencies",
2217
- "optionalDependencies"
2218
- ];
2219
- const NAME_KEYED_FIELDS = ["peerDependenciesMeta", "dependenciesMeta"];
2220
- const NAME_LIST_FIELDS = ["bundleDependencies", "bundledDependencies"];
2221
- const CATALOG_FIELD = "catalog";
2222
- const NAMED_CATALOGS_FIELD = "catalogs";
2223
- const WORKSPACES_FIELD = "workspaces";
2224
- const OVERRIDE_FIELDS = ["overrides", "resolutions"];
2225
- const PNPM_SETTINGS_FIELD = "pnpm";
2226
- const OLD_PEER_DEPENDENTS = /* @__PURE__ */ new Map([
2227
- ["swr-openapi", { peer: "openapi-fetch", range: ">=0.15" }]
2228
- ]);
2229
- const PATH_SEPARATOR$9 = "/";
2230
- const QUERY_START = "?";
2231
- function renamedSpecifier(value) {
2232
- const ends = [value.indexOf(PATH_SEPARATOR$9), value.indexOf(QUERY_START)].filter(
2233
- (at) => at !== -1
2234
- );
2235
- const name = value.slice(0, ends.length === 0 ? value.length : Math.min(...ends));
2236
- const renamed = PACKAGE_RENAMES.get(name);
2237
- if (renamed === void 0) {
2238
- return void 0;
2239
- }
2240
- const rest = value.slice(name.length);
2241
- const subpath = rest.split(QUERY_START, 1)[0] ?? rest;
2242
- return { to: `${renamed}${rest}`, isDeep: subpath !== "" && subpath !== PATH_SEPARATOR$9 };
2243
- }
2244
- const NODE_MODULES_FOLDER = "node_modules/";
2245
- function oldPackageInNodeModules(path) {
2246
- return [...PACKAGE_RENAMES.keys()].find(
2247
- (name) => path.includes(`${NODE_MODULES_FOLDER}${name}${PATH_SEPARATOR$9}`)
2248
- );
2249
- }
2250
-
2251
2545
  function packageReference(value) {
2252
2546
  const bare = value.startsWith(NPM_SPECIFIER_PREFIX) ? value.slice(NPM_SPECIFIER_PREFIX.length) : value;
2253
2547
  const at = bare.indexOf("@", 1);
@@ -2282,10 +2576,104 @@ function runsGenerator(command) {
2282
2576
  return word !== void 0 && packageReference(word.value).name === GENERATOR_COMMAND;
2283
2577
  }
2284
2578
 
2579
+ const CODE_OPTIONS = /* @__PURE__ */ new Map([
2580
+ ["python", /* @__PURE__ */ new Set(["-c"])],
2581
+ ["node", /* @__PURE__ */ new Set(["-e", "--eval", "-p", "--print"])],
2582
+ ["bun", /* @__PURE__ */ new Set(["-e", "--eval", "-p", "--print"])],
2583
+ ["ruby", /* @__PURE__ */ new Set(["-e"])],
2584
+ ["perl", /* @__PURE__ */ new Set(["-e", "-E"])]
2585
+ ]);
2586
+ const PYTHON_RE = /^(?:python[0-9.]*|py)$/;
2587
+ const PYTHON = "python";
2588
+ const PATH_SEPARATOR_RE$1 = /[/\\]/;
2589
+ const WINDOWS_EXECUTABLE_RE$1 = /\.exe$/i;
2590
+ const OPTION_START$1 = "-";
2591
+ function interpreterOf(word) {
2592
+ const name = (word.value.split(PATH_SEPARATOR_RE$1).at(-1) ?? "").replace(
2593
+ WINDOWS_EXECUTABLE_RE$1,
2594
+ ""
2595
+ );
2596
+ return PYTHON_RE.test(name) ? PYTHON : name;
2597
+ }
2598
+ function codeAfter(command, program) {
2599
+ const options = CODE_OPTIONS.get(interpreterOf(command[program]));
2600
+ for (let index = program + 1; options !== void 0 && index < command.length; index += 1) {
2601
+ const word = command[index];
2602
+ if (!word.value.startsWith(OPTION_START$1)) {
2603
+ return void 0;
2604
+ }
2605
+ if (options.has(word.value)) {
2606
+ return command[index + 1];
2607
+ }
2608
+ }
2609
+ return void 0;
2610
+ }
2611
+ function inlineCode(command) {
2612
+ for (const [index, program] of command.entries()) {
2613
+ const code = codeAfter(command, index);
2614
+ if (code !== void 0) {
2615
+ return { code, program };
2616
+ }
2617
+ }
2618
+ return void 0;
2619
+ }
2620
+
2621
+ const EXPANSION_HEAD_RE = /^\$\{([A-Za-z_][A-Za-z0-9_]*):?([-=+])/;
2622
+ const USES = {
2623
+ "-": "default",
2624
+ "=": "assign",
2625
+ "+": "alternative"
2626
+ };
2627
+ const SINGLE_QUOTE = "'";
2628
+ const BACKSLASH$2 = "\\";
2629
+ function expansionAt(text, start) {
2630
+ const head = EXPANSION_HEAD_RE.exec(text.slice(start));
2631
+ const end = head === null ? void 0 : parameterExpansionEnd(text, start);
2632
+ if (head === null || end === void 0) {
2633
+ return void 0;
2634
+ }
2635
+ const wordStart = start + head[0].length;
2636
+ return {
2637
+ name: head[1],
2638
+ use: USES[head[2]],
2639
+ text: text.slice(wordStart, end - 1),
2640
+ start: wordStart,
2641
+ expansionStart: start,
2642
+ expansionEnd: end
2643
+ };
2644
+ }
2645
+ function wordExpansions(raw) {
2646
+ const found = [];
2647
+ for (let index = 0; index < raw.length; ) {
2648
+ const char = raw[index];
2649
+ if (char === SINGLE_QUOTE) {
2650
+ const close = raw.indexOf(SINGLE_QUOTE, index + 1);
2651
+ index = close === -1 ? raw.length : close + 1;
2652
+ } else if (char === BACKSLASH$2) {
2653
+ index += 2;
2654
+ } else if (raw.startsWith(PARAMETER_OPEN, index)) {
2655
+ const expansion = expansionAt(raw, index);
2656
+ found.push(...expansion === void 0 ? [] : [expansion]);
2657
+ index = expansion?.expansionEnd ?? index + PARAMETER_OPEN.length;
2658
+ } else {
2659
+ index += 1;
2660
+ }
2661
+ }
2662
+ return found;
2663
+ }
2664
+ const DOUBLE_QUOTE$1 = '"';
2665
+ function wholeWordExpansion(raw) {
2666
+ const isQuoted = raw.length > 1 && raw.startsWith(DOUBLE_QUOTE$1) && raw.endsWith(DOUBLE_QUOTE$1);
2667
+ const inner = isQuoted ? 1 : 0;
2668
+ const [expansion] = wordExpansions(raw);
2669
+ const isWhole = expansion?.expansionStart === inner && expansion.expansionEnd === raw.length - inner;
2670
+ return isWhole ? expansion : void 0;
2671
+ }
2672
+
2285
2673
  const HEREDOC_OPERATOR = "<<";
2286
2674
  const HERE_STRING_OPERATOR = "<<<";
2287
2675
  const STRIP_TABS = "-";
2288
- const LINE_BREAK$2 = "\n";
2676
+ const LINE_BREAK$5 = "\n";
2289
2677
  const LEADING_TABS_RE = /^\t+/;
2290
2678
  const CARRIAGE_RETURN_RE = /\r$/;
2291
2679
  function heredocAt(text, index, where) {
@@ -2317,7 +2705,7 @@ function isDelimiterLine(text, lineStart, end, heredoc) {
2317
2705
  function readBody(text, start, heredoc) {
2318
2706
  let lineStart = start;
2319
2707
  while (lineStart < text.length) {
2320
- const lineEnd = text.indexOf(LINE_BREAK$2, lineStart);
2708
+ const lineEnd = text.indexOf(LINE_BREAK$5, lineStart);
2321
2709
  const end = lineEnd === -1 ? text.length : lineEnd;
2322
2710
  if (isDelimiterLine(text, lineStart, end, heredoc)) {
2323
2711
  const body = { command: heredoc.command, start, text: text.slice(start, lineStart) };
@@ -2335,11 +2723,69 @@ function readBodies(text, start, pending) {
2335
2723
  bodies.push(read.body);
2336
2724
  end = read.end;
2337
2725
  }
2338
- return { bodies, end };
2726
+ return { bodies, end };
2727
+ }
2728
+
2729
+ const EXPRESSION_OPEN = "${{";
2730
+ const EXPRESSION_CLOSE = "}}";
2731
+ const LITERAL_QUOTE = "'";
2732
+ const SHELL_SINGLE_QUOTE = "'";
2733
+ const BACKSLASH$1 = "\\";
2734
+ const DOUBLE_QUOTE = '"';
2735
+ function expressionEnd(text, start) {
2736
+ for (let index = start + EXPRESSION_OPEN.length; index < text.length; ) {
2737
+ if (text.startsWith(EXPRESSION_CLOSE, index)) {
2738
+ return index + EXPRESSION_CLOSE.length;
2739
+ }
2740
+ const close = text[index] === LITERAL_QUOTE ? text.indexOf(LITERAL_QUOTE, index + 1) : index;
2741
+ index = close === -1 ? text.length : close + 1;
2742
+ }
2743
+ return void 0;
2744
+ }
2745
+ function literalsIn(raw, start, end) {
2746
+ const found = [];
2747
+ let open = raw.indexOf(LITERAL_QUOTE, start);
2748
+ while (open !== -1 && open < end) {
2749
+ let close = raw.indexOf(LITERAL_QUOTE, open + 1);
2750
+ let isEscaped = false;
2751
+ while (close !== -1 && raw[close + 1] === LITERAL_QUOTE) {
2752
+ isEscaped = true;
2753
+ close = raw.indexOf(LITERAL_QUOTE, close + 2);
2754
+ }
2755
+ if (close === -1) {
2756
+ break;
2757
+ }
2758
+ found.push(...isEscaped ? [] : [{ text: raw.slice(open + 1, close), start: open + 1 }]);
2759
+ open = raw.indexOf(LITERAL_QUOTE, close + 1);
2760
+ }
2761
+ return found;
2762
+ }
2763
+ function expressionLiterals(raw) {
2764
+ const found = [];
2765
+ for (let index = 0; index < raw.length; ) {
2766
+ if (raw[index] === SHELL_SINGLE_QUOTE) {
2767
+ const close = raw.indexOf(SHELL_SINGLE_QUOTE, index + 1);
2768
+ index = close === -1 ? raw.length : close + 1;
2769
+ } else if (raw[index] === BACKSLASH$1) {
2770
+ index += 2;
2771
+ } else if (raw.startsWith(EXPRESSION_OPEN, index)) {
2772
+ const end = expressionEnd(raw, index) ?? raw.length;
2773
+ found.push(...literalsIn(raw, index + EXPRESSION_OPEN.length, end));
2774
+ index = end;
2775
+ } else {
2776
+ index += 1;
2777
+ }
2778
+ }
2779
+ return found;
2780
+ }
2781
+ function isWholeExpression(raw) {
2782
+ const isQuoted = raw.length > 1 && raw.startsWith(DOUBLE_QUOTE) && raw.endsWith(DOUBLE_QUOTE);
2783
+ const inner = isQuoted ? 1 : 0;
2784
+ return raw.startsWith(EXPRESSION_OPEN, inner) && expressionEnd(raw, inner) === raw.length - inner;
2339
2785
  }
2340
2786
 
2341
2787
  const COMMENT = "#";
2342
- const LINE_BREAK$1 = "\n";
2788
+ const LINE_BREAK$4 = "\n";
2343
2789
  function skipTrivia(text, index) {
2344
2790
  const char = text[index];
2345
2791
  if (WORD_BREAKS.has(char)) {
@@ -2369,7 +2815,7 @@ function topLevelCommands(text, indent) {
2369
2815
  index = heredoc.end;
2370
2816
  } else if (afterTrivia !== index) {
2371
2817
  index = afterTrivia;
2372
- } else if (text[index] === LINE_BREAK$1 && pending.length > 0) {
2818
+ } else if (text[index] === LINE_BREAK$4 && pending.length > 0) {
2373
2819
  const read = readBodies(text, index + 1, pending);
2374
2820
  heredocs.push(...read.bodies);
2375
2821
  pending = [];
@@ -2434,18 +2880,18 @@ const DOCKER_RUN = "RUN";
2434
2880
  const PASS_THROUGH = "cat";
2435
2881
  const PATH_SEPARATOR$8 = "/";
2436
2882
  const OPTION_PREFIX$1 = "-";
2437
- const programName = (command) => command[0]?.value.split(PATH_SEPARATOR$8).at(-1) ?? "";
2883
+ const programName$1 = (command) => command[0]?.value.split(PATH_SEPARATOR$8).at(-1) ?? "";
2438
2884
  const isOption = (word) => word.value.startsWith(OPTION_PREFIX$1);
2439
- const isScriptShell = (command) => SHELLS$1.has(programName(command)) && command.slice(1).every(isOption);
2885
+ const isScriptShell = (command) => SHELLS$1.has(programName$1(command)) && command.slice(1).every(isOption);
2440
2886
  function readsScript(command) {
2441
- if (programName(command) !== DOCKER_RUN) {
2887
+ if (programName$1(command) !== DOCKER_RUN) {
2442
2888
  return isScriptShell(command);
2443
2889
  }
2444
2890
  const after = command.slice(1);
2445
2891
  const shell = after.slice(after.findIndex((word) => !isOption(word)));
2446
2892
  return after.every(isOption) || isScriptShell(shell);
2447
2893
  }
2448
- const passesThrough = (command) => programName(command) === PASS_THROUGH && command.slice(1).every(isOption);
2894
+ const passesThrough = (command) => programName$1(command) === PASS_THROUGH && command.slice(1).every(isOption);
2449
2895
  function scriptsRunBy(top, index, command) {
2450
2896
  const previous = index - 1;
2451
2897
  const isPipedScript = top.pipes.has(previous) && isScriptShell(command) && passesThrough(simpleCommand(top.commands[previous] ?? []));
@@ -2457,16 +2903,21 @@ const shifted = (words, offset) => words.map((word) => ({ ...word, start: word.s
2457
2903
  function shellCommandsAt(text, offset, indent = 0) {
2458
2904
  return shellCommands(text, indent).map((words) => shifted(words, offset));
2459
2905
  }
2906
+ const expansionCommands = (word) => [...wordExpansions(word.raw), ...expressionLiterals(word.raw)].flatMap(
2907
+ (part) => shellCommandsAt(part.text, word.start + part.start)
2908
+ );
2460
2909
  function innerCommands(command) {
2461
- const quoted = command.flatMap((word) => {
2462
- const inner = nestedCommandText(word);
2463
- return inner === void 0 ? [] : shellCommandsAt(inner, word.start + 1);
2910
+ const assignments = new Map(shellAssignments(command).map((found) => [found.word, found]));
2911
+ const code = inlineCode(command)?.code;
2912
+ return command.flatMap((word) => {
2913
+ const inner = word === code ? void 0 : nestedCommandText(word);
2914
+ if (inner !== void 0) {
2915
+ return shellCommandsAt(inner, word.start + 1);
2916
+ }
2917
+ const assignment = assignments.get(word);
2918
+ const value = assignment === void 0 ? void 0 : assignmentValueText(assignment);
2919
+ return value === void 0 ? expansionCommands(word) : shellCommandsAt(value.text, value.start);
2464
2920
  });
2465
- const assigned = shellAssignments(command).flatMap((assignment) => {
2466
- const value = assignmentValueText(assignment);
2467
- return value === void 0 ? [] : shellCommandsAt(value.text, value.start);
2468
- });
2469
- return [...quoted, ...assigned];
2470
2921
  }
2471
2922
  function shellCommands(text, indent = 0) {
2472
2923
  const top = topLevelCommands(text, indent);
@@ -2540,19 +2991,27 @@ function expandedName(text) {
2540
2991
  const match = SHELL_VARIABLE_RE.exec(text);
2541
2992
  return match === null ? void 0 : match[1] ?? match[2];
2542
2993
  }
2543
- function programDefinition$1({ name, value }) {
2544
- const target = expandedName(value.trim());
2545
- if (target !== void 0) {
2546
- return { name, kind: "reference", target };
2547
- }
2548
- return { name, kind: "command", runsGenerator: shellCommands(value).some(runsGenerator) };
2994
+ const textRunsGenerator = (text) => shellCommands(text).some(runsGenerator);
2995
+ function textDefinition(name, text) {
2996
+ const target = expandedName(text.trim());
2997
+ return target === void 0 ? { name, kind: "command", runsGenerator: textRunsGenerator(text) } : { name, kind: "reference", target };
2998
+ }
2999
+ function expansionDefinitions(name, expansion) {
3000
+ const word = textDefinition(name, expansion.text);
3001
+ const isOutside = expansion.use === "alternative" || expansion.name === name;
3002
+ const variable = isOutside ? { name, kind: "command", runsGenerator: false } : { name, kind: "reference", target: expansion.name };
3003
+ return [word, variable];
3004
+ }
3005
+ function programDefinitions$1({ name, value }) {
3006
+ const expansion = wholeWordExpansion(value.trim());
3007
+ return expansion === void 0 ? [textDefinition(name, value)] : expansionDefinitions(name, expansion);
2549
3008
  }
2550
3009
  function shellVariables(definitions) {
2551
3010
  const values = /* @__PURE__ */ new Map();
2552
3011
  for (const { name, value } of definitions) {
2553
3012
  values.set(name, [values.get(name), value].filter((part) => part !== void 0).join(" "));
2554
3013
  }
2555
- const programs = variablePrograms(definitions.map(programDefinition$1));
3014
+ const programs = variablePrograms(definitions.flatMap(programDefinitions$1));
2556
3015
  const nameAt = (word) => expandedName(word.raw);
2557
3016
  return {
2558
3017
  programAt: (program) => {
@@ -2573,8 +3032,15 @@ function combinedUse(left, right) {
2573
3032
  }
2574
3033
  return left.program === right.program ? left : { ...left, program: "generator-or-other" };
2575
3034
  }
3035
+ function assignedByExpansions(commands) {
3036
+ return commands.flat().flatMap((word) => wordExpansions(word.raw)).filter((expansion) => expansion.use === "assign").flatMap(({ name, text }) => [
3037
+ { name, value: text },
3038
+ { name, value: "" }
3039
+ ]);
3040
+ }
2576
3041
  function withShellAssignments(commands, file) {
2577
- const definitions = commands.flatMap(shellAssignments);
3042
+ const assigned = commands.flatMap(shellAssignments).filter((found) => !isArrayAssignment(found));
3043
+ const definitions = [...assigned, ...assignedByExpansions(commands)];
2578
3044
  if (definitions.length === 0) {
2579
3045
  return file;
2580
3046
  }
@@ -2584,6 +3050,35 @@ function withShellAssignments(commands, file) {
2584
3050
  referenceAt: (word) => own.referenceAt(word) ?? file.referenceAt(word)
2585
3051
  };
2586
3052
  }
3053
+ const joinedProgram = (left, right) => left === right ? left : "generator-or-other";
3054
+ function expansionUse(word, variables) {
3055
+ const expansion = wholeWordExpansion(word.raw);
3056
+ if (expansion === void 0) {
3057
+ return void 0;
3058
+ }
3059
+ const programOf = (name) => (name === void 0 ? void 0 : variables.programAt({ ...word, raw: `$${name}` })?.program) ?? "other";
3060
+ const target = expandedName(expansion.text.trim());
3061
+ const textProgram = textRunsGenerator(expansion.text) ? "generator" : "other";
3062
+ const own = target === void 0 ? textProgram : programOf(target);
3063
+ const variable = programOf(expansion.use === "alternative" ? void 0 : expansion.name);
3064
+ return { written: word.raw, program: joinedProgram(own, variable) };
3065
+ }
3066
+ function withWordExpansions(variables) {
3067
+ return {
3068
+ programAt: (program) => variables.programAt(program) ?? expansionUse(program, variables),
3069
+ referenceAt: (word) => variables.referenceAt(word)
3070
+ };
3071
+ }
3072
+ const VARIABLE_NAME = "[A-Za-z_][A-Za-z0-9_]*";
3073
+ const VARIABLE_WORD_RE = new RegExp(
3074
+ `^"?\\$\\$?(?:${VARIABLE_NAME}|\\{${VARIABLE_NAME}(?:\\[[^\\]]*\\])?\\})"?$`
3075
+ );
3076
+ function withUnreadVariables(variables) {
3077
+ return {
3078
+ programAt: (program) => variables.programAt(program) ?? (VARIABLE_WORD_RE.test(program.raw) || isWholeExpression(program.raw) ? { written: program.raw, program: "generator-or-other" } : void 0),
3079
+ referenceAt: (word) => variables.referenceAt(word)
3080
+ };
3081
+ }
2587
3082
 
2588
3083
  const QUOTES$1 = /* @__PURE__ */ new Set(["'", '"']);
2589
3084
  const PATH_SEPARATOR$7 = "/";
@@ -2694,6 +3189,14 @@ function commandFindings(command, locate, variables) {
2694
3189
  const handled = new Set(found.map((finding) => finding.offset));
2695
3190
  return [...found, ...unmigratedWordFindings(words, handled, locate)];
2696
3191
  }
3192
+ function inlineCodeFindings({ code, program }, locate) {
3193
+ if (!namesOriginalPackage(code.value)) {
3194
+ return [];
3195
+ }
3196
+ const location = locate(code.start);
3197
+ const manualStep = { kind: "script-not-shell", location, shell: program.value };
3198
+ return [{ offset: code.start, manualStep }];
3199
+ }
2697
3200
  function knownFindings(words, locate, variables) {
2698
3201
  const roles = commandRoles(words);
2699
3202
  const findings = roles.packages.flatMap((found2) => packageFindings(found2, locate));
@@ -2701,6 +3204,10 @@ function knownFindings(words, locate, variables) {
2701
3204
  if (program === void 0) {
2702
3205
  return findings;
2703
3206
  }
3207
+ const code = inlineCode(words);
3208
+ if (code !== void 0) {
3209
+ return [...findings, ...inlineCodeFindings(code, locate)];
3210
+ }
2704
3211
  const found = programFindings(program, locate(program.start));
2705
3212
  findings.push(...found);
2706
3213
  const isRenamed = found.some((finding) => finding.change?.kind === "command-renamed");
@@ -2720,7 +3227,7 @@ function shellTextFindings(text, locate) {
2720
3227
  return commandWordFindings(shellCommands(text), locate);
2721
3228
  }
2722
3229
  function commandWordFindings(commands, locate, variables = NO_VARIABLES) {
2723
- const known = withShellAssignments(commands, variables);
3230
+ const known = withUnreadVariables(withWordExpansions(withShellAssignments(commands, variables)));
2724
3231
  const findings = commands.flatMap((words) => commandFindings(words, locate, known)).sort((left, right) => left.offset - right.offset);
2725
3232
  return {
2726
3233
  edits: findings.flatMap((finding) => finding.edit === void 0 ? [] : [finding.edit]),
@@ -2794,45 +3301,6 @@ function commandStringSteps(text, strings) {
2794
3301
  });
2795
3302
  }
2796
3303
 
2797
- const isMap = (node) => node?.kind === "map";
2798
- const isScalar = (node) => node.kind === "scalar";
2799
- function textEntries(map) {
2800
- return map.entries.flatMap(({ key, value }) => isScalar(key) ? [{ key, value }] : []);
2801
- }
2802
- function valuesAt(map, key) {
2803
- return textEntries(map).filter((entry) => entry.key.value === key).map((entry) => entry.value);
2804
- }
2805
- function mapsAt(map, key) {
2806
- return valuesAt(map, key).filter(isMap);
2807
- }
2808
- function stringsBelow(node, path) {
2809
- switch (node.kind) {
2810
- case "scalar":
2811
- return [{ ...node, path }];
2812
- case "list":
2813
- return node.items.flatMap((item, index) => stringsBelow(item, [...path, index]));
2814
- case "map":
2815
- return textEntries(node).flatMap(
2816
- ({ key, value }) => stringsBelow(value, [...path, key.value])
2817
- );
2818
- default:
2819
- return [];
2820
- }
2821
- }
2822
- function documentStrings(documents) {
2823
- return documents.flatMap((document) => stringsBelow(document, []));
2824
- }
2825
- function ownerKey(path) {
2826
- return path.findLast((part) => typeof part === "string");
2827
- }
2828
- function unreadableConfig(text, offset) {
2829
- if (!namesOriginalPackage(text)) {
2830
- return unchanged(text);
2831
- }
2832
- const position = offset === void 0 ? void 0 : { line: lineAt(text, offset), column: columnAt(text, offset) };
2833
- return { ...unchanged(text), manualSteps: [{ kind: "config-unreadable", position }] };
2834
- }
2835
-
2836
3304
  const KEPT_PROTOCOLS = ["catalog:", "workspace:"];
2837
3305
  const LOCAL_PROTOCOLS = ["workspace:", "link:", "file:", "portal:"];
2838
3306
  const RANGE_SEPARATOR = "||";
@@ -2915,8 +3383,12 @@ function localPackageNames(manifest) {
2915
3383
  );
2916
3384
  }
2917
3385
  function localPackagesFor(manifests, filePath) {
2918
- const containing = manifests.filter(({ path }) => isAtOrAbove(path, filePath));
2919
- return new Set(containing.flatMap(({ localPackages }) => [...localPackages ?? []]));
3386
+ const nearestFirst = manifests.filter(({ path }) => isAtOrAbove(path, filePath)).sort((left, right) => directoryOf(right.path).length - directoryOf(left.path).length);
3387
+ const candidates = new Set(
3388
+ nearestFirst.flatMap(({ localPackages }) => [...localPackages ?? []])
3389
+ );
3390
+ const isLocal = (name) => nearestFirst.find(({ names }) => names?.has(name) === true)?.localPackages?.has(name) === true;
3391
+ return new Set([...candidates].filter(isLocal));
2920
3392
  }
2921
3393
  function isDeclaredFor(manifests, filePath, name) {
2922
3394
  const original = ORIGINAL_NAMES.get(name);
@@ -2950,7 +3422,7 @@ const NOT_LINE_BREAK_RE$1 = /[^\n]/g;
2950
3422
  const ARRAY_END = "]";
2951
3423
  const VARIABLE_INSTRUCTIONS = /* @__PURE__ */ new Set(["ARG", "ENV"]);
2952
3424
  const LEGACY_ENV_INSTRUCTION = "ENV";
2953
- const ASSIGNMENT = "=";
3425
+ const ASSIGNMENT$1 = "=";
2954
3426
  const QUOTES = /* @__PURE__ */ new Set(['"', "'"]);
2955
3427
  function matchAt(pattern, text, index) {
2956
3428
  pattern.lastIndex = index;
@@ -3049,7 +3521,7 @@ function escapedScriptSteps(scripts, lineOf) {
3049
3521
  });
3050
3522
  }
3051
3523
  function assignedVariable(word) {
3052
- const at = word.raw.indexOf(ASSIGNMENT);
3524
+ const at = word.raw.indexOf(ASSIGNMENT$1);
3053
3525
  if (at <= 0) {
3054
3526
  return void 0;
3055
3527
  }
@@ -3057,36 +3529,52 @@ function assignedVariable(word) {
3057
3529
  const isQuoted = raw.length >= 2 && QUOTES.has(raw[0]) && raw.at(-1) === raw[0];
3058
3530
  const value = isQuoted ? raw.slice(1, -1) : raw;
3059
3531
  const start = word.start + at + 1 + (isQuoted ? 1 : 0);
3060
- return { name: word.raw.slice(0, at), commands: shellCommandsAt(value, start) };
3532
+ return { name: word.raw.slice(0, at), value, start };
3061
3533
  }
3062
- function dockerVariables(commands) {
3063
- return commands.flatMap(([instruction, first, ...rest]) => {
3534
+ function instructionVariables(text, words) {
3535
+ const [instruction, first, ...rest] = words;
3536
+ const last = rest.at(-1);
3537
+ if (instruction.value === LEGACY_ENV_INSTRUCTION && !first.raw.includes(ASSIGNMENT$1)) {
3538
+ const value = last === void 0 ? "" : text.slice(rest[0]?.start, last.end);
3539
+ return [{ name: first.value, value, start: rest[0]?.start ?? first.end }];
3540
+ }
3541
+ return [first, ...rest].flatMap((word) => assignedVariable(word) ?? []);
3542
+ }
3543
+ function variableInstructions(text, commands) {
3544
+ return commands.flatMap((words) => {
3545
+ const [instruction, first] = words;
3064
3546
  if (instruction === void 0 || first === void 0) {
3065
3547
  return [];
3066
3548
  }
3067
3549
  if (!VARIABLE_INSTRUCTIONS.has(instruction.value)) {
3068
3550
  return [];
3069
3551
  }
3070
- if (instruction.value === LEGACY_ENV_INSTRUCTION && !first.raw.includes(ASSIGNMENT)) {
3071
- return [{ name: first.value, commands: [rest] }];
3072
- }
3073
- return [first, ...rest].flatMap((word) => assignedVariable(word) ?? []);
3552
+ const end = words.at(-1).end;
3553
+ return [{ start: instruction.start, end, variables: instructionVariables(text, words) }];
3074
3554
  });
3075
3555
  }
3076
- const definitionOf = ({ name, commands }) => ({
3077
- name,
3078
- value: commands.flatMap((words) => words.map((word) => word.raw)).join(" ")
3079
- });
3556
+ function withoutVariableInstructions(commands, instructions) {
3557
+ const isInInstruction = (words) => {
3558
+ const start = words[0]?.start ?? -1;
3559
+ return instructions.some(
3560
+ (instruction) => start >= instruction.start && start < instruction.end
3561
+ );
3562
+ };
3563
+ return commands.filter((words) => !isInInstruction(words));
3564
+ }
3565
+ const valueCommands = ({ value, start }) => shellCommandsAt(value, start);
3080
3566
  function migrateDockerfile(text, file) {
3081
3567
  const lineOf = lineLocator(text);
3082
3568
  const locate = (offset) => `${file}:${lineOf(offset)}`;
3083
3569
  const forms = execForms(text);
3084
3570
  const exec = execCommands(forms);
3085
3571
  const shellForm = shellCommands(withoutExecForms(text, forms));
3086
- const definitions = dockerVariables(shellForm);
3087
- const values = definitions.flatMap((variable) => variable.commands);
3088
- const commands = [...shellForm, ...values, ...exec.commands];
3089
- const variables = shellVariables(definitions.map(definitionOf));
3572
+ const instructions = variableInstructions(text, shellForm);
3573
+ const definitions = instructions.flatMap((instruction) => instruction.variables);
3574
+ const values = definitions.flatMap(valueCommands);
3575
+ const run = withoutVariableInstructions(shellForm, instructions);
3576
+ const commands = [...run, ...values, ...exec.commands];
3577
+ const variables = shellVariables(definitions);
3090
3578
  const { edits, changes, manualSteps } = commandWordFindings(commands, locate, variables);
3091
3579
  const steps = [...manualSteps, ...escapedScriptSteps(exec.escaped, lineOf)];
3092
3580
  return { text: applyTextEdits(text, edits), changes, manualSteps: steps };
@@ -3378,30 +3866,12 @@ function migrateImportMap(text, read, version) {
3378
3866
  }
3379
3867
 
3380
3868
  const IMPORT_MAP_FIELD = "importMap";
3381
- const SEPARATOR = "/";
3382
- const CURRENT_FOLDER = ".";
3383
- const PARENT_FOLDER = "..";
3384
- const URL_SCHEME = /^[a-z][a-z\d+.-]*:/i;
3385
3869
  function importMapReferences(roots) {
3386
3870
  return roots.flatMap((root) => valuesAt(root, IMPORT_MAP_FIELD)).flatMap((value) => isScalar(value) ? [value.value] : []);
3387
3871
  }
3388
- function resolvedPath(configPath, reference) {
3389
- const parts = configPath.split(SEPARATOR).slice(0, -1);
3390
- for (const part of reference.split(SEPARATOR)) {
3391
- if (part === PARENT_FOLDER) {
3392
- if (parts.pop() === void 0) {
3393
- return void 0;
3394
- }
3395
- } else if (part !== CURRENT_FOLDER && part !== "") {
3396
- parts.push(part);
3397
- }
3398
- }
3399
- return parts.join(SEPARATOR);
3400
- }
3401
3872
  function importMapLocation(configPath, reference) {
3402
- const isOutside = URL_SCHEME.test(reference) || reference.startsWith(SEPARATOR);
3403
- const path = isOutside ? void 0 : resolvedPath(configPath, reference);
3404
- return path === void 0 || path === "" ? { kind: "not-editable", reference } : { kind: "in-project", path };
3873
+ const path = referencedProjectPath(configPath, reference);
3874
+ return path === void 0 ? { kind: "not-editable", reference } : { kind: "in-project", path };
3405
3875
  }
3406
3876
  const importMapLocations = (configPath, roots) => importMapReferences(roots).map((reference) => importMapLocation(configPath, reference));
3407
3877
  function importMapSteps(configPath, read) {
@@ -3456,8 +3926,8 @@ function distinctStrings(strings) {
3456
3926
  outer = enclosing.at(-1);
3457
3927
  }
3458
3928
  if (outer !== void 0 && found.end > outer.end) {
3459
- throw new Error(
3460
- `Source strings overlap at offsets ${outer.start}-${outer.end} and ${found.start}-${found.end}; the file was not changed.`
3929
+ throw new MigrationBugError(
3930
+ `source strings overlap at offsets ${outer.start}-${outer.end} and ${found.start}-${found.end}`
3461
3931
  );
3462
3932
  }
3463
3933
  enclosing.push(found);
@@ -3580,58 +4050,365 @@ function migrateJsonCommands(text, strings, patterns) {
3580
4050
  return { text: applyTextEdits(text, edits), changes, manualSteps };
3581
4051
  }
3582
4052
 
3583
- const ASSIGNMENT_RE$1 = /^(?:export[ \t]+)?([A-Za-z_][A-Za-z0-9_-]*)[ \t]*:=[ \t]*(.*)$/gm;
3584
- const STRING_VALUE_RE = /^(?:"((?:[^"\\\n]|\\.)*)"|'([^'\n]*)')(?:[ \t]+#.*)?$/;
3585
- const NAME_VALUE_RE = /^([A-Za-z_][A-Za-z0-9_-]*)(?:[ \t]+#.*)?$/;
3586
- const INTERPOLATION_RE = /\{\{[^\n]*?\}\}/g;
4053
+ const DELIMITERS = ['"""', "'''", "```", '"', "'", "`"];
4054
+ const BACKTICK = "`";
4055
+ const ESCAPING_DELIMITER = '"';
4056
+ const ESCAPE = "\\";
4057
+ const COMMENT_START$1 = "#";
4058
+ const LINE_BREAK$3 = "\n";
4059
+ function closingOffset(text, delimiter, from) {
4060
+ if (delimiter !== ESCAPING_DELIMITER) {
4061
+ const close = text.indexOf(delimiter, from);
4062
+ return close === -1 ? void 0 : close;
4063
+ }
4064
+ for (let index = from; index < text.length; index += 1) {
4065
+ const char = text[index];
4066
+ if (char === ESCAPE) {
4067
+ index += 1;
4068
+ } else if (char === ESCAPING_DELIMITER || char === LINE_BREAK$3) {
4069
+ return char === ESCAPING_DELIMITER ? index : void 0;
4070
+ }
4071
+ }
4072
+ return void 0;
4073
+ }
4074
+ function literalAt(text, offset) {
4075
+ const delimiter = DELIMITERS.find((candidate) => text.startsWith(candidate, offset));
4076
+ if (delimiter === void 0) {
4077
+ return void 0;
4078
+ }
4079
+ const contentStart = offset + delimiter.length;
4080
+ const close = closingOffset(text, delimiter, contentStart);
4081
+ if (close === void 0) {
4082
+ return void 0;
4083
+ }
4084
+ const kind = delimiter.startsWith(BACKTICK) ? "backtick" : "string";
4085
+ const content = text.slice(contentStart, close);
4086
+ return { kind, start: offset, end: close + delimiter.length, contentStart, content };
4087
+ }
4088
+ function expressionAt(text, start) {
4089
+ const literals = [];
4090
+ let index = start;
4091
+ while (index < text.length && text[index] !== LINE_BREAK$3 && text[index] !== COMMENT_START$1) {
4092
+ const literal = literalAt(text, index);
4093
+ literals.push(...literal === void 0 ? [] : [literal]);
4094
+ index = literal?.end ?? index + 1;
4095
+ }
4096
+ return { literals, end: index };
4097
+ }
4098
+ function literalsBetween(text, start, end) {
4099
+ const literals = [];
4100
+ for (let index = start; index < end; ) {
4101
+ const literal = literalAt(text, index);
4102
+ literals.push(...literal === void 0 ? [] : [literal]);
4103
+ index = literal?.end ?? index + 1;
4104
+ }
4105
+ return literals;
4106
+ }
4107
+ const INTERPOLATION_OPEN = "{{";
4108
+ const INTERPOLATION_CLOSE = "}}";
4109
+ function interpolationAt(text, start) {
4110
+ const literals = [];
4111
+ for (let index = start + INTERPOLATION_OPEN.length; index < text.length; ) {
4112
+ if (text.startsWith(INTERPOLATION_CLOSE, index)) {
4113
+ return { start, end: index + INTERPOLATION_CLOSE.length, literals };
4114
+ }
4115
+ if (text[index] === LINE_BREAK$3) {
4116
+ return void 0;
4117
+ }
4118
+ const literal = literalAt(text, index);
4119
+ literals.push(...literal === void 0 ? [] : [literal]);
4120
+ index = literal?.end ?? index + 1;
4121
+ }
4122
+ return void 0;
4123
+ }
4124
+ function interpolationsOf(text) {
4125
+ const found = [];
4126
+ for (let open = text.indexOf(INTERPOLATION_OPEN); open !== -1; ) {
4127
+ const interpolation = interpolationAt(text, open);
4128
+ found.push(...interpolation === void 0 ? [] : [interpolation]);
4129
+ const next = interpolation?.end ?? open + INTERPOLATION_OPEN.length;
4130
+ open = text.indexOf(INTERPOLATION_OPEN, next);
4131
+ }
4132
+ return found;
4133
+ }
4134
+
4135
+ const RECIPE_NAME_RE = /^@?[A-Za-z_][A-Za-z0-9_-]*(?=[ \t:])/gm;
4136
+ const PARAMETER_RE = /^[+*$]*([A-Za-z_][A-Za-z0-9_-]*)(?:=([\s\S]*))?$/;
4137
+ const NAME_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
4138
+ const COLON = ":";
4139
+ const EQUALS = "=";
4140
+ const OPEN_PAREN = "(";
4141
+ const CLOSE_PAREN = ")";
4142
+ const COMMENT_START = "#";
4143
+ const LINE_BREAK$2 = "\n";
4144
+ const BLANK_RE = /[ \t]/;
4145
+ const depthChange = (char) => char === OPEN_PAREN ? 1 : char === CLOSE_PAREN ? -1 : 0;
4146
+ function headerColon(text, from) {
4147
+ for (let index = from; index < text.length && text[index] !== LINE_BREAK$2; ) {
4148
+ const literal = literalAt(text, index);
4149
+ const char = text[index];
4150
+ if (literal !== void 0) {
4151
+ index = literal.end;
4152
+ continue;
4153
+ }
4154
+ if (char === COMMENT_START || char === COLON && text[index + 1] === EQUALS) {
4155
+ return void 0;
4156
+ }
4157
+ if (char === COLON) {
4158
+ return index;
4159
+ }
4160
+ index += 1;
4161
+ }
4162
+ return void 0;
4163
+ }
4164
+ function parameterWords(text, start, end) {
4165
+ const words = [];
4166
+ let wordStart;
4167
+ let depth = 0;
4168
+ for (let index = start; index < end; ) {
4169
+ const isBreak = depth === 0 && BLANK_RE.test(text[index] ?? "");
4170
+ if (isBreak && wordStart !== void 0) {
4171
+ words.push({ start: wordStart, end: index });
4172
+ }
4173
+ wordStart = isBreak ? void 0 : wordStart ?? index;
4174
+ const literal = isBreak ? void 0 : literalAt(text, index);
4175
+ depth += literal === void 0 ? depthChange(text[index]) : 0;
4176
+ index = literal?.end ?? index + 1;
4177
+ }
4178
+ return wordStart === void 0 ? words : [...words, { start: wordStart, end }];
4179
+ }
4180
+ function parameterDefault(text, word) {
4181
+ const written = text.slice(word.start, word.end);
4182
+ const match = PARAMETER_RE.exec(written);
4183
+ const value = match?.[2];
4184
+ if (match === null || value === void 0) {
4185
+ return [];
4186
+ }
4187
+ const defaultStart = word.end - value.length;
4188
+ const literals = literalsBetween(text, defaultStart, word.end);
4189
+ const target = NAME_RE.test(value) ? value : void 0;
4190
+ return [{ name: match[1], literals, target }];
4191
+ }
4192
+ function recipeHeaders(text) {
4193
+ return [...text.matchAll(RECIPE_NAME_RE)].flatMap((match) => {
4194
+ const nameEnd = match.index + match[0].length;
4195
+ const colon = headerColon(text, nameEnd);
4196
+ if (colon === void 0) {
4197
+ return [];
4198
+ }
4199
+ const words = parameterWords(text, nameEnd, colon);
4200
+ const defaults = words.flatMap((word) => parameterDefault(text, word));
4201
+ return [{ start: match.index, end: colon, defaults }];
4202
+ });
4203
+ }
4204
+
4205
+ const STEP_SHELL_KEY = "shell";
4206
+ const DEFAULT_SHELL_PATH = ["defaults", "run", STEP_SHELL_KEY];
4207
+ const COMMAND_LINE_SHELLS = /* @__PURE__ */ new Set([
4208
+ ...["sh", "bash", "dash", "ash", "zsh", "ksh"],
4209
+ ...["pwsh", "powershell", "cmd"],
4210
+ ...["msys2", "wsl-bash"]
4211
+ ]);
4212
+ const ENV_PROGRAM = "env";
4213
+ const ENV_OPTIONS_WITH_VALUE = /* @__PURE__ */ new Set(["-u", "--unset", "-C", "--chdir"]);
4214
+ const OPTION_START = "-";
4215
+ const ASSIGNMENT = "=";
4216
+ const PATH_SEPARATOR_RE = /[/\\]/;
4217
+ const WINDOWS_EXECUTABLE_RE = /\.exe$/i;
4218
+ const WHITESPACE_RE = /\s+/;
4219
+ const EXECUTORS_KEY = "executors";
4220
+ const EXECUTOR_SHELL_PATH_LENGTH = 3;
4221
+ const EXECUTOR_KEY = "executor";
4222
+ const EXECUTOR_NAME_KEY = "name";
4223
+ const programName = (word) => (word.split(PATH_SEPARATOR_RE).at(-1) ?? "").replace(WINDOWS_EXECUTABLE_RE, "");
4224
+ function envPrefixLength(words) {
4225
+ let index = 0;
4226
+ while (index < words.length) {
4227
+ const word = words[index] ?? "";
4228
+ if (ENV_OPTIONS_WITH_VALUE.has(word)) {
4229
+ index += 2;
4230
+ } else if (word.startsWith(OPTION_START) || word.includes(ASSIGNMENT)) {
4231
+ index += 1;
4232
+ } else {
4233
+ break;
4234
+ }
4235
+ }
4236
+ return index;
4237
+ }
4238
+ function shellProgram(shell) {
4239
+ const words = shell.trim().split(WHITESPACE_RE);
4240
+ const first = programName(words[0] ?? "");
4241
+ if (first !== ENV_PROGRAM) {
4242
+ return first;
4243
+ }
4244
+ const rest = words.slice(1);
4245
+ return programName(rest[envPrefixLength(rest)] ?? "");
4246
+ }
4247
+ const isCommandLineShell = (shell) => COMMAND_LINE_SHELLS.has(shellProgram(shell));
4248
+ const endsWith = (path, keys) => keys.every((key, index) => path[path.length - keys.length + index] === key);
4249
+ const isDefaultShell = (value) => endsWith(value.path, DEFAULT_SHELL_PATH);
4250
+ function scopeOf(value) {
4251
+ const keys = isDefaultShell(value) ? DEFAULT_SHELL_PATH.length : 1;
4252
+ return JSON.stringify(value.path.slice(0, -keys));
4253
+ }
4254
+ function executorShells(shells) {
4255
+ const isExecutorShell = ({ path }) => path.length === EXECUTOR_SHELL_PATH_LENGTH && path[0] === EXECUTORS_KEY;
4256
+ return new Map(
4257
+ shells.filter(isExecutorShell).map((shell) => [String(shell.path[1]), shell.value])
4258
+ );
4259
+ }
4260
+ function executorJob({ path }) {
4261
+ if (path.at(-1) === EXECUTOR_KEY) {
4262
+ return path.slice(0, -1);
4263
+ }
4264
+ const isNamed = path.at(-1) === EXECUTOR_NAME_KEY && path.at(-2) === EXECUTOR_KEY;
4265
+ return isNamed ? path.slice(0, -2) : void 0;
4266
+ }
4267
+ function inheritedShells(strings, shells) {
4268
+ const byExecutor = executorShells(shells);
4269
+ return strings.flatMap((value) => {
4270
+ const job = executorJob(value);
4271
+ const shell = byExecutor.get(value.value);
4272
+ return job === void 0 || shell === void 0 ? [] : [[JSON.stringify(job), shell]];
4273
+ });
4274
+ }
4275
+ function stepShells(strings) {
4276
+ const shells = strings.filter((value) => value.path.at(-1) === STEP_SHELL_KEY);
4277
+ const isOwnShell = (value) => !isDefaultShell(value);
4278
+ const ordered = [...shells.filter(isDefaultShell), ...shells.filter(isOwnShell)];
4279
+ const byScope = new Map([
4280
+ ...inheritedShells(strings, shells),
4281
+ ...ordered.map((value) => [scopeOf(value), value.value])
4282
+ ]);
4283
+ return (value) => {
4284
+ for (let depth = value.path.length - 1; depth >= 0; depth -= 1) {
4285
+ const shell = byScope.get(JSON.stringify(value.path.slice(0, depth)));
4286
+ if (shell !== void 0) {
4287
+ return isCommandLineShell(shell) ? void 0 : shell;
4288
+ }
4289
+ }
4290
+ return void 0;
4291
+ };
4292
+ }
4293
+ function otherShellFindings(values, shellOf, locate) {
4294
+ const manualSteps = values.flatMap((value) => {
4295
+ const shell = shellOf(value);
4296
+ return shell === void 0 || !namesOriginalPackage(value.value) ? [] : [{ kind: "script-not-shell", location: locate(value.start), shell }];
4297
+ });
4298
+ return { edits: [], changes: [], manualSteps };
4299
+ }
4300
+
4301
+ const LINE_BREAK$1 = "\n";
4302
+ const SHEBANG = "#!";
4303
+ const BODY_LINE_RE = /^(?:[ \t][^\n]*|[ \t]*\r?)$/;
4304
+ function nextLineStart(text, offset) {
4305
+ const lineBreak = text.indexOf(LINE_BREAK$1, offset);
4306
+ return lineBreak === -1 ? text.length : lineBreak + 1;
4307
+ }
4308
+ function bodyEnd(text, start) {
4309
+ let end = start;
4310
+ while (end < text.length) {
4311
+ const lineEnd = nextLineStart(text, end);
4312
+ if (!BODY_LINE_RE.test(text.slice(end, lineEnd).replace(LINE_BREAK$1, ""))) {
4313
+ break;
4314
+ }
4315
+ end = lineEnd;
4316
+ }
4317
+ return end;
4318
+ }
4319
+ function shebangRecipe(text, header) {
4320
+ const start = nextLineStart(text, header.end);
4321
+ const end = bodyEnd(text, start);
4322
+ const firstLine = text.slice(start, end).split(LINE_BREAK$1).map((line) => line.trim()).find((line) => line.length > 0);
4323
+ if (firstLine === void 0 || !firstLine.startsWith(SHEBANG)) {
4324
+ return [];
4325
+ }
4326
+ const shell = firstLine.slice(SHEBANG.length).trim();
4327
+ const shebang = text.indexOf(SHEBANG, start);
4328
+ return isCommandLineShell(shell) ? [] : [{ start, end, shebang, shell }];
4329
+ }
4330
+ const shebangRecipes = (text, headers) => headers.flatMap((header) => shebangRecipe(text, header));
4331
+
4332
+ const ASSIGNMENT_RE$1 = /^(?:export[ \t]+)?([A-Za-z_][A-Za-z0-9_-]*)[ \t]*:=[ \t]*/gm;
4333
+ const NAME_VALUE_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
3587
4334
  const VARIABLE_INTERPOLATION_RE = /^[@+-]*\{\{[ \t]*([A-Za-z_][A-Za-z0-9_-]*)[ \t]*\}\}$/;
3588
4335
  const MASK$1 = "_";
3589
4336
  const BLANK = " ";
3590
- const assignmentsOf$1 = (text) => [...text.matchAll(ASSIGNMENT_RE$1)].map((match) => {
3591
- const written = match[2];
3592
- const valueStart = match.index + match[0].length - written.length;
3593
- return { name: match[1], value: written.trim(), start: match.index, valueStart };
3594
- });
3595
- function stringValue(value) {
3596
- const match = STRING_VALUE_RE.exec(value);
3597
- return match === null ? void 0 : match[1] ?? match[2];
4337
+ function assignmentsOf$1(text) {
4338
+ return [...text.matchAll(ASSIGNMENT_RE$1)].map((match) => {
4339
+ const valueStart = match.index + match[0].length;
4340
+ const { literals, end } = expressionAt(text, valueStart);
4341
+ const value = text.slice(valueStart, end).trim();
4342
+ return { name: match[1], value, start: match.index, end, literals };
4343
+ });
4344
+ }
4345
+ function onlyString({ value, literals }) {
4346
+ const [literal] = literals;
4347
+ if (literals.length !== 1 || literal?.kind !== "string") {
4348
+ return void 0;
4349
+ }
4350
+ return value.length === literal.end - literal.start ? literal : void 0;
3598
4351
  }
3599
- function programDefinition({ name, value }) {
3600
- const target = NAME_VALUE_RE.exec(value)?.[1];
3601
- if (target !== void 0) {
3602
- return { name, kind: "reference", target };
4352
+ const stringRunsGenerator = (literal) => literal.kind === "string" && shellCommands(literal.content).some(runsGenerator);
4353
+ function computedDefinitions(name, literals) {
4354
+ const other = { name, kind: "command", runsGenerator: false };
4355
+ const isMaybeGenerator = literals.some(stringRunsGenerator);
4356
+ return isMaybeGenerator ? [{ name, kind: "command", runsGenerator: true }, other] : [other];
4357
+ }
4358
+ function assignmentDefinitions(assignment) {
4359
+ const { name, value } = assignment;
4360
+ if (NAME_VALUE_RE.test(value)) {
4361
+ return [{ name, kind: "reference", target: value }];
3603
4362
  }
3604
- const text = stringValue(value);
3605
- const runs = text !== void 0 && shellCommands(text).some(runsGenerator);
3606
- return { name, kind: "command", runsGenerator: runs };
4363
+ const literal = onlyString(assignment);
4364
+ return literal === void 0 ? computedDefinitions(name, assignment.literals) : [{ name, kind: "command", runsGenerator: stringRunsGenerator(literal) }];
4365
+ }
4366
+ function parameterDefinitions({ name, literals, target }) {
4367
+ const fromCommandLine = computedDefinitions(name, literals);
4368
+ return target === void 0 ? fromCommandLine : [{ name, kind: "reference", target }, ...fromCommandLine];
3607
4369
  }
3608
- function shellText$1(text, assignments) {
4370
+ function shellText$1(text, spans) {
3609
4371
  const parts = [];
3610
4372
  let copiedUpTo = 0;
3611
- for (const { start, valueStart, value } of assignments) {
3612
- const end = valueStart + value.length;
4373
+ for (const { start, end } of [...spans].sort((left, right) => left.start - right.start)) {
3613
4374
  parts.push(text.slice(copiedUpTo, start), BLANK.repeat(end - start));
3614
4375
  copiedUpTo = end;
3615
4376
  }
3616
4377
  parts.push(text.slice(copiedUpTo));
3617
- return parts.join("").replace(INTERPOLATION_RE, (found) => MASK$1.repeat(found.length));
3618
- }
3619
- function valueCommands({ value, valueStart }) {
3620
- const inner = stringValue(value);
3621
- return inner === void 0 ? [] : shellCommandsAt(inner, valueStart + 1);
4378
+ const blanked = parts.join("");
4379
+ const interpolations = interpolationsOf(blanked);
4380
+ const masked = interpolations.reduce(
4381
+ (current, { start, end }) => current.slice(0, start) + MASK$1.repeat(end - start) + current.slice(end),
4382
+ blanked
4383
+ );
4384
+ return { text: masked, interpolations };
3622
4385
  }
3623
- function justfileVariables(text, assignments, programs) {
4386
+ const literalCommands = (literals) => literals.flatMap((literal) => shellCommandsAt(literal.content, literal.contentStart));
4387
+ const defaultValue = ({ literals }) => literals.map((literal) => literal.content).join(BLANK);
4388
+ function variableValues$1(assignments, defaults) {
3624
4389
  const values = /* @__PURE__ */ new Map();
3625
- for (const { name, value } of assignments) {
3626
- values.set(name, [values.get(name), stringValue(value) ?? value].join(" ").trim());
4390
+ const named = [
4391
+ ...assignments.map((found) => ({ ...found, value: onlyString(found)?.content ?? found.value })),
4392
+ ...defaults.map((found) => ({ name: found.name, value: defaultValue(found) }))
4393
+ ];
4394
+ for (const { name, value } of named) {
4395
+ values.set(name, [values.get(name), value].join(BLANK).trim());
3627
4396
  }
4397
+ return values;
4398
+ }
4399
+ function computedProgram(written) {
4400
+ const literals = interpolationsOf(written).flatMap((interpolation) => interpolation.literals);
4401
+ return literals.some(stringRunsGenerator) ? "generator-or-other" : void 0;
4402
+ }
4403
+ function justfileVariables(text, values, programs) {
3628
4404
  const writtenAt = (word) => text.slice(word.start, word.end);
3629
4405
  const nameAt = (word) => VARIABLE_INTERPOLATION_RE.exec(writtenAt(word))?.[1];
3630
4406
  return {
3631
- programAt: (program) => {
3632
- const name = nameAt(program);
3633
- const found = name === void 0 ? void 0 : programs.get(name);
3634
- return found === void 0 ? void 0 : { written: writtenAt(program), program: found };
4407
+ programAt: (word) => {
4408
+ const name = nameAt(word);
4409
+ const written = writtenAt(word);
4410
+ const found = name === void 0 ? computedProgram(written) : programs.get(name);
4411
+ return found === void 0 ? void 0 : { written, program: found };
3635
4412
  },
3636
4413
  referenceAt: (word) => {
3637
4414
  const name = nameAt(word);
@@ -3640,18 +4417,29 @@ function justfileVariables(text, assignments, programs) {
3640
4417
  }
3641
4418
  };
3642
4419
  }
4420
+ const shebangSteps = (text, recipes, locate) => recipes.filter((recipe) => namesOriginalPackage(text.slice(recipe.start, recipe.end))).map(({ shebang, shell }) => ({ kind: "script-not-shell", location: locate(shebang), shell }));
3643
4421
  function migrateJustfile(text, file) {
3644
4422
  const lineOf = lineLocator(text);
3645
4423
  const assignments = assignmentsOf$1(text);
3646
- const programs = variablePrograms(assignments.map(programDefinition));
3647
- const variables = justfileVariables(text, assignments, programs);
4424
+ const headers = recipeHeaders(text);
4425
+ const defaults = headers.flatMap((header) => header.defaults);
4426
+ const programs = variablePrograms([
4427
+ ...assignments.flatMap(assignmentDefinitions),
4428
+ ...defaults.flatMap(parameterDefinitions)
4429
+ ]);
4430
+ const variables = justfileVariables(text, variableValues$1(assignments, defaults), programs);
4431
+ const scripts = shebangRecipes(text, headers);
4432
+ const shell = shellText$1(text, [...assignments, ...headers, ...scripts]);
3648
4433
  const commands = [
3649
- ...shellCommands(shellText$1(text, assignments)),
3650
- ...assignments.flatMap(valueCommands)
4434
+ ...shellCommands(shell.text),
4435
+ ...literalCommands(assignments.flatMap((assignment) => assignment.literals)),
4436
+ ...literalCommands(defaults.flatMap((found) => found.literals)),
4437
+ ...literalCommands(shell.interpolations.flatMap((found) => found.literals))
3651
4438
  ];
3652
4439
  const locate = (offset) => `${file}:${lineOf(offset)}`;
3653
4440
  const { edits, changes, manualSteps } = commandWordFindings(commands, locate, variables);
3654
- return { text: applyTextEdits(text, edits), changes, manualSteps };
4441
+ const steps = [...manualSteps, ...shebangSteps(text, scripts, locate)];
4442
+ return { text: applyTextEdits(text, edits), changes, manualSteps: steps };
3655
4443
  }
3656
4444
 
3657
4445
  const ASSIGNMENT_RE = new RegExp(
@@ -3733,6 +4521,8 @@ function withoutAssignmentHeads(text, assignments) {
3733
4521
  parts.push(text.slice(copiedUpTo));
3734
4522
  return parts.join("");
3735
4523
  }
4524
+ const COMPUTED_RE = /^\$[({]/;
4525
+ const isComputed = (written) => COMPUTED_RE.test(written.trim().replace(RECIPE_PREFIX_RE, ""));
3736
4526
  function referencedName(written) {
3737
4527
  const match = VARIABLE_REFERENCE_RE.exec(written.trim().replace(RECIPE_PREFIX_RE, ""));
3738
4528
  return match === null ? void 0 : match[1] ?? match[2] ?? match[3];
@@ -3764,10 +4554,12 @@ function makefileVariables(text, assignments, programs) {
3764
4554
  const nameAt = (word) => referencedName(text.slice(word.start, word.end));
3765
4555
  const variables = {
3766
4556
  programAt: (program) => {
3767
- const name = nameAt(program);
3768
- const found = name === void 0 ? void 0 : programs.get(name);
3769
4557
  const written = text.slice(program.start, program.end);
3770
- return found === void 0 ? void 0 : { written, program: found };
4558
+ const name = nameAt(program);
4559
+ if (name === void 0) {
4560
+ return isComputed(written) ? { written, program: "generator-or-other" } : void 0;
4561
+ }
4562
+ return { written, program: programs.get(name) ?? "generator-or-other" };
3771
4563
  },
3772
4564
  referenceAt: (word) => {
3773
4565
  const name = nameAt(word);
@@ -4129,29 +4921,6 @@ function oldPackagePatchSteps(fileName) {
4129
4921
  return name === void 0 || to === void 0 ? [] : [{ kind: "old-package-patch", name, to }];
4130
4922
  }
4131
4923
 
4132
- const STEP_SHELL_KEY = "shell";
4133
- const COMMAND_LINE_SHELLS = /* @__PURE__ */ new Set([
4134
- ...["sh", "bash", "dash", "ash", "zsh", "ksh"],
4135
- ...["pwsh", "powershell", "cmd"]
4136
- ]);
4137
- const PATH_SEPARATOR_RE = /[/\\]/;
4138
- const WHITESPACE_RE = /\s+/;
4139
- const shellProgram = (shell) => shell.trim().split(WHITESPACE_RE)[0]?.split(PATH_SEPARATOR_RE).at(-1) ?? "";
4140
- function stepShells(strings) {
4141
- const stepOf = (path) => JSON.stringify(path.slice(0, -1));
4142
- const others = new Map(
4143
- strings.filter((value) => value.path.at(-1) === STEP_SHELL_KEY).filter((value) => !COMMAND_LINE_SHELLS.has(shellProgram(value.value))).map((value) => [stepOf(value.path), value.value])
4144
- );
4145
- return (value) => others.get(stepOf(value.path));
4146
- }
4147
- function otherShellFindings(values, shellOf, locate) {
4148
- const manualSteps = values.flatMap((value) => {
4149
- const shell = shellOf(value);
4150
- return shell === void 0 || !namesOriginalPackage(value.value) ? [] : [{ kind: "script-not-shell", location: locate(value.start), shell }];
4151
- });
4152
- return { edits: [], changes: [], manualSteps };
4153
- }
4154
-
4155
4924
  const YAML_COMMAND_KEYS = /* @__PURE__ */ new Set([
4156
4925
  "run",
4157
4926
  "script",
@@ -4176,12 +4945,12 @@ const TASKFILE_COMMAND_KEYS = /* @__PURE__ */ new Set([
4176
4945
  "status"
4177
4946
  ]);
4178
4947
 
4179
- const API_DESCRIPTION_KEYS = /* @__PURE__ */ new Set(["openapi", "swagger", "asyncapi"]);
4180
- const isApiDescription = (documents) => documents.some(
4181
- (root) => isMap(root) && textEntries(root).some(({ key }) => API_DESCRIPTION_KEYS.has(key.value))
4182
- );
4183
4948
  const ENVIRONMENT_KEYS = /* @__PURE__ */ new Set(["env"]);
4184
4949
  const isEnvironmentValue = (value) => value.path.length >= 2 && ENVIRONMENT_KEYS.has(String(value.path.at(-2)));
4950
+ const INPUTS_KEY = "inputs";
4951
+ const DEFAULT_KEY = "default";
4952
+ const INPUT_DEFAULT_DEPTH = 3;
4953
+ const isInputDefault = (value) => value.path.length >= INPUT_DEFAULT_DEPTH && value.path.at(-1) === DEFAULT_KEY && value.path.at(-INPUT_DEFAULT_DEPTH) === INPUTS_KEY;
4185
4954
  function environmentVariables(text, strings) {
4186
4955
  return shellVariables(
4187
4956
  strings.filter(isEnvironmentValue).map((value) => ({
@@ -4265,7 +5034,7 @@ function migrateYamlCommands(text, file, strings, keys = YAML_COMMAND_KEYS) {
4265
5034
  const values = valuesOf(strings, keys).filter((value) => shellOf(value) === void 0);
4266
5035
  const commandValues = new Set(values.filter((value) => !isExecFormItem(value)));
4267
5036
  const shellValues = strings.filter(
4268
- (value) => commandValues.has(value) || isEnvironmentValue(value)
5037
+ (value) => commandValues.has(value) || isEnvironmentValue(value) || isInputDefault(value)
4269
5038
  );
4270
5039
  const variables = environmentVariables(text, strings);
4271
5040
  return appliedFindings(text, [
@@ -4489,6 +5258,28 @@ const JSON5_MANIFEST_FILE = "package.json5";
4489
5258
  const MANIFEST_FILE = "package.json";
4490
5259
  const PROJECT_CONFIG_NAME = /^[jt]sconfig(\.[^/]+)?\.json$/;
4491
5260
  const LOCK_FILES = /* @__PURE__ */ new Set(["pnpm-lock.yaml", "yarn.lock", "bun.lock"]);
5261
+ const CI_CONFIG_FILES = /* @__PURE__ */ new Set([
5262
+ ".gitlab-ci.yml",
5263
+ ".gitlab-ci.yaml",
5264
+ "bitbucket-pipelines.yml",
5265
+ "azure-pipelines.yml",
5266
+ ".drone.yml",
5267
+ ".woodpecker.yml",
5268
+ ".travis.yml"
5269
+ ]);
5270
+ const CI_CONFIG_FOLDERS = [
5271
+ ".github/workflows/",
5272
+ ".gitea/workflows/",
5273
+ ".forgejo/workflows/",
5274
+ ".circleci/",
5275
+ ".gitlab/",
5276
+ ".woodpecker/"
5277
+ ];
5278
+ const FOLDER_SEPARATOR = "/";
5279
+ function isCiConfigFile(path) {
5280
+ const inFolder = (folder) => path.startsWith(folder) || path.includes(`${FOLDER_SEPARATOR}${folder}`);
5281
+ return CI_CONFIG_FILES.has(baseName(path)) || CI_CONFIG_FOLDERS.some(inFolder);
5282
+ }
4492
5283
  function baseName(path) {
4493
5284
  return path.slice(path.lastIndexOf("/") + 1);
4494
5285
  }
@@ -4589,7 +5380,7 @@ const COMMAND_CONFIG_KINDS = [
4589
5380
  )
4590
5381
  }
4591
5382
  ];
4592
- const yamlCommandsStep = (path) => (text, read) => read.kind === "read" && isApiDescription(read.documents) ? unchanged(text) : stringsStep((current, strings) => migrateYamlCommands(current, path, strings))(text, read);
5383
+ const yamlCommandsStep = (path, isPart) => (text, read) => isPart || !isCiConfigFile(path) && read.kind === "read" && isApiDescription(read.documents) ? unchanged(text) : stringsStep((current, strings) => migrateYamlCommands(current, path, strings))(text, read);
4593
5384
  const manifestTarget = ({ version, catalogs, aliases }, path) => ({
4594
5385
  version,
4595
5386
  catalogs,
@@ -4665,7 +5456,11 @@ const FILE_KINDS = [
4665
5456
  },
4666
5457
  {
4667
5458
  matches: (path) => YAML_EXTENSIONS.has(extensionOf(path)),
4668
- migrate: whenNamed(yamlConfig((_context, path) => [yamlCommandsStep(path)]))
5459
+ migrate: whenNamed(
5460
+ yamlConfig((context, path) => [
5461
+ yamlCommandsStep(path, context.apiDescriptionParts.has(path))
5462
+ ])
5463
+ )
4669
5464
  },
4670
5465
  {
4671
5466
  matches: (path) => DOCKERFILE_NAMES.has(baseName(path)),
@@ -4694,6 +5489,58 @@ function fileMigrationFor(path, namedImportMaps = NO_NAMED_IMPORT_MAPS) {
4694
5489
  return FILE_KINDS.find((kind) => kind.matches(path))?.migrate;
4695
5490
  }
4696
5491
 
5492
+ const NO_API_DESCRIPTION_PARTS = /* @__PURE__ */ new Set();
5493
+ const JSON_EXTENSION = ".json";
5494
+ const isYaml = (path) => YAML_EXTENSIONS.has(extensionOf(path));
5495
+ const isDescriptionFormat = (path) => (isYaml(path) || extensionOf(path) === JSON_EXTENSION) && !isCiConfigFile(path);
5496
+ async function textOf(path, files) {
5497
+ const read = await files.read(path);
5498
+ return read.kind === "read" ? read.text : void 0;
5499
+ }
5500
+ function documentsOf(path, text, readers) {
5501
+ const parsed = (isYaml(path) ? readers.yaml : readers.json).read(text);
5502
+ return parsed.kind === "read" ? [...parsed.documents] : void 0;
5503
+ }
5504
+ async function hasNamingYaml(paths, files) {
5505
+ for (const path of paths.filter(isYaml)) {
5506
+ const text = await textOf(path, files);
5507
+ if (text !== void 0 && namesOriginalPackage(text)) {
5508
+ return true;
5509
+ }
5510
+ }
5511
+ return false;
5512
+ }
5513
+ async function descriptionRoots(paths, { files, readers }) {
5514
+ const roots = /* @__PURE__ */ new Map();
5515
+ for (const path of paths.filter(isDescriptionFormat)) {
5516
+ const text = await textOf(path, files);
5517
+ const documents = text !== void 0 && mayBeApiDescription(text) ? documentsOf(path, text, readers) : void 0;
5518
+ if (documents !== void 0 && isApiDescription(documents)) {
5519
+ roots.set(path, documents);
5520
+ }
5521
+ }
5522
+ return roots;
5523
+ }
5524
+ async function apiDescriptionParts(paths, dependencies) {
5525
+ if (!await hasNamingYaml(paths, dependencies.files)) {
5526
+ return NO_API_DESCRIPTION_PARTS;
5527
+ }
5528
+ const listed = new Set(paths.filter(isDescriptionFormat));
5529
+ const roots = await descriptionRoots(paths, dependencies);
5530
+ const parts = /* @__PURE__ */ new Set();
5531
+ const pending = [...roots].flatMap(([path, documents]) => referencedFiles(path, documents));
5532
+ for (let path = pending.pop(); path !== void 0; path = pending.pop()) {
5533
+ if (!listed.has(path) || roots.has(path) || parts.has(path)) {
5534
+ continue;
5535
+ }
5536
+ parts.add(path);
5537
+ const text = await textOf(path, dependencies.files);
5538
+ const documents = text === void 0 ? void 0 : documentsOf(path, text, dependencies.readers);
5539
+ pending.push(...documents === void 0 ? [] : referencedFiles(path, documents));
5540
+ }
5541
+ return parts;
5542
+ }
5543
+
4697
5544
  const readJson = (text, readers) => readers.json.read(text);
4698
5545
  const readYaml = (text, readers) => readers.yaml.read(text);
4699
5546
  function planSourceOf(path, namedImportMaps) {
@@ -4774,9 +5621,23 @@ async function planProjectTarget(paths, dependencies, version) {
4774
5621
  isWorkspaceRoot
4775
5622
  });
4776
5623
  const declarations = withNamedImportMaps(manifests, importMaps);
4777
- return { version, catalogs, manifests: declarations, aliases, importMaps };
5624
+ const parts = await apiDescriptionParts(paths, dependencies);
5625
+ return {
5626
+ ...{ version, catalogs, manifests: declarations, aliases, importMaps },
5627
+ apiDescriptionParts: parts
5628
+ };
4778
5629
  }
4779
5630
 
5631
+ function attemptMigration(migrate) {
5632
+ try {
5633
+ return { kind: "migrated", migration: migrate() };
5634
+ } catch (error) {
5635
+ if (error instanceof MigrationBugError) {
5636
+ return { kind: "skipped", reason: { kind: "migration-bug", message: error.message } };
5637
+ }
5638
+ throw error;
5639
+ }
5640
+ }
4780
5641
  async function planFile(path, dependencies, target) {
4781
5642
  const migrate = fileMigrationFor(path, target.importMaps);
4782
5643
  if (migrate === void 0) {
@@ -4787,12 +5648,16 @@ async function planFile(path, dependencies, target) {
4787
5648
  return read;
4788
5649
  }
4789
5650
  const { rewriter, scanner, readers } = dependencies;
4790
- const { version, catalogs, manifests, aliases } = target;
5651
+ const { version, catalogs, manifests, aliases, apiDescriptionParts } = target;
4791
5652
  const context = {
4792
- ...{ version, catalogs, manifests, aliases },
5653
+ ...{ version, catalogs, manifests, aliases, apiDescriptionParts },
4793
5654
  ...{ rewriter, scanner, readers }
4794
5655
  };
4795
- const migration = migrate(read.text, path, context);
5656
+ const attempt = attemptMigration(() => migrate(read.text, path, context));
5657
+ if (attempt.kind === "skipped") {
5658
+ return attempt;
5659
+ }
5660
+ const { migration } = attempt;
4796
5661
  const isChanged = migration.text !== read.text;
4797
5662
  const { changes, manualSteps } = migration;
4798
5663
  if (isChanged || manualSteps.length > 0) {
@@ -4879,7 +5744,7 @@ function terminalSafeText(text) {
4879
5744
  const PINNED_NOTE = "change it to openapi-typesafe@<version>";
4880
5745
  const OLD_FILE_NOTE$1 = "is a file of openapi-typescript, which is no longer installed: run openapi-typesafe instead";
4881
5746
  const FLAGS_IN_VARIABLE_NOTE = "holds old flags of the generator and may serve other commands, so it was kept: rename";
4882
- const NOT_ONLY_GENERATOR_NOTE = "runs openapi-typescript in some of its definitions in this file and another program in others, so the flags after it were kept; where it runs the generator, rename";
5747
+ const NOT_ONLY_GENERATOR_NOTE = "may run openapi-typescript or another program (set elsewhere in the file, in another step or outside it), so the flags after it were kept; where it runs the generator, rename";
4883
5748
  const OLD_NAME_IN_COMMAND_NOTE = "names an old package where the tool cannot tell what runs it (a script name, an argument of a program it does not know), so it was kept: migrate it by hand";
4884
5749
  const SCRIPT_NOT_SHELL_NOTE = "so the tool does not read it as commands: migrate the old package names in it by hand";
4885
5750
  const COMMAND_STRING_NOTE = "a string runs openapi-typescript, so it was kept; change it to";
@@ -4920,7 +5785,7 @@ function commandStepMessage(step) {
4920
5785
  case "old-name-in-command":
4921
5786
  return `${step.location}: "${step.word}" ${OLD_NAME_IN_COMMAND_NOTE}`;
4922
5787
  case "script-not-shell":
4923
- return `${step.location}: the step runs with shell: ${step.shell}, ${SCRIPT_NOT_SHELL_NOTE}`;
5788
+ return `${step.location}: the script runs with ${step.shell}, ${SCRIPT_NOT_SHELL_NOTE}`;
4924
5789
  case "command-string":
4925
5790
  return `line ${step.line}: ${COMMAND_STRING_NOTE} "${step.to}"`;
4926
5791
  default:
@@ -4936,9 +5801,12 @@ const KEEP_REASONS = {
4936
5801
  "shared-options": "its options object is used elsewhere too",
4937
5802
  "unknown-return": "a return value is not a plain ts.factory type",
4938
5803
  "typed-options": "its options are declared with a type built from OpenAPITSOptions, which the tool cannot change to OpenAPITSIrOptions",
4939
- "spread-options": "its options take keys from another object (`...base`), whose callbacks and type the tool cannot see"
5804
+ "spread-options": "its options take keys from another object (`...base`), whose callbacks and type the tool cannot see",
5805
+ "declared-type": "its declared type (a `const` type, JSDoc `@type` or `@returns`) names a type the tool cannot change to type text",
5806
+ "in-spread": "it is in an object that the options take keys from (`...base`), and the tool rewrites only a transform written in the options"
4940
5807
  };
4941
5808
  const OPTIONS_NOTE = `the options of openapiTS() are not an object written in this file: check them for transform, postTransform and transformProperty. See ${IR_CALLBACKS_GUIDE}`;
5809
+ const SPREAD_NOTE = `the options of openapiTS() take keys from an object the tool cannot read (\`...base\`): check it for transform, postTransform and transformProperty. See ${IR_CALLBACKS_GUIDE}`;
4942
5810
  const DEEP_IMPORT_NOTE = "imports a file inside the package; check that it exists";
4943
5811
  const OLD_FILE_NOTE = "imports a file from node_modules of";
4944
5812
  const OLD_FILE_FIX_NOTE = "which is no longer installed: import from";
@@ -4954,6 +5822,7 @@ function packageNotDeclaredMessage(step) {
4954
5822
  }
4955
5823
  const SOURCE_STEP_KINDS = /* @__PURE__ */ new Set([
4956
5824
  "options-not-inspected",
5825
+ "options-spread-not-inspected",
4957
5826
  "deep-import",
4958
5827
  "old-package-import",
4959
5828
  "dynamic-specifier",
@@ -4967,6 +5836,8 @@ function sourceStepMessage(step) {
4967
5836
  switch (step.kind) {
4968
5837
  case "options-not-inspected":
4969
5838
  return `line ${step.line}: ${OPTIONS_NOTE}`;
5839
+ case "options-spread-not-inspected":
5840
+ return `line ${step.line}: ${SPREAD_NOTE}`;
4970
5841
  case "deep-import":
4971
5842
  return `line ${step.line}: "${step.specifier}" ${DEEP_IMPORT_NOTE}`;
4972
5843
  case "old-package-import":
@@ -5121,7 +5992,7 @@ function changeMessage(change) {
5121
5992
  return assertNever(change);
5122
5993
  }
5123
5994
  }
5124
- const SKIPPED_HEADING = "Skipped (not read, not written):";
5995
+ const SKIPPED_HEADING = "Skipped (not migrated, not written):";
5125
5996
  function skippedMessage(reason) {
5126
5997
  switch (reason.kind) {
5127
5998
  case "outside-root":
@@ -5130,6 +6001,8 @@ function skippedMessage(reason) {
5130
6001
  return "it is not UTF-8 text, so it was not changed; check it by hand";
5131
6002
  case "io-error":
5132
6003
  return `it could not be read (${reason.code})`;
6004
+ case "migration-bug":
6005
+ return `the tool could not migrate it (${reason.message}), a bug of the tool: it was not changed, so migrate it by hand, and please report the bug`;
5133
6006
  default:
5134
6007
  return assertNever(reason);
5135
6008
  }
@@ -5316,7 +6189,8 @@ async function migrateRoot(request, root, logger, dependencies) {
5316
6189
  const input = { version: dependencies.version, isWrite: request.isWrite };
5317
6190
  const migration = await migrateProject({ files, rewriter, scanner, readers, logger }, input);
5318
6191
  dependencies.writeStdout(renderReport(migration));
5319
- return migration.write.kind === "failed" ? EXIT_CODES.failure : EXIT_CODES.success;
6192
+ const hasBug = migration.skipped.some((file) => file.reason.kind === "migration-bug");
6193
+ return migration.write.kind === "failed" || hasBug ? EXIT_CODES.failure : EXIT_CODES.success;
5320
6194
  }
5321
6195
  async function runMigration(request, dependencies) {
5322
6196
  const log = openLog(request, dependencies);