openapi-typesafe-migrate 1.0.0-rc.7 → 1.0.0-rc.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -0
- package/README.md +45 -24
- package/dist/cli/main.mjs +527 -151
- package/dist/cli/main.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli/main.mjs
CHANGED
|
@@ -609,15 +609,17 @@ function columnAt(text, offset) {
|
|
|
609
609
|
return offset - text.lastIndexOf("\n", offset - 1);
|
|
610
610
|
}
|
|
611
611
|
|
|
612
|
+
class MigrationBugError extends Error {
|
|
613
|
+
name = "MigrationBugError";
|
|
614
|
+
}
|
|
615
|
+
|
|
612
616
|
function applyTextEdits(text, edits) {
|
|
613
617
|
const sorted = [...edits].sort((left, right) => left.start - right.start);
|
|
614
618
|
let edited = "";
|
|
615
619
|
let copiedUpTo = 0;
|
|
616
620
|
for (const edit of sorted) {
|
|
617
621
|
if (edit.start < copiedUpTo) {
|
|
618
|
-
throw new
|
|
619
|
-
`Edits overlap at offset ${edit.start}; the file was not changed (a migration bug).`
|
|
620
|
-
);
|
|
622
|
+
throw new MigrationBugError(`edits overlap at offset ${edit.start}`);
|
|
621
623
|
}
|
|
622
624
|
edited += `${text.slice(copiedUpTo, edit.start)}${edit.text}`;
|
|
623
625
|
copiedUpTo = edit.end;
|
|
@@ -625,6 +627,80 @@ function applyTextEdits(text, edits) {
|
|
|
625
627
|
return edited + text.slice(copiedUpTo);
|
|
626
628
|
}
|
|
627
629
|
|
|
630
|
+
const TYPE_TEXT = "string";
|
|
631
|
+
const TYPESCRIPT_MODULES$1 = /* @__PURE__ */ new Set(["typescript", "@typescript/typescript6"]);
|
|
632
|
+
function isTypescriptReference(node, bindings) {
|
|
633
|
+
const name = node.typeName;
|
|
634
|
+
if (ts.isIdentifier(name)) {
|
|
635
|
+
return bindings.bindingOf(name)?.kind === "typescript-import";
|
|
636
|
+
}
|
|
637
|
+
return ts.isIdentifier(name.left) && bindings.bindingOf(name.left)?.kind === "typescript-module";
|
|
638
|
+
}
|
|
639
|
+
function isTypescriptImportType(node) {
|
|
640
|
+
const { argument } = node;
|
|
641
|
+
return !node.isTypeOf && ts.isLiteralTypeNode(argument) && ts.isStringLiteral(argument.literal) && TYPESCRIPT_MODULES$1.has(argument.literal.text);
|
|
642
|
+
}
|
|
643
|
+
function joined$1(found) {
|
|
644
|
+
const edits = [];
|
|
645
|
+
for (const part of found) {
|
|
646
|
+
if (part === void 0) {
|
|
647
|
+
return void 0;
|
|
648
|
+
}
|
|
649
|
+
edits.push(...part);
|
|
650
|
+
}
|
|
651
|
+
return edits;
|
|
652
|
+
}
|
|
653
|
+
const memberEdits = (node, bindings) => joined$1(
|
|
654
|
+
node.members.map(
|
|
655
|
+
(member) => ts.isPropertySignature(member) ? typeEdits(member.type, bindings) : void 0
|
|
656
|
+
)
|
|
657
|
+
);
|
|
658
|
+
const replaced = (node) => [
|
|
659
|
+
{ start: node.getStart(), end: node.getEnd(), text: TYPE_TEXT }
|
|
660
|
+
];
|
|
661
|
+
const isKeyword = (node) => node.kind >= ts.SyntaxKind.FirstKeyword && node.kind <= ts.SyntaxKind.LastKeyword;
|
|
662
|
+
function typeEdits(node, bindings) {
|
|
663
|
+
if (node === void 0) {
|
|
664
|
+
return void 0;
|
|
665
|
+
}
|
|
666
|
+
if (ts.isLiteralTypeNode(node) || isKeyword(node)) {
|
|
667
|
+
return [];
|
|
668
|
+
}
|
|
669
|
+
if (ts.isUnionTypeNode(node) || ts.isIntersectionTypeNode(node)) {
|
|
670
|
+
return joined$1(node.types.map((part) => typeEdits(part, bindings)));
|
|
671
|
+
}
|
|
672
|
+
if (ts.isParenthesizedTypeNode(node)) {
|
|
673
|
+
return typeEdits(node.type, bindings);
|
|
674
|
+
}
|
|
675
|
+
if (ts.isTypeLiteralNode(node)) {
|
|
676
|
+
return memberEdits(node, bindings);
|
|
677
|
+
}
|
|
678
|
+
const isNodeType = ts.isTypeReferenceNode(node) ? isTypescriptReference(node, bindings) : ts.isImportTypeNode(node) && isTypescriptImportType(node);
|
|
679
|
+
return isNodeType ? replaced(node) : void 0;
|
|
680
|
+
}
|
|
681
|
+
function functionReturnType(node) {
|
|
682
|
+
return ts.isFunctionTypeNode(node) || ts.isJSDocFunctionType(node) ? node.type : void 0;
|
|
683
|
+
}
|
|
684
|
+
function declaredType(node) {
|
|
685
|
+
const annotation = ts.isVariableDeclaration(node) ? node.type : void 0;
|
|
686
|
+
const isJavaScript = (node.flags & ts.NodeFlags.JavaScriptFile) !== 0;
|
|
687
|
+
return annotation ?? (isJavaScript ? ts.getJSDocType(node) : void 0);
|
|
688
|
+
}
|
|
689
|
+
function annotationEdits(type, bindings) {
|
|
690
|
+
const returned = functionReturnType(type);
|
|
691
|
+
if (returned !== void 0) {
|
|
692
|
+
return typeEdits(returned, bindings);
|
|
693
|
+
}
|
|
694
|
+
return ts.isIndexedAccessTypeNode(type) || isKeyword(type) ? [] : void 0;
|
|
695
|
+
}
|
|
696
|
+
function callbackTypeEdits(annotations, callback, bindings) {
|
|
697
|
+
const returnsTag = ts.getJSDocReturnTag(callback)?.typeExpression?.type;
|
|
698
|
+
return joined$1([
|
|
699
|
+
...annotations.map((type) => annotationEdits(type, bindings)),
|
|
700
|
+
returnsTag === void 0 ? [] : typeEdits(returnsTag, bindings)
|
|
701
|
+
]);
|
|
702
|
+
}
|
|
703
|
+
|
|
628
704
|
const hasExportModifier = (declaration) => (ts.getCombinedModifierFlags(declaration) & ts.ModifierFlags.Export) !== 0;
|
|
629
705
|
const GENERATOR_MODULES = /* @__PURE__ */ new Set([
|
|
630
706
|
"openapi-typesafe",
|
|
@@ -848,6 +924,22 @@ function generatorCalls(file, bindings) {
|
|
|
848
924
|
return calls;
|
|
849
925
|
}
|
|
850
926
|
|
|
927
|
+
const TRANSFORM = "transform";
|
|
928
|
+
const LEGACY_ONLY_CALLBACKS = ["postTransform", "transformProperty"];
|
|
929
|
+
const IR_CALLBACKS_OPTION = "irCallbacks";
|
|
930
|
+
function propertyNamed(options, name) {
|
|
931
|
+
return options.properties.find((property) => {
|
|
932
|
+
const key = property.name;
|
|
933
|
+
return key !== void 0 && (ts.isIdentifier(key) || ts.isStringLiteral(key)) && key.text === name;
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
function legacyCallbackSteps(options, text) {
|
|
937
|
+
return LEGACY_ONLY_CALLBACKS.flatMap((callback) => {
|
|
938
|
+
const property = propertyNamed(options, callback);
|
|
939
|
+
return property === void 0 ? [] : [{ kind: "legacy-callback", line: lineAt(text, property.getStart()), callback }];
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
|
|
851
943
|
const TS_NODE_OPTIONS = "OpenAPITSOptions";
|
|
852
944
|
const IR_OPTIONS = "OpenAPITSIrOptions";
|
|
853
945
|
const TYPE_MODIFIER = "type ";
|
|
@@ -1080,6 +1172,41 @@ function scriptRegions(text, path) {
|
|
|
1080
1172
|
return componentRegions(text, extension === ASTRO_EXTENSION);
|
|
1081
1173
|
}
|
|
1082
1174
|
|
|
1175
|
+
function spreadObject(expression, bindings) {
|
|
1176
|
+
const { value } = unwrapped(expression);
|
|
1177
|
+
if (ts.isObjectLiteralExpression(value)) {
|
|
1178
|
+
return value;
|
|
1179
|
+
}
|
|
1180
|
+
const binding = ts.isIdentifier(value) ? bindings.bindingOf(value) : void 0;
|
|
1181
|
+
const initializer = binding?.kind === "constant" ? unwrapped(binding.initializer).value : void 0;
|
|
1182
|
+
return initializer !== void 0 && ts.isObjectLiteralExpression(initializer) ? initializer : void 0;
|
|
1183
|
+
}
|
|
1184
|
+
function spreadObjectSteps(source, text, bindings, seen) {
|
|
1185
|
+
if (seen.has(source) || propertyNamed(source, IR_CALLBACKS_OPTION) !== void 0) {
|
|
1186
|
+
return [];
|
|
1187
|
+
}
|
|
1188
|
+
seen.add(source);
|
|
1189
|
+
const transform = propertyNamed(source, TRANSFORM);
|
|
1190
|
+
const kept = transform === void 0 ? [] : [
|
|
1191
|
+
{
|
|
1192
|
+
kind: "transform-not-rewritten",
|
|
1193
|
+
line: lineAt(text, transform.getStart()),
|
|
1194
|
+
reason: "in-spread"
|
|
1195
|
+
}
|
|
1196
|
+
];
|
|
1197
|
+
return [
|
|
1198
|
+
...legacyCallbackSteps(source, text),
|
|
1199
|
+
...kept,
|
|
1200
|
+
...spreadSteps(source, text, bindings, seen)
|
|
1201
|
+
];
|
|
1202
|
+
}
|
|
1203
|
+
function spreadSteps(options, text, bindings, seen = /* @__PURE__ */ new Set()) {
|
|
1204
|
+
return options.properties.filter(ts.isSpreadAssignment).flatMap((spread) => {
|
|
1205
|
+
const source = spreadObject(spread.expression, bindings);
|
|
1206
|
+
return source === void 0 ? [{ kind: "options-spread-not-inspected", line: lineAt(text, spread.getStart()) }] : spreadObjectSteps(source, text, bindings, seen);
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1083
1210
|
function namedFunction(name, names) {
|
|
1084
1211
|
const binding = names.bindings.bindingOf(name);
|
|
1085
1212
|
if (binding?.kind === "function") {
|
|
@@ -1108,7 +1235,7 @@ function callbackFunction(property, names) {
|
|
|
1108
1235
|
function callbackAnnotations(property, names) {
|
|
1109
1236
|
const name = namedValue(property);
|
|
1110
1237
|
const declaration = name === void 0 ? void 0 : names.bindings.declarationOf(name);
|
|
1111
|
-
const type = declaration
|
|
1238
|
+
const type = declaredType(declaration ?? property);
|
|
1112
1239
|
return type === void 0 ? [] : [type];
|
|
1113
1240
|
}
|
|
1114
1241
|
function isSharedFunction(property, names) {
|
|
@@ -1438,9 +1565,6 @@ function removalEdits(removed, text) {
|
|
|
1438
1565
|
return [...statements].flatMap((statement) => statementEdits(statement, isRemoved, text));
|
|
1439
1566
|
}
|
|
1440
1567
|
|
|
1441
|
-
const TRANSFORM = "transform";
|
|
1442
|
-
const LEGACY_ONLY_CALLBACKS = ["postTransform", "transformProperty"];
|
|
1443
|
-
const IR_CALLBACKS_OPTION = "irCallbacks";
|
|
1444
1568
|
const IR_CALLBACKS_TRUE = "true";
|
|
1445
1569
|
const IR_CALLBACKS_TRUE_CONST = "true as const";
|
|
1446
1570
|
const IR_CALLBACKS_TRUE_CONST_JS = "/** @type {const} */ (true)";
|
|
@@ -1450,12 +1574,6 @@ const noFindings = () => ({
|
|
|
1450
1574
|
manualSteps: [],
|
|
1451
1575
|
typeReferences: []
|
|
1452
1576
|
});
|
|
1453
|
-
function propertyNamed(options, name) {
|
|
1454
|
-
return options.properties.find((property) => {
|
|
1455
|
-
const key = property.name;
|
|
1456
|
-
return key !== void 0 && (ts.isIdentifier(key) || ts.isStringLiteral(key)) && key.text === name;
|
|
1457
|
-
});
|
|
1458
|
-
}
|
|
1459
1577
|
function returnTypeEdits(callback, text) {
|
|
1460
1578
|
const type = callback.type;
|
|
1461
1579
|
if (type === void 0) {
|
|
@@ -1472,7 +1590,15 @@ function transformEdits(property, names, text) {
|
|
|
1472
1590
|
if (isSharedFunction(property, names)) {
|
|
1473
1591
|
return "shared-function";
|
|
1474
1592
|
}
|
|
1475
|
-
const
|
|
1593
|
+
const declared = callbackTypeEdits(
|
|
1594
|
+
callbackAnnotations(property, names),
|
|
1595
|
+
callback,
|
|
1596
|
+
names.bindings
|
|
1597
|
+
);
|
|
1598
|
+
if (declared === void 0) {
|
|
1599
|
+
return "declared-type";
|
|
1600
|
+
}
|
|
1601
|
+
const edits = [...returnTypeEdits(callback, text), ...declared];
|
|
1476
1602
|
for (const returned of returnedExpressions(callback.body)) {
|
|
1477
1603
|
const returnEditList = returnEdits(returned, names);
|
|
1478
1604
|
if (returnEditList === void 0) {
|
|
@@ -1503,12 +1629,6 @@ function keepReason(options) {
|
|
|
1503
1629
|
}
|
|
1504
1630
|
return options.properties.some(ts.isSpreadAssignment) ? "spread-options" : void 0;
|
|
1505
1631
|
}
|
|
1506
|
-
function legacyCallbackSteps(options, text) {
|
|
1507
|
-
return LEGACY_ONLY_CALLBACKS.flatMap((callback) => {
|
|
1508
|
-
const property = propertyNamed(options, callback);
|
|
1509
|
-
return property === void 0 ? [] : [{ kind: "legacy-callback", line: lineAt(text, property.getStart()), callback }];
|
|
1510
|
-
});
|
|
1511
|
-
}
|
|
1512
1632
|
function transformRewrite(transform, options, names, text) {
|
|
1513
1633
|
const edits = keepReason(options.options) ?? transformEdits(transform, names, text);
|
|
1514
1634
|
if (typeof edits === "string") {
|
|
@@ -1524,6 +1644,7 @@ function migrateOptions(options, text, names) {
|
|
|
1524
1644
|
}
|
|
1525
1645
|
const findings = noFindings();
|
|
1526
1646
|
findings.manualSteps.push(...legacyCallbackSteps(options.options, text));
|
|
1647
|
+
findings.manualSteps.push(...spreadSteps(options.options, text, names.bindings));
|
|
1527
1648
|
const transform = propertyNamed(options.options, TRANSFORM);
|
|
1528
1649
|
if (transform === void 0) {
|
|
1529
1650
|
return findings;
|
|
@@ -1542,12 +1663,13 @@ function migrateOptions(options, text, names) {
|
|
|
1542
1663
|
findings.changes.push({ kind: "transform-rewritten", line });
|
|
1543
1664
|
return findings;
|
|
1544
1665
|
}
|
|
1545
|
-
function sharedOptionsFindings(options, text) {
|
|
1666
|
+
function sharedOptionsFindings(options, text, bindings) {
|
|
1546
1667
|
const findings = noFindings();
|
|
1547
1668
|
if (propertyNamed(options, IR_CALLBACKS_OPTION) !== void 0) {
|
|
1548
1669
|
return findings;
|
|
1549
1670
|
}
|
|
1550
1671
|
findings.manualSteps.push(...legacyCallbackSteps(options, text));
|
|
1672
|
+
findings.manualSteps.push(...spreadSteps(options, text, bindings));
|
|
1551
1673
|
const transform = propertyNamed(options, TRANSFORM);
|
|
1552
1674
|
if (transform !== void 0) {
|
|
1553
1675
|
const line = lineAt(text, transform.getStart());
|
|
@@ -1563,7 +1685,7 @@ function callFindings(found, text, names) {
|
|
|
1563
1685
|
case "own":
|
|
1564
1686
|
return migrateOptions(options, text, names);
|
|
1565
1687
|
case "shared":
|
|
1566
|
-
return sharedOptionsFindings(options.options, text);
|
|
1688
|
+
return sharedOptionsFindings(options.options, text, names.bindings);
|
|
1567
1689
|
case "unknown": {
|
|
1568
1690
|
const line = lineAt(text, options.expression.getStart());
|
|
1569
1691
|
return { ...noFindings(), manualSteps: [{ kind: "options-not-inspected", line }] };
|
|
@@ -1958,7 +2080,7 @@ function renamedSpecifier(value) {
|
|
|
1958
2080
|
const NODE_MODULES_FOLDER = "node_modules/";
|
|
1959
2081
|
function oldPackageInNodeModules(path) {
|
|
1960
2082
|
return [...PACKAGE_RENAMES.keys()].find(
|
|
1961
|
-
(name) => path.includes(`${NODE_MODULES_FOLDER}${name}${PATH_SEPARATOR$9}`)
|
|
2083
|
+
(name) => path.includes(`${NODE_MODULES_FOLDER}${name}${PATH_SEPARATOR$9}`) || path.endsWith(`${NODE_MODULES_FOLDER}${name}`)
|
|
1962
2084
|
);
|
|
1963
2085
|
}
|
|
1964
2086
|
|
|
@@ -2037,8 +2159,11 @@ const DESCRIPTION_KEY_LINE_RE = new RegExp(
|
|
|
2037
2159
|
"m"
|
|
2038
2160
|
);
|
|
2039
2161
|
const mayBeApiDescription = (text) => DESCRIPTION_KEY_LINE_RE.test(text);
|
|
2162
|
+
const SPEC_VERSION_RE = /^\d+\.\d+(?:\.\d+)?(?:-[0-9A-Za-z.-]+)?$/;
|
|
2040
2163
|
const isApiDescription = (documents) => documents.some(
|
|
2041
|
-
(root) => isMap(root) && textEntries(root).some(
|
|
2164
|
+
(root) => isMap(root) && textEntries(root).some(
|
|
2165
|
+
({ key, value }) => API_DESCRIPTION_KEY_SET.has(key.value) && isScalar(value) && SPEC_VERSION_RE.test(value.value)
|
|
2166
|
+
)
|
|
2042
2167
|
);
|
|
2043
2168
|
const REF_KEY = "$ref";
|
|
2044
2169
|
const FRAGMENT_START = "#";
|
|
@@ -2193,8 +2318,8 @@ const COMMAND_SEPARATORS = /* @__PURE__ */ new Set([
|
|
|
2193
2318
|
"\n"
|
|
2194
2319
|
]);
|
|
2195
2320
|
const WORD_BREAKS = /* @__PURE__ */ new Set([" ", " ", "\r", "<", ">"]);
|
|
2196
|
-
const BACKSLASH$
|
|
2197
|
-
const DOUBLE_QUOTE_ESCAPES = /* @__PURE__ */ new Set(["$", "`", '"', BACKSLASH$
|
|
2321
|
+
const BACKSLASH$3 = "\\";
|
|
2322
|
+
const DOUBLE_QUOTE_ESCAPES = /* @__PURE__ */ new Set(["$", "`", '"', BACKSLASH$3, "\n"]);
|
|
2198
2323
|
function singleQuoted(text, start) {
|
|
2199
2324
|
const close = text.indexOf("'", start + 1);
|
|
2200
2325
|
const end = close === -1 ? text.length : close + 1;
|
|
@@ -2205,13 +2330,13 @@ function doubleQuoted(text, start) {
|
|
|
2205
2330
|
let index = start + 1;
|
|
2206
2331
|
while (index < text.length && text[index] !== '"') {
|
|
2207
2332
|
const next = text[index + 1] ?? "";
|
|
2208
|
-
const isEscape = text[index] === BACKSLASH$
|
|
2333
|
+
const isEscape = text[index] === BACKSLASH$3 && DOUBLE_QUOTE_ESCAPES.has(next);
|
|
2209
2334
|
value += isEscape ? next === "\n" ? "" : next : text[index];
|
|
2210
2335
|
index += isEscape ? 2 : 1;
|
|
2211
2336
|
}
|
|
2212
2337
|
return { end: Math.min(index + 1, text.length), value };
|
|
2213
2338
|
}
|
|
2214
|
-
const isLineContinuation = (text, index) => text[index] === BACKSLASH$
|
|
2339
|
+
const isLineContinuation = (text, index) => text[index] === BACKSLASH$3 && (text[index + 1] === "\n" || text.startsWith("\r\n", index + 1));
|
|
2215
2340
|
const ARITHMETIC_OPEN = "$((";
|
|
2216
2341
|
const NESTING_OPEN = "(";
|
|
2217
2342
|
const NESTING_CLOSE = ")";
|
|
@@ -2254,7 +2379,7 @@ function quotedOrEscapedEnd(text, index) {
|
|
|
2254
2379
|
if (char === '"') {
|
|
2255
2380
|
return doubleQuoted(text, index).end;
|
|
2256
2381
|
}
|
|
2257
|
-
return char === BACKSLASH$
|
|
2382
|
+
return char === BACKSLASH$3 ? index + 2 : void 0;
|
|
2258
2383
|
}
|
|
2259
2384
|
function expansionPart(text, index) {
|
|
2260
2385
|
if (text.startsWith(ARITHMETIC_OPEN, index)) {
|
|
@@ -2275,7 +2400,7 @@ function wordPart(text, index) {
|
|
|
2275
2400
|
if (char === '"') {
|
|
2276
2401
|
return doubleQuoted(text, index);
|
|
2277
2402
|
}
|
|
2278
|
-
if (char === BACKSLASH$
|
|
2403
|
+
if (char === BACKSLASH$3 && index + 1 < text.length) {
|
|
2279
2404
|
return { end: index + 2, value: text[index + 1] };
|
|
2280
2405
|
}
|
|
2281
2406
|
return { end: index + 1, value: char };
|
|
@@ -2363,9 +2488,11 @@ function skipAssignments(words, start) {
|
|
|
2363
2488
|
}
|
|
2364
2489
|
return index;
|
|
2365
2490
|
}
|
|
2491
|
+
const WINDOWS_PROGRAM_SUFFIX_RE = /\.(?:cmd|exe|ps1)$/i;
|
|
2492
|
+
const entryWord = (word) => word?.value.replace(WINDOWS_PROGRAM_SUFFIX_RE, "");
|
|
2366
2493
|
function matchingPrefix(entries, words, index) {
|
|
2367
2494
|
return entries.find(
|
|
2368
|
-
(entry) => entry.words.every((name, offset) => words[index + offset]
|
|
2495
|
+
(entry) => entry.words.every((name, offset) => entryWord(words[index + offset]) === name)
|
|
2369
2496
|
);
|
|
2370
2497
|
}
|
|
2371
2498
|
function inlineOptionPackage(word, launcher) {
|
|
@@ -2449,6 +2576,48 @@ function runsGenerator(command) {
|
|
|
2449
2576
|
return word !== void 0 && packageReference(word.value).name === GENERATOR_COMMAND;
|
|
2450
2577
|
}
|
|
2451
2578
|
|
|
2579
|
+
const CODE_OPTIONS = /* @__PURE__ */ new Map([
|
|
2580
|
+
["python", /* @__PURE__ */ new Set(["-c"])],
|
|
2581
|
+
["node", /* @__PURE__ */ new Set(["-e", "--eval", "-p", "--print"])],
|
|
2582
|
+
["bun", /* @__PURE__ */ new Set(["-e", "--eval", "-p", "--print"])],
|
|
2583
|
+
["ruby", /* @__PURE__ */ new Set(["-e"])],
|
|
2584
|
+
["perl", /* @__PURE__ */ new Set(["-e", "-E"])]
|
|
2585
|
+
]);
|
|
2586
|
+
const PYTHON_RE = /^(?:python[0-9.]*|py)$/;
|
|
2587
|
+
const PYTHON = "python";
|
|
2588
|
+
const PATH_SEPARATOR_RE$1 = /[/\\]/;
|
|
2589
|
+
const WINDOWS_EXECUTABLE_RE$1 = /\.exe$/i;
|
|
2590
|
+
const OPTION_START$1 = "-";
|
|
2591
|
+
function interpreterOf(word) {
|
|
2592
|
+
const name = (word.value.split(PATH_SEPARATOR_RE$1).at(-1) ?? "").replace(
|
|
2593
|
+
WINDOWS_EXECUTABLE_RE$1,
|
|
2594
|
+
""
|
|
2595
|
+
);
|
|
2596
|
+
return PYTHON_RE.test(name) ? PYTHON : name;
|
|
2597
|
+
}
|
|
2598
|
+
function codeAfter(command, program) {
|
|
2599
|
+
const options = CODE_OPTIONS.get(interpreterOf(command[program]));
|
|
2600
|
+
for (let index = program + 1; options !== void 0 && index < command.length; index += 1) {
|
|
2601
|
+
const word = command[index];
|
|
2602
|
+
if (!word.value.startsWith(OPTION_START$1)) {
|
|
2603
|
+
return void 0;
|
|
2604
|
+
}
|
|
2605
|
+
if (options.has(word.value)) {
|
|
2606
|
+
return command[index + 1];
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
return void 0;
|
|
2610
|
+
}
|
|
2611
|
+
function inlineCode(command) {
|
|
2612
|
+
for (const [index, program] of command.entries()) {
|
|
2613
|
+
const code = codeAfter(command, index);
|
|
2614
|
+
if (code !== void 0) {
|
|
2615
|
+
return { code, program };
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
return void 0;
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2452
2621
|
const EXPANSION_HEAD_RE = /^\$\{([A-Za-z_][A-Za-z0-9_]*):?([-=+])/;
|
|
2453
2622
|
const USES = {
|
|
2454
2623
|
"-": "default",
|
|
@@ -2456,7 +2625,7 @@ const USES = {
|
|
|
2456
2625
|
"+": "alternative"
|
|
2457
2626
|
};
|
|
2458
2627
|
const SINGLE_QUOTE = "'";
|
|
2459
|
-
const BACKSLASH$
|
|
2628
|
+
const BACKSLASH$2 = "\\";
|
|
2460
2629
|
function expansionAt(text, start) {
|
|
2461
2630
|
const head = EXPANSION_HEAD_RE.exec(text.slice(start));
|
|
2462
2631
|
const end = head === null ? void 0 : parameterExpansionEnd(text, start);
|
|
@@ -2480,7 +2649,7 @@ function wordExpansions(raw) {
|
|
|
2480
2649
|
if (char === SINGLE_QUOTE) {
|
|
2481
2650
|
const close = raw.indexOf(SINGLE_QUOTE, index + 1);
|
|
2482
2651
|
index = close === -1 ? raw.length : close + 1;
|
|
2483
|
-
} else if (char === BACKSLASH$
|
|
2652
|
+
} else if (char === BACKSLASH$2) {
|
|
2484
2653
|
index += 2;
|
|
2485
2654
|
} else if (raw.startsWith(PARAMETER_OPEN, index)) {
|
|
2486
2655
|
const expansion = expansionAt(raw, index);
|
|
@@ -2492,9 +2661,9 @@ function wordExpansions(raw) {
|
|
|
2492
2661
|
}
|
|
2493
2662
|
return found;
|
|
2494
2663
|
}
|
|
2495
|
-
const DOUBLE_QUOTE = '"';
|
|
2664
|
+
const DOUBLE_QUOTE$1 = '"';
|
|
2496
2665
|
function wholeWordExpansion(raw) {
|
|
2497
|
-
const isQuoted = raw.length > 1 && raw.startsWith(DOUBLE_QUOTE) && raw.endsWith(DOUBLE_QUOTE);
|
|
2666
|
+
const isQuoted = raw.length > 1 && raw.startsWith(DOUBLE_QUOTE$1) && raw.endsWith(DOUBLE_QUOTE$1);
|
|
2498
2667
|
const inner = isQuoted ? 1 : 0;
|
|
2499
2668
|
const [expansion] = wordExpansions(raw);
|
|
2500
2669
|
const isWhole = expansion?.expansionStart === inner && expansion.expansionEnd === raw.length - inner;
|
|
@@ -2504,7 +2673,7 @@ function wholeWordExpansion(raw) {
|
|
|
2504
2673
|
const HEREDOC_OPERATOR = "<<";
|
|
2505
2674
|
const HERE_STRING_OPERATOR = "<<<";
|
|
2506
2675
|
const STRIP_TABS = "-";
|
|
2507
|
-
const LINE_BREAK$
|
|
2676
|
+
const LINE_BREAK$5 = "\n";
|
|
2508
2677
|
const LEADING_TABS_RE = /^\t+/;
|
|
2509
2678
|
const CARRIAGE_RETURN_RE = /\r$/;
|
|
2510
2679
|
function heredocAt(text, index, where) {
|
|
@@ -2536,7 +2705,7 @@ function isDelimiterLine(text, lineStart, end, heredoc) {
|
|
|
2536
2705
|
function readBody(text, start, heredoc) {
|
|
2537
2706
|
let lineStart = start;
|
|
2538
2707
|
while (lineStart < text.length) {
|
|
2539
|
-
const lineEnd = text.indexOf(LINE_BREAK$
|
|
2708
|
+
const lineEnd = text.indexOf(LINE_BREAK$5, lineStart);
|
|
2540
2709
|
const end = lineEnd === -1 ? text.length : lineEnd;
|
|
2541
2710
|
if (isDelimiterLine(text, lineStart, end, heredoc)) {
|
|
2542
2711
|
const body = { command: heredoc.command, start, text: text.slice(start, lineStart) };
|
|
@@ -2557,8 +2726,66 @@ function readBodies(text, start, pending) {
|
|
|
2557
2726
|
return { bodies, end };
|
|
2558
2727
|
}
|
|
2559
2728
|
|
|
2729
|
+
const EXPRESSION_OPEN = "${{";
|
|
2730
|
+
const EXPRESSION_CLOSE = "}}";
|
|
2731
|
+
const LITERAL_QUOTE = "'";
|
|
2732
|
+
const SHELL_SINGLE_QUOTE = "'";
|
|
2733
|
+
const BACKSLASH$1 = "\\";
|
|
2734
|
+
const DOUBLE_QUOTE = '"';
|
|
2735
|
+
function expressionEnd(text, start) {
|
|
2736
|
+
for (let index = start + EXPRESSION_OPEN.length; index < text.length; ) {
|
|
2737
|
+
if (text.startsWith(EXPRESSION_CLOSE, index)) {
|
|
2738
|
+
return index + EXPRESSION_CLOSE.length;
|
|
2739
|
+
}
|
|
2740
|
+
const close = text[index] === LITERAL_QUOTE ? text.indexOf(LITERAL_QUOTE, index + 1) : index;
|
|
2741
|
+
index = close === -1 ? text.length : close + 1;
|
|
2742
|
+
}
|
|
2743
|
+
return void 0;
|
|
2744
|
+
}
|
|
2745
|
+
function literalsIn(raw, start, end) {
|
|
2746
|
+
const found = [];
|
|
2747
|
+
let open = raw.indexOf(LITERAL_QUOTE, start);
|
|
2748
|
+
while (open !== -1 && open < end) {
|
|
2749
|
+
let close = raw.indexOf(LITERAL_QUOTE, open + 1);
|
|
2750
|
+
let isEscaped = false;
|
|
2751
|
+
while (close !== -1 && raw[close + 1] === LITERAL_QUOTE) {
|
|
2752
|
+
isEscaped = true;
|
|
2753
|
+
close = raw.indexOf(LITERAL_QUOTE, close + 2);
|
|
2754
|
+
}
|
|
2755
|
+
if (close === -1) {
|
|
2756
|
+
break;
|
|
2757
|
+
}
|
|
2758
|
+
found.push(...isEscaped ? [] : [{ text: raw.slice(open + 1, close), start: open + 1 }]);
|
|
2759
|
+
open = raw.indexOf(LITERAL_QUOTE, close + 1);
|
|
2760
|
+
}
|
|
2761
|
+
return found;
|
|
2762
|
+
}
|
|
2763
|
+
function expressionLiterals(raw) {
|
|
2764
|
+
const found = [];
|
|
2765
|
+
for (let index = 0; index < raw.length; ) {
|
|
2766
|
+
if (raw[index] === SHELL_SINGLE_QUOTE) {
|
|
2767
|
+
const close = raw.indexOf(SHELL_SINGLE_QUOTE, index + 1);
|
|
2768
|
+
index = close === -1 ? raw.length : close + 1;
|
|
2769
|
+
} else if (raw[index] === BACKSLASH$1) {
|
|
2770
|
+
index += 2;
|
|
2771
|
+
} else if (raw.startsWith(EXPRESSION_OPEN, index)) {
|
|
2772
|
+
const end = expressionEnd(raw, index) ?? raw.length;
|
|
2773
|
+
found.push(...literalsIn(raw, index + EXPRESSION_OPEN.length, end));
|
|
2774
|
+
index = end;
|
|
2775
|
+
} else {
|
|
2776
|
+
index += 1;
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
return found;
|
|
2780
|
+
}
|
|
2781
|
+
function isWholeExpression(raw) {
|
|
2782
|
+
const isQuoted = raw.length > 1 && raw.startsWith(DOUBLE_QUOTE) && raw.endsWith(DOUBLE_QUOTE);
|
|
2783
|
+
const inner = isQuoted ? 1 : 0;
|
|
2784
|
+
return raw.startsWith(EXPRESSION_OPEN, inner) && expressionEnd(raw, inner) === raw.length - inner;
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2560
2787
|
const COMMENT = "#";
|
|
2561
|
-
const LINE_BREAK$
|
|
2788
|
+
const LINE_BREAK$4 = "\n";
|
|
2562
2789
|
function skipTrivia(text, index) {
|
|
2563
2790
|
const char = text[index];
|
|
2564
2791
|
if (WORD_BREAKS.has(char)) {
|
|
@@ -2588,7 +2815,7 @@ function topLevelCommands(text, indent) {
|
|
|
2588
2815
|
index = heredoc.end;
|
|
2589
2816
|
} else if (afterTrivia !== index) {
|
|
2590
2817
|
index = afterTrivia;
|
|
2591
|
-
} else if (text[index] === LINE_BREAK$
|
|
2818
|
+
} else if (text[index] === LINE_BREAK$4 && pending.length > 0) {
|
|
2592
2819
|
const read = readBodies(text, index + 1, pending);
|
|
2593
2820
|
heredocs.push(...read.bodies);
|
|
2594
2821
|
pending = [];
|
|
@@ -2676,13 +2903,14 @@ const shifted = (words, offset) => words.map((word) => ({ ...word, start: word.s
|
|
|
2676
2903
|
function shellCommandsAt(text, offset, indent = 0) {
|
|
2677
2904
|
return shellCommands(text, indent).map((words) => shifted(words, offset));
|
|
2678
2905
|
}
|
|
2679
|
-
const expansionCommands = (word) => wordExpansions(word.raw).flatMap(
|
|
2680
|
-
(
|
|
2906
|
+
const expansionCommands = (word) => [...wordExpansions(word.raw), ...expressionLiterals(word.raw)].flatMap(
|
|
2907
|
+
(part) => shellCommandsAt(part.text, word.start + part.start)
|
|
2681
2908
|
);
|
|
2682
2909
|
function innerCommands(command) {
|
|
2683
2910
|
const assignments = new Map(shellAssignments(command).map((found) => [found.word, found]));
|
|
2911
|
+
const code = inlineCode(command)?.code;
|
|
2684
2912
|
return command.flatMap((word) => {
|
|
2685
|
-
const inner = nestedCommandText(word);
|
|
2913
|
+
const inner = word === code ? void 0 : nestedCommandText(word);
|
|
2686
2914
|
if (inner !== void 0) {
|
|
2687
2915
|
return shellCommandsAt(inner, word.start + 1);
|
|
2688
2916
|
}
|
|
@@ -2847,7 +3075,7 @@ const VARIABLE_WORD_RE = new RegExp(
|
|
|
2847
3075
|
);
|
|
2848
3076
|
function withUnreadVariables(variables) {
|
|
2849
3077
|
return {
|
|
2850
|
-
programAt: (program) => variables.programAt(program) ?? (VARIABLE_WORD_RE.test(program.raw) ? { written: program.raw, program: "generator-or-other" } : void 0),
|
|
3078
|
+
programAt: (program) => variables.programAt(program) ?? (VARIABLE_WORD_RE.test(program.raw) || isWholeExpression(program.raw) ? { written: program.raw, program: "generator-or-other" } : void 0),
|
|
2851
3079
|
referenceAt: (word) => variables.referenceAt(word)
|
|
2852
3080
|
};
|
|
2853
3081
|
}
|
|
@@ -2961,6 +3189,14 @@ function commandFindings(command, locate, variables) {
|
|
|
2961
3189
|
const handled = new Set(found.map((finding) => finding.offset));
|
|
2962
3190
|
return [...found, ...unmigratedWordFindings(words, handled, locate)];
|
|
2963
3191
|
}
|
|
3192
|
+
function inlineCodeFindings({ code, program }, locate) {
|
|
3193
|
+
if (!namesOriginalPackage(code.value)) {
|
|
3194
|
+
return [];
|
|
3195
|
+
}
|
|
3196
|
+
const location = locate(code.start);
|
|
3197
|
+
const manualStep = { kind: "script-not-shell", location, shell: program.value };
|
|
3198
|
+
return [{ offset: code.start, manualStep }];
|
|
3199
|
+
}
|
|
2964
3200
|
function knownFindings(words, locate, variables) {
|
|
2965
3201
|
const roles = commandRoles(words);
|
|
2966
3202
|
const findings = roles.packages.flatMap((found2) => packageFindings(found2, locate));
|
|
@@ -2968,6 +3204,10 @@ function knownFindings(words, locate, variables) {
|
|
|
2968
3204
|
if (program === void 0) {
|
|
2969
3205
|
return findings;
|
|
2970
3206
|
}
|
|
3207
|
+
const code = inlineCode(words);
|
|
3208
|
+
if (code !== void 0) {
|
|
3209
|
+
return [...findings, ...inlineCodeFindings(code, locate)];
|
|
3210
|
+
}
|
|
2971
3211
|
const found = programFindings(program, locate(program.start));
|
|
2972
3212
|
findings.push(...found);
|
|
2973
3213
|
const isRenamed = found.some((finding) => finding.change?.kind === "command-renamed");
|
|
@@ -3289,36 +3529,52 @@ function assignedVariable(word) {
|
|
|
3289
3529
|
const isQuoted = raw.length >= 2 && QUOTES.has(raw[0]) && raw.at(-1) === raw[0];
|
|
3290
3530
|
const value = isQuoted ? raw.slice(1, -1) : raw;
|
|
3291
3531
|
const start = word.start + at + 1 + (isQuoted ? 1 : 0);
|
|
3292
|
-
return { name: word.raw.slice(0, at),
|
|
3532
|
+
return { name: word.raw.slice(0, at), value, start };
|
|
3293
3533
|
}
|
|
3294
|
-
function
|
|
3295
|
-
|
|
3534
|
+
function instructionVariables(text, words) {
|
|
3535
|
+
const [instruction, first, ...rest] = words;
|
|
3536
|
+
const last = rest.at(-1);
|
|
3537
|
+
if (instruction.value === LEGACY_ENV_INSTRUCTION && !first.raw.includes(ASSIGNMENT$1)) {
|
|
3538
|
+
const value = last === void 0 ? "" : text.slice(rest[0]?.start, last.end);
|
|
3539
|
+
return [{ name: first.value, value, start: rest[0]?.start ?? first.end }];
|
|
3540
|
+
}
|
|
3541
|
+
return [first, ...rest].flatMap((word) => assignedVariable(word) ?? []);
|
|
3542
|
+
}
|
|
3543
|
+
function variableInstructions(text, commands) {
|
|
3544
|
+
return commands.flatMap((words) => {
|
|
3545
|
+
const [instruction, first] = words;
|
|
3296
3546
|
if (instruction === void 0 || first === void 0) {
|
|
3297
3547
|
return [];
|
|
3298
3548
|
}
|
|
3299
3549
|
if (!VARIABLE_INSTRUCTIONS.has(instruction.value)) {
|
|
3300
3550
|
return [];
|
|
3301
3551
|
}
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
}
|
|
3305
|
-
return [first, ...rest].flatMap((word) => assignedVariable(word) ?? []);
|
|
3552
|
+
const end = words.at(-1).end;
|
|
3553
|
+
return [{ start: instruction.start, end, variables: instructionVariables(text, words) }];
|
|
3306
3554
|
});
|
|
3307
3555
|
}
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3556
|
+
function withoutVariableInstructions(commands, instructions) {
|
|
3557
|
+
const isInInstruction = (words) => {
|
|
3558
|
+
const start = words[0]?.start ?? -1;
|
|
3559
|
+
return instructions.some(
|
|
3560
|
+
(instruction) => start >= instruction.start && start < instruction.end
|
|
3561
|
+
);
|
|
3562
|
+
};
|
|
3563
|
+
return commands.filter((words) => !isInInstruction(words));
|
|
3564
|
+
}
|
|
3565
|
+
const valueCommands = ({ value, start }) => shellCommandsAt(value, start);
|
|
3312
3566
|
function migrateDockerfile(text, file) {
|
|
3313
3567
|
const lineOf = lineLocator(text);
|
|
3314
3568
|
const locate = (offset) => `${file}:${lineOf(offset)}`;
|
|
3315
3569
|
const forms = execForms(text);
|
|
3316
3570
|
const exec = execCommands(forms);
|
|
3317
3571
|
const shellForm = shellCommands(withoutExecForms(text, forms));
|
|
3318
|
-
const
|
|
3319
|
-
const
|
|
3320
|
-
const
|
|
3321
|
-
const
|
|
3572
|
+
const instructions = variableInstructions(text, shellForm);
|
|
3573
|
+
const definitions = instructions.flatMap((instruction) => instruction.variables);
|
|
3574
|
+
const values = definitions.flatMap(valueCommands);
|
|
3575
|
+
const run = withoutVariableInstructions(shellForm, instructions);
|
|
3576
|
+
const commands = [...run, ...values, ...exec.commands];
|
|
3577
|
+
const variables = shellVariables(definitions);
|
|
3322
3578
|
const { edits, changes, manualSteps } = commandWordFindings(commands, locate, variables);
|
|
3323
3579
|
const steps = [...manualSteps, ...escapedScriptSteps(exec.escaped, lineOf)];
|
|
3324
3580
|
return { text: applyTextEdits(text, edits), changes, manualSteps: steps };
|
|
@@ -3670,8 +3926,8 @@ function distinctStrings(strings) {
|
|
|
3670
3926
|
outer = enclosing.at(-1);
|
|
3671
3927
|
}
|
|
3672
3928
|
if (outer !== void 0 && found.end > outer.end) {
|
|
3673
|
-
throw new
|
|
3674
|
-
`
|
|
3929
|
+
throw new MigrationBugError(
|
|
3930
|
+
`source strings overlap at offsets ${outer.start}-${outer.end} and ${found.start}-${found.end}`
|
|
3675
3931
|
);
|
|
3676
3932
|
}
|
|
3677
3933
|
enclosing.push(found);
|
|
@@ -3799,7 +4055,7 @@ const BACKTICK = "`";
|
|
|
3799
4055
|
const ESCAPING_DELIMITER = '"';
|
|
3800
4056
|
const ESCAPE = "\\";
|
|
3801
4057
|
const COMMENT_START$1 = "#";
|
|
3802
|
-
const LINE_BREAK$
|
|
4058
|
+
const LINE_BREAK$3 = "\n";
|
|
3803
4059
|
function closingOffset(text, delimiter, from) {
|
|
3804
4060
|
if (delimiter !== ESCAPING_DELIMITER) {
|
|
3805
4061
|
const close = text.indexOf(delimiter, from);
|
|
@@ -3809,7 +4065,7 @@ function closingOffset(text, delimiter, from) {
|
|
|
3809
4065
|
const char = text[index];
|
|
3810
4066
|
if (char === ESCAPE) {
|
|
3811
4067
|
index += 1;
|
|
3812
|
-
} else if (char === ESCAPING_DELIMITER || char === LINE_BREAK$
|
|
4068
|
+
} else if (char === ESCAPING_DELIMITER || char === LINE_BREAK$3) {
|
|
3813
4069
|
return char === ESCAPING_DELIMITER ? index : void 0;
|
|
3814
4070
|
}
|
|
3815
4071
|
}
|
|
@@ -3832,7 +4088,7 @@ function literalAt(text, offset) {
|
|
|
3832
4088
|
function expressionAt(text, start) {
|
|
3833
4089
|
const literals = [];
|
|
3834
4090
|
let index = start;
|
|
3835
|
-
while (index < text.length && text[index] !== LINE_BREAK$
|
|
4091
|
+
while (index < text.length && text[index] !== LINE_BREAK$3 && text[index] !== COMMENT_START$1) {
|
|
3836
4092
|
const literal = literalAt(text, index);
|
|
3837
4093
|
literals.push(...literal === void 0 ? [] : [literal]);
|
|
3838
4094
|
index = literal?.end ?? index + 1;
|
|
@@ -3856,7 +4112,7 @@ function interpolationAt(text, start) {
|
|
|
3856
4112
|
if (text.startsWith(INTERPOLATION_CLOSE, index)) {
|
|
3857
4113
|
return { start, end: index + INTERPOLATION_CLOSE.length, literals };
|
|
3858
4114
|
}
|
|
3859
|
-
if (text[index] === LINE_BREAK$
|
|
4115
|
+
if (text[index] === LINE_BREAK$3) {
|
|
3860
4116
|
return void 0;
|
|
3861
4117
|
}
|
|
3862
4118
|
const literal = literalAt(text, index);
|
|
@@ -3884,11 +4140,11 @@ const EQUALS = "=";
|
|
|
3884
4140
|
const OPEN_PAREN = "(";
|
|
3885
4141
|
const CLOSE_PAREN = ")";
|
|
3886
4142
|
const COMMENT_START = "#";
|
|
3887
|
-
const LINE_BREAK$
|
|
4143
|
+
const LINE_BREAK$2 = "\n";
|
|
3888
4144
|
const BLANK_RE = /[ \t]/;
|
|
3889
4145
|
const depthChange = (char) => char === OPEN_PAREN ? 1 : char === CLOSE_PAREN ? -1 : 0;
|
|
3890
4146
|
function headerColon(text, from) {
|
|
3891
|
-
for (let index = from; index < text.length && text[index] !== LINE_BREAK$
|
|
4147
|
+
for (let index = from; index < text.length && text[index] !== LINE_BREAK$2; ) {
|
|
3892
4148
|
const literal = literalAt(text, index);
|
|
3893
4149
|
const char = text[index];
|
|
3894
4150
|
if (literal !== void 0) {
|
|
@@ -3946,6 +4202,133 @@ function recipeHeaders(text) {
|
|
|
3946
4202
|
});
|
|
3947
4203
|
}
|
|
3948
4204
|
|
|
4205
|
+
const STEP_SHELL_KEY = "shell";
|
|
4206
|
+
const DEFAULT_SHELL_PATH = ["defaults", "run", STEP_SHELL_KEY];
|
|
4207
|
+
const COMMAND_LINE_SHELLS = /* @__PURE__ */ new Set([
|
|
4208
|
+
...["sh", "bash", "dash", "ash", "zsh", "ksh"],
|
|
4209
|
+
...["pwsh", "powershell", "cmd"],
|
|
4210
|
+
...["msys2", "wsl-bash"]
|
|
4211
|
+
]);
|
|
4212
|
+
const ENV_PROGRAM = "env";
|
|
4213
|
+
const ENV_OPTIONS_WITH_VALUE = /* @__PURE__ */ new Set(["-u", "--unset", "-C", "--chdir"]);
|
|
4214
|
+
const OPTION_START = "-";
|
|
4215
|
+
const ASSIGNMENT = "=";
|
|
4216
|
+
const PATH_SEPARATOR_RE = /[/\\]/;
|
|
4217
|
+
const WINDOWS_EXECUTABLE_RE = /\.exe$/i;
|
|
4218
|
+
const WHITESPACE_RE = /\s+/;
|
|
4219
|
+
const EXECUTORS_KEY = "executors";
|
|
4220
|
+
const EXECUTOR_SHELL_PATH_LENGTH = 3;
|
|
4221
|
+
const EXECUTOR_KEY = "executor";
|
|
4222
|
+
const EXECUTOR_NAME_KEY = "name";
|
|
4223
|
+
const programName = (word) => (word.split(PATH_SEPARATOR_RE).at(-1) ?? "").replace(WINDOWS_EXECUTABLE_RE, "");
|
|
4224
|
+
function envPrefixLength(words) {
|
|
4225
|
+
let index = 0;
|
|
4226
|
+
while (index < words.length) {
|
|
4227
|
+
const word = words[index] ?? "";
|
|
4228
|
+
if (ENV_OPTIONS_WITH_VALUE.has(word)) {
|
|
4229
|
+
index += 2;
|
|
4230
|
+
} else if (word.startsWith(OPTION_START) || word.includes(ASSIGNMENT)) {
|
|
4231
|
+
index += 1;
|
|
4232
|
+
} else {
|
|
4233
|
+
break;
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4236
|
+
return index;
|
|
4237
|
+
}
|
|
4238
|
+
function shellProgram(shell) {
|
|
4239
|
+
const words = shell.trim().split(WHITESPACE_RE);
|
|
4240
|
+
const first = programName(words[0] ?? "");
|
|
4241
|
+
if (first !== ENV_PROGRAM) {
|
|
4242
|
+
return first;
|
|
4243
|
+
}
|
|
4244
|
+
const rest = words.slice(1);
|
|
4245
|
+
return programName(rest[envPrefixLength(rest)] ?? "");
|
|
4246
|
+
}
|
|
4247
|
+
const isCommandLineShell = (shell) => COMMAND_LINE_SHELLS.has(shellProgram(shell));
|
|
4248
|
+
const endsWith = (path, keys) => keys.every((key, index) => path[path.length - keys.length + index] === key);
|
|
4249
|
+
const isDefaultShell = (value) => endsWith(value.path, DEFAULT_SHELL_PATH);
|
|
4250
|
+
function scopeOf(value) {
|
|
4251
|
+
const keys = isDefaultShell(value) ? DEFAULT_SHELL_PATH.length : 1;
|
|
4252
|
+
return JSON.stringify(value.path.slice(0, -keys));
|
|
4253
|
+
}
|
|
4254
|
+
function executorShells(shells) {
|
|
4255
|
+
const isExecutorShell = ({ path }) => path.length === EXECUTOR_SHELL_PATH_LENGTH && path[0] === EXECUTORS_KEY;
|
|
4256
|
+
return new Map(
|
|
4257
|
+
shells.filter(isExecutorShell).map((shell) => [String(shell.path[1]), shell.value])
|
|
4258
|
+
);
|
|
4259
|
+
}
|
|
4260
|
+
function executorJob({ path }) {
|
|
4261
|
+
if (path.at(-1) === EXECUTOR_KEY) {
|
|
4262
|
+
return path.slice(0, -1);
|
|
4263
|
+
}
|
|
4264
|
+
const isNamed = path.at(-1) === EXECUTOR_NAME_KEY && path.at(-2) === EXECUTOR_KEY;
|
|
4265
|
+
return isNamed ? path.slice(0, -2) : void 0;
|
|
4266
|
+
}
|
|
4267
|
+
function inheritedShells(strings, shells) {
|
|
4268
|
+
const byExecutor = executorShells(shells);
|
|
4269
|
+
return strings.flatMap((value) => {
|
|
4270
|
+
const job = executorJob(value);
|
|
4271
|
+
const shell = byExecutor.get(value.value);
|
|
4272
|
+
return job === void 0 || shell === void 0 ? [] : [[JSON.stringify(job), shell]];
|
|
4273
|
+
});
|
|
4274
|
+
}
|
|
4275
|
+
function stepShells(strings) {
|
|
4276
|
+
const shells = strings.filter((value) => value.path.at(-1) === STEP_SHELL_KEY);
|
|
4277
|
+
const isOwnShell = (value) => !isDefaultShell(value);
|
|
4278
|
+
const ordered = [...shells.filter(isDefaultShell), ...shells.filter(isOwnShell)];
|
|
4279
|
+
const byScope = new Map([
|
|
4280
|
+
...inheritedShells(strings, shells),
|
|
4281
|
+
...ordered.map((value) => [scopeOf(value), value.value])
|
|
4282
|
+
]);
|
|
4283
|
+
return (value) => {
|
|
4284
|
+
for (let depth = value.path.length - 1; depth >= 0; depth -= 1) {
|
|
4285
|
+
const shell = byScope.get(JSON.stringify(value.path.slice(0, depth)));
|
|
4286
|
+
if (shell !== void 0) {
|
|
4287
|
+
return isCommandLineShell(shell) ? void 0 : shell;
|
|
4288
|
+
}
|
|
4289
|
+
}
|
|
4290
|
+
return void 0;
|
|
4291
|
+
};
|
|
4292
|
+
}
|
|
4293
|
+
function otherShellFindings(values, shellOf, locate) {
|
|
4294
|
+
const manualSteps = values.flatMap((value) => {
|
|
4295
|
+
const shell = shellOf(value);
|
|
4296
|
+
return shell === void 0 || !namesOriginalPackage(value.value) ? [] : [{ kind: "script-not-shell", location: locate(value.start), shell }];
|
|
4297
|
+
});
|
|
4298
|
+
return { edits: [], changes: [], manualSteps };
|
|
4299
|
+
}
|
|
4300
|
+
|
|
4301
|
+
const LINE_BREAK$1 = "\n";
|
|
4302
|
+
const SHEBANG = "#!";
|
|
4303
|
+
const BODY_LINE_RE = /^(?:[ \t][^\n]*|[ \t]*\r?)$/;
|
|
4304
|
+
function nextLineStart(text, offset) {
|
|
4305
|
+
const lineBreak = text.indexOf(LINE_BREAK$1, offset);
|
|
4306
|
+
return lineBreak === -1 ? text.length : lineBreak + 1;
|
|
4307
|
+
}
|
|
4308
|
+
function bodyEnd(text, start) {
|
|
4309
|
+
let end = start;
|
|
4310
|
+
while (end < text.length) {
|
|
4311
|
+
const lineEnd = nextLineStart(text, end);
|
|
4312
|
+
if (!BODY_LINE_RE.test(text.slice(end, lineEnd).replace(LINE_BREAK$1, ""))) {
|
|
4313
|
+
break;
|
|
4314
|
+
}
|
|
4315
|
+
end = lineEnd;
|
|
4316
|
+
}
|
|
4317
|
+
return end;
|
|
4318
|
+
}
|
|
4319
|
+
function shebangRecipe(text, header) {
|
|
4320
|
+
const start = nextLineStart(text, header.end);
|
|
4321
|
+
const end = bodyEnd(text, start);
|
|
4322
|
+
const firstLine = text.slice(start, end).split(LINE_BREAK$1).map((line) => line.trim()).find((line) => line.length > 0);
|
|
4323
|
+
if (firstLine === void 0 || !firstLine.startsWith(SHEBANG)) {
|
|
4324
|
+
return [];
|
|
4325
|
+
}
|
|
4326
|
+
const shell = firstLine.slice(SHEBANG.length).trim();
|
|
4327
|
+
const shebang = text.indexOf(SHEBANG, start);
|
|
4328
|
+
return isCommandLineShell(shell) ? [] : [{ start, end, shebang, shell }];
|
|
4329
|
+
}
|
|
4330
|
+
const shebangRecipes = (text, headers) => headers.flatMap((header) => shebangRecipe(text, header));
|
|
4331
|
+
|
|
3949
4332
|
const ASSIGNMENT_RE$1 = /^(?:export[ \t]+)?([A-Za-z_][A-Za-z0-9_-]*)[ \t]*:=[ \t]*/gm;
|
|
3950
4333
|
const NAME_VALUE_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
3951
4334
|
const VARIABLE_INTERPOLATION_RE = /^[@+-]*\{\{[ \t]*([A-Za-z_][A-Za-z0-9_-]*)[ \t]*\}\}$/;
|
|
@@ -4001,11 +4384,15 @@ function shellText$1(text, spans) {
|
|
|
4001
4384
|
return { text: masked, interpolations };
|
|
4002
4385
|
}
|
|
4003
4386
|
const literalCommands = (literals) => literals.flatMap((literal) => shellCommandsAt(literal.content, literal.contentStart));
|
|
4004
|
-
|
|
4387
|
+
const defaultValue = ({ literals }) => literals.map((literal) => literal.content).join(BLANK);
|
|
4388
|
+
function variableValues$1(assignments, defaults) {
|
|
4005
4389
|
const values = /* @__PURE__ */ new Map();
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4390
|
+
const named = [
|
|
4391
|
+
...assignments.map((found) => ({ ...found, value: onlyString(found)?.content ?? found.value })),
|
|
4392
|
+
...defaults.map((found) => ({ name: found.name, value: defaultValue(found) }))
|
|
4393
|
+
];
|
|
4394
|
+
for (const { name, value } of named) {
|
|
4395
|
+
values.set(name, [values.get(name), value].join(BLANK).trim());
|
|
4009
4396
|
}
|
|
4010
4397
|
return values;
|
|
4011
4398
|
}
|
|
@@ -4030,6 +4417,7 @@ function justfileVariables(text, values, programs) {
|
|
|
4030
4417
|
}
|
|
4031
4418
|
};
|
|
4032
4419
|
}
|
|
4420
|
+
const shebangSteps = (text, recipes, locate) => recipes.filter((recipe) => namesOriginalPackage(text.slice(recipe.start, recipe.end))).map(({ shebang, shell }) => ({ kind: "script-not-shell", location: locate(shebang), shell }));
|
|
4033
4421
|
function migrateJustfile(text, file) {
|
|
4034
4422
|
const lineOf = lineLocator(text);
|
|
4035
4423
|
const assignments = assignmentsOf$1(text);
|
|
@@ -4039,8 +4427,9 @@ function migrateJustfile(text, file) {
|
|
|
4039
4427
|
...assignments.flatMap(assignmentDefinitions),
|
|
4040
4428
|
...defaults.flatMap(parameterDefinitions)
|
|
4041
4429
|
]);
|
|
4042
|
-
const variables = justfileVariables(text, variableValues$1(assignments), programs);
|
|
4043
|
-
const
|
|
4430
|
+
const variables = justfileVariables(text, variableValues$1(assignments, defaults), programs);
|
|
4431
|
+
const scripts = shebangRecipes(text, headers);
|
|
4432
|
+
const shell = shellText$1(text, [...assignments, ...headers, ...scripts]);
|
|
4044
4433
|
const commands = [
|
|
4045
4434
|
...shellCommands(shell.text),
|
|
4046
4435
|
...literalCommands(assignments.flatMap((assignment) => assignment.literals)),
|
|
@@ -4049,7 +4438,8 @@ function migrateJustfile(text, file) {
|
|
|
4049
4438
|
];
|
|
4050
4439
|
const locate = (offset) => `${file}:${lineOf(offset)}`;
|
|
4051
4440
|
const { edits, changes, manualSteps } = commandWordFindings(commands, locate, variables);
|
|
4052
|
-
|
|
4441
|
+
const steps = [...manualSteps, ...shebangSteps(text, scripts, locate)];
|
|
4442
|
+
return { text: applyTextEdits(text, edits), changes, manualSteps: steps };
|
|
4053
4443
|
}
|
|
4054
4444
|
|
|
4055
4445
|
const ASSIGNMENT_RE = new RegExp(
|
|
@@ -4131,6 +4521,8 @@ function withoutAssignmentHeads(text, assignments) {
|
|
|
4131
4521
|
parts.push(text.slice(copiedUpTo));
|
|
4132
4522
|
return parts.join("");
|
|
4133
4523
|
}
|
|
4524
|
+
const COMPUTED_RE = /^\$[({]/;
|
|
4525
|
+
const isComputed = (written) => COMPUTED_RE.test(written.trim().replace(RECIPE_PREFIX_RE, ""));
|
|
4134
4526
|
function referencedName(written) {
|
|
4135
4527
|
const match = VARIABLE_REFERENCE_RE.exec(written.trim().replace(RECIPE_PREFIX_RE, ""));
|
|
4136
4528
|
return match === null ? void 0 : match[1] ?? match[2] ?? match[3];
|
|
@@ -4162,10 +4554,12 @@ function makefileVariables(text, assignments, programs) {
|
|
|
4162
4554
|
const nameAt = (word) => referencedName(text.slice(word.start, word.end));
|
|
4163
4555
|
const variables = {
|
|
4164
4556
|
programAt: (program) => {
|
|
4165
|
-
const name = nameAt(program);
|
|
4166
|
-
const found = name === void 0 ? void 0 : programs.get(name) ?? "generator-or-other";
|
|
4167
4557
|
const written = text.slice(program.start, program.end);
|
|
4168
|
-
|
|
4558
|
+
const name = nameAt(program);
|
|
4559
|
+
if (name === void 0) {
|
|
4560
|
+
return isComputed(written) ? { written, program: "generator-or-other" } : void 0;
|
|
4561
|
+
}
|
|
4562
|
+
return { written, program: programs.get(name) ?? "generator-or-other" };
|
|
4169
4563
|
},
|
|
4170
4564
|
referenceAt: (word) => {
|
|
4171
4565
|
const name = nameAt(word);
|
|
@@ -4527,73 +4921,6 @@ function oldPackagePatchSteps(fileName) {
|
|
|
4527
4921
|
return name === void 0 || to === void 0 ? [] : [{ kind: "old-package-patch", name, to }];
|
|
4528
4922
|
}
|
|
4529
4923
|
|
|
4530
|
-
const STEP_SHELL_KEY = "shell";
|
|
4531
|
-
const DEFAULT_SHELL_PATH = ["defaults", "run", STEP_SHELL_KEY];
|
|
4532
|
-
const COMMAND_LINE_SHELLS = /* @__PURE__ */ new Set([
|
|
4533
|
-
...["sh", "bash", "dash", "ash", "zsh", "ksh"],
|
|
4534
|
-
...["pwsh", "powershell", "cmd"],
|
|
4535
|
-
...["msys2", "wsl-bash"]
|
|
4536
|
-
]);
|
|
4537
|
-
const ENV_PROGRAM = "env";
|
|
4538
|
-
const ENV_OPTIONS_WITH_VALUE = /* @__PURE__ */ new Set(["-u", "--unset", "-C", "--chdir"]);
|
|
4539
|
-
const OPTION_START = "-";
|
|
4540
|
-
const ASSIGNMENT = "=";
|
|
4541
|
-
const PATH_SEPARATOR_RE = /[/\\]/;
|
|
4542
|
-
const WINDOWS_EXECUTABLE_RE = /\.exe$/i;
|
|
4543
|
-
const WHITESPACE_RE = /\s+/;
|
|
4544
|
-
const programName = (word) => (word.split(PATH_SEPARATOR_RE).at(-1) ?? "").replace(WINDOWS_EXECUTABLE_RE, "");
|
|
4545
|
-
function envPrefixLength(words) {
|
|
4546
|
-
let index = 0;
|
|
4547
|
-
while (index < words.length) {
|
|
4548
|
-
const word = words[index] ?? "";
|
|
4549
|
-
if (ENV_OPTIONS_WITH_VALUE.has(word)) {
|
|
4550
|
-
index += 2;
|
|
4551
|
-
} else if (word.startsWith(OPTION_START) || word.includes(ASSIGNMENT)) {
|
|
4552
|
-
index += 1;
|
|
4553
|
-
} else {
|
|
4554
|
-
break;
|
|
4555
|
-
}
|
|
4556
|
-
}
|
|
4557
|
-
return index;
|
|
4558
|
-
}
|
|
4559
|
-
function shellProgram(shell) {
|
|
4560
|
-
const words = shell.trim().split(WHITESPACE_RE);
|
|
4561
|
-
const first = programName(words[0] ?? "");
|
|
4562
|
-
if (first !== ENV_PROGRAM) {
|
|
4563
|
-
return first;
|
|
4564
|
-
}
|
|
4565
|
-
const rest = words.slice(1);
|
|
4566
|
-
return programName(rest[envPrefixLength(rest)] ?? "");
|
|
4567
|
-
}
|
|
4568
|
-
const endsWith = (path, keys) => keys.every((key, index) => path[path.length - keys.length + index] === key);
|
|
4569
|
-
const isDefaultShell = (value) => endsWith(value.path, DEFAULT_SHELL_PATH);
|
|
4570
|
-
function scopeOf(value) {
|
|
4571
|
-
const keys = isDefaultShell(value) ? DEFAULT_SHELL_PATH.length : 1;
|
|
4572
|
-
return JSON.stringify(value.path.slice(0, -keys));
|
|
4573
|
-
}
|
|
4574
|
-
function stepShells(strings) {
|
|
4575
|
-
const shells = strings.filter((value) => value.path.at(-1) === STEP_SHELL_KEY);
|
|
4576
|
-
const isOwnShell = (value) => !isDefaultShell(value);
|
|
4577
|
-
const ordered = [...shells.filter(isDefaultShell), ...shells.filter(isOwnShell)];
|
|
4578
|
-
const byScope = new Map(ordered.map((value) => [scopeOf(value), value.value]));
|
|
4579
|
-
return (value) => {
|
|
4580
|
-
for (let depth = value.path.length - 1; depth >= 0; depth -= 1) {
|
|
4581
|
-
const shell = byScope.get(JSON.stringify(value.path.slice(0, depth)));
|
|
4582
|
-
if (shell !== void 0) {
|
|
4583
|
-
return COMMAND_LINE_SHELLS.has(shellProgram(shell)) ? void 0 : shell;
|
|
4584
|
-
}
|
|
4585
|
-
}
|
|
4586
|
-
return void 0;
|
|
4587
|
-
};
|
|
4588
|
-
}
|
|
4589
|
-
function otherShellFindings(values, shellOf, locate) {
|
|
4590
|
-
const manualSteps = values.flatMap((value) => {
|
|
4591
|
-
const shell = shellOf(value);
|
|
4592
|
-
return shell === void 0 || !namesOriginalPackage(value.value) ? [] : [{ kind: "script-not-shell", location: locate(value.start), shell }];
|
|
4593
|
-
});
|
|
4594
|
-
return { edits: [], changes: [], manualSteps };
|
|
4595
|
-
}
|
|
4596
|
-
|
|
4597
4924
|
const YAML_COMMAND_KEYS = /* @__PURE__ */ new Set([
|
|
4598
4925
|
"run",
|
|
4599
4926
|
"script",
|
|
@@ -4620,6 +4947,10 @@ const TASKFILE_COMMAND_KEYS = /* @__PURE__ */ new Set([
|
|
|
4620
4947
|
|
|
4621
4948
|
const ENVIRONMENT_KEYS = /* @__PURE__ */ new Set(["env"]);
|
|
4622
4949
|
const isEnvironmentValue = (value) => value.path.length >= 2 && ENVIRONMENT_KEYS.has(String(value.path.at(-2)));
|
|
4950
|
+
const INPUTS_KEY = "inputs";
|
|
4951
|
+
const DEFAULT_KEY = "default";
|
|
4952
|
+
const INPUT_DEFAULT_DEPTH = 3;
|
|
4953
|
+
const isInputDefault = (value) => value.path.length >= INPUT_DEFAULT_DEPTH && value.path.at(-1) === DEFAULT_KEY && value.path.at(-INPUT_DEFAULT_DEPTH) === INPUTS_KEY;
|
|
4623
4954
|
function environmentVariables(text, strings) {
|
|
4624
4955
|
return shellVariables(
|
|
4625
4956
|
strings.filter(isEnvironmentValue).map((value) => ({
|
|
@@ -4703,7 +5034,7 @@ function migrateYamlCommands(text, file, strings, keys = YAML_COMMAND_KEYS) {
|
|
|
4703
5034
|
const values = valuesOf(strings, keys).filter((value) => shellOf(value) === void 0);
|
|
4704
5035
|
const commandValues = new Set(values.filter((value) => !isExecFormItem(value)));
|
|
4705
5036
|
const shellValues = strings.filter(
|
|
4706
|
-
(value) => commandValues.has(value) || isEnvironmentValue(value)
|
|
5037
|
+
(value) => commandValues.has(value) || isEnvironmentValue(value) || isInputDefault(value)
|
|
4707
5038
|
);
|
|
4708
5039
|
const variables = environmentVariables(text, strings);
|
|
4709
5040
|
return appliedFindings(text, [
|
|
@@ -4927,6 +5258,28 @@ const JSON5_MANIFEST_FILE = "package.json5";
|
|
|
4927
5258
|
const MANIFEST_FILE = "package.json";
|
|
4928
5259
|
const PROJECT_CONFIG_NAME = /^[jt]sconfig(\.[^/]+)?\.json$/;
|
|
4929
5260
|
const LOCK_FILES = /* @__PURE__ */ new Set(["pnpm-lock.yaml", "yarn.lock", "bun.lock"]);
|
|
5261
|
+
const CI_CONFIG_FILES = /* @__PURE__ */ new Set([
|
|
5262
|
+
".gitlab-ci.yml",
|
|
5263
|
+
".gitlab-ci.yaml",
|
|
5264
|
+
"bitbucket-pipelines.yml",
|
|
5265
|
+
"azure-pipelines.yml",
|
|
5266
|
+
".drone.yml",
|
|
5267
|
+
".woodpecker.yml",
|
|
5268
|
+
".travis.yml"
|
|
5269
|
+
]);
|
|
5270
|
+
const CI_CONFIG_FOLDERS = [
|
|
5271
|
+
".github/workflows/",
|
|
5272
|
+
".gitea/workflows/",
|
|
5273
|
+
".forgejo/workflows/",
|
|
5274
|
+
".circleci/",
|
|
5275
|
+
".gitlab/",
|
|
5276
|
+
".woodpecker/"
|
|
5277
|
+
];
|
|
5278
|
+
const FOLDER_SEPARATOR = "/";
|
|
5279
|
+
function isCiConfigFile(path) {
|
|
5280
|
+
const inFolder = (folder) => path.startsWith(folder) || path.includes(`${FOLDER_SEPARATOR}${folder}`);
|
|
5281
|
+
return CI_CONFIG_FILES.has(baseName(path)) || CI_CONFIG_FOLDERS.some(inFolder);
|
|
5282
|
+
}
|
|
4930
5283
|
function baseName(path) {
|
|
4931
5284
|
return path.slice(path.lastIndexOf("/") + 1);
|
|
4932
5285
|
}
|
|
@@ -5027,7 +5380,7 @@ const COMMAND_CONFIG_KINDS = [
|
|
|
5027
5380
|
)
|
|
5028
5381
|
}
|
|
5029
5382
|
];
|
|
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);
|
|
5383
|
+
const yamlCommandsStep = (path, isPart) => (text, read) => isPart || !isCiConfigFile(path) && read.kind === "read" && isApiDescription(read.documents) ? unchanged(text) : stringsStep((current, strings) => migrateYamlCommands(current, path, strings))(text, read);
|
|
5031
5384
|
const manifestTarget = ({ version, catalogs, aliases }, path) => ({
|
|
5032
5385
|
version,
|
|
5033
5386
|
catalogs,
|
|
@@ -5139,7 +5492,7 @@ function fileMigrationFor(path, namedImportMaps = NO_NAMED_IMPORT_MAPS) {
|
|
|
5139
5492
|
const NO_API_DESCRIPTION_PARTS = /* @__PURE__ */ new Set();
|
|
5140
5493
|
const JSON_EXTENSION = ".json";
|
|
5141
5494
|
const isYaml = (path) => YAML_EXTENSIONS.has(extensionOf(path));
|
|
5142
|
-
const isDescriptionFormat = (path) => isYaml(path) || extensionOf(path) === JSON_EXTENSION;
|
|
5495
|
+
const isDescriptionFormat = (path) => (isYaml(path) || extensionOf(path) === JSON_EXTENSION) && !isCiConfigFile(path);
|
|
5143
5496
|
async function textOf(path, files) {
|
|
5144
5497
|
const read = await files.read(path);
|
|
5145
5498
|
return read.kind === "read" ? read.text : void 0;
|
|
@@ -5275,6 +5628,16 @@ async function planProjectTarget(paths, dependencies, version) {
|
|
|
5275
5628
|
};
|
|
5276
5629
|
}
|
|
5277
5630
|
|
|
5631
|
+
function attemptMigration(migrate) {
|
|
5632
|
+
try {
|
|
5633
|
+
return { kind: "migrated", migration: migrate() };
|
|
5634
|
+
} catch (error) {
|
|
5635
|
+
if (error instanceof MigrationBugError) {
|
|
5636
|
+
return { kind: "skipped", reason: { kind: "migration-bug", message: error.message } };
|
|
5637
|
+
}
|
|
5638
|
+
throw error;
|
|
5639
|
+
}
|
|
5640
|
+
}
|
|
5278
5641
|
async function planFile(path, dependencies, target) {
|
|
5279
5642
|
const migrate = fileMigrationFor(path, target.importMaps);
|
|
5280
5643
|
if (migrate === void 0) {
|
|
@@ -5290,7 +5653,11 @@ async function planFile(path, dependencies, target) {
|
|
|
5290
5653
|
...{ version, catalogs, manifests, aliases, apiDescriptionParts },
|
|
5291
5654
|
...{ rewriter, scanner, readers }
|
|
5292
5655
|
};
|
|
5293
|
-
const
|
|
5656
|
+
const attempt = attemptMigration(() => migrate(read.text, path, context));
|
|
5657
|
+
if (attempt.kind === "skipped") {
|
|
5658
|
+
return attempt;
|
|
5659
|
+
}
|
|
5660
|
+
const { migration } = attempt;
|
|
5294
5661
|
const isChanged = migration.text !== read.text;
|
|
5295
5662
|
const { changes, manualSteps } = migration;
|
|
5296
5663
|
if (isChanged || manualSteps.length > 0) {
|
|
@@ -5418,7 +5785,7 @@ function commandStepMessage(step) {
|
|
|
5418
5785
|
case "old-name-in-command":
|
|
5419
5786
|
return `${step.location}: "${step.word}" ${OLD_NAME_IN_COMMAND_NOTE}`;
|
|
5420
5787
|
case "script-not-shell":
|
|
5421
|
-
return `${step.location}: the
|
|
5788
|
+
return `${step.location}: the script runs with ${step.shell}, ${SCRIPT_NOT_SHELL_NOTE}`;
|
|
5422
5789
|
case "command-string":
|
|
5423
5790
|
return `line ${step.line}: ${COMMAND_STRING_NOTE} "${step.to}"`;
|
|
5424
5791
|
default:
|
|
@@ -5434,9 +5801,12 @@ const KEEP_REASONS = {
|
|
|
5434
5801
|
"shared-options": "its options object is used elsewhere too",
|
|
5435
5802
|
"unknown-return": "a return value is not a plain ts.factory type",
|
|
5436
5803
|
"typed-options": "its options are declared with a type built from OpenAPITSOptions, which the tool cannot change to OpenAPITSIrOptions",
|
|
5437
|
-
"spread-options": "its options take keys from another object (`...base`), whose callbacks and type the tool cannot see"
|
|
5804
|
+
"spread-options": "its options take keys from another object (`...base`), whose callbacks and type the tool cannot see",
|
|
5805
|
+
"declared-type": "its declared type (a `const` type, JSDoc `@type` or `@returns`) names a type the tool cannot change to type text",
|
|
5806
|
+
"in-spread": "it is in an object that the options take keys from (`...base`), and the tool rewrites only a transform written in the options"
|
|
5438
5807
|
};
|
|
5439
5808
|
const OPTIONS_NOTE = `the options of openapiTS() are not an object written in this file: check them for transform, postTransform and transformProperty. See ${IR_CALLBACKS_GUIDE}`;
|
|
5809
|
+
const SPREAD_NOTE = `the options of openapiTS() take keys from an object the tool cannot read (\`...base\`): check it for transform, postTransform and transformProperty. See ${IR_CALLBACKS_GUIDE}`;
|
|
5440
5810
|
const DEEP_IMPORT_NOTE = "imports a file inside the package; check that it exists";
|
|
5441
5811
|
const OLD_FILE_NOTE = "imports a file from node_modules of";
|
|
5442
5812
|
const OLD_FILE_FIX_NOTE = "which is no longer installed: import from";
|
|
@@ -5452,6 +5822,7 @@ function packageNotDeclaredMessage(step) {
|
|
|
5452
5822
|
}
|
|
5453
5823
|
const SOURCE_STEP_KINDS = /* @__PURE__ */ new Set([
|
|
5454
5824
|
"options-not-inspected",
|
|
5825
|
+
"options-spread-not-inspected",
|
|
5455
5826
|
"deep-import",
|
|
5456
5827
|
"old-package-import",
|
|
5457
5828
|
"dynamic-specifier",
|
|
@@ -5465,6 +5836,8 @@ function sourceStepMessage(step) {
|
|
|
5465
5836
|
switch (step.kind) {
|
|
5466
5837
|
case "options-not-inspected":
|
|
5467
5838
|
return `line ${step.line}: ${OPTIONS_NOTE}`;
|
|
5839
|
+
case "options-spread-not-inspected":
|
|
5840
|
+
return `line ${step.line}: ${SPREAD_NOTE}`;
|
|
5468
5841
|
case "deep-import":
|
|
5469
5842
|
return `line ${step.line}: "${step.specifier}" ${DEEP_IMPORT_NOTE}`;
|
|
5470
5843
|
case "old-package-import":
|
|
@@ -5619,7 +5992,7 @@ function changeMessage(change) {
|
|
|
5619
5992
|
return assertNever(change);
|
|
5620
5993
|
}
|
|
5621
5994
|
}
|
|
5622
|
-
const SKIPPED_HEADING = "Skipped (not
|
|
5995
|
+
const SKIPPED_HEADING = "Skipped (not migrated, not written):";
|
|
5623
5996
|
function skippedMessage(reason) {
|
|
5624
5997
|
switch (reason.kind) {
|
|
5625
5998
|
case "outside-root":
|
|
@@ -5628,6 +6001,8 @@ function skippedMessage(reason) {
|
|
|
5628
6001
|
return "it is not UTF-8 text, so it was not changed; check it by hand";
|
|
5629
6002
|
case "io-error":
|
|
5630
6003
|
return `it could not be read (${reason.code})`;
|
|
6004
|
+
case "migration-bug":
|
|
6005
|
+
return `the tool could not migrate it (${reason.message}), a bug of the tool: it was not changed, so migrate it by hand, and please report the bug`;
|
|
5631
6006
|
default:
|
|
5632
6007
|
return assertNever(reason);
|
|
5633
6008
|
}
|
|
@@ -5814,7 +6189,8 @@ async function migrateRoot(request, root, logger, dependencies) {
|
|
|
5814
6189
|
const input = { version: dependencies.version, isWrite: request.isWrite };
|
|
5815
6190
|
const migration = await migrateProject({ files, rewriter, scanner, readers, logger }, input);
|
|
5816
6191
|
dependencies.writeStdout(renderReport(migration));
|
|
5817
|
-
|
|
6192
|
+
const hasBug = migration.skipped.some((file) => file.reason.kind === "migration-bug");
|
|
6193
|
+
return migration.write.kind === "failed" || hasBug ? EXIT_CODES.failure : EXIT_CODES.success;
|
|
5818
6194
|
}
|
|
5819
6195
|
async function runMigration(request, dependencies) {
|
|
5820
6196
|
const log = openLog(request, dependencies);
|