openapi-typesafe-migrate 1.0.0-rc.6 → 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
@@ -737,7 +737,9 @@ function indexUses(file, bindings) {
737
737
  if (declaration !== void 0 && !isDeclaredName(node, declaration)) {
738
738
  uses.set(declaration, [...uses.get(declaration) ?? [], node]);
739
739
  }
740
- ts.forEachChild(node, visit);
740
+ for (const child of node.getChildren(file)) {
741
+ visit(child);
742
+ }
741
743
  };
742
744
  visit(file);
743
745
  return uses;
@@ -812,18 +814,23 @@ function constantOptions(name, bindings, outerTypes) {
812
814
  return { kind: "shared", options: found.value };
813
815
  }
814
816
  const declared = declaration.type;
815
- const annotations = [
816
- ...outerTypes,
817
+ const ownTypes = [
817
818
  ...found.types,
818
819
  ...declared === void 0 ? [] : [declared],
819
820
  ...jsDocTypes(declaration)
820
821
  ];
821
- 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
+ };
822
829
  }
823
830
  function optionsOf(argument, bindings) {
824
831
  const { value, types } = unwrapped(argument);
825
832
  if (ts.isObjectLiteralExpression(value)) {
826
- return { kind: "own", options: value, annotations: types };
833
+ return { kind: "own", options: value, annotations: types, isContextuallyTyped: true };
827
834
  }
828
835
  return ts.isIdentifier(value) ? constantOptions(value, bindings, types) : { kind: "unknown", expression: argument };
829
836
  }
@@ -1434,6 +1441,9 @@ function removalEdits(removed, text) {
1434
1441
  const TRANSFORM = "transform";
1435
1442
  const LEGACY_ONLY_CALLBACKS = ["postTransform", "transformProperty"];
1436
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)";
1437
1447
  const noFindings = () => ({
1438
1448
  edits: [],
1439
1449
  changes: [],
@@ -1472,10 +1482,17 @@ function transformEdits(property, names, text) {
1472
1482
  }
1473
1483
  return edits;
1474
1484
  }
1475
- 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) {
1476
1493
  const lineStart = text.lastIndexOf("\n", transformStart - 1) + 1;
1477
1494
  const before = text.slice(lineStart, transformStart);
1478
- const option = `${IR_CALLBACKS_OPTION}: true,`;
1495
+ const option = `${IR_CALLBACKS_OPTION}: ${value},`;
1479
1496
  return /^\s*$/.test(before) ? `${option}
1480
1497
  ${before}` : `${option} `;
1481
1498
  }
@@ -1518,7 +1535,9 @@ function migrateOptions(options, text, names) {
1518
1535
  findings.manualSteps.push({ kind: "transform-not-rewritten", line, reason: result });
1519
1536
  return findings;
1520
1537
  }
1521
- 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);
1522
1541
  findings.typeReferences.push(...result.typeReferences);
1523
1542
  findings.changes.push({ kind: "transform-rewritten", line });
1524
1543
  return findings;
@@ -1667,7 +1686,7 @@ function isModuleSpecifier(literal) {
1667
1686
  }
1668
1687
  return ts.isExternalModuleReference(parent);
1669
1688
  }
1670
- function stringValue$1(node, file) {
1689
+ function stringValue(node, file) {
1671
1690
  return ts.isTemplateExpression(node) ? file.text.slice(node.getStart(file) + 1, node.getEnd() - 1) : node.text;
1672
1691
  }
1673
1692
  function collectStrings(root, file, offset, found) {
@@ -1678,7 +1697,7 @@ function collectStrings(root, file, offset, found) {
1678
1697
  found.set(start, {
1679
1698
  start,
1680
1699
  end,
1681
- value: stringValue$1(node, file),
1700
+ value: stringValue(node, file),
1682
1701
  isModuleSpecifier: isModuleSpecifier(node)
1683
1702
  });
1684
1703
  }
@@ -1887,6 +1906,150 @@ function createYamlTextReader() {
1887
1906
  return { read };
1888
1907
  }
1889
1908
 
1909
+ const PACKAGE_RENAMES = /* @__PURE__ */ new Map([
1910
+ ["openapi-typescript", "openapi-typesafe"],
1911
+ ["openapi-fetch", "openapi-typesafe-fetch"],
1912
+ ["openapi-react-query", "openapi-typesafe-react-query"],
1913
+ ["openapi-typescript-helpers", "openapi-typesafe-helpers"]
1914
+ ]);
1915
+ function namesOriginalPackage(text) {
1916
+ return [...PACKAGE_RENAMES.keys()].some((name) => text.includes(name));
1917
+ }
1918
+ const GENERATOR_COMMAND = "openapi-typescript";
1919
+ const MIGRATED_GENERATOR_COMMAND = "openapi-typesafe";
1920
+ const FLAG_RENAMES = /* @__PURE__ */ new Map([
1921
+ ["--immutable-types", "--immutable"],
1922
+ ["-it", "--immutable"],
1923
+ ["--support-array-length", "--array-length"],
1924
+ ["--redoc", "--redocly"],
1925
+ ["-ap", "--additional-properties"]
1926
+ ]);
1927
+ const DEPENDENCY_FIELDS = [
1928
+ "dependencies",
1929
+ "devDependencies",
1930
+ "peerDependencies",
1931
+ "optionalDependencies"
1932
+ ];
1933
+ const NAME_KEYED_FIELDS = ["peerDependenciesMeta", "dependenciesMeta"];
1934
+ const NAME_LIST_FIELDS = ["bundleDependencies", "bundledDependencies"];
1935
+ const CATALOG_FIELD = "catalog";
1936
+ const NAMED_CATALOGS_FIELD = "catalogs";
1937
+ const WORKSPACES_FIELD = "workspaces";
1938
+ const OVERRIDE_FIELDS = ["overrides", "resolutions"];
1939
+ const PNPM_SETTINGS_FIELD = "pnpm";
1940
+ const OLD_PEER_DEPENDENTS = /* @__PURE__ */ new Map([
1941
+ ["swr-openapi", { peer: "openapi-fetch", range: ">=0.15" }]
1942
+ ]);
1943
+ const PATH_SEPARATOR$9 = "/";
1944
+ const QUERY_START = "?";
1945
+ function renamedSpecifier(value) {
1946
+ const ends = [value.indexOf(PATH_SEPARATOR$9), value.indexOf(QUERY_START)].filter(
1947
+ (at) => at !== -1
1948
+ );
1949
+ const name = value.slice(0, ends.length === 0 ? value.length : Math.min(...ends));
1950
+ const renamed = PACKAGE_RENAMES.get(name);
1951
+ if (renamed === void 0) {
1952
+ return void 0;
1953
+ }
1954
+ const rest = value.slice(name.length);
1955
+ const subpath = rest.split(QUERY_START, 1)[0] ?? rest;
1956
+ return { to: `${renamed}${rest}`, isDeep: subpath !== "" && subpath !== PATH_SEPARATOR$9 };
1957
+ }
1958
+ const NODE_MODULES_FOLDER = "node_modules/";
1959
+ function oldPackageInNodeModules(path) {
1960
+ return [...PACKAGE_RENAMES.keys()].find(
1961
+ (name) => path.includes(`${NODE_MODULES_FOLDER}${name}${PATH_SEPARATOR$9}`)
1962
+ );
1963
+ }
1964
+
1965
+ const isMap = (node) => node?.kind === "map";
1966
+ const isScalar = (node) => node.kind === "scalar";
1967
+ function textEntries(map) {
1968
+ return map.entries.flatMap(({ key, value }) => isScalar(key) ? [{ key, value }] : []);
1969
+ }
1970
+ function valuesAt(map, key) {
1971
+ return textEntries(map).filter((entry) => entry.key.value === key).map((entry) => entry.value);
1972
+ }
1973
+ function mapsAt(map, key) {
1974
+ return valuesAt(map, key).filter(isMap);
1975
+ }
1976
+ function stringsBelow(node, path) {
1977
+ switch (node.kind) {
1978
+ case "scalar":
1979
+ return [{ ...node, path }];
1980
+ case "list":
1981
+ return node.items.flatMap((item, index) => stringsBelow(item, [...path, index]));
1982
+ case "map":
1983
+ return textEntries(node).flatMap(
1984
+ ({ key, value }) => stringsBelow(value, [...path, key.value])
1985
+ );
1986
+ default:
1987
+ return [];
1988
+ }
1989
+ }
1990
+ function documentStrings(documents) {
1991
+ return documents.flatMap((document) => stringsBelow(document, []));
1992
+ }
1993
+ function ownerKey(path) {
1994
+ return path.findLast((part) => typeof part === "string");
1995
+ }
1996
+ function unreadableConfig(text, offset) {
1997
+ if (!namesOriginalPackage(text)) {
1998
+ return unchanged(text);
1999
+ }
2000
+ const position = offset === void 0 ? void 0 : { line: lineAt(text, offset), column: columnAt(text, offset) };
2001
+ return { ...unchanged(text), manualSteps: [{ kind: "config-unreadable", position }] };
2002
+ }
2003
+
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;
2014
+ }
2015
+ } else if (part !== CURRENT_FOLDER && part !== "") {
2016
+ parts.push(part);
2017
+ }
2018
+ }
2019
+ return parts.join(SEPARATOR);
2020
+ }
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;
2025
+ }
2026
+
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"
2038
+ );
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];
2050
+ });
2051
+ }
2052
+
1890
2053
  const NONE = /* @__PURE__ */ new Set();
1891
2054
  const PACKAGE_OPTIONS = /* @__PURE__ */ new Set(["-p", "--package"]);
1892
2055
  const PACKAGE_MANAGER_OPTIONS = /* @__PURE__ */ new Set([
@@ -2030,8 +2193,8 @@ const COMMAND_SEPARATORS = /* @__PURE__ */ new Set([
2030
2193
  "\n"
2031
2194
  ]);
2032
2195
  const WORD_BREAKS = /* @__PURE__ */ new Set([" ", " ", "\r", "<", ">"]);
2033
- const BACKSLASH$1 = "\\";
2034
- const DOUBLE_QUOTE_ESCAPES = /* @__PURE__ */ new Set(["$", "`", '"', BACKSLASH$1, "\n"]);
2196
+ const BACKSLASH$2 = "\\";
2197
+ const DOUBLE_QUOTE_ESCAPES = /* @__PURE__ */ new Set(["$", "`", '"', BACKSLASH$2, "\n"]);
2035
2198
  function singleQuoted(text, start) {
2036
2199
  const close = text.indexOf("'", start + 1);
2037
2200
  const end = close === -1 ? text.length : close + 1;
@@ -2042,13 +2205,13 @@ function doubleQuoted(text, start) {
2042
2205
  let index = start + 1;
2043
2206
  while (index < text.length && text[index] !== '"') {
2044
2207
  const next = text[index + 1] ?? "";
2045
- const isEscape = text[index] === BACKSLASH$1 && DOUBLE_QUOTE_ESCAPES.has(next);
2208
+ const isEscape = text[index] === BACKSLASH$2 && DOUBLE_QUOTE_ESCAPES.has(next);
2046
2209
  value += isEscape ? next === "\n" ? "" : next : text[index];
2047
2210
  index += isEscape ? 2 : 1;
2048
2211
  }
2049
2212
  return { end: Math.min(index + 1, text.length), value };
2050
2213
  }
2051
- const isLineContinuation = (text, index) => text[index] === BACKSLASH$1 && (text[index + 1] === "\n" || text.startsWith("\r\n", index + 1));
2214
+ const isLineContinuation = (text, index) => text[index] === BACKSLASH$2 && (text[index + 1] === "\n" || text.startsWith("\r\n", index + 1));
2052
2215
  const ARITHMETIC_OPEN = "$((";
2053
2216
  const NESTING_OPEN = "(";
2054
2217
  const NESTING_CLOSE = ")";
@@ -2063,11 +2226,48 @@ function arithmeticExpansion(text, start) {
2063
2226
  }
2064
2227
  return void 0;
2065
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
+ }
2066
2266
  function wordPart(text, index) {
2067
2267
  const char = text[index];
2068
- const arithmetic = text.startsWith(ARITHMETIC_OPEN, index) ? arithmeticExpansion(text, index) : void 0;
2069
- if (arithmetic !== void 0) {
2070
- return arithmetic;
2268
+ const expansion = expansionPart(text, index);
2269
+ if (expansion !== void 0) {
2270
+ return expansion;
2071
2271
  }
2072
2272
  if (char === "'") {
2073
2273
  return singleQuoted(text, index);
@@ -2075,11 +2275,23 @@ function wordPart(text, index) {
2075
2275
  if (char === '"') {
2076
2276
  return doubleQuoted(text, index);
2077
2277
  }
2078
- if (char === BACKSLASH$1 && index + 1 < text.length) {
2278
+ if (char === BACKSLASH$2 && index + 1 < text.length) {
2079
2279
  return { end: index + 2, value: text[index + 1] };
2080
2280
  }
2081
2281
  return { end: index + 1, value: char };
2082
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
+ }
2083
2295
  const endsWord = (text, index) => {
2084
2296
  const char = text[index];
2085
2297
  return WORD_BREAKS.has(char) || COMMAND_SEPARATORS.has(char) || isLineContinuation(text, index);
@@ -2087,8 +2299,13 @@ const endsWord = (text, index) => {
2087
2299
  function readWord(text, start) {
2088
2300
  let value = "";
2089
2301
  let index = start;
2090
- while (index < text.length && !endsWord(text, index)) {
2091
- const part = wordPart(text, index);
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) };
2092
2309
  value += part.value;
2093
2310
  index = part.end;
2094
2311
  }
@@ -2119,11 +2336,17 @@ function shellAssignments(command) {
2119
2336
  return name === void 0 ? [] : [{ name, value: word.value.slice(name.length + 1), word }];
2120
2337
  });
2121
2338
  }
2339
+ const ARRAY_OPEN = "(";
2340
+ const ARRAY_CLOSE = ")";
2341
+ const isArrayAssignment = (assignment) => assignment.word.raw.startsWith(ARRAY_OPEN, assignment.name.length + 1);
2122
2342
  function assignmentValueText(assignment) {
2123
2343
  const at = assignment.name.length + 1;
2124
2344
  const raw = assignment.word.raw.slice(at);
2125
2345
  const start = assignment.word.start + at;
2126
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
+ }
2127
2350
  if (QUOTES$3.has(quote)) {
2128
2351
  const isOneString = raw.length > 1 && raw.endsWith(quote) && wordPart(raw, 0).end === raw.length;
2129
2352
  return isOneString ? { text: raw.slice(1, -1), start: start + 1 } : void 0;
@@ -2192,62 +2415,6 @@ function commandRoles(words) {
2192
2415
  return { program: void 0, packages };
2193
2416
  }
2194
2417
 
2195
- const PACKAGE_RENAMES = /* @__PURE__ */ new Map([
2196
- ["openapi-typescript", "openapi-typesafe"],
2197
- ["openapi-fetch", "openapi-typesafe-fetch"],
2198
- ["openapi-react-query", "openapi-typesafe-react-query"],
2199
- ["openapi-typescript-helpers", "openapi-typesafe-helpers"]
2200
- ]);
2201
- function namesOriginalPackage(text) {
2202
- return [...PACKAGE_RENAMES.keys()].some((name) => text.includes(name));
2203
- }
2204
- const GENERATOR_COMMAND = "openapi-typescript";
2205
- const MIGRATED_GENERATOR_COMMAND = "openapi-typesafe";
2206
- const FLAG_RENAMES = /* @__PURE__ */ new Map([
2207
- ["--immutable-types", "--immutable"],
2208
- ["-it", "--immutable"],
2209
- ["--support-array-length", "--array-length"],
2210
- ["--redoc", "--redocly"],
2211
- ["-ap", "--additional-properties"]
2212
- ]);
2213
- const DEPENDENCY_FIELDS = [
2214
- "dependencies",
2215
- "devDependencies",
2216
- "peerDependencies",
2217
- "optionalDependencies"
2218
- ];
2219
- const NAME_KEYED_FIELDS = ["peerDependenciesMeta", "dependenciesMeta"];
2220
- const NAME_LIST_FIELDS = ["bundleDependencies", "bundledDependencies"];
2221
- const CATALOG_FIELD = "catalog";
2222
- const NAMED_CATALOGS_FIELD = "catalogs";
2223
- const WORKSPACES_FIELD = "workspaces";
2224
- const OVERRIDE_FIELDS = ["overrides", "resolutions"];
2225
- const PNPM_SETTINGS_FIELD = "pnpm";
2226
- const OLD_PEER_DEPENDENTS = /* @__PURE__ */ new Map([
2227
- ["swr-openapi", { peer: "openapi-fetch", range: ">=0.15" }]
2228
- ]);
2229
- const PATH_SEPARATOR$9 = "/";
2230
- const QUERY_START = "?";
2231
- function renamedSpecifier(value) {
2232
- const ends = [value.indexOf(PATH_SEPARATOR$9), value.indexOf(QUERY_START)].filter(
2233
- (at) => at !== -1
2234
- );
2235
- const name = value.slice(0, ends.length === 0 ? value.length : Math.min(...ends));
2236
- const renamed = PACKAGE_RENAMES.get(name);
2237
- if (renamed === void 0) {
2238
- return void 0;
2239
- }
2240
- const rest = value.slice(name.length);
2241
- const subpath = rest.split(QUERY_START, 1)[0] ?? rest;
2242
- return { to: `${renamed}${rest}`, isDeep: subpath !== "" && subpath !== PATH_SEPARATOR$9 };
2243
- }
2244
- const NODE_MODULES_FOLDER = "node_modules/";
2245
- function oldPackageInNodeModules(path) {
2246
- return [...PACKAGE_RENAMES.keys()].find(
2247
- (name) => path.includes(`${NODE_MODULES_FOLDER}${name}${PATH_SEPARATOR$9}`)
2248
- );
2249
- }
2250
-
2251
2418
  function packageReference(value) {
2252
2419
  const bare = value.startsWith(NPM_SPECIFIER_PREFIX) ? value.slice(NPM_SPECIFIER_PREFIX.length) : value;
2253
2420
  const at = bare.indexOf("@", 1);
@@ -2282,10 +2449,62 @@ function runsGenerator(command) {
2282
2449
  return word !== void 0 && packageReference(word.value).name === GENERATOR_COMMAND;
2283
2450
  }
2284
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;
2465
+ }
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
+ };
2475
+ }
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
+ }
2492
+ }
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;
2502
+ }
2503
+
2285
2504
  const HEREDOC_OPERATOR = "<<";
2286
2505
  const HERE_STRING_OPERATOR = "<<<";
2287
2506
  const STRIP_TABS = "-";
2288
- const LINE_BREAK$2 = "\n";
2507
+ const LINE_BREAK$4 = "\n";
2289
2508
  const LEADING_TABS_RE = /^\t+/;
2290
2509
  const CARRIAGE_RETURN_RE = /\r$/;
2291
2510
  function heredocAt(text, index, where) {
@@ -2317,7 +2536,7 @@ function isDelimiterLine(text, lineStart, end, heredoc) {
2317
2536
  function readBody(text, start, heredoc) {
2318
2537
  let lineStart = start;
2319
2538
  while (lineStart < text.length) {
2320
- const lineEnd = text.indexOf(LINE_BREAK$2, lineStart);
2539
+ const lineEnd = text.indexOf(LINE_BREAK$4, lineStart);
2321
2540
  const end = lineEnd === -1 ? text.length : lineEnd;
2322
2541
  if (isDelimiterLine(text, lineStart, end, heredoc)) {
2323
2542
  const body = { command: heredoc.command, start, text: text.slice(start, lineStart) };
@@ -2339,7 +2558,7 @@ function readBodies(text, start, pending) {
2339
2558
  }
2340
2559
 
2341
2560
  const COMMENT = "#";
2342
- const LINE_BREAK$1 = "\n";
2561
+ const LINE_BREAK$3 = "\n";
2343
2562
  function skipTrivia(text, index) {
2344
2563
  const char = text[index];
2345
2564
  if (WORD_BREAKS.has(char)) {
@@ -2369,7 +2588,7 @@ function topLevelCommands(text, indent) {
2369
2588
  index = heredoc.end;
2370
2589
  } else if (afterTrivia !== index) {
2371
2590
  index = afterTrivia;
2372
- } else if (text[index] === LINE_BREAK$1 && pending.length > 0) {
2591
+ } else if (text[index] === LINE_BREAK$3 && pending.length > 0) {
2373
2592
  const read = readBodies(text, index + 1, pending);
2374
2593
  heredocs.push(...read.bodies);
2375
2594
  pending = [];
@@ -2434,18 +2653,18 @@ const DOCKER_RUN = "RUN";
2434
2653
  const PASS_THROUGH = "cat";
2435
2654
  const PATH_SEPARATOR$8 = "/";
2436
2655
  const OPTION_PREFIX$1 = "-";
2437
- const programName = (command) => command[0]?.value.split(PATH_SEPARATOR$8).at(-1) ?? "";
2656
+ const programName$1 = (command) => command[0]?.value.split(PATH_SEPARATOR$8).at(-1) ?? "";
2438
2657
  const isOption = (word) => word.value.startsWith(OPTION_PREFIX$1);
2439
- 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);
2440
2659
  function readsScript(command) {
2441
- if (programName(command) !== DOCKER_RUN) {
2660
+ if (programName$1(command) !== DOCKER_RUN) {
2442
2661
  return isScriptShell(command);
2443
2662
  }
2444
2663
  const after = command.slice(1);
2445
2664
  const shell = after.slice(after.findIndex((word) => !isOption(word)));
2446
2665
  return after.every(isOption) || isScriptShell(shell);
2447
2666
  }
2448
- 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);
2449
2668
  function scriptsRunBy(top, index, command) {
2450
2669
  const previous = index - 1;
2451
2670
  const isPipedScript = top.pipes.has(previous) && isScriptShell(command) && passesThrough(simpleCommand(top.commands[previous] ?? []));
@@ -2457,16 +2676,20 @@ const shifted = (words, offset) => words.map((word) => ({ ...word, start: word.s
2457
2676
  function shellCommandsAt(text, offset, indent = 0) {
2458
2677
  return shellCommands(text, indent).map((words) => shifted(words, offset));
2459
2678
  }
2679
+ const expansionCommands = (word) => wordExpansions(word.raw).flatMap(
2680
+ (expansion) => shellCommandsAt(expansion.text, word.start + expansion.start)
2681
+ );
2460
2682
  function innerCommands(command) {
2461
- const quoted = command.flatMap((word) => {
2683
+ const assignments = new Map(shellAssignments(command).map((found) => [found.word, found]));
2684
+ return command.flatMap((word) => {
2462
2685
  const inner = nestedCommandText(word);
2463
- return inner === void 0 ? [] : shellCommandsAt(inner, word.start + 1);
2464
- });
2465
- const assigned = shellAssignments(command).flatMap((assignment) => {
2466
- const value = assignmentValueText(assignment);
2467
- return value === void 0 ? [] : shellCommandsAt(value.text, value.start);
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);
2468
2692
  });
2469
- return [...quoted, ...assigned];
2470
2693
  }
2471
2694
  function shellCommands(text, indent = 0) {
2472
2695
  const top = topLevelCommands(text, indent);
@@ -2540,19 +2763,27 @@ function expandedName(text) {
2540
2763
  const match = SHELL_VARIABLE_RE.exec(text);
2541
2764
  return match === null ? void 0 : match[1] ?? match[2];
2542
2765
  }
2543
- function programDefinition$1({ name, value }) {
2544
- const target = expandedName(value.trim());
2545
- if (target !== void 0) {
2546
- return { name, kind: "reference", target };
2547
- }
2548
- return { name, kind: "command", runsGenerator: shellCommands(value).some(runsGenerator) };
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);
2549
2780
  }
2550
2781
  function shellVariables(definitions) {
2551
2782
  const values = /* @__PURE__ */ new Map();
2552
2783
  for (const { name, value } of definitions) {
2553
2784
  values.set(name, [values.get(name), value].filter((part) => part !== void 0).join(" "));
2554
2785
  }
2555
- const programs = variablePrograms(definitions.map(programDefinition$1));
2786
+ const programs = variablePrograms(definitions.flatMap(programDefinitions$1));
2556
2787
  const nameAt = (word) => expandedName(word.raw);
2557
2788
  return {
2558
2789
  programAt: (program) => {
@@ -2573,8 +2804,15 @@ function combinedUse(left, right) {
2573
2804
  }
2574
2805
  return left.program === right.program ? left : { ...left, program: "generator-or-other" };
2575
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
+ }
2576
2813
  function withShellAssignments(commands, file) {
2577
- const definitions = commands.flatMap(shellAssignments);
2814
+ const assigned = commands.flatMap(shellAssignments).filter((found) => !isArrayAssignment(found));
2815
+ const definitions = [...assigned, ...assignedByExpansions(commands)];
2578
2816
  if (definitions.length === 0) {
2579
2817
  return file;
2580
2818
  }
@@ -2584,6 +2822,35 @@ function withShellAssignments(commands, file) {
2584
2822
  referenceAt: (word) => own.referenceAt(word) ?? file.referenceAt(word)
2585
2823
  };
2586
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
+ }
2587
2854
 
2588
2855
  const QUOTES$1 = /* @__PURE__ */ new Set(["'", '"']);
2589
2856
  const PATH_SEPARATOR$7 = "/";
@@ -2720,7 +2987,7 @@ function shellTextFindings(text, locate) {
2720
2987
  return commandWordFindings(shellCommands(text), locate);
2721
2988
  }
2722
2989
  function commandWordFindings(commands, locate, variables = NO_VARIABLES) {
2723
- const known = withShellAssignments(commands, variables);
2990
+ const known = withUnreadVariables(withWordExpansions(withShellAssignments(commands, variables)));
2724
2991
  const findings = commands.flatMap((words) => commandFindings(words, locate, known)).sort((left, right) => left.offset - right.offset);
2725
2992
  return {
2726
2993
  edits: findings.flatMap((finding) => finding.edit === void 0 ? [] : [finding.edit]),
@@ -2794,45 +3061,6 @@ function commandStringSteps(text, strings) {
2794
3061
  });
2795
3062
  }
2796
3063
 
2797
- const isMap = (node) => node?.kind === "map";
2798
- const isScalar = (node) => node.kind === "scalar";
2799
- function textEntries(map) {
2800
- return map.entries.flatMap(({ key, value }) => isScalar(key) ? [{ key, value }] : []);
2801
- }
2802
- function valuesAt(map, key) {
2803
- return textEntries(map).filter((entry) => entry.key.value === key).map((entry) => entry.value);
2804
- }
2805
- function mapsAt(map, key) {
2806
- return valuesAt(map, key).filter(isMap);
2807
- }
2808
- function stringsBelow(node, path) {
2809
- switch (node.kind) {
2810
- case "scalar":
2811
- return [{ ...node, path }];
2812
- case "list":
2813
- return node.items.flatMap((item, index) => stringsBelow(item, [...path, index]));
2814
- case "map":
2815
- return textEntries(node).flatMap(
2816
- ({ key, value }) => stringsBelow(value, [...path, key.value])
2817
- );
2818
- default:
2819
- return [];
2820
- }
2821
- }
2822
- function documentStrings(documents) {
2823
- return documents.flatMap((document) => stringsBelow(document, []));
2824
- }
2825
- function ownerKey(path) {
2826
- return path.findLast((part) => typeof part === "string");
2827
- }
2828
- function unreadableConfig(text, offset) {
2829
- if (!namesOriginalPackage(text)) {
2830
- return unchanged(text);
2831
- }
2832
- const position = offset === void 0 ? void 0 : { line: lineAt(text, offset), column: columnAt(text, offset) };
2833
- return { ...unchanged(text), manualSteps: [{ kind: "config-unreadable", position }] };
2834
- }
2835
-
2836
3064
  const KEPT_PROTOCOLS = ["catalog:", "workspace:"];
2837
3065
  const LOCAL_PROTOCOLS = ["workspace:", "link:", "file:", "portal:"];
2838
3066
  const RANGE_SEPARATOR = "||";
@@ -2915,8 +3143,12 @@ function localPackageNames(manifest) {
2915
3143
  );
2916
3144
  }
2917
3145
  function localPackagesFor(manifests, filePath) {
2918
- const containing = manifests.filter(({ path }) => isAtOrAbove(path, filePath));
2919
- return new Set(containing.flatMap(({ localPackages }) => [...localPackages ?? []]));
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));
2920
3152
  }
2921
3153
  function isDeclaredFor(manifests, filePath, name) {
2922
3154
  const original = ORIGINAL_NAMES.get(name);
@@ -2950,7 +3182,7 @@ const NOT_LINE_BREAK_RE$1 = /[^\n]/g;
2950
3182
  const ARRAY_END = "]";
2951
3183
  const VARIABLE_INSTRUCTIONS = /* @__PURE__ */ new Set(["ARG", "ENV"]);
2952
3184
  const LEGACY_ENV_INSTRUCTION = "ENV";
2953
- const ASSIGNMENT = "=";
3185
+ const ASSIGNMENT$1 = "=";
2954
3186
  const QUOTES = /* @__PURE__ */ new Set(['"', "'"]);
2955
3187
  function matchAt(pattern, text, index) {
2956
3188
  pattern.lastIndex = index;
@@ -3049,7 +3281,7 @@ function escapedScriptSteps(scripts, lineOf) {
3049
3281
  });
3050
3282
  }
3051
3283
  function assignedVariable(word) {
3052
- const at = word.raw.indexOf(ASSIGNMENT);
3284
+ const at = word.raw.indexOf(ASSIGNMENT$1);
3053
3285
  if (at <= 0) {
3054
3286
  return void 0;
3055
3287
  }
@@ -3067,7 +3299,7 @@ function dockerVariables(commands) {
3067
3299
  if (!VARIABLE_INSTRUCTIONS.has(instruction.value)) {
3068
3300
  return [];
3069
3301
  }
3070
- if (instruction.value === LEGACY_ENV_INSTRUCTION && !first.raw.includes(ASSIGNMENT)) {
3302
+ if (instruction.value === LEGACY_ENV_INSTRUCTION && !first.raw.includes(ASSIGNMENT$1)) {
3071
3303
  return [{ name: first.value, commands: [rest] }];
3072
3304
  }
3073
3305
  return [first, ...rest].flatMap((word) => assignedVariable(word) ?? []);
@@ -3378,30 +3610,12 @@ function migrateImportMap(text, read, version) {
3378
3610
  }
3379
3611
 
3380
3612
  const IMPORT_MAP_FIELD = "importMap";
3381
- const SEPARATOR = "/";
3382
- const CURRENT_FOLDER = ".";
3383
- const PARENT_FOLDER = "..";
3384
- const URL_SCHEME = /^[a-z][a-z\d+.-]*:/i;
3385
3613
  function importMapReferences(roots) {
3386
3614
  return roots.flatMap((root) => valuesAt(root, IMPORT_MAP_FIELD)).flatMap((value) => isScalar(value) ? [value.value] : []);
3387
3615
  }
3388
- function resolvedPath(configPath, reference) {
3389
- const parts = configPath.split(SEPARATOR).slice(0, -1);
3390
- for (const part of reference.split(SEPARATOR)) {
3391
- if (part === PARENT_FOLDER) {
3392
- if (parts.pop() === void 0) {
3393
- return void 0;
3394
- }
3395
- } else if (part !== CURRENT_FOLDER && part !== "") {
3396
- parts.push(part);
3397
- }
3398
- }
3399
- return parts.join(SEPARATOR);
3400
- }
3401
3616
  function importMapLocation(configPath, reference) {
3402
- const isOutside = URL_SCHEME.test(reference) || reference.startsWith(SEPARATOR);
3403
- const path = isOutside ? void 0 : resolvedPath(configPath, reference);
3404
- return path === void 0 || path === "" ? { kind: "not-editable", reference } : { kind: "in-project", path };
3617
+ const path = referencedProjectPath(configPath, reference);
3618
+ return path === void 0 ? { kind: "not-editable", reference } : { kind: "in-project", path };
3405
3619
  }
3406
3620
  const importMapLocations = (configPath, roots) => importMapReferences(roots).map((reference) => importMapLocation(configPath, reference));
3407
3621
  function importMapSteps(configPath, read) {
@@ -3580,58 +3794,234 @@ function migrateJsonCommands(text, strings, patterns) {
3580
3794
  return { text: applyTextEdits(text, edits), changes, manualSteps };
3581
3795
  }
3582
3796
 
3583
- const ASSIGNMENT_RE$1 = /^(?:export[ \t]+)?([A-Za-z_][A-Za-z0-9_-]*)[ \t]*:=[ \t]*(.*)$/gm;
3584
- const STRING_VALUE_RE = /^(?:"((?:[^"\\\n]|\\.)*)"|'([^'\n]*)')(?:[ \t]+#.*)?$/;
3585
- const NAME_VALUE_RE = /^([A-Za-z_][A-Za-z0-9_-]*)(?:[ \t]+#.*)?$/;
3586
- const INTERPOLATION_RE = /\{\{[^\n]*?\}\}/g;
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_-]*$/;
3587
3951
  const VARIABLE_INTERPOLATION_RE = /^[@+-]*\{\{[ \t]*([A-Za-z_][A-Za-z0-9_-]*)[ \t]*\}\}$/;
3588
3952
  const MASK$1 = "_";
3589
3953
  const BLANK = " ";
3590
- const assignmentsOf$1 = (text) => [...text.matchAll(ASSIGNMENT_RE$1)].map((match) => {
3591
- const written = match[2];
3592
- const valueStart = match.index + match[0].length - written.length;
3593
- return { name: match[1], value: written.trim(), start: match.index, valueStart };
3594
- });
3595
- function stringValue(value) {
3596
- const match = STRING_VALUE_RE.exec(value);
3597
- return match === null ? void 0 : match[1] ?? match[2];
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];
3598
3974
  }
3599
- function programDefinition({ name, value }) {
3600
- const target = NAME_VALUE_RE.exec(value)?.[1];
3601
- if (target !== void 0) {
3602
- return { name, kind: "reference", target };
3975
+ function assignmentDefinitions(assignment) {
3976
+ const { name, value } = assignment;
3977
+ if (NAME_VALUE_RE.test(value)) {
3978
+ return [{ name, kind: "reference", target: value }];
3603
3979
  }
3604
- const text = stringValue(value);
3605
- const runs = text !== void 0 && shellCommands(text).some(runsGenerator);
3606
- return { name, kind: "command", runsGenerator: runs };
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];
3607
3986
  }
3608
- function shellText$1(text, assignments) {
3987
+ function shellText$1(text, spans) {
3609
3988
  const parts = [];
3610
3989
  let copiedUpTo = 0;
3611
- for (const { start, valueStart, value } of assignments) {
3612
- const end = valueStart + value.length;
3990
+ for (const { start, end } of [...spans].sort((left, right) => left.start - right.start)) {
3613
3991
  parts.push(text.slice(copiedUpTo, start), BLANK.repeat(end - start));
3614
3992
  copiedUpTo = end;
3615
3993
  }
3616
3994
  parts.push(text.slice(copiedUpTo));
3617
- return parts.join("").replace(INTERPOLATION_RE, (found) => MASK$1.repeat(found.length));
3618
- }
3619
- function valueCommands({ value, valueStart }) {
3620
- const inner = stringValue(value);
3621
- return inner === void 0 ? [] : shellCommandsAt(inner, valueStart + 1);
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 };
3622
4002
  }
3623
- function justfileVariables(text, assignments, programs) {
4003
+ const literalCommands = (literals) => literals.flatMap((literal) => shellCommandsAt(literal.content, literal.contentStart));
4004
+ function variableValues$1(assignments) {
3624
4005
  const values = /* @__PURE__ */ new Map();
3625
- for (const { name, value } of assignments) {
3626
- values.set(name, [values.get(name), stringValue(value) ?? value].join(" ").trim());
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());
3627
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) {
3628
4017
  const writtenAt = (word) => text.slice(word.start, word.end);
3629
4018
  const nameAt = (word) => VARIABLE_INTERPOLATION_RE.exec(writtenAt(word))?.[1];
3630
4019
  return {
3631
- programAt: (program) => {
3632
- const name = nameAt(program);
3633
- const found = name === void 0 ? void 0 : programs.get(name);
3634
- return found === void 0 ? void 0 : { written: writtenAt(program), program: found };
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 };
3635
4025
  },
3636
4026
  referenceAt: (word) => {
3637
4027
  const name = nameAt(word);
@@ -3643,11 +4033,19 @@ function justfileVariables(text, assignments, programs) {
3643
4033
  function migrateJustfile(text, file) {
3644
4034
  const lineOf = lineLocator(text);
3645
4035
  const assignments = assignmentsOf$1(text);
3646
- const programs = variablePrograms(assignments.map(programDefinition));
3647
- const variables = justfileVariables(text, assignments, programs);
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]);
3648
4044
  const commands = [
3649
- ...shellCommands(shellText$1(text, assignments)),
3650
- ...assignments.flatMap(valueCommands)
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))
3651
4049
  ];
3652
4050
  const locate = (offset) => `${file}:${lineOf(offset)}`;
3653
4051
  const { edits, changes, manualSteps } = commandWordFindings(commands, locate, variables);
@@ -3765,7 +4163,7 @@ function makefileVariables(text, assignments, programs) {
3765
4163
  const variables = {
3766
4164
  programAt: (program) => {
3767
4165
  const name = nameAt(program);
3768
- const found = name === void 0 ? void 0 : programs.get(name);
4166
+ const found = name === void 0 ? void 0 : programs.get(name) ?? "generator-or-other";
3769
4167
  const written = text.slice(program.start, program.end);
3770
4168
  return found === void 0 ? void 0 : { written, program: found };
3771
4169
  },
@@ -4130,19 +4528,63 @@ function oldPackagePatchSteps(fileName) {
4130
4528
  }
4131
4529
 
4132
4530
  const STEP_SHELL_KEY = "shell";
4531
+ const DEFAULT_SHELL_PATH = ["defaults", "run", STEP_SHELL_KEY];
4133
4532
  const COMMAND_LINE_SHELLS = /* @__PURE__ */ new Set([
4134
4533
  ...["sh", "bash", "dash", "ash", "zsh", "ksh"],
4135
- ...["pwsh", "powershell", "cmd"]
4534
+ ...["pwsh", "powershell", "cmd"],
4535
+ ...["msys2", "wsl-bash"]
4136
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 = "=";
4137
4541
  const PATH_SEPARATOR_RE = /[/\\]/;
4542
+ const WINDOWS_EXECUTABLE_RE = /\.exe$/i;
4138
4543
  const WHITESPACE_RE = /\s+/;
4139
- const shellProgram = (shell) => shell.trim().split(WHITESPACE_RE)[0]?.split(PATH_SEPARATOR_RE).at(-1) ?? "";
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
+ }
4140
4574
  function stepShells(strings) {
4141
- const stepOf = (path) => JSON.stringify(path.slice(0, -1));
4142
- const others = new Map(
4143
- strings.filter((value) => value.path.at(-1) === STEP_SHELL_KEY).filter((value) => !COMMAND_LINE_SHELLS.has(shellProgram(value.value))).map((value) => [stepOf(value.path), value.value])
4144
- );
4145
- return (value) => others.get(stepOf(value.path));
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
+ };
4146
4588
  }
4147
4589
  function otherShellFindings(values, shellOf, locate) {
4148
4590
  const manualSteps = values.flatMap((value) => {
@@ -4176,10 +4618,6 @@ const TASKFILE_COMMAND_KEYS = /* @__PURE__ */ new Set([
4176
4618
  "status"
4177
4619
  ]);
4178
4620
 
4179
- const API_DESCRIPTION_KEYS = /* @__PURE__ */ new Set(["openapi", "swagger", "asyncapi"]);
4180
- const isApiDescription = (documents) => documents.some(
4181
- (root) => isMap(root) && textEntries(root).some(({ key }) => API_DESCRIPTION_KEYS.has(key.value))
4182
- );
4183
4621
  const ENVIRONMENT_KEYS = /* @__PURE__ */ new Set(["env"]);
4184
4622
  const isEnvironmentValue = (value) => value.path.length >= 2 && ENVIRONMENT_KEYS.has(String(value.path.at(-2)));
4185
4623
  function environmentVariables(text, strings) {
@@ -4589,7 +5027,7 @@ const COMMAND_CONFIG_KINDS = [
4589
5027
  )
4590
5028
  }
4591
5029
  ];
4592
- const yamlCommandsStep = (path) => (text, read) => read.kind === "read" && isApiDescription(read.documents) ? unchanged(text) : stringsStep((current, strings) => migrateYamlCommands(current, path, strings))(text, read);
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);
4593
5031
  const manifestTarget = ({ version, catalogs, aliases }, path) => ({
4594
5032
  version,
4595
5033
  catalogs,
@@ -4665,7 +5103,11 @@ const FILE_KINDS = [
4665
5103
  },
4666
5104
  {
4667
5105
  matches: (path) => YAML_EXTENSIONS.has(extensionOf(path)),
4668
- migrate: whenNamed(yamlConfig((_context, path) => [yamlCommandsStep(path)]))
5106
+ migrate: whenNamed(
5107
+ yamlConfig((context, path) => [
5108
+ yamlCommandsStep(path, context.apiDescriptionParts.has(path))
5109
+ ])
5110
+ )
4669
5111
  },
4670
5112
  {
4671
5113
  matches: (path) => DOCKERFILE_NAMES.has(baseName(path)),
@@ -4694,6 +5136,58 @@ function fileMigrationFor(path, namedImportMaps = NO_NAMED_IMPORT_MAPS) {
4694
5136
  return FILE_KINDS.find((kind) => kind.matches(path))?.migrate;
4695
5137
  }
4696
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
+
4697
5191
  const readJson = (text, readers) => readers.json.read(text);
4698
5192
  const readYaml = (text, readers) => readers.yaml.read(text);
4699
5193
  function planSourceOf(path, namedImportMaps) {
@@ -4774,7 +5268,11 @@ async function planProjectTarget(paths, dependencies, version) {
4774
5268
  isWorkspaceRoot
4775
5269
  });
4776
5270
  const declarations = withNamedImportMaps(manifests, importMaps);
4777
- return { version, catalogs, manifests: declarations, aliases, importMaps };
5271
+ const parts = await apiDescriptionParts(paths, dependencies);
5272
+ return {
5273
+ ...{ version, catalogs, manifests: declarations, aliases, importMaps },
5274
+ apiDescriptionParts: parts
5275
+ };
4778
5276
  }
4779
5277
 
4780
5278
  async function planFile(path, dependencies, target) {
@@ -4787,9 +5285,9 @@ async function planFile(path, dependencies, target) {
4787
5285
  return read;
4788
5286
  }
4789
5287
  const { rewriter, scanner, readers } = dependencies;
4790
- const { version, catalogs, manifests, aliases } = target;
5288
+ const { version, catalogs, manifests, aliases, apiDescriptionParts } = target;
4791
5289
  const context = {
4792
- ...{ version, catalogs, manifests, aliases },
5290
+ ...{ version, catalogs, manifests, aliases, apiDescriptionParts },
4793
5291
  ...{ rewriter, scanner, readers }
4794
5292
  };
4795
5293
  const migration = migrate(read.text, path, context);
@@ -4879,7 +5377,7 @@ function terminalSafeText(text) {
4879
5377
  const PINNED_NOTE = "change it to openapi-typesafe@<version>";
4880
5378
  const OLD_FILE_NOTE$1 = "is a file of openapi-typescript, which is no longer installed: run openapi-typesafe instead";
4881
5379
  const FLAGS_IN_VARIABLE_NOTE = "holds old flags of the generator and may serve other commands, so it was kept: rename";
4882
- const NOT_ONLY_GENERATOR_NOTE = "runs openapi-typescript in some of its definitions in this file and another program in others, so the flags after it were kept; where it runs the generator, rename";
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";
4883
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";
4884
5382
  const SCRIPT_NOT_SHELL_NOTE = "so the tool does not read it as commands: migrate the old package names in it by hand";
4885
5383
  const COMMAND_STRING_NOTE = "a string runs openapi-typescript, so it was kept; change it to";