openapi-typesafe-migrate 1.0.0-rc.11 → 1.0.0-rc.12

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
@@ -910,12 +910,19 @@ function optionsOf(argument, bindings) {
910
910
  }
911
911
  return ts.isIdentifier(value) ? constantOptions(value, bindings, types) : { kind: "unknown", expression: argument };
912
912
  }
913
+ const UNDEFINED = "undefined";
914
+ function isNoValue(argument, bindings) {
915
+ const { value } = unwrapped(argument);
916
+ const isUndefined = ts.isIdentifier(value) && value.text === UNDEFINED && bindings.declarationOf(value) === void 0;
917
+ return isUndefined || ts.isVoidExpression(value);
918
+ }
913
919
  function generatorCalls(file, bindings) {
914
920
  const calls = [];
915
921
  const visit = (node) => {
916
922
  if (ts.isCallExpression(node) && isGenerator(node.expression, bindings)) {
917
923
  const argument = node.arguments[1];
918
- const options = argument === void 0 ? void 0 : optionsOf(argument, bindings);
924
+ const isOmitted = argument === void 0 || isNoValue(argument, bindings);
925
+ const options = isOmitted ? void 0 : optionsOf(argument, bindings);
919
926
  calls.push({ call: node, options });
920
927
  }
921
928
  ts.forEachChild(node, visit);
@@ -949,7 +956,7 @@ function importTypeName(node) {
949
956
  const isOptions = !node.isTypeOf && qualifier !== void 0 && ts.isIdentifier(qualifier) && qualifier.text === TS_NODE_OPTIONS;
950
957
  return isGeneratorModule && isOptions ? qualifier : void 0;
951
958
  }
952
- function referencedName$1(node) {
959
+ function referencedName$2(node) {
953
960
  if (ts.isTypeReferenceNode(node)) {
954
961
  return node.typeName;
955
962
  }
@@ -965,7 +972,7 @@ function optionsTypeName(node, bindings) {
965
972
  if (ts.isImportTypeNode(node)) {
966
973
  return importTypeName(node);
967
974
  }
968
- const name = referencedName$1(node);
975
+ const name = referencedName$2(node);
969
976
  if (name !== void 0 && ts.isIdentifier(name)) {
970
977
  const binding = bindings.bindingOf(name);
971
978
  return binding?.kind === "helper-import" && binding.name === TS_NODE_OPTIONS ? name : void 0;
@@ -978,7 +985,7 @@ function optionsTypeName(node, bindings) {
978
985
  return isGeneratorModule && parts.right.text === TS_NODE_OPTIONS ? parts.right : void 0;
979
986
  }
980
987
  function localTypeOf(node, bindings) {
981
- const name = referencedName$1(node);
988
+ const name = referencedName$2(node);
982
989
  const binding = name !== void 0 && ts.isIdentifier(name) ? bindings.bindingOf(name) : void 0;
983
990
  return binding?.kind === "local-type" ? binding.declaration : void 0;
984
991
  }
@@ -2072,10 +2079,10 @@ const PNPM_SETTINGS_FIELD = "pnpm";
2072
2079
  const OLD_PEER_DEPENDENTS = /* @__PURE__ */ new Map([
2073
2080
  ["swr-openapi", { peer: "openapi-fetch", range: ">=0.15" }]
2074
2081
  ]);
2075
- const PATH_SEPARATOR$a = "/";
2082
+ const PATH_SEPARATOR$b = "/";
2076
2083
  const QUERY_START = "?";
2077
2084
  function renamedSpecifier(value) {
2078
- const ends = [value.indexOf(PATH_SEPARATOR$a), value.indexOf(QUERY_START)].filter(
2085
+ const ends = [value.indexOf(PATH_SEPARATOR$b), value.indexOf(QUERY_START)].filter(
2079
2086
  (at) => at !== -1
2080
2087
  );
2081
2088
  const name = value.slice(0, ends.length === 0 ? value.length : Math.min(...ends));
@@ -2085,12 +2092,12 @@ function renamedSpecifier(value) {
2085
2092
  }
2086
2093
  const rest = value.slice(name.length);
2087
2094
  const subpath = rest.split(QUERY_START, 1)[0] ?? rest;
2088
- return { to: `${renamed}${rest}`, isDeep: subpath !== "" && subpath !== PATH_SEPARATOR$a };
2095
+ return { to: `${renamed}${rest}`, isDeep: subpath !== "" && subpath !== PATH_SEPARATOR$b };
2089
2096
  }
2090
2097
  const NODE_MODULES_FOLDER = "node_modules/";
2091
2098
  function oldPackageInNodeModules(path) {
2092
2099
  return [...PACKAGE_RENAMES.keys()].find(
2093
- (name) => path.includes(`${NODE_MODULES_FOLDER}${name}${PATH_SEPARATOR$a}`) || path.endsWith(`${NODE_MODULES_FOLDER}${name}`)
2100
+ (name) => path.includes(`${NODE_MODULES_FOLDER}${name}${PATH_SEPARATOR$b}`) || path.endsWith(`${NODE_MODULES_FOLDER}${name}`)
2094
2101
  );
2095
2102
  }
2096
2103
 
@@ -2311,6 +2318,14 @@ const LAUNCHERS = [
2311
2318
  launcher(["xargs"], NONE, XARGS_OPTIONS),
2312
2319
  launcher(["nohup"]),
2313
2320
  launcher(["sudo"], NONE, /* @__PURE__ */ new Set(["-u", "-g"])),
2321
+ // The init programs of containers run their arguments as the container's command, so an
2322
+ // entrypoint `["tini", "--"]` gives the generator the command's flags.
2323
+ launcher(["tini"], NONE, /* @__PURE__ */ new Set(["-p", "-e"])),
2324
+ launcher(["docker-init"], NONE, /* @__PURE__ */ new Set(["-p", "-e"])),
2325
+ launcher(["dumb-init"], NONE, /* @__PURE__ */ new Set(["-r", "--rewrite"])),
2326
+ launcher(["catatonit"]),
2327
+ withOperands(["gosu"], 1),
2328
+ withOperands(["su-exec"], 1),
2314
2329
  // The shell form of Dockerfile instructions runs the rest of the line in a shell.
2315
2330
  launcher(["RUN"], NONE, /* @__PURE__ */ new Set(["--mount", "--network", "--security"])),
2316
2331
  launcher(["CMD"]),
@@ -2341,7 +2356,7 @@ const EXPRESSION_CLOSE$1 = "}}";
2341
2356
  const LITERAL_QUOTE = "'";
2342
2357
  const SHELL_SINGLE_QUOTE = "'";
2343
2358
  const BACKSLASH$3 = "\\";
2344
- const DOUBLE_QUOTE$1 = '"';
2359
+ const DOUBLE_QUOTE$2 = '"';
2345
2360
  function expressionEnd(text, start) {
2346
2361
  for (let index = start + EXPRESSION_OPEN.length; index < text.length; ) {
2347
2362
  if (text.startsWith(EXPRESSION_CLOSE$1, index)) {
@@ -2405,7 +2420,7 @@ function mapExpressions(text, map) {
2405
2420
  return parts.join("");
2406
2421
  }
2407
2422
  function isWholeExpression(raw) {
2408
- const isQuoted = raw.length > 1 && raw.startsWith(DOUBLE_QUOTE$1) && raw.endsWith(DOUBLE_QUOTE$1);
2423
+ const isQuoted = raw.length > 1 && raw.startsWith(DOUBLE_QUOTE$2) && raw.endsWith(DOUBLE_QUOTE$2);
2409
2424
  const inner = isQuoted ? 1 : 0;
2410
2425
  return raw.startsWith(EXPRESSION_OPEN, inner) && expressionEnd(raw, inner) === raw.length - inner;
2411
2426
  }
@@ -2421,7 +2436,7 @@ const COMMAND_SEPARATORS = /* @__PURE__ */ new Set([
2421
2436
  ]);
2422
2437
  const WORD_BREAKS = /* @__PURE__ */ new Set([" ", " ", "\r", "<", ">"]);
2423
2438
  const BACKSLASH$2 = "\\";
2424
- const DOUBLE_QUOTE_ESCAPES = /* @__PURE__ */ new Set(["$", "`", '"', BACKSLASH$2, "\n"]);
2439
+ const DOUBLE_QUOTE_ESCAPES$1 = /* @__PURE__ */ new Set(["$", "`", '"', BACKSLASH$2, "\n"]);
2425
2440
  function singleQuoted(text, start) {
2426
2441
  const close = text.indexOf("'", start + 1);
2427
2442
  const end = close === -1 ? text.length : close + 1;
@@ -2432,7 +2447,7 @@ function doubleQuoted(text, start) {
2432
2447
  let index = start + 1;
2433
2448
  while (index < text.length && text[index] !== '"') {
2434
2449
  const next = text[index + 1] ?? "";
2435
- const isEscape = text[index] === BACKSLASH$2 && DOUBLE_QUOTE_ESCAPES.has(next);
2450
+ const isEscape = text[index] === BACKSLASH$2 && DOUBLE_QUOTE_ESCAPES$1.has(next);
2436
2451
  value += isEscape ? next === "\n" ? "" : next : text[index];
2437
2452
  index += isEscape ? 2 : 1;
2438
2453
  }
@@ -2512,7 +2527,7 @@ function wordPart(text, index) {
2512
2527
  function doubleQuotedOffsets(text, start, end) {
2513
2528
  const offsets = [];
2514
2529
  for (let index = start + 1; index < end && text[index] !== '"'; index++) {
2515
- const isEscape = text[index] === BACKSLASH$2 && DOUBLE_QUOTE_ESCAPES.has(text[index + 1] ?? "");
2530
+ const isEscape = text[index] === BACKSLASH$2 && DOUBLE_QUOTE_ESCAPES$1.has(text[index + 1] ?? "");
2516
2531
  index += isEscape ? 1 : 0;
2517
2532
  if (text[index] !== "\n" || !isEscape) {
2518
2533
  offsets.push(index);
@@ -2572,6 +2587,42 @@ function readWord(text, start) {
2572
2587
  return { start, end: index, value, raw: text.slice(start, index) };
2573
2588
  }
2574
2589
 
2590
+ const writtenOffset = (word, index) => word.written?.[index] ?? word.start + index;
2591
+ function rawText(word, from, text) {
2592
+ return { text, offsets: Array.from(text, (_, index) => writtenOffset(word, from + index)) };
2593
+ }
2594
+ function unquotedRawText(word, from) {
2595
+ const { value, offsets } = unquotedText(word.raw.slice(from));
2596
+ return { text: value, offsets: offsets.map((index) => writtenOffset(word, from + index)) };
2597
+ }
2598
+ function placedWord(word, { offsets }) {
2599
+ const written = Array.from(word.raw, (_, index) => {
2600
+ return offsets[writtenOffset(word, index)];
2601
+ });
2602
+ const start = written[0] ?? offsets[word.start];
2603
+ const end = (written.at(-1) ?? start - 1) + 1;
2604
+ const isInOnePiece = written.every((offset, index) => offset === start + index);
2605
+ const placed = { start, end, value: word.value, raw: word.raw };
2606
+ return isInOnePiece ? placed : { ...placed, written };
2607
+ }
2608
+ function movedWord(word, offset) {
2609
+ const moved = { ...word, start: word.start + offset, end: word.end + offset };
2610
+ return word.written === void 0 ? moved : { ...moved, written: word.written.map((at) => at + offset) };
2611
+ }
2612
+ function writtenSpan(word, from, length) {
2613
+ const first = writtenOffset(word, from);
2614
+ const last = writtenOffset(word, from + length - 1);
2615
+ return last - first === length - 1 ? { start: first, end: last + 1 } : void 0;
2616
+ }
2617
+ function placedSpan({ offsets }, length) {
2618
+ const first = offsets[0];
2619
+ const last = offsets[length - 1];
2620
+ if (first === void 0 || last === void 0 || last - first !== length - 1) {
2621
+ return void 0;
2622
+ }
2623
+ return { start: first, end: last + 1 };
2624
+ }
2625
+
2575
2626
  const ASSIGNMENT_RE$2 = /^([A-Za-z_][A-Za-z0-9_]*)(\+?=)/;
2576
2627
  const APPEND_OPERATOR$1 = "+=";
2577
2628
  const DECLARATION_BUILTINS = /* @__PURE__ */ new Set([
@@ -2585,8 +2636,9 @@ function leadingAssignments(command) {
2585
2636
  const index = command.findIndex((word) => !ASSIGNMENT_RE$2.test(word.raw));
2586
2637
  return index === -1 ? command.length : index;
2587
2638
  }
2588
- function assignmentOf(word) {
2589
- const match = ASSIGNMENT_RE$2.exec(word.raw);
2639
+ const ARGUMENT_ASSIGNMENT_RE = /^([A-Za-z_][A-Za-z0-9_.-]*)(:::=|::=|:=|\?=|!=|\+?=)/;
2640
+ function assignmentOf(word, pattern = ASSIGNMENT_RE$2) {
2641
+ const match = pattern.exec(word.raw);
2590
2642
  const name = match?.[1];
2591
2643
  if (match === null || name === void 0) {
2592
2644
  return [];
@@ -2599,42 +2651,22 @@ function shellAssignments(command) {
2599
2651
  const count = leadingAssignments(command);
2600
2652
  const isDeclaration = DECLARATION_BUILTINS.has(command[count]?.value ?? "");
2601
2653
  const words = [...command.slice(0, count), ...isDeclaration ? command.slice(count + 1) : []];
2602
- return words.flatMap(assignmentOf);
2654
+ return words.flatMap((word) => assignmentOf(word));
2603
2655
  }
2604
2656
  function argumentAssignments(command) {
2605
2657
  const own = new Set(shellAssignments(command).map(({ word }) => word));
2606
- return command.slice(leadingAssignments(command)).filter((word) => !own.has(word)).flatMap(assignmentOf);
2658
+ return command.slice(leadingAssignments(command)).filter((word) => !own.has(word)).flatMap((word) => assignmentOf(word, ARGUMENT_ASSIGNMENT_RE));
2607
2659
  }
2608
2660
  const COMMAND_LINE_RE = /[\s;&|()`]/;
2609
2661
  const holdsCommandLine = (assignment) => COMMAND_LINE_RE.test(assignment.value);
2610
2662
  const ARRAY_OPEN = "(";
2611
2663
  const isArrayAssignment = (assignment) => assignment.word.raw.startsWith(ARRAY_OPEN, assignment.head);
2612
- function arrayValue({ word, head }) {
2613
- const start = word.start + head + 1;
2614
- const place = (found) => ({
2615
- ...found,
2616
- start: found.start + start,
2617
- end: found.end + start
2618
- });
2619
- return { text: word.raw.slice(head + 1, -1), place };
2620
- }
2621
2664
  function assignmentValue(assignment) {
2622
- if (isArrayAssignment(assignment)) {
2623
- return arrayValue(assignment);
2624
- }
2625
2665
  const { word, head } = assignment;
2626
- const { value, offsets } = unquotedText(word.raw.slice(head));
2627
- const written = (index) => head + offsets[index];
2628
- const place = (found) => {
2629
- const first = written(found.start);
2630
- const end = written(found.end - 1) + 1;
2631
- const raw = word.raw.slice(first, end);
2632
- return { ...found, start: word.start + first, end: word.start + end, raw };
2633
- };
2634
- return { text: value, place };
2666
+ return isArrayAssignment(assignment) ? rawText(word, head + 1, word.raw.slice(head + 1, -1)) : unquotedRawText(word, head);
2635
2667
  }
2636
2668
 
2637
- const END_OF_OPTIONS = "--";
2669
+ const END_OF_OPTIONS$1 = "--";
2638
2670
  const isOption$1 = (word) => word.value.startsWith("-") && word.value.length > 1;
2639
2671
  function skipAssignments(words, start) {
2640
2672
  let index = start;
@@ -2646,7 +2678,8 @@ function skipAssignments(words, start) {
2646
2678
  const WINDOWS_PROGRAM_SUFFIX_RE = /\.(?:cmd|exe|ps1)$/i;
2647
2679
  const windowsSuffix = (value) => WINDOWS_PROGRAM_SUFFIX_RE.exec(value)?.[0] ?? "";
2648
2680
  const withoutWindowsSuffix = (value) => value.slice(0, value.length - windowsSuffix(value).length);
2649
- const entryWord = (word) => word === void 0 ? void 0 : withoutWindowsSuffix(word.value);
2681
+ const FOLDERS_RE = /^.*[/\\]/;
2682
+ const entryWord = (word) => word === void 0 ? void 0 : withoutWindowsSuffix(word.value.replace(FOLDERS_RE, ""));
2650
2683
  function matchingPrefix(entries, words, index) {
2651
2684
  return entries.find(
2652
2685
  (entry) => entry.words.every((name, offset) => entryWord(words[index + offset]) === name)
@@ -2661,7 +2694,7 @@ function launchedProgram(words, start, launcher, packages) {
2661
2694
  let index = start;
2662
2695
  while (index < words.length && isOption$1(words[index])) {
2663
2696
  const option = words[index++];
2664
- if (option.value === END_OF_OPTIONS) {
2697
+ if (option.value === END_OF_OPTIONS$1) {
2665
2698
  break;
2666
2699
  }
2667
2700
  const inline = option.value.includes("=") ? inlineOptionPackage(option, launcher) : void 0;
@@ -2712,13 +2745,28 @@ const unreadArgumentValues = (words) => new Map(
2712
2745
  );
2713
2746
  function unmigratedWordFindings(words, handledOffsets, locate) {
2714
2747
  const values = unreadArgumentValues(words);
2748
+ const assignments = new Set(shellAssignments(words).map(({ word }) => word));
2715
2749
  const namesOld = (word) => namesOldPackage(word.value) || namesOldPackage(values.get(word) ?? "");
2716
- return words.filter((word) => !handledOffsets.has(word.start) && namesOld(word)).map((word) => ({
2750
+ return words.filter((word) => !handledOffsets.has(word.start) && !assignments.has(word) && namesOld(word)).map((word) => ({
2717
2751
  offset: word.start,
2718
2752
  manualStep: { kind: "old-name-in-command", location: locate(word.start), word: word.value }
2719
2753
  }));
2720
2754
  }
2721
2755
 
2756
+ const PATH_SEPARATOR$a = "/";
2757
+ const WINDOWS_SEPARATOR_RE = /\\/g;
2758
+ const QUOTE_RE = /["']/g;
2759
+ const WINDOWS_PATH_RE = /^[^/]*[^\\/\s]\\[^\\/\s][^/]*$/;
2760
+ function programPath(word) {
2761
+ const suffix = windowsSuffix(word.value);
2762
+ const written = word.raw.replace(QUOTE_RE, "");
2763
+ const isWindowsPath = suffix !== "" || WINDOWS_PATH_RE.test(written);
2764
+ if (!isWindowsPath) {
2765
+ return { path: word.value, suffix, written: word.value };
2766
+ }
2767
+ return { path: written.replace(WINDOWS_SEPARATOR_RE, PATH_SEPARATOR$a), suffix, written };
2768
+ }
2769
+
2722
2770
  const RECIPE_PREFIX_RE$1 = /^[@+-]+(?=.)/;
2723
2771
  function withoutRecipePrefix(words) {
2724
2772
  const [first, ...rest] = words;
@@ -2731,11 +2779,20 @@ const NO_VARIABLES = {
2731
2779
  programAt: () => void 0,
2732
2780
  referenceAt: () => void 0
2733
2781
  };
2782
+ const PATH_SEPARATOR$9 = "/";
2783
+ const BIN_FOLDER$1 = "/.bin/";
2784
+ function namesGenerator(word) {
2785
+ const { path, suffix } = programPath(word);
2786
+ if (path.includes(PATH_SEPARATOR$9)) {
2787
+ return path.endsWith(`${BIN_FOLDER$1}${GENERATOR_COMMAND}${suffix}`);
2788
+ }
2789
+ return packageReference(path.slice(0, path.length - suffix.length)).name === GENERATOR_COMMAND;
2790
+ }
2734
2791
  function runsGenerator(command) {
2735
2792
  const words = withoutRecipePrefix(command);
2736
2793
  const { program } = commandRoles(words);
2737
2794
  const word = program === void 0 ? void 0 : words[program];
2738
- return word !== void 0 && packageReference(withoutWindowsSuffix(word.value)).name === GENERATOR_COMMAND;
2795
+ return word !== void 0 && namesGenerator(word);
2739
2796
  }
2740
2797
 
2741
2798
  const COMMAND_PREFIX_WORDS = /* @__PURE__ */ new Set([
@@ -2775,7 +2832,7 @@ function simpleCommand(words) {
2775
2832
  const HEREDOC_OPERATOR = "<<";
2776
2833
  const HERE_STRING_OPERATOR = "<<<";
2777
2834
  const STRIP_TABS = "-";
2778
- const LINE_BREAK$6 = "\n";
2835
+ const LINE_BREAK$7 = "\n";
2779
2836
  const LEADING_TABS_RE = /^\t+/;
2780
2837
  const CARRIAGE_RETURN_RE = /\r$/;
2781
2838
  function heredocAt(text, index, where) {
@@ -2807,7 +2864,7 @@ function isDelimiterLine(text, lineStart, end, heredoc) {
2807
2864
  function readBody(text, start, heredoc) {
2808
2865
  let lineStart = start;
2809
2866
  while (lineStart < text.length) {
2810
- const lineEnd = text.indexOf(LINE_BREAK$6, lineStart);
2867
+ const lineEnd = text.indexOf(LINE_BREAK$7, lineStart);
2811
2868
  const end = lineEnd === -1 ? text.length : lineEnd;
2812
2869
  if (isDelimiterLine(text, lineStart, end, heredoc)) {
2813
2870
  const body = { command: heredoc.command, start, text: text.slice(start, lineStart) };
@@ -2876,7 +2933,7 @@ const USES = {
2876
2933
  "=": "assign",
2877
2934
  "+": "alternative"
2878
2935
  };
2879
- const SINGLE_QUOTE = "'";
2936
+ const SINGLE_QUOTE$1 = "'";
2880
2937
  const BACKSLASH$1 = "\\";
2881
2938
  function expansionAt(text, start) {
2882
2939
  const head = EXPANSION_HEAD_RE.exec(text.slice(start));
@@ -2898,8 +2955,8 @@ function wordExpansions(raw) {
2898
2955
  const found = [];
2899
2956
  for (let index = 0; index < raw.length; ) {
2900
2957
  const char = raw[index];
2901
- if (char === SINGLE_QUOTE) {
2902
- const close = raw.indexOf(SINGLE_QUOTE, index + 1);
2958
+ if (char === SINGLE_QUOTE$1) {
2959
+ const close = raw.indexOf(SINGLE_QUOTE$1, index + 1);
2903
2960
  index = close === -1 ? raw.length : close + 1;
2904
2961
  } else if (char === BACKSLASH$1) {
2905
2962
  index += 2;
@@ -2913,17 +2970,45 @@ function wordExpansions(raw) {
2913
2970
  }
2914
2971
  return found;
2915
2972
  }
2916
- const DOUBLE_QUOTE = '"';
2973
+ const DOUBLE_QUOTE$1 = '"';
2917
2974
  function wholeWordExpansion(raw) {
2918
- const isQuoted = raw.length > 1 && raw.startsWith(DOUBLE_QUOTE) && raw.endsWith(DOUBLE_QUOTE);
2975
+ const isQuoted = raw.length > 1 && raw.startsWith(DOUBLE_QUOTE$1) && raw.endsWith(DOUBLE_QUOTE$1);
2919
2976
  const inner = isQuoted ? 1 : 0;
2920
2977
  const [expansion] = wordExpansions(raw);
2921
2978
  const isWhole = expansion?.expansionStart === inner && expansion.expansionEnd === raw.length - inner;
2922
2979
  return isWhole ? expansion : void 0;
2923
2980
  }
2924
2981
 
2982
+ const SHELLS = /* @__PURE__ */ new Set(["sh", "bash", "dash", "ash", "zsh", "ksh"]);
2983
+ const OPTION_PREFIXES = ["-", "+"];
2984
+ const SHORT_OPTION_PREFIX = "-";
2985
+ const LONG_OPTION_PREFIX = "--";
2986
+ const END_OF_OPTIONS = "--";
2987
+ const COMMAND_OPTION = "c";
2988
+ const SET_OPTIONS = /* @__PURE__ */ new Set(["-o", "+o"]);
2989
+ const PATH_SEPARATOR$8 = "/";
2990
+ const isShellOption = (word) => OPTION_PREFIXES.some((prefix) => word.startsWith(prefix));
2991
+ function shellScript(words) {
2992
+ const program = words[0]?.value.split(PATH_SEPARATOR$8).at(-1) ?? "";
2993
+ if (!SHELLS.has(program)) {
2994
+ return void 0;
2995
+ }
2996
+ let hasCommandOption = false;
2997
+ let index = 1;
2998
+ for (let word = words[index]?.value; word !== void 0; word = words[index]?.value) {
2999
+ if (word === END_OF_OPTIONS || !isShellOption(word)) {
3000
+ const script = word === END_OF_OPTIONS ? words[index + 1] : words[index];
3001
+ return hasCommandOption ? script : void 0;
3002
+ }
3003
+ const isShort = word.startsWith(SHORT_OPTION_PREFIX) && !word.startsWith(LONG_OPTION_PREFIX);
3004
+ hasCommandOption ||= isShort && word.includes(COMMAND_OPTION);
3005
+ index += SET_OPTIONS.has(word) ? 2 : 1;
3006
+ }
3007
+ return void 0;
3008
+ }
3009
+
2925
3010
  const COMMENT = "#";
2926
- const LINE_BREAK$5 = "\n";
3011
+ const LINE_BREAK$6 = "\n";
2927
3012
  function skipTrivia(text, index) {
2928
3013
  const char = text[index];
2929
3014
  if (WORD_BREAKS.has(char)) {
@@ -2953,7 +3038,7 @@ function topLevelCommands(text, indent) {
2953
3038
  index = heredoc.end;
2954
3039
  } else if (afterTrivia !== index) {
2955
3040
  index = afterTrivia;
2956
- } else if (text[index] === LINE_BREAK$5 && pending.length > 0) {
3041
+ } else if (text[index] === LINE_BREAK$6 && pending.length > 0) {
2957
3042
  const read = readBodies(text, index + 1, pending);
2958
3043
  heredocs.push(...read.bodies);
2959
3044
  pending = [];
@@ -2973,21 +3058,23 @@ function topLevelCommands(text, indent) {
2973
3058
  }
2974
3059
  return { commands, heredocs, pipes };
2975
3060
  }
2976
- const QUOTES$2 = /* @__PURE__ */ new Set(["'", '"']);
3061
+ const QUOTES$1 = /* @__PURE__ */ new Set(["'", '"']);
2977
3062
  const NESTED_COMMAND_RE = /[\s;&|()`]/;
2978
- function nestedCommandText(word) {
3063
+ function isQuotedCommandLine(word) {
2979
3064
  const quote = word.raw[0];
2980
- const isOneQuotedString = QUOTES$2.has(quote) && word.raw.length > 1 && word.raw.endsWith(quote) && wordPart(word.raw, 0).end === word.raw.length;
2981
- return isOneQuotedString && NESTED_COMMAND_RE.test(word.value) ? word.raw.slice(1, -1) : void 0;
3065
+ const isOneQuotedString = QUOTES$1.has(quote) && word.raw.length > 1 && word.raw.endsWith(quote) && wordPart(word.raw, 0).end === word.raw.length;
3066
+ return isOneQuotedString && NESTED_COMMAND_RE.test(word.value);
3067
+ }
3068
+ function nestedCommandText(word, script) {
3069
+ return word === script || isQuotedCommandLine(word) ? unquotedRawText(word, 0) : void 0;
2982
3070
  }
2983
- const SHELLS$1 = /* @__PURE__ */ new Set(["sh", "bash", "dash", "ash", "zsh", "ksh"]);
2984
3071
  const DOCKER_RUN = "RUN";
2985
3072
  const PASS_THROUGH = "cat";
2986
- const PATH_SEPARATOR$9 = "/";
2987
- const OPTION_PREFIX$1 = "-";
2988
- const programName$1 = (command) => command[0]?.value.split(PATH_SEPARATOR$9).at(-1) ?? "";
2989
- const isOption = (word) => word.value.startsWith(OPTION_PREFIX$1);
2990
- const isScriptShell = (command) => SHELLS$1.has(programName$1(command)) && command.slice(1).every(isOption);
3073
+ const PATH_SEPARATOR$7 = "/";
3074
+ const OPTION_PREFIX = "-";
3075
+ const programName$1 = (command) => command[0]?.value.split(PATH_SEPARATOR$7).at(-1) ?? "";
3076
+ const isOption = (word) => word.value.startsWith(OPTION_PREFIX);
3077
+ const isScriptShell = (command) => SHELLS.has(programName$1(command)) && command.slice(1).every(isOption);
2991
3078
  function readsScript(command) {
2992
3079
  if (programName$1(command) !== DOCKER_RUN) {
2993
3080
  return isScriptShell(command);
@@ -3004,9 +3091,11 @@ function scriptsRunBy(top, index, command) {
3004
3091
  (body) => body.command === index && readsScript(command) || body.command === previous && isPipedScript
3005
3092
  );
3006
3093
  }
3007
- const shifted = (words, offset) => words.map((word) => ({ ...word, start: word.start + offset, end: word.end + offset }));
3008
3094
  function shellCommandsAt(text, offset, indent = 0) {
3009
- return shellCommands(text, indent).map((words) => shifted(words, offset));
3095
+ return shellCommands(text, indent).map((words) => words.map((word) => movedWord(word, offset)));
3096
+ }
3097
+ function placedCommands(placed) {
3098
+ return shellCommands(placed.text).map((words) => words.map((word) => placedWord(word, placed)));
3010
3099
  }
3011
3100
  function expansionCommands(word) {
3012
3101
  const expansions = wordExpansions(word.raw);
@@ -3014,7 +3103,7 @@ function expansionCommands(word) {
3014
3103
  ({ start }) => expansions.every((expansion) => start < expansion.start || start >= expansion.expansionEnd)
3015
3104
  );
3016
3105
  return [...expansions, ...literals].flatMap(
3017
- (part) => shellCommandsAt(part.text, word.start + part.start)
3106
+ (part) => placedCommands(rawText(word, part.start, part.text))
3018
3107
  );
3019
3108
  }
3020
3109
  function commandLineAssignments(command) {
@@ -3024,20 +3113,17 @@ function commandLineAssignments(command) {
3024
3113
  ];
3025
3114
  return new Map(read.map((assignment) => [assignment.word, assignment]));
3026
3115
  }
3027
- function valueCommands$1(assignment) {
3028
- const value = assignmentValue(assignment);
3029
- return shellCommands(value.text).map((words) => words.map(value.place));
3030
- }
3031
3116
  function innerCommands(command) {
3032
3117
  const assignments = commandLineAssignments(command);
3033
3118
  const code = inlineCode(command)?.code;
3119
+ const script = shellScript(command);
3034
3120
  return command.flatMap((word) => {
3035
- const inner = word === code ? void 0 : nestedCommandText(word);
3121
+ const inner = word === code ? void 0 : nestedCommandText(word, script);
3036
3122
  if (inner !== void 0) {
3037
- return shellCommandsAt(inner, word.start + 1);
3123
+ return placedCommands(inner);
3038
3124
  }
3039
3125
  const assignment = assignments.get(word);
3040
- return assignment === void 0 ? expansionCommands(word) : valueCommands$1(assignment);
3126
+ return assignment === void 0 ? expansionCommands(word) : placedCommands(assignmentValue(assignment));
3041
3127
  });
3042
3128
  }
3043
3129
  function shellCommands(text, indent = 0) {
@@ -3140,11 +3226,15 @@ function shellAppend(assignment, variables) {
3140
3226
  }
3141
3227
  const isOwnGeneratorCommand = (place, command) => place === "after-outside" && command !== void 0 && runsGenerator(command);
3142
3228
  function appendedText(assignment) {
3143
- const value = assignmentValue(assignment);
3144
- return shellCommands(value.text).map((words) => words.map(value.place));
3229
+ return placedCommands(assignmentValue(assignment));
3230
+ }
3231
+ const appendsOwnGenerator = ({ assignment, place }) => isOwnGeneratorCommand(place, appendedText(assignment)[0]);
3232
+ function setsKnownProgram(placed, all) {
3233
+ const chain = all.slice(0, all.indexOf(placed) + 1).filter((earlier) => earlier.assignment.name === placed.assignment.name);
3234
+ return chain.some((part) => part.place !== "after-outside" || appendsOwnGenerator(part));
3145
3235
  }
3146
3236
  function shellAppends(commands, variables) {
3147
- const appends = placedAssignments$1(commands).filter(isAfterValue).filter(({ assignment, place }) => !isOwnGeneratorCommand(place, appendedText(assignment)[0])).map(({ assignment }) => shellAppend(assignment, variables));
3237
+ const appends = placedAssignments$1(commands).filter(isAfterValue).filter((placed) => !appendsOwnGenerator(placed)).map(({ assignment }) => shellAppend(assignment, variables));
3148
3238
  const taken = new Set(appends.flatMap(({ command }) => command === void 0 ? [] : [command]));
3149
3239
  const isTaken = (command) => [...taken].some(
3150
3240
  (found) => found.length === command.length && found[0]?.start === command[0]?.start
@@ -3155,23 +3245,29 @@ function shellAppends(commands, variables) {
3155
3245
  };
3156
3246
  }
3157
3247
 
3248
+ const passedFlag = (text) => text.split("=", 1)[0];
3158
3249
  function flagFindings(word, locate) {
3159
- const flag = word.raw.split("=", 1)[0];
3250
+ const passed = unquotedRawText(word, 0);
3251
+ const flag = passedFlag(passed.text);
3160
3252
  const to = FLAG_RENAMES.get(flag);
3161
3253
  if (to === void 0) {
3162
3254
  return [];
3163
3255
  }
3164
3256
  const offset = word.start;
3165
- const edit = { start: offset, end: offset + flag.length, text: to };
3166
- return [
3167
- { offset, edit, change: { kind: "flag-renamed", location: locate(offset), from: flag, to } }
3168
- ];
3257
+ const location = locate(offset);
3258
+ const span = placedSpan(passed, flag.length);
3259
+ if (span === void 0) {
3260
+ const manualStep = { kind: "split-flag", location, flag };
3261
+ return [{ offset, manualStep }];
3262
+ }
3263
+ const edit = { ...span, text: to };
3264
+ return [{ offset, edit, change: { kind: "flag-renamed", location, from: flag, to } }];
3169
3265
  }
3170
3266
  function renamedFlags(value) {
3171
- const flags = shellCommands(value).flat().map((word) => word.raw.split("=", 1)[0]);
3267
+ const flags = shellCommands(value).flat().map((word) => passedFlag(word.value));
3172
3268
  return [...new Set(flags.filter((flag) => FLAG_RENAMES.has(flag)))];
3173
3269
  }
3174
- function argumentFindings$1(word, locate, variables) {
3270
+ function argumentFindings(word, locate, variables) {
3175
3271
  const reference = variables.referenceAt(word);
3176
3272
  if (reference === void 0) {
3177
3273
  return flagFindings(word, locate);
@@ -3195,7 +3291,7 @@ const oldFlagsOf = (findings) => findings.flatMap(({ change, manualStep }) => {
3195
3291
  return manualStep?.kind === "flags-in-variable" ? [...manualStep.flags] : [];
3196
3292
  });
3197
3293
  function programArgumentFindings(words, { offset, use }, locate, variables) {
3198
- const found = words.flatMap((word) => argumentFindings$1(word, locate, variables));
3294
+ const found = words.flatMap((word) => argumentFindings(word, locate, variables));
3199
3295
  if (use?.program !== "generator-or-other") {
3200
3296
  return found;
3201
3297
  }
@@ -3219,20 +3315,6 @@ function appendedValueFindings({ words, program }, locate, variables) {
3219
3315
  return [...found, ...unmigratedWordFindings(words, handled, locate)];
3220
3316
  }
3221
3317
 
3222
- const PATH_SEPARATOR$8 = "/";
3223
- const WINDOWS_SEPARATOR_RE = /\\/g;
3224
- const QUOTE_RE = /["']/g;
3225
- const WINDOWS_PATH_RE = /^[^/]*[^\\/\s]\\[^\\/\s][^/]*$/;
3226
- function programPath(word) {
3227
- const suffix = windowsSuffix(word.value);
3228
- const written = word.raw.replace(QUOTE_RE, "");
3229
- const isWindowsPath = suffix !== "" || WINDOWS_PATH_RE.test(written);
3230
- if (!isWindowsPath) {
3231
- return { path: word.value, suffix, written: word.value };
3232
- }
3233
- return { path: written.replace(WINDOWS_SEPARATOR_RE, PATH_SEPARATOR$8), suffix, written };
3234
- }
3235
-
3236
3318
  const SHELL_VARIABLE_RE$1 = /^"?\$(?:([A-Za-z_][A-Za-z0-9_]*)|\{([A-Za-z_][A-Za-z0-9_]*)\})"?$/;
3237
3319
  function expandedName$1(text) {
3238
3320
  const match = SHELL_VARIABLE_RE$1.exec(text);
@@ -3257,8 +3339,12 @@ function expansionDefinitions(name, expansion) {
3257
3339
  const variable = isOutside ? { name, kind: "command", runsGenerator: false } : { name, kind: "reference", target: expansion.name };
3258
3340
  return [word, variable];
3259
3341
  }
3260
- function programDefinitions$1({ name, value, isAppended }) {
3261
- if (isAppended === true) {
3342
+ function programDefinitions$1(definition) {
3343
+ const { name, value } = definition;
3344
+ if (definition.isAfterOutside === true) {
3345
+ return [{ name, kind: "run-time" }];
3346
+ }
3347
+ if (definition.isAppended === true) {
3262
3348
  return [{ name, kind: "no-program" }];
3263
3349
  }
3264
3350
  const expansion = wholeWordExpansion(value.trim());
@@ -3297,12 +3383,14 @@ function assignedByExpansions(commands) {
3297
3383
  ]);
3298
3384
  }
3299
3385
  function withShellAssignments(commands, file) {
3300
- const assigned = placedAssignments$1(commands).flatMap((placed) => {
3301
- const { assignment, place } = placed;
3302
- if (isArrayAssignment(assignment) || place === "after-outside") {
3386
+ const placed = placedAssignments$1(commands);
3387
+ const assigned = placed.flatMap((entry) => {
3388
+ const { assignment } = entry;
3389
+ if (isArrayAssignment(assignment) || !setsKnownProgram(entry, placed)) {
3303
3390
  return [];
3304
3391
  }
3305
- return [{ name: assignment.name, value: assignment.value, isAppended: isAfterValue(placed) }];
3392
+ const { name, value } = assignment;
3393
+ return appendsOwnGenerator(entry) ? [{ name, value, isAfterOutside: true }] : [{ name, value, isAppended: isAfterValue(entry) }];
3306
3394
  });
3307
3395
  const definitions = [...assigned, ...assignedByExpansions(commands)];
3308
3396
  if (definitions.length === 0) {
@@ -3350,18 +3438,19 @@ function withUnreadVariables(variables) {
3350
3438
  };
3351
3439
  }
3352
3440
 
3353
- const QUOTES$1 = /* @__PURE__ */ new Set(["'", '"']);
3354
- const PATH_SEPARATOR$7 = "/";
3441
+ const QUOTES = /* @__PURE__ */ new Set(["'", '"']);
3442
+ const PATH_SEPARATOR$6 = "/";
3355
3443
  const BIN_FOLDER = "/.bin/";
3356
3444
  const OLD_PACKAGE_FOLDER = `node_modules/${GENERATOR_COMMAND}/`;
3357
3445
  function renameAtEnd(word, name, to, suffix = "") {
3358
3446
  const at = word.raw.lastIndexOf(name);
3359
3447
  const tail = word.raw.slice(at + name.length);
3360
3448
  const quote = tail.slice(suffix.length);
3361
- if (at === -1 || quote !== "" && !QUOTES$1.has(quote)) {
3449
+ if (at === -1 || quote !== "" && !QUOTES.has(quote)) {
3362
3450
  return void 0;
3363
3451
  }
3364
- return { start: word.start + at, end: word.start + at + name.length, text: to };
3452
+ const span = writtenSpan(word, at, name.length);
3453
+ return span === void 0 ? void 0 : { ...span, text: to };
3365
3454
  }
3366
3455
  function packageFindings(found, locate) {
3367
3456
  const { name, isPinned } = packageReference(found.value);
@@ -3386,7 +3475,7 @@ function programPathFindings(word, program, location) {
3386
3475
  }
3387
3476
  if (!path.endsWith(`${BIN_FOLDER}${GENERATOR_COMMAND}${suffix}`)) {
3388
3477
  const manualStep = { kind: "old-name-in-command", location, word: written };
3389
- const isGenerator = path.endsWith(`${PATH_SEPARATOR$7}${GENERATOR_COMMAND}${suffix}`);
3478
+ const isGenerator = path.endsWith(`${PATH_SEPARATOR$6}${GENERATOR_COMMAND}${suffix}`);
3390
3479
  return isGenerator ? [{ offset, manualStep }] : [];
3391
3480
  }
3392
3481
  const edit = renameAtEnd(word, GENERATOR_COMMAND, MIGRATED_GENERATOR_COMMAND, suffix);
@@ -3394,7 +3483,7 @@ function programPathFindings(word, program, location) {
3394
3483
  }
3395
3484
  function programFindings(word, location) {
3396
3485
  const program = programPath(word);
3397
- if (program.path.includes(PATH_SEPARATOR$7)) {
3486
+ if (program.path.includes(PATH_SEPARATOR$6)) {
3398
3487
  return programPathFindings(word, program, location);
3399
3488
  }
3400
3489
  const { path, suffix } = program;
@@ -3459,8 +3548,17 @@ function commandWordFindings(commands, locate, variables = NO_VARIABLES, appende
3459
3548
  )
3460
3549
  ]);
3461
3550
  }
3551
+ function uniqueFindings(found) {
3552
+ const seen = /* @__PURE__ */ new Set();
3553
+ return found.filter((finding) => {
3554
+ const key = JSON.stringify(finding);
3555
+ const isNew = !seen.has(key);
3556
+ seen.add(key);
3557
+ return isNew;
3558
+ });
3559
+ }
3462
3560
  function textFindings(found) {
3463
- const findings = [...found].sort((left, right) => left.offset - right.offset);
3561
+ const findings = uniqueFindings(found).sort((left, right) => left.offset - right.offset);
3464
3562
  return {
3465
3563
  edits: findings.flatMap((finding) => finding.edit === void 0 ? [] : [finding.edit]),
3466
3564
  changes: findings.flatMap((finding) => finding.change === void 0 ? [] : [finding.change]),
@@ -3600,13 +3698,13 @@ const NO_LONGER_INSTALLED = /* @__PURE__ */ new Map([
3600
3698
  const ORIGINAL_NAMES = new Map(
3601
3699
  [...PACKAGE_RENAMES].map(([original, successor]) => [successor, original])
3602
3700
  );
3603
- const PATH_SEPARATOR$6 = "/";
3701
+ const PATH_SEPARATOR$5 = "/";
3604
3702
  function namesUninstalledPackage(text) {
3605
3703
  return [...NO_LONGER_INSTALLED.keys()].some(
3606
3704
  (name) => text.includes(name) || text.includes(ORIGINAL_NAMES.get(name) ?? name)
3607
3705
  );
3608
3706
  }
3609
- const directoryOf = (path) => path.slice(0, path.lastIndexOf(PATH_SEPARATOR$6) + 1);
3707
+ const directoryOf = (path) => path.slice(0, path.lastIndexOf(PATH_SEPARATOR$5) + 1);
3610
3708
  const isAtOrAbove = (path, filePath) => filePath.startsWith(directoryOf(path));
3611
3709
  function declaredNames(manifest) {
3612
3710
  return DEPENDENCY_FIELDS.flatMap((field) => mapsAt(manifest, field)).flatMap(textEntries).map(({ key }) => key.value);
@@ -3631,7 +3729,7 @@ function isDeclaredFor(manifests, filePath, name) {
3631
3729
  return containing.length === 0 || containing.some(declares);
3632
3730
  }
3633
3731
  const packageOfSpecifier = (specifier) => {
3634
- const slash = specifier.indexOf(PATH_SEPARATOR$6);
3732
+ const slash = specifier.indexOf(PATH_SEPARATOR$5);
3635
3733
  return slash === -1 ? specifier : specifier.slice(0, slash);
3636
3734
  };
3637
3735
  function undeclaredImportSteps(imports, filePath, manifests) {
@@ -3642,28 +3740,23 @@ function undeclaredImportSteps(imports, filePath, manifests) {
3642
3740
  });
3643
3741
  }
3644
3742
 
3645
- const SHELLS = /* @__PURE__ */ new Set(["sh", "bash", "dash", "ash", "zsh"]);
3646
- const OPTION_PREFIX = "-";
3647
- const LONG_OPTION_PREFIX = "--";
3648
- const COMMAND_OPTION = "c";
3649
- const SET_OPTION = "-o";
3650
- const PATH_SEPARATOR$5 = "/";
3651
- function shellScript(words) {
3652
- const program = words[0]?.value.split(PATH_SEPARATOR$5).at(-1) ?? "";
3653
- if (!SHELLS.has(program)) {
3654
- return void 0;
3655
- }
3656
- let index = 1;
3657
- for (let option = words[index]?.value; option !== void 0; option = words[index]?.value) {
3658
- if (!option.startsWith(OPTION_PREFIX)) {
3659
- return void 0;
3660
- }
3661
- if (!option.startsWith(LONG_OPTION_PREFIX) && option.includes(COMMAND_OPTION)) {
3662
- return words[index + 1];
3663
- }
3664
- index += option === SET_OPTION ? 2 : 1;
3665
- }
3666
- return void 0;
3743
+ const ARGUMENTS_WORDS = new Set(
3744
+ ["$", "$$"].flatMap(
3745
+ (dollar) => ["@", "{@}"].flatMap((name) => ["", '"'].map((quote) => `${quote}${dollar}${name}${quote}`))
3746
+ )
3747
+ );
3748
+ function runsArguments(script) {
3749
+ return shellCommands(script).some((words) => {
3750
+ const { program } = commandRoles(words);
3751
+ return program !== void 0 && ARGUMENTS_WORDS.has(words[program]?.raw ?? "");
3752
+ });
3753
+ }
3754
+ function runsItsCommand(entrypoint) {
3755
+ const script = shellScript(entrypoint);
3756
+ return script === void 0 || runsArguments(script.value);
3757
+ }
3758
+ function containerCommand(entrypoint, command) {
3759
+ return runsGenerator(command) && runsItsCommand(entrypoint) ? [entrypoint, command] : [[...entrypoint, ...command]];
3667
3760
  }
3668
3761
  function entrypointProgram(entrypoint) {
3669
3762
  const script = shellScript(entrypoint);
@@ -3714,12 +3807,17 @@ function entrypointOf(stages, index) {
3714
3807
  const base = baseStage(stages, index);
3715
3808
  return own ?? (base === -1 ? void 0 : entrypointOf(stages, base));
3716
3809
  }
3717
- function entrypointCommandFindings(text, forms, locate) {
3810
+ function entrypointPairs(text, forms) {
3718
3811
  const stages = dockerStages(text, forms);
3719
3812
  return stages.flatMap((stage, index) => {
3720
3813
  const entrypoint = entrypointOf(stages, index)?.form;
3721
3814
  const command = stage.command?.form;
3722
- if (entrypoint === void 0 || command === void 0) {
3815
+ return entrypoint === void 0 || command === void 0 ? [] : [{ entrypoint, command }];
3816
+ });
3817
+ }
3818
+ function entrypointCommandFindings(pairs, locate) {
3819
+ return pairs.flatMap(({ entrypoint, command }) => {
3820
+ if (shellScript(entrypoint.words) === void 0) {
3723
3821
  return [];
3724
3822
  }
3725
3823
  const pair = { entrypoint: entrypoint.words, command: command.words };
@@ -3727,16 +3825,140 @@ function entrypointCommandFindings(text, forms, locate) {
3727
3825
  });
3728
3826
  }
3729
3827
 
3828
+ const DEFAULT_ESCAPE = "\\";
3829
+ const DIRECTIVE_RE = /^#[ \t]*([A-Za-z]+)[ \t]*=[ \t]*(\S+)[ \t]*$/;
3830
+ const ESCAPE_DIRECTIVE = "escape";
3831
+ const LINE_BREAK$5 = "\n";
3832
+ const CARRIAGE_RETURN = "\r";
3833
+ const BLANKS = /* @__PURE__ */ new Set([" ", " ", CARRIAGE_RETURN, LINE_BREAK$5]);
3834
+ const SINGLE_QUOTE = "'";
3835
+ const DOUBLE_QUOTE = '"';
3836
+ const DOUBLE_QUOTE_ESCAPES = /* @__PURE__ */ new Set([DOUBLE_QUOTE, "$"]);
3837
+ const ASSIGNMENT$1 = "=";
3838
+ const VARIABLE_INSTRUCTIONS = /* @__PURE__ */ new Set(["ARG", "ENV"]);
3839
+ const LEGACY_ENV_INSTRUCTION = "ENV";
3840
+ function escapeCharacter(text) {
3841
+ for (const line of text.split(LINE_BREAK$5)) {
3842
+ const directive = DIRECTIVE_RE.exec(line.replace(CARRIAGE_RETURN, ""));
3843
+ if (directive === null) {
3844
+ return DEFAULT_ESCAPE;
3845
+ }
3846
+ if (directive[1]?.toLowerCase() === ESCAPE_DIRECTIVE) {
3847
+ return directive[2];
3848
+ }
3849
+ }
3850
+ return DEFAULT_ESCAPE;
3851
+ }
3852
+ function instructionEnd(text, index, escapeMark) {
3853
+ for (let end = text.indexOf(LINE_BREAK$5, index); end !== -1; ) {
3854
+ if (!text.slice(index, end).trimEnd().endsWith(escapeMark)) {
3855
+ return end;
3856
+ }
3857
+ index = end + 1;
3858
+ end = text.indexOf(LINE_BREAK$5, index);
3859
+ }
3860
+ return text.length;
3861
+ }
3862
+ const add = (word, text, index) => {
3863
+ word.value += text[index];
3864
+ word.offsets.push(index);
3865
+ };
3866
+ function isContinuation(text, index, escapeMark) {
3867
+ const lineEnd = text.indexOf(LINE_BREAK$5, index);
3868
+ return text[index] === escapeMark && lineEnd !== -1 && text.slice(index + 1, lineEnd).trim() === "";
3869
+ }
3870
+ function isQuotedEscape(text, index, escapeMark) {
3871
+ const next = text[index + 1] ?? "";
3872
+ return text[index] === escapeMark && (DOUBLE_QUOTE_ESCAPES.has(next) || next === escapeMark);
3873
+ }
3874
+ function readQuoted(text, index, word, escapeMark) {
3875
+ const quote = text[index];
3876
+ let at = index + 1;
3877
+ for (; at < text.length && text[at] !== quote; at++) {
3878
+ if (isContinuation(text, at, escapeMark)) {
3879
+ at = text.indexOf(LINE_BREAK$5, at);
3880
+ } else {
3881
+ at += quote === DOUBLE_QUOTE && isQuotedEscape(text, at, escapeMark) ? 1 : 0;
3882
+ add(word, text, at);
3883
+ }
3884
+ }
3885
+ return at + 1;
3886
+ }
3887
+ function readPart(text, index, word, escapeMark) {
3888
+ const char = text[index];
3889
+ if (char === SINGLE_QUOTE || char === DOUBLE_QUOTE) {
3890
+ return readQuoted(text, index, word, escapeMark);
3891
+ }
3892
+ const end = index + (char === escapeMark && index + 1 < text.length ? 2 : 1);
3893
+ add(word, text, end - 1);
3894
+ return end;
3895
+ }
3896
+ function endWord(word, words) {
3897
+ const value = word.value.trimEnd();
3898
+ if (value !== "") {
3899
+ words.push({ text: value, offsets: word.offsets.slice(0, value.length) });
3900
+ }
3901
+ return { value: "", offsets: [] };
3902
+ }
3903
+ function dockerWords(text, range, escapeMark, isWhole = false) {
3904
+ const words = [];
3905
+ const instruction = text.slice(0, range.end);
3906
+ let word = { value: "", offsets: [] };
3907
+ for (let index = range.start; index < range.end; ) {
3908
+ if (isContinuation(text, index, escapeMark)) {
3909
+ index = text.indexOf(LINE_BREAK$5, index) + 1;
3910
+ } else if (BLANKS.has(text[index]) && (!isWhole || word.value === "")) {
3911
+ word = endWord(word, words);
3912
+ index++;
3913
+ } else {
3914
+ index = readPart(instruction, index, word, escapeMark);
3915
+ }
3916
+ }
3917
+ endWord(word, words);
3918
+ return words;
3919
+ }
3920
+ function assignedVariable({ text, offsets }) {
3921
+ const at = text.indexOf(ASSIGNMENT$1);
3922
+ if (at <= 0) {
3923
+ return [];
3924
+ }
3925
+ return [
3926
+ {
3927
+ name: text.slice(0, at),
3928
+ value: { text: text.slice(at + 1), offsets: offsets.slice(at + 1) }
3929
+ }
3930
+ ];
3931
+ }
3932
+ function instructionVariables(text, keyword, range, escapeMark) {
3933
+ const [first, ...rest] = dockerWords(text, range, escapeMark);
3934
+ if (first === void 0) {
3935
+ return [];
3936
+ }
3937
+ if (keyword === LEGACY_ENV_INSTRUCTION && !first.text.includes(ASSIGNMENT$1)) {
3938
+ const valueStart = rest[0]?.offsets[0] ?? range.end;
3939
+ const [value] = dockerWords(text, { start: valueStart, end: range.end }, escapeMark, true);
3940
+ return [{ name: first.text, value: value ?? { text: "", offsets: [] } }];
3941
+ }
3942
+ return [first, ...rest].flatMap(assignedVariable);
3943
+ }
3944
+ function variableInstructions(text, commands) {
3945
+ const escapeMark = escapeCharacter(text);
3946
+ return commands.flatMap(([keyword]) => {
3947
+ if (keyword === void 0 || !VARIABLE_INSTRUCTIONS.has(keyword.value)) {
3948
+ return [];
3949
+ }
3950
+ const range = { start: keyword.end, end: instructionEnd(text, keyword.end, escapeMark) };
3951
+ const variables = instructionVariables(text, keyword.value, range, escapeMark);
3952
+ return [{ start: keyword.start, end: range.end, variables }];
3953
+ });
3954
+ }
3955
+
3730
3956
  const EXEC_FORM_START_RE = /^[ \t]*(?:RUN|CMD|ENTRYPOINT)(?:[ \t]+--\S+)*[ \t]+\[/gm;
3731
3957
  const ARRAY_BLANK_RE = /(?:[ \t\r\n]|\\\r?\n)*/y;
3732
3958
  const QUOTED_RE = /"(?:[^"\\\n]|\\.)*"/y;
3733
3959
  const ITEM_SEPARATOR = ",";
3734
3960
  const NOT_LINE_BREAK_RE$1 = /[^\n]/g;
3735
3961
  const ARRAY_END = "]";
3736
- const VARIABLE_INSTRUCTIONS = /* @__PURE__ */ new Set(["ARG", "ENV"]);
3737
- const LEGACY_ENV_INSTRUCTION = "ENV";
3738
- const ASSIGNMENT$1 = "=";
3739
- const QUOTES = /* @__PURE__ */ new Set(['"', "'"]);
3740
3962
  function matchAt(pattern, text, index) {
3741
3963
  pattern.lastIndex = index;
3742
3964
  return pattern.exec(text)?.[0];
@@ -3783,10 +4005,15 @@ function execForms(text) {
3783
4005
  return form === void 0 ? [] : [{ ...form, lineStart: match.index }];
3784
4006
  });
3785
4007
  }
3786
- function execCommands(forms) {
4008
+ function execCommands(forms, pairs) {
4009
+ const paired = new Set(pairs.map((pair) => pair.command));
3787
4010
  const commands = [];
3788
4011
  const escaped = [];
3789
- for (const { words } of forms) {
4012
+ const read = [
4013
+ ...forms.filter((form) => !paired.has(form)).map((form) => form.words),
4014
+ ...pairs.flatMap((pair) => containerCommand(pair.entrypoint.words, pair.command.words))
4015
+ ];
4016
+ for (const words of read) {
3790
4017
  commands.push(words);
3791
4018
  const script = shellScript(words);
3792
4019
  if (script !== void 0 && script.raw === script.value) {
@@ -3795,7 +4022,8 @@ function execCommands(forms) {
3795
4022
  escaped.push(script);
3796
4023
  }
3797
4024
  }
3798
- return { commands, escaped };
4025
+ const scripts = new Map(escaped.map((script) => [script.start, script]));
4026
+ return { commands, escaped: [...scripts.values()] };
3799
4027
  }
3800
4028
  function withoutExecForms(text, forms) {
3801
4029
  const parts = [];
@@ -3816,39 +4044,6 @@ function escapedScriptSteps(scripts, lineOf) {
3816
4044
  return migrated === script.value ? [] : [{ kind: "command-string", line: lineOf(script.start), to: migrated }];
3817
4045
  });
3818
4046
  }
3819
- function assignedVariable(word) {
3820
- const at = word.raw.indexOf(ASSIGNMENT$1);
3821
- if (at <= 0) {
3822
- return void 0;
3823
- }
3824
- const raw = word.raw.slice(at + 1);
3825
- const isQuoted = raw.length >= 2 && QUOTES.has(raw[0]) && raw.at(-1) === raw[0];
3826
- const value = isQuoted ? raw.slice(1, -1) : raw;
3827
- const start = word.start + at + 1 + (isQuoted ? 1 : 0);
3828
- return { name: word.raw.slice(0, at), value, start };
3829
- }
3830
- function instructionVariables(text, words) {
3831
- const [instruction, first, ...rest] = words;
3832
- const last = rest.at(-1);
3833
- if (instruction.value === LEGACY_ENV_INSTRUCTION && !first.raw.includes(ASSIGNMENT$1)) {
3834
- const value = last === void 0 ? "" : text.slice(rest[0]?.start, last.end);
3835
- return [{ name: first.value, value, start: rest[0]?.start ?? first.end }];
3836
- }
3837
- return [first, ...rest].flatMap((word) => assignedVariable(word) ?? []);
3838
- }
3839
- function variableInstructions(text, commands) {
3840
- return commands.flatMap((words) => {
3841
- const [instruction, first] = words;
3842
- if (instruction === void 0 || first === void 0) {
3843
- return [];
3844
- }
3845
- if (!VARIABLE_INSTRUCTIONS.has(instruction.value)) {
3846
- return [];
3847
- }
3848
- const end = words.at(-1).end;
3849
- return [{ start: instruction.start, end, variables: instructionVariables(text, words) }];
3850
- });
3851
- }
3852
4047
  function withoutVariableInstructions(commands, instructions) {
3853
4048
  const isInInstruction = (words) => {
3854
4049
  const start = words[0]?.start ?? -1;
@@ -3858,20 +4053,21 @@ function withoutVariableInstructions(commands, instructions) {
3858
4053
  };
3859
4054
  return commands.filter((words) => !isInInstruction(words));
3860
4055
  }
3861
- const valueCommands = ({ value, start }) => shellCommandsAt(value, start);
3862
4056
  function migrateDockerfile(text, file) {
3863
4057
  const lineOf = lineLocator(text);
3864
4058
  const locate = (offset) => `${file}:${lineOf(offset)}`;
3865
4059
  const forms = execForms(text);
3866
- const exec = execCommands(forms);
4060
+ const pairs = entrypointPairs(text, forms);
4061
+ const exec = execCommands(forms, pairs);
3867
4062
  const shellForm = shellCommands(withoutExecForms(text, forms));
3868
4063
  const instructions = variableInstructions(text, shellForm);
3869
- const definitions = instructions.flatMap((instruction) => instruction.variables);
3870
- const values = definitions.flatMap(valueCommands);
4064
+ const variables = instructions.flatMap((instruction) => instruction.variables);
4065
+ const definitions = variables.map(({ name, value }) => ({ name, value: value.text }));
4066
+ const values = variables.flatMap(({ value }) => placedCommands(value));
3871
4067
  const run = withoutVariableInstructions(shellForm, instructions);
3872
4068
  const commands = [...run, ...values, ...exec.commands];
3873
4069
  const found = commandWordFindings(commands, locate, shellVariables(definitions));
3874
- const entry = textFindings(entrypointCommandFindings(text, forms, locate));
4070
+ const entry = textFindings(entrypointCommandFindings(pairs, locate));
3875
4071
  const edits = [...found.edits, ...entry.edits];
3876
4072
  const changes = [...found.changes, ...entry.changes];
3877
4073
  const steps = [...found.manualSteps, ...entry.manualSteps];
@@ -5011,7 +5207,7 @@ function withoutAssignmentHeads(text, assignments) {
5011
5207
  }
5012
5208
  const COMPUTED_RE = /^\$[({]/;
5013
5209
  const isComputed = (written) => COMPUTED_RE.test(written.trim().replace(RECIPE_PREFIX_RE, ""));
5014
- function referencedName(written) {
5210
+ function referencedName$1(written) {
5015
5211
  const match = VARIABLE_REFERENCE_RE.exec(written.trim().replace(RECIPE_PREFIX_RE, ""));
5016
5212
  return match === null ? void 0 : match[1] ?? match[2] ?? match[3];
5017
5213
  }
@@ -5029,7 +5225,7 @@ function valueDefinition(text, { name, valueStart, valueEnd }, commands) {
5029
5225
  return { name, kind: "no-program" };
5030
5226
  }
5031
5227
  const program = programWritten(text, first);
5032
- const target = referencedName(program);
5228
+ const target = referencedName$1(program);
5033
5229
  if (target !== void 0) {
5034
5230
  return { name, kind: "reference", target };
5035
5231
  }
@@ -5088,7 +5284,7 @@ function variableValues(text, assignments) {
5088
5284
  }
5089
5285
  function makefileVariables(text, assignments, programs) {
5090
5286
  const values = variableValues(text, assignments);
5091
- const nameAt = (word) => referencedName(text.slice(word.start, word.end));
5287
+ const nameAt = (word) => referencedName$1(text.slice(word.start, word.end));
5092
5288
  const variables = {
5093
5289
  programAt: (program) => {
5094
5290
  const written = text.slice(program.start, program.end);
@@ -5509,49 +5705,90 @@ function withInputDefaults(variables, defaults) {
5509
5705
  }
5510
5706
 
5511
5707
  const ARGUMENT_KEYS = /* @__PURE__ */ new Map([
5512
- ["entrypoint", "command"],
5513
- ["command", "args"]
5708
+ ["entrypoint", ["command", "args"]],
5709
+ ["command", ["args"]]
5514
5710
  ]);
5515
5711
  const PAIRED_KEYS = /* @__PURE__ */ new Set([
5516
5712
  ...ARGUMENT_KEYS.keys(),
5517
- ...ARGUMENT_KEYS.values()
5713
+ ...[...ARGUMENT_KEYS.values()].flat()
5518
5714
  ]);
5519
- const TRAILING_KEY = "args";
5715
+ const MERGE_KEY = "<<";
5520
5716
  const LINE_BREAK = "\n";
5521
5717
  const isItem = (string) => typeof string.path.at(-1) === "number";
5522
5718
  const isContainerListItem = (string) => isItem(string) && PAIRED_KEYS.has(String(string.path.at(-2)));
5523
5719
  const isList = (value) => value.strings[0] !== void 0 && isItem(value.strings[0]);
5524
- const mapOf = (string) => JSON.stringify(string.path.slice(0, isItem(string) ? -2 : -1));
5720
+ function mapOf(string) {
5721
+ const path = string.path.slice(0, isItem(string) ? -2 : -1);
5722
+ let isMerged = false;
5723
+ for (; ; ) {
5724
+ if (path.at(-1) === MERGE_KEY) {
5725
+ path.pop();
5726
+ } else if (typeof path.at(-1) === "number" && path.at(-2) === MERGE_KEY) {
5727
+ path.splice(-2);
5728
+ } else {
5729
+ return { map: JSON.stringify(path), isMerged };
5730
+ }
5731
+ isMerged = true;
5732
+ }
5733
+ }
5525
5734
  function isNextItem(value, string) {
5526
5735
  const last = value?.strings.at(-1);
5527
5736
  return last !== void 0 && isItem(last) && isItem(string) && JSON.stringify(last.path.slice(0, -1)) === JSON.stringify(string.path.slice(0, -1)) && Number(string.path.at(-1)) > Number(last.path.at(-1));
5528
5737
  }
5529
5738
  function keyValues(strings) {
5530
5739
  const values = [];
5531
- const starts = /* @__PURE__ */ new Set();
5532
5740
  for (const string of strings) {
5533
5741
  const key = ownerKey(string.path) ?? "";
5534
5742
  const isValue = isItem(string) ? string.path.at(-2) === key : string.path.at(-1) === key;
5535
- if (!PAIRED_KEYS.has(key) || !isValue || starts.has(string.start)) {
5743
+ if (!PAIRED_KEYS.has(key) || !isValue) {
5536
5744
  continue;
5537
5745
  }
5538
- starts.add(string.start);
5539
5746
  const last = values.at(-1);
5540
5747
  if (last !== void 0 && isNextItem(last, string)) {
5541
5748
  last.strings.push(string);
5542
5749
  } else {
5543
- values.push({ map: mapOf(string), key, strings: [string] });
5750
+ values.push({ ...mapOf(string), key, strings: [string] });
5544
5751
  }
5545
5752
  }
5546
5753
  return values;
5547
5754
  }
5755
+ function mapValues(values) {
5756
+ const maps = [];
5757
+ const latest = /* @__PURE__ */ new Map();
5758
+ for (const value of values) {
5759
+ let map = latest.get(value.map);
5760
+ if (map === void 0 || !value.isMerged && map.own.has(value.key)) {
5761
+ map = { own: /* @__PURE__ */ new Map(), merged: /* @__PURE__ */ new Map() };
5762
+ maps.push(map);
5763
+ latest.set(value.map, map);
5764
+ }
5765
+ const values2 = value.isMerged ? map.merged : map.own;
5766
+ if (!values2.has(value.key)) {
5767
+ values2.set(value.key, value);
5768
+ }
5769
+ }
5770
+ return maps;
5771
+ }
5772
+ function containerOf({ own, merged }) {
5773
+ const keyValue = (key) => own.get(key) ?? merged.get(key);
5774
+ for (const [key, argumentKeys] of ARGUMENT_KEYS) {
5775
+ const entrypoint = keyValue(key);
5776
+ if (entrypoint !== void 0) {
5777
+ const argumentKey = argumentKeys.find((found) => keyValue(found) !== void 0);
5778
+ return [
5779
+ { entrypoint, command: argumentKey === void 0 ? void 0 : keyValue(argumentKey) }
5780
+ ];
5781
+ }
5782
+ }
5783
+ return [];
5784
+ }
5548
5785
  const isPlaceable = (string, text) => !string.isEscaped && !(string.isFolded && text.slice(string.content.start, string.content.end).includes(LINE_BREAK));
5549
5786
  function scalarWords(scalar, text) {
5550
5787
  const { start, end } = scalar.content;
5551
5788
  const isPlaced = isPlaceable(scalar, text);
5552
- const [words = []] = shellCommands(isPlaced ? text.slice(start, end) : scalar.value);
5553
- const at = (word) => isPlaced ? { ...word, start: word.start + start, end: word.end + start } : { ...word, start: scalar.start, end: scalar.start };
5554
- return { words: words.map(at), isPlaced };
5789
+ const commands = shellCommands(isPlaced ? text.slice(start, end) : scalar.value);
5790
+ const at = (word) => isPlaced ? movedWord(word, start) : { ...word, start: scalar.start, end: scalar.start };
5791
+ return { words: (commands[0] ?? []).map(at), isPlaced, isWhole: commands.length <= 1 };
5555
5792
  }
5556
5793
  function valueWords(value, text) {
5557
5794
  const [first] = value.strings;
@@ -5562,56 +5799,37 @@ function valueWords(value, text) {
5562
5799
  const { start, end } = item.content;
5563
5800
  return { start, end, value: item.value, raw: text.slice(start, end), item };
5564
5801
  });
5565
- return { words, isPlaced: true };
5566
- }
5567
- function pairOf(left, right) {
5568
- if (right === void 0 || left.map !== right.map) {
5569
- return void 0;
5570
- }
5571
- if (ARGUMENT_KEYS.get(left.key) === right.key) {
5572
- return [left, right];
5573
- }
5574
- return ARGUMENT_KEYS.get(right.key) === left.key ? [right, left] : void 0;
5575
- }
5576
- function containers(values) {
5577
- const found = [];
5578
- for (let index = 0; index < values.length; index++) {
5579
- const value = values[index];
5580
- const pair = pairOf(value, values[index + 1]);
5581
- if (pair !== void 0) {
5582
- found.push({ entrypoint: pair[0], command: pair[1] });
5583
- index++;
5584
- } else if (value.key !== TRAILING_KEY) {
5585
- found.push({ entrypoint: value, command: void 0 });
5586
- }
5587
- }
5588
- return found;
5589
- }
5590
- function listCommand({ entrypoint, command }, text) {
5591
- const parts = command === void 0 ? [entrypoint] : [entrypoint, command];
5592
- return parts.filter(isList).flatMap((part) => valueWords(part, text).words);
5802
+ return { words, isPlaced: true, isWhole: true };
5593
5803
  }
5594
- function argumentFindings(container, text, locate) {
5595
- const { entrypoint, command } = container;
5804
+ const isJoinable = (words) => words.isPlaced && words.isWhole;
5805
+ function containerReading({ entrypoint, command }, text, locate) {
5806
+ const first = valueWords(entrypoint, text);
5596
5807
  if (command === void 0) {
5597
- return [];
5598
- }
5599
- const words = valueWords(entrypoint, text).words;
5600
- if (isList(entrypoint) && isList(command) && shellScript(words) === void 0) {
5601
- return [];
5808
+ return { commands: isList(entrypoint) ? [first.words] : [], flags: [], scalars: [] };
5602
5809
  }
5603
5810
  const args = valueWords(command, text);
5604
- const pair = { entrypoint: words, command: args.words, written: entrypoint.key };
5605
- return entrypointArgumentFindings({ ...pair, isPlaced: args.isPlaced }, locate);
5811
+ const pair = { entrypoint: first.words, command: args.words, written: entrypoint.key };
5812
+ if (isJoinable(first) && isJoinable(args)) {
5813
+ const isScript = shellScript(first.words) !== void 0;
5814
+ return {
5815
+ commands: containerCommand(first.words, args.words),
5816
+ flags: isScript ? entrypointArgumentFindings(pair, locate) : [],
5817
+ scalars: [entrypoint, command].filter((part) => !isList(part)).flatMap((part) => part.strings)
5818
+ };
5819
+ }
5820
+ const lists = [entrypoint, command].filter(isList).map((part) => valueWords(part, text).words);
5821
+ const flags = entrypointArgumentFindings({ ...pair, isPlaced: args.isPlaced }, locate);
5822
+ return { commands: lists, flags, scalars: [] };
5606
5823
  }
5607
5824
  function containerCommands(text, strings, locate) {
5608
- const found = containers(keyValues(strings));
5609
- const commands = found.map((container) => listCommand(container, text));
5825
+ const found = mapValues(keyValues(strings)).flatMap(containerOf).map((container) => containerReading(container, text, locate));
5826
+ const commands = found.flatMap((reading) => reading.commands);
5610
5827
  const scripts = commands.flatMap((words) => shellScript(words)?.item ?? []);
5611
- const flags = found.flatMap((container) => argumentFindings(container, text, locate));
5828
+ const flags = found.flatMap((reading) => reading.flags);
5612
5829
  return {
5613
5830
  findings: [commandWordFindings(commands, locate), textFindings(flags)],
5614
- scripts: new Set(scripts)
5831
+ scripts: new Set(scripts),
5832
+ commandStrings: new Set(found.flatMap((reading) => reading.scalars.map(({ start }) => start)))
5615
5833
  };
5616
5834
  }
5617
5835
 
@@ -5820,15 +6038,40 @@ const AZURE_PIPELINES_FILE = {
5820
6038
  includes: AZURE_TEMPLATES
5821
6039
  };
5822
6040
  const ENV_EXPRESSION_RE = /\$\{\{[ \t]*env\.([A-Za-z_][A-Za-z0-9_]*)[ \t]*\}\}/g;
6041
+ const WORKFLOW_ENVIRONMENT = {
6042
+ ...PLAIN,
6043
+ keys: /* @__PURE__ */ new Set(),
6044
+ reference: ENV_EXPRESSION_RE
6045
+ };
5823
6046
  const asShellVariable = (reference, name) => `$${name}`.padEnd(reference.length, " ");
6047
+ const YAML_FILE_KINDS = [
6048
+ DEFAULT_YAML_FILE,
6049
+ GITLAB_CI_FILE,
6050
+ CI_ENVIRONMENT_FILE,
6051
+ CIRCLECI_FILE,
6052
+ COMPOSE_FILE,
6053
+ TASKFILE,
6054
+ AZURE_PIPELINES_FILE
6055
+ ];
6056
+ const referencedName = (environment, groups) => environment.shellName(
6057
+ groups.find((part) => typeof part === "string") ?? "",
6058
+ VARIABLES_KEY
6059
+ );
6060
+ function referencedVariables(text) {
6061
+ const environments = [...YAML_FILE_KINDS.map((kind) => kind.environment), WORKFLOW_ENVIRONMENT];
6062
+ return environments.flatMap(
6063
+ (environment) => environment.reference === void 0 ? [] : [...text.matchAll(environment.reference)].map(
6064
+ (match) => referencedName(environment, match.slice(1))
6065
+ )
6066
+ );
6067
+ }
5824
6068
  function withToolReferences(text, { environment, names }) {
5825
- const { reference, shellName } = environment;
6069
+ const { reference } = environment;
5826
6070
  if (reference === void 0) {
5827
6071
  return text;
5828
6072
  }
5829
6073
  return text.replace(reference, (written, ...groups) => {
5830
- const group = groups.find((part) => typeof part === "string") ?? "";
5831
- const name = shellName(group, VARIABLES_KEY);
6074
+ const name = referencedName(environment, groups);
5832
6075
  return names.has(name) ? asShellVariable(written, name) : written;
5833
6076
  });
5834
6077
  }
@@ -5995,18 +6238,21 @@ function appliedFindings(text, found) {
5995
6238
  manualSteps: found.flatMap((findings) => findings.manualSteps)
5996
6239
  };
5997
6240
  }
5998
- function migrateYamlCommands(text, file, strings, { commandKeys, environment, parameters } = DEFAULT_YAML_FILE) {
6241
+ const NO_NAMES = /* @__PURE__ */ new Set();
6242
+ function migrateYamlCommands(text, file, strings, reading = DEFAULT_YAML_FILE) {
6243
+ const { commandKeys, environment, parameters, outsideVariables = NO_NAMES } = reading;
5999
6244
  const lineOf = lineLocator(text);
6000
6245
  const locate = (offset) => `${file}:${lineOf(offset)}`;
6001
6246
  const shellOf = stepShells(strings);
6002
6247
  const values = valuesOf(strings, commandKeys).filter((value) => shellOf(value) === void 0);
6003
- const commandValues = new Set(values.filter((value) => !isContainerListItem(value)));
6004
- const { findings, scripts } = containerCommands(text, strings, locate);
6248
+ const { findings, scripts, commandStrings } = containerCommands(text, strings, locate);
6249
+ const isOwnCommand = (value) => !isContainerListItem(value) && !commandStrings.has(value.start);
6250
+ const commandValues = new Set(values.filter(isOwnCommand));
6005
6251
  const entries = environmentEntries(strings, environment);
6006
6252
  const defaults = parameterDefaults(strings, parameters);
6007
6253
  const isShellText = (value) => commandValues.has(value) || scripts.has(value) || entries.has(value) || defaults.has(value);
6008
6254
  const shellValues = oncePerPlace(strings.filter(isShellText));
6009
- const names = new Set([...entries.values()].map((entry) => entry.name));
6255
+ const names = /* @__PURE__ */ new Set([...[...entries.values()].map((entry) => entry.name), ...outsideVariables]);
6010
6256
  const references = { environment, names, parameters };
6011
6257
  const inputs = inputValues(defaults);
6012
6258
  const variablesAt = scopedVariables({ text, entries, inputs, references });
@@ -6063,7 +6309,7 @@ function listsByHook(strings, key) {
6063
6309
  }
6064
6310
  function entryWords(text, entry) {
6065
6311
  const { start } = entry.content;
6066
- return shellCommands(shellText(text, entry)).flat().map((word) => ({ ...word, start: word.start + start, end: word.end + start }));
6312
+ return shellCommands(shellText(text, entry)).flat().map((word) => movedWord(word, start));
6067
6313
  }
6068
6314
  const itemWord = (text, item) => {
6069
6315
  const { start, end } = item.content;
@@ -6185,7 +6431,7 @@ function usesVariable(text, names) {
6185
6431
  const alternatives = [...names].map((name2) => name2.replace(SPECIAL_CHARACTERS_RE$1, "\\$&"));
6186
6432
  const name = `(?:${alternatives.join("|")})`;
6187
6433
  const expansion = `\\$(?:\\{|\\(|env:)?${name}${NAME_END}|%${name}%`;
6188
- return new RegExp(expansion).test(text);
6434
+ return new RegExp(expansion).test(text) || referencedVariables(text).some((found) => names.has(found));
6189
6435
  }
6190
6436
 
6191
6437
  const withSteps = (migration, steps) => ({
@@ -6451,8 +6697,13 @@ const COMMAND_CONFIG_KINDS = [
6451
6697
  {
6452
6698
  matches: (path) => TASKFILE_NAMES.has(baseName(path)),
6453
6699
  migrate: whenNamed(
6454
- yamlConfig((_context, path) => [
6455
- usedStringsStep((text, strings) => migrateYamlCommands(text, path, strings, TASKFILE))
6700
+ yamlConfig((context, path) => [
6701
+ usedStringsStep(
6702
+ (text, strings) => migrateYamlCommands(text, path, strings, {
6703
+ ...TASKFILE,
6704
+ outsideVariables: context.yamlReach.generatorVariables
6705
+ })
6706
+ )
6456
6707
  ])
6457
6708
  )
6458
6709
  },
@@ -6469,7 +6720,10 @@ const yamlCommandsStep = (path, { apiDescriptionParts, yamlReach }) => (text, re
6469
6720
  const isCiFile = isCiConfigFile(path) || yamlReach.kinds.has(path);
6470
6721
  const isApi = !isCiFile && read.kind === "read" && isApiDescription(read.documents);
6471
6722
  return apiDescriptionParts.has(path) || isApi ? unchanged(text) : usedStringsStep(
6472
- (current, strings) => migrateYamlCommands(current, path, strings, yamlKindOf(yamlReach, path))
6723
+ (current, strings) => migrateYamlCommands(current, path, strings, {
6724
+ ...yamlKindOf(yamlReach, path),
6725
+ outsideVariables: yamlReach.generatorVariables
6726
+ })
6473
6727
  )(text, read);
6474
6728
  };
6475
6729
  const manifestTarget = ({ version, catalogs, aliases }, path) => ({
@@ -6836,6 +7090,7 @@ const NOT_ONLY_GENERATOR_NOTE = "may run openapi-typescript or another program (
6836
7090
  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";
6837
7091
  const SCRIPT_NOT_SHELL_NOTE = "so the tool does not read it as commands: migrate the old package names in it by hand";
6838
7092
  const COMMAND_STRING_NOTE = "a string runs openapi-typescript, so it was kept; change it to";
7093
+ const SPLIT_FLAG_NOTE = "is an old flag of the generator that quotes split where it is written, so it was kept: rename";
6839
7094
  function pinnedPackageMessage(step) {
6840
7095
  const successor = PACKAGE_RENAMES.get(step.name) ?? step.name;
6841
7096
  return `${step.location} names a pinned ${step.name}@<version>: change it to ${successor}@<version>`;
@@ -6854,6 +7109,7 @@ const COMMAND_STEP_KINDS = /* @__PURE__ */ new Set([
6854
7109
  "pinned-package",
6855
7110
  "old-package-file",
6856
7111
  "old-name-in-command",
7112
+ "split-flag",
6857
7113
  "script-not-shell",
6858
7114
  "command-string"
6859
7115
  ]);
@@ -6872,6 +7128,8 @@ function commandStepMessage(step) {
6872
7128
  return `${step.location}: ${step.path} ${OLD_FILE_NOTE$1}`;
6873
7129
  case "old-name-in-command":
6874
7130
  return `${step.location}: "${step.word}" ${OLD_NAME_IN_COMMAND_NOTE}`;
7131
+ case "split-flag":
7132
+ return `${step.location}: ${step.flag} ${SPLIT_FLAG_NOTE} ${flagRenames([step.flag])}`;
6875
7133
  case "script-not-shell":
6876
7134
  return `${step.location}: the script runs with ${step.shell}, ${SCRIPT_NOT_SHELL_NOTE}`;
6877
7135
  case "command-string":