openapi-typesafe-migrate 1.0.0-rc.5 → 1.0.0-rc.7

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
@@ -560,6 +560,21 @@ async function inspectProjectRoot(root) {
560
560
  }
561
561
  }
562
562
 
563
+ const BROKEN_PIPE = "EPIPE";
564
+ function isBrokenPipe(error) {
565
+ return error.code === BROKEN_PIPE;
566
+ }
567
+ function createPipeSafeWriter(stream) {
568
+ stream.on("error", (error) => {
569
+ if (!isBrokenPipe(error)) {
570
+ throw error;
571
+ }
572
+ });
573
+ return (text) => {
574
+ stream.write(text);
575
+ };
576
+ }
577
+
563
578
  function unchanged(text) {
564
579
  return { text, changes: [], manualSteps: [] };
565
580
  }
@@ -610,6 +625,7 @@ function applyTextEdits(text, edits) {
610
625
  return edited + text.slice(copiedUpTo);
611
626
  }
612
627
 
628
+ const hasExportModifier = (declaration) => (ts.getCombinedModifierFlags(declaration) & ts.ModifierFlags.Export) !== 0;
613
629
  const GENERATOR_MODULES = /* @__PURE__ */ new Set([
614
630
  "openapi-typesafe",
615
631
  "openapi-typescript"
@@ -692,7 +708,13 @@ function createChecker(file) {
692
708
  }
693
709
  function symbolOf(checker, identifier) {
694
710
  const parent = identifier.parent;
695
- return ts.isShorthandPropertyAssignment(parent) && parent.name === identifier ? checker.getShorthandAssignmentValueSymbol(parent) : checker.getSymbolAtLocation(identifier);
711
+ if (ts.isShorthandPropertyAssignment(parent) && parent.name === identifier) {
712
+ return checker.getShorthandAssignmentValueSymbol(parent);
713
+ }
714
+ if (ts.isExportSpecifier(parent)) {
715
+ return checker.getExportSpecifierLocalTargetSymbol(parent);
716
+ }
717
+ return checker.getSymbolAtLocation(identifier);
696
718
  }
697
719
  function isDeclaredName(identifier, declaration) {
698
720
  const declaredName = declaration.name;
@@ -715,7 +737,9 @@ function indexUses(file, bindings) {
715
737
  if (declaration !== void 0 && !isDeclaredName(node, declaration)) {
716
738
  uses.set(declaration, [...uses.get(declaration) ?? [], node]);
717
739
  }
718
- ts.forEachChild(node, visit);
740
+ for (const child of node.getChildren(file)) {
741
+ visit(child);
742
+ }
719
743
  };
720
744
  visit(file);
721
745
  return uses;
@@ -778,7 +802,6 @@ function unwrapped(expression, types = []) {
778
802
  }
779
803
  return { value: expression, types };
780
804
  }
781
- const isExported$1 = (declaration) => (ts.getCombinedModifierFlags(declaration) & ts.ModifierFlags.Export) !== 0;
782
805
  function constantOptions(name, bindings, outerTypes) {
783
806
  const binding = bindings.bindingOf(name);
784
807
  const declaration = bindings.declarationOf(name);
@@ -786,23 +809,28 @@ function constantOptions(name, bindings, outerTypes) {
786
809
  if (found === void 0 || !ts.isObjectLiteralExpression(found.value)) {
787
810
  return { kind: "unknown", expression: name };
788
811
  }
789
- const isOwn = bindings.usesOf(declaration).length === 1 && !isExported$1(declaration);
812
+ const isOwn = bindings.usesOf(declaration).length === 1 && !hasExportModifier(declaration);
790
813
  if (!isOwn) {
791
814
  return { kind: "shared", options: found.value };
792
815
  }
793
816
  const declared = declaration.type;
794
- const annotations = [
795
- ...outerTypes,
817
+ const ownTypes = [
796
818
  ...found.types,
797
819
  ...declared === void 0 ? [] : [declared],
798
820
  ...jsDocTypes(declaration)
799
821
  ];
800
- return { kind: "own", options: found.value, annotations };
822
+ const annotations = [...outerTypes, ...ownTypes];
823
+ return {
824
+ kind: "own",
825
+ options: found.value,
826
+ annotations,
827
+ isContextuallyTyped: ownTypes.length > 0
828
+ };
801
829
  }
802
830
  function optionsOf(argument, bindings) {
803
831
  const { value, types } = unwrapped(argument);
804
832
  if (ts.isObjectLiteralExpression(value)) {
805
- return { kind: "own", options: value, annotations: types };
833
+ return { kind: "own", options: value, annotations: types, isContextuallyTyped: true };
806
834
  }
807
835
  return ts.isIdentifier(value) ? constantOptions(value, bindings, types) : { kind: "unknown", expression: argument };
808
836
  }
@@ -1086,7 +1114,7 @@ function callbackAnnotations(property, names) {
1086
1114
  function isSharedFunction(property, names) {
1087
1115
  const name = namedValue(property);
1088
1116
  const declaration = name === void 0 ? void 0 : names.bindings.declarationOf(name);
1089
- return declaration !== void 0 && names.bindings.usesOf(declaration).length > 1;
1117
+ return declaration !== void 0 && (names.bindings.usesOf(declaration).length > 1 || hasExportModifier(declaration));
1090
1118
  }
1091
1119
 
1092
1120
  const KEYWORDS = /* @__PURE__ */ new Map([
@@ -1305,7 +1333,6 @@ function returnEdits(expression, names) {
1305
1333
  return typeTextEdit(expression, names);
1306
1334
  }
1307
1335
 
1308
- const isExported = (node) => (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) !== 0;
1309
1336
  function isRemovable(node, binding, names) {
1310
1337
  switch (binding.kind) {
1311
1338
  case "typescript-module":
@@ -1313,7 +1340,7 @@ function isRemovable(node, binding, names) {
1313
1340
  case "helper-import":
1314
1341
  return true;
1315
1342
  case "constant":
1316
- return !isExported(node) && typeTextOf(binding.initializer, names) !== void 0;
1343
+ return !hasExportModifier(node) && typeTextOf(binding.initializer, names) !== void 0;
1317
1344
  default:
1318
1345
  return false;
1319
1346
  }
@@ -1414,6 +1441,9 @@ function removalEdits(removed, text) {
1414
1441
  const TRANSFORM = "transform";
1415
1442
  const LEGACY_ONLY_CALLBACKS = ["postTransform", "transformProperty"];
1416
1443
  const IR_CALLBACKS_OPTION = "irCallbacks";
1444
+ const IR_CALLBACKS_TRUE = "true";
1445
+ const IR_CALLBACKS_TRUE_CONST = "true as const";
1446
+ const IR_CALLBACKS_TRUE_CONST_JS = "/** @type {const} */ (true)";
1417
1447
  const noFindings = () => ({
1418
1448
  edits: [],
1419
1449
  changes: [],
@@ -1452,10 +1482,17 @@ function transformEdits(property, names, text) {
1452
1482
  }
1453
1483
  return edits;
1454
1484
  }
1455
- function irCallbacksOption(text, transformStart) {
1485
+ function irCallbacksValue(options) {
1486
+ if (options.isContextuallyTyped) {
1487
+ return IR_CALLBACKS_TRUE;
1488
+ }
1489
+ const isJavaScript = (options.options.flags & ts.NodeFlags.JavaScriptFile) !== 0;
1490
+ return isJavaScript ? IR_CALLBACKS_TRUE_CONST_JS : IR_CALLBACKS_TRUE_CONST;
1491
+ }
1492
+ function irCallbacksOption(text, transformStart, value) {
1456
1493
  const lineStart = text.lastIndexOf("\n", transformStart - 1) + 1;
1457
1494
  const before = text.slice(lineStart, transformStart);
1458
- const option = `${IR_CALLBACKS_OPTION}: true,`;
1495
+ const option = `${IR_CALLBACKS_OPTION}: ${value},`;
1459
1496
  return /^\s*$/.test(before) ? `${option}
1460
1497
  ${before}` : `${option} `;
1461
1498
  }
@@ -1498,7 +1535,9 @@ function migrateOptions(options, text, names) {
1498
1535
  findings.manualSteps.push({ kind: "transform-not-rewritten", line, reason: result });
1499
1536
  return findings;
1500
1537
  }
1501
- findings.edits.push({ start, end: start, text: irCallbacksOption(text, start) }, ...result.edits);
1538
+ const at = transform.getStart(void 0, true);
1539
+ const option = irCallbacksOption(text, at, irCallbacksValue(options));
1540
+ findings.edits.push({ start: at, end: at, text: option }, ...result.edits);
1502
1541
  findings.typeReferences.push(...result.typeReferences);
1503
1542
  findings.changes.push({ kind: "transform-rewritten", line });
1504
1543
  return findings;
@@ -1867,30 +1906,6 @@ function createYamlTextReader() {
1867
1906
  return { read };
1868
1907
  }
1869
1908
 
1870
- const CATALOG_PROTOCOL = "catalog:";
1871
- const DEFAULT_CATALOG = "default";
1872
- function catalogOfSpec(spec) {
1873
- if (!spec.startsWith(CATALOG_PROTOCOL)) {
1874
- return void 0;
1875
- }
1876
- return spec.slice(CATALOG_PROTOCOL.length).trim() || DEFAULT_CATALOG;
1877
- }
1878
- class CatalogEntries {
1879
- #keys = /* @__PURE__ */ new Set();
1880
- add(catalog, name) {
1881
- this.#keys.add(JSON.stringify([catalog, name]));
1882
- }
1883
- has(catalog, name) {
1884
- return this.#keys.has(JSON.stringify([catalog, name]));
1885
- }
1886
- }
1887
- const NO_CATALOG_PLAN = {
1888
- inUse: new CatalogEntries(),
1889
- kept: new CatalogEntries(),
1890
- defined: new CatalogEntries(),
1891
- isWorkspaceRoot: false
1892
- };
1893
-
1894
1909
  const PACKAGE_RENAMES = /* @__PURE__ */ new Map([
1895
1910
  ["openapi-typescript", "openapi-typesafe"],
1896
1911
  ["openapi-fetch", "openapi-typesafe-fetch"],
@@ -1986,477 +2001,69 @@ function unreadableConfig(text, offset) {
1986
2001
  return { ...unchanged(text), manualSteps: [{ kind: "config-unreadable", position }] };
1987
2002
  }
1988
2003
 
1989
- const PARENT_SEPARATOR = ">";
1990
- const PATH_SEPARATOR$8 = "/";
1991
- const SCOPE_PREFIX$2 = "@";
1992
- function overrideTarget(key) {
1993
- const child = key.split(PARENT_SEPARATOR).at(-1) ?? key;
1994
- const segments = child.split(PATH_SEPARATOR$8);
1995
- const isScoped = segments.length > 1 && segments.at(-2)?.startsWith(SCOPE_PREFIX$2) === true;
1996
- const name = isScoped ? segments.slice(-2).join(PATH_SEPARATOR$8) : segments.at(-1) ?? child;
1997
- const versionAt = name.indexOf(SCOPE_PREFIX$2, 1);
1998
- return versionAt === -1 ? name : name.slice(0, versionAt);
1999
- }
2000
- function overriddenPackages(keys) {
2001
- const targets = keys.map(overrideTarget);
2002
- return [...new Set(targets.filter((target) => PACKAGE_RENAMES.has(target)))];
2003
- }
2004
-
2005
- const isEditable = (node) => node.kind === "scalar" && node.encode !== void 0;
2006
- const replaceScalar = (scalar, value) => ({
2007
- start: scalar.start,
2008
- end: scalar.end,
2009
- text: scalar.encode(value)
2010
- });
2011
- function specEntries(map) {
2012
- return textEntries(map).flatMap(({ key, value }) => {
2013
- if (!isEditable(key)) {
2014
- return [];
2015
- }
2016
- const spec = isEditable(value) ? { value: value.value, replace: (to) => replaceScalar(value, to) } : void 0;
2017
- return [{ name: key.value, spec, renameKey: (name) => replaceScalar(key, name) }];
2018
- });
2019
- }
2020
- function keyEntries(map) {
2021
- return textEntries(map).map(({ key }) => key).filter(isEditable).map((key) => ({ name: key.value, rename: (name) => replaceScalar(key, name) }));
2022
- }
2023
- function itemEntries(list) {
2024
- return list.items.filter(isEditable).map((item) => ({ name: item.value, rename: (name) => replaceScalar(item, name) }));
2025
- }
2026
- function nestedKeys(node) {
2027
- if (node.kind !== "map") {
2028
- return [];
2029
- }
2030
- return textEntries(node).flatMap(({ key, value }) => [key.value, ...nestedKeys(value)]);
2031
- }
2032
-
2033
- const PATH_SEPARATOR$7 = ".";
2034
- function catalogMaps(root, prefix = "") {
2035
- const maps = mapsAt(root, CATALOG_FIELD).map((map) => ({
2036
- name: DEFAULT_CATALOG,
2037
- label: `${prefix}${CATALOG_FIELD}`,
2038
- map
2039
- }));
2040
- for (const catalogs of mapsAt(root, NAMED_CATALOGS_FIELD)) {
2041
- for (const { key, value } of textEntries(catalogs)) {
2042
- if (isMap(value)) {
2043
- const label = [prefix + NAMED_CATALOGS_FIELD, key.value].join(PATH_SEPARATOR$7);
2044
- maps.push({ name: key.value, label, map: value });
2045
- }
2046
- }
2047
- }
2048
- return maps;
2049
- }
2050
- const manifestCatalogMaps = (manifest) => [
2051
- ...catalogMaps(manifest),
2052
- ...mapsAt(manifest, WORKSPACES_FIELD).flatMap(
2053
- (workspaces) => catalogMaps(workspaces, `${WORKSPACES_FIELD}${PATH_SEPARATOR$7}`)
2054
- )
2055
- ];
2056
- function entriesInUse(manifests) {
2057
- const inUse = new CatalogEntries();
2058
- const fields = manifests.flatMap(
2059
- (manifest) => DEPENDENCY_FIELDS.flatMap((field) => mapsAt(manifest, field))
2060
- );
2061
- for (const dependencies of fields) {
2062
- const entries = specEntries(dependencies);
2063
- const names = new Set(entries.map((entry) => entry.name));
2064
- for (const { name, spec } of entries) {
2065
- const catalog = spec === void 0 ? void 0 : catalogOfSpec(spec.value);
2066
- const to = PACKAGE_RENAMES.get(name);
2067
- if (catalog !== void 0 && to !== void 0 && names.has(to)) {
2068
- inUse.add(catalog, name);
2004
+ const SEPARATOR = "/";
2005
+ const CURRENT_FOLDER = ".";
2006
+ const PARENT_FOLDER = "..";
2007
+ const URL_SCHEME = /^[a-z][a-z\d+.-]*:/i;
2008
+ function resolvedPath(fromPath, reference) {
2009
+ const parts = fromPath.split(SEPARATOR).slice(0, -1);
2010
+ for (const part of reference.split(SEPARATOR)) {
2011
+ if (part === PARENT_FOLDER) {
2012
+ if (parts.pop() === void 0) {
2013
+ return void 0;
2069
2014
  }
2015
+ } else if (part !== CURRENT_FOLDER && part !== "") {
2016
+ parts.push(part);
2070
2017
  }
2071
2018
  }
2072
- return inUse;
2073
- }
2074
- function nestedTexts(node) {
2075
- if (node.kind !== "map") {
2076
- return [];
2077
- }
2078
- return textEntries(node).flatMap(
2079
- ({ key, value }) => value.kind === "scalar" ? [{ key: key.value, value: value.value }] : nestedTexts(value)
2080
- );
2081
- }
2082
- function addOverrideUses(documents, inUse) {
2083
- const sections = documents.flatMap(
2084
- (document) => [...OVERRIDE_FIELDS, PNPM_SETTINGS_FIELD].flatMap((field) => valuesAt(document, field))
2085
- );
2086
- for (const { key, value } of sections.flatMap(nestedTexts)) {
2087
- const catalog = catalogOfSpec(value);
2088
- const name = overrideTarget(key);
2089
- if (catalog !== void 0 && PACKAGE_RENAMES.has(name)) {
2090
- inUse.add(catalog, name);
2091
- }
2092
- }
2019
+ return parts.join(SEPARATOR);
2093
2020
  }
2094
- function planCatalogs({
2095
- manifests,
2096
- catalogFiles,
2097
- isWorkspaceRoot
2098
- }) {
2099
- const inUse = entriesInUse(manifests);
2100
- addOverrideUses([...manifests, ...catalogFiles], inUse);
2101
- const kept = new CatalogEntries();
2102
- const defined = new CatalogEntries();
2103
- const catalogs = [
2104
- ...catalogFiles.flatMap((file) => catalogMaps(file)),
2105
- ...manifests.flatMap(manifestCatalogMaps)
2106
- ];
2107
- for (const { name: catalog, map } of catalogs) {
2108
- const entries = specEntries(map);
2109
- const names = new Set(entries.map((entry) => entry.name));
2110
- for (const { name, spec } of entries) {
2111
- defined.add(catalog, name);
2112
- const to = PACKAGE_RENAMES.get(name);
2113
- const stays = spec === void 0 || inUse.has(catalog, name);
2114
- if (to !== void 0 && !names.has(to) && stays) {
2115
- kept.add(catalog, name);
2116
- }
2117
- }
2118
- }
2119
- return { inUse, kept, defined, isWorkspaceRoot };
2021
+ function referencedProjectPath(fromPath, reference) {
2022
+ const isOutside = URL_SCHEME.test(reference) || reference.startsWith(SEPARATOR);
2023
+ const path = isOutside ? void 0 : resolvedPath(fromPath, reference);
2024
+ return path === "" ? void 0 : path;
2120
2025
  }
2121
2026
 
2122
- const NO_LONGER_INSTALLED = /* @__PURE__ */ new Map([
2123
- ["openapi-typesafe-helpers", ["openapi-fetch", "openapi-react-query"]]
2124
- ]);
2125
- const ORIGINAL_NAMES = new Map(
2126
- [...PACKAGE_RENAMES].map(([original, successor]) => [successor, original])
2027
+ const API_DESCRIPTION_KEYS = [
2028
+ "openapi",
2029
+ "swagger",
2030
+ "asyncapi",
2031
+ "overlay",
2032
+ "arazzo"
2033
+ ];
2034
+ const API_DESCRIPTION_KEY_SET = new Set(API_DESCRIPTION_KEYS);
2035
+ const DESCRIPTION_KEY_LINE_RE = new RegExp(
2036
+ `^[ \\t{]*["']?(?:${API_DESCRIPTION_KEYS.join("|")})["']?[ \\t]*:`,
2037
+ "m"
2127
2038
  );
2128
- const PATH_SEPARATOR$6 = "/";
2129
- function namesUninstalledPackage(text) {
2130
- return [...NO_LONGER_INSTALLED.keys()].some(
2131
- (name) => text.includes(name) || text.includes(ORIGINAL_NAMES.get(name) ?? name)
2132
- );
2133
- }
2134
- const directoryOf = (path) => path.slice(0, path.lastIndexOf(PATH_SEPARATOR$6) + 1);
2135
- function declaredNames(manifest) {
2136
- return DEPENDENCY_FIELDS.flatMap((field) => mapsAt(manifest, field)).flatMap(textEntries).map(({ key }) => key.value);
2137
- }
2138
- function isDeclaredFor(manifests, filePath, name) {
2139
- const original = ORIGINAL_NAMES.get(name);
2140
- const containing = manifests.filter(({ path }) => filePath.startsWith(directoryOf(path)));
2141
- const declares = ({ names }) => names === void 0 || names.has(name) || original !== void 0 && names.has(original);
2142
- return containing.length === 0 || containing.some(declares);
2143
- }
2144
- const packageOfSpecifier = (specifier) => {
2145
- const slash = specifier.indexOf(PATH_SEPARATOR$6);
2146
- return slash === -1 ? specifier : specifier.slice(0, slash);
2147
- };
2148
- function undeclaredImportSteps(imports, filePath, manifests) {
2149
- return imports.flatMap(({ specifier, line }) => {
2150
- const name = packageOfSpecifier(specifier);
2151
- const installers = NO_LONGER_INSTALLED.get(name);
2152
- return installers === void 0 || isDeclaredFor(manifests, filePath, name) ? [] : [{ kind: "package-not-declared", line, name, installers }];
2039
+ const mayBeApiDescription = (text) => DESCRIPTION_KEY_LINE_RE.test(text);
2040
+ const isApiDescription = (documents) => documents.some(
2041
+ (root) => isMap(root) && textEntries(root).some(({ key }) => API_DESCRIPTION_KEY_SET.has(key.value))
2042
+ );
2043
+ const REF_KEY = "$ref";
2044
+ const FRAGMENT_START = "#";
2045
+ function referencedFiles(path, documents) {
2046
+ return documentStrings(documents).filter((string) => string.path.at(-1) === REF_KEY).flatMap(({ value }) => {
2047
+ const file = value.split(FRAGMENT_START)[0] ?? "";
2048
+ const referenced = referencedProjectPath(path, file);
2049
+ return referenced === void 0 ? [] : [referenced];
2153
2050
  });
2154
2051
  }
2155
2052
 
2156
- function assertNever(value) {
2157
- throw new Error(`Unexpected variant: ${JSON.stringify(value)}`);
2158
- }
2159
-
2160
- const KEPT_PROTOCOLS = ["catalog:", "workspace:"];
2161
- const RANGE_SEPARATOR = "||";
2162
- const HYPHEN = "-";
2163
- const WHITE_SPACE_RE = /\s+/;
2164
- const SPACED_OPERATOR_RE = /(<=|>=|<|>|=|~>|~|\^)\s+/g;
2165
- const OPERATOR = String.raw`(<=|>=|<|>|=|~>|~|\^)?`;
2166
- const VERSION_PART = String.raw`(\d+|[xX*])`;
2167
- const LABELS = String.raw`(?:-[\w.-]+)?(?:\+[\w.-]+)?`;
2168
- const COMPARATOR_RE = new RegExp(
2169
- String.raw`^${OPERATOR}v?${VERSION_PART}(?:\.${VERSION_PART})?(?:\.${VERSION_PART})?${LABELS}$`
2170
- );
2171
- const NUMBER_RE = /^\d+$/;
2172
- const OPERATOR_STYLES = {
2173
- "^": "^",
2174
- // `>=` means "this or newer"; with a new package line, a caret range says the same safely.
2175
- ">=": "^",
2176
- ">": "^",
2177
- "~": "~",
2178
- "~>": "~"
2179
- };
2180
- const isNumber = (part) => part !== void 0 && NUMBER_RE.test(part);
2181
- function versionStyle(major, minor, patch) {
2182
- if (!isNumber(major)) {
2183
- return void 0;
2184
- }
2185
- if (isNumber(minor) && isNumber(patch)) {
2186
- return "";
2187
- }
2188
- return isNumber(minor) ? "~" : "^";
2189
- }
2190
- function rangeStyle(spec) {
2191
- const sets = spec.replace(SPACED_OPERATOR_RE, "$1").split(RANGE_SEPARATOR);
2192
- const tokens = sets.map((set) => set.trim().split(WHITE_SPACE_RE));
2193
- const comparators = tokens.flat().filter((token) => token !== HYPHEN);
2194
- const matches = comparators.map((comparator) => COMPARATOR_RE.exec(comparator));
2195
- const first = matches[0];
2196
- if (first === void 0 || first === null || matches.includes(null)) {
2197
- return void 0;
2198
- }
2199
- const operator = first[1];
2200
- const isHyphenRange = tokens[0]?.[1] === HYPHEN;
2201
- if (operator !== void 0 && operator !== "=") {
2202
- return OPERATOR_STYLES[operator];
2203
- }
2204
- return isHyphenRange ? "^" : versionStyle(first[2], first[3], first[4]);
2205
- }
2206
- function migrateVersionSpec(spec, version) {
2207
- if (KEPT_PROTOCOLS.some((protocol) => spec.startsWith(protocol))) {
2208
- return { kind: "kept", spec, isReported: false };
2209
- }
2210
- const style = rangeStyle(spec);
2211
- if (style === void 0) {
2212
- return { kind: "kept", spec, isReported: true };
2213
- }
2214
- return { kind: "replaced", spec: `${style}${version}` };
2215
- }
2216
-
2217
- const NPM_PROTOCOL = "npm:";
2218
- const SCOPE_PREFIX$1 = "@";
2219
- const PATH_SEPARATOR$5 = "/";
2220
- const VERSION_SEPARATOR = "@";
2221
- function nameEnd(text) {
2222
- const searchFrom = text.startsWith(SCOPE_PREFIX$1) ? text.indexOf(PATH_SEPARATOR$5) + 1 : 0;
2223
- const ends = [VERSION_SEPARATOR, PATH_SEPARATOR$5].map((separator) => text.indexOf(separator, Math.max(searchFrom, 1))).filter((index) => index !== -1);
2224
- return ends.length === 0 ? text.length : Math.min(...ends);
2225
- }
2226
- function parseNpmSpec(spec) {
2227
- if (!spec.startsWith(NPM_PROTOCOL)) {
2228
- return void 0;
2229
- }
2230
- const body = spec.slice(NPM_PROTOCOL.length);
2231
- const lead = body.startsWith(PATH_SEPARATOR$5) ? PATH_SEPARATOR$5 : "";
2232
- const text = body.slice(lead.length);
2233
- const end = nameEnd(text);
2234
- const name = text.slice(0, end);
2235
- if (!text.startsWith(VERSION_SEPARATOR, end)) {
2236
- return { lead, name, range: void 0, rest: text.slice(end) };
2237
- }
2238
- const rangeEnd = text.indexOf(PATH_SEPARATOR$5, end);
2239
- const restStart = rangeEnd === -1 ? text.length : rangeEnd;
2240
- return { lead, name, range: text.slice(end + 1, restStart), rest: text.slice(restStart) };
2241
- }
2242
- function isOldPackageNpmSpec(spec) {
2243
- const parsed = parseNpmSpec(spec);
2244
- return parsed !== void 0 && PACKAGE_RENAMES.has(parsed.name);
2245
- }
2246
- function migrateNpmSpec(spec, version) {
2247
- const parsed = parseNpmSpec(spec);
2248
- const renamed = parsed === void 0 ? void 0 : PACKAGE_RENAMES.get(parsed.name);
2249
- if (parsed === void 0 || renamed === void 0) {
2250
- return void 0;
2251
- }
2252
- const head = `${NPM_PROTOCOL}${parsed.lead}${renamed}`;
2253
- const isDeep = parsed.rest !== "" && parsed.rest !== PATH_SEPARATOR$5;
2254
- if (parsed.range === void 0) {
2255
- return { spec: `${head}${parsed.rest}`, isReported: false, isDeep };
2256
- }
2257
- const decision = migrateVersionSpec(parsed.range, version);
2258
- const isReported = decision.kind === "kept" && decision.isReported;
2259
- const migrated = `${head}${VERSION_SEPARATOR}${decision.spec}${parsed.rest}`;
2260
- return { spec: migrated, isReported, isDeep };
2261
- }
2262
-
2263
- const SCHEME_SEPARATOR = "://";
2264
- const HIDDEN_USERINFO = "***";
2265
- const USERINFO_END = "@";
2266
- const AUTHORITY_END_RE = /[/?#"'`\s]/;
2267
- function scanAuthority(text, start) {
2268
- let userinfoEnd = -1;
2269
- let end = start;
2270
- for (; end < text.length && !AUTHORITY_END_RE.test(text.charAt(end)); end++) {
2271
- if (text.charAt(end) === USERINFO_END) {
2272
- userinfoEnd = end;
2273
- }
2274
- }
2275
- return { end, userinfoEnd };
2276
- }
2277
- function hideUrlCredentials(text) {
2278
- let hidden = "";
2279
- let copiedUpTo = 0;
2280
- for (let found = text.indexOf(SCHEME_SEPARATOR); found !== -1; ) {
2281
- const start = found + SCHEME_SEPARATOR.length;
2282
- const { end, userinfoEnd } = scanAuthority(text, start);
2283
- if (userinfoEnd !== -1) {
2284
- hidden += `${text.slice(copiedUpTo, start)}${HIDDEN_USERINFO}`;
2285
- copiedUpTo = userinfoEnd;
2286
- }
2287
- found = text.indexOf(SCHEME_SEPARATOR, end);
2288
- }
2289
- return `${hidden}${text.slice(copiedUpTo)}`;
2290
- }
2291
-
2292
- const newFindings = () => ({ edits: [], changes: [], manualSteps: [] });
2293
- const PEER_FIELD = "peerDependencies";
2294
- const mapLabel = (map) => map.kind === "catalog" ? map.catalog : map.field;
2295
- const renamedKey = (name, map) => map.kind === "imports" ? renamedSpecifier(name)?.to : PACKAGE_RENAMES.get(name);
2296
- function migrateSpec(spec, version) {
2297
- const npm = migrateNpmSpec(spec, version);
2298
- if (npm !== void 0) {
2299
- return { spec: npm.spec, isReported: npm.isReported };
2300
- }
2301
- const decision = migrateVersionSpec(spec, version);
2302
- return { spec: decision.spec, isReported: decision.kind === "kept" && decision.isReported };
2303
- }
2304
- function isFree({ from, to, field, names }, findings) {
2305
- if (names.has(to)) {
2306
- findings.manualSteps.push({ kind: "name-collision", field, from, to });
2307
- return false;
2308
- }
2309
- return true;
2310
- }
2311
- function migrateAlias(entry, { map, version, findings }) {
2312
- const migrated = entry.spec === void 0 ? void 0 : migrateNpmSpec(entry.spec.value, version);
2313
- if (entry.spec === void 0 || migrated === void 0) {
2314
- return;
2315
- }
2316
- findings.edits.push(entry.spec.replace(migrated.spec));
2317
- const spec = hideUrlCredentials(migrated.spec);
2318
- findings.changes.push({ kind: "alias-renamed", field: mapLabel(map), name: entry.name, spec });
2319
- if (migrated.isReported) {
2320
- findings.manualSteps.push({ kind: "spec-kept", name: entry.name, spec });
2321
- }
2322
- }
2323
- function renamedChange(map, from, to, spec) {
2324
- switch (map.kind) {
2325
- case "dependencies":
2326
- return { kind: "dependency-renamed", field: map.field, from, to, spec };
2327
- case "catalog":
2328
- return { kind: "catalog-entry-renamed", catalog: map.catalog, from, to, spec };
2329
- case "imports":
2330
- return { kind: "import-renamed", field: map.field, from, to, target: spec };
2331
- default:
2332
- return assertNever(map);
2333
- }
2334
- }
2335
- function migrateEntry(entry, spec, to, { map, version, findings }) {
2336
- const old = spec.value;
2337
- const migrated = migrateSpec(old, version);
2338
- findings.edits.push(entry.renameKey(to));
2339
- if (migrated.spec !== old) {
2340
- findings.edits.push(spec.replace(migrated.spec));
2341
- }
2342
- findings.changes.push(renamedChange(map, entry.name, to, hideUrlCredentials(migrated.spec)));
2343
- if (migrated.isReported) {
2344
- findings.manualSteps.push({ kind: "spec-kept", name: to, spec: hideUrlCredentials(old) });
2345
- }
2346
- if (map.kind === "dependencies" && map.field === PEER_FIELD) {
2347
- findings.manualSteps.push({ kind: "peer-dependency-renamed", name: to });
2348
- }
2349
- }
2350
- function catalogHold(entry, spec, { map, catalogs }) {
2351
- if (map.kind === "catalog") {
2352
- const isInUse = catalogs.inUse.has(map.name, entry.name);
2353
- return isInUse ? { kind: "catalog-entry-in-use", catalog: map.catalog, name: entry.name } : void 0;
2354
- }
2355
- const catalog = map.kind === "dependencies" ? catalogOfSpec(spec.value) : void 0;
2356
- if (catalog === void 0) {
2357
- return void 0;
2358
- }
2359
- const reference = { field: mapLabel(map), name: entry.name, catalog };
2360
- if (!catalogs.defined.has(catalog, entry.name)) {
2361
- const kind = catalogs.isWorkspaceRoot ? "catalog-entry-missing" : "catalog-entry-outside";
2362
- return { kind, ...reference };
2363
- }
2364
- return catalogs.kept.has(catalog, entry.name) ? { kind: "catalog-reference-kept", ...reference } : void 0;
2365
- }
2366
- function migrateSpecMap(entries, context) {
2367
- const names = new Set(entries.map((entry) => entry.name));
2368
- for (const entry of entries) {
2369
- const to = renamedKey(entry.name, context.map);
2370
- const field = mapLabel(context.map);
2371
- if (to === void 0) {
2372
- migrateAlias(entry, context);
2373
- continue;
2374
- }
2375
- if (!isFree({ from: entry.name, to, field, names }, context.findings)) {
2376
- continue;
2377
- }
2378
- const hold = entry.spec === void 0 ? void 0 : catalogHold(entry, entry.spec, context);
2379
- if (entry.spec === void 0) {
2380
- context.findings.manualSteps.push({ kind: "entry-not-edited", field, name: entry.name });
2381
- } else if (hold !== void 0) {
2382
- context.findings.manualSteps.push(hold);
2383
- } else {
2384
- migrateEntry(entry, entry.spec, to, context);
2385
- }
2386
- }
2387
- }
2388
- function renameNames(entries, field, findings) {
2389
- const names = new Set(entries.map((entry) => entry.name));
2390
- for (const entry of entries) {
2391
- const to = PACKAGE_RENAMES.get(entry.name);
2392
- if (to !== void 0 && isFree({ from: entry.name, to, field, names }, findings)) {
2393
- findings.edits.push(entry.rename(to));
2394
- findings.changes.push({
2395
- kind: "package-name-renamed",
2396
- location: field,
2397
- from: entry.name,
2398
- to
2399
- });
2400
- }
2401
- }
2402
- }
2403
-
2404
- const IMPORTS_FIELD$1 = "imports";
2405
- const SCOPES_FIELD = "scopes";
2406
- const FIELD_SEPARATOR$2 = ".";
2407
- function migrateImports(config, version, findings) {
2408
- for (const imports of mapsAt(config, IMPORTS_FIELD$1)) {
2409
- const map = { kind: "imports", field: IMPORTS_FIELD$1 };
2410
- migrateSpecMap(specEntries(imports), { map, version, findings, catalogs: NO_CATALOG_PLAN });
2411
- }
2412
- for (const scopes of mapsAt(config, SCOPES_FIELD)) {
2413
- for (const { key, value } of textEntries(scopes)) {
2414
- if (isMap(value)) {
2415
- const field = [SCOPES_FIELD, key.value].join(FIELD_SEPARATOR$2);
2416
- const map = { kind: "imports", field };
2417
- migrateSpecMap(specEntries(value), { map, version, findings, catalogs: NO_CATALOG_PLAN });
2418
- }
2419
- }
2420
- }
2421
- }
2422
- const PATH_SEPARATOR$4 = "/";
2423
- const SCOPE_PREFIX = "@";
2424
- function importMapKeys(config) {
2425
- const scoped = mapsAt(config, SCOPES_FIELD).flatMap(textEntries).flatMap(({ value }) => isMap(value) ? [value] : []);
2426
- return [...mapsAt(config, IMPORTS_FIELD$1), ...scoped].flatMap(textEntries).map(({ key }) => key.value);
2427
- }
2428
- function importMapNames(config) {
2429
- return importMapKeys(config).map((key) => {
2430
- const parts = key.split(PATH_SEPARATOR$4);
2431
- return key.startsWith(SCOPE_PREFIX) ? parts.slice(0, 2).join(PATH_SEPARATOR$4) : parts[0] ?? key;
2432
- });
2433
- }
2434
- function migrateImportMap(text, read, version) {
2435
- if (read.kind === "unreadable") {
2436
- return unreadableConfig(text, read.offset);
2437
- }
2438
- const findings = newFindings();
2439
- for (const config of read.documents.filter(isMap)) {
2440
- migrateImports(config, version, findings);
2441
- }
2442
- const { edits, changes, manualSteps } = findings;
2443
- return { text: applyTextEdits(text, edits), changes, manualSteps };
2444
- }
2445
-
2446
- const NONE = /* @__PURE__ */ new Set();
2447
- const PACKAGE_OPTIONS = /* @__PURE__ */ new Set(["-p", "--package"]);
2448
- const PACKAGE_MANAGER_OPTIONS = /* @__PURE__ */ new Set([
2449
- "--filter",
2450
- "-F",
2451
- "--dir",
2452
- "-C",
2453
- "--prefix",
2454
- "--cwd",
2455
- "--workspace"
2456
- ]);
2457
- const launcher = (words, packageOptions = NONE, valueOptions = NONE) => {
2458
- const allValueOptions = /* @__PURE__ */ new Set([...valueOptions, ...packageOptions]);
2459
- return { words, packageOptions, valueOptions: allValueOptions, operands: 0 };
2053
+ const NONE = /* @__PURE__ */ new Set();
2054
+ const PACKAGE_OPTIONS = /* @__PURE__ */ new Set(["-p", "--package"]);
2055
+ const PACKAGE_MANAGER_OPTIONS = /* @__PURE__ */ new Set([
2056
+ "--filter",
2057
+ "-F",
2058
+ "--dir",
2059
+ "-C",
2060
+ "--prefix",
2061
+ "--cwd",
2062
+ "--workspace"
2063
+ ]);
2064
+ const launcher = (words, packageOptions = NONE, valueOptions = NONE) => {
2065
+ const allValueOptions = /* @__PURE__ */ new Set([...valueOptions, ...packageOptions]);
2066
+ return { words, packageOptions, valueOptions: allValueOptions, operands: 0 };
2460
2067
  };
2461
2068
  const CONTAINER_OPTIONS = /* @__PURE__ */ new Set([
2462
2069
  "-e",
@@ -2576,12 +2183,182 @@ const INSTALLERS = [
2576
2183
  ];
2577
2184
  const NPM_SPECIFIER_PREFIX = "npm:";
2578
2185
 
2579
- const ASSIGNMENT_RE$1 = /^[A-Za-z_][A-Za-z0-9_]*=/;
2186
+ const COMMAND_SEPARATORS = /* @__PURE__ */ new Set([
2187
+ ";",
2188
+ "&",
2189
+ "|",
2190
+ "(",
2191
+ ")",
2192
+ "`",
2193
+ "\n"
2194
+ ]);
2195
+ const WORD_BREAKS = /* @__PURE__ */ new Set([" ", " ", "\r", "<", ">"]);
2196
+ const BACKSLASH$2 = "\\";
2197
+ const DOUBLE_QUOTE_ESCAPES = /* @__PURE__ */ new Set(["$", "`", '"', BACKSLASH$2, "\n"]);
2198
+ function singleQuoted(text, start) {
2199
+ const close = text.indexOf("'", start + 1);
2200
+ const end = close === -1 ? text.length : close + 1;
2201
+ return { end, value: text.slice(start + 1, close === -1 ? text.length : close) };
2202
+ }
2203
+ function doubleQuoted(text, start) {
2204
+ let value = "";
2205
+ let index = start + 1;
2206
+ while (index < text.length && text[index] !== '"') {
2207
+ const next = text[index + 1] ?? "";
2208
+ const isEscape = text[index] === BACKSLASH$2 && DOUBLE_QUOTE_ESCAPES.has(next);
2209
+ value += isEscape ? next === "\n" ? "" : next : text[index];
2210
+ index += isEscape ? 2 : 1;
2211
+ }
2212
+ return { end: Math.min(index + 1, text.length), value };
2213
+ }
2214
+ const isLineContinuation = (text, index) => text[index] === BACKSLASH$2 && (text[index + 1] === "\n" || text.startsWith("\r\n", index + 1));
2215
+ const ARITHMETIC_OPEN = "$((";
2216
+ const NESTING_OPEN = "(";
2217
+ const NESTING_CLOSE = ")";
2218
+ const ARITHMETIC_DEPTH = 2;
2219
+ function arithmeticExpansion(text, start) {
2220
+ let depth = ARITHMETIC_DEPTH;
2221
+ for (let index = start + ARITHMETIC_OPEN.length; index < text.length; index++) {
2222
+ depth += text[index] === NESTING_OPEN ? 1 : text[index] === NESTING_CLOSE ? -1 : 0;
2223
+ if (depth === 0) {
2224
+ return { end: index + 1, value: text.slice(start, index + 1) };
2225
+ }
2226
+ }
2227
+ return void 0;
2228
+ }
2229
+ const PARAMETER_OPEN = "${";
2230
+ const PARAMETER_CLOSE = "}";
2231
+ function parameterExpansionEnd(text, start) {
2232
+ let depth = 1;
2233
+ for (let index = start + PARAMETER_OPEN.length; index < text.length; ) {
2234
+ if (text.startsWith(PARAMETER_OPEN, index)) {
2235
+ depth += 1;
2236
+ index += PARAMETER_OPEN.length;
2237
+ } else if (text[index] === PARAMETER_CLOSE) {
2238
+ depth -= 1;
2239
+ index += 1;
2240
+ if (depth === 0) {
2241
+ return index;
2242
+ }
2243
+ } else {
2244
+ index = quotedOrEscapedEnd(text, index) ?? index + 1;
2245
+ }
2246
+ }
2247
+ return void 0;
2248
+ }
2249
+ function quotedOrEscapedEnd(text, index) {
2250
+ const char = text[index];
2251
+ if (char === "'") {
2252
+ return singleQuoted(text, index).end;
2253
+ }
2254
+ if (char === '"') {
2255
+ return doubleQuoted(text, index).end;
2256
+ }
2257
+ return char === BACKSLASH$2 ? index + 2 : void 0;
2258
+ }
2259
+ function expansionPart(text, index) {
2260
+ if (text.startsWith(ARITHMETIC_OPEN, index)) {
2261
+ return arithmeticExpansion(text, index);
2262
+ }
2263
+ const end = text.startsWith(PARAMETER_OPEN, index) ? parameterExpansionEnd(text, index) : void 0;
2264
+ return end === void 0 ? void 0 : { end, value: text.slice(index, end) };
2265
+ }
2266
+ function wordPart(text, index) {
2267
+ const char = text[index];
2268
+ const expansion = expansionPart(text, index);
2269
+ if (expansion !== void 0) {
2270
+ return expansion;
2271
+ }
2272
+ if (char === "'") {
2273
+ return singleQuoted(text, index);
2274
+ }
2275
+ if (char === '"') {
2276
+ return doubleQuoted(text, index);
2277
+ }
2278
+ if (char === BACKSLASH$2 && index + 1 < text.length) {
2279
+ return { end: index + 2, value: text[index + 1] };
2280
+ }
2281
+ return { end: index + 1, value: char };
2282
+ }
2283
+ const ARRAY_HEAD_RE = /^[A-Za-z_][A-Za-z0-9_]*\+?=$/;
2284
+ function arrayListEnd(text, index) {
2285
+ let depth = 0;
2286
+ for (let at = index; at < text.length; ) {
2287
+ depth += text[at] === NESTING_OPEN ? 1 : text[at] === NESTING_CLOSE ? -1 : 0;
2288
+ if (depth === 0) {
2289
+ return at + 1;
2290
+ }
2291
+ at = quotedOrEscapedEnd(text, at) ?? at + 1;
2292
+ }
2293
+ return void 0;
2294
+ }
2295
+ const endsWord = (text, index) => {
2296
+ const char = text[index];
2297
+ return WORD_BREAKS.has(char) || COMMAND_SEPARATORS.has(char) || isLineContinuation(text, index);
2298
+ };
2299
+ function readWord(text, start) {
2300
+ let value = "";
2301
+ let index = start;
2302
+ while (index < text.length) {
2303
+ const isArrayList = text[index] === NESTING_OPEN && ARRAY_HEAD_RE.test(text.slice(start, index));
2304
+ const list = isArrayList ? arrayListEnd(text, index) : void 0;
2305
+ if (list === void 0 && endsWord(text, index)) {
2306
+ break;
2307
+ }
2308
+ const part = list === void 0 ? wordPart(text, index) : { end: list, value: text.slice(index, list) };
2309
+ value += part.value;
2310
+ index = part.end;
2311
+ }
2312
+ return { start, end: index, value, raw: text.slice(start, index) };
2313
+ }
2314
+
2315
+ const ASSIGNMENT_RE$2 = /^([A-Za-z_][A-Za-z0-9_]*)=/;
2316
+ const DECLARATION_BUILTINS = /* @__PURE__ */ new Set([
2317
+ "export",
2318
+ "readonly",
2319
+ "local",
2320
+ "declare",
2321
+ "typeset"
2322
+ ]);
2323
+ const QUOTES$3 = /* @__PURE__ */ new Set(["'", '"']);
2324
+ const UNPLAIN_RE = /['"\\$`]/;
2325
+ function leadingAssignments(command) {
2326
+ const index = command.findIndex((word) => !ASSIGNMENT_RE$2.test(word.raw));
2327
+ return index === -1 ? command.length : index;
2328
+ }
2329
+ function shellAssignments(command) {
2330
+ const count = leadingAssignments(command);
2331
+ const isDeclaration = DECLARATION_BUILTINS.has(command[count]?.value ?? "");
2332
+ const words = [...command.slice(0, count), ...isDeclaration ? command.slice(count + 1) : []];
2333
+ return words.flatMap((word) => {
2334
+ const match = ASSIGNMENT_RE$2.exec(word.raw);
2335
+ const name = match?.[1];
2336
+ return name === void 0 ? [] : [{ name, value: word.value.slice(name.length + 1), word }];
2337
+ });
2338
+ }
2339
+ const ARRAY_OPEN = "(";
2340
+ const ARRAY_CLOSE = ")";
2341
+ const isArrayAssignment = (assignment) => assignment.word.raw.startsWith(ARRAY_OPEN, assignment.name.length + 1);
2342
+ function assignmentValueText(assignment) {
2343
+ const at = assignment.name.length + 1;
2344
+ const raw = assignment.word.raw.slice(at);
2345
+ const start = assignment.word.start + at;
2346
+ const quote = raw[0] ?? "";
2347
+ if (isArrayAssignment(assignment)) {
2348
+ return raw.endsWith(ARRAY_CLOSE) ? { text: raw.slice(1, -1), start: start + 1 } : void 0;
2349
+ }
2350
+ if (QUOTES$3.has(quote)) {
2351
+ const isOneString = raw.length > 1 && raw.endsWith(quote) && wordPart(raw, 0).end === raw.length;
2352
+ return isOneString ? { text: raw.slice(1, -1), start: start + 1 } : void 0;
2353
+ }
2354
+ return UNPLAIN_RE.test(raw) ? void 0 : { text: raw, start };
2355
+ }
2356
+
2580
2357
  const END_OF_OPTIONS = "--";
2581
2358
  const isOption$1 = (word) => word.value.startsWith("-") && word.value.length > 1;
2582
2359
  function skipAssignments(words, start) {
2583
2360
  let index = start;
2584
- while (index < words.length && ASSIGNMENT_RE$1.test(words[index].raw)) {
2361
+ while (index < words.length && ASSIGNMENT_RE$2.test(words[index].raw)) {
2585
2362
  index++;
2586
2363
  }
2587
2364
  return index;
@@ -2653,85 +2430,81 @@ function unmigratedWordFindings(words, handledOffsets, locate) {
2653
2430
  }));
2654
2431
  }
2655
2432
 
2656
- const COMMAND_SEPARATORS = /* @__PURE__ */ new Set([
2657
- ";",
2658
- "&",
2659
- "|",
2660
- "(",
2661
- ")",
2662
- "`",
2663
- "\n"
2664
- ]);
2665
- const WORD_BREAKS = /* @__PURE__ */ new Set([" ", " ", "\r", "<", ">"]);
2666
- const BACKSLASH$1 = "\\";
2667
- const DOUBLE_QUOTE_ESCAPES = /* @__PURE__ */ new Set(["$", "`", '"', BACKSLASH$1, "\n"]);
2668
- function singleQuoted(text, start) {
2669
- const close = text.indexOf("'", start + 1);
2670
- const end = close === -1 ? text.length : close + 1;
2671
- return { end, value: text.slice(start + 1, close === -1 ? text.length : close) };
2672
- }
2673
- function doubleQuoted(text, start) {
2674
- let value = "";
2675
- let index = start + 1;
2676
- while (index < text.length && text[index] !== '"') {
2677
- const next = text[index + 1] ?? "";
2678
- const isEscape = text[index] === BACKSLASH$1 && DOUBLE_QUOTE_ESCAPES.has(next);
2679
- value += isEscape ? next === "\n" ? "" : next : text[index];
2680
- index += isEscape ? 2 : 1;
2433
+ const RECIPE_PREFIX_RE$1 = /^[@+-]+(?=.)/;
2434
+ function withoutRecipePrefix(words) {
2435
+ const [first, ...rest] = words;
2436
+ if (first === void 0 || !RECIPE_PREFIX_RE$1.test(first.value)) {
2437
+ return words;
2681
2438
  }
2682
- return { end: Math.min(index + 1, text.length), value };
2439
+ return [{ ...first, value: first.value.replace(RECIPE_PREFIX_RE$1, "") }, ...rest];
2683
2440
  }
2684
- const isLineContinuation = (text, index) => text[index] === BACKSLASH$1 && (text[index + 1] === "\n" || text.startsWith("\r\n", index + 1));
2685
- const ARITHMETIC_OPEN = "$((";
2686
- const NESTING_OPEN = "(";
2687
- const NESTING_CLOSE = ")";
2688
- const ARITHMETIC_DEPTH = 2;
2689
- function arithmeticExpansion(text, start) {
2690
- let depth = ARITHMETIC_DEPTH;
2691
- for (let index = start + ARITHMETIC_OPEN.length; index < text.length; index++) {
2692
- depth += text[index] === NESTING_OPEN ? 1 : text[index] === NESTING_CLOSE ? -1 : 0;
2693
- if (depth === 0) {
2694
- return { end: index + 1, value: text.slice(start, index + 1) };
2695
- }
2696
- }
2697
- return void 0;
2441
+ const NO_VARIABLES = {
2442
+ programAt: () => void 0,
2443
+ referenceAt: () => void 0
2444
+ };
2445
+ function runsGenerator(command) {
2446
+ const words = withoutRecipePrefix(command);
2447
+ const { program } = commandRoles(words);
2448
+ const word = program === void 0 ? void 0 : words[program];
2449
+ return word !== void 0 && packageReference(word.value).name === GENERATOR_COMMAND;
2698
2450
  }
2699
- function wordPart(text, index) {
2700
- const char = text[index];
2701
- const arithmetic = text.startsWith(ARITHMETIC_OPEN, index) ? arithmeticExpansion(text, index) : void 0;
2702
- if (arithmetic !== void 0) {
2703
- return arithmetic;
2704
- }
2705
- if (char === "'") {
2706
- return singleQuoted(text, index);
2707
- }
2708
- if (char === '"') {
2709
- return doubleQuoted(text, index);
2710
- }
2711
- if (char === BACKSLASH$1 && index + 1 < text.length) {
2712
- return { end: index + 2, value: text[index + 1] };
2451
+
2452
+ const EXPANSION_HEAD_RE = /^\$\{([A-Za-z_][A-Za-z0-9_]*):?([-=+])/;
2453
+ const USES = {
2454
+ "-": "default",
2455
+ "=": "assign",
2456
+ "+": "alternative"
2457
+ };
2458
+ const SINGLE_QUOTE = "'";
2459
+ const BACKSLASH$1 = "\\";
2460
+ function expansionAt(text, start) {
2461
+ const head = EXPANSION_HEAD_RE.exec(text.slice(start));
2462
+ const end = head === null ? void 0 : parameterExpansionEnd(text, start);
2463
+ if (head === null || end === void 0) {
2464
+ return void 0;
2713
2465
  }
2714
- return { end: index + 1, value: char };
2466
+ const wordStart = start + head[0].length;
2467
+ return {
2468
+ name: head[1],
2469
+ use: USES[head[2]],
2470
+ text: text.slice(wordStart, end - 1),
2471
+ start: wordStart,
2472
+ expansionStart: start,
2473
+ expansionEnd: end
2474
+ };
2715
2475
  }
2716
- const endsWord = (text, index) => {
2717
- const char = text[index];
2718
- return WORD_BREAKS.has(char) || COMMAND_SEPARATORS.has(char) || isLineContinuation(text, index);
2719
- };
2720
- function readWord(text, start) {
2721
- let value = "";
2722
- let index = start;
2723
- while (index < text.length && !endsWord(text, index)) {
2724
- const part = wordPart(text, index);
2725
- value += part.value;
2726
- index = part.end;
2476
+ function wordExpansions(raw) {
2477
+ const found = [];
2478
+ for (let index = 0; index < raw.length; ) {
2479
+ const char = raw[index];
2480
+ if (char === SINGLE_QUOTE) {
2481
+ const close = raw.indexOf(SINGLE_QUOTE, index + 1);
2482
+ index = close === -1 ? raw.length : close + 1;
2483
+ } else if (char === BACKSLASH$1) {
2484
+ index += 2;
2485
+ } else if (raw.startsWith(PARAMETER_OPEN, index)) {
2486
+ const expansion = expansionAt(raw, index);
2487
+ found.push(...expansion === void 0 ? [] : [expansion]);
2488
+ index = expansion?.expansionEnd ?? index + PARAMETER_OPEN.length;
2489
+ } else {
2490
+ index += 1;
2491
+ }
2727
2492
  }
2728
- return { start, end: index, value, raw: text.slice(start, index) };
2493
+ return found;
2494
+ }
2495
+ const DOUBLE_QUOTE = '"';
2496
+ function wholeWordExpansion(raw) {
2497
+ const isQuoted = raw.length > 1 && raw.startsWith(DOUBLE_QUOTE) && raw.endsWith(DOUBLE_QUOTE);
2498
+ const inner = isQuoted ? 1 : 0;
2499
+ const [expansion] = wordExpansions(raw);
2500
+ const isWhole = expansion?.expansionStart === inner && expansion.expansionEnd === raw.length - inner;
2501
+ return isWhole ? expansion : void 0;
2729
2502
  }
2730
2503
 
2731
2504
  const HEREDOC_OPERATOR = "<<";
2732
2505
  const HERE_STRING_OPERATOR = "<<<";
2733
2506
  const STRIP_TABS = "-";
2734
- const LINE_BREAK$2 = "\n";
2507
+ const LINE_BREAK$4 = "\n";
2735
2508
  const LEADING_TABS_RE = /^\t+/;
2736
2509
  const CARRIAGE_RETURN_RE = /\r$/;
2737
2510
  function heredocAt(text, index, where) {
@@ -2763,7 +2536,7 @@ function isDelimiterLine(text, lineStart, end, heredoc) {
2763
2536
  function readBody(text, start, heredoc) {
2764
2537
  let lineStart = start;
2765
2538
  while (lineStart < text.length) {
2766
- const lineEnd = text.indexOf(LINE_BREAK$2, lineStart);
2539
+ const lineEnd = text.indexOf(LINE_BREAK$4, lineStart);
2767
2540
  const end = lineEnd === -1 ? text.length : lineEnd;
2768
2541
  if (isDelimiterLine(text, lineStart, end, heredoc)) {
2769
2542
  const body = { command: heredoc.command, start, text: text.slice(start, lineStart) };
@@ -2785,7 +2558,7 @@ function readBodies(text, start, pending) {
2785
2558
  }
2786
2559
 
2787
2560
  const COMMENT = "#";
2788
- const LINE_BREAK$1 = "\n";
2561
+ const LINE_BREAK$3 = "\n";
2789
2562
  function skipTrivia(text, index) {
2790
2563
  const char = text[index];
2791
2564
  if (WORD_BREAKS.has(char)) {
@@ -2815,7 +2588,7 @@ function topLevelCommands(text, indent) {
2815
2588
  index = heredoc.end;
2816
2589
  } else if (afterTrivia !== index) {
2817
2590
  index = afterTrivia;
2818
- } else if (text[index] === LINE_BREAK$1 && pending.length > 0) {
2591
+ } else if (text[index] === LINE_BREAK$3 && pending.length > 0) {
2819
2592
  const read = readBodies(text, index + 1, pending);
2820
2593
  heredocs.push(...read.bodies);
2821
2594
  pending = [];
@@ -2878,20 +2651,20 @@ function nestedCommandText(word) {
2878
2651
  const SHELLS$1 = /* @__PURE__ */ new Set(["sh", "bash", "dash", "ash", "zsh", "ksh"]);
2879
2652
  const DOCKER_RUN = "RUN";
2880
2653
  const PASS_THROUGH = "cat";
2881
- const PATH_SEPARATOR$3 = "/";
2654
+ const PATH_SEPARATOR$8 = "/";
2882
2655
  const OPTION_PREFIX$1 = "-";
2883
- const programName = (command) => command[0]?.value.split(PATH_SEPARATOR$3).at(-1) ?? "";
2656
+ const programName$1 = (command) => command[0]?.value.split(PATH_SEPARATOR$8).at(-1) ?? "";
2884
2657
  const isOption = (word) => word.value.startsWith(OPTION_PREFIX$1);
2885
- const isScriptShell = (command) => SHELLS$1.has(programName(command)) && command.slice(1).every(isOption);
2658
+ const isScriptShell = (command) => SHELLS$1.has(programName$1(command)) && command.slice(1).every(isOption);
2886
2659
  function readsScript(command) {
2887
- if (programName(command) !== DOCKER_RUN) {
2660
+ if (programName$1(command) !== DOCKER_RUN) {
2888
2661
  return isScriptShell(command);
2889
2662
  }
2890
2663
  const after = command.slice(1);
2891
2664
  const shell = after.slice(after.findIndex((word) => !isOption(word)));
2892
2665
  return after.every(isOption) || isScriptShell(shell);
2893
2666
  }
2894
- const passesThrough = (command) => programName(command) === PASS_THROUGH && command.slice(1).every(isOption);
2667
+ const passesThrough = (command) => programName$1(command) === PASS_THROUGH && command.slice(1).every(isOption);
2895
2668
  function scriptsRunBy(top, index, command) {
2896
2669
  const previous = index - 1;
2897
2670
  const isPipedScript = top.pipes.has(previous) && isScriptShell(command) && passesThrough(simpleCommand(top.commands[previous] ?? []));
@@ -2903,6 +2676,21 @@ const shifted = (words, offset) => words.map((word) => ({ ...word, start: word.s
2903
2676
  function shellCommandsAt(text, offset, indent = 0) {
2904
2677
  return shellCommands(text, indent).map((words) => shifted(words, offset));
2905
2678
  }
2679
+ const expansionCommands = (word) => wordExpansions(word.raw).flatMap(
2680
+ (expansion) => shellCommandsAt(expansion.text, word.start + expansion.start)
2681
+ );
2682
+ function innerCommands(command) {
2683
+ const assignments = new Map(shellAssignments(command).map((found) => [found.word, found]));
2684
+ return command.flatMap((word) => {
2685
+ const inner = nestedCommandText(word);
2686
+ if (inner !== void 0) {
2687
+ return shellCommandsAt(inner, word.start + 1);
2688
+ }
2689
+ const assignment = assignments.get(word);
2690
+ const value = assignment === void 0 ? void 0 : assignmentValueText(assignment);
2691
+ return value === void 0 ? expansionCommands(word) : shellCommandsAt(value.text, value.start);
2692
+ });
2693
+ }
2906
2694
  function shellCommands(text, indent = 0) {
2907
2695
  const top = topLevelCommands(text, indent);
2908
2696
  const commands = [];
@@ -2911,71 +2699,211 @@ function shellCommands(text, indent = 0) {
2911
2699
  if (command.length > 0) {
2912
2700
  commands.push(command);
2913
2701
  }
2914
- for (const word of command) {
2915
- const inner = nestedCommandText(word);
2916
- if (inner !== void 0) {
2917
- const nested = shellCommands(inner).map((found) => shifted(found, word.start + 1));
2918
- commands.push(...nested);
2919
- }
2920
- }
2702
+ commands.push(...innerCommands(command));
2921
2703
  const scripts = scriptsRunBy(top, index, command);
2922
2704
  commands.push(...scripts.flatMap((body) => shellCommandsAt(body.text, body.start, indent)));
2923
2705
  }
2924
2706
  return commands;
2925
2707
  }
2926
2708
 
2927
- const QUOTES$1 = /* @__PURE__ */ new Set(["'", '"']);
2928
- const PATH_SEPARATOR$2 = "/";
2929
- const BIN_FOLDER = "/.bin/";
2930
- const OLD_PACKAGE_FOLDER = `node_modules/${GENERATOR_COMMAND}/`;
2931
- function renameAtEnd(word, name, to) {
2932
- const at = word.raw.lastIndexOf(name);
2933
- const tail = word.raw.slice(at + name.length);
2934
- if (at === -1 || tail !== "" && !QUOTES$1.has(tail)) {
2935
- return void 0;
2936
- }
2937
- return { start: word.start + at, end: word.start + at + name.length, text: to };
2938
- }
2939
- function packageFindings(found, locate) {
2940
- const { name, isPinned } = packageReference(found.value);
2941
- const to = PACKAGE_RENAMES.get(name);
2942
- const offset = found.word.start;
2943
- if (to === void 0) {
2944
- return [];
2945
- }
2946
- const location = locate(offset);
2947
- if (isPinned) {
2948
- return [{ offset, manualStep: { kind: "pinned-package", location, name } }];
2949
- }
2950
- const edit = renameAtEnd(found.word, name, to);
2951
- const change = { kind: "package-name-renamed", location, from: name, to };
2952
- return edit === void 0 ? [] : [{ offset, edit, change }];
2709
+ function assertNever(value) {
2710
+ throw new Error(`Unexpected variant: ${JSON.stringify(value)}`);
2953
2711
  }
2954
- function programPathFindings(word, location) {
2955
- const offset = word.start;
2956
- if (word.value.includes(OLD_PACKAGE_FOLDER)) {
2957
- return [{ offset, manualStep: { kind: "old-package-file", location, path: word.value } }];
2712
+
2713
+ function canRunGenerator(definition, generators) {
2714
+ switch (definition.kind) {
2715
+ case "command":
2716
+ return definition.runsGenerator;
2717
+ case "reference":
2718
+ return generators.has(definition.target);
2719
+ case "append":
2720
+ return false;
2721
+ default:
2722
+ return assertNever(definition);
2958
2723
  }
2959
- const isBin = word.value.endsWith(`${BIN_FOLDER}${GENERATOR_COMMAND}`);
2960
- const edit = isBin ? renameAtEnd(word, GENERATOR_COMMAND, MIGRATED_GENERATOR_COMMAND) : void 0;
2961
- return edit === void 0 ? [] : [{ offset, edit, change: { kind: "command-renamed", location } }];
2962
2724
  }
2963
- function programFindings(word, location) {
2964
- if (word.value.includes(PATH_SEPARATOR$2)) {
2965
- return programPathFindings(word, location);
2966
- }
2967
- const { name, isPinned } = packageReference(word.value);
2968
- if (name !== GENERATOR_COMMAND) {
2969
- return [];
2970
- }
2971
- if (isPinned) {
2972
- return [{ offset: word.start, manualStep: { kind: "versioned-command", location } }];
2725
+ function canRunOther(definition, others, defined) {
2726
+ switch (definition.kind) {
2727
+ case "command":
2728
+ return !definition.runsGenerator;
2729
+ case "reference":
2730
+ return others.has(definition.target) || !defined.has(definition.target);
2731
+ case "append":
2732
+ return false;
2733
+ default:
2734
+ return assertNever(definition);
2973
2735
  }
2974
- const edit = renameAtEnd(word, GENERATOR_COMMAND, MIGRATED_GENERATOR_COMMAND);
2975
- const change = { kind: "command-renamed", location };
2976
- return edit === void 0 ? [] : [{ offset: word.start, edit, change }];
2977
2736
  }
2978
- function flagFindings(word, locate) {
2737
+ function variablePrograms(definitions) {
2738
+ const defined = new Set(definitions.map((definition) => definition.name));
2739
+ const generators = /* @__PURE__ */ new Set();
2740
+ const others = /* @__PURE__ */ new Set();
2741
+ for (let size = -1; size !== generators.size + others.size; ) {
2742
+ size = generators.size + others.size;
2743
+ for (const definition of definitions) {
2744
+ if (canRunGenerator(definition, generators)) {
2745
+ generators.add(definition.name);
2746
+ }
2747
+ if (canRunOther(definition, others, defined)) {
2748
+ others.add(definition.name);
2749
+ }
2750
+ }
2751
+ }
2752
+ const programOf = (name) => {
2753
+ if (!generators.has(name)) {
2754
+ return "other";
2755
+ }
2756
+ return others.has(name) ? "generator-or-other" : "generator";
2757
+ };
2758
+ return new Map([...defined].map((name) => [name, programOf(name)]));
2759
+ }
2760
+
2761
+ const SHELL_VARIABLE_RE = /^"?\$(?:([A-Za-z_][A-Za-z0-9_]*)|\{([A-Za-z_][A-Za-z0-9_]*)\})"?$/;
2762
+ function expandedName(text) {
2763
+ const match = SHELL_VARIABLE_RE.exec(text);
2764
+ return match === null ? void 0 : match[1] ?? match[2];
2765
+ }
2766
+ const textRunsGenerator = (text) => shellCommands(text).some(runsGenerator);
2767
+ function textDefinition(name, text) {
2768
+ const target = expandedName(text.trim());
2769
+ return target === void 0 ? { name, kind: "command", runsGenerator: textRunsGenerator(text) } : { name, kind: "reference", target };
2770
+ }
2771
+ function expansionDefinitions(name, expansion) {
2772
+ const word = textDefinition(name, expansion.text);
2773
+ const isOutside = expansion.use === "alternative" || expansion.name === name;
2774
+ const variable = isOutside ? { name, kind: "command", runsGenerator: false } : { name, kind: "reference", target: expansion.name };
2775
+ return [word, variable];
2776
+ }
2777
+ function programDefinitions$1({ name, value }) {
2778
+ const expansion = wholeWordExpansion(value.trim());
2779
+ return expansion === void 0 ? [textDefinition(name, value)] : expansionDefinitions(name, expansion);
2780
+ }
2781
+ function shellVariables(definitions) {
2782
+ const values = /* @__PURE__ */ new Map();
2783
+ for (const { name, value } of definitions) {
2784
+ values.set(name, [values.get(name), value].filter((part) => part !== void 0).join(" "));
2785
+ }
2786
+ const programs = variablePrograms(definitions.flatMap(programDefinitions$1));
2787
+ const nameAt = (word) => expandedName(word.raw);
2788
+ return {
2789
+ programAt: (program) => {
2790
+ const name = nameAt(program);
2791
+ const found = name === void 0 ? void 0 : programs.get(name);
2792
+ return found === void 0 ? void 0 : { written: program.raw, program: found };
2793
+ },
2794
+ referenceAt: (word) => {
2795
+ const name = nameAt(word);
2796
+ const value = name === void 0 ? void 0 : values.get(name);
2797
+ return value === void 0 ? void 0 : { written: word.raw, value };
2798
+ }
2799
+ };
2800
+ }
2801
+ function combinedUse(left, right) {
2802
+ if (left === void 0 || right === void 0) {
2803
+ return left ?? right;
2804
+ }
2805
+ return left.program === right.program ? left : { ...left, program: "generator-or-other" };
2806
+ }
2807
+ function assignedByExpansions(commands) {
2808
+ return commands.flat().flatMap((word) => wordExpansions(word.raw)).filter((expansion) => expansion.use === "assign").flatMap(({ name, text }) => [
2809
+ { name, value: text },
2810
+ { name, value: "" }
2811
+ ]);
2812
+ }
2813
+ function withShellAssignments(commands, file) {
2814
+ const assigned = commands.flatMap(shellAssignments).filter((found) => !isArrayAssignment(found));
2815
+ const definitions = [...assigned, ...assignedByExpansions(commands)];
2816
+ if (definitions.length === 0) {
2817
+ return file;
2818
+ }
2819
+ const own = shellVariables(definitions);
2820
+ return {
2821
+ programAt: (program) => combinedUse(own.programAt(program), file.programAt(program)),
2822
+ referenceAt: (word) => own.referenceAt(word) ?? file.referenceAt(word)
2823
+ };
2824
+ }
2825
+ const joinedProgram = (left, right) => left === right ? left : "generator-or-other";
2826
+ function expansionUse(word, variables) {
2827
+ const expansion = wholeWordExpansion(word.raw);
2828
+ if (expansion === void 0) {
2829
+ return void 0;
2830
+ }
2831
+ const programOf = (name) => (name === void 0 ? void 0 : variables.programAt({ ...word, raw: `$${name}` })?.program) ?? "other";
2832
+ const target = expandedName(expansion.text.trim());
2833
+ const textProgram = textRunsGenerator(expansion.text) ? "generator" : "other";
2834
+ const own = target === void 0 ? textProgram : programOf(target);
2835
+ const variable = programOf(expansion.use === "alternative" ? void 0 : expansion.name);
2836
+ return { written: word.raw, program: joinedProgram(own, variable) };
2837
+ }
2838
+ function withWordExpansions(variables) {
2839
+ return {
2840
+ programAt: (program) => variables.programAt(program) ?? expansionUse(program, variables),
2841
+ referenceAt: (word) => variables.referenceAt(word)
2842
+ };
2843
+ }
2844
+ const VARIABLE_NAME = "[A-Za-z_][A-Za-z0-9_]*";
2845
+ const VARIABLE_WORD_RE = new RegExp(
2846
+ `^"?\\$\\$?(?:${VARIABLE_NAME}|\\{${VARIABLE_NAME}(?:\\[[^\\]]*\\])?\\})"?$`
2847
+ );
2848
+ function withUnreadVariables(variables) {
2849
+ return {
2850
+ programAt: (program) => variables.programAt(program) ?? (VARIABLE_WORD_RE.test(program.raw) ? { written: program.raw, program: "generator-or-other" } : void 0),
2851
+ referenceAt: (word) => variables.referenceAt(word)
2852
+ };
2853
+ }
2854
+
2855
+ const QUOTES$1 = /* @__PURE__ */ new Set(["'", '"']);
2856
+ const PATH_SEPARATOR$7 = "/";
2857
+ const BIN_FOLDER = "/.bin/";
2858
+ const OLD_PACKAGE_FOLDER = `node_modules/${GENERATOR_COMMAND}/`;
2859
+ function renameAtEnd(word, name, to) {
2860
+ const at = word.raw.lastIndexOf(name);
2861
+ const tail = word.raw.slice(at + name.length);
2862
+ if (at === -1 || tail !== "" && !QUOTES$1.has(tail)) {
2863
+ return void 0;
2864
+ }
2865
+ return { start: word.start + at, end: word.start + at + name.length, text: to };
2866
+ }
2867
+ function packageFindings(found, locate) {
2868
+ const { name, isPinned } = packageReference(found.value);
2869
+ const to = PACKAGE_RENAMES.get(name);
2870
+ const offset = found.word.start;
2871
+ if (to === void 0) {
2872
+ return [];
2873
+ }
2874
+ const location = locate(offset);
2875
+ if (isPinned) {
2876
+ return [{ offset, manualStep: { kind: "pinned-package", location, name } }];
2877
+ }
2878
+ const edit = renameAtEnd(found.word, name, to);
2879
+ const change = { kind: "package-name-renamed", location, from: name, to };
2880
+ return edit === void 0 ? [] : [{ offset, edit, change }];
2881
+ }
2882
+ function programPathFindings(word, location) {
2883
+ const offset = word.start;
2884
+ if (word.value.includes(OLD_PACKAGE_FOLDER)) {
2885
+ return [{ offset, manualStep: { kind: "old-package-file", location, path: word.value } }];
2886
+ }
2887
+ const isBin = word.value.endsWith(`${BIN_FOLDER}${GENERATOR_COMMAND}`);
2888
+ const edit = isBin ? renameAtEnd(word, GENERATOR_COMMAND, MIGRATED_GENERATOR_COMMAND) : void 0;
2889
+ return edit === void 0 ? [] : [{ offset, edit, change: { kind: "command-renamed", location } }];
2890
+ }
2891
+ function programFindings(word, location) {
2892
+ if (word.value.includes(PATH_SEPARATOR$7)) {
2893
+ return programPathFindings(word, location);
2894
+ }
2895
+ const { name, isPinned } = packageReference(word.value);
2896
+ if (name !== GENERATOR_COMMAND) {
2897
+ return [];
2898
+ }
2899
+ if (isPinned) {
2900
+ return [{ offset: word.start, manualStep: { kind: "versioned-command", location } }];
2901
+ }
2902
+ const edit = renameAtEnd(word, GENERATOR_COMMAND, MIGRATED_GENERATOR_COMMAND);
2903
+ const change = { kind: "command-renamed", location };
2904
+ return edit === void 0 ? [] : [{ offset: word.start, edit, change }];
2905
+ }
2906
+ function flagFindings(word, locate) {
2979
2907
  const flag = word.raw.split("=", 1)[0];
2980
2908
  const to = FLAG_RENAMES.get(flag);
2981
2909
  if (to === void 0) {
@@ -2987,18 +2915,6 @@ function flagFindings(word, locate) {
2987
2915
  { offset, edit, change: { kind: "flag-renamed", location: locate(offset), from: flag, to } }
2988
2916
  ];
2989
2917
  }
2990
- const RECIPE_PREFIX_RE$1 = /^[@+-]+(?=.)/;
2991
- function withoutRecipePrefix(words) {
2992
- const [first, ...rest] = words;
2993
- if (first === void 0 || !RECIPE_PREFIX_RE$1.test(first.value)) {
2994
- return words;
2995
- }
2996
- return [{ ...first, value: first.value.replace(RECIPE_PREFIX_RE$1, "") }, ...rest];
2997
- }
2998
- const NO_VARIABLES = {
2999
- programAt: () => void 0,
3000
- referenceAt: () => void 0
3001
- };
3002
2918
  function renamedFlags(value) {
3003
2919
  const flags = shellCommands(value).flat().map((word) => word.raw.split("=", 1)[0]);
3004
2920
  return [...new Set(flags.filter((flag) => FLAG_RENAMES.has(flag)))];
@@ -3067,17 +2983,12 @@ function knownFindings(words, locate, variables) {
3067
2983
  );
3068
2984
  return findings;
3069
2985
  }
3070
- function runsGenerator(command) {
3071
- const words = withoutRecipePrefix(command);
3072
- const { program } = commandRoles(words);
3073
- const word = program === void 0 ? void 0 : words[program];
3074
- return word !== void 0 && packageReference(word.value).name === GENERATOR_COMMAND;
3075
- }
3076
2986
  function shellTextFindings(text, locate) {
3077
2987
  return commandWordFindings(shellCommands(text), locate);
3078
2988
  }
3079
2989
  function commandWordFindings(commands, locate, variables = NO_VARIABLES) {
3080
- const findings = commands.flatMap((words) => commandFindings(words, locate, variables)).sort((left, right) => left.offset - right.offset);
2990
+ const known = withUnreadVariables(withWordExpansions(withShellAssignments(commands, variables)));
2991
+ const findings = commands.flatMap((words) => commandFindings(words, locate, known)).sort((left, right) => left.offset - right.offset);
3081
2992
  return {
3082
2993
  edits: findings.flatMap((finding) => finding.edit === void 0 ? [] : [finding.edit]),
3083
2994
  changes: findings.flatMap((finding) => finding.change === void 0 ? [] : [finding.change]),
@@ -3122,6 +3033,7 @@ const NX_WORKSPACE_COMMAND_PATHS = nxTargetPaths([
3122
3033
  ]);
3123
3034
  const MANIFEST_COMMAND_PATHS = [
3124
3035
  ["scripts", ANY],
3036
+ ["wireit", ANY, "command"],
3125
3037
  ...simpleGitHooksPaths(["simple-git-hooks"]),
3126
3038
  ...lintStagedPaths(["lint-staged"]),
3127
3039
  ...nxTargetPaths(["nx", "targets"])
@@ -3149,94 +3061,120 @@ function commandStringSteps(text, strings) {
3149
3061
  });
3150
3062
  }
3151
3063
 
3152
- function canRunGenerator(definition, generators) {
3153
- switch (definition.kind) {
3154
- case "command":
3155
- return definition.runsGenerator;
3156
- case "reference":
3157
- return generators.has(definition.target);
3158
- case "append":
3159
- return false;
3160
- default:
3161
- return assertNever(definition);
3064
+ const KEPT_PROTOCOLS = ["catalog:", "workspace:"];
3065
+ const LOCAL_PROTOCOLS = ["workspace:", "link:", "file:", "portal:"];
3066
+ const RANGE_SEPARATOR = "||";
3067
+ const HYPHEN = "-";
3068
+ const WHITE_SPACE_RE = /\s+/;
3069
+ const SPACED_OPERATOR_RE = /(<=|>=|<|>|=|~>|~|\^)\s+/g;
3070
+ const OPERATOR = String.raw`(<=|>=|<|>|=|~>|~|\^)?`;
3071
+ const VERSION_PART = String.raw`(\d+|[xX*])`;
3072
+ const LABELS = String.raw`(?:-[\w.-]+)?(?:\+[\w.-]+)?`;
3073
+ const COMPARATOR_RE = new RegExp(
3074
+ String.raw`^${OPERATOR}v?${VERSION_PART}(?:\.${VERSION_PART})?(?:\.${VERSION_PART})?${LABELS}$`
3075
+ );
3076
+ const NUMBER_RE = /^\d+$/;
3077
+ const OPERATOR_STYLES = {
3078
+ "^": "^",
3079
+ // `>=` means "this or newer"; with a new package line, a caret range says the same safely.
3080
+ ">=": "^",
3081
+ ">": "^",
3082
+ "~": "~",
3083
+ "~>": "~"
3084
+ };
3085
+ const isNumber = (part) => part !== void 0 && NUMBER_RE.test(part);
3086
+ function versionStyle(major, minor, patch) {
3087
+ if (!isNumber(major)) {
3088
+ return void 0;
3162
3089
  }
3163
- }
3164
- function canRunOther(definition, others, defined) {
3165
- switch (definition.kind) {
3166
- case "command":
3167
- return !definition.runsGenerator;
3168
- case "reference":
3169
- return others.has(definition.target) || !defined.has(definition.target);
3170
- case "append":
3171
- return false;
3172
- default:
3173
- return assertNever(definition);
3090
+ if (isNumber(minor) && isNumber(patch)) {
3091
+ return "";
3174
3092
  }
3093
+ return isNumber(minor) ? "~" : "^";
3175
3094
  }
3176
- function variablePrograms(definitions) {
3177
- const defined = new Set(definitions.map((definition) => definition.name));
3178
- const generators = /* @__PURE__ */ new Set();
3179
- const others = /* @__PURE__ */ new Set();
3180
- for (let size = -1; size !== generators.size + others.size; ) {
3181
- size = generators.size + others.size;
3182
- for (const definition of definitions) {
3183
- if (canRunGenerator(definition, generators)) {
3184
- generators.add(definition.name);
3185
- }
3186
- if (canRunOther(definition, others, defined)) {
3187
- others.add(definition.name);
3188
- }
3189
- }
3095
+ function rangeStyle(spec) {
3096
+ const sets = spec.replace(SPACED_OPERATOR_RE, "$1").split(RANGE_SEPARATOR);
3097
+ const tokens = sets.map((set) => set.trim().split(WHITE_SPACE_RE));
3098
+ const comparators = tokens.flat().filter((token) => token !== HYPHEN);
3099
+ const matches = comparators.map((comparator) => COMPARATOR_RE.exec(comparator));
3100
+ const first = matches[0];
3101
+ if (first === void 0 || first === null || matches.includes(null)) {
3102
+ return void 0;
3190
3103
  }
3191
- const programOf = (name) => {
3192
- if (!generators.has(name)) {
3193
- return "other";
3194
- }
3195
- return others.has(name) ? "generator-or-other" : "generator";
3196
- };
3197
- return new Map([...defined].map((name) => [name, programOf(name)]));
3198
- }
3199
-
3200
- const SHELL_VARIABLE_RE = /^"?\$(?:([A-Za-z_][A-Za-z0-9_]*)|\{([A-Za-z_][A-Za-z0-9_]*)\})"?$/;
3201
- function expandedName(text) {
3202
- const match = SHELL_VARIABLE_RE.exec(text);
3203
- return match === null ? void 0 : match[1] ?? match[2];
3204
- }
3205
- function programDefinition({ name, value }) {
3206
- const target = expandedName(value.trim());
3207
- if (target !== void 0) {
3208
- return { name, kind: "reference", target };
3104
+ const operator = first[1];
3105
+ const isHyphenRange = tokens[0]?.[1] === HYPHEN;
3106
+ if (operator !== void 0 && operator !== "=") {
3107
+ return OPERATOR_STYLES[operator];
3209
3108
  }
3210
- return { name, kind: "command", runsGenerator: shellCommands(value).some(runsGenerator) };
3109
+ return isHyphenRange ? "^" : versionStyle(first[2], first[3], first[4]);
3211
3110
  }
3212
- function shellVariables(definitions) {
3213
- const values = /* @__PURE__ */ new Map();
3214
- for (const { name, value } of definitions) {
3215
- values.set(name, [values.get(name), value].filter((part) => part !== void 0).join(" "));
3111
+ const isLocalPackageSpec = (spec) => LOCAL_PROTOCOLS.some((protocol) => spec.startsWith(protocol));
3112
+ function migrateVersionSpec(spec, version) {
3113
+ if (KEPT_PROTOCOLS.some((protocol) => spec.startsWith(protocol))) {
3114
+ return { kind: "kept", spec, isReported: false };
3216
3115
  }
3217
- const programs = variablePrograms(definitions.map(programDefinition));
3218
- const nameAt = (word) => expandedName(word.raw);
3219
- return {
3220
- programAt: (program) => {
3221
- const name = nameAt(program);
3222
- const found = name === void 0 ? void 0 : programs.get(name);
3223
- return found === void 0 ? void 0 : { written: program.raw, program: found };
3224
- },
3225
- referenceAt: (word) => {
3226
- const name = nameAt(word);
3227
- const value = name === void 0 ? void 0 : values.get(name);
3228
- return value === void 0 ? void 0 : { written: word.raw, value };
3229
- }
3230
- };
3116
+ const style = rangeStyle(spec);
3117
+ if (style === void 0) {
3118
+ return { kind: "kept", spec, isReported: true };
3119
+ }
3120
+ return { kind: "replaced", spec: `${style}${version}` };
3231
3121
  }
3232
3122
 
3233
- const EXEC_FORM_START_RE = /^[ \t]*(?:RUN|CMD|ENTRYPOINT)(?:[ \t]+--\S+)*[ \t]+\[/gm;
3234
- const SHELLS = /* @__PURE__ */ new Set(["sh", "bash", "dash", "ash", "zsh"]);
3235
- const OPTION_PREFIX = "-";
3236
- const LONG_OPTION_PREFIX = "--";
3123
+ const NO_LONGER_INSTALLED = /* @__PURE__ */ new Map([
3124
+ ["openapi-typesafe-helpers", ["openapi-fetch", "openapi-react-query"]]
3125
+ ]);
3126
+ const ORIGINAL_NAMES = new Map(
3127
+ [...PACKAGE_RENAMES].map(([original, successor]) => [successor, original])
3128
+ );
3129
+ const PATH_SEPARATOR$6 = "/";
3130
+ function namesUninstalledPackage(text) {
3131
+ return [...NO_LONGER_INSTALLED.keys()].some(
3132
+ (name) => text.includes(name) || text.includes(ORIGINAL_NAMES.get(name) ?? name)
3133
+ );
3134
+ }
3135
+ const directoryOf = (path) => path.slice(0, path.lastIndexOf(PATH_SEPARATOR$6) + 1);
3136
+ const isAtOrAbove = (path, filePath) => filePath.startsWith(directoryOf(path));
3137
+ function declaredNames(manifest) {
3138
+ return DEPENDENCY_FIELDS.flatMap((field) => mapsAt(manifest, field)).flatMap(textEntries).map(({ key }) => key.value);
3139
+ }
3140
+ function localPackageNames(manifest) {
3141
+ return DEPENDENCY_FIELDS.flatMap((field) => mapsAt(manifest, field)).flatMap(textEntries).flatMap(
3142
+ ({ key, value }) => isScalar(value) && isLocalPackageSpec(value.value) ? [key.value] : []
3143
+ );
3144
+ }
3145
+ function localPackagesFor(manifests, filePath) {
3146
+ const nearestFirst = manifests.filter(({ path }) => isAtOrAbove(path, filePath)).sort((left, right) => directoryOf(right.path).length - directoryOf(left.path).length);
3147
+ const candidates = new Set(
3148
+ nearestFirst.flatMap(({ localPackages }) => [...localPackages ?? []])
3149
+ );
3150
+ const isLocal = (name) => nearestFirst.find(({ names }) => names?.has(name) === true)?.localPackages?.has(name) === true;
3151
+ return new Set([...candidates].filter(isLocal));
3152
+ }
3153
+ function isDeclaredFor(manifests, filePath, name) {
3154
+ const original = ORIGINAL_NAMES.get(name);
3155
+ const containing = manifests.filter(({ path }) => isAtOrAbove(path, filePath));
3156
+ const declares = ({ names }) => names === void 0 || names.has(name) || original !== void 0 && names.has(original);
3157
+ return containing.length === 0 || containing.some(declares);
3158
+ }
3159
+ const packageOfSpecifier = (specifier) => {
3160
+ const slash = specifier.indexOf(PATH_SEPARATOR$6);
3161
+ return slash === -1 ? specifier : specifier.slice(0, slash);
3162
+ };
3163
+ function undeclaredImportSteps(imports, filePath, manifests) {
3164
+ return imports.flatMap(({ specifier, line }) => {
3165
+ const name = packageOfSpecifier(specifier);
3166
+ const installers = NO_LONGER_INSTALLED.get(name);
3167
+ return installers === void 0 || isDeclaredFor(manifests, filePath, name) ? [] : [{ kind: "package-not-declared", line, name, installers }];
3168
+ });
3169
+ }
3170
+
3171
+ const EXEC_FORM_START_RE = /^[ \t]*(?:RUN|CMD|ENTRYPOINT)(?:[ \t]+--\S+)*[ \t]+\[/gm;
3172
+ const SHELLS = /* @__PURE__ */ new Set(["sh", "bash", "dash", "ash", "zsh"]);
3173
+ const OPTION_PREFIX = "-";
3174
+ const LONG_OPTION_PREFIX = "--";
3237
3175
  const COMMAND_OPTION = "c";
3238
3176
  const SET_OPTION = "-o";
3239
- const PATH_SEPARATOR$1 = "/";
3177
+ const PATH_SEPARATOR$5 = "/";
3240
3178
  const ARRAY_BLANK_RE = /(?:[ \t\r\n]|\\\r?\n)*/y;
3241
3179
  const QUOTED_RE = /"(?:[^"\\\n]|\\.)*"/y;
3242
3180
  const ITEM_SEPARATOR = ",";
@@ -3244,7 +3182,7 @@ const NOT_LINE_BREAK_RE$1 = /[^\n]/g;
3244
3182
  const ARRAY_END = "]";
3245
3183
  const VARIABLE_INSTRUCTIONS = /* @__PURE__ */ new Set(["ARG", "ENV"]);
3246
3184
  const LEGACY_ENV_INSTRUCTION = "ENV";
3247
- const ASSIGNMENT = "=";
3185
+ const ASSIGNMENT$1 = "=";
3248
3186
  const QUOTES = /* @__PURE__ */ new Set(['"', "'"]);
3249
3187
  function matchAt(pattern, text, index) {
3250
3188
  pattern.lastIndex = index;
@@ -3286,131 +3224,398 @@ function execFormAt(text, start) {
3286
3224
  index++;
3287
3225
  }
3288
3226
  }
3289
- function execForms(text) {
3290
- return [...text.matchAll(EXEC_FORM_START_RE)].flatMap((match) => {
3291
- const form = execFormAt(text, match.index + match[0].length - 1);
3292
- return form === void 0 ? [] : [form];
3293
- });
3227
+ function execForms(text) {
3228
+ return [...text.matchAll(EXEC_FORM_START_RE)].flatMap((match) => {
3229
+ const form = execFormAt(text, match.index + match[0].length - 1);
3230
+ return form === void 0 ? [] : [form];
3231
+ });
3232
+ }
3233
+ function shellScript(words) {
3234
+ const program = words[0]?.value.split(PATH_SEPARATOR$5).at(-1) ?? "";
3235
+ if (!SHELLS.has(program)) {
3236
+ return void 0;
3237
+ }
3238
+ let index = 1;
3239
+ for (let option = words[index]?.value; option !== void 0; option = words[index]?.value) {
3240
+ if (!option.startsWith(OPTION_PREFIX)) {
3241
+ return void 0;
3242
+ }
3243
+ if (!option.startsWith(LONG_OPTION_PREFIX) && option.includes(COMMAND_OPTION)) {
3244
+ return words[index + 1];
3245
+ }
3246
+ index += option === SET_OPTION ? 2 : 1;
3247
+ }
3248
+ return void 0;
3249
+ }
3250
+ function execCommands(forms) {
3251
+ const commands = [];
3252
+ const escaped = [];
3253
+ for (const { words } of forms) {
3254
+ commands.push(words);
3255
+ const script = shellScript(words);
3256
+ if (script !== void 0 && script.raw === script.value) {
3257
+ commands.push(...shellCommandsAt(script.value, script.start));
3258
+ } else if (script !== void 0) {
3259
+ escaped.push(script);
3260
+ }
3261
+ }
3262
+ return { commands, escaped };
3263
+ }
3264
+ function withoutExecForms(text, forms) {
3265
+ const parts = [];
3266
+ let copiedUpTo = 0;
3267
+ for (const { start, end } of forms) {
3268
+ parts.push(
3269
+ text.slice(copiedUpTo, start),
3270
+ text.slice(start, end).replace(NOT_LINE_BREAK_RE$1, " ")
3271
+ );
3272
+ copiedUpTo = end;
3273
+ }
3274
+ parts.push(text.slice(copiedUpTo));
3275
+ return parts.join("");
3276
+ }
3277
+ function escapedScriptSteps(scripts, lineOf) {
3278
+ return scripts.flatMap((script) => {
3279
+ const migrated = migrateCommand(script.value, "").text;
3280
+ return migrated === script.value ? [] : [{ kind: "command-string", line: lineOf(script.start), to: migrated }];
3281
+ });
3282
+ }
3283
+ function assignedVariable(word) {
3284
+ const at = word.raw.indexOf(ASSIGNMENT$1);
3285
+ if (at <= 0) {
3286
+ return void 0;
3287
+ }
3288
+ const raw = word.raw.slice(at + 1);
3289
+ const isQuoted = raw.length >= 2 && QUOTES.has(raw[0]) && raw.at(-1) === raw[0];
3290
+ const value = isQuoted ? raw.slice(1, -1) : raw;
3291
+ const start = word.start + at + 1 + (isQuoted ? 1 : 0);
3292
+ return { name: word.raw.slice(0, at), commands: shellCommandsAt(value, start) };
3293
+ }
3294
+ function dockerVariables(commands) {
3295
+ return commands.flatMap(([instruction, first, ...rest]) => {
3296
+ if (instruction === void 0 || first === void 0) {
3297
+ return [];
3298
+ }
3299
+ if (!VARIABLE_INSTRUCTIONS.has(instruction.value)) {
3300
+ return [];
3301
+ }
3302
+ if (instruction.value === LEGACY_ENV_INSTRUCTION && !first.raw.includes(ASSIGNMENT$1)) {
3303
+ return [{ name: first.value, commands: [rest] }];
3304
+ }
3305
+ return [first, ...rest].flatMap((word) => assignedVariable(word) ?? []);
3306
+ });
3307
+ }
3308
+ const definitionOf = ({ name, commands }) => ({
3309
+ name,
3310
+ value: commands.flatMap((words) => words.map((word) => word.raw)).join(" ")
3311
+ });
3312
+ function migrateDockerfile(text, file) {
3313
+ const lineOf = lineLocator(text);
3314
+ const locate = (offset) => `${file}:${lineOf(offset)}`;
3315
+ const forms = execForms(text);
3316
+ const exec = execCommands(forms);
3317
+ const shellForm = shellCommands(withoutExecForms(text, forms));
3318
+ const definitions = dockerVariables(shellForm);
3319
+ const values = definitions.flatMap((variable) => variable.commands);
3320
+ const commands = [...shellForm, ...values, ...exec.commands];
3321
+ const variables = shellVariables(definitions.map(definitionOf));
3322
+ const { edits, changes, manualSteps } = commandWordFindings(commands, locate, variables);
3323
+ const steps = [...manualSteps, ...escapedScriptSteps(exec.escaped, lineOf)];
3324
+ return { text: applyTextEdits(text, edits), changes, manualSteps: steps };
3325
+ }
3326
+
3327
+ const CATALOG_PROTOCOL = "catalog:";
3328
+ const DEFAULT_CATALOG = "default";
3329
+ function catalogOfSpec(spec) {
3330
+ if (!spec.startsWith(CATALOG_PROTOCOL)) {
3331
+ return void 0;
3332
+ }
3333
+ return spec.slice(CATALOG_PROTOCOL.length).trim() || DEFAULT_CATALOG;
3334
+ }
3335
+ class CatalogEntries {
3336
+ #keys = /* @__PURE__ */ new Set();
3337
+ add(catalog, name) {
3338
+ this.#keys.add(JSON.stringify([catalog, name]));
3339
+ }
3340
+ has(catalog, name) {
3341
+ return this.#keys.has(JSON.stringify([catalog, name]));
3342
+ }
3343
+ }
3344
+ const NO_CATALOG_PLAN = {
3345
+ inUse: new CatalogEntries(),
3346
+ kept: new CatalogEntries(),
3347
+ defined: new CatalogEntries(),
3348
+ isWorkspaceRoot: false
3349
+ };
3350
+
3351
+ const NPM_PROTOCOL = "npm:";
3352
+ const SCOPE_PREFIX$2 = "@";
3353
+ const PATH_SEPARATOR$4 = "/";
3354
+ const VERSION_SEPARATOR = "@";
3355
+ function nameEnd(text) {
3356
+ const searchFrom = text.startsWith(SCOPE_PREFIX$2) ? text.indexOf(PATH_SEPARATOR$4) + 1 : 0;
3357
+ const ends = [VERSION_SEPARATOR, PATH_SEPARATOR$4].map((separator) => text.indexOf(separator, Math.max(searchFrom, 1))).filter((index) => index !== -1);
3358
+ return ends.length === 0 ? text.length : Math.min(...ends);
3359
+ }
3360
+ function parseNpmSpec(spec) {
3361
+ if (!spec.startsWith(NPM_PROTOCOL)) {
3362
+ return void 0;
3363
+ }
3364
+ const body = spec.slice(NPM_PROTOCOL.length);
3365
+ const lead = body.startsWith(PATH_SEPARATOR$4) ? PATH_SEPARATOR$4 : "";
3366
+ const text = body.slice(lead.length);
3367
+ const end = nameEnd(text);
3368
+ const name = text.slice(0, end);
3369
+ if (!text.startsWith(VERSION_SEPARATOR, end)) {
3370
+ return { lead, name, range: void 0, rest: text.slice(end) };
3371
+ }
3372
+ const rangeEnd = text.indexOf(PATH_SEPARATOR$4, end);
3373
+ const restStart = rangeEnd === -1 ? text.length : rangeEnd;
3374
+ return { lead, name, range: text.slice(end + 1, restStart), rest: text.slice(restStart) };
3375
+ }
3376
+ function isOldPackageNpmSpec(spec) {
3377
+ const parsed = parseNpmSpec(spec);
3378
+ return parsed !== void 0 && PACKAGE_RENAMES.has(parsed.name);
3379
+ }
3380
+ function migrateNpmSpec(spec, version) {
3381
+ const parsed = parseNpmSpec(spec);
3382
+ const renamed = parsed === void 0 ? void 0 : PACKAGE_RENAMES.get(parsed.name);
3383
+ if (parsed === void 0 || renamed === void 0) {
3384
+ return void 0;
3385
+ }
3386
+ const head = `${NPM_PROTOCOL}${parsed.lead}${renamed}`;
3387
+ const isDeep = parsed.rest !== "" && parsed.rest !== PATH_SEPARATOR$4;
3388
+ if (parsed.range === void 0) {
3389
+ return { spec: `${head}${parsed.rest}`, isReported: false, isDeep };
3390
+ }
3391
+ const decision = migrateVersionSpec(parsed.range, version);
3392
+ const isReported = decision.kind === "kept" && decision.isReported;
3393
+ const migrated = `${head}${VERSION_SEPARATOR}${decision.spec}${parsed.rest}`;
3394
+ return { spec: migrated, isReported, isDeep };
3395
+ }
3396
+
3397
+ const SCHEME_SEPARATOR = "://";
3398
+ const HIDDEN_USERINFO = "***";
3399
+ const USERINFO_END = "@";
3400
+ const AUTHORITY_END_RE = /[/?#"'`\s]/;
3401
+ function scanAuthority(text, start) {
3402
+ let userinfoEnd = -1;
3403
+ let end = start;
3404
+ for (; end < text.length && !AUTHORITY_END_RE.test(text.charAt(end)); end++) {
3405
+ if (text.charAt(end) === USERINFO_END) {
3406
+ userinfoEnd = end;
3407
+ }
3408
+ }
3409
+ return { end, userinfoEnd };
3410
+ }
3411
+ function hideUrlCredentials(text) {
3412
+ let hidden = "";
3413
+ let copiedUpTo = 0;
3414
+ for (let found = text.indexOf(SCHEME_SEPARATOR); found !== -1; ) {
3415
+ const start = found + SCHEME_SEPARATOR.length;
3416
+ const { end, userinfoEnd } = scanAuthority(text, start);
3417
+ if (userinfoEnd !== -1) {
3418
+ hidden += `${text.slice(copiedUpTo, start)}${HIDDEN_USERINFO}`;
3419
+ copiedUpTo = userinfoEnd;
3420
+ }
3421
+ found = text.indexOf(SCHEME_SEPARATOR, end);
3422
+ }
3423
+ return `${hidden}${text.slice(copiedUpTo)}`;
3424
+ }
3425
+
3426
+ const newFindings = () => ({ edits: [], changes: [], manualSteps: [] });
3427
+ const PEER_FIELD = "peerDependencies";
3428
+ const mapLabel = (map) => map.kind === "catalog" ? map.catalog : map.field;
3429
+ const renamedKey = (name, map) => map.kind === "imports" ? renamedSpecifier(name)?.to : PACKAGE_RENAMES.get(name);
3430
+ function migrateSpec(spec, version) {
3431
+ const npm = migrateNpmSpec(spec, version);
3432
+ if (npm !== void 0) {
3433
+ return { spec: npm.spec, isReported: npm.isReported };
3434
+ }
3435
+ const decision = migrateVersionSpec(spec, version);
3436
+ return { spec: decision.spec, isReported: decision.kind === "kept" && decision.isReported };
3437
+ }
3438
+ function isFree({ from, to, field, names }, findings) {
3439
+ if (names.has(to)) {
3440
+ findings.manualSteps.push({ kind: "name-collision", field, from, to });
3441
+ return false;
3442
+ }
3443
+ return true;
3444
+ }
3445
+ function migrateAlias(entry, { map, version, findings }) {
3446
+ const migrated = entry.spec === void 0 ? void 0 : migrateNpmSpec(entry.spec.value, version);
3447
+ if (entry.spec === void 0 || migrated === void 0) {
3448
+ return;
3449
+ }
3450
+ findings.edits.push(entry.spec.replace(migrated.spec));
3451
+ const spec = hideUrlCredentials(migrated.spec);
3452
+ findings.changes.push({ kind: "alias-renamed", field: mapLabel(map), name: entry.name, spec });
3453
+ if (migrated.isReported) {
3454
+ findings.manualSteps.push({ kind: "spec-kept", name: entry.name, spec });
3455
+ }
3456
+ }
3457
+ function renamedChange(map, from, to, spec) {
3458
+ switch (map.kind) {
3459
+ case "dependencies":
3460
+ return { kind: "dependency-renamed", field: map.field, from, to, spec };
3461
+ case "catalog":
3462
+ return { kind: "catalog-entry-renamed", catalog: map.catalog, from, to, spec };
3463
+ case "imports":
3464
+ return { kind: "import-renamed", field: map.field, from, to, target: spec };
3465
+ default:
3466
+ return assertNever(map);
3467
+ }
3468
+ }
3469
+ function migrateEntry(entry, spec, to, { map, version, findings }) {
3470
+ const old = spec.value;
3471
+ const migrated = migrateSpec(old, version);
3472
+ findings.edits.push(entry.renameKey(to));
3473
+ if (migrated.spec !== old) {
3474
+ findings.edits.push(spec.replace(migrated.spec));
3475
+ }
3476
+ findings.changes.push(renamedChange(map, entry.name, to, hideUrlCredentials(migrated.spec)));
3477
+ if (migrated.isReported) {
3478
+ findings.manualSteps.push({ kind: "spec-kept", name: to, spec: hideUrlCredentials(old) });
3479
+ }
3480
+ if (map.kind === "dependencies" && map.field === PEER_FIELD) {
3481
+ findings.manualSteps.push({ kind: "peer-dependency-renamed", name: to });
3482
+ }
3483
+ }
3484
+ function catalogHold(entry, spec, { map, catalogs }) {
3485
+ if (map.kind === "catalog") {
3486
+ const isInUse = catalogs.inUse.has(map.name, entry.name);
3487
+ return isInUse ? { kind: "catalog-entry-in-use", catalog: map.catalog, name: entry.name } : void 0;
3488
+ }
3489
+ const catalog = map.kind === "dependencies" ? catalogOfSpec(spec.value) : void 0;
3490
+ if (catalog === void 0) {
3491
+ return void 0;
3492
+ }
3493
+ const reference = { field: mapLabel(map), name: entry.name, catalog };
3494
+ if (!catalogs.defined.has(catalog, entry.name)) {
3495
+ const kind = catalogs.isWorkspaceRoot ? "catalog-entry-missing" : "catalog-entry-outside";
3496
+ return { kind, ...reference };
3497
+ }
3498
+ return catalogs.kept.has(catalog, entry.name) ? { kind: "catalog-reference-kept", ...reference } : void 0;
3499
+ }
3500
+ function localPackageHold(entry, spec, { map }) {
3501
+ return map.kind === "dependencies" && isLocalPackageSpec(spec.value) ? { kind: "local-package", field: map.field, name: entry.name, spec: spec.value } : void 0;
3294
3502
  }
3295
- function shellScript(words) {
3296
- const program = words[0]?.value.split(PATH_SEPARATOR$1).at(-1) ?? "";
3297
- if (!SHELLS.has(program)) {
3298
- return void 0;
3299
- }
3300
- let index = 1;
3301
- for (let option = words[index]?.value; option !== void 0; option = words[index]?.value) {
3302
- if (!option.startsWith(OPTION_PREFIX)) {
3303
- return void 0;
3503
+ const holdOf = (entry, spec, context) => localPackageHold(entry, spec, context) ?? catalogHold(entry, spec, context);
3504
+ function migrateSpecMap(entries, context) {
3505
+ const names = new Set(entries.map((entry) => entry.name));
3506
+ for (const entry of entries) {
3507
+ const to = renamedKey(entry.name, context.map);
3508
+ const field = mapLabel(context.map);
3509
+ if (to === void 0) {
3510
+ migrateAlias(entry, context);
3511
+ continue;
3304
3512
  }
3305
- if (!option.startsWith(LONG_OPTION_PREFIX) && option.includes(COMMAND_OPTION)) {
3306
- return words[index + 1];
3513
+ if (!isFree({ from: entry.name, to, field, names }, context.findings)) {
3514
+ continue;
3307
3515
  }
3308
- index += option === SET_OPTION ? 2 : 1;
3309
- }
3310
- return void 0;
3311
- }
3312
- function execCommands(forms) {
3313
- const commands = [];
3314
- const escaped = [];
3315
- for (const { words } of forms) {
3316
- commands.push(words);
3317
- const script = shellScript(words);
3318
- if (script !== void 0 && script.raw === script.value) {
3319
- commands.push(...shellCommandsAt(script.value, script.start));
3320
- } else if (script !== void 0) {
3321
- escaped.push(script);
3516
+ const hold = entry.spec === void 0 ? void 0 : holdOf(entry, entry.spec, context);
3517
+ if (entry.spec === void 0) {
3518
+ context.findings.manualSteps.push({ kind: "entry-not-edited", field, name: entry.name });
3519
+ } else if (hold !== void 0) {
3520
+ context.findings.manualSteps.push(hold);
3521
+ } else {
3522
+ migrateEntry(entry, entry.spec, to, context);
3322
3523
  }
3323
3524
  }
3324
- return { commands, escaped };
3325
3525
  }
3326
- function withoutExecForms(text, forms) {
3327
- const parts = [];
3328
- let copiedUpTo = 0;
3329
- for (const { start, end } of forms) {
3330
- parts.push(
3331
- text.slice(copiedUpTo, start),
3332
- text.slice(start, end).replace(NOT_LINE_BREAK_RE$1, " ")
3333
- );
3334
- copiedUpTo = end;
3526
+ function renameNames(entries, field, findings) {
3527
+ const names = new Set(entries.map((entry) => entry.name));
3528
+ for (const entry of entries) {
3529
+ const to = PACKAGE_RENAMES.get(entry.name);
3530
+ if (to !== void 0 && isFree({ from: entry.name, to, field, names }, findings)) {
3531
+ findings.edits.push(entry.rename(to));
3532
+ findings.changes.push({
3533
+ kind: "package-name-renamed",
3534
+ location: field,
3535
+ from: entry.name,
3536
+ to
3537
+ });
3538
+ }
3335
3539
  }
3336
- parts.push(text.slice(copiedUpTo));
3337
- return parts.join("");
3338
3540
  }
3339
- function escapedScriptSteps(scripts, lineOf) {
3340
- return scripts.flatMap((script) => {
3341
- const migrated = migrateCommand(script.value, "").text;
3342
- return migrated === script.value ? [] : [{ kind: "command-string", line: lineOf(script.start), to: migrated }];
3541
+
3542
+ const isEditable = (node) => node.kind === "scalar" && node.encode !== void 0;
3543
+ const replaceScalar = (scalar, value) => ({
3544
+ start: scalar.start,
3545
+ end: scalar.end,
3546
+ text: scalar.encode(value)
3547
+ });
3548
+ function specEntries(map) {
3549
+ return textEntries(map).flatMap(({ key, value }) => {
3550
+ if (!isEditable(key)) {
3551
+ return [];
3552
+ }
3553
+ const spec = isEditable(value) ? { value: value.value, replace: (to) => replaceScalar(value, to) } : void 0;
3554
+ return [{ name: key.value, spec, renameKey: (name) => replaceScalar(key, name) }];
3343
3555
  });
3344
3556
  }
3345
- function assignedVariable(word) {
3346
- const at = word.raw.indexOf(ASSIGNMENT);
3347
- if (at <= 0) {
3348
- return void 0;
3557
+ function keyEntries(map) {
3558
+ return textEntries(map).map(({ key }) => key).filter(isEditable).map((key) => ({ name: key.value, rename: (name) => replaceScalar(key, name) }));
3559
+ }
3560
+ function itemEntries(list) {
3561
+ return list.items.filter(isEditable).map((item) => ({ name: item.value, rename: (name) => replaceScalar(item, name) }));
3562
+ }
3563
+ function nestedKeys(node) {
3564
+ if (node.kind !== "map") {
3565
+ return [];
3349
3566
  }
3350
- const raw = word.raw.slice(at + 1);
3351
- const isQuoted = raw.length >= 2 && QUOTES.has(raw[0]) && raw.at(-1) === raw[0];
3352
- const value = isQuoted ? raw.slice(1, -1) : raw;
3353
- const start = word.start + at + 1 + (isQuoted ? 1 : 0);
3354
- return { name: word.raw.slice(0, at), commands: shellCommandsAt(value, start) };
3567
+ return textEntries(node).flatMap(({ key, value }) => [key.value, ...nestedKeys(value)]);
3355
3568
  }
3356
- function dockerVariables(commands) {
3357
- return commands.flatMap(([instruction, first, ...rest]) => {
3358
- if (instruction === void 0 || first === void 0) {
3359
- return [];
3360
- }
3361
- if (!VARIABLE_INSTRUCTIONS.has(instruction.value)) {
3362
- return [];
3363
- }
3364
- if (instruction.value === LEGACY_ENV_INSTRUCTION && !first.raw.includes(ASSIGNMENT)) {
3365
- return [{ name: first.value, commands: [rest] }];
3569
+
3570
+ const IMPORTS_FIELD$1 = "imports";
3571
+ const SCOPES_FIELD = "scopes";
3572
+ const FIELD_SEPARATOR$2 = ".";
3573
+ function migrateImports(config, version, findings) {
3574
+ for (const imports of mapsAt(config, IMPORTS_FIELD$1)) {
3575
+ const map = { kind: "imports", field: IMPORTS_FIELD$1 };
3576
+ migrateSpecMap(specEntries(imports), { map, version, findings, catalogs: NO_CATALOG_PLAN });
3577
+ }
3578
+ for (const scopes of mapsAt(config, SCOPES_FIELD)) {
3579
+ for (const { key, value } of textEntries(scopes)) {
3580
+ if (isMap(value)) {
3581
+ const field = [SCOPES_FIELD, key.value].join(FIELD_SEPARATOR$2);
3582
+ const map = { kind: "imports", field };
3583
+ migrateSpecMap(specEntries(value), { map, version, findings, catalogs: NO_CATALOG_PLAN });
3584
+ }
3366
3585
  }
3367
- return [first, ...rest].flatMap((word) => assignedVariable(word) ?? []);
3586
+ }
3587
+ }
3588
+ const PATH_SEPARATOR$3 = "/";
3589
+ const SCOPE_PREFIX$1 = "@";
3590
+ function importMapKeys(config) {
3591
+ const scoped = mapsAt(config, SCOPES_FIELD).flatMap(textEntries).flatMap(({ value }) => isMap(value) ? [value] : []);
3592
+ return [...mapsAt(config, IMPORTS_FIELD$1), ...scoped].flatMap(textEntries).map(({ key }) => key.value);
3593
+ }
3594
+ function importMapNames(config) {
3595
+ return importMapKeys(config).map((key) => {
3596
+ const parts = key.split(PATH_SEPARATOR$3);
3597
+ return key.startsWith(SCOPE_PREFIX$1) ? parts.slice(0, 2).join(PATH_SEPARATOR$3) : parts[0] ?? key;
3368
3598
  });
3369
3599
  }
3370
- const definitionOf = ({ name, commands }) => ({
3371
- name,
3372
- value: commands.flatMap((words) => words.map((word) => word.raw)).join(" ")
3373
- });
3374
- function migrateDockerfile(text, file) {
3375
- const lineOf = lineLocator(text);
3376
- const locate = (offset) => `${file}:${lineOf(offset)}`;
3377
- const forms = execForms(text);
3378
- const exec = execCommands(forms);
3379
- const shellForm = shellCommands(withoutExecForms(text, forms));
3380
- const definitions = dockerVariables(shellForm);
3381
- const values = definitions.flatMap((variable) => variable.commands);
3382
- const commands = [...shellForm, ...values, ...exec.commands];
3383
- const variables = shellVariables(definitions.map(definitionOf));
3384
- const { edits, changes, manualSteps } = commandWordFindings(commands, locate, variables);
3385
- const steps = [...manualSteps, ...escapedScriptSteps(exec.escaped, lineOf)];
3386
- return { text: applyTextEdits(text, edits), changes, manualSteps: steps };
3600
+ function migrateImportMap(text, read, version) {
3601
+ if (read.kind === "unreadable") {
3602
+ return unreadableConfig(text, read.offset);
3603
+ }
3604
+ const findings = newFindings();
3605
+ for (const config of read.documents.filter(isMap)) {
3606
+ migrateImports(config, version, findings);
3607
+ }
3608
+ const { edits, changes, manualSteps } = findings;
3609
+ return { text: applyTextEdits(text, edits), changes, manualSteps };
3387
3610
  }
3388
3611
 
3389
3612
  const IMPORT_MAP_FIELD = "importMap";
3390
- const SEPARATOR = "/";
3391
- const CURRENT_FOLDER = ".";
3392
- const PARENT_FOLDER = "..";
3393
- const URL_SCHEME = /^[a-z][a-z\d+.-]*:/i;
3394
3613
  function importMapReferences(roots) {
3395
3614
  return roots.flatMap((root) => valuesAt(root, IMPORT_MAP_FIELD)).flatMap((value) => isScalar(value) ? [value.value] : []);
3396
3615
  }
3397
- function resolvedPath(configPath, reference) {
3398
- const parts = configPath.split(SEPARATOR).slice(0, -1);
3399
- for (const part of reference.split(SEPARATOR)) {
3400
- if (part === PARENT_FOLDER) {
3401
- if (parts.pop() === void 0) {
3402
- return void 0;
3403
- }
3404
- } else if (part !== CURRENT_FOLDER && part !== "") {
3405
- parts.push(part);
3406
- }
3407
- }
3408
- return parts.join(SEPARATOR);
3409
- }
3410
3616
  function importMapLocation(configPath, reference) {
3411
- const isOutside = URL_SCHEME.test(reference) || reference.startsWith(SEPARATOR);
3412
- const path = isOutside ? void 0 : resolvedPath(configPath, reference);
3413
- return path === void 0 || path === "" ? { kind: "not-editable", reference } : { kind: "in-project", path };
3617
+ const path = referencedProjectPath(configPath, reference);
3618
+ return path === void 0 ? { kind: "not-editable", reference } : { kind: "in-project", path };
3414
3619
  }
3415
3620
  const importMapLocations = (configPath, roots) => importMapReferences(roots).map((reference) => importMapLocation(configPath, reference));
3416
3621
  function importMapSteps(configPath, read) {
@@ -3488,14 +3693,15 @@ function unrenamedSteps(found, line, dynamic) {
3488
3693
  const to = name === void 0 ? void 0 : PACKAGE_RENAMES.get(name);
3489
3694
  return name === void 0 || to === void 0 ? [] : [{ kind: "old-package-import", line, specifier: found.value, name, to }];
3490
3695
  }
3491
- function migrateModuleSpecifiers(text, strings, version) {
3696
+ function migrateModuleSpecifiers(text, strings, version, kept = /* @__PURE__ */ new Set()) {
3492
3697
  const changes = [];
3493
3698
  const manualSteps = [];
3494
3699
  let migrated = "";
3495
3700
  let copiedUpTo = 0;
3496
3701
  const lineOf = lineLocator(text);
3497
3702
  for (const found of distinctStrings(strings)) {
3498
- const renamed = renamedValue(found.value, version);
3703
+ const isKept = kept.has(packageOfSpecifier(found.value));
3704
+ const renamed = isKept ? void 0 : renamedValue(found.value, version);
3499
3705
  const line = lineOf(found.start);
3500
3706
  const dynamic = found.isModuleSpecifier ? dynamicPackage(found.value) : void 0;
3501
3707
  if (renamed === void 0) {
@@ -3568,23 +3774,281 @@ function textsAt(node, path) {
3568
3774
  return [];
3569
3775
  }
3570
3776
  }
3571
- const jestConfigSteps = (node, path) => textsAt(node, path).flatMap(
3572
- ({ value, path: at }) => patternSteps(value, at.join(FIELD_SEPARATOR$1), false)
3573
- );
3574
- const jestJsonSteps = (read) => read.kind === "read" ? read.documents.filter(isMap).flatMap((root) => jestConfigSteps(root, [])) : [];
3575
-
3576
- function migrateJsonCommands(text, strings, patterns) {
3577
- const changes = [];
3578
- const manualSteps = [];
3579
- const edits = [];
3580
- for (const found of strings.filter((string) => isCommandPath(string.path, patterns))) {
3581
- const migrated = migrateCommand(found.value, found.path.join("."));
3582
- changes.push(...migrated.changes);
3583
- manualSteps.push(...migrated.manualSteps);
3584
- if (migrated.text !== found.value) {
3585
- edits.push({ start: found.start, end: found.end, text: JSON.stringify(migrated.text) });
3586
- }
3777
+ const jestConfigSteps = (node, path) => textsAt(node, path).flatMap(
3778
+ ({ value, path: at }) => patternSteps(value, at.join(FIELD_SEPARATOR$1), false)
3779
+ );
3780
+ const jestJsonSteps = (read) => read.kind === "read" ? read.documents.filter(isMap).flatMap((root) => jestConfigSteps(root, [])) : [];
3781
+
3782
+ function migrateJsonCommands(text, strings, patterns) {
3783
+ const changes = [];
3784
+ const manualSteps = [];
3785
+ const edits = [];
3786
+ for (const found of strings.filter((string) => isCommandPath(string.path, patterns))) {
3787
+ const migrated = migrateCommand(found.value, found.path.join("."));
3788
+ changes.push(...migrated.changes);
3789
+ manualSteps.push(...migrated.manualSteps);
3790
+ if (migrated.text !== found.value) {
3791
+ edits.push({ start: found.start, end: found.end, text: JSON.stringify(migrated.text) });
3792
+ }
3793
+ }
3794
+ return { text: applyTextEdits(text, edits), changes, manualSteps };
3795
+ }
3796
+
3797
+ const DELIMITERS = ['"""', "'''", "```", '"', "'", "`"];
3798
+ const BACKTICK = "`";
3799
+ const ESCAPING_DELIMITER = '"';
3800
+ const ESCAPE = "\\";
3801
+ const COMMENT_START$1 = "#";
3802
+ const LINE_BREAK$2 = "\n";
3803
+ function closingOffset(text, delimiter, from) {
3804
+ if (delimiter !== ESCAPING_DELIMITER) {
3805
+ const close = text.indexOf(delimiter, from);
3806
+ return close === -1 ? void 0 : close;
3807
+ }
3808
+ for (let index = from; index < text.length; index += 1) {
3809
+ const char = text[index];
3810
+ if (char === ESCAPE) {
3811
+ index += 1;
3812
+ } else if (char === ESCAPING_DELIMITER || char === LINE_BREAK$2) {
3813
+ return char === ESCAPING_DELIMITER ? index : void 0;
3814
+ }
3815
+ }
3816
+ return void 0;
3817
+ }
3818
+ function literalAt(text, offset) {
3819
+ const delimiter = DELIMITERS.find((candidate) => text.startsWith(candidate, offset));
3820
+ if (delimiter === void 0) {
3821
+ return void 0;
3822
+ }
3823
+ const contentStart = offset + delimiter.length;
3824
+ const close = closingOffset(text, delimiter, contentStart);
3825
+ if (close === void 0) {
3826
+ return void 0;
3827
+ }
3828
+ const kind = delimiter.startsWith(BACKTICK) ? "backtick" : "string";
3829
+ const content = text.slice(contentStart, close);
3830
+ return { kind, start: offset, end: close + delimiter.length, contentStart, content };
3831
+ }
3832
+ function expressionAt(text, start) {
3833
+ const literals = [];
3834
+ let index = start;
3835
+ while (index < text.length && text[index] !== LINE_BREAK$2 && text[index] !== COMMENT_START$1) {
3836
+ const literal = literalAt(text, index);
3837
+ literals.push(...literal === void 0 ? [] : [literal]);
3838
+ index = literal?.end ?? index + 1;
3839
+ }
3840
+ return { literals, end: index };
3841
+ }
3842
+ function literalsBetween(text, start, end) {
3843
+ const literals = [];
3844
+ for (let index = start; index < end; ) {
3845
+ const literal = literalAt(text, index);
3846
+ literals.push(...literal === void 0 ? [] : [literal]);
3847
+ index = literal?.end ?? index + 1;
3848
+ }
3849
+ return literals;
3850
+ }
3851
+ const INTERPOLATION_OPEN = "{{";
3852
+ const INTERPOLATION_CLOSE = "}}";
3853
+ function interpolationAt(text, start) {
3854
+ const literals = [];
3855
+ for (let index = start + INTERPOLATION_OPEN.length; index < text.length; ) {
3856
+ if (text.startsWith(INTERPOLATION_CLOSE, index)) {
3857
+ return { start, end: index + INTERPOLATION_CLOSE.length, literals };
3858
+ }
3859
+ if (text[index] === LINE_BREAK$2) {
3860
+ return void 0;
3861
+ }
3862
+ const literal = literalAt(text, index);
3863
+ literals.push(...literal === void 0 ? [] : [literal]);
3864
+ index = literal?.end ?? index + 1;
3865
+ }
3866
+ return void 0;
3867
+ }
3868
+ function interpolationsOf(text) {
3869
+ const found = [];
3870
+ for (let open = text.indexOf(INTERPOLATION_OPEN); open !== -1; ) {
3871
+ const interpolation = interpolationAt(text, open);
3872
+ found.push(...interpolation === void 0 ? [] : [interpolation]);
3873
+ const next = interpolation?.end ?? open + INTERPOLATION_OPEN.length;
3874
+ open = text.indexOf(INTERPOLATION_OPEN, next);
3875
+ }
3876
+ return found;
3877
+ }
3878
+
3879
+ const RECIPE_NAME_RE = /^@?[A-Za-z_][A-Za-z0-9_-]*(?=[ \t:])/gm;
3880
+ const PARAMETER_RE = /^[+*$]*([A-Za-z_][A-Za-z0-9_-]*)(?:=([\s\S]*))?$/;
3881
+ const NAME_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
3882
+ const COLON = ":";
3883
+ const EQUALS = "=";
3884
+ const OPEN_PAREN = "(";
3885
+ const CLOSE_PAREN = ")";
3886
+ const COMMENT_START = "#";
3887
+ const LINE_BREAK$1 = "\n";
3888
+ const BLANK_RE = /[ \t]/;
3889
+ const depthChange = (char) => char === OPEN_PAREN ? 1 : char === CLOSE_PAREN ? -1 : 0;
3890
+ function headerColon(text, from) {
3891
+ for (let index = from; index < text.length && text[index] !== LINE_BREAK$1; ) {
3892
+ const literal = literalAt(text, index);
3893
+ const char = text[index];
3894
+ if (literal !== void 0) {
3895
+ index = literal.end;
3896
+ continue;
3897
+ }
3898
+ if (char === COMMENT_START || char === COLON && text[index + 1] === EQUALS) {
3899
+ return void 0;
3900
+ }
3901
+ if (char === COLON) {
3902
+ return index;
3903
+ }
3904
+ index += 1;
3905
+ }
3906
+ return void 0;
3907
+ }
3908
+ function parameterWords(text, start, end) {
3909
+ const words = [];
3910
+ let wordStart;
3911
+ let depth = 0;
3912
+ for (let index = start; index < end; ) {
3913
+ const isBreak = depth === 0 && BLANK_RE.test(text[index] ?? "");
3914
+ if (isBreak && wordStart !== void 0) {
3915
+ words.push({ start: wordStart, end: index });
3916
+ }
3917
+ wordStart = isBreak ? void 0 : wordStart ?? index;
3918
+ const literal = isBreak ? void 0 : literalAt(text, index);
3919
+ depth += literal === void 0 ? depthChange(text[index]) : 0;
3920
+ index = literal?.end ?? index + 1;
3921
+ }
3922
+ return wordStart === void 0 ? words : [...words, { start: wordStart, end }];
3923
+ }
3924
+ function parameterDefault(text, word) {
3925
+ const written = text.slice(word.start, word.end);
3926
+ const match = PARAMETER_RE.exec(written);
3927
+ const value = match?.[2];
3928
+ if (match === null || value === void 0) {
3929
+ return [];
3930
+ }
3931
+ const defaultStart = word.end - value.length;
3932
+ const literals = literalsBetween(text, defaultStart, word.end);
3933
+ const target = NAME_RE.test(value) ? value : void 0;
3934
+ return [{ name: match[1], literals, target }];
3935
+ }
3936
+ function recipeHeaders(text) {
3937
+ return [...text.matchAll(RECIPE_NAME_RE)].flatMap((match) => {
3938
+ const nameEnd = match.index + match[0].length;
3939
+ const colon = headerColon(text, nameEnd);
3940
+ if (colon === void 0) {
3941
+ return [];
3942
+ }
3943
+ const words = parameterWords(text, nameEnd, colon);
3944
+ const defaults = words.flatMap((word) => parameterDefault(text, word));
3945
+ return [{ start: match.index, end: colon, defaults }];
3946
+ });
3947
+ }
3948
+
3949
+ const ASSIGNMENT_RE$1 = /^(?:export[ \t]+)?([A-Za-z_][A-Za-z0-9_-]*)[ \t]*:=[ \t]*/gm;
3950
+ const NAME_VALUE_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
3951
+ const VARIABLE_INTERPOLATION_RE = /^[@+-]*\{\{[ \t]*([A-Za-z_][A-Za-z0-9_-]*)[ \t]*\}\}$/;
3952
+ const MASK$1 = "_";
3953
+ const BLANK = " ";
3954
+ function assignmentsOf$1(text) {
3955
+ return [...text.matchAll(ASSIGNMENT_RE$1)].map((match) => {
3956
+ const valueStart = match.index + match[0].length;
3957
+ const { literals, end } = expressionAt(text, valueStart);
3958
+ const value = text.slice(valueStart, end).trim();
3959
+ return { name: match[1], value, start: match.index, end, literals };
3960
+ });
3961
+ }
3962
+ function onlyString({ value, literals }) {
3963
+ const [literal] = literals;
3964
+ if (literals.length !== 1 || literal?.kind !== "string") {
3965
+ return void 0;
3966
+ }
3967
+ return value.length === literal.end - literal.start ? literal : void 0;
3968
+ }
3969
+ const stringRunsGenerator = (literal) => literal.kind === "string" && shellCommands(literal.content).some(runsGenerator);
3970
+ function computedDefinitions(name, literals) {
3971
+ const other = { name, kind: "command", runsGenerator: false };
3972
+ const isMaybeGenerator = literals.some(stringRunsGenerator);
3973
+ return isMaybeGenerator ? [{ name, kind: "command", runsGenerator: true }, other] : [other];
3974
+ }
3975
+ function assignmentDefinitions(assignment) {
3976
+ const { name, value } = assignment;
3977
+ if (NAME_VALUE_RE.test(value)) {
3978
+ return [{ name, kind: "reference", target: value }];
3979
+ }
3980
+ const literal = onlyString(assignment);
3981
+ return literal === void 0 ? computedDefinitions(name, assignment.literals) : [{ name, kind: "command", runsGenerator: stringRunsGenerator(literal) }];
3982
+ }
3983
+ function parameterDefinitions({ name, literals, target }) {
3984
+ const fromCommandLine = computedDefinitions(name, literals);
3985
+ return target === void 0 ? fromCommandLine : [{ name, kind: "reference", target }, ...fromCommandLine];
3986
+ }
3987
+ function shellText$1(text, spans) {
3988
+ const parts = [];
3989
+ let copiedUpTo = 0;
3990
+ for (const { start, end } of [...spans].sort((left, right) => left.start - right.start)) {
3991
+ parts.push(text.slice(copiedUpTo, start), BLANK.repeat(end - start));
3992
+ copiedUpTo = end;
3993
+ }
3994
+ parts.push(text.slice(copiedUpTo));
3995
+ const blanked = parts.join("");
3996
+ const interpolations = interpolationsOf(blanked);
3997
+ const masked = interpolations.reduce(
3998
+ (current, { start, end }) => current.slice(0, start) + MASK$1.repeat(end - start) + current.slice(end),
3999
+ blanked
4000
+ );
4001
+ return { text: masked, interpolations };
4002
+ }
4003
+ const literalCommands = (literals) => literals.flatMap((literal) => shellCommandsAt(literal.content, literal.contentStart));
4004
+ function variableValues$1(assignments) {
4005
+ const values = /* @__PURE__ */ new Map();
4006
+ for (const assignment of assignments) {
4007
+ const value = onlyString(assignment)?.content ?? assignment.value;
4008
+ values.set(assignment.name, [values.get(assignment.name), value].join(" ").trim());
3587
4009
  }
4010
+ return values;
4011
+ }
4012
+ function computedProgram(written) {
4013
+ const literals = interpolationsOf(written).flatMap((interpolation) => interpolation.literals);
4014
+ return literals.some(stringRunsGenerator) ? "generator-or-other" : void 0;
4015
+ }
4016
+ function justfileVariables(text, values, programs) {
4017
+ const writtenAt = (word) => text.slice(word.start, word.end);
4018
+ const nameAt = (word) => VARIABLE_INTERPOLATION_RE.exec(writtenAt(word))?.[1];
4019
+ return {
4020
+ programAt: (word) => {
4021
+ const name = nameAt(word);
4022
+ const written = writtenAt(word);
4023
+ const found = name === void 0 ? computedProgram(written) : programs.get(name);
4024
+ return found === void 0 ? void 0 : { written, program: found };
4025
+ },
4026
+ referenceAt: (word) => {
4027
+ const name = nameAt(word);
4028
+ const value = name === void 0 ? void 0 : values.get(name);
4029
+ return value === void 0 ? void 0 : { written: writtenAt(word), value };
4030
+ }
4031
+ };
4032
+ }
4033
+ function migrateJustfile(text, file) {
4034
+ const lineOf = lineLocator(text);
4035
+ const assignments = assignmentsOf$1(text);
4036
+ const headers = recipeHeaders(text);
4037
+ const defaults = headers.flatMap((header) => header.defaults);
4038
+ const programs = variablePrograms([
4039
+ ...assignments.flatMap(assignmentDefinitions),
4040
+ ...defaults.flatMap(parameterDefinitions)
4041
+ ]);
4042
+ const variables = justfileVariables(text, variableValues$1(assignments), programs);
4043
+ const shell = shellText$1(text, [...assignments, ...headers]);
4044
+ const commands = [
4045
+ ...shellCommands(shell.text),
4046
+ ...literalCommands(assignments.flatMap((assignment) => assignment.literals)),
4047
+ ...literalCommands(defaults.flatMap((found) => found.literals)),
4048
+ ...literalCommands(shell.interpolations.flatMap((found) => found.literals))
4049
+ ];
4050
+ const locate = (offset) => `${file}:${lineOf(offset)}`;
4051
+ const { edits, changes, manualSteps } = commandWordFindings(commands, locate, variables);
3588
4052
  return { text: applyTextEdits(text, edits), changes, manualSteps };
3589
4053
  }
3590
4054
 
@@ -3699,7 +4163,7 @@ function makefileVariables(text, assignments, programs) {
3699
4163
  const variables = {
3700
4164
  programAt: (program) => {
3701
4165
  const name = nameAt(program);
3702
- const found = name === void 0 ? void 0 : programs.get(name);
4166
+ const found = name === void 0 ? void 0 : programs.get(name) ?? "generator-or-other";
3703
4167
  const written = text.slice(program.start, program.end);
3704
4168
  return found === void 0 ? void 0 : { written, program: found };
3705
4169
  },
@@ -3723,6 +4187,140 @@ function migrateMakefile(text, file) {
3723
4187
  return { text: applyTextEdits(text, edits), changes, manualSteps };
3724
4188
  }
3725
4189
 
4190
+ const PARENT_SEPARATOR = ">";
4191
+ const PATH_SEPARATOR$2 = "/";
4192
+ const SCOPE_PREFIX = "@";
4193
+ function overrideTarget(key) {
4194
+ const child = key.split(PARENT_SEPARATOR).at(-1) ?? key;
4195
+ const segments = child.split(PATH_SEPARATOR$2);
4196
+ const isScoped = segments.length > 1 && segments.at(-2)?.startsWith(SCOPE_PREFIX) === true;
4197
+ const name = isScoped ? segments.slice(-2).join(PATH_SEPARATOR$2) : segments.at(-1) ?? child;
4198
+ const versionAt = name.indexOf(SCOPE_PREFIX, 1);
4199
+ return versionAt === -1 ? name : name.slice(0, versionAt);
4200
+ }
4201
+ function overriddenPackages(keys) {
4202
+ const targets = keys.map(overrideTarget);
4203
+ return [...new Set(targets.filter((target) => PACKAGE_RENAMES.has(target)))];
4204
+ }
4205
+
4206
+ const PATH_SEPARATOR$1 = ".";
4207
+ function catalogMaps(root, prefix = "") {
4208
+ const maps = mapsAt(root, CATALOG_FIELD).map((map) => ({
4209
+ name: DEFAULT_CATALOG,
4210
+ label: `${prefix}${CATALOG_FIELD}`,
4211
+ map
4212
+ }));
4213
+ for (const catalogs of mapsAt(root, NAMED_CATALOGS_FIELD)) {
4214
+ for (const { key, value } of textEntries(catalogs)) {
4215
+ if (isMap(value)) {
4216
+ const label = [prefix + NAMED_CATALOGS_FIELD, key.value].join(PATH_SEPARATOR$1);
4217
+ maps.push({ name: key.value, label, map: value });
4218
+ }
4219
+ }
4220
+ }
4221
+ return maps;
4222
+ }
4223
+ const manifestCatalogMaps = (manifest) => [
4224
+ ...catalogMaps(manifest),
4225
+ ...mapsAt(manifest, WORKSPACES_FIELD).flatMap(
4226
+ (workspaces) => catalogMaps(workspaces, `${WORKSPACES_FIELD}${PATH_SEPARATOR$1}`)
4227
+ )
4228
+ ];
4229
+ function entriesInUse(manifests) {
4230
+ const inUse = new CatalogEntries();
4231
+ const fields = manifests.flatMap(
4232
+ (manifest) => DEPENDENCY_FIELDS.flatMap((field) => mapsAt(manifest, field))
4233
+ );
4234
+ for (const dependencies of fields) {
4235
+ const entries = specEntries(dependencies);
4236
+ const names = new Set(entries.map((entry) => entry.name));
4237
+ for (const { name, spec } of entries) {
4238
+ const catalog = spec === void 0 ? void 0 : catalogOfSpec(spec.value);
4239
+ const to = PACKAGE_RENAMES.get(name);
4240
+ if (catalog !== void 0 && to !== void 0 && names.has(to)) {
4241
+ inUse.add(catalog, name);
4242
+ }
4243
+ }
4244
+ }
4245
+ return inUse;
4246
+ }
4247
+ function nestedTexts(node) {
4248
+ if (node.kind !== "map") {
4249
+ return [];
4250
+ }
4251
+ return textEntries(node).flatMap(
4252
+ ({ key, value }) => value.kind === "scalar" ? [{ key: key.value, value: value.value }] : nestedTexts(value)
4253
+ );
4254
+ }
4255
+ function addOverrideUses(documents, inUse) {
4256
+ const sections = documents.flatMap(
4257
+ (document) => [...OVERRIDE_FIELDS, PNPM_SETTINGS_FIELD].flatMap((field) => valuesAt(document, field))
4258
+ );
4259
+ for (const { key, value } of sections.flatMap(nestedTexts)) {
4260
+ const catalog = catalogOfSpec(value);
4261
+ const name = overrideTarget(key);
4262
+ if (catalog !== void 0 && PACKAGE_RENAMES.has(name)) {
4263
+ inUse.add(catalog, name);
4264
+ }
4265
+ }
4266
+ }
4267
+ function planCatalogs({
4268
+ manifests,
4269
+ catalogFiles,
4270
+ isWorkspaceRoot
4271
+ }) {
4272
+ const inUse = entriesInUse(manifests);
4273
+ addOverrideUses([...manifests, ...catalogFiles], inUse);
4274
+ const kept = new CatalogEntries();
4275
+ const defined = new CatalogEntries();
4276
+ const catalogs = [
4277
+ ...catalogFiles.flatMap((file) => catalogMaps(file)),
4278
+ ...manifests.flatMap(manifestCatalogMaps)
4279
+ ];
4280
+ for (const { name: catalog, map } of catalogs) {
4281
+ const entries = specEntries(map);
4282
+ const names = new Set(entries.map((entry) => entry.name));
4283
+ for (const { name, spec } of entries) {
4284
+ defined.add(catalog, name);
4285
+ const to = PACKAGE_RENAMES.get(name);
4286
+ const stays = spec === void 0 || inUse.has(catalog, name);
4287
+ if (to !== void 0 && !names.has(to) && stays) {
4288
+ kept.add(catalog, name);
4289
+ }
4290
+ }
4291
+ }
4292
+ return { inUse, kept, defined, isWorkspaceRoot };
4293
+ }
4294
+
4295
+ const PNPM_OVERRIDES = "overrides";
4296
+ function isSuccessorAlias(key, value) {
4297
+ const successor = PACKAGE_RENAMES.get(overrideTarget(key));
4298
+ return value.kind === "scalar" && value.value.startsWith(NPM_SPECIFIER_PREFIX) && packageReference(value.value).name === successor;
4299
+ }
4300
+ function overriddenOldPackages(node) {
4301
+ const names = node.kind === "map" ? textEntries(node).flatMap(
4302
+ ({ key, value }) => isSuccessorAlias(key.value, value) ? [] : [overrideTarget(key.value), ...overriddenOldPackages(value)]
4303
+ ) : documentStrings([node]).map((string) => packageReference(string.value).name);
4304
+ return [...new Set(names.filter((name) => PACKAGE_RENAMES.has(name)))];
4305
+ }
4306
+ function aliasedIn(node) {
4307
+ if (node.kind !== "map") {
4308
+ return [];
4309
+ }
4310
+ return textEntries(node).flatMap(
4311
+ ({ key, value }) => isSuccessorAlias(key.value, value) ? [overrideTarget(key.value)] : aliasedIn(value)
4312
+ );
4313
+ }
4314
+ function successorAliasedPackages(root) {
4315
+ const fields = [...OVERRIDE_FIELDS].flatMap((field) => mapsAt(root, field));
4316
+ const pnpm = mapsAt(root, PNPM_SETTINGS_FIELD).flatMap((map) => mapsAt(map, PNPM_OVERRIDES));
4317
+ return [...new Set([...fields, ...pnpm].flatMap(aliasedIn))];
4318
+ }
4319
+ function aliasedPackagesFor(aliases, filePath) {
4320
+ const applying = aliases.filter(({ path }) => isAtOrAbove(path, filePath));
4321
+ return new Set(applying.flatMap(({ names }) => [...names]));
4322
+ }
4323
+
3726
4324
  function oldPackagesNamedBy(node) {
3727
4325
  const values = documentStrings([node]).map((string) => packageReference(string.value).name);
3728
4326
  const names = [...nestedKeys(node).map(overrideTarget), ...values];
@@ -3735,7 +4333,8 @@ function reportSettingSections(settings, file, findings) {
3735
4333
  }
3736
4334
  const field = `${file.prefix}${key.value}`;
3737
4335
  const isOverride = file.overrideSections.has(key.value);
3738
- for (const name of oldPackagesNamedBy(value)) {
4336
+ const names = isOverride ? overriddenOldPackages(value) : oldPackagesNamedBy(value);
4337
+ for (const name of names) {
3739
4338
  findings.manualSteps.push(
3740
4339
  isOverride ? { kind: "override-names-package", field, name } : { kind: "setting-names-package", field, name }
3741
4340
  );
@@ -3806,7 +4405,18 @@ function migrateBrowserField(manifest, findings) {
3806
4405
  }
3807
4406
  }
3808
4407
  }
4408
+ function reportOldPackagePaths(node, prefix, findings) {
4409
+ for (const value of documentStrings([node])) {
4410
+ const name = oldPackageInNodeModules(value.value);
4411
+ const to = name === void 0 ? void 0 : PACKAGE_RENAMES.get(name);
4412
+ if (name !== void 0 && to !== void 0) {
4413
+ const location = [...prefix, ...value.path].join(PATH_SEPARATOR);
4414
+ findings.manualSteps.push({ kind: "old-package-path", location, path: value.value, to });
4415
+ }
4416
+ }
4417
+ }
3809
4418
 
4419
+ const TYPES_VERSIONS_FIELD = "typesVersions";
3810
4420
  function migrateDependencyFields(manifest, target, findings) {
3811
4421
  for (const field of DEPENDENCY_FIELDS) {
3812
4422
  for (const dependencies of mapsAt(manifest, field)) {
@@ -3852,7 +4462,7 @@ const PNPM_SETTINGS = {
3852
4462
  };
3853
4463
  function reportSettings(manifest, findings) {
3854
4464
  for (const field of OVERRIDE_FIELDS) {
3855
- for (const name of valuesAt(manifest, field).flatMap(oldPackagesNamedBy)) {
4465
+ for (const name of valuesAt(manifest, field).flatMap(overriddenOldPackages)) {
3856
4466
  findings.manualSteps.push({ kind: "override-names-package", field, name });
3857
4467
  }
3858
4468
  }
@@ -3860,10 +4470,10 @@ function reportSettings(manifest, findings) {
3860
4470
  reportSettingSections(settings, PNPM_SETTINGS, findings);
3861
4471
  }
3862
4472
  }
3863
- function reportOldPeers(manifest, findings) {
4473
+ function reportOldPeers(manifest, aliased, findings) {
3864
4474
  const dependencies = new Set(declaredNames(manifest));
3865
4475
  for (const [dependent, { peer, range }] of OLD_PEER_DEPENDENTS) {
3866
- if (dependencies.has(dependent)) {
4476
+ if (dependencies.has(dependent) && !aliased.has(peer)) {
3867
4477
  findings.manualSteps.push({ kind: "old-peer-needed", dependent, peer, range });
3868
4478
  }
3869
4479
  }
@@ -3875,11 +4485,14 @@ function migrateManifestMap(manifest, target, findings) {
3875
4485
  migrateCatalogs$1(manifest, target, findings);
3876
4486
  migrateSubpathImports(manifest, findings);
3877
4487
  migrateBrowserField(manifest, findings);
4488
+ for (const versions of valuesAt(manifest, TYPES_VERSIONS_FIELD)) {
4489
+ reportOldPackagePaths(versions, [TYPES_VERSIONS_FIELD], findings);
4490
+ }
3878
4491
  reportSettings(manifest, findings);
3879
4492
  for (const jest of valuesAt(manifest, JEST_FIELD)) {
3880
4493
  findings.manualSteps.push(...jestConfigSteps(jest, [JEST_FIELD]));
3881
4494
  }
3882
- reportOldPeers(manifest, findings);
4495
+ reportOldPeers(manifest, target.aliased ?? /* @__PURE__ */ new Set(), findings);
3883
4496
  }
3884
4497
  function migratePackageManifest(text, read, target) {
3885
4498
  if (read.kind === "unreadable") {
@@ -3914,6 +4527,73 @@ function oldPackagePatchSteps(fileName) {
3914
4527
  return name === void 0 || to === void 0 ? [] : [{ kind: "old-package-patch", name, to }];
3915
4528
  }
3916
4529
 
4530
+ const STEP_SHELL_KEY = "shell";
4531
+ const DEFAULT_SHELL_PATH = ["defaults", "run", STEP_SHELL_KEY];
4532
+ const COMMAND_LINE_SHELLS = /* @__PURE__ */ new Set([
4533
+ ...["sh", "bash", "dash", "ash", "zsh", "ksh"],
4534
+ ...["pwsh", "powershell", "cmd"],
4535
+ ...["msys2", "wsl-bash"]
4536
+ ]);
4537
+ const ENV_PROGRAM = "env";
4538
+ const ENV_OPTIONS_WITH_VALUE = /* @__PURE__ */ new Set(["-u", "--unset", "-C", "--chdir"]);
4539
+ const OPTION_START = "-";
4540
+ const ASSIGNMENT = "=";
4541
+ const PATH_SEPARATOR_RE = /[/\\]/;
4542
+ const WINDOWS_EXECUTABLE_RE = /\.exe$/i;
4543
+ const WHITESPACE_RE = /\s+/;
4544
+ const programName = (word) => (word.split(PATH_SEPARATOR_RE).at(-1) ?? "").replace(WINDOWS_EXECUTABLE_RE, "");
4545
+ function envPrefixLength(words) {
4546
+ let index = 0;
4547
+ while (index < words.length) {
4548
+ const word = words[index] ?? "";
4549
+ if (ENV_OPTIONS_WITH_VALUE.has(word)) {
4550
+ index += 2;
4551
+ } else if (word.startsWith(OPTION_START) || word.includes(ASSIGNMENT)) {
4552
+ index += 1;
4553
+ } else {
4554
+ break;
4555
+ }
4556
+ }
4557
+ return index;
4558
+ }
4559
+ function shellProgram(shell) {
4560
+ const words = shell.trim().split(WHITESPACE_RE);
4561
+ const first = programName(words[0] ?? "");
4562
+ if (first !== ENV_PROGRAM) {
4563
+ return first;
4564
+ }
4565
+ const rest = words.slice(1);
4566
+ return programName(rest[envPrefixLength(rest)] ?? "");
4567
+ }
4568
+ const endsWith = (path, keys) => keys.every((key, index) => path[path.length - keys.length + index] === key);
4569
+ const isDefaultShell = (value) => endsWith(value.path, DEFAULT_SHELL_PATH);
4570
+ function scopeOf(value) {
4571
+ const keys = isDefaultShell(value) ? DEFAULT_SHELL_PATH.length : 1;
4572
+ return JSON.stringify(value.path.slice(0, -keys));
4573
+ }
4574
+ function stepShells(strings) {
4575
+ const shells = strings.filter((value) => value.path.at(-1) === STEP_SHELL_KEY);
4576
+ const isOwnShell = (value) => !isDefaultShell(value);
4577
+ const ordered = [...shells.filter(isDefaultShell), ...shells.filter(isOwnShell)];
4578
+ const byScope = new Map(ordered.map((value) => [scopeOf(value), value.value]));
4579
+ return (value) => {
4580
+ for (let depth = value.path.length - 1; depth >= 0; depth -= 1) {
4581
+ const shell = byScope.get(JSON.stringify(value.path.slice(0, depth)));
4582
+ if (shell !== void 0) {
4583
+ return COMMAND_LINE_SHELLS.has(shellProgram(shell)) ? void 0 : shell;
4584
+ }
4585
+ }
4586
+ return void 0;
4587
+ };
4588
+ }
4589
+ function otherShellFindings(values, shellOf, locate) {
4590
+ const manualSteps = values.flatMap((value) => {
4591
+ const shell = shellOf(value);
4592
+ return shell === void 0 || !namesOriginalPackage(value.value) ? [] : [{ kind: "script-not-shell", location: locate(value.start), shell }];
4593
+ });
4594
+ return { edits: [], changes: [], manualSteps };
4595
+ }
4596
+
3917
4597
  const YAML_COMMAND_KEYS = /* @__PURE__ */ new Set([
3918
4598
  "run",
3919
4599
  "script",
@@ -4017,9 +4697,10 @@ function appliedFindings(text, found) {
4017
4697
  };
4018
4698
  }
4019
4699
  function migrateYamlCommands(text, file, strings, keys = YAML_COMMAND_KEYS) {
4020
- const values = valuesOf(strings, keys);
4021
4700
  const lineOf = lineLocator(text);
4022
4701
  const locate = (offset) => `${file}:${lineOf(offset)}`;
4702
+ const shellOf = stepShells(strings);
4703
+ const values = valuesOf(strings, keys).filter((value) => shellOf(value) === void 0);
4023
4704
  const commandValues = new Set(values.filter((value) => !isExecFormItem(value)));
4024
4705
  const shellValues = strings.filter(
4025
4706
  (value) => commandValues.has(value) || isEnvironmentValue(value)
@@ -4027,7 +4708,8 @@ function migrateYamlCommands(text, file, strings, keys = YAML_COMMAND_KEYS) {
4027
4708
  const variables = environmentVariables(text, strings);
4028
4709
  return appliedFindings(text, [
4029
4710
  ...shellValues.map((value) => valueFindings(text, value, locate, variables)),
4030
- commandWordFindings(execFormCommands(text, values.filter(isExecFormItem)), locate)
4711
+ commandWordFindings(execFormCommands(text, values.filter(isExecFormItem)), locate),
4712
+ otherShellFindings(valuesOf(strings, keys), shellOf, locate)
4031
4713
  ]);
4032
4714
  }
4033
4715
  function migrateYamlPathCommands(text, strings, patterns) {
@@ -4115,16 +4797,6 @@ const COMPILER_OPTIONS_FIELD = "compilerOptions";
4115
4797
  const PATHS_FIELD = "paths";
4116
4798
  const TYPES_FIELD = "types";
4117
4799
  const FIELD_SEPARATOR = ".";
4118
- function reportOldPackagePaths(config, findings) {
4119
- for (const value of documentStrings([config])) {
4120
- const name = oldPackageInNodeModules(value.value);
4121
- const to = name === void 0 ? void 0 : PACKAGE_RENAMES.get(name);
4122
- if (name !== void 0 && to !== void 0) {
4123
- const location = value.path.join(FIELD_SEPARATOR);
4124
- findings.manualSteps.push({ kind: "old-package-path", location, path: value.value, to });
4125
- }
4126
- }
4127
- }
4128
4800
  function migrateCompilerOptions(options, findings) {
4129
4801
  for (const paths of mapsAt(options, PATHS_FIELD)) {
4130
4802
  const field = [COMPILER_OPTIONS_FIELD, PATHS_FIELD].join(FIELD_SEPARATOR);
@@ -4146,7 +4818,7 @@ function migrateTsconfig(text, read) {
4146
4818
  for (const options of mapsAt(config, COMPILER_OPTIONS_FIELD)) {
4147
4819
  migrateCompilerOptions(options, findings);
4148
4820
  }
4149
- reportOldPackagePaths(config, findings);
4821
+ reportOldPackagePaths(config, [], findings);
4150
4822
  }
4151
4823
  const { edits, changes, manualSteps } = findings;
4152
4824
  return { text: applyTextEdits(text, edits), changes, manualSteps };
@@ -4215,7 +4887,7 @@ const SHELL_EXTENSIONS = /* @__PURE__ */ new Set([".sh", ".bash", ".zsh"]);
4215
4887
  const MAKEFILE_EXTENSION = ".mk";
4216
4888
  const YAML_EXTENSIONS = /* @__PURE__ */ new Set([".yml", ".yaml"]);
4217
4889
  const MAKEFILE_NAMES = /* @__PURE__ */ new Set(["Makefile", "makefile", "GNUmakefile"]);
4218
- const SHELL_FILE_NAMES = /* @__PURE__ */ new Set(["justfile", "Justfile", ".justfile"]);
4890
+ const JUSTFILE_NAMES = /* @__PURE__ */ new Set(["justfile", "Justfile", ".justfile"]);
4219
4891
  const DOCKERFILE_NAMES = /* @__PURE__ */ new Set(["Dockerfile", "Containerfile"]);
4220
4892
  const HUSKY_FOLDER = ".husky/";
4221
4893
  const HUSKY_INTERNALS = ".husky/_/";
@@ -4289,10 +4961,11 @@ function joinedNames(sets) {
4289
4961
  }
4290
4962
  function withNamedImportMaps(declarations, importMaps) {
4291
4963
  const mapNames = new Map(declarations.map(({ path, names }) => [path, names]));
4292
- return declarations.filter(({ path }) => !importMaps.has(path)).map(({ path, names }) => {
4964
+ return declarations.filter(({ path }) => !importMaps.has(path)).map((declaration) => {
4965
+ const { path, names } = declaration;
4293
4966
  const maps = [...importMaps].filter(([, configs]) => configs.includes(path));
4294
4967
  const named = maps.flatMap(([map]) => mapNames.has(map) ? [mapNames.get(map)] : []);
4295
- return { path, names: joinedNames([names, ...named]) };
4968
+ return { ...declaration, names: joinedNames([names, ...named]) };
4296
4969
  });
4297
4970
  }
4298
4971
 
@@ -4300,8 +4973,9 @@ const isHuskyHook = (path) => (path.startsWith(HUSKY_FOLDER) || path.includes(`/
4300
4973
  const isDependabotConfig = (path) => [...DEPENDABOT_FILES].some((file) => path === file || path.endsWith(`/${file}`));
4301
4974
  function migrateSource(text, path, context) {
4302
4975
  const strings = context.scanner.scan(text, path);
4976
+ const kept = localPackagesFor(context.manifests, path);
4303
4977
  const migrated = chain(
4304
- migrateModuleSpecifiers(text, strings, context.version),
4978
+ migrateModuleSpecifiers(text, strings, context.version, kept),
4305
4979
  (renamed) => context.rewriter.rewrite(renamed, path)
4306
4980
  );
4307
4981
  const imports = importedSpecifiers(text, strings);
@@ -4353,18 +5027,24 @@ const COMMAND_CONFIG_KINDS = [
4353
5027
  )
4354
5028
  }
4355
5029
  ];
5030
+ const yamlCommandsStep = (path, isPart) => (text, read) => isPart || read.kind === "read" && isApiDescription(read.documents) ? unchanged(text) : stringsStep((current, strings) => migrateYamlCommands(current, path, strings))(text, read);
5031
+ const manifestTarget = ({ version, catalogs, aliases }, path) => ({
5032
+ version,
5033
+ catalogs,
5034
+ aliased: aliasedPackagesFor(aliases, path)
5035
+ });
4356
5036
  const FILE_KINDS = [
4357
5037
  {
4358
5038
  matches: (path) => baseName(path) === MANIFEST_FILE,
4359
- migrate: jsonConfig(({ version, catalogs }) => [
4360
- (text, read) => migratePackageManifest(text, read, { version, catalogs }),
5039
+ migrate: jsonConfig((context, path) => [
5040
+ (text, read) => migratePackageManifest(text, read, manifestTarget(context, path)),
4361
5041
  jsonCommandsStep(MANIFEST_COMMAND_PATHS)
4362
5042
  ])
4363
5043
  },
4364
5044
  {
4365
5045
  matches: (path) => YAML_MANIFEST_FILES.has(baseName(path)),
4366
- migrate: yamlConfig(({ version, catalogs }) => [
4367
- (text, read) => migratePackageManifest(text, read, { version, catalogs }),
5046
+ migrate: yamlConfig((context, path) => [
5047
+ (text, read) => migratePackageManifest(text, read, manifestTarget(context, path)),
4368
5048
  yamlPathCommands(MANIFEST_COMMAND_PATHS)
4369
5049
  ])
4370
5050
  },
@@ -4424,8 +5104,8 @@ const FILE_KINDS = [
4424
5104
  {
4425
5105
  matches: (path) => YAML_EXTENSIONS.has(extensionOf(path)),
4426
5106
  migrate: whenNamed(
4427
- yamlConfig((_context, path) => [
4428
- stringsStep((text, strings) => migrateYamlCommands(text, path, strings))
5107
+ yamlConfig((context, path) => [
5108
+ yamlCommandsStep(path, context.apiDescriptionParts.has(path))
4429
5109
  ])
4430
5110
  )
4431
5111
  },
@@ -4438,7 +5118,11 @@ const FILE_KINDS = [
4438
5118
  migrate: whenNamed((text, path) => migrateMakefile(text, path))
4439
5119
  },
4440
5120
  {
4441
- matches: (path) => SHELL_EXTENSIONS.has(extensionOf(path)) || SHELL_FILE_NAMES.has(baseName(path)) || isHuskyHook(path),
5121
+ matches: (path) => JUSTFILE_NAMES.has(baseName(path)),
5122
+ migrate: whenNamed((text, path) => migrateJustfile(text, path))
5123
+ },
5124
+ {
5125
+ matches: (path) => SHELL_EXTENSIONS.has(extensionOf(path)) || isHuskyHook(path),
4442
5126
  migrate: whenNamed((text, path) => migrateCommandLines(text, path))
4443
5127
  }
4444
5128
  ];
@@ -4452,6 +5136,58 @@ function fileMigrationFor(path, namedImportMaps = NO_NAMED_IMPORT_MAPS) {
4452
5136
  return FILE_KINDS.find((kind) => kind.matches(path))?.migrate;
4453
5137
  }
4454
5138
 
5139
+ const NO_API_DESCRIPTION_PARTS = /* @__PURE__ */ new Set();
5140
+ const JSON_EXTENSION = ".json";
5141
+ const isYaml = (path) => YAML_EXTENSIONS.has(extensionOf(path));
5142
+ const isDescriptionFormat = (path) => isYaml(path) || extensionOf(path) === JSON_EXTENSION;
5143
+ async function textOf(path, files) {
5144
+ const read = await files.read(path);
5145
+ return read.kind === "read" ? read.text : void 0;
5146
+ }
5147
+ function documentsOf(path, text, readers) {
5148
+ const parsed = (isYaml(path) ? readers.yaml : readers.json).read(text);
5149
+ return parsed.kind === "read" ? [...parsed.documents] : void 0;
5150
+ }
5151
+ async function hasNamingYaml(paths, files) {
5152
+ for (const path of paths.filter(isYaml)) {
5153
+ const text = await textOf(path, files);
5154
+ if (text !== void 0 && namesOriginalPackage(text)) {
5155
+ return true;
5156
+ }
5157
+ }
5158
+ return false;
5159
+ }
5160
+ async function descriptionRoots(paths, { files, readers }) {
5161
+ const roots = /* @__PURE__ */ new Map();
5162
+ for (const path of paths.filter(isDescriptionFormat)) {
5163
+ const text = await textOf(path, files);
5164
+ const documents = text !== void 0 && mayBeApiDescription(text) ? documentsOf(path, text, readers) : void 0;
5165
+ if (documents !== void 0 && isApiDescription(documents)) {
5166
+ roots.set(path, documents);
5167
+ }
5168
+ }
5169
+ return roots;
5170
+ }
5171
+ async function apiDescriptionParts(paths, dependencies) {
5172
+ if (!await hasNamingYaml(paths, dependencies.files)) {
5173
+ return NO_API_DESCRIPTION_PARTS;
5174
+ }
5175
+ const listed = new Set(paths.filter(isDescriptionFormat));
5176
+ const roots = await descriptionRoots(paths, dependencies);
5177
+ const parts = /* @__PURE__ */ new Set();
5178
+ const pending = [...roots].flatMap(([path, documents]) => referencedFiles(path, documents));
5179
+ for (let path = pending.pop(); path !== void 0; path = pending.pop()) {
5180
+ if (!listed.has(path) || roots.has(path) || parts.has(path)) {
5181
+ continue;
5182
+ }
5183
+ parts.add(path);
5184
+ const text = await textOf(path, dependencies.files);
5185
+ const documents = text === void 0 ? void 0 : documentsOf(path, text, dependencies.readers);
5186
+ pending.push(...documents === void 0 ? [] : referencedFiles(path, documents));
5187
+ }
5188
+ return parts;
5189
+ }
5190
+
4455
5191
  const readJson = (text, readers) => readers.json.read(text);
4456
5192
  const readYaml = (text, readers) => readers.yaml.read(text);
4457
5193
  function planSourceOf(path, namedImportMaps) {
@@ -4472,27 +5208,6 @@ function planSourceOf(path, namedImportMaps) {
4472
5208
  return isCatalogFile ? { role: "catalogs", read: readYaml } : void 0;
4473
5209
  }
4474
5210
 
4475
- async function planFile(path, dependencies, target) {
4476
- const migrate = fileMigrationFor(path, target.importMaps);
4477
- if (migrate === void 0) {
4478
- return { kind: "not-migrated" };
4479
- }
4480
- const read = await dependencies.files.read(path);
4481
- if (read.kind === "skipped") {
4482
- return read;
4483
- }
4484
- const { rewriter, scanner, readers } = dependencies;
4485
- const { version, catalogs, manifests } = target;
4486
- const context = { version, catalogs, manifests, rewriter, scanner, readers };
4487
- const migration = migrate(read.text, path, context);
4488
- const isChanged = migration.text !== read.text;
4489
- const { changes, manualSteps } = migration;
4490
- if (isChanged || manualSteps.length > 0) {
4491
- dependencies.logger.info("file_migrated", { path, isChanged, changes, manualSteps });
4492
- }
4493
- const report = { path, isChanged, changes, manualSteps };
4494
- return { kind: "planned", file: { report, text: isChanged ? migration.text : void 0 } };
4495
- }
4496
5211
  async function readForPlan(path, source, dependencies) {
4497
5212
  const read = await dependencies.files.read(path);
4498
5213
  if (read.kind !== "read") {
@@ -4516,9 +5231,22 @@ function marksWorkspaceRoot(path, roots) {
4516
5231
  return manifests.some((manifest) => valuesAt(manifest, WORKSPACES_FIELD).length > 0);
4517
5232
  }
4518
5233
  const DECLARED_NAMES = { manifest: declaredNames, "import-map": importMapNames, catalogs: void 0 };
5234
+ function declarationsOf(path, role, roots) {
5235
+ const names = DECLARED_NAMES[role];
5236
+ if (names === void 0) {
5237
+ return [];
5238
+ }
5239
+ const local = role === "manifest" ? (roots ?? []).flatMap(localPackageNames) : [];
5240
+ return [{ path, names: roots && new Set(roots.flatMap(names)), localPackages: new Set(local) }];
5241
+ }
5242
+ function aliasesOf(path, roots) {
5243
+ const names = (roots ?? []).flatMap(successorAliasedPackages);
5244
+ return names.length === 0 ? [] : [{ path, names: new Set(names) }];
5245
+ }
4519
5246
  async function planProjectTarget(paths, dependencies, version) {
4520
5247
  const documents = { manifest: [], catalogs: [] };
4521
5248
  const manifests = [];
5249
+ const aliases = [];
4522
5250
  let isWorkspaceRoot = false;
4523
5251
  const importMaps = await namedImportMaps(paths, dependencies.files, dependencies.readers);
4524
5252
  for (const path of paths) {
@@ -4531,17 +5259,45 @@ async function planProjectTarget(paths, dependencies, version) {
4531
5259
  if (namesOriginal && source.role !== "import-map") {
4532
5260
  documents[source.role].push(...roots ?? []);
4533
5261
  }
4534
- const names = DECLARED_NAMES[source.role];
4535
- if (names !== void 0) {
4536
- manifests.push({ path, names: roots && new Set(roots.flatMap(names)) });
4537
- }
5262
+ manifests.push(...declarationsOf(path, source.role, roots));
5263
+ aliases.push(...aliasesOf(path, roots));
4538
5264
  }
4539
5265
  const catalogs = planCatalogs({
4540
5266
  manifests: documents.manifest,
4541
5267
  catalogFiles: documents.catalogs,
4542
5268
  isWorkspaceRoot
4543
5269
  });
4544
- return { version, catalogs, manifests: withNamedImportMaps(manifests, importMaps), importMaps };
5270
+ const declarations = withNamedImportMaps(manifests, importMaps);
5271
+ const parts = await apiDescriptionParts(paths, dependencies);
5272
+ return {
5273
+ ...{ version, catalogs, manifests: declarations, aliases, importMaps },
5274
+ apiDescriptionParts: parts
5275
+ };
5276
+ }
5277
+
5278
+ async function planFile(path, dependencies, target) {
5279
+ const migrate = fileMigrationFor(path, target.importMaps);
5280
+ if (migrate === void 0) {
5281
+ return { kind: "not-migrated" };
5282
+ }
5283
+ const read = await dependencies.files.read(path);
5284
+ if (read.kind === "skipped") {
5285
+ return read;
5286
+ }
5287
+ const { rewriter, scanner, readers } = dependencies;
5288
+ const { version, catalogs, manifests, aliases, apiDescriptionParts } = target;
5289
+ const context = {
5290
+ ...{ version, catalogs, manifests, aliases, apiDescriptionParts },
5291
+ ...{ rewriter, scanner, readers }
5292
+ };
5293
+ const migration = migrate(read.text, path, context);
5294
+ const isChanged = migration.text !== read.text;
5295
+ const { changes, manualSteps } = migration;
5296
+ if (isChanged || manualSteps.length > 0) {
5297
+ dependencies.logger.info("file_migrated", { path, isChanged, changes, manualSteps });
5298
+ }
5299
+ const report = { path, isChanged, changes, manualSteps };
5300
+ return { kind: "planned", file: { report, text: isChanged ? migration.text : void 0 } };
4545
5301
  }
4546
5302
  async function planProject(paths, dependencies, version) {
4547
5303
  const files = [];
@@ -4621,8 +5377,9 @@ function terminalSafeText(text) {
4621
5377
  const PINNED_NOTE = "change it to openapi-typesafe@<version>";
4622
5378
  const OLD_FILE_NOTE$1 = "is a file of openapi-typescript, which is no longer installed: run openapi-typesafe instead";
4623
5379
  const FLAGS_IN_VARIABLE_NOTE = "holds old flags of the generator and may serve other commands, so it was kept: rename";
4624
- 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";
5380
+ 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";
4625
5381
  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";
5382
+ const SCRIPT_NOT_SHELL_NOTE = "so the tool does not read it as commands: migrate the old package names in it by hand";
4626
5383
  const COMMAND_STRING_NOTE = "a string runs openapi-typescript, so it was kept; change it to";
4627
5384
  function pinnedPackageMessage(step) {
4628
5385
  const successor = PACKAGE_RENAMES.get(step.name) ?? step.name;
@@ -4642,6 +5399,7 @@ const COMMAND_STEP_KINDS = /* @__PURE__ */ new Set([
4642
5399
  "pinned-package",
4643
5400
  "old-package-file",
4644
5401
  "old-name-in-command",
5402
+ "script-not-shell",
4645
5403
  "command-string"
4646
5404
  ]);
4647
5405
  const isCommandStep = (step) => COMMAND_STEP_KINDS.has(step.kind);
@@ -4659,6 +5417,8 @@ function commandStepMessage(step) {
4659
5417
  return `${step.location}: ${step.path} ${OLD_FILE_NOTE$1}`;
4660
5418
  case "old-name-in-command":
4661
5419
  return `${step.location}: "${step.word}" ${OLD_NAME_IN_COMMAND_NOTE}`;
5420
+ case "script-not-shell":
5421
+ return `${step.location}: the step runs with shell: ${step.shell}, ${SCRIPT_NOT_SHELL_NOTE}`;
4662
5422
  case "command-string":
4663
5423
  return `line ${step.line}: ${COMMAND_STRING_NOTE} "${step.to}"`;
4664
5424
  default:
@@ -4732,10 +5492,12 @@ const PATCH_NOTE = "check whether the fix is still needed, then make the patch a
4732
5492
  const PATTERN_NOTE = "is a pattern that names";
4733
5493
  const PATTERN_FIX_NOTE = "where it means the package, make it match";
4734
5494
  const SETTING_NOTE = "remove the setting if it no longer applies, or make it name";
5495
+ const LOCAL_PACKAGE_NOTE = "points at a local package, not the npm package, so it and the imports of it were kept: if that package is a copy of the original, move it to";
4735
5496
  const DEEP_TARGET_NOTE = "points at a file inside the package; check that it exists";
4736
5497
  const OLD_PATH_NOTE = "is a path into node_modules of an old package, which is no longer installed:";
4737
5498
  const OLD_PATH_FIX_NOTE = "point it at the files of";
4738
5499
  const renamed = (name) => PACKAGE_RENAMES.get(name) ?? name;
5500
+ const localPackageMessage = (step) => `${step.field}: ${step.name} "${step.spec}" ${LOCAL_PACKAGE_NOTE} ${renamed(step.name)}`;
4739
5501
  function oldPeerMessage(step) {
4740
5502
  const { dependent, peer, range } = step;
4741
5503
  const successor = PACKAGE_RENAMES.get(peer) ?? peer;
@@ -4779,6 +5541,8 @@ function packageStepMessage(step) {
4779
5541
  return `${step.name} is a peer dependency: ${PEER_NOTE}`;
4780
5542
  case "spec-kept":
4781
5543
  return `${step.name} kept the spec "${step.spec}": point it at an openapi-typesafe version`;
5544
+ case "local-package":
5545
+ return localPackageMessage(step);
4782
5546
  case "override-names-package":
4783
5547
  return `"${step.field}" names ${step.name}: update or remove that override`;
4784
5548
  case "setting-names-package":
@@ -4817,6 +5581,7 @@ function manualStepMessage(step) {
4817
5581
  return catalogStepMessage(step);
4818
5582
  case "peer-dependency-renamed":
4819
5583
  case "spec-kept":
5584
+ case "local-package":
4820
5585
  case "override-names-package":
4821
5586
  case "setting-names-package":
4822
5587
  case "pattern-names-package":
@@ -5107,8 +5872,8 @@ const createGit = (root) => createGitFileList({
5107
5872
  const dependencies = {
5108
5873
  version: readVersion(),
5109
5874
  cwd: process.cwd(),
5110
- writeStdout: (text) => process.stdout.write(text),
5111
- writeStderr: (text) => process.stderr.write(text),
5875
+ writeStdout: createPipeSafeWriter(process.stdout),
5876
+ writeStderr: createPipeSafeWriter(process.stderr),
5112
5877
  inspectRoot: inspectProjectRoot,
5113
5878
  createFiles: (root, logger) => createNodeProjectFiles(root, { git: createGit(root), logger }),
5114
5879
  createLogger,