openapi-typesafe-migrate 1.0.0-rc.2 → 1.0.0-rc.4
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 +34 -0
- package/README.md +88 -21
- package/dist/cli/main.mjs +2917 -486
- package/dist/cli/main.mjs.map +1 -1
- package/package.json +5 -3
package/dist/cli/main.mjs
CHANGED
|
@@ -1,11 +1,157 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { constants, mkdtempSync, mkdirSync, appendFileSync, openSync, fstatSync, closeSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
|
-
import { join, dirname,
|
|
3
|
+
import { win32, posix, join, dirname, isAbsolute, relative, sep, basename, resolve } from 'node:path';
|
|
4
|
+
import { realpath, lstat, stat, access, readFile, writeFile, chmod, rename, rm, readdir } from 'node:fs/promises';
|
|
4
5
|
import { spawn } from 'node:child_process';
|
|
5
|
-
import { realpath, readFile, lstat, writeFile, chmod, rename, rm, readdir } from 'node:fs/promises';
|
|
6
6
|
import ts from '@typescript/typescript6';
|
|
7
|
+
import { Scalar, parseAllDocuments, isScalar as isScalar$1, isSeq, isMap as isMap$1, isNode, CST } from 'yaml';
|
|
7
8
|
import { parseArgs } from 'node:util';
|
|
8
9
|
|
|
10
|
+
const WINDOWS = "win32";
|
|
11
|
+
const PATH_VARIABLE = "PATH";
|
|
12
|
+
const GIT_NAME = "git";
|
|
13
|
+
const WINDOWS_EXECUTABLE_EXTENSION = ".exe";
|
|
14
|
+
const GIT_FOLDER = ".git";
|
|
15
|
+
const PARENT_FOLDER = "..";
|
|
16
|
+
const PACKAGES_FOLDER = "node_modules";
|
|
17
|
+
const ABSENT_CODES = /* @__PURE__ */ new Set(["ENOENT", "ENOTDIR", "EACCES", "EPERM"]);
|
|
18
|
+
function isAbsent(error) {
|
|
19
|
+
return ABSENT_CODES.has(error?.code ?? "");
|
|
20
|
+
}
|
|
21
|
+
async function unlessAbsent(check, absent) {
|
|
22
|
+
try {
|
|
23
|
+
return await check();
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (isAbsent(error)) {
|
|
26
|
+
return absent;
|
|
27
|
+
}
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const pathsOf = (platform) => platform === WINDOWS ? win32 : posix;
|
|
32
|
+
function pathVariable({ environment, platform }) {
|
|
33
|
+
const name = platform === WINDOWS ? Object.keys(environment).find((key) => key.toUpperCase() === PATH_VARIABLE) : PATH_VARIABLE;
|
|
34
|
+
return name === void 0 ? "" : environment[name] ?? "";
|
|
35
|
+
}
|
|
36
|
+
function isAtOrUnder(paths, folder, target) {
|
|
37
|
+
const path = paths.relative(folder, target);
|
|
38
|
+
return !(path === PARENT_FOLDER || path.startsWith(`${PARENT_FOLDER}${paths.sep}`)) && !paths.isAbsolute(path);
|
|
39
|
+
}
|
|
40
|
+
function isInPackagesFolder(host, path) {
|
|
41
|
+
const separator = host.platform === WINDOWS ? /[\\/]/ : posix.sep;
|
|
42
|
+
return path.split(separator).some((segment) => segment.toLowerCase() === PACKAGES_FOLDER);
|
|
43
|
+
}
|
|
44
|
+
async function isTrustedFolder(host, project, folder) {
|
|
45
|
+
const paths = pathsOf(host.platform);
|
|
46
|
+
if (!paths.isAbsolute(folder) || isInPackagesFolder(host, folder)) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
const real = await host.fileSystem.realPath(folder);
|
|
50
|
+
return real !== void 0 && !isAtOrUnder(paths, project, real) && !isInPackagesFolder(host, real);
|
|
51
|
+
}
|
|
52
|
+
async function findGitExecutable(host) {
|
|
53
|
+
const paths = pathsOf(host.platform);
|
|
54
|
+
const name = host.platform === WINDOWS ? `${GIT_NAME}${WINDOWS_EXECUTABLE_EXTENSION}` : GIT_NAME;
|
|
55
|
+
const root = paths.resolve(host.projectRoot);
|
|
56
|
+
const project = await host.fileSystem.realPath(root) ?? root;
|
|
57
|
+
for (const folder of pathVariable(host).split(paths.delimiter)) {
|
|
58
|
+
const candidate = paths.join(folder, name);
|
|
59
|
+
const isTrusted = await isTrustedFolder(host, project, folder);
|
|
60
|
+
if (isTrusted && await host.fileSystem.isExecutableFile(candidate)) {
|
|
61
|
+
return candidate;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return void 0;
|
|
65
|
+
}
|
|
66
|
+
async function findWorkTree(directory, { platform, fileSystem }, isRepository) {
|
|
67
|
+
const paths = pathsOf(platform);
|
|
68
|
+
let folder = paths.resolve(directory);
|
|
69
|
+
for (; ; ) {
|
|
70
|
+
const parent = paths.dirname(folder);
|
|
71
|
+
const hasGit = await fileSystem.hasEntry(paths.join(folder, GIT_FOLDER));
|
|
72
|
+
if (hasGit && await isRepository(folder, parent)) {
|
|
73
|
+
return folder;
|
|
74
|
+
}
|
|
75
|
+
if (parent === folder) {
|
|
76
|
+
return void 0;
|
|
77
|
+
}
|
|
78
|
+
folder = parent;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function createNodeGitFileSystem() {
|
|
82
|
+
return {
|
|
83
|
+
isExecutableFile: (path) => unlessAbsent(async () => {
|
|
84
|
+
const isFile = (await stat(path)).isFile();
|
|
85
|
+
await access(path, constants.X_OK);
|
|
86
|
+
return isFile;
|
|
87
|
+
}, false),
|
|
88
|
+
hasEntry: (path) => unlessAbsent(async () => {
|
|
89
|
+
await lstat(path);
|
|
90
|
+
return true;
|
|
91
|
+
}, false),
|
|
92
|
+
realPath: (path) => unlessAbsent(() => realpath(path), void 0)
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const spawnGitProcess = ({ executable, directory, gitArguments, environment }) => new Promise((resolve) => {
|
|
97
|
+
const child = spawn(executable, [...gitArguments], { cwd: directory, env: environment });
|
|
98
|
+
const output = [];
|
|
99
|
+
const errors = [];
|
|
100
|
+
child.stdout.on("data", (chunk) => output.push(chunk));
|
|
101
|
+
child.stderr.on("data", (chunk) => errors.push(chunk));
|
|
102
|
+
child.on("error", (error) => resolve({ kind: "failed", reason: error.message }));
|
|
103
|
+
child.on("close", (exitCode) => {
|
|
104
|
+
resolve({
|
|
105
|
+
kind: "exited",
|
|
106
|
+
exitCode,
|
|
107
|
+
output: Buffer.concat(output).toString("utf8"),
|
|
108
|
+
errors: Buffer.concat(errors).toString("utf8").trim()
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
const HARDENED_CONFIG = [
|
|
113
|
+
"-c",
|
|
114
|
+
"core.fsmonitor=false",
|
|
115
|
+
"-c",
|
|
116
|
+
"protocol.allow=never",
|
|
117
|
+
"-c",
|
|
118
|
+
"safe.bareRepository=explicit"
|
|
119
|
+
];
|
|
120
|
+
const HARDENED_ENVIRONMENT = {
|
|
121
|
+
GIT_ALLOW_PROTOCOL: "",
|
|
122
|
+
GIT_NO_LAZY_FETCH: "1",
|
|
123
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
124
|
+
};
|
|
125
|
+
const REPOSITORY_VARIABLES = [
|
|
126
|
+
"GIT_DIR",
|
|
127
|
+
"GIT_WORK_TREE",
|
|
128
|
+
"GIT_COMMON_DIR",
|
|
129
|
+
"GIT_INDEX_FILE",
|
|
130
|
+
"GIT_OBJECT_DIRECTORY",
|
|
131
|
+
"GIT_ALTERNATE_OBJECT_DIRECTORIES"
|
|
132
|
+
];
|
|
133
|
+
const WORK_TREE_VARIABLE = "GIT_WORK_TREE";
|
|
134
|
+
const CEILING_VARIABLE = "GIT_CEILING_DIRECTORIES";
|
|
135
|
+
function hardenedEnvironment(command) {
|
|
136
|
+
const kept = Object.entries(command.environment).filter(
|
|
137
|
+
([name]) => !REPOSITORY_VARIABLES.includes(name.toUpperCase())
|
|
138
|
+
);
|
|
139
|
+
const { workTree, ceiling } = command;
|
|
140
|
+
const forced = {
|
|
141
|
+
...workTree === void 0 ? {} : { [WORK_TREE_VARIABLE]: workTree },
|
|
142
|
+
...ceiling === void 0 ? {} : { [CEILING_VARIABLE]: ceiling }
|
|
143
|
+
};
|
|
144
|
+
return { ...Object.fromEntries(kept), ...forced, ...HARDENED_ENVIRONMENT };
|
|
145
|
+
}
|
|
146
|
+
function hardenedInvocation(command, gitArguments) {
|
|
147
|
+
return {
|
|
148
|
+
executable: command.executable,
|
|
149
|
+
directory: command.directory,
|
|
150
|
+
gitArguments: [...HARDENED_CONFIG, ...gitArguments],
|
|
151
|
+
environment: hardenedEnvironment(command)
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
9
155
|
const LS_FILES_ARGUMENTS = [
|
|
10
156
|
"ls-files",
|
|
11
157
|
"--cached",
|
|
@@ -15,36 +161,13 @@ const LS_FILES_ARGUMENTS = [
|
|
|
15
161
|
"-z"
|
|
16
162
|
];
|
|
17
163
|
const CHECK_IGNORE_ARGUMENTS = ["check-ignore", "--quiet", "."];
|
|
164
|
+
const REPOSITORY_CHECK_ARGUMENTS = ["rev-parse", "--git-dir"];
|
|
18
165
|
const IGNORED_EXIT_CODE = 0;
|
|
19
166
|
const NOT_IGNORED_EXIT_CODE = 1;
|
|
20
167
|
const IGNORED_REASON = "the git repository around the folder ignores it";
|
|
21
|
-
const
|
|
22
|
-
const HARDENED_CONFIG = [
|
|
23
|
-
"-c",
|
|
24
|
-
"core.fsmonitor=false",
|
|
25
|
-
"-c",
|
|
26
|
-
"safe.bareRepository=explicit"
|
|
27
|
-
];
|
|
168
|
+
const NO_GIT_REASON = "no git executable in an absolute folder of PATH";
|
|
28
169
|
const TRAILING_SLASH_RE = /\/$/;
|
|
29
170
|
const SELF_PATHS = /* @__PURE__ */ new Set(["", "."]);
|
|
30
|
-
function runGit(directory, gitArguments) {
|
|
31
|
-
return new Promise((resolve) => {
|
|
32
|
-
const child = spawn(GIT_COMMAND, [...HARDENED_CONFIG, ...gitArguments], { cwd: directory });
|
|
33
|
-
const output = [];
|
|
34
|
-
const errors = [];
|
|
35
|
-
child.stdout.on("data", (chunk) => output.push(chunk));
|
|
36
|
-
child.stderr.on("data", (chunk) => errors.push(chunk));
|
|
37
|
-
child.on("error", (error) => resolve({ kind: "failed", reason: error.message }));
|
|
38
|
-
child.on("close", (exitCode) => {
|
|
39
|
-
resolve({
|
|
40
|
-
kind: "exited",
|
|
41
|
-
exitCode,
|
|
42
|
-
output: Buffer.concat(output).toString("utf8"),
|
|
43
|
-
errors: Buffer.concat(errors).toString("utf8").trim()
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
171
|
function failureReason(run) {
|
|
49
172
|
if (run.kind === "failed") {
|
|
50
173
|
return run.reason;
|
|
@@ -54,27 +177,47 @@ function failureReason(run) {
|
|
|
54
177
|
function listedPaths$1(output) {
|
|
55
178
|
return output.split("\0").map((path) => path.replace(TRAILING_SLASH_RE, "")).filter((path) => !SELF_PATHS.has(path));
|
|
56
179
|
}
|
|
57
|
-
async function
|
|
58
|
-
const ignored = await
|
|
180
|
+
async function listWith(command, run) {
|
|
181
|
+
const ignored = await run(hardenedInvocation(command, CHECK_IGNORE_ARGUMENTS));
|
|
59
182
|
if (ignored.kind === "exited" && ignored.exitCode === IGNORED_EXIT_CODE) {
|
|
60
183
|
return { kind: "unavailable", reason: IGNORED_REASON };
|
|
61
184
|
}
|
|
62
185
|
if (ignored.kind === "failed" || ignored.exitCode !== NOT_IGNORED_EXIT_CODE) {
|
|
63
186
|
return { kind: "unavailable", reason: failureReason(ignored) };
|
|
64
187
|
}
|
|
65
|
-
const listed = await
|
|
188
|
+
const listed = await run(hardenedInvocation(command, LS_FILES_ARGUMENTS));
|
|
66
189
|
if (listed.kind === "failed" || listed.exitCode !== 0) {
|
|
67
190
|
return { kind: "unavailable", reason: failureReason(listed) };
|
|
68
191
|
}
|
|
69
192
|
return { kind: "listed", paths: listedPaths$1(listed.output) };
|
|
70
193
|
}
|
|
71
|
-
class
|
|
72
|
-
|
|
73
|
-
|
|
194
|
+
class HardenedGitFileList {
|
|
195
|
+
#host;
|
|
196
|
+
#process;
|
|
197
|
+
/** PATH is searched once, on the first listing. */
|
|
198
|
+
#executable;
|
|
199
|
+
constructor(options) {
|
|
200
|
+
this.#host = options;
|
|
201
|
+
this.#process = options.process ?? spawnGitProcess;
|
|
202
|
+
}
|
|
203
|
+
async list(directory) {
|
|
204
|
+
this.#executable ??= findGitExecutable(this.#host);
|
|
205
|
+
const executable = await this.#executable;
|
|
206
|
+
if (executable === void 0) {
|
|
207
|
+
return { kind: "unavailable", reason: NO_GIT_REASON };
|
|
208
|
+
}
|
|
209
|
+
const { environment } = this.#host;
|
|
210
|
+
const isRepository = async (folder, ceiling) => {
|
|
211
|
+
const command = { executable, directory: folder, environment, workTree: void 0, ceiling };
|
|
212
|
+
const run = await this.#process(hardenedInvocation(command, REPOSITORY_CHECK_ARGUMENTS));
|
|
213
|
+
return run.kind === "exited" && run.exitCode === 0;
|
|
214
|
+
};
|
|
215
|
+
const workTree = await findWorkTree(directory, this.#host, isRepository);
|
|
216
|
+
return listWith({ executable, directory, environment, workTree }, this.#process);
|
|
74
217
|
}
|
|
75
218
|
}
|
|
76
|
-
function createGitFileList() {
|
|
77
|
-
return new
|
|
219
|
+
function createGitFileList(options) {
|
|
220
|
+
return new HardenedGitFileList(options);
|
|
78
221
|
}
|
|
79
222
|
|
|
80
223
|
const LOG_FILE_MODE = 384;
|
|
@@ -82,15 +225,39 @@ const TEMPORARY_FOLDER_PREFIX = "openapi-typesafe-migrate-";
|
|
|
82
225
|
const LOG_EXTENSION = ".jsonl";
|
|
83
226
|
const TIME_SEPARATOR = ":";
|
|
84
227
|
const FILE_NAME_TIME_SEPARATOR = "-";
|
|
228
|
+
class LogPathIsFolderError extends Error {
|
|
229
|
+
name = "LogPathIsFolderError";
|
|
230
|
+
constructor(path) {
|
|
231
|
+
super(`${path} is a folder; --log takes a file path`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const EXCLUSIVE_CREATE_FLAG = "wx";
|
|
235
|
+
const APPEND_FLAG = "a";
|
|
236
|
+
const IS_A_DIRECTORY_CODE = "EISDIR";
|
|
237
|
+
function openLogFile(path, isExclusive) {
|
|
238
|
+
let descriptor;
|
|
239
|
+
try {
|
|
240
|
+
descriptor = openSync(path, isExclusive ? EXCLUSIVE_CREATE_FLAG : APPEND_FLAG, LOG_FILE_MODE);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
throw isDirectoryError(error) ? new LogPathIsFolderError(path) : error;
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
if (fstatSync(descriptor).isDirectory()) {
|
|
246
|
+
throw new LogPathIsFolderError(path);
|
|
247
|
+
}
|
|
248
|
+
} finally {
|
|
249
|
+
closeSync(descriptor);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const isDirectoryError = (error) => error instanceof Error && "code" in error && error.code === IS_A_DIRECTORY_CODE;
|
|
85
253
|
class JsonLinesLogger {
|
|
86
254
|
#options;
|
|
87
255
|
constructor(options) {
|
|
88
256
|
this.#options = options;
|
|
89
|
-
if (options.isExclusive) {
|
|
90
|
-
writeFileSync(options.path, "", { flag: "wx", mode: LOG_FILE_MODE });
|
|
91
|
-
} else {
|
|
257
|
+
if (!options.isExclusive) {
|
|
92
258
|
mkdirSync(dirname(options.path), { recursive: true });
|
|
93
259
|
}
|
|
260
|
+
openLogFile(options.path, options.isExclusive);
|
|
94
261
|
}
|
|
95
262
|
info(event, fields = {}) {
|
|
96
263
|
this.#append("info", event, fields);
|
|
@@ -114,6 +281,101 @@ function createTemporaryLogPath(parent, startTime) {
|
|
|
114
281
|
return join(folder, `${name}${LOG_EXTENSION}`);
|
|
115
282
|
}
|
|
116
283
|
|
|
284
|
+
const FILE_NAME = "document.json";
|
|
285
|
+
const QUOTE_LENGTH = 1;
|
|
286
|
+
const encode = (value) => JSON.stringify(value);
|
|
287
|
+
function spanOf$1(node, file) {
|
|
288
|
+
return { start: node.getStart(file), end: node.end };
|
|
289
|
+
}
|
|
290
|
+
function convertString(node, file) {
|
|
291
|
+
const span = spanOf$1(node, file);
|
|
292
|
+
const content = { start: span.start + QUOTE_LENGTH, end: span.end - QUOTE_LENGTH };
|
|
293
|
+
return { kind: "scalar", value: node.text, content, isFolded: false, encode, ...span };
|
|
294
|
+
}
|
|
295
|
+
function convertObject(node, file) {
|
|
296
|
+
const entries = node.properties.filter(ts.isPropertyAssignment).map(
|
|
297
|
+
(property) => ({
|
|
298
|
+
key: ts.isStringLiteral(property.name) ? convertString(property.name, file) : { kind: "other", ...spanOf$1(property.name, file) },
|
|
299
|
+
value: convert$1(property.initializer, file)
|
|
300
|
+
})
|
|
301
|
+
);
|
|
302
|
+
return { kind: "map", entries, ...spanOf$1(node, file) };
|
|
303
|
+
}
|
|
304
|
+
function convert$1(node, file) {
|
|
305
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
306
|
+
return convertObject(node, file);
|
|
307
|
+
}
|
|
308
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
309
|
+
const items = node.elements.map((item) => convert$1(item, file));
|
|
310
|
+
return { kind: "list", items, ...spanOf$1(node, file) };
|
|
311
|
+
}
|
|
312
|
+
if (ts.isStringLiteral(node)) {
|
|
313
|
+
return convertString(node, file);
|
|
314
|
+
}
|
|
315
|
+
return { kind: "other", ...spanOf$1(node, file) };
|
|
316
|
+
}
|
|
317
|
+
function firstError(text) {
|
|
318
|
+
const { error } = ts.parseConfigFileTextToJson(FILE_NAME, text);
|
|
319
|
+
return error === void 0 ? void 0 : error.start ?? 0;
|
|
320
|
+
}
|
|
321
|
+
function readTree(text) {
|
|
322
|
+
const errorAt = firstError(text);
|
|
323
|
+
if (errorAt !== void 0) {
|
|
324
|
+
return { kind: "unreadable", offset: errorAt };
|
|
325
|
+
}
|
|
326
|
+
const file = ts.parseJsonText(FILE_NAME, text);
|
|
327
|
+
const documents = file.statements.map((statement) => convert$1(statement.expression, file));
|
|
328
|
+
return { kind: "read", documents };
|
|
329
|
+
}
|
|
330
|
+
function read$1(text) {
|
|
331
|
+
try {
|
|
332
|
+
return readTree(text);
|
|
333
|
+
} catch (error) {
|
|
334
|
+
if (error instanceof RangeError) {
|
|
335
|
+
return { kind: "unreadable", offset: void 0 };
|
|
336
|
+
}
|
|
337
|
+
throw error;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function createJsonTextReader() {
|
|
341
|
+
return { read: read$1 };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const PARENT_DIRECTORY = "..";
|
|
345
|
+
const UNRESOLVED_PATH_CODES = /* @__PURE__ */ new Set(["ENOENT", "ENOTDIR", "ELOOP"]);
|
|
346
|
+
function isRelativeDownward(path) {
|
|
347
|
+
return !isAbsolute(path) && !path.split("/").includes(PARENT_DIRECTORY);
|
|
348
|
+
}
|
|
349
|
+
function isInFolder(folder, target) {
|
|
350
|
+
const path = relative(folder, target);
|
|
351
|
+
return path !== "" && path !== PARENT_DIRECTORY && !path.startsWith(`${PARENT_DIRECTORY}${sep}`) && !isAbsolute(path);
|
|
352
|
+
}
|
|
353
|
+
async function entryAt(root, realRoot, path) {
|
|
354
|
+
if (!isRelativeDownward(path)) {
|
|
355
|
+
return "outside";
|
|
356
|
+
}
|
|
357
|
+
try {
|
|
358
|
+
const stats = await lstat(join(root, path));
|
|
359
|
+
const isListable = stats.isFile() || stats.isDirectory();
|
|
360
|
+
if (!isListable && !stats.isSymbolicLink()) {
|
|
361
|
+
return "other";
|
|
362
|
+
}
|
|
363
|
+
const realPath = await realpath(join(root, path));
|
|
364
|
+
if (realPath !== realRoot && !isInFolder(realRoot, realPath)) {
|
|
365
|
+
return "outside";
|
|
366
|
+
}
|
|
367
|
+
if (!isListable) {
|
|
368
|
+
return "other";
|
|
369
|
+
}
|
|
370
|
+
return stats.isFile() ? "file" : "directory";
|
|
371
|
+
} catch (error) {
|
|
372
|
+
if (UNRESOLVED_PATH_CODES.has(error.code ?? "")) {
|
|
373
|
+
return "other";
|
|
374
|
+
}
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
117
379
|
const SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
118
380
|
"node_modules",
|
|
119
381
|
".git",
|
|
@@ -129,15 +391,35 @@ const CURRENT_DIRECTORY = ".";
|
|
|
129
391
|
const TEMPORARY_SUFFIX = ".openapi-typesafe-migrate.tmp";
|
|
130
392
|
const PERMISSION_BITS = 4095;
|
|
131
393
|
const TEMPORARY_MODE = 384;
|
|
132
|
-
const
|
|
394
|
+
const OUTSIDE_ROOT = { kind: "outside-root" };
|
|
395
|
+
const NOT_UTF_8 = { kind: "not-utf-8" };
|
|
396
|
+
const UTF_8 = "utf-8";
|
|
397
|
+
function decodeUtf8(bytes) {
|
|
398
|
+
try {
|
|
399
|
+
const text = new TextDecoder(UTF_8, { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
400
|
+
return { kind: "read", text };
|
|
401
|
+
} catch (error) {
|
|
402
|
+
if (error instanceof TypeError) {
|
|
403
|
+
return { kind: "skipped", reason: NOT_UTF_8 };
|
|
404
|
+
}
|
|
405
|
+
throw error;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
function ioProblem(error) {
|
|
409
|
+
const code = error?.code;
|
|
410
|
+
if (typeof code !== "string") {
|
|
411
|
+
throw error;
|
|
412
|
+
}
|
|
413
|
+
return { kind: "io-error", code };
|
|
414
|
+
}
|
|
133
415
|
function isInSkippedDirectory(path) {
|
|
134
416
|
return path.split("/").slice(0, -1).some((directory) => SKIPPED_DIRECTORIES.has(directory));
|
|
135
417
|
}
|
|
136
|
-
async function listTree(root,
|
|
137
|
-
const entries = await readdir(join(root,
|
|
418
|
+
async function listTree(root, relative) {
|
|
419
|
+
const entries = await readdir(join(root, relative), { withFileTypes: true });
|
|
138
420
|
const nested = await Promise.all(
|
|
139
421
|
entries.map((entry) => {
|
|
140
|
-
const path =
|
|
422
|
+
const path = relative === "" ? entry.name : `${relative}/${entry.name}`;
|
|
141
423
|
if (entry.isDirectory()) {
|
|
142
424
|
return SKIPPED_DIRECTORIES.has(entry.name) ? [] : listTree(root, path);
|
|
143
425
|
}
|
|
@@ -146,33 +428,6 @@ async function listTree(root, relative2) {
|
|
|
146
428
|
);
|
|
147
429
|
return nested.flat();
|
|
148
430
|
}
|
|
149
|
-
function isRelativeDownward(path) {
|
|
150
|
-
return !isAbsolute(path) && !path.split("/").includes(PARENT_DIRECTORY);
|
|
151
|
-
}
|
|
152
|
-
function isInFolder(folder, target) {
|
|
153
|
-
const path = relative(folder, target);
|
|
154
|
-
return path !== "" && path !== PARENT_DIRECTORY && !path.startsWith(`${PARENT_DIRECTORY}${sep}`) && !isAbsolute(path);
|
|
155
|
-
}
|
|
156
|
-
async function entryAt(root, realRoot, path) {
|
|
157
|
-
if (!isRelativeDownward(path)) {
|
|
158
|
-
return "outside";
|
|
159
|
-
}
|
|
160
|
-
try {
|
|
161
|
-
const stats = await lstat(join(root, path));
|
|
162
|
-
if (!stats.isFile() && !stats.isDirectory()) {
|
|
163
|
-
return "other";
|
|
164
|
-
}
|
|
165
|
-
if (!isInFolder(realRoot, await realpath(join(root, path)))) {
|
|
166
|
-
return "outside";
|
|
167
|
-
}
|
|
168
|
-
return stats.isFile() ? "file" : "directory";
|
|
169
|
-
} catch (error) {
|
|
170
|
-
if (error.code === "ENOENT") {
|
|
171
|
-
return "other";
|
|
172
|
-
}
|
|
173
|
-
throw error;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
431
|
async function listedPaths(root, realRoot, paths) {
|
|
177
432
|
const candidates = paths.filter((path) => !isInSkippedDirectory(path));
|
|
178
433
|
const entries = await Promise.all(candidates.map((path) => entryAt(root, realRoot, path)));
|
|
@@ -180,7 +435,7 @@ async function listedPaths(root, realRoot, paths) {
|
|
|
180
435
|
return {
|
|
181
436
|
files: withEntry("file"),
|
|
182
437
|
directories: withEntry("directory").filter((path) => !SKIPPED_DIRECTORIES.has(baseName$1(path))),
|
|
183
|
-
skipped: withEntry("outside").map((path) => ({ path, reason:
|
|
438
|
+
skipped: withEntry("outside").map((path) => ({ path, reason: OUTSIDE_ROOT }))
|
|
184
439
|
};
|
|
185
440
|
}
|
|
186
441
|
function baseName$1(path) {
|
|
@@ -240,15 +495,31 @@ class NodeProjectFiles {
|
|
|
240
495
|
return joinLists([{ files, skipped }, ...nested]);
|
|
241
496
|
}
|
|
242
497
|
async read(path) {
|
|
243
|
-
|
|
498
|
+
try {
|
|
499
|
+
const target = await this.#pathInRoot(path);
|
|
500
|
+
return target === void 0 ? { kind: "skipped", reason: OUTSIDE_ROOT } : decodeUtf8(await readFile(target));
|
|
501
|
+
} catch (error) {
|
|
502
|
+
return { kind: "skipped", reason: ioProblem(error) };
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
async write(path, text) {
|
|
506
|
+
try {
|
|
507
|
+
const target = await this.#pathInRoot(path);
|
|
508
|
+
if (target === void 0) {
|
|
509
|
+
return { kind: "failed", reason: OUTSIDE_ROOT };
|
|
510
|
+
}
|
|
511
|
+
await this.#replace(target, text, path);
|
|
512
|
+
return { kind: "written" };
|
|
513
|
+
} catch (error) {
|
|
514
|
+
return { kind: "failed", reason: ioProblem(error) };
|
|
515
|
+
}
|
|
244
516
|
}
|
|
245
517
|
/**
|
|
246
518
|
* Writes a new file and renames it over the old one, so the write never goes through a link: a
|
|
247
519
|
* hard link to a file outside the root keeps its old text, and is reported in the log. The new
|
|
248
520
|
* file keeps the mode of the old one.
|
|
249
521
|
*/
|
|
250
|
-
async
|
|
251
|
-
const target = await this.#pathInRoot(path);
|
|
522
|
+
async #replace(target, text, path) {
|
|
252
523
|
const { mode, nlink } = await lstat(target);
|
|
253
524
|
const temporary = join(dirname(target), `.${basename(target)}${TEMPORARY_SUFFIX}`);
|
|
254
525
|
await writeFile(temporary, text, { encoding: "utf8", flag: "wx", mode: TEMPORARY_MODE });
|
|
@@ -263,11 +534,11 @@ class NodeProjectFiles {
|
|
|
263
534
|
this.#options.logger.warn("hard_link_separated", { path, linkCount: nlink });
|
|
264
535
|
}
|
|
265
536
|
}
|
|
266
|
-
/** The real path of a project file
|
|
537
|
+
/** The real path of a project file, or undefined for one that resolves outside the root. */
|
|
267
538
|
async #pathInRoot(path) {
|
|
268
539
|
const realPath = isRelativeDownward(path) ? await realpath(join(this.#root, path)) : void 0;
|
|
269
540
|
if (realPath === void 0 || !isInFolder(await realpath(this.#root), realPath)) {
|
|
270
|
-
|
|
541
|
+
return void 0;
|
|
271
542
|
}
|
|
272
543
|
return realPath;
|
|
273
544
|
}
|
|
@@ -275,6 +546,17 @@ class NodeProjectFiles {
|
|
|
275
546
|
function createNodeProjectFiles(root, options) {
|
|
276
547
|
return new NodeProjectFiles(root, options);
|
|
277
548
|
}
|
|
549
|
+
const MISSING_PATH_CODES = /* @__PURE__ */ new Set(["ENOENT", "ENOTDIR"]);
|
|
550
|
+
async function inspectProjectRoot(root) {
|
|
551
|
+
try {
|
|
552
|
+
return (await stat(root)).isDirectory() ? "directory" : "not-a-directory";
|
|
553
|
+
} catch (error) {
|
|
554
|
+
if (MISSING_PATH_CODES.has(error.code ?? "")) {
|
|
555
|
+
return "missing";
|
|
556
|
+
}
|
|
557
|
+
throw error;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
278
560
|
|
|
279
561
|
function unchanged(text) {
|
|
280
562
|
return { text, changes: [], manualSteps: [] };
|
|
@@ -287,80 +569,213 @@ function lineAt(text, offset) {
|
|
|
287
569
|
}
|
|
288
570
|
return line;
|
|
289
571
|
}
|
|
572
|
+
function lineLocator(text) {
|
|
573
|
+
const lineStarts = [0];
|
|
574
|
+
for (let index = text.indexOf("\n"); index !== -1; index = text.indexOf("\n", index + 1)) {
|
|
575
|
+
lineStarts.push(index + 1);
|
|
576
|
+
}
|
|
577
|
+
return (offset) => {
|
|
578
|
+
let low = 0;
|
|
579
|
+
let high = lineStarts.length - 1;
|
|
580
|
+
while (low < high) {
|
|
581
|
+
const middle = Math.ceil((low + high) / 2);
|
|
582
|
+
if (lineStarts[middle] <= offset) {
|
|
583
|
+
low = middle;
|
|
584
|
+
} else {
|
|
585
|
+
high = middle - 1;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return low + 1;
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
function columnAt(text, offset) {
|
|
592
|
+
return offset - text.lastIndexOf("\n", offset - 1);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function applyTextEdits(text, edits) {
|
|
596
|
+
const sorted = [...edits].sort((left, right) => left.start - right.start);
|
|
597
|
+
let edited = "";
|
|
598
|
+
let copiedUpTo = 0;
|
|
599
|
+
for (const edit of sorted) {
|
|
600
|
+
if (edit.start < copiedUpTo) {
|
|
601
|
+
throw new Error(
|
|
602
|
+
`Edits overlap at offset ${edit.start}; the file was not changed (a migration bug).`
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
edited += `${text.slice(copiedUpTo, edit.start)}${edit.text}`;
|
|
606
|
+
copiedUpTo = edit.end;
|
|
607
|
+
}
|
|
608
|
+
return edited + text.slice(copiedUpTo);
|
|
609
|
+
}
|
|
290
610
|
|
|
291
611
|
const GENERATOR_MODULES = /* @__PURE__ */ new Set(["openapi-typesafe", "openapi-typescript"]);
|
|
292
612
|
const TYPESCRIPT_MODULES = /* @__PURE__ */ new Set(["typescript", "@typescript/typescript6"]);
|
|
293
|
-
|
|
613
|
+
const DEFAULT_EXPORT$1 = "default";
|
|
614
|
+
function addNamedImports(clause, isGenerator, into) {
|
|
294
615
|
const bindings = clause.namedBindings;
|
|
295
616
|
if (bindings === void 0 || !ts.isNamedImports(bindings)) {
|
|
296
617
|
return;
|
|
297
618
|
}
|
|
298
619
|
for (const element of bindings.elements) {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
function addTypescriptImport(clause, names) {
|
|
303
|
-
if (clause.name !== void 0) {
|
|
304
|
-
names.typescriptModules.add(clause.name.text);
|
|
305
|
-
}
|
|
306
|
-
const bindings = clause.namedBindings;
|
|
307
|
-
if (bindings !== void 0 && ts.isNamespaceImport(bindings)) {
|
|
308
|
-
names.typescriptModules.add(bindings.name.text);
|
|
309
|
-
}
|
|
310
|
-
namedImports(clause, names.typescriptImports);
|
|
311
|
-
}
|
|
312
|
-
function addGeneratorImport(clause, names) {
|
|
313
|
-
if (clause.name !== void 0) {
|
|
314
|
-
names.generatorFunctions.add(clause.name.text);
|
|
620
|
+
const name = (element.propertyName ?? element.name).text;
|
|
621
|
+
const binding = isGenerator ? name === DEFAULT_EXPORT$1 ? { kind: "generator-function" } : { kind: "helper-import", name } : { kind: "typescript-import", name };
|
|
622
|
+
into.set(element, binding);
|
|
315
623
|
}
|
|
316
|
-
namedImports(clause, names.helperImports);
|
|
317
624
|
}
|
|
318
|
-
function addImport(statement,
|
|
625
|
+
function addImport(statement, into) {
|
|
319
626
|
const clause = statement.importClause;
|
|
320
|
-
|
|
627
|
+
const specifier = ts.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : "";
|
|
628
|
+
const isGenerator = GENERATOR_MODULES.has(specifier);
|
|
629
|
+
if (clause === void 0 || !isGenerator && !TYPESCRIPT_MODULES.has(specifier)) {
|
|
321
630
|
return;
|
|
322
631
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
632
|
+
if (clause.name !== void 0) {
|
|
633
|
+
into.set(clause, isGenerator ? { kind: "generator-function" } : { kind: "typescript-module" });
|
|
634
|
+
}
|
|
635
|
+
const namespace = clause.namedBindings;
|
|
636
|
+
if (namespace !== void 0 && ts.isNamespaceImport(namespace)) {
|
|
637
|
+
const kind = isGenerator ? "generator-module" : "typescript-module";
|
|
638
|
+
into.set(namespace, { kind });
|
|
328
639
|
}
|
|
640
|
+
addNamedImports(clause, isGenerator, into);
|
|
329
641
|
}
|
|
330
|
-
function addConstants(statement,
|
|
642
|
+
function addConstants(statement, into) {
|
|
331
643
|
if ((statement.declarationList.flags & ts.NodeFlags.Const) === 0) {
|
|
332
644
|
return;
|
|
333
645
|
}
|
|
334
646
|
for (const declaration of statement.declarationList.declarations) {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
continue;
|
|
338
|
-
}
|
|
339
|
-
names.constants.set(declaration.name.text, initializer);
|
|
340
|
-
if (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)) {
|
|
341
|
-
names.functions.set(declaration.name.text, initializer);
|
|
647
|
+
if (ts.isIdentifier(declaration.name) && declaration.initializer !== void 0) {
|
|
648
|
+
into.set(declaration, { kind: "constant", initializer: declaration.initializer });
|
|
342
649
|
}
|
|
343
650
|
}
|
|
344
651
|
}
|
|
345
|
-
function
|
|
346
|
-
const
|
|
347
|
-
typescriptModules: /* @__PURE__ */ new Set(),
|
|
348
|
-
typescriptImports: /* @__PURE__ */ new Map(),
|
|
349
|
-
helperImports: /* @__PURE__ */ new Map(),
|
|
350
|
-
constants: /* @__PURE__ */ new Map(),
|
|
351
|
-
generatorFunctions: /* @__PURE__ */ new Set(),
|
|
352
|
-
functions: /* @__PURE__ */ new Map()
|
|
353
|
-
};
|
|
652
|
+
function collectBindings(file) {
|
|
653
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
354
654
|
for (const statement of file.statements) {
|
|
355
655
|
if (ts.isImportDeclaration(statement)) {
|
|
356
|
-
addImport(statement,
|
|
656
|
+
addImport(statement, bindings);
|
|
357
657
|
} else if (ts.isVariableStatement(statement)) {
|
|
358
|
-
addConstants(statement,
|
|
658
|
+
addConstants(statement, bindings);
|
|
359
659
|
} else if (ts.isFunctionDeclaration(statement) && statement.name !== void 0) {
|
|
360
|
-
|
|
660
|
+
bindings.set(statement, { kind: "function", declaration: statement });
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return bindings;
|
|
664
|
+
}
|
|
665
|
+
function singleFileHost(file) {
|
|
666
|
+
return {
|
|
667
|
+
getSourceFile: (name) => name === file.fileName ? file : void 0,
|
|
668
|
+
getDefaultLibFileName: () => "lib.d.ts",
|
|
669
|
+
writeFile: () => void 0,
|
|
670
|
+
getCurrentDirectory: () => "",
|
|
671
|
+
getCanonicalFileName: (name) => name,
|
|
672
|
+
useCaseSensitiveFileNames: () => true,
|
|
673
|
+
getNewLine: () => "\n",
|
|
674
|
+
fileExists: (name) => name === file.fileName,
|
|
675
|
+
readFile: () => void 0
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
function createChecker(file) {
|
|
679
|
+
const options = { noLib: true, noResolve: true, allowJs: true, types: [] };
|
|
680
|
+
const host = singleFileHost(file);
|
|
681
|
+
return ts.createProgram({ rootNames: [file.fileName], options, host }).getTypeChecker();
|
|
682
|
+
}
|
|
683
|
+
function symbolOf(checker, identifier) {
|
|
684
|
+
const parent = identifier.parent;
|
|
685
|
+
return ts.isShorthandPropertyAssignment(parent) && parent.name === identifier ? checker.getShorthandAssignmentValueSymbol(parent) : checker.getSymbolAtLocation(identifier);
|
|
686
|
+
}
|
|
687
|
+
function isDeclaredName(identifier, declaration) {
|
|
688
|
+
const declaredName = declaration.name;
|
|
689
|
+
return declaredName === identifier || ts.findAncestor(identifier, ts.isImportDeclaration) !== void 0;
|
|
690
|
+
}
|
|
691
|
+
function indexUses(file, bindings) {
|
|
692
|
+
const uses = /* @__PURE__ */ new Map();
|
|
693
|
+
const visit = (node) => {
|
|
694
|
+
const declaration = ts.isIdentifier(node) ? bindings.declarationOf(node) : void 0;
|
|
695
|
+
if (declaration !== void 0 && !isDeclaredName(node, declaration)) {
|
|
696
|
+
uses.set(declaration, [...uses.get(declaration) ?? [], node]);
|
|
361
697
|
}
|
|
698
|
+
ts.forEachChild(node, visit);
|
|
699
|
+
};
|
|
700
|
+
visit(file);
|
|
701
|
+
return uses;
|
|
702
|
+
}
|
|
703
|
+
class CheckedFileBindings {
|
|
704
|
+
hasGenerator;
|
|
705
|
+
#file;
|
|
706
|
+
#bindings;
|
|
707
|
+
declarations;
|
|
708
|
+
#checker;
|
|
709
|
+
#uses;
|
|
710
|
+
constructor(file) {
|
|
711
|
+
this.#file = file;
|
|
712
|
+
this.#bindings = collectBindings(file);
|
|
713
|
+
this.declarations = this.#bindings;
|
|
714
|
+
this.hasGenerator = [...this.#bindings.values()].some(
|
|
715
|
+
(binding) => binding.kind === "generator-function" || binding.kind === "generator-module"
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
declarationOf(identifier) {
|
|
719
|
+
this.#checker ??= createChecker(this.#file);
|
|
720
|
+
const declaration = symbolOf(this.#checker, identifier)?.declarations?.[0];
|
|
721
|
+
return declaration !== void 0 && this.#bindings.has(declaration) ? declaration : void 0;
|
|
722
|
+
}
|
|
723
|
+
bindingOf(identifier) {
|
|
724
|
+
const declaration = this.declarationOf(identifier);
|
|
725
|
+
return declaration === void 0 ? void 0 : this.#bindings.get(declaration);
|
|
726
|
+
}
|
|
727
|
+
usesOf(declaration) {
|
|
728
|
+
this.#uses ??= indexUses(this.#file, this);
|
|
729
|
+
return this.#uses.get(declaration) ?? [];
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
function createFileBindings(file) {
|
|
733
|
+
return new CheckedFileBindings(file);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
const DEFAULT_EXPORT = "default";
|
|
737
|
+
function isGenerator(callee, bindings) {
|
|
738
|
+
if (ts.isIdentifier(callee)) {
|
|
739
|
+
return bindings.bindingOf(callee)?.kind === "generator-function";
|
|
740
|
+
}
|
|
741
|
+
return ts.isPropertyAccessExpression(callee) && callee.name.text === DEFAULT_EXPORT && ts.isIdentifier(callee.expression) && bindings.bindingOf(callee.expression)?.kind === "generator-module";
|
|
742
|
+
}
|
|
743
|
+
function unwrapped(expression) {
|
|
744
|
+
if (ts.isParenthesizedExpression(expression) || ts.isAsExpression(expression) || ts.isSatisfiesExpression(expression)) {
|
|
745
|
+
return unwrapped(expression.expression);
|
|
746
|
+
}
|
|
747
|
+
return expression;
|
|
748
|
+
}
|
|
749
|
+
const isExported$1 = (declaration) => (ts.getCombinedModifierFlags(declaration) & ts.ModifierFlags.Export) !== 0;
|
|
750
|
+
function constantOptions(name, bindings) {
|
|
751
|
+
const binding = bindings.bindingOf(name);
|
|
752
|
+
const declaration = bindings.declarationOf(name);
|
|
753
|
+
const value = binding?.kind === "constant" ? unwrapped(binding.initializer) : void 0;
|
|
754
|
+
if (value === void 0 || !ts.isObjectLiteralExpression(value)) {
|
|
755
|
+
return { kind: "unknown", expression: name };
|
|
756
|
+
}
|
|
757
|
+
const isOwn = bindings.usesOf(declaration).length === 1;
|
|
758
|
+
return { kind: isOwn && !isExported$1(declaration) ? "own" : "shared", options: value };
|
|
759
|
+
}
|
|
760
|
+
function optionsOf(argument, bindings) {
|
|
761
|
+
const value = unwrapped(argument);
|
|
762
|
+
if (ts.isObjectLiteralExpression(value)) {
|
|
763
|
+
return { kind: "own", options: value };
|
|
362
764
|
}
|
|
363
|
-
return
|
|
765
|
+
return ts.isIdentifier(value) ? constantOptions(value, bindings) : { kind: "unknown", expression: argument };
|
|
766
|
+
}
|
|
767
|
+
function generatorCalls(file, bindings) {
|
|
768
|
+
const calls = [];
|
|
769
|
+
const visit = (node) => {
|
|
770
|
+
if (ts.isCallExpression(node) && isGenerator(node.expression, bindings)) {
|
|
771
|
+
const argument = node.arguments[1];
|
|
772
|
+
const options = argument === void 0 ? void 0 : optionsOf(argument, bindings);
|
|
773
|
+
calls.push({ call: node, options });
|
|
774
|
+
}
|
|
775
|
+
ts.forEachChild(node, visit);
|
|
776
|
+
};
|
|
777
|
+
visit(file);
|
|
778
|
+
return calls;
|
|
364
779
|
}
|
|
365
780
|
|
|
366
781
|
const SCRIPT_KINDS = /* @__PURE__ */ new Map([
|
|
@@ -467,22 +882,19 @@ const HELPER_CONSTANTS = /* @__PURE__ */ new Map([
|
|
|
467
882
|
["UNKNOWN", "unknown"]
|
|
468
883
|
]);
|
|
469
884
|
const TYPE_NAME_RE = /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*$/;
|
|
470
|
-
const plain = (text) => ({ text, operator: null });
|
|
471
|
-
function
|
|
472
|
-
if (
|
|
473
|
-
|
|
885
|
+
const plain$1 = (text) => ({ text, operator: null });
|
|
886
|
+
function isTypescriptMember(target, member, names) {
|
|
887
|
+
if (ts.isIdentifier(target)) {
|
|
888
|
+
const binding = names.bindings.bindingOf(target);
|
|
889
|
+
return binding?.kind === "typescript-import" && binding.name === member;
|
|
474
890
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
891
|
+
return ts.isPropertyAccessExpression(target) && target.name.text === member && ts.isIdentifier(target.expression) && names.bindings.bindingOf(target.expression)?.kind === "typescript-module";
|
|
892
|
+
}
|
|
893
|
+
function factoryMethod(callee, names) {
|
|
894
|
+
return ts.isPropertyAccessExpression(callee) && isTypescriptMember(callee.expression, "factory", names) ? callee.name.text : void 0;
|
|
478
895
|
}
|
|
479
896
|
function syntaxKindMember(expression, names) {
|
|
480
|
-
|
|
481
|
-
return void 0;
|
|
482
|
-
}
|
|
483
|
-
const target = expression.expression;
|
|
484
|
-
const isSyntaxKind = ts.isIdentifier(target) ? names.typescriptImports.get(target.text) === "SyntaxKind" : ts.isPropertyAccessExpression(target) && target.name.text === "SyntaxKind" && ts.isIdentifier(target.expression) && names.typescriptModules.has(target.expression.text);
|
|
485
|
-
return isSyntaxKind ? expression.name.text : void 0;
|
|
897
|
+
return ts.isPropertyAccessExpression(expression) && isTypescriptMember(expression.expression, "SyntaxKind", names) ? expression.name.text : void 0;
|
|
486
898
|
}
|
|
487
899
|
function typeName(argument, names) {
|
|
488
900
|
if (argument === void 0) {
|
|
@@ -496,6 +908,18 @@ function typeName(argument, names) {
|
|
|
496
908
|
}
|
|
497
909
|
return void 0;
|
|
498
910
|
}
|
|
911
|
+
const digits = (digit) => `${digit}(?:_?${digit})*`;
|
|
912
|
+
const DECIMAL_LITERAL = `(?:(?:0|[1-9](?:_?\\d)*)(?:\\.(?:${digits("\\d")})?)?|\\.${digits("\\d")})(?:[eE][+-]?${digits("\\d")})?`;
|
|
913
|
+
const NUMERIC_LITERAL_RE = new RegExp(
|
|
914
|
+
`^(?:0[xX]${digits("[\\da-fA-F]")}|0[oO]${digits("[0-7]")}|0[bB]${digits("[01]")}|${DECIMAL_LITERAL})$`
|
|
915
|
+
);
|
|
916
|
+
function numericLiteralText(value) {
|
|
917
|
+
if (ts.isNumericLiteral(value)) {
|
|
918
|
+
const number = Number(value.text);
|
|
919
|
+
return Number.isFinite(number) ? String(number) : void 0;
|
|
920
|
+
}
|
|
921
|
+
return ts.isStringLiteralLike(value) && NUMERIC_LITERAL_RE.test(value.text) ? value.text : void 0;
|
|
922
|
+
}
|
|
499
923
|
function literalText(argument, names) {
|
|
500
924
|
if (argument === void 0 || !ts.isCallExpression(argument)) {
|
|
501
925
|
return void 0;
|
|
@@ -512,7 +936,7 @@ function literalText(argument, names) {
|
|
|
512
936
|
case "createStringLiteral":
|
|
513
937
|
return value !== void 0 && ts.isStringLiteralLike(value) ? JSON.stringify(value.text) : void 0;
|
|
514
938
|
case "createNumericLiteral":
|
|
515
|
-
return value
|
|
939
|
+
return value === void 0 ? void 0 : numericLiteralText(value);
|
|
516
940
|
default:
|
|
517
941
|
return void 0;
|
|
518
942
|
}
|
|
@@ -535,15 +959,15 @@ function factoryCallText(call, method, names) {
|
|
|
535
959
|
switch (method) {
|
|
536
960
|
case "createTypeReferenceNode": {
|
|
537
961
|
const name = second === void 0 ? typeName(first, names) : void 0;
|
|
538
|
-
return name === void 0 ? void 0 : plain(name);
|
|
962
|
+
return name === void 0 ? void 0 : plain$1(name);
|
|
539
963
|
}
|
|
540
964
|
case "createKeywordTypeNode": {
|
|
541
965
|
const keyword = first === void 0 ? void 0 : KEYWORDS.get(syntaxKindMember(first, names) ?? "");
|
|
542
|
-
return keyword === void 0 ? void 0 : plain(keyword);
|
|
966
|
+
return keyword === void 0 ? void 0 : plain$1(keyword);
|
|
543
967
|
}
|
|
544
968
|
case "createLiteralTypeNode": {
|
|
545
969
|
const literal = literalText(first, names);
|
|
546
|
-
return literal === void 0 ? void 0 : plain(literal);
|
|
970
|
+
return literal === void 0 ? void 0 : plain$1(literal);
|
|
547
971
|
}
|
|
548
972
|
case "createUnionTypeNode":
|
|
549
973
|
return joined(first, "|", names);
|
|
@@ -551,7 +975,7 @@ function factoryCallText(call, method, names) {
|
|
|
551
975
|
return joined(first, "&", names);
|
|
552
976
|
case "createArrayTypeNode": {
|
|
553
977
|
const element = first === void 0 ? void 0 : typeTextOf(first, names);
|
|
554
|
-
return element === void 0 ? void 0 : plain(element.operator ? `(${element.text})[]` : `${element.text}[]`);
|
|
978
|
+
return element === void 0 ? void 0 : plain$1(element.operator ? `(${element.text})[]` : `${element.text}[]`);
|
|
555
979
|
}
|
|
556
980
|
default:
|
|
557
981
|
return void 0;
|
|
@@ -561,20 +985,25 @@ function helperCallText(call, names) {
|
|
|
561
985
|
if (!ts.isIdentifier(call.expression)) {
|
|
562
986
|
return void 0;
|
|
563
987
|
}
|
|
564
|
-
const
|
|
988
|
+
const binding = names.bindings.bindingOf(call.expression);
|
|
989
|
+
const helper = binding?.kind === "helper-import" ? binding.name : void 0;
|
|
565
990
|
if (helper === "tsUnion") {
|
|
566
991
|
return joined(call.arguments[0], "|", names);
|
|
567
992
|
}
|
|
568
993
|
return helper === "tsIntersection" ? joined(call.arguments[0], "&", names) : void 0;
|
|
569
994
|
}
|
|
570
995
|
function identifierText(identifier, names) {
|
|
571
|
-
const
|
|
572
|
-
const
|
|
573
|
-
if (
|
|
574
|
-
|
|
996
|
+
const declaration = names.bindings.declarationOf(identifier);
|
|
997
|
+
const binding = names.bindings.bindingOf(identifier);
|
|
998
|
+
if (binding?.kind === "helper-import") {
|
|
999
|
+
const constant = HELPER_CONSTANTS.get(binding.name);
|
|
1000
|
+
return constant === void 0 ? void 0 : plain$1(constant);
|
|
575
1001
|
}
|
|
576
|
-
|
|
577
|
-
|
|
1002
|
+
if (binding?.kind !== "constant" || names.following.has(declaration)) {
|
|
1003
|
+
return void 0;
|
|
1004
|
+
}
|
|
1005
|
+
const following = /* @__PURE__ */ new Set([...names.following, declaration]);
|
|
1006
|
+
return typeTextOf(binding.initializer, { ...names, following });
|
|
578
1007
|
}
|
|
579
1008
|
function typeTextOf(expression, names) {
|
|
580
1009
|
if (ts.isParenthesizedExpression(expression)) {
|
|
@@ -644,6 +1073,112 @@ function returnEdits(expression, names) {
|
|
|
644
1073
|
return typeTextEdit(expression, names);
|
|
645
1074
|
}
|
|
646
1075
|
|
|
1076
|
+
const isExported = (node) => (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) !== 0;
|
|
1077
|
+
function isRemovable(node, binding, names) {
|
|
1078
|
+
switch (binding.kind) {
|
|
1079
|
+
case "typescript-module":
|
|
1080
|
+
case "typescript-import":
|
|
1081
|
+
case "helper-import":
|
|
1082
|
+
return true;
|
|
1083
|
+
case "constant":
|
|
1084
|
+
return !isExported(node) && typeTextOf(binding.initializer, names) !== void 0;
|
|
1085
|
+
default:
|
|
1086
|
+
return false;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
const isInside = (node, ranges) => ranges.some((range) => node.getStart() >= range.start && node.getEnd() <= range.end);
|
|
1090
|
+
function unusedDeclarations(names, replaced) {
|
|
1091
|
+
const ranges = replaced.filter((edit) => edit.end > edit.start);
|
|
1092
|
+
const removed = [];
|
|
1093
|
+
for (let isChanged = true; isChanged; ) {
|
|
1094
|
+
isChanged = false;
|
|
1095
|
+
for (const [node, binding] of names.bindings.declarations) {
|
|
1096
|
+
const uses = names.bindings.usesOf(node);
|
|
1097
|
+
const isUnused = uses.length > 0 && uses.every((use) => isInside(use, ranges));
|
|
1098
|
+
const isNew = removed.every((found) => found.node !== node);
|
|
1099
|
+
if (isNew && isUnused && isRemovable(node, binding, names)) {
|
|
1100
|
+
const name = node.name?.getText() ?? "";
|
|
1101
|
+
removed.push({ node, name });
|
|
1102
|
+
ranges.push({ start: node.getStart(), end: node.getEnd() });
|
|
1103
|
+
isChanged = true;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
return removed;
|
|
1108
|
+
}
|
|
1109
|
+
function statementRemoval(statement, text) {
|
|
1110
|
+
const start = statement.getStart(void 0, true);
|
|
1111
|
+
const end = statement.getEnd();
|
|
1112
|
+
const lineStart = text.lastIndexOf("\n", start - 1) + 1;
|
|
1113
|
+
const lineBreak = text.indexOf("\n", end);
|
|
1114
|
+
const lineEnd = lineBreak === -1 ? text.length : lineBreak + 1;
|
|
1115
|
+
const isAlone = text.slice(lineStart, start).trim() === "" && text.slice(end, lineEnd).trim() === "";
|
|
1116
|
+
return isAlone ? { start: lineStart, end: lineEnd, text: "" } : { start, end, text: "" };
|
|
1117
|
+
}
|
|
1118
|
+
function listRemovals(elements, isRemoved) {
|
|
1119
|
+
const lastKept = elements.findLastIndex((element) => !isRemoved(element));
|
|
1120
|
+
const edits = [];
|
|
1121
|
+
elements.forEach((element, index) => {
|
|
1122
|
+
const next = elements[index + 1];
|
|
1123
|
+
if (index < lastKept && isRemoved(element) && next !== void 0) {
|
|
1124
|
+
edits.push({ start: element.getStart(), end: next.getStart(), text: "" });
|
|
1125
|
+
}
|
|
1126
|
+
});
|
|
1127
|
+
const last = elements.at(-1);
|
|
1128
|
+
if (lastKept !== -1 && last !== void 0 && lastKept < elements.length - 1) {
|
|
1129
|
+
edits.push({ start: elements[lastKept].getEnd(), end: last.getEnd(), text: "" });
|
|
1130
|
+
}
|
|
1131
|
+
return edits;
|
|
1132
|
+
}
|
|
1133
|
+
function importEdits(statement, isRemoved, text) {
|
|
1134
|
+
const clause = statement.importClause;
|
|
1135
|
+
const bindings = clause.namedBindings;
|
|
1136
|
+
const named = bindings !== void 0 && ts.isNamedImports(bindings) ? bindings : void 0;
|
|
1137
|
+
const namespace = bindings !== void 0 && ts.isNamespaceImport(bindings) ? bindings : void 0;
|
|
1138
|
+
const parts = [
|
|
1139
|
+
...clause.name === void 0 ? [] : [clause],
|
|
1140
|
+
...namespace === void 0 ? [] : [namespace],
|
|
1141
|
+
...named?.elements ?? []
|
|
1142
|
+
];
|
|
1143
|
+
if (parts.every(isRemoved)) {
|
|
1144
|
+
return [statementRemoval(statement, text)];
|
|
1145
|
+
}
|
|
1146
|
+
const defaultEnd = clause.name?.getEnd() ?? 0;
|
|
1147
|
+
const edits = [];
|
|
1148
|
+
if (clause.name !== void 0 && isRemoved(clause)) {
|
|
1149
|
+
edits.push({ start: clause.name.getStart(), end: bindings.getStart(), text: "" });
|
|
1150
|
+
}
|
|
1151
|
+
if (namespace !== void 0 && isRemoved(namespace)) {
|
|
1152
|
+
edits.push({ start: defaultEnd, end: namespace.getEnd(), text: "" });
|
|
1153
|
+
}
|
|
1154
|
+
if (named !== void 0 && named.elements.length > 0 && named.elements.every(isRemoved)) {
|
|
1155
|
+
edits.push({ start: defaultEnd, end: named.getEnd(), text: "" });
|
|
1156
|
+
} else if (named !== void 0) {
|
|
1157
|
+
edits.push(...listRemovals(named.elements, isRemoved));
|
|
1158
|
+
}
|
|
1159
|
+
return edits;
|
|
1160
|
+
}
|
|
1161
|
+
function statementEdits(statement, isRemoved, text) {
|
|
1162
|
+
if (ts.isImportDeclaration(statement)) {
|
|
1163
|
+
return importEdits(statement, isRemoved, text);
|
|
1164
|
+
}
|
|
1165
|
+
const declarations = statement.declarationList.declarations;
|
|
1166
|
+
return declarations.every(isRemoved) ? [statementRemoval(statement, text)] : listRemovals(declarations, isRemoved);
|
|
1167
|
+
}
|
|
1168
|
+
function removalEdits(removed, text) {
|
|
1169
|
+
const nodes = new Set(removed.map((found) => found.node));
|
|
1170
|
+
const isRemoved = (node) => nodes.has(node);
|
|
1171
|
+
const statements = new Set(
|
|
1172
|
+
removed.map(
|
|
1173
|
+
(found) => ts.findAncestor(
|
|
1174
|
+
found.node,
|
|
1175
|
+
(node) => ts.isVariableStatement(node) || ts.isImportDeclaration(node)
|
|
1176
|
+
)
|
|
1177
|
+
)
|
|
1178
|
+
);
|
|
1179
|
+
return [...statements].flatMap((statement) => statementEdits(statement, isRemoved, text));
|
|
1180
|
+
}
|
|
1181
|
+
|
|
647
1182
|
const TRANSFORM = "transform";
|
|
648
1183
|
const LEGACY_ONLY_CALLBACKS = ["postTransform", "transformProperty"];
|
|
649
1184
|
const IR_CALLBACKS_OPTION = "irCallbacks";
|
|
@@ -653,6 +1188,14 @@ function propertyNamed(options, name) {
|
|
|
653
1188
|
return key !== void 0 && (ts.isIdentifier(key) || ts.isStringLiteral(key)) && key.text === name;
|
|
654
1189
|
});
|
|
655
1190
|
}
|
|
1191
|
+
function namedFunction(name, names) {
|
|
1192
|
+
const binding = names.bindings.bindingOf(name);
|
|
1193
|
+
if (binding?.kind === "function") {
|
|
1194
|
+
return binding.declaration;
|
|
1195
|
+
}
|
|
1196
|
+
const value = binding?.kind === "constant" ? binding.initializer : void 0;
|
|
1197
|
+
return value !== void 0 && (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) ? value : void 0;
|
|
1198
|
+
}
|
|
656
1199
|
function callbackFunction(property, names) {
|
|
657
1200
|
if (ts.isMethodDeclaration(property)) {
|
|
658
1201
|
return property;
|
|
@@ -662,17 +1205,16 @@ function callbackFunction(property, names) {
|
|
|
662
1205
|
}
|
|
663
1206
|
const value = property.initializer;
|
|
664
1207
|
if (ts.isIdentifier(value)) {
|
|
665
|
-
return
|
|
1208
|
+
return namedFunction(value, names);
|
|
666
1209
|
}
|
|
667
1210
|
return ts.isArrowFunction(value) || ts.isFunctionExpression(value) ? value : void 0;
|
|
668
1211
|
}
|
|
669
|
-
function isSharedFunction(property,
|
|
1212
|
+
function isSharedFunction(property, names) {
|
|
670
1213
|
if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.initializer)) {
|
|
671
1214
|
return false;
|
|
672
1215
|
}
|
|
673
|
-
const
|
|
674
|
-
|
|
675
|
-
return uses.length > 2;
|
|
1216
|
+
const declaration = names.bindings.declarationOf(property.initializer);
|
|
1217
|
+
return declaration !== void 0 && names.bindings.usesOf(declaration).length > 1;
|
|
676
1218
|
}
|
|
677
1219
|
function returnTypeEdits(callback, text) {
|
|
678
1220
|
const type = callback.type;
|
|
@@ -687,7 +1229,7 @@ function transformEdits(property, names, text) {
|
|
|
687
1229
|
if (callback?.body === void 0) {
|
|
688
1230
|
return "not-a-function";
|
|
689
1231
|
}
|
|
690
|
-
if (isSharedFunction(property,
|
|
1232
|
+
if (isSharedFunction(property, names)) {
|
|
691
1233
|
return "shared-function";
|
|
692
1234
|
}
|
|
693
1235
|
const edits = returnTypeEdits(callback, text);
|
|
@@ -741,35 +1283,66 @@ function migrateOptions(options, text, names) {
|
|
|
741
1283
|
findings.changes.push({ kind: "transform-rewritten", line });
|
|
742
1284
|
return findings;
|
|
743
1285
|
}
|
|
744
|
-
function
|
|
745
|
-
|
|
746
|
-
|
|
1286
|
+
function sharedOptionsFindings(options, text) {
|
|
1287
|
+
const findings = { edits: [], changes: [], manualSteps: [] };
|
|
1288
|
+
if (propertyNamed(options, IR_CALLBACKS_OPTION) !== void 0) {
|
|
1289
|
+
return findings;
|
|
1290
|
+
}
|
|
1291
|
+
findings.manualSteps.push(...legacyCallbackSteps(options, text));
|
|
1292
|
+
const transform = propertyNamed(options, TRANSFORM);
|
|
1293
|
+
if (transform !== void 0) {
|
|
1294
|
+
const line = lineAt(text, transform.getStart());
|
|
1295
|
+
findings.manualSteps.push({ kind: "transform-not-rewritten", line, reason: "shared-options" });
|
|
747
1296
|
}
|
|
748
|
-
|
|
749
|
-
const isGenerator = names.generatorFunctions.has(node.expression.text);
|
|
750
|
-
return isGenerator && options !== void 0 && ts.isObjectLiteralExpression(options) ? options : void 0;
|
|
1297
|
+
return findings;
|
|
751
1298
|
}
|
|
752
|
-
function
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
1299
|
+
function callFindings(found, text, names) {
|
|
1300
|
+
const options = found.options;
|
|
1301
|
+
switch (options?.kind) {
|
|
1302
|
+
case void 0:
|
|
1303
|
+
return { edits: [], changes: [], manualSteps: [] };
|
|
1304
|
+
case "own":
|
|
1305
|
+
return migrateOptions(options.options, text, names);
|
|
1306
|
+
case "shared":
|
|
1307
|
+
return sharedOptionsFindings(options.options, text);
|
|
1308
|
+
case "unknown": {
|
|
1309
|
+
const line = lineAt(text, options.expression.getStart());
|
|
1310
|
+
return { edits: [], changes: [], manualSteps: [{ kind: "options-not-inspected", line }] };
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
757
1313
|
}
|
|
758
1314
|
function findCallFindings(file, text, names) {
|
|
759
1315
|
const all = { edits: [], changes: [], manualSteps: [] };
|
|
760
|
-
const
|
|
761
|
-
|
|
1316
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1317
|
+
for (const found of generatorCalls(file, names.bindings)) {
|
|
1318
|
+
const options = found.options?.kind === "unknown" ? void 0 : found.options?.options;
|
|
1319
|
+
if (options !== void 0 && seen.has(options)) {
|
|
1320
|
+
continue;
|
|
1321
|
+
}
|
|
762
1322
|
if (options !== void 0) {
|
|
763
|
-
|
|
764
|
-
all.edits.push(...found.edits);
|
|
765
|
-
all.changes.push(...found.changes);
|
|
766
|
-
all.manualSteps.push(...found.manualSteps);
|
|
1323
|
+
seen.add(options);
|
|
767
1324
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
1325
|
+
const findings = callFindings(found, text, names);
|
|
1326
|
+
all.edits.push(...findings.edits);
|
|
1327
|
+
all.changes.push(...findings.changes);
|
|
1328
|
+
all.manualSteps.push(...findings.manualSteps);
|
|
1329
|
+
}
|
|
771
1330
|
return all;
|
|
772
1331
|
}
|
|
1332
|
+
function rewriteFile(file, names) {
|
|
1333
|
+
const text = file.text;
|
|
1334
|
+
const { edits, changes, manualSteps } = findCallFindings(file, text, names);
|
|
1335
|
+
const removed = unusedDeclarations(names, edits);
|
|
1336
|
+
for (const found of removed) {
|
|
1337
|
+
changes.push({
|
|
1338
|
+
kind: "declaration-removed",
|
|
1339
|
+
line: lineAt(text, found.node.getStart()),
|
|
1340
|
+
name: found.name
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
const allEdits = [...edits, ...removalEdits(removed, text)];
|
|
1344
|
+
return { text: applyTextEdits(text, allEdits), changes, manualSteps };
|
|
1345
|
+
}
|
|
773
1346
|
class TypescriptCallbackRewriter {
|
|
774
1347
|
rewrite(text, path) {
|
|
775
1348
|
const scriptKind = plainScriptKind(path);
|
|
@@ -777,12 +1350,11 @@ class TypescriptCallbackRewriter {
|
|
|
777
1350
|
return unchanged(text);
|
|
778
1351
|
}
|
|
779
1352
|
const file = ts.createSourceFile(path, text, ts.ScriptTarget.Latest, true, scriptKind);
|
|
780
|
-
const
|
|
781
|
-
if (
|
|
1353
|
+
const bindings = createFileBindings(file);
|
|
1354
|
+
if (!bindings.hasGenerator) {
|
|
782
1355
|
return unchanged(text);
|
|
783
1356
|
}
|
|
784
|
-
|
|
785
|
-
return { text: applyEdits(text, edits), changes, manualSteps };
|
|
1357
|
+
return rewriteFile(file, { bindings, following: /* @__PURE__ */ new Set() });
|
|
786
1358
|
}
|
|
787
1359
|
}
|
|
788
1360
|
function createTypescriptCallbackRewriter() {
|
|
@@ -853,15 +1425,18 @@ function isModuleSpecifier(literal) {
|
|
|
853
1425
|
}
|
|
854
1426
|
return ts.isExternalModuleReference(parent);
|
|
855
1427
|
}
|
|
1428
|
+
function stringValue(node, file) {
|
|
1429
|
+
return ts.isTemplateExpression(node) ? file.text.slice(node.getStart(file) + 1, node.getEnd() - 1) : node.text;
|
|
1430
|
+
}
|
|
856
1431
|
function collectStrings(root, file, offset, found) {
|
|
857
1432
|
const visit = (node) => {
|
|
858
|
-
if (ts.isStringLiteralLike(node)) {
|
|
1433
|
+
if (ts.isStringLiteralLike(node) || ts.isTemplateExpression(node)) {
|
|
859
1434
|
const start = offset + node.getStart(file) + 1;
|
|
860
1435
|
const end = offset + node.getEnd() - 1;
|
|
861
1436
|
found.set(start, {
|
|
862
1437
|
start,
|
|
863
1438
|
end,
|
|
864
|
-
value: node
|
|
1439
|
+
value: stringValue(node, file),
|
|
865
1440
|
isModuleSpecifier: isModuleSpecifier(node)
|
|
866
1441
|
});
|
|
867
1442
|
}
|
|
@@ -932,12 +1507,166 @@ function createTypescriptStringScanner() {
|
|
|
932
1507
|
return new TypescriptSourceStringScanner();
|
|
933
1508
|
}
|
|
934
1509
|
|
|
1510
|
+
const plain = (value) => value;
|
|
1511
|
+
const singleQuoted$1 = (value) => `'${value.replaceAll("'", "''")}'`;
|
|
1512
|
+
const doubleQuoted$1 = (value) => JSON.stringify(value);
|
|
1513
|
+
const QUOTINGS = {
|
|
1514
|
+
[Scalar.PLAIN]: [plain, singleQuoted$1, doubleQuoted$1],
|
|
1515
|
+
[Scalar.QUOTE_SINGLE]: [singleQuoted$1, doubleQuoted$1],
|
|
1516
|
+
[Scalar.QUOTE_DOUBLE]: [doubleQuoted$1]
|
|
1517
|
+
};
|
|
1518
|
+
function readsBack(written, value, isFlow) {
|
|
1519
|
+
const [document] = parseAllDocuments(isFlow ? `[${written}]` : `- ${written}`);
|
|
1520
|
+
const list = document?.contents;
|
|
1521
|
+
const item = isSeq(list) && list.items.length === 1 ? list.items[0] : void 0;
|
|
1522
|
+
return document?.errors.length === 0 && isScalar$1(item) && item.value === value;
|
|
1523
|
+
}
|
|
1524
|
+
function encoderFor(quotings, isFlow) {
|
|
1525
|
+
return (value) => quotings.map((quoting) => quoting(value)).find((text) => readsBack(text, value, isFlow)) ?? doubleQuoted$1(value);
|
|
1526
|
+
}
|
|
1527
|
+
function blockContent(token) {
|
|
1528
|
+
const headerLength = token.props.reduce((length, prop) => length + CST.stringify(prop).length, 0);
|
|
1529
|
+
const start = token.offset + headerLength;
|
|
1530
|
+
return { start, end: start + token.source.trimEnd().length };
|
|
1531
|
+
}
|
|
1532
|
+
function contentRange(token, node) {
|
|
1533
|
+
switch (token?.type) {
|
|
1534
|
+
case "scalar":
|
|
1535
|
+
return { start: token.offset, end: token.offset + token.source.length };
|
|
1536
|
+
case "single-quoted-scalar":
|
|
1537
|
+
case "double-quoted-scalar":
|
|
1538
|
+
return { start: token.offset + 1, end: token.offset + token.source.length - 1 };
|
|
1539
|
+
case "block-scalar":
|
|
1540
|
+
return blockContent(token);
|
|
1541
|
+
default:
|
|
1542
|
+
return node;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
function spanOf(node) {
|
|
1546
|
+
const [start, end] = node.range ?? [0, 0];
|
|
1547
|
+
return { start, end };
|
|
1548
|
+
}
|
|
1549
|
+
function scalarText(node) {
|
|
1550
|
+
if (typeof node.value === "string") {
|
|
1551
|
+
return node.value;
|
|
1552
|
+
}
|
|
1553
|
+
const isWritten = node.type === Scalar.PLAIN && node.value !== null;
|
|
1554
|
+
return isWritten ? node.source : void 0;
|
|
1555
|
+
}
|
|
1556
|
+
function convertScalar(node, isFlow) {
|
|
1557
|
+
const span = spanOf(node);
|
|
1558
|
+
const value = scalarText(node);
|
|
1559
|
+
if (value === void 0) {
|
|
1560
|
+
return { kind: "other", ...span };
|
|
1561
|
+
}
|
|
1562
|
+
const quotings = node.type === void 0 ? void 0 : QUOTINGS[node.type];
|
|
1563
|
+
const isReplaceable = quotings !== void 0 && node.anchor === void 0 && node.tag === void 0;
|
|
1564
|
+
return {
|
|
1565
|
+
kind: "scalar",
|
|
1566
|
+
value,
|
|
1567
|
+
content: contentRange(node.srcToken, span),
|
|
1568
|
+
isFolded: node.type !== Scalar.BLOCK_LITERAL,
|
|
1569
|
+
encode: isReplaceable ? encoderFor(quotings, isFlow) : void 0,
|
|
1570
|
+
...span
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
const flowOf = (conversion, node) => ({
|
|
1574
|
+
isFlow: conversion.isFlow || node.flow === true
|
|
1575
|
+
});
|
|
1576
|
+
function convert(node, conversion) {
|
|
1577
|
+
if (isScalar$1(node)) {
|
|
1578
|
+
return convertScalar(node, conversion.isFlow);
|
|
1579
|
+
}
|
|
1580
|
+
if (isSeq(node)) {
|
|
1581
|
+
const inner = flowOf(conversion, node);
|
|
1582
|
+
return { kind: "list", items: node.items.map((item) => convert(item, inner)), ...spanOf(node) };
|
|
1583
|
+
}
|
|
1584
|
+
if (isMap$1(node)) {
|
|
1585
|
+
const inner = flowOf(conversion, node);
|
|
1586
|
+
const entries = node.items.map((pair) => ({
|
|
1587
|
+
key: convert(pair.key, inner),
|
|
1588
|
+
value: convert(pair.value, inner)
|
|
1589
|
+
}));
|
|
1590
|
+
return { kind: "map", entries, ...spanOf(node) };
|
|
1591
|
+
}
|
|
1592
|
+
return { kind: "other", ...spanOf(isNode(node) ? node : {}) };
|
|
1593
|
+
}
|
|
1594
|
+
function duplicateKeyOffset(node) {
|
|
1595
|
+
if (isSeq(node)) {
|
|
1596
|
+
return firstOffset(node.items);
|
|
1597
|
+
}
|
|
1598
|
+
if (!isMap$1(node)) {
|
|
1599
|
+
return void 0;
|
|
1600
|
+
}
|
|
1601
|
+
const keys = /* @__PURE__ */ new Set();
|
|
1602
|
+
for (const pair of node.items) {
|
|
1603
|
+
if (isScalar$1(pair.key) && keys.has(pair.key.value)) {
|
|
1604
|
+
return spanOf(pair.key).start;
|
|
1605
|
+
}
|
|
1606
|
+
keys.add(isScalar$1(pair.key) ? pair.key.value : pair.key);
|
|
1607
|
+
}
|
|
1608
|
+
return firstOffset(node.items.flatMap((pair) => [pair.key, pair.value]));
|
|
1609
|
+
}
|
|
1610
|
+
function firstOffset(nodes) {
|
|
1611
|
+
for (const node of nodes) {
|
|
1612
|
+
const offset = duplicateKeyOffset(node);
|
|
1613
|
+
if (offset !== void 0) {
|
|
1614
|
+
return offset;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
return void 0;
|
|
1618
|
+
}
|
|
1619
|
+
const YAML_PARSE_OPTIONS = { keepSourceTokens: true, uniqueKeys: false };
|
|
1620
|
+
function read(text) {
|
|
1621
|
+
const documents = parseAllDocuments(text, YAML_PARSE_OPTIONS);
|
|
1622
|
+
const error = documents.flatMap((document) => document.errors)[0];
|
|
1623
|
+
if (error !== void 0) {
|
|
1624
|
+
return { kind: "unreadable", offset: error.pos[0] };
|
|
1625
|
+
}
|
|
1626
|
+
const duplicate = firstOffset(documents.map((document) => document.contents));
|
|
1627
|
+
if (duplicate !== void 0) {
|
|
1628
|
+
return { kind: "unreadable", offset: duplicate };
|
|
1629
|
+
}
|
|
1630
|
+
const roots = documents.flatMap(
|
|
1631
|
+
(document) => document.contents === null ? [] : [convert(document.contents, { isFlow: false })]
|
|
1632
|
+
);
|
|
1633
|
+
return { kind: "read", documents: roots };
|
|
1634
|
+
}
|
|
1635
|
+
function createYamlTextReader() {
|
|
1636
|
+
return { read };
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
const CATALOG_PROTOCOL = "catalog:";
|
|
1640
|
+
const DEFAULT_CATALOG = "default";
|
|
1641
|
+
function catalogOfSpec(spec) {
|
|
1642
|
+
if (!spec.startsWith(CATALOG_PROTOCOL)) {
|
|
1643
|
+
return void 0;
|
|
1644
|
+
}
|
|
1645
|
+
return spec.slice(CATALOG_PROTOCOL.length) || DEFAULT_CATALOG;
|
|
1646
|
+
}
|
|
1647
|
+
class CatalogEntries {
|
|
1648
|
+
#keys = /* @__PURE__ */ new Set();
|
|
1649
|
+
add(catalog, name) {
|
|
1650
|
+
this.#keys.add(JSON.stringify([catalog, name]));
|
|
1651
|
+
}
|
|
1652
|
+
has(catalog, name) {
|
|
1653
|
+
return this.#keys.has(JSON.stringify([catalog, name]));
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
const NO_CATALOG_PLAN = {
|
|
1657
|
+
inUse: new CatalogEntries(),
|
|
1658
|
+
kept: new CatalogEntries()
|
|
1659
|
+
};
|
|
1660
|
+
|
|
935
1661
|
const PACKAGE_RENAMES = /* @__PURE__ */ new Map([
|
|
936
1662
|
["openapi-typescript", "openapi-typesafe"],
|
|
937
1663
|
["openapi-fetch", "openapi-typesafe-fetch"],
|
|
938
1664
|
["openapi-react-query", "openapi-typesafe-react-query"],
|
|
939
1665
|
["openapi-typescript-helpers", "openapi-typesafe-helpers"]
|
|
940
1666
|
]);
|
|
1667
|
+
function namesOriginalPackage(text) {
|
|
1668
|
+
return [...PACKAGE_RENAMES.keys()].some((name) => text.includes(name));
|
|
1669
|
+
}
|
|
941
1670
|
const GENERATOR_COMMAND = "openapi-typescript";
|
|
942
1671
|
const MIGRATED_GENERATOR_COMMAND = "openapi-typesafe";
|
|
943
1672
|
const FLAG_RENAMES = /* @__PURE__ */ new Map([
|
|
@@ -953,71 +1682,328 @@ const DEPENDENCY_FIELDS = [
|
|
|
953
1682
|
"peerDependencies",
|
|
954
1683
|
"optionalDependencies"
|
|
955
1684
|
];
|
|
1685
|
+
const NAME_KEYED_FIELDS = ["peerDependenciesMeta", "dependenciesMeta"];
|
|
1686
|
+
const NAME_LIST_FIELDS = ["bundleDependencies", "bundledDependencies"];
|
|
1687
|
+
const CATALOG_FIELD = "catalog";
|
|
1688
|
+
const NAMED_CATALOGS_FIELD = "catalogs";
|
|
1689
|
+
const WORKSPACES_FIELD = "workspaces";
|
|
956
1690
|
const OVERRIDE_FIELDS = ["overrides", "resolutions", "pnpm"];
|
|
957
1691
|
const OLD_PEER_DEPENDENTS = /* @__PURE__ */ new Map([
|
|
958
1692
|
["swr-openapi", { peer: "openapi-fetch", range: ">=0.15" }]
|
|
959
1693
|
]);
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
const
|
|
963
|
-
const
|
|
964
|
-
|
|
965
|
-
)
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
}
|
|
971
|
-
return text.replace(FLAG_RE, (flag) => {
|
|
972
|
-
const renamed = FLAG_RENAMES.get(flag);
|
|
973
|
-
changes.push({ kind: "flag-renamed", location, from: flag, to: renamed });
|
|
974
|
-
return renamed;
|
|
975
|
-
});
|
|
1694
|
+
const PATH_SEPARATOR$7 = "/";
|
|
1695
|
+
function renamedSpecifier(value) {
|
|
1696
|
+
const slash = value.indexOf(PATH_SEPARATOR$7);
|
|
1697
|
+
const name = slash === -1 ? value : value.slice(0, slash);
|
|
1698
|
+
const renamed = PACKAGE_RENAMES.get(name);
|
|
1699
|
+
if (renamed === void 0) {
|
|
1700
|
+
return void 0;
|
|
1701
|
+
}
|
|
1702
|
+
const subpath = value.slice(name.length);
|
|
1703
|
+
return { to: `${renamed}${subpath}`, isDeep: subpath !== "" && subpath !== PATH_SEPARATOR$7 };
|
|
976
1704
|
}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1705
|
+
|
|
1706
|
+
const isMap = (node) => node?.kind === "map";
|
|
1707
|
+
const isScalar = (node) => node.kind === "scalar";
|
|
1708
|
+
function textEntries(map) {
|
|
1709
|
+
return map.entries.flatMap(({ key, value }) => isScalar(key) ? [{ key, value }] : []);
|
|
1710
|
+
}
|
|
1711
|
+
function valuesAt(map, key) {
|
|
1712
|
+
return textEntries(map).filter((entry) => entry.key.value === key).map((entry) => entry.value);
|
|
1713
|
+
}
|
|
1714
|
+
function mapsAt(map, key) {
|
|
1715
|
+
return valuesAt(map, key).filter(isMap);
|
|
1716
|
+
}
|
|
1717
|
+
function stringsBelow(node, path) {
|
|
1718
|
+
switch (node.kind) {
|
|
1719
|
+
case "scalar":
|
|
1720
|
+
return [{ ...node, path }];
|
|
1721
|
+
case "list":
|
|
1722
|
+
return node.items.flatMap((item, index) => stringsBelow(item, [...path, index]));
|
|
1723
|
+
case "map":
|
|
1724
|
+
return textEntries(node).flatMap(
|
|
1725
|
+
({ key, value }) => stringsBelow(value, [...path, key.value])
|
|
1726
|
+
);
|
|
1727
|
+
default:
|
|
1728
|
+
return [];
|
|
982
1729
|
}
|
|
983
|
-
const renamed = text.replace(COMMAND_RE, () => {
|
|
984
|
-
changes.push({ kind: "command-renamed", location });
|
|
985
|
-
return MIGRATED_GENERATOR_COMMAND;
|
|
986
|
-
});
|
|
987
|
-
return { text: renameFlags(renamed, location, changes), changes, manualSteps };
|
|
988
1730
|
}
|
|
989
|
-
function
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1731
|
+
function documentStrings(documents) {
|
|
1732
|
+
return documents.flatMap((document) => stringsBelow(document, []));
|
|
1733
|
+
}
|
|
1734
|
+
function ownerKey(path) {
|
|
1735
|
+
return path.findLast((part) => typeof part === "string");
|
|
1736
|
+
}
|
|
1737
|
+
function unreadableConfig(text, offset) {
|
|
1738
|
+
if (!namesOriginalPackage(text)) {
|
|
1739
|
+
return unchanged(text);
|
|
1740
|
+
}
|
|
1741
|
+
const position = offset === void 0 ? void 0 : { line: lineAt(text, offset), column: columnAt(text, offset) };
|
|
1742
|
+
return { ...unchanged(text), manualSteps: [{ kind: "config-unreadable", position }] };
|
|
999
1743
|
}
|
|
1000
1744
|
|
|
1001
1745
|
const PARENT_SEPARATOR = ">";
|
|
1002
|
-
const PATH_SEPARATOR = "/";
|
|
1003
|
-
const SCOPE_PREFIX = "@";
|
|
1746
|
+
const PATH_SEPARATOR$6 = "/";
|
|
1747
|
+
const SCOPE_PREFIX$2 = "@";
|
|
1004
1748
|
function overrideTarget(key) {
|
|
1005
1749
|
const child = key.split(PARENT_SEPARATOR).at(-1) ?? key;
|
|
1006
|
-
const segments = child.split(PATH_SEPARATOR);
|
|
1007
|
-
const isScoped = segments.length > 1 && segments.at(-2)?.startsWith(SCOPE_PREFIX) === true;
|
|
1008
|
-
const name = isScoped ? segments.slice(-2).join(PATH_SEPARATOR) : segments.at(-1) ?? child;
|
|
1009
|
-
const versionAt = name.indexOf(SCOPE_PREFIX, 1);
|
|
1750
|
+
const segments = child.split(PATH_SEPARATOR$6);
|
|
1751
|
+
const isScoped = segments.length > 1 && segments.at(-2)?.startsWith(SCOPE_PREFIX$2) === true;
|
|
1752
|
+
const name = isScoped ? segments.slice(-2).join(PATH_SEPARATOR$6) : segments.at(-1) ?? child;
|
|
1753
|
+
const versionAt = name.indexOf(SCOPE_PREFIX$2, 1);
|
|
1010
1754
|
return versionAt === -1 ? name : name.slice(0, versionAt);
|
|
1011
1755
|
}
|
|
1012
|
-
function
|
|
1013
|
-
|
|
1756
|
+
function overriddenPackages(keys) {
|
|
1757
|
+
const targets = keys.map(overrideTarget);
|
|
1758
|
+
return [...new Set(targets.filter((target) => PACKAGE_RENAMES.has(target)))];
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
const isEditable = (node) => node.kind === "scalar" && node.encode !== void 0;
|
|
1762
|
+
const replaceScalar = (scalar, value) => ({
|
|
1763
|
+
start: scalar.start,
|
|
1764
|
+
end: scalar.end,
|
|
1765
|
+
text: scalar.encode(value)
|
|
1766
|
+
});
|
|
1767
|
+
function specEntries(map) {
|
|
1768
|
+
return textEntries(map).flatMap(({ key, value }) => {
|
|
1769
|
+
if (!isEditable(key)) {
|
|
1770
|
+
return [];
|
|
1771
|
+
}
|
|
1772
|
+
const spec = isEditable(value) ? { value: value.value, replace: (to) => replaceScalar(value, to) } : void 0;
|
|
1773
|
+
return [{ name: key.value, spec, renameKey: (name) => replaceScalar(key, name) }];
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
function keyEntries(map) {
|
|
1777
|
+
return textEntries(map).map(({ key }) => key).filter(isEditable).map((key) => ({ name: key.value, rename: (name) => replaceScalar(key, name) }));
|
|
1778
|
+
}
|
|
1779
|
+
function itemEntries(list) {
|
|
1780
|
+
return list.items.filter(isEditable).map((item) => ({ name: item.value, rename: (name) => replaceScalar(item, name) }));
|
|
1781
|
+
}
|
|
1782
|
+
function nestedKeys(node) {
|
|
1783
|
+
if (node.kind !== "map") {
|
|
1014
1784
|
return [];
|
|
1015
1785
|
}
|
|
1016
|
-
return
|
|
1786
|
+
return textEntries(node).flatMap(({ key, value }) => [key.value, ...nestedKeys(value)]);
|
|
1017
1787
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1788
|
+
|
|
1789
|
+
const PATH_SEPARATOR$5 = ".";
|
|
1790
|
+
function catalogMaps(root, prefix = "") {
|
|
1791
|
+
const maps = mapsAt(root, CATALOG_FIELD).map((map) => ({
|
|
1792
|
+
name: DEFAULT_CATALOG,
|
|
1793
|
+
label: `${prefix}${CATALOG_FIELD}`,
|
|
1794
|
+
map
|
|
1795
|
+
}));
|
|
1796
|
+
for (const catalogs of mapsAt(root, NAMED_CATALOGS_FIELD)) {
|
|
1797
|
+
for (const { key, value } of textEntries(catalogs)) {
|
|
1798
|
+
if (isMap(value)) {
|
|
1799
|
+
const label = [prefix + NAMED_CATALOGS_FIELD, key.value].join(PATH_SEPARATOR$5);
|
|
1800
|
+
maps.push({ name: key.value, label, map: value });
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
return maps;
|
|
1805
|
+
}
|
|
1806
|
+
const manifestCatalogMaps = (manifest) => [
|
|
1807
|
+
...catalogMaps(manifest),
|
|
1808
|
+
...mapsAt(manifest, WORKSPACES_FIELD).flatMap(
|
|
1809
|
+
(workspaces) => catalogMaps(workspaces, `${WORKSPACES_FIELD}${PATH_SEPARATOR$5}`)
|
|
1810
|
+
)
|
|
1811
|
+
];
|
|
1812
|
+
function entriesInUse(manifests) {
|
|
1813
|
+
const inUse = new CatalogEntries();
|
|
1814
|
+
const fields = manifests.flatMap(
|
|
1815
|
+
(manifest) => DEPENDENCY_FIELDS.flatMap((field) => mapsAt(manifest, field))
|
|
1816
|
+
);
|
|
1817
|
+
for (const dependencies of fields) {
|
|
1818
|
+
const entries = specEntries(dependencies);
|
|
1819
|
+
const names = new Set(entries.map((entry) => entry.name));
|
|
1820
|
+
for (const { name, spec } of entries) {
|
|
1821
|
+
const catalog = spec === void 0 ? void 0 : catalogOfSpec(spec.value);
|
|
1822
|
+
const to = PACKAGE_RENAMES.get(name);
|
|
1823
|
+
if (catalog !== void 0 && to !== void 0 && names.has(to)) {
|
|
1824
|
+
inUse.add(catalog, name);
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
return inUse;
|
|
1829
|
+
}
|
|
1830
|
+
function nestedTexts(node) {
|
|
1831
|
+
if (node.kind !== "map") {
|
|
1832
|
+
return [];
|
|
1833
|
+
}
|
|
1834
|
+
return textEntries(node).flatMap(
|
|
1835
|
+
({ key, value }) => value.kind === "scalar" ? [{ key: key.value, value: value.value }] : nestedTexts(value)
|
|
1836
|
+
);
|
|
1837
|
+
}
|
|
1838
|
+
function addOverrideUses(documents, inUse) {
|
|
1839
|
+
const sections = documents.flatMap(
|
|
1840
|
+
(document) => OVERRIDE_FIELDS.flatMap((field) => valuesAt(document, field))
|
|
1841
|
+
);
|
|
1842
|
+
for (const { key, value } of sections.flatMap(nestedTexts)) {
|
|
1843
|
+
const catalog = catalogOfSpec(value);
|
|
1844
|
+
const name = overrideTarget(key);
|
|
1845
|
+
if (catalog !== void 0 && PACKAGE_RENAMES.has(name)) {
|
|
1846
|
+
inUse.add(catalog, name);
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
function planCatalogs({ manifests, catalogFiles }) {
|
|
1851
|
+
const inUse = entriesInUse(manifests);
|
|
1852
|
+
addOverrideUses([...manifests, ...catalogFiles], inUse);
|
|
1853
|
+
const kept = new CatalogEntries();
|
|
1854
|
+
const catalogs = [
|
|
1855
|
+
...catalogFiles.flatMap((file) => catalogMaps(file)),
|
|
1856
|
+
...manifests.flatMap(manifestCatalogMaps)
|
|
1857
|
+
];
|
|
1858
|
+
for (const { name: catalog, map } of catalogs) {
|
|
1859
|
+
const entries = specEntries(map);
|
|
1860
|
+
const names = new Set(entries.map((entry) => entry.name));
|
|
1861
|
+
for (const { name, spec } of entries) {
|
|
1862
|
+
const to = PACKAGE_RENAMES.get(name);
|
|
1863
|
+
const stays = spec === void 0 || inUse.has(catalog, name);
|
|
1864
|
+
if (to !== void 0 && !names.has(to) && stays) {
|
|
1865
|
+
kept.add(catalog, name);
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
return { inUse, kept };
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
const NO_LONGER_INSTALLED = /* @__PURE__ */ new Map([
|
|
1873
|
+
["openapi-typesafe-helpers", ["openapi-fetch", "openapi-react-query"]]
|
|
1874
|
+
]);
|
|
1875
|
+
const ORIGINAL_NAMES = new Map(
|
|
1876
|
+
[...PACKAGE_RENAMES].map(([original, successor]) => [successor, original])
|
|
1877
|
+
);
|
|
1878
|
+
const PATH_SEPARATOR$4 = "/";
|
|
1879
|
+
function namesUninstalledPackage(text) {
|
|
1880
|
+
return [...NO_LONGER_INSTALLED.keys()].some(
|
|
1881
|
+
(name) => text.includes(name) || text.includes(ORIGINAL_NAMES.get(name) ?? name)
|
|
1882
|
+
);
|
|
1883
|
+
}
|
|
1884
|
+
const directoryOf = (path) => path.slice(0, path.lastIndexOf(PATH_SEPARATOR$4) + 1);
|
|
1885
|
+
function declaredNames(manifest) {
|
|
1886
|
+
return DEPENDENCY_FIELDS.flatMap((field) => mapsAt(manifest, field)).flatMap(textEntries).map(({ key }) => key.value);
|
|
1887
|
+
}
|
|
1888
|
+
function isDeclaredFor(manifests, filePath, name) {
|
|
1889
|
+
const original = ORIGINAL_NAMES.get(name);
|
|
1890
|
+
const containing = manifests.filter(({ path }) => filePath.startsWith(directoryOf(path)));
|
|
1891
|
+
const declares = ({ names }) => names === void 0 || names.has(name) || original !== void 0 && names.has(original);
|
|
1892
|
+
return containing.length === 0 || containing.some(declares);
|
|
1893
|
+
}
|
|
1894
|
+
const packageOfSpecifier = (specifier) => {
|
|
1895
|
+
const slash = specifier.indexOf(PATH_SEPARATOR$4);
|
|
1896
|
+
return slash === -1 ? specifier : specifier.slice(0, slash);
|
|
1897
|
+
};
|
|
1898
|
+
function undeclaredImportSteps(imports, filePath, manifests) {
|
|
1899
|
+
return imports.flatMap(({ specifier, line }) => {
|
|
1900
|
+
const name = packageOfSpecifier(specifier);
|
|
1901
|
+
const installers = NO_LONGER_INSTALLED.get(name);
|
|
1902
|
+
return installers === void 0 || isDeclaredFor(manifests, filePath, name) ? [] : [{ kind: "package-not-declared", line, name, installers }];
|
|
1903
|
+
});
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
function assertNever(value) {
|
|
1907
|
+
throw new Error(`Unexpected variant: ${JSON.stringify(value)}`);
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
const KEPT_PROTOCOLS = ["catalog:", "workspace:"];
|
|
1911
|
+
const RANGE_SEPARATOR = "||";
|
|
1912
|
+
const HYPHEN = "-";
|
|
1913
|
+
const WHITE_SPACE_RE = /\s+/;
|
|
1914
|
+
const SPACED_OPERATOR_RE = /(<=|>=|<|>|=|~>|~|\^)\s+/g;
|
|
1915
|
+
const OPERATOR = String.raw`(<=|>=|<|>|=|~>|~|\^)?`;
|
|
1916
|
+
const VERSION_PART = String.raw`(\d+|[xX*])`;
|
|
1917
|
+
const LABELS = String.raw`(?:-[\w.-]+)?(?:\+[\w.-]+)?`;
|
|
1918
|
+
const COMPARATOR_RE = new RegExp(
|
|
1919
|
+
String.raw`^${OPERATOR}v?${VERSION_PART}(?:\.${VERSION_PART})?(?:\.${VERSION_PART})?${LABELS}$`
|
|
1920
|
+
);
|
|
1921
|
+
const NUMBER_RE = /^\d+$/;
|
|
1922
|
+
const OPERATOR_STYLES = {
|
|
1923
|
+
"^": "^",
|
|
1924
|
+
// `>=` means "this or newer"; with a new package line, a caret range says the same safely.
|
|
1925
|
+
">=": "^",
|
|
1926
|
+
">": "^",
|
|
1927
|
+
"~": "~",
|
|
1928
|
+
"~>": "~"
|
|
1929
|
+
};
|
|
1930
|
+
const isNumber = (part) => part !== void 0 && NUMBER_RE.test(part);
|
|
1931
|
+
function versionStyle(major, minor, patch) {
|
|
1932
|
+
if (!isNumber(major)) {
|
|
1933
|
+
return void 0;
|
|
1934
|
+
}
|
|
1935
|
+
if (isNumber(minor) && isNumber(patch)) {
|
|
1936
|
+
return "";
|
|
1937
|
+
}
|
|
1938
|
+
return isNumber(minor) ? "~" : "^";
|
|
1939
|
+
}
|
|
1940
|
+
function rangeStyle(spec) {
|
|
1941
|
+
const sets = spec.replace(SPACED_OPERATOR_RE, "$1").split(RANGE_SEPARATOR);
|
|
1942
|
+
const tokens = sets.map((set) => set.trim().split(WHITE_SPACE_RE));
|
|
1943
|
+
const comparators = tokens.flat().filter((token) => token !== HYPHEN);
|
|
1944
|
+
const matches = comparators.map((comparator) => COMPARATOR_RE.exec(comparator));
|
|
1945
|
+
const first = matches[0];
|
|
1946
|
+
if (first === void 0 || first === null || matches.includes(null)) {
|
|
1947
|
+
return void 0;
|
|
1948
|
+
}
|
|
1949
|
+
const operator = first[1];
|
|
1950
|
+
const isHyphenRange = tokens[0]?.[1] === HYPHEN;
|
|
1951
|
+
if (operator !== void 0 && operator !== "=") {
|
|
1952
|
+
return OPERATOR_STYLES[operator];
|
|
1953
|
+
}
|
|
1954
|
+
return isHyphenRange ? "^" : versionStyle(first[2], first[3], first[4]);
|
|
1955
|
+
}
|
|
1956
|
+
function migrateVersionSpec(spec, version) {
|
|
1957
|
+
if (KEPT_PROTOCOLS.some((protocol) => spec.startsWith(protocol))) {
|
|
1958
|
+
return { kind: "kept", spec, isReported: false };
|
|
1959
|
+
}
|
|
1960
|
+
const style = rangeStyle(spec);
|
|
1961
|
+
if (style === void 0) {
|
|
1962
|
+
return { kind: "kept", spec, isReported: true };
|
|
1963
|
+
}
|
|
1964
|
+
return { kind: "replaced", spec: `${style}${version}` };
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
const NPM_PROTOCOL = "npm:";
|
|
1968
|
+
const SCOPE_PREFIX$1 = "@";
|
|
1969
|
+
const PATH_SEPARATOR$3 = "/";
|
|
1970
|
+
const VERSION_SEPARATOR = "@";
|
|
1971
|
+
function nameEnd(text) {
|
|
1972
|
+
const searchFrom = text.startsWith(SCOPE_PREFIX$1) ? text.indexOf(PATH_SEPARATOR$3) + 1 : 0;
|
|
1973
|
+
const ends = [VERSION_SEPARATOR, PATH_SEPARATOR$3].map((separator) => text.indexOf(separator, Math.max(searchFrom, 1))).filter((index) => index !== -1);
|
|
1974
|
+
return ends.length === 0 ? text.length : Math.min(...ends);
|
|
1975
|
+
}
|
|
1976
|
+
function parseNpmSpec(spec) {
|
|
1977
|
+
if (!spec.startsWith(NPM_PROTOCOL)) {
|
|
1978
|
+
return void 0;
|
|
1979
|
+
}
|
|
1980
|
+
const body = spec.slice(NPM_PROTOCOL.length);
|
|
1981
|
+
const lead = body.startsWith(PATH_SEPARATOR$3) ? PATH_SEPARATOR$3 : "";
|
|
1982
|
+
const text = body.slice(lead.length);
|
|
1983
|
+
const end = nameEnd(text);
|
|
1984
|
+
const name = text.slice(0, end);
|
|
1985
|
+
if (!text.startsWith(VERSION_SEPARATOR, end)) {
|
|
1986
|
+
return { lead, name, range: void 0, rest: text.slice(end) };
|
|
1987
|
+
}
|
|
1988
|
+
const rangeEnd = text.indexOf(PATH_SEPARATOR$3, end);
|
|
1989
|
+
const restStart = rangeEnd === -1 ? text.length : rangeEnd;
|
|
1990
|
+
return { lead, name, range: text.slice(end + 1, restStart), rest: text.slice(restStart) };
|
|
1991
|
+
}
|
|
1992
|
+
function migrateNpmSpec(spec, version) {
|
|
1993
|
+
const parsed = parseNpmSpec(spec);
|
|
1994
|
+
const renamed = parsed === void 0 ? void 0 : PACKAGE_RENAMES.get(parsed.name);
|
|
1995
|
+
if (parsed === void 0 || renamed === void 0) {
|
|
1996
|
+
return void 0;
|
|
1997
|
+
}
|
|
1998
|
+
const head = `${NPM_PROTOCOL}${parsed.lead}${renamed}`;
|
|
1999
|
+
const isDeep = parsed.rest !== "" && parsed.rest !== PATH_SEPARATOR$3;
|
|
2000
|
+
if (parsed.range === void 0) {
|
|
2001
|
+
return { spec: `${head}${parsed.rest}`, isReported: false, isDeep };
|
|
2002
|
+
}
|
|
2003
|
+
const decision = migrateVersionSpec(parsed.range, version);
|
|
2004
|
+
const isReported = decision.kind === "kept" && decision.isReported;
|
|
2005
|
+
const migrated = `${head}${VERSION_SEPARATOR}${decision.spec}${parsed.rest}`;
|
|
2006
|
+
return { spec: migrated, isReported, isDeep };
|
|
1021
2007
|
}
|
|
1022
2008
|
|
|
1023
2009
|
const SCHEME_SEPARATOR = "://";
|
|
@@ -1032,140 +2018,1121 @@ function scanAuthority(text, start) {
|
|
|
1032
2018
|
userinfoEnd = end;
|
|
1033
2019
|
}
|
|
1034
2020
|
}
|
|
1035
|
-
return { end, userinfoEnd };
|
|
2021
|
+
return { end, userinfoEnd };
|
|
2022
|
+
}
|
|
2023
|
+
function hideUrlCredentials(text) {
|
|
2024
|
+
let hidden = "";
|
|
2025
|
+
let copiedUpTo = 0;
|
|
2026
|
+
for (let found = text.indexOf(SCHEME_SEPARATOR); found !== -1; ) {
|
|
2027
|
+
const start = found + SCHEME_SEPARATOR.length;
|
|
2028
|
+
const { end, userinfoEnd } = scanAuthority(text, start);
|
|
2029
|
+
if (userinfoEnd !== -1) {
|
|
2030
|
+
hidden += `${text.slice(copiedUpTo, start)}${HIDDEN_USERINFO}`;
|
|
2031
|
+
copiedUpTo = userinfoEnd;
|
|
2032
|
+
}
|
|
2033
|
+
found = text.indexOf(SCHEME_SEPARATOR, end);
|
|
2034
|
+
}
|
|
2035
|
+
return `${hidden}${text.slice(copiedUpTo)}`;
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
const newFindings = () => ({ edits: [], changes: [], manualSteps: [] });
|
|
2039
|
+
const PEER_FIELD = "peerDependencies";
|
|
2040
|
+
const mapLabel = (map) => map.kind === "catalog" ? map.catalog : map.field;
|
|
2041
|
+
const renamedKey = (name, map) => map.kind === "imports" ? renamedSpecifier(name)?.to : PACKAGE_RENAMES.get(name);
|
|
2042
|
+
function migrateSpec(spec, version) {
|
|
2043
|
+
const npm = migrateNpmSpec(spec, version);
|
|
2044
|
+
if (npm !== void 0) {
|
|
2045
|
+
return { spec: npm.spec, isReported: npm.isReported };
|
|
2046
|
+
}
|
|
2047
|
+
const decision = migrateVersionSpec(spec, version);
|
|
2048
|
+
return { spec: decision.spec, isReported: decision.kind === "kept" && decision.isReported };
|
|
2049
|
+
}
|
|
2050
|
+
function isFree({ from, to, field, names }, findings) {
|
|
2051
|
+
if (names.has(to)) {
|
|
2052
|
+
findings.manualSteps.push({ kind: "name-collision", field, from, to });
|
|
2053
|
+
return false;
|
|
2054
|
+
}
|
|
2055
|
+
return true;
|
|
2056
|
+
}
|
|
2057
|
+
function migrateAlias(entry, { map, version, findings }) {
|
|
2058
|
+
const migrated = entry.spec === void 0 ? void 0 : migrateNpmSpec(entry.spec.value, version);
|
|
2059
|
+
if (entry.spec === void 0 || migrated === void 0) {
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2062
|
+
findings.edits.push(entry.spec.replace(migrated.spec));
|
|
2063
|
+
const spec = hideUrlCredentials(migrated.spec);
|
|
2064
|
+
findings.changes.push({ kind: "alias-renamed", field: mapLabel(map), name: entry.name, spec });
|
|
2065
|
+
if (migrated.isReported) {
|
|
2066
|
+
findings.manualSteps.push({ kind: "spec-kept", name: entry.name, spec });
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
function renamedChange(map, from, to, spec) {
|
|
2070
|
+
switch (map.kind) {
|
|
2071
|
+
case "dependencies":
|
|
2072
|
+
return { kind: "dependency-renamed", field: map.field, from, to, spec };
|
|
2073
|
+
case "catalog":
|
|
2074
|
+
return { kind: "catalog-entry-renamed", catalog: map.catalog, from, to, spec };
|
|
2075
|
+
case "imports":
|
|
2076
|
+
return { kind: "import-renamed", field: map.field, from, to, target: spec };
|
|
2077
|
+
default:
|
|
2078
|
+
return assertNever(map);
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
function migrateEntry(entry, spec, to, { map, version, findings }) {
|
|
2082
|
+
const old = spec.value;
|
|
2083
|
+
const migrated = migrateSpec(old, version);
|
|
2084
|
+
findings.edits.push(entry.renameKey(to));
|
|
2085
|
+
if (migrated.spec !== old) {
|
|
2086
|
+
findings.edits.push(spec.replace(migrated.spec));
|
|
2087
|
+
}
|
|
2088
|
+
findings.changes.push(renamedChange(map, entry.name, to, hideUrlCredentials(migrated.spec)));
|
|
2089
|
+
if (migrated.isReported) {
|
|
2090
|
+
findings.manualSteps.push({ kind: "spec-kept", name: to, spec: hideUrlCredentials(old) });
|
|
2091
|
+
}
|
|
2092
|
+
if (map.kind === "dependencies" && map.field === PEER_FIELD) {
|
|
2093
|
+
findings.manualSteps.push({ kind: "peer-dependency-renamed", name: to });
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
function catalogHold(entry, spec, { map, catalogs }) {
|
|
2097
|
+
if (map.kind === "catalog") {
|
|
2098
|
+
const isInUse = catalogs.inUse.has(map.name, entry.name);
|
|
2099
|
+
return isInUse ? { kind: "catalog-entry-in-use", catalog: map.catalog, name: entry.name } : void 0;
|
|
2100
|
+
}
|
|
2101
|
+
const catalog = map.kind === "dependencies" ? catalogOfSpec(spec.value) : void 0;
|
|
2102
|
+
return catalog !== void 0 && catalogs.kept.has(catalog, entry.name) ? { kind: "catalog-reference-kept", field: map.field, name: entry.name, catalog } : void 0;
|
|
2103
|
+
}
|
|
2104
|
+
function migrateSpecMap(entries, context) {
|
|
2105
|
+
const names = new Set(entries.map((entry) => entry.name));
|
|
2106
|
+
for (const entry of entries) {
|
|
2107
|
+
const to = renamedKey(entry.name, context.map);
|
|
2108
|
+
const field = mapLabel(context.map);
|
|
2109
|
+
if (to === void 0) {
|
|
2110
|
+
migrateAlias(entry, context);
|
|
2111
|
+
continue;
|
|
2112
|
+
}
|
|
2113
|
+
if (!isFree({ from: entry.name, to, field, names }, context.findings)) {
|
|
2114
|
+
continue;
|
|
2115
|
+
}
|
|
2116
|
+
const hold = entry.spec === void 0 ? void 0 : catalogHold(entry, entry.spec, context);
|
|
2117
|
+
if (entry.spec === void 0) {
|
|
2118
|
+
context.findings.manualSteps.push({ kind: "entry-not-edited", field, name: entry.name });
|
|
2119
|
+
} else if (hold !== void 0) {
|
|
2120
|
+
context.findings.manualSteps.push(hold);
|
|
2121
|
+
} else {
|
|
2122
|
+
migrateEntry(entry, entry.spec, to, context);
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
function renameNames(entries, field, findings) {
|
|
2127
|
+
const names = new Set(entries.map((entry) => entry.name));
|
|
2128
|
+
for (const entry of entries) {
|
|
2129
|
+
const to = PACKAGE_RENAMES.get(entry.name);
|
|
2130
|
+
if (to !== void 0 && isFree({ from: entry.name, to, field, names }, findings)) {
|
|
2131
|
+
findings.edits.push(entry.rename(to));
|
|
2132
|
+
findings.changes.push({
|
|
2133
|
+
kind: "package-name-renamed",
|
|
2134
|
+
location: field,
|
|
2135
|
+
from: entry.name,
|
|
2136
|
+
to
|
|
2137
|
+
});
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
const IMPORTS_FIELD = "imports";
|
|
2143
|
+
const SCOPES_FIELD = "scopes";
|
|
2144
|
+
const FIELD_SEPARATOR = ".";
|
|
2145
|
+
function migrateImports(config, version, findings) {
|
|
2146
|
+
for (const imports of mapsAt(config, IMPORTS_FIELD)) {
|
|
2147
|
+
const map = { kind: "imports", field: IMPORTS_FIELD };
|
|
2148
|
+
migrateSpecMap(specEntries(imports), { map, version, findings, catalogs: NO_CATALOG_PLAN });
|
|
2149
|
+
}
|
|
2150
|
+
for (const scopes of mapsAt(config, SCOPES_FIELD)) {
|
|
2151
|
+
for (const { key, value } of textEntries(scopes)) {
|
|
2152
|
+
if (isMap(value)) {
|
|
2153
|
+
const field = [SCOPES_FIELD, key.value].join(FIELD_SEPARATOR);
|
|
2154
|
+
const map = { kind: "imports", field };
|
|
2155
|
+
migrateSpecMap(specEntries(value), { map, version, findings, catalogs: NO_CATALOG_PLAN });
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
const PATH_SEPARATOR$2 = "/";
|
|
2161
|
+
const SCOPE_PREFIX = "@";
|
|
2162
|
+
function importMapKeys(config) {
|
|
2163
|
+
const scoped = mapsAt(config, SCOPES_FIELD).flatMap(textEntries).flatMap(({ value }) => isMap(value) ? [value] : []);
|
|
2164
|
+
return [...mapsAt(config, IMPORTS_FIELD), ...scoped].flatMap(textEntries).map(({ key }) => key.value);
|
|
2165
|
+
}
|
|
2166
|
+
function importMapNames(config) {
|
|
2167
|
+
return importMapKeys(config).map((key) => {
|
|
2168
|
+
const parts = key.split(PATH_SEPARATOR$2);
|
|
2169
|
+
return key.startsWith(SCOPE_PREFIX) ? parts.slice(0, 2).join(PATH_SEPARATOR$2) : parts[0] ?? key;
|
|
2170
|
+
});
|
|
2171
|
+
}
|
|
2172
|
+
function migrateImportMap(text, read, version) {
|
|
2173
|
+
if (read.kind === "unreadable") {
|
|
2174
|
+
return unreadableConfig(text, read.offset);
|
|
2175
|
+
}
|
|
2176
|
+
const findings = newFindings();
|
|
2177
|
+
for (const config of read.documents.filter(isMap)) {
|
|
2178
|
+
migrateImports(config, version, findings);
|
|
2179
|
+
}
|
|
2180
|
+
const { edits, changes, manualSteps } = findings;
|
|
2181
|
+
return { text: applyTextEdits(text, edits), changes, manualSteps };
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
const NONE = /* @__PURE__ */ new Set();
|
|
2185
|
+
const PACKAGE_OPTIONS = /* @__PURE__ */ new Set(["-p", "--package"]);
|
|
2186
|
+
const PACKAGE_MANAGER_OPTIONS = /* @__PURE__ */ new Set([
|
|
2187
|
+
"--filter",
|
|
2188
|
+
"-F",
|
|
2189
|
+
"--dir",
|
|
2190
|
+
"-C",
|
|
2191
|
+
"--prefix",
|
|
2192
|
+
"--cwd",
|
|
2193
|
+
"--workspace"
|
|
2194
|
+
]);
|
|
2195
|
+
const launcher = (words, packageOptions = NONE, valueOptions = NONE) => {
|
|
2196
|
+
const allValueOptions = /* @__PURE__ */ new Set([...valueOptions, ...packageOptions]);
|
|
2197
|
+
return { words, packageOptions, valueOptions: allValueOptions, operands: 0 };
|
|
2198
|
+
};
|
|
2199
|
+
const CONTAINER_OPTIONS = /* @__PURE__ */ new Set([
|
|
2200
|
+
"-e",
|
|
2201
|
+
"--env",
|
|
2202
|
+
"--env-file",
|
|
2203
|
+
"-v",
|
|
2204
|
+
"--volume",
|
|
2205
|
+
"--mount",
|
|
2206
|
+
"-p",
|
|
2207
|
+
"--publish",
|
|
2208
|
+
"-w",
|
|
2209
|
+
"--workdir",
|
|
2210
|
+
"-u",
|
|
2211
|
+
"--user",
|
|
2212
|
+
"--name",
|
|
2213
|
+
"--network",
|
|
2214
|
+
"--entrypoint",
|
|
2215
|
+
"--platform",
|
|
2216
|
+
"-l",
|
|
2217
|
+
"--label",
|
|
2218
|
+
"-h",
|
|
2219
|
+
"--hostname",
|
|
2220
|
+
"--add-host"
|
|
2221
|
+
]);
|
|
2222
|
+
const container = (words) => ({
|
|
2223
|
+
...launcher(words, NONE, CONTAINER_OPTIONS),
|
|
2224
|
+
operands: 1
|
|
2225
|
+
});
|
|
2226
|
+
const LAUNCHERS = [
|
|
2227
|
+
launcher(["npm", "exec"], PACKAGE_OPTIONS),
|
|
2228
|
+
launcher(["npm", "x"], PACKAGE_OPTIONS),
|
|
2229
|
+
launcher(["pnpm", "exec"], NONE, PACKAGE_MANAGER_OPTIONS),
|
|
2230
|
+
launcher(["pnpm", "dlx"], /* @__PURE__ */ new Set(["--package"])),
|
|
2231
|
+
launcher(["yarn", "dlx"], PACKAGE_OPTIONS),
|
|
2232
|
+
launcher(["yarn", "exec"]),
|
|
2233
|
+
launcher(["bun", "x"], PACKAGE_OPTIONS),
|
|
2234
|
+
launcher(["deno", "run"], NONE, /* @__PURE__ */ new Set(["--config", "-c", "--import-map", "--location"])),
|
|
2235
|
+
launcher(["deno", "x"]),
|
|
2236
|
+
launcher(["npx"], PACKAGE_OPTIONS, /* @__PURE__ */ new Set(["-c", "--call"])),
|
|
2237
|
+
launcher(["pnpx"], PACKAGE_OPTIONS),
|
|
2238
|
+
launcher(["bunx"], PACKAGE_OPTIONS),
|
|
2239
|
+
launcher(["pnpm"], NONE, PACKAGE_MANAGER_OPTIONS),
|
|
2240
|
+
launcher(["yarn"], NONE, PACKAGE_MANAGER_OPTIONS),
|
|
2241
|
+
launcher(["node"], NONE, /* @__PURE__ */ new Set(["-r", "--require", "--import", "--loader", "--env-file"])),
|
|
2242
|
+
launcher(["cross-env"]),
|
|
2243
|
+
launcher(["dotenv"], NONE, /* @__PURE__ */ new Set(["-e", "-c", "-v"])),
|
|
2244
|
+
launcher(["env"], NONE, /* @__PURE__ */ new Set(["-u", "--unset", "-C", "--chdir"])),
|
|
2245
|
+
launcher(["exec"]),
|
|
2246
|
+
launcher(["time"]),
|
|
2247
|
+
launcher(["nohup"]),
|
|
2248
|
+
launcher(["sudo"], NONE, /* @__PURE__ */ new Set(["-u", "-g"])),
|
|
2249
|
+
// The shell form of Dockerfile instructions runs the rest of the line in a shell.
|
|
2250
|
+
launcher(["RUN"], NONE, /* @__PURE__ */ new Set(["--mount", "--network", "--security"])),
|
|
2251
|
+
launcher(["CMD"]),
|
|
2252
|
+
launcher(["ENTRYPOINT"]),
|
|
2253
|
+
container(["docker", "run"]),
|
|
2254
|
+
container(["docker", "exec"]),
|
|
2255
|
+
container(["docker", "compose", "run"]),
|
|
2256
|
+
container(["docker", "compose", "exec"]),
|
|
2257
|
+
container(["podman", "run"]),
|
|
2258
|
+
container(["podman", "exec"])
|
|
2259
|
+
];
|
|
2260
|
+
const installers = (manager, subcommands) => subcommands.map((subcommand) => ({ words: [manager, subcommand] }));
|
|
2261
|
+
const NPM_INSTALL = ["install", "i", "add", "update", "up", "uninstall", "remove", "rm", "un"];
|
|
2262
|
+
const INSTALLERS = [
|
|
2263
|
+
...installers("npm", NPM_INSTALL),
|
|
2264
|
+
...installers("pnpm", ["add", "update", "up", "remove", "rm", "uninstall", "un"]),
|
|
2265
|
+
...installers("yarn", ["add", "upgrade", "up", "remove"]),
|
|
2266
|
+
...installers("bun", ["add", "a", "update", "remove", "rm", "install", "i"]),
|
|
2267
|
+
...installers("deno", ["add", "install", "i", "remove", "uninstall"])
|
|
2268
|
+
];
|
|
2269
|
+
const NPM_SPECIFIER_PREFIX = "npm:";
|
|
2270
|
+
|
|
2271
|
+
const ASSIGNMENT_RE$1 = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
2272
|
+
const END_OF_OPTIONS = "--";
|
|
2273
|
+
const isOption = (word) => word.value.startsWith("-") && word.value.length > 1;
|
|
2274
|
+
function skipAssignments(words, start) {
|
|
2275
|
+
let index = start;
|
|
2276
|
+
while (index < words.length && ASSIGNMENT_RE$1.test(words[index].raw)) {
|
|
2277
|
+
index++;
|
|
2278
|
+
}
|
|
2279
|
+
return index;
|
|
2280
|
+
}
|
|
2281
|
+
function matchingPrefix(entries, words, index) {
|
|
2282
|
+
return entries.find(
|
|
2283
|
+
(entry) => entry.words.every((name, offset) => words[index + offset]?.value === name)
|
|
2284
|
+
);
|
|
2285
|
+
}
|
|
2286
|
+
function inlineOptionPackage(word, launcher) {
|
|
2287
|
+
const equals = word.value.indexOf("=");
|
|
2288
|
+
const name = word.value.slice(0, equals);
|
|
2289
|
+
return launcher.packageOptions.has(name) ? { word, value: word.value.slice(equals + 1) } : void 0;
|
|
2290
|
+
}
|
|
2291
|
+
function launchedProgram(words, start, launcher, packages) {
|
|
2292
|
+
let index = start;
|
|
2293
|
+
while (index < words.length && isOption(words[index])) {
|
|
2294
|
+
const option = words[index++];
|
|
2295
|
+
if (option.value === END_OF_OPTIONS) {
|
|
2296
|
+
break;
|
|
2297
|
+
}
|
|
2298
|
+
const inline = option.value.includes("=") ? inlineOptionPackage(option, launcher) : void 0;
|
|
2299
|
+
if (inline !== void 0) {
|
|
2300
|
+
packages.push(inline);
|
|
2301
|
+
}
|
|
2302
|
+
const value = words[index];
|
|
2303
|
+
if (!option.value.includes("=") && launcher.valueOptions.has(option.value) && value) {
|
|
2304
|
+
if (launcher.packageOptions.has(option.value)) {
|
|
2305
|
+
packages.push({ word: value, value: value.value });
|
|
2306
|
+
}
|
|
2307
|
+
index++;
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
return index;
|
|
2311
|
+
}
|
|
2312
|
+
const installArguments = (words, start) => words.slice(start).filter((word) => !isOption(word)).map((word) => ({ word, value: word.value }));
|
|
2313
|
+
function commandRoles(words) {
|
|
2314
|
+
const packages = [];
|
|
2315
|
+
let index = skipAssignments(words, 0);
|
|
2316
|
+
while (index < words.length) {
|
|
2317
|
+
const installer = matchingPrefix(INSTALLERS, words, index);
|
|
2318
|
+
if (installer !== void 0) {
|
|
2319
|
+
packages.push(...installArguments(words, index + installer.words.length));
|
|
2320
|
+
return { program: void 0, packages };
|
|
2321
|
+
}
|
|
2322
|
+
const launcher = matchingPrefix(LAUNCHERS, words, index);
|
|
2323
|
+
if (launcher === void 0) {
|
|
2324
|
+
return { program: index, packages };
|
|
2325
|
+
}
|
|
2326
|
+
const start = index + launcher.words.length;
|
|
2327
|
+
const afterOptions = launchedProgram(words, start, launcher, packages);
|
|
2328
|
+
index = skipAssignments(words, afterOptions + launcher.operands);
|
|
2329
|
+
}
|
|
2330
|
+
return { program: void 0, packages };
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
const COMMAND_SEPARATORS = /* @__PURE__ */ new Set([";", "&", "|", "(", ")", "`", "\n"]);
|
|
2334
|
+
const WORD_BREAKS = /* @__PURE__ */ new Set([" ", " ", "\r", "<", ">"]);
|
|
2335
|
+
const BACKSLASH$1 = "\\";
|
|
2336
|
+
const COMMENT = "#";
|
|
2337
|
+
const DOUBLE_QUOTE_ESCAPES = /* @__PURE__ */ new Set(["$", "`", '"', BACKSLASH$1, "\n"]);
|
|
2338
|
+
function singleQuoted(text, start) {
|
|
2339
|
+
const close = text.indexOf("'", start + 1);
|
|
2340
|
+
const end = close === -1 ? text.length : close + 1;
|
|
2341
|
+
return { end, value: text.slice(start + 1, close === -1 ? text.length : close) };
|
|
2342
|
+
}
|
|
2343
|
+
function doubleQuoted(text, start) {
|
|
2344
|
+
let value = "";
|
|
2345
|
+
let index = start + 1;
|
|
2346
|
+
while (index < text.length && text[index] !== '"') {
|
|
2347
|
+
const next = text[index + 1] ?? "";
|
|
2348
|
+
const isEscape = text[index] === BACKSLASH$1 && DOUBLE_QUOTE_ESCAPES.has(next);
|
|
2349
|
+
value += isEscape ? next === "\n" ? "" : next : text[index];
|
|
2350
|
+
index += isEscape ? 2 : 1;
|
|
2351
|
+
}
|
|
2352
|
+
return { end: Math.min(index + 1, text.length), value };
|
|
2353
|
+
}
|
|
2354
|
+
const isLineContinuation = (text, index) => text[index] === BACKSLASH$1 && (text[index + 1] === "\n" || text.startsWith("\r\n", index + 1));
|
|
2355
|
+
function wordPart(text, index) {
|
|
2356
|
+
const char = text[index];
|
|
2357
|
+
if (char === "'") {
|
|
2358
|
+
return singleQuoted(text, index);
|
|
2359
|
+
}
|
|
2360
|
+
if (char === '"') {
|
|
2361
|
+
return doubleQuoted(text, index);
|
|
2362
|
+
}
|
|
2363
|
+
if (char === BACKSLASH$1 && index + 1 < text.length) {
|
|
2364
|
+
return { end: index + 2, value: text[index + 1] };
|
|
2365
|
+
}
|
|
2366
|
+
return { end: index + 1, value: char };
|
|
2367
|
+
}
|
|
2368
|
+
const endsWord = (text, index) => {
|
|
2369
|
+
const char = text[index];
|
|
2370
|
+
return WORD_BREAKS.has(char) || COMMAND_SEPARATORS.has(char) || isLineContinuation(text, index);
|
|
2371
|
+
};
|
|
2372
|
+
function readWord(text, start) {
|
|
2373
|
+
let value = "";
|
|
2374
|
+
let index = start;
|
|
2375
|
+
while (index < text.length && !endsWord(text, index)) {
|
|
2376
|
+
const part = wordPart(text, index);
|
|
2377
|
+
value += part.value;
|
|
2378
|
+
index = part.end;
|
|
2379
|
+
}
|
|
2380
|
+
return { start, end: index, value, raw: text.slice(start, index) };
|
|
2381
|
+
}
|
|
2382
|
+
function skipTrivia(text, index) {
|
|
2383
|
+
const char = text[index];
|
|
2384
|
+
if (WORD_BREAKS.has(char)) {
|
|
2385
|
+
return index + 1;
|
|
2386
|
+
}
|
|
2387
|
+
if (isLineContinuation(text, index)) {
|
|
2388
|
+
return text.indexOf("\n", index) + 1;
|
|
2389
|
+
}
|
|
2390
|
+
if (char === COMMENT) {
|
|
2391
|
+
const lineEnd = text.indexOf("\n", index);
|
|
2392
|
+
return lineEnd === -1 ? text.length : lineEnd;
|
|
2393
|
+
}
|
|
2394
|
+
return index;
|
|
2395
|
+
}
|
|
2396
|
+
function topLevelCommands(text) {
|
|
2397
|
+
const commands = [[]];
|
|
2398
|
+
let index = 0;
|
|
2399
|
+
while (index < text.length) {
|
|
2400
|
+
const afterTrivia = skipTrivia(text, index);
|
|
2401
|
+
if (afterTrivia !== index) {
|
|
2402
|
+
index = afterTrivia;
|
|
2403
|
+
} else if (COMMAND_SEPARATORS.has(text[index])) {
|
|
2404
|
+
commands.push([]);
|
|
2405
|
+
index++;
|
|
2406
|
+
} else {
|
|
2407
|
+
const word = readWord(text, index);
|
|
2408
|
+
commands.at(-1)?.push(word);
|
|
2409
|
+
index = word.end;
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
return commands.filter((words) => words.length > 0);
|
|
2413
|
+
}
|
|
2414
|
+
const COMMAND_PREFIX_WORDS = /* @__PURE__ */ new Set([
|
|
2415
|
+
"if",
|
|
2416
|
+
"then",
|
|
2417
|
+
"elif",
|
|
2418
|
+
"else",
|
|
2419
|
+
"fi",
|
|
2420
|
+
"while",
|
|
2421
|
+
"until",
|
|
2422
|
+
"do",
|
|
2423
|
+
"done",
|
|
2424
|
+
"!",
|
|
2425
|
+
"{",
|
|
2426
|
+
"}",
|
|
2427
|
+
"esac"
|
|
2428
|
+
]);
|
|
2429
|
+
const CLAUSE_WORDS = /* @__PURE__ */ new Set(["for", "select", "case", "[["]);
|
|
2430
|
+
const FUNCTION_WORD = "function";
|
|
2431
|
+
const FUNCTION_HEADER_LENGTH = 2;
|
|
2432
|
+
const isReserved = (word, words) => word !== void 0 && word.raw === word.value && words.has(word.value);
|
|
2433
|
+
function prefixLength(words, index) {
|
|
2434
|
+
if (isReserved(words[index], COMMAND_PREFIX_WORDS)) {
|
|
2435
|
+
return 1;
|
|
2436
|
+
}
|
|
2437
|
+
const word = words[index];
|
|
2438
|
+
return word?.raw === FUNCTION_WORD ? FUNCTION_HEADER_LENGTH : 0;
|
|
2439
|
+
}
|
|
2440
|
+
function simpleCommand(words) {
|
|
2441
|
+
let first = 0;
|
|
2442
|
+
for (let length = prefixLength(words, 0); length > 0; length = prefixLength(words, first)) {
|
|
2443
|
+
first += length;
|
|
2444
|
+
}
|
|
2445
|
+
return isReserved(words[first], CLAUSE_WORDS) ? [] : words.slice(first);
|
|
2446
|
+
}
|
|
2447
|
+
const QUOTES$2 = /* @__PURE__ */ new Set(["'", '"']);
|
|
2448
|
+
const NESTED_COMMAND_RE = /[\s;&|()`]/;
|
|
2449
|
+
function nestedCommandText(word) {
|
|
2450
|
+
const quote = word.raw[0];
|
|
2451
|
+
const isOneQuotedString = QUOTES$2.has(quote) && word.raw.length > 1 && word.raw.endsWith(quote) && wordPart(word.raw, 0).end === word.raw.length;
|
|
2452
|
+
return isOneQuotedString && NESTED_COMMAND_RE.test(word.value) ? word.raw.slice(1, -1) : void 0;
|
|
2453
|
+
}
|
|
2454
|
+
const shifted = (words, offset) => words.map((word) => ({ ...word, start: word.start + offset, end: word.end + offset }));
|
|
2455
|
+
function shellCommandsAt(text, offset) {
|
|
2456
|
+
return shellCommands(text).map((words) => shifted(words, offset));
|
|
2457
|
+
}
|
|
2458
|
+
function shellCommands(text) {
|
|
2459
|
+
const commands = [];
|
|
2460
|
+
for (const command of topLevelCommands(text).map(simpleCommand)) {
|
|
2461
|
+
if (command.length > 0) {
|
|
2462
|
+
commands.push(command);
|
|
2463
|
+
}
|
|
2464
|
+
for (const word of command) {
|
|
2465
|
+
const inner = nestedCommandText(word);
|
|
2466
|
+
if (inner !== void 0) {
|
|
2467
|
+
const nested = shellCommands(inner).map((words) => shifted(words, word.start + 1));
|
|
2468
|
+
commands.push(...nested);
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
return commands;
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
const QUOTES$1 = /* @__PURE__ */ new Set(["'", '"']);
|
|
2476
|
+
const PATH_SEPARATOR$1 = "/";
|
|
2477
|
+
const BIN_FOLDER = "/.bin/";
|
|
2478
|
+
const OLD_PACKAGE_FOLDER = `node_modules/${GENERATOR_COMMAND}/`;
|
|
2479
|
+
function renameAtEnd(word, name, to) {
|
|
2480
|
+
const at = word.raw.lastIndexOf(name);
|
|
2481
|
+
const tail = word.raw.slice(at + name.length);
|
|
2482
|
+
if (at === -1 || tail !== "" && !QUOTES$1.has(tail)) {
|
|
2483
|
+
return void 0;
|
|
2484
|
+
}
|
|
2485
|
+
return { start: word.start + at, end: word.start + at + name.length, text: to };
|
|
2486
|
+
}
|
|
2487
|
+
function packageReference(value) {
|
|
2488
|
+
const bare = value.startsWith(NPM_SPECIFIER_PREFIX) ? value.slice(NPM_SPECIFIER_PREFIX.length) : value;
|
|
2489
|
+
const at = bare.indexOf("@", 1);
|
|
2490
|
+
return { name: at === -1 ? bare : bare.slice(0, at), isPinned: at !== -1 };
|
|
2491
|
+
}
|
|
2492
|
+
function packageFindings(found, locate) {
|
|
2493
|
+
const { name, isPinned } = packageReference(found.value);
|
|
2494
|
+
const to = PACKAGE_RENAMES.get(name);
|
|
2495
|
+
const offset = found.word.start;
|
|
2496
|
+
if (to === void 0) {
|
|
2497
|
+
return [];
|
|
2498
|
+
}
|
|
2499
|
+
const location = locate(offset);
|
|
2500
|
+
if (isPinned) {
|
|
2501
|
+
return [{ offset, manualStep: { kind: "pinned-package", location, name } }];
|
|
2502
|
+
}
|
|
2503
|
+
const edit = renameAtEnd(found.word, name, to);
|
|
2504
|
+
const change = { kind: "package-name-renamed", location, from: name, to };
|
|
2505
|
+
return edit === void 0 ? [] : [{ offset, edit, change }];
|
|
2506
|
+
}
|
|
2507
|
+
function programPathFindings(word, location) {
|
|
2508
|
+
const offset = word.start;
|
|
2509
|
+
if (word.value.includes(OLD_PACKAGE_FOLDER)) {
|
|
2510
|
+
return [{ offset, manualStep: { kind: "old-package-file", location, path: word.value } }];
|
|
2511
|
+
}
|
|
2512
|
+
const isBin = word.value.endsWith(`${BIN_FOLDER}${GENERATOR_COMMAND}`);
|
|
2513
|
+
const edit = isBin ? renameAtEnd(word, GENERATOR_COMMAND, MIGRATED_GENERATOR_COMMAND) : void 0;
|
|
2514
|
+
return edit === void 0 ? [] : [{ offset, edit, change: { kind: "command-renamed", location } }];
|
|
2515
|
+
}
|
|
2516
|
+
function programFindings(word, location) {
|
|
2517
|
+
if (word.value.includes(PATH_SEPARATOR$1)) {
|
|
2518
|
+
return programPathFindings(word, location);
|
|
2519
|
+
}
|
|
2520
|
+
const { name, isPinned } = packageReference(word.value);
|
|
2521
|
+
if (name !== GENERATOR_COMMAND) {
|
|
2522
|
+
return [];
|
|
2523
|
+
}
|
|
2524
|
+
if (isPinned) {
|
|
2525
|
+
return [{ offset: word.start, manualStep: { kind: "versioned-command", location } }];
|
|
2526
|
+
}
|
|
2527
|
+
const edit = renameAtEnd(word, GENERATOR_COMMAND, MIGRATED_GENERATOR_COMMAND);
|
|
2528
|
+
const change = { kind: "command-renamed", location };
|
|
2529
|
+
return edit === void 0 ? [] : [{ offset: word.start, edit, change }];
|
|
2530
|
+
}
|
|
2531
|
+
function flagFindings(word, locate) {
|
|
2532
|
+
const flag = word.raw.split("=", 1)[0];
|
|
2533
|
+
const to = FLAG_RENAMES.get(flag);
|
|
2534
|
+
if (to === void 0) {
|
|
2535
|
+
return [];
|
|
2536
|
+
}
|
|
2537
|
+
const offset = word.start;
|
|
2538
|
+
const edit = { start: offset, end: offset + flag.length, text: to };
|
|
2539
|
+
return [
|
|
2540
|
+
{ offset, edit, change: { kind: "flag-renamed", location: locate(offset), from: flag, to } }
|
|
2541
|
+
];
|
|
2542
|
+
}
|
|
2543
|
+
const RECIPE_PREFIX_RE$1 = /^[@+-]+(?=.)/;
|
|
2544
|
+
function withoutRecipePrefix(words) {
|
|
2545
|
+
const [first, ...rest] = words;
|
|
2546
|
+
if (first === void 0 || !RECIPE_PREFIX_RE$1.test(first.value)) {
|
|
2547
|
+
return words;
|
|
2548
|
+
}
|
|
2549
|
+
return [{ ...first, value: first.value.replace(RECIPE_PREFIX_RE$1, "") }, ...rest];
|
|
2550
|
+
}
|
|
2551
|
+
const NO_VARIABLES = {
|
|
2552
|
+
programAt: () => void 0,
|
|
2553
|
+
referenceAt: () => void 0
|
|
2554
|
+
};
|
|
2555
|
+
function renamedFlags(value) {
|
|
2556
|
+
const flags = shellCommands(value).flat().map((word) => word.raw.split("=", 1)[0]);
|
|
2557
|
+
return [...new Set(flags.filter((flag) => FLAG_RENAMES.has(flag)))];
|
|
2558
|
+
}
|
|
2559
|
+
function argumentFindings(word, locate, variables) {
|
|
2560
|
+
const reference = variables.referenceAt(word);
|
|
2561
|
+
if (reference === void 0) {
|
|
2562
|
+
return flagFindings(word, locate);
|
|
2563
|
+
}
|
|
2564
|
+
const flags = renamedFlags(reference.value);
|
|
2565
|
+
if (flags.length === 0) {
|
|
2566
|
+
return [];
|
|
2567
|
+
}
|
|
2568
|
+
const manualStep = {
|
|
2569
|
+
kind: "flags-in-variable",
|
|
2570
|
+
location: locate(word.start),
|
|
2571
|
+
variable: reference.written,
|
|
2572
|
+
flags
|
|
2573
|
+
};
|
|
2574
|
+
return [{ offset: word.start, manualStep }];
|
|
2575
|
+
}
|
|
2576
|
+
const oldFlagsOf = (findings) => findings.flatMap(({ change, manualStep }) => {
|
|
2577
|
+
if (change?.kind === "flag-renamed") {
|
|
2578
|
+
return [change.from];
|
|
2579
|
+
}
|
|
2580
|
+
return manualStep?.kind === "flags-in-variable" ? [...manualStep.flags] : [];
|
|
2581
|
+
});
|
|
2582
|
+
function uncertainFlagSteps(program, variable, found, locate) {
|
|
2583
|
+
const flags = [...new Set(oldFlagsOf(found))];
|
|
2584
|
+
if (flags.length === 0) {
|
|
2585
|
+
return [];
|
|
2586
|
+
}
|
|
2587
|
+
const manualStep = {
|
|
2588
|
+
kind: "variable-not-only-generator",
|
|
2589
|
+
location: locate(program.start),
|
|
2590
|
+
variable,
|
|
2591
|
+
flags
|
|
2592
|
+
};
|
|
2593
|
+
return [{ offset: program.start, manualStep }];
|
|
2594
|
+
}
|
|
2595
|
+
function commandFindings(command, locate, variables) {
|
|
2596
|
+
const words = withoutRecipePrefix(command);
|
|
2597
|
+
const roles = commandRoles(words);
|
|
2598
|
+
const findings = roles.packages.flatMap((found2) => packageFindings(found2, locate));
|
|
2599
|
+
const program = roles.program === void 0 ? void 0 : words[roles.program];
|
|
2600
|
+
if (program === void 0) {
|
|
2601
|
+
return findings;
|
|
2602
|
+
}
|
|
2603
|
+
const found = programFindings(program, locate(program.start));
|
|
2604
|
+
findings.push(...found);
|
|
2605
|
+
const isRenamed = found.some((finding) => finding.change?.kind === "command-renamed");
|
|
2606
|
+
const variable = variables.programAt(program);
|
|
2607
|
+
if (!isRenamed && (variable === void 0 || variable.program === "other")) {
|
|
2608
|
+
return findings;
|
|
2609
|
+
}
|
|
2610
|
+
const flags = words.slice(roles.program + 1);
|
|
2611
|
+
const flagFound = flags.flatMap((word) => argumentFindings(word, locate, variables));
|
|
2612
|
+
const isUncertain = variable?.program === "generator-or-other";
|
|
2613
|
+
findings.push(
|
|
2614
|
+
...isUncertain ? uncertainFlagSteps(program, variable.written, flagFound, locate) : flagFound
|
|
2615
|
+
);
|
|
2616
|
+
return findings;
|
|
2617
|
+
}
|
|
2618
|
+
function runsGenerator(command) {
|
|
2619
|
+
const words = withoutRecipePrefix(command);
|
|
2620
|
+
const { program } = commandRoles(words);
|
|
2621
|
+
const word = program === void 0 ? void 0 : words[program];
|
|
2622
|
+
return word !== void 0 && packageReference(word.value).name === GENERATOR_COMMAND;
|
|
2623
|
+
}
|
|
2624
|
+
function shellTextFindings(text, locate) {
|
|
2625
|
+
return commandWordFindings(shellCommands(text), locate);
|
|
2626
|
+
}
|
|
2627
|
+
function commandWordFindings(commands, locate, variables = NO_VARIABLES) {
|
|
2628
|
+
const findings = commands.flatMap((words) => commandFindings(words, locate, variables)).sort((left, right) => left.offset - right.offset);
|
|
2629
|
+
return {
|
|
2630
|
+
edits: findings.flatMap((finding) => finding.edit === void 0 ? [] : [finding.edit]),
|
|
2631
|
+
changes: findings.flatMap((finding) => finding.change === void 0 ? [] : [finding.change]),
|
|
2632
|
+
manualSteps: findings.flatMap(
|
|
2633
|
+
(finding) => finding.manualStep === void 0 ? [] : [finding.manualStep]
|
|
2634
|
+
)
|
|
2635
|
+
};
|
|
2636
|
+
}
|
|
2637
|
+
function migrateShellText(text, locate) {
|
|
2638
|
+
const { edits, changes, manualSteps } = shellTextFindings(text, locate);
|
|
2639
|
+
return { text: applyTextEdits(text, edits), changes, manualSteps };
|
|
2640
|
+
}
|
|
2641
|
+
function migrateCommand(text, location) {
|
|
2642
|
+
return migrateShellText(text, () => location);
|
|
2643
|
+
}
|
|
2644
|
+
function migrateCommandLines(text, file) {
|
|
2645
|
+
const lineOf = lineLocator(text);
|
|
2646
|
+
return migrateShellText(text, (offset) => `${file}:${lineOf(offset)}`);
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
const NO_LOCATION = "";
|
|
2650
|
+
function commandStringSteps(text, strings) {
|
|
2651
|
+
const lineOf = lineLocator(text);
|
|
2652
|
+
return strings.flatMap((found) => {
|
|
2653
|
+
if (found.isModuleSpecifier || PACKAGE_RENAMES.has(found.value)) {
|
|
2654
|
+
return [];
|
|
2655
|
+
}
|
|
2656
|
+
const migrated = migrateCommand(found.value, NO_LOCATION).text;
|
|
2657
|
+
return migrated === found.value ? [] : [{ kind: "command-string", line: lineOf(found.start), to: migrated }];
|
|
2658
|
+
});
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
function canRunGenerator(definition, generators) {
|
|
2662
|
+
switch (definition.kind) {
|
|
2663
|
+
case "command":
|
|
2664
|
+
return definition.runsGenerator;
|
|
2665
|
+
case "reference":
|
|
2666
|
+
return generators.has(definition.target);
|
|
2667
|
+
case "append":
|
|
2668
|
+
return false;
|
|
2669
|
+
default:
|
|
2670
|
+
return assertNever(definition);
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
function canRunOther(definition, others, defined) {
|
|
2674
|
+
switch (definition.kind) {
|
|
2675
|
+
case "command":
|
|
2676
|
+
return !definition.runsGenerator;
|
|
2677
|
+
case "reference":
|
|
2678
|
+
return others.has(definition.target) || !defined.has(definition.target);
|
|
2679
|
+
case "append":
|
|
2680
|
+
return false;
|
|
2681
|
+
default:
|
|
2682
|
+
return assertNever(definition);
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
function variablePrograms(definitions) {
|
|
2686
|
+
const defined = new Set(definitions.map((definition) => definition.name));
|
|
2687
|
+
const generators = /* @__PURE__ */ new Set();
|
|
2688
|
+
const others = /* @__PURE__ */ new Set();
|
|
2689
|
+
for (let size = -1; size !== generators.size + others.size; ) {
|
|
2690
|
+
size = generators.size + others.size;
|
|
2691
|
+
for (const definition of definitions) {
|
|
2692
|
+
if (canRunGenerator(definition, generators)) {
|
|
2693
|
+
generators.add(definition.name);
|
|
2694
|
+
}
|
|
2695
|
+
if (canRunOther(definition, others, defined)) {
|
|
2696
|
+
others.add(definition.name);
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
const programOf = (name) => {
|
|
2701
|
+
if (!generators.has(name)) {
|
|
2702
|
+
return "other";
|
|
2703
|
+
}
|
|
2704
|
+
return others.has(name) ? "generator-or-other" : "generator";
|
|
2705
|
+
};
|
|
2706
|
+
return new Map([...defined].map((name) => [name, programOf(name)]));
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2709
|
+
const SHELL_VARIABLE_RE = /^"?\$(?:([A-Za-z_][A-Za-z0-9_]*)|\{([A-Za-z_][A-Za-z0-9_]*)\})"?$/;
|
|
2710
|
+
function expandedName(text) {
|
|
2711
|
+
const match = SHELL_VARIABLE_RE.exec(text);
|
|
2712
|
+
return match === null ? void 0 : match[1] ?? match[2];
|
|
2713
|
+
}
|
|
2714
|
+
function programDefinition({ name, value }) {
|
|
2715
|
+
const target = expandedName(value.trim());
|
|
2716
|
+
if (target !== void 0) {
|
|
2717
|
+
return { name, kind: "reference", target };
|
|
2718
|
+
}
|
|
2719
|
+
return { name, kind: "command", runsGenerator: shellCommands(value).some(runsGenerator) };
|
|
2720
|
+
}
|
|
2721
|
+
function shellVariables(definitions) {
|
|
2722
|
+
const values = /* @__PURE__ */ new Map();
|
|
2723
|
+
for (const { name, value } of definitions) {
|
|
2724
|
+
values.set(name, [values.get(name), value].filter((part) => part !== void 0).join(" "));
|
|
2725
|
+
}
|
|
2726
|
+
const programs = variablePrograms(definitions.map(programDefinition));
|
|
2727
|
+
const nameAt = (word) => expandedName(word.raw);
|
|
2728
|
+
return {
|
|
2729
|
+
programAt: (program) => {
|
|
2730
|
+
const name = nameAt(program);
|
|
2731
|
+
const found = name === void 0 ? void 0 : programs.get(name);
|
|
2732
|
+
return found === void 0 ? void 0 : { written: program.raw, program: found };
|
|
2733
|
+
},
|
|
2734
|
+
referenceAt: (word) => {
|
|
2735
|
+
const name = nameAt(word);
|
|
2736
|
+
const value = name === void 0 ? void 0 : values.get(name);
|
|
2737
|
+
return value === void 0 ? void 0 : { written: word.raw, value };
|
|
2738
|
+
}
|
|
2739
|
+
};
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
const EXEC_FORM_START_RE = /^[ \t]*(?:RUN|CMD|ENTRYPOINT)(?:[ \t]+--\S+)*[ \t]+\[/gm;
|
|
2743
|
+
const SHELLS = /* @__PURE__ */ new Set(["sh", "bash", "dash", "ash", "zsh"]);
|
|
2744
|
+
const OPTION_PREFIX = "-";
|
|
2745
|
+
const LONG_OPTION_PREFIX = "--";
|
|
2746
|
+
const COMMAND_OPTION = "c";
|
|
2747
|
+
const SET_OPTION = "-o";
|
|
2748
|
+
const PATH_SEPARATOR = "/";
|
|
2749
|
+
const ARRAY_BLANK_RE = /(?:[ \t\r\n]|\\\r?\n)*/y;
|
|
2750
|
+
const QUOTED_RE = /"(?:[^"\\\n]|\\.)*"/y;
|
|
2751
|
+
const ITEM_SEPARATOR = ",";
|
|
2752
|
+
const NOT_LINE_BREAK_RE$1 = /[^\n]/g;
|
|
2753
|
+
const ARRAY_END = "]";
|
|
2754
|
+
const VARIABLE_INSTRUCTIONS = /* @__PURE__ */ new Set(["ARG", "ENV"]);
|
|
2755
|
+
const LEGACY_ENV_INSTRUCTION = "ENV";
|
|
2756
|
+
const ASSIGNMENT = "=";
|
|
2757
|
+
const QUOTES = /* @__PURE__ */ new Set(['"', "'"]);
|
|
2758
|
+
function matchAt(pattern, text, index) {
|
|
2759
|
+
pattern.lastIndex = index;
|
|
2760
|
+
return pattern.exec(text)?.[0];
|
|
2761
|
+
}
|
|
2762
|
+
function jsonString(raw) {
|
|
2763
|
+
try {
|
|
2764
|
+
return JSON.parse(raw);
|
|
2765
|
+
} catch (error) {
|
|
2766
|
+
if (error instanceof SyntaxError) {
|
|
2767
|
+
return void 0;
|
|
2768
|
+
}
|
|
2769
|
+
throw error;
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
function execFormAt(text, start) {
|
|
2773
|
+
const words = [];
|
|
2774
|
+
let index = start + 1;
|
|
2775
|
+
for (; ; ) {
|
|
2776
|
+
index += matchAt(ARRAY_BLANK_RE, text, index)?.length ?? 0;
|
|
2777
|
+
const raw = matchAt(QUOTED_RE, text, index);
|
|
2778
|
+
if (raw === void 0) {
|
|
2779
|
+
const isEmpty = text[index] === ARRAY_END && words.length === 0;
|
|
2780
|
+
return isEmpty ? { start, end: index + 1, words } : void 0;
|
|
2781
|
+
}
|
|
2782
|
+
const value = jsonString(raw);
|
|
2783
|
+
if (value === void 0) {
|
|
2784
|
+
return void 0;
|
|
2785
|
+
}
|
|
2786
|
+
words.push({ start: index + 1, end: index + raw.length - 1, value, raw: raw.slice(1, -1) });
|
|
2787
|
+
index += raw.length;
|
|
2788
|
+
index += matchAt(ARRAY_BLANK_RE, text, index)?.length ?? 0;
|
|
2789
|
+
if (text[index] === ARRAY_END) {
|
|
2790
|
+
return { start, end: index + 1, words };
|
|
2791
|
+
}
|
|
2792
|
+
if (text[index] !== ITEM_SEPARATOR) {
|
|
2793
|
+
return void 0;
|
|
2794
|
+
}
|
|
2795
|
+
index++;
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
function execForms(text) {
|
|
2799
|
+
return [...text.matchAll(EXEC_FORM_START_RE)].flatMap((match) => {
|
|
2800
|
+
const form = execFormAt(text, match.index + match[0].length - 1);
|
|
2801
|
+
return form === void 0 ? [] : [form];
|
|
2802
|
+
});
|
|
2803
|
+
}
|
|
2804
|
+
function shellScript(words) {
|
|
2805
|
+
const program = words[0]?.value.split(PATH_SEPARATOR).at(-1) ?? "";
|
|
2806
|
+
if (!SHELLS.has(program)) {
|
|
2807
|
+
return void 0;
|
|
2808
|
+
}
|
|
2809
|
+
let index = 1;
|
|
2810
|
+
for (let option = words[index]?.value; option !== void 0; option = words[index]?.value) {
|
|
2811
|
+
if (!option.startsWith(OPTION_PREFIX)) {
|
|
2812
|
+
return void 0;
|
|
2813
|
+
}
|
|
2814
|
+
if (!option.startsWith(LONG_OPTION_PREFIX) && option.includes(COMMAND_OPTION)) {
|
|
2815
|
+
return words[index + 1];
|
|
2816
|
+
}
|
|
2817
|
+
index += option === SET_OPTION ? 2 : 1;
|
|
2818
|
+
}
|
|
2819
|
+
return void 0;
|
|
2820
|
+
}
|
|
2821
|
+
function execCommands(forms) {
|
|
2822
|
+
const commands = [];
|
|
2823
|
+
const escaped = [];
|
|
2824
|
+
for (const { words } of forms) {
|
|
2825
|
+
commands.push(words);
|
|
2826
|
+
const script = shellScript(words);
|
|
2827
|
+
if (script !== void 0 && script.raw === script.value) {
|
|
2828
|
+
commands.push(...shellCommandsAt(script.value, script.start));
|
|
2829
|
+
} else if (script !== void 0) {
|
|
2830
|
+
escaped.push(script);
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
return { commands, escaped };
|
|
2834
|
+
}
|
|
2835
|
+
function withoutExecForms(text, forms) {
|
|
2836
|
+
const parts = [];
|
|
2837
|
+
let copiedUpTo = 0;
|
|
2838
|
+
for (const { start, end } of forms) {
|
|
2839
|
+
parts.push(
|
|
2840
|
+
text.slice(copiedUpTo, start),
|
|
2841
|
+
text.slice(start, end).replace(NOT_LINE_BREAK_RE$1, " ")
|
|
2842
|
+
);
|
|
2843
|
+
copiedUpTo = end;
|
|
2844
|
+
}
|
|
2845
|
+
parts.push(text.slice(copiedUpTo));
|
|
2846
|
+
return parts.join("");
|
|
2847
|
+
}
|
|
2848
|
+
function escapedScriptSteps(scripts, lineOf) {
|
|
2849
|
+
return scripts.flatMap((script) => {
|
|
2850
|
+
const migrated = migrateCommand(script.value, "").text;
|
|
2851
|
+
return migrated === script.value ? [] : [{ kind: "command-string", line: lineOf(script.start), to: migrated }];
|
|
2852
|
+
});
|
|
2853
|
+
}
|
|
2854
|
+
function assignedVariable(word) {
|
|
2855
|
+
const at = word.raw.indexOf(ASSIGNMENT);
|
|
2856
|
+
if (at <= 0) {
|
|
2857
|
+
return void 0;
|
|
2858
|
+
}
|
|
2859
|
+
const raw = word.raw.slice(at + 1);
|
|
2860
|
+
const isQuoted = raw.length >= 2 && QUOTES.has(raw[0]) && raw.at(-1) === raw[0];
|
|
2861
|
+
const value = isQuoted ? raw.slice(1, -1) : raw;
|
|
2862
|
+
const start = word.start + at + 1 + (isQuoted ? 1 : 0);
|
|
2863
|
+
return { name: word.raw.slice(0, at), commands: shellCommandsAt(value, start) };
|
|
2864
|
+
}
|
|
2865
|
+
function dockerVariables(commands) {
|
|
2866
|
+
return commands.flatMap(([instruction, first, ...rest]) => {
|
|
2867
|
+
if (instruction === void 0 || first === void 0) {
|
|
2868
|
+
return [];
|
|
2869
|
+
}
|
|
2870
|
+
if (!VARIABLE_INSTRUCTIONS.has(instruction.value)) {
|
|
2871
|
+
return [];
|
|
2872
|
+
}
|
|
2873
|
+
if (instruction.value === LEGACY_ENV_INSTRUCTION && !first.raw.includes(ASSIGNMENT)) {
|
|
2874
|
+
return [{ name: first.value, commands: [rest] }];
|
|
2875
|
+
}
|
|
2876
|
+
return [first, ...rest].flatMap((word) => assignedVariable(word) ?? []);
|
|
2877
|
+
});
|
|
2878
|
+
}
|
|
2879
|
+
const definitionOf = ({ name, commands }) => ({
|
|
2880
|
+
name,
|
|
2881
|
+
value: commands.flatMap((words) => words.map((word) => word.raw)).join(" ")
|
|
2882
|
+
});
|
|
2883
|
+
function migrateDockerfile(text, file) {
|
|
2884
|
+
const lineOf = lineLocator(text);
|
|
2885
|
+
const locate = (offset) => `${file}:${lineOf(offset)}`;
|
|
2886
|
+
const forms = execForms(text);
|
|
2887
|
+
const exec = execCommands(forms);
|
|
2888
|
+
const shellForm = shellCommands(withoutExecForms(text, forms));
|
|
2889
|
+
const definitions = dockerVariables(shellForm);
|
|
2890
|
+
const values = definitions.flatMap((variable) => variable.commands);
|
|
2891
|
+
const commands = [...shellForm, ...values, ...exec.commands];
|
|
2892
|
+
const variables = shellVariables(definitions.map(definitionOf));
|
|
2893
|
+
const { edits, changes, manualSteps } = commandWordFindings(commands, locate, variables);
|
|
2894
|
+
const steps = [...manualSteps, ...escapedScriptSteps(exec.escaped, lineOf)];
|
|
2895
|
+
return { text: applyTextEdits(text, edits), changes, manualSteps: steps };
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
const ANY = "*";
|
|
2899
|
+
const PACKAGE_SCRIPT_PATHS = [["scripts", ANY]];
|
|
2900
|
+
const NX_COMMAND_PATHS = [
|
|
2901
|
+
["targets", ANY, "command"],
|
|
2902
|
+
["targets", ANY, "options", "command"],
|
|
2903
|
+
["targets", ANY, "options", "commands", ANY],
|
|
2904
|
+
["targets", ANY, "options", "commands", ANY, "command"]
|
|
2905
|
+
];
|
|
2906
|
+
const DENO_TASK_PATHS = [
|
|
2907
|
+
["tasks", ANY],
|
|
2908
|
+
["tasks", ANY, "command"]
|
|
2909
|
+
];
|
|
2910
|
+
const matches = (path, pattern) => path.length === pattern.length && pattern.every((part, index) => part === ANY || part === String(path[index]));
|
|
2911
|
+
function migrateJsonCommands(text, strings, patterns) {
|
|
2912
|
+
const changes = [];
|
|
2913
|
+
const manualSteps = [];
|
|
2914
|
+
const edits = [];
|
|
2915
|
+
for (const found of strings.filter((string) => patterns.some((p) => matches(string.path, p)))) {
|
|
2916
|
+
const migrated = migrateCommand(found.value, found.path.join("."));
|
|
2917
|
+
changes.push(...migrated.changes);
|
|
2918
|
+
manualSteps.push(...migrated.manualSteps);
|
|
2919
|
+
if (migrated.text !== found.value) {
|
|
2920
|
+
edits.push({ start: found.start, end: found.end, text: JSON.stringify(migrated.text) });
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
return { text: applyTextEdits(text, edits), changes, manualSteps };
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2926
|
+
const ASSIGNMENT_RE = new RegExp(
|
|
2927
|
+
[
|
|
2928
|
+
"^(?:[^\\s:#=][^:#=\\n]*:[ \\t]*)?",
|
|
2929
|
+
"(?:(?:export|override|private)[ \\t]+)*",
|
|
2930
|
+
"([A-Za-z_][A-Za-z0-9_.-]*)",
|
|
2931
|
+
"[ \\t]*(:::=|::=|:=|\\?=|\\+=|!=|=)"
|
|
2932
|
+
].join(""),
|
|
2933
|
+
"gm"
|
|
2934
|
+
);
|
|
2935
|
+
const VARIABLE_REFERENCE_RE = /^\$(?:\(([A-Za-z_][A-Za-z0-9_.-]*)\)|\{([A-Za-z_][A-Za-z0-9_.-]*)\}|([A-Za-z_]))$/;
|
|
2936
|
+
const RECIPE_PREFIX_RE = /^[@+-]+/;
|
|
2937
|
+
const DOLLAR = "$";
|
|
2938
|
+
const CLOSING = { "(": ")", "{": "}" };
|
|
2939
|
+
const NOT_LINE_BREAK_RE = /[^\n]/g;
|
|
2940
|
+
const MASK = "_";
|
|
2941
|
+
const LINE_BREAK = "\n";
|
|
2942
|
+
const BACKSLASH = "\\";
|
|
2943
|
+
function expansionEnd(text, start) {
|
|
2944
|
+
const open = text[start + 1] ?? "";
|
|
2945
|
+
const close = CLOSING[open];
|
|
2946
|
+
if (close === void 0) {
|
|
2947
|
+
return void 0;
|
|
2948
|
+
}
|
|
2949
|
+
let depth = 0;
|
|
2950
|
+
for (let index = start + 1; index < text.length; index++) {
|
|
2951
|
+
if (text[index] === open) {
|
|
2952
|
+
depth++;
|
|
2953
|
+
} else if (text[index] === close && --depth === 0) {
|
|
2954
|
+
return index + 1;
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
return void 0;
|
|
1036
2958
|
}
|
|
1037
|
-
function
|
|
1038
|
-
|
|
2959
|
+
function maskExpansions(text) {
|
|
2960
|
+
const parts = [];
|
|
1039
2961
|
let copiedUpTo = 0;
|
|
1040
|
-
for (let
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
2962
|
+
for (let index = text.indexOf(DOLLAR); index !== -1; index = text.indexOf(DOLLAR, index + 1)) {
|
|
2963
|
+
if (text[index + 1] === DOLLAR) {
|
|
2964
|
+
index++;
|
|
2965
|
+
continue;
|
|
2966
|
+
}
|
|
2967
|
+
const end = expansionEnd(text, index);
|
|
2968
|
+
if (end !== void 0) {
|
|
2969
|
+
parts.push(text.slice(copiedUpTo, index + 1), MASK.repeat(end - index - 1));
|
|
2970
|
+
copiedUpTo = end;
|
|
2971
|
+
index = end - 1;
|
|
1046
2972
|
}
|
|
1047
|
-
found = text.indexOf(SCHEME_SEPARATOR, end);
|
|
1048
2973
|
}
|
|
1049
|
-
|
|
2974
|
+
parts.push(text.slice(copiedUpTo));
|
|
2975
|
+
return parts.join("");
|
|
1050
2976
|
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
return { kind: "kept", spec, isReported: false };
|
|
2977
|
+
const APPEND_OPERATOR = "+=";
|
|
2978
|
+
function logicalLineEnd(text, index) {
|
|
2979
|
+
let end = text.indexOf(LINE_BREAK, index);
|
|
2980
|
+
while (end !== -1 && text[end - 1] === BACKSLASH) {
|
|
2981
|
+
end = text.indexOf(LINE_BREAK, end + 1);
|
|
1057
2982
|
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
2983
|
+
return end === -1 ? text.length : end;
|
|
2984
|
+
}
|
|
2985
|
+
const assignmentsOf = (text) => [...text.matchAll(ASSIGNMENT_RE)].map((match) => {
|
|
2986
|
+
const valueStart = match.index + match[0].length;
|
|
2987
|
+
const valueEnd = logicalLineEnd(text, valueStart);
|
|
2988
|
+
const name = match[1];
|
|
2989
|
+
const isAppend = match[2] === APPEND_OPERATOR;
|
|
2990
|
+
return { name, isAppend, start: match.index, valueStart, valueEnd };
|
|
2991
|
+
});
|
|
2992
|
+
function withoutAssignmentHeads(text, assignments) {
|
|
2993
|
+
const parts = [];
|
|
2994
|
+
let copiedUpTo = 0;
|
|
2995
|
+
for (const { start, valueStart } of assignments) {
|
|
2996
|
+
parts.push(
|
|
2997
|
+
text.slice(copiedUpTo, start),
|
|
2998
|
+
text.slice(start, valueStart).replace(NOT_LINE_BREAK_RE, " ")
|
|
2999
|
+
);
|
|
3000
|
+
copiedUpTo = valueStart;
|
|
1061
3001
|
}
|
|
1062
|
-
|
|
1063
|
-
return
|
|
3002
|
+
parts.push(text.slice(copiedUpTo));
|
|
3003
|
+
return parts.join("");
|
|
3004
|
+
}
|
|
3005
|
+
function referencedName(written) {
|
|
3006
|
+
const match = VARIABLE_REFERENCE_RE.exec(written.trim().replace(RECIPE_PREFIX_RE, ""));
|
|
3007
|
+
return match === null ? void 0 : match[1] ?? match[2] ?? match[3];
|
|
3008
|
+
}
|
|
3009
|
+
function programDefinitions(text, commands, assignments) {
|
|
3010
|
+
const generatorStarts = commands.filter(runsGenerator).map((command) => command[0].start);
|
|
3011
|
+
return assignments.map(({ name, isAppend, valueStart, valueEnd }) => {
|
|
3012
|
+
if (isAppend) {
|
|
3013
|
+
return { name, kind: "append" };
|
|
3014
|
+
}
|
|
3015
|
+
const target = referencedName(text.slice(valueStart, valueEnd));
|
|
3016
|
+
if (target !== void 0) {
|
|
3017
|
+
return { name, kind: "reference", target };
|
|
3018
|
+
}
|
|
3019
|
+
const runs = generatorStarts.some((start) => valueStart <= start && start < valueEnd);
|
|
3020
|
+
return { name, kind: "command", runsGenerator: runs };
|
|
3021
|
+
});
|
|
3022
|
+
}
|
|
3023
|
+
function variableValues(text, assignments) {
|
|
3024
|
+
const values = /* @__PURE__ */ new Map();
|
|
3025
|
+
for (const { name, valueStart, valueEnd } of assignments) {
|
|
3026
|
+
const value = text.slice(valueStart, valueEnd);
|
|
3027
|
+
values.set(name, [values.get(name), value].filter((part) => part !== void 0).join(" "));
|
|
3028
|
+
}
|
|
3029
|
+
return values;
|
|
3030
|
+
}
|
|
3031
|
+
function makefileVariables(text, assignments, programs) {
|
|
3032
|
+
const values = variableValues(text, assignments);
|
|
3033
|
+
const nameAt = (word) => referencedName(text.slice(word.start, word.end));
|
|
3034
|
+
const variables = {
|
|
3035
|
+
programAt: (program) => {
|
|
3036
|
+
const name = nameAt(program);
|
|
3037
|
+
const found = name === void 0 ? void 0 : programs.get(name);
|
|
3038
|
+
const written = text.slice(program.start, program.end);
|
|
3039
|
+
return found === void 0 ? void 0 : { written, program: found };
|
|
3040
|
+
},
|
|
3041
|
+
referenceAt: (word) => {
|
|
3042
|
+
const name = nameAt(word);
|
|
3043
|
+
const value = name === void 0 ? void 0 : values.get(name);
|
|
3044
|
+
const written = text.slice(word.start, word.end);
|
|
3045
|
+
return value === void 0 ? void 0 : { written, value };
|
|
3046
|
+
}
|
|
3047
|
+
};
|
|
3048
|
+
return variables;
|
|
3049
|
+
}
|
|
3050
|
+
function migrateMakefile(text, file) {
|
|
3051
|
+
const lineOf = lineLocator(text);
|
|
3052
|
+
const assignments = assignmentsOf(text);
|
|
3053
|
+
const commands = shellCommands(withoutAssignmentHeads(maskExpansions(text), assignments));
|
|
3054
|
+
const programs = variablePrograms(programDefinitions(text, commands, assignments));
|
|
3055
|
+
const locate = (offset) => `${file}:${lineOf(offset)}`;
|
|
3056
|
+
const variables = makefileVariables(text, assignments, programs);
|
|
3057
|
+
const { edits, changes, manualSteps } = commandWordFindings(commands, locate, variables);
|
|
3058
|
+
return { text: applyTextEdits(text, edits), changes, manualSteps };
|
|
1064
3059
|
}
|
|
1065
3060
|
|
|
1066
|
-
|
|
1067
|
-
const
|
|
1068
|
-
const
|
|
1069
|
-
const
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
const
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
Object.entries(dependencies).map(([name, spec]) => {
|
|
1082
|
-
const renamed = PACKAGE_RENAMES.get(name);
|
|
1083
|
-
if (renamed === void 0 || typeof spec !== "string") {
|
|
1084
|
-
return [name, spec];
|
|
1085
|
-
}
|
|
1086
|
-
const decision = migrateVersionSpec(spec, version);
|
|
1087
|
-
findings.changes.push({
|
|
1088
|
-
kind: "dependency-renamed",
|
|
1089
|
-
field,
|
|
1090
|
-
from: name,
|
|
1091
|
-
to: renamed,
|
|
1092
|
-
spec: hideUrlCredentials(decision.spec)
|
|
1093
|
-
});
|
|
1094
|
-
if (decision.kind === "kept" && decision.isReported) {
|
|
1095
|
-
findings.manualSteps.push({
|
|
1096
|
-
kind: "spec-kept",
|
|
1097
|
-
name: renamed,
|
|
1098
|
-
spec: hideUrlCredentials(spec)
|
|
1099
|
-
});
|
|
1100
|
-
}
|
|
1101
|
-
if (field === "peerDependencies") {
|
|
1102
|
-
findings.manualSteps.push({ kind: "peer-dependency-renamed", name: renamed });
|
|
3061
|
+
function migrateDependencyFields(manifest, target, findings) {
|
|
3062
|
+
for (const field of DEPENDENCY_FIELDS) {
|
|
3063
|
+
for (const dependencies of mapsAt(manifest, field)) {
|
|
3064
|
+
const map = { kind: "dependencies", field };
|
|
3065
|
+
migrateSpecMap(specEntries(dependencies), { ...target, map, findings });
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
function migrateNameKeys(manifest, findings) {
|
|
3070
|
+
for (const field of NAME_KEYED_FIELDS) {
|
|
3071
|
+
for (const meta of mapsAt(manifest, field)) {
|
|
3072
|
+
renameNames(keyEntries(meta), field, findings);
|
|
3073
|
+
const versioned = textEntries(meta).map(({ key }) => key.value).filter((key) => !PACKAGE_RENAMES.has(key));
|
|
3074
|
+
for (const name of overriddenPackages(versioned)) {
|
|
3075
|
+
findings.manualSteps.push({ kind: "override-names-package", field, name });
|
|
1103
3076
|
}
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
);
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
1107
3079
|
}
|
|
1108
|
-
function
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
if (
|
|
1112
|
-
|
|
3080
|
+
function migrateNameLists(manifest, findings) {
|
|
3081
|
+
for (const field of NAME_LIST_FIELDS) {
|
|
3082
|
+
for (const list of valuesAt(manifest, field)) {
|
|
3083
|
+
if (list.kind === "list") {
|
|
3084
|
+
renameNames(itemEntries(list), field, findings);
|
|
1113
3085
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
function migrateCatalogs$1(manifest, target, findings) {
|
|
3090
|
+
for (const { name, label, map } of manifestCatalogMaps(manifest)) {
|
|
3091
|
+
const context = {
|
|
3092
|
+
...target,
|
|
3093
|
+
map: { kind: "catalog", catalog: label, name },
|
|
3094
|
+
findings
|
|
3095
|
+
};
|
|
3096
|
+
migrateSpecMap(specEntries(map), context);
|
|
3097
|
+
}
|
|
1120
3098
|
}
|
|
1121
3099
|
function reportOverrides(manifest, findings) {
|
|
1122
3100
|
for (const field of OVERRIDE_FIELDS) {
|
|
1123
|
-
|
|
3101
|
+
const keys = valuesAt(manifest, field).flatMap(nestedKeys);
|
|
3102
|
+
for (const name of overriddenPackages(keys)) {
|
|
1124
3103
|
findings.manualSteps.push({ kind: "override-names-package", field, name });
|
|
1125
3104
|
}
|
|
1126
3105
|
}
|
|
1127
3106
|
}
|
|
1128
3107
|
function reportOldPeers(manifest, findings) {
|
|
3108
|
+
const dependencies = new Set(declaredNames(manifest));
|
|
1129
3109
|
for (const [dependent, { peer, range }] of OLD_PEER_DEPENDENTS) {
|
|
1130
|
-
|
|
1131
|
-
const dependencies = manifest[field];
|
|
1132
|
-
return isObject(dependencies) && Object.hasOwn(dependencies, dependent);
|
|
1133
|
-
});
|
|
1134
|
-
if (isDependency) {
|
|
3110
|
+
if (dependencies.has(dependent)) {
|
|
1135
3111
|
findings.manualSteps.push({ kind: "old-peer-needed", dependent, peer, range });
|
|
1136
3112
|
}
|
|
1137
3113
|
}
|
|
1138
3114
|
}
|
|
1139
|
-
function
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
migrated[field] = renameDependencies(field, dependencies, version, findings);
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
if (isObject(manifest.scripts)) {
|
|
1148
|
-
migrated.scripts = migrateScripts(manifest.scripts, findings);
|
|
1149
|
-
}
|
|
3115
|
+
function migrateManifestMap(manifest, target, findings) {
|
|
3116
|
+
migrateDependencyFields(manifest, target, findings);
|
|
3117
|
+
migrateNameKeys(manifest, findings);
|
|
3118
|
+
migrateNameLists(manifest, findings);
|
|
3119
|
+
migrateCatalogs$1(manifest, target, findings);
|
|
1150
3120
|
reportOverrides(manifest, findings);
|
|
1151
3121
|
reportOldPeers(manifest, findings);
|
|
1152
|
-
return migrated;
|
|
1153
3122
|
}
|
|
1154
|
-
function migratePackageManifest(text,
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
manifest = JSON.parse(text);
|
|
1158
|
-
} catch (error) {
|
|
1159
|
-
const reason = hideUrlCredentials(error instanceof Error ? error.message : String(error));
|
|
1160
|
-
return { ...unchanged(text), manualSteps: [{ kind: "manifest-unreadable", reason }] };
|
|
3123
|
+
function migratePackageManifest(text, read, target) {
|
|
3124
|
+
if (read.kind === "unreadable") {
|
|
3125
|
+
return unreadableConfig(text, read.offset);
|
|
1161
3126
|
}
|
|
1162
|
-
|
|
1163
|
-
|
|
3127
|
+
const findings = newFindings();
|
|
3128
|
+
for (const manifest of read.documents.filter(isMap)) {
|
|
3129
|
+
migrateManifestMap(manifest, target, findings);
|
|
1164
3130
|
}
|
|
1165
|
-
const
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
3131
|
+
const { edits, changes, manualSteps } = findings;
|
|
3132
|
+
return { text: applyTextEdits(text, edits), changes, manualSteps };
|
|
3133
|
+
}
|
|
3134
|
+
function unreadManifest(text) {
|
|
3135
|
+
return namesOriginalPackage(text) ? { ...unchanged(text), manualSteps: [{ kind: "manifest-not-read" }] } : unchanged(text);
|
|
1169
3136
|
}
|
|
1170
3137
|
|
|
1171
3138
|
const SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
@@ -1181,44 +3148,66 @@ const SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
1181
3148
|
".svelte",
|
|
1182
3149
|
".astro"
|
|
1183
3150
|
]);
|
|
1184
|
-
function renamedValue(value) {
|
|
1185
|
-
const
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
if (renamed === void 0) {
|
|
1189
|
-
return void 0;
|
|
3151
|
+
function renamedValue(value, version) {
|
|
3152
|
+
const npm = migrateNpmSpec(value, version);
|
|
3153
|
+
if (npm === void 0) {
|
|
3154
|
+
return renamedSpecifier(value);
|
|
1190
3155
|
}
|
|
1191
|
-
|
|
3156
|
+
const renamed = { to: npm.spec, isDeep: npm.isDeep };
|
|
3157
|
+
return npm.isReported ? { ...renamed, keptSpec: hideUrlCredentials(value) } : renamed;
|
|
1192
3158
|
}
|
|
3159
|
+
const TEMPLATE_SUBSTITUTION = "${";
|
|
3160
|
+
function dynamicPackage(value) {
|
|
3161
|
+
const at = value.indexOf(TEMPLATE_SUBSTITUTION);
|
|
3162
|
+
return at === -1 ? void 0 : PACKAGE_RENAMES.get(value.slice(0, at));
|
|
3163
|
+
}
|
|
3164
|
+
const byStartThenOuter = (left, right) => left.start - right.start || right.end - left.end;
|
|
1193
3165
|
function distinctStrings(strings) {
|
|
1194
3166
|
const distinct = [];
|
|
1195
|
-
|
|
3167
|
+
const enclosing = [];
|
|
3168
|
+
for (const found of [...strings].sort(byStartThenOuter)) {
|
|
1196
3169
|
const previous = distinct.at(-1);
|
|
1197
|
-
if (previous ===
|
|
1198
|
-
distinct.push(found);
|
|
1199
|
-
} else if (found.start === previous.start && found.end === previous.end) {
|
|
3170
|
+
if (previous?.start === found.start && previous.end === found.end) {
|
|
1200
3171
|
const isModuleSpecifier = previous.isModuleSpecifier || found.isModuleSpecifier;
|
|
1201
3172
|
distinct[distinct.length - 1] = { ...previous, isModuleSpecifier };
|
|
1202
|
-
|
|
3173
|
+
continue;
|
|
3174
|
+
}
|
|
3175
|
+
let outer = enclosing.at(-1);
|
|
3176
|
+
while (outer !== void 0 && outer.end <= found.start) {
|
|
3177
|
+
enclosing.pop();
|
|
3178
|
+
outer = enclosing.at(-1);
|
|
3179
|
+
}
|
|
3180
|
+
if (outer !== void 0 && found.end > outer.end) {
|
|
1203
3181
|
throw new Error(
|
|
1204
|
-
`Source strings overlap at offsets ${
|
|
3182
|
+
`Source strings overlap at offsets ${outer.start}-${outer.end} and ${found.start}-${found.end}; the file was not changed.`
|
|
1205
3183
|
);
|
|
1206
3184
|
}
|
|
3185
|
+
enclosing.push(found);
|
|
3186
|
+
distinct.push(found);
|
|
1207
3187
|
}
|
|
1208
3188
|
return distinct;
|
|
1209
3189
|
}
|
|
1210
|
-
|
|
3190
|
+
const dynamicStep = (line, value, to) => ({
|
|
3191
|
+
kind: "dynamic-specifier",
|
|
3192
|
+
line,
|
|
3193
|
+
value,
|
|
3194
|
+
to
|
|
3195
|
+
});
|
|
3196
|
+
function migrateModuleSpecifiers(text, strings, version) {
|
|
1211
3197
|
const changes = [];
|
|
1212
3198
|
const manualSteps = [];
|
|
1213
3199
|
let migrated = "";
|
|
1214
3200
|
let copiedUpTo = 0;
|
|
3201
|
+
const lineOf = lineLocator(text);
|
|
1215
3202
|
for (const found of distinctStrings(strings)) {
|
|
1216
|
-
const renamed = renamedValue(found.value);
|
|
3203
|
+
const renamed = renamedValue(found.value, version);
|
|
3204
|
+
const line = lineOf(found.start);
|
|
3205
|
+
const dynamic = found.isModuleSpecifier ? dynamicPackage(found.value) : void 0;
|
|
1217
3206
|
if (renamed === void 0) {
|
|
3207
|
+
manualSteps.push(...dynamic === void 0 ? [] : [dynamicStep(line, found.value, dynamic)]);
|
|
1218
3208
|
continue;
|
|
1219
3209
|
}
|
|
1220
|
-
|
|
1221
|
-
if (!found.isModuleSpecifier) {
|
|
3210
|
+
if (!found.isModuleSpecifier || found.start < copiedUpTo) {
|
|
1222
3211
|
manualSteps.push({ kind: "string-names-package", line, value: found.value, to: renamed.to });
|
|
1223
3212
|
continue;
|
|
1224
3213
|
}
|
|
@@ -1226,86 +3215,186 @@ function migrateModuleSpecifiers(text, strings) {
|
|
|
1226
3215
|
if (renamed.isDeep) {
|
|
1227
3216
|
manualSteps.push({ kind: "deep-import", line, specifier: renamed.to });
|
|
1228
3217
|
}
|
|
3218
|
+
if (renamed.keptSpec !== void 0) {
|
|
3219
|
+
manualSteps.push({ kind: "spec-kept", name: renamed.to, spec: renamed.keptSpec });
|
|
3220
|
+
}
|
|
1229
3221
|
migrated += `${text.slice(copiedUpTo, found.start)}${renamed.to}`;
|
|
1230
3222
|
copiedUpTo = found.end;
|
|
1231
3223
|
}
|
|
1232
3224
|
return { text: migrated + text.slice(copiedUpTo), changes, manualSteps };
|
|
1233
3225
|
}
|
|
3226
|
+
function importedSpecifiers(text, strings) {
|
|
3227
|
+
const lineOf = lineLocator(text);
|
|
3228
|
+
return strings.filter((found) => found.isModuleSpecifier).map((found) => ({
|
|
3229
|
+
specifier: renamedSpecifier(found.value)?.to ?? found.value,
|
|
3230
|
+
line: lineOf(found.start)
|
|
3231
|
+
}));
|
|
3232
|
+
}
|
|
1234
3233
|
|
|
1235
|
-
const
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
const
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
}
|
|
1246
|
-
const [whole, indent, keyQuote, name, specQuote, spec] = entry;
|
|
1247
|
-
const rest = line.slice(whole.length);
|
|
1248
|
-
const decision = migrateVersionSpec(spec, version);
|
|
1249
|
-
const key = `${keyQuote}${renamed}${keyQuote}`;
|
|
1250
|
-
const migrated = `${indent}${key}: ${specQuote}${decision.spec}${specQuote}${rest}`;
|
|
1251
|
-
const change = { kind: "catalog-entry-renamed", from: name, to: renamed };
|
|
1252
|
-
if (decision.kind === "kept" && decision.isReported) {
|
|
1253
|
-
return {
|
|
1254
|
-
line: migrated,
|
|
1255
|
-
change,
|
|
1256
|
-
manualStep: { kind: "spec-kept", name: renamed, spec: hideUrlCredentials(spec) }
|
|
3234
|
+
const PNPM_WORKSPACE_FILE = {
|
|
3235
|
+
reportedSections: /* @__PURE__ */ new Set(["overrides", "patchedDependencies"])
|
|
3236
|
+
};
|
|
3237
|
+
const YARNRC_FILE = { reportedSections: /* @__PURE__ */ new Set() };
|
|
3238
|
+
function migrateCatalogs(workspace, target, findings) {
|
|
3239
|
+
for (const { name, label, map } of catalogMaps(workspace)) {
|
|
3240
|
+
const context = {
|
|
3241
|
+
...target,
|
|
3242
|
+
map: { kind: "catalog", catalog: label, name },
|
|
3243
|
+
findings
|
|
1257
3244
|
};
|
|
3245
|
+
migrateSpecMap(specEntries(map), context);
|
|
1258
3246
|
}
|
|
1259
|
-
return { line: migrated, change };
|
|
1260
3247
|
}
|
|
1261
|
-
function
|
|
1262
|
-
const
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
3248
|
+
function reportSections(workspace, file, findings) {
|
|
3249
|
+
for (const field of file.reportedSections) {
|
|
3250
|
+
for (const name of overriddenPackages(valuesAt(workspace, field).flatMap(nestedKeys))) {
|
|
3251
|
+
findings.manualSteps.push({ kind: "override-names-package", field, name });
|
|
3252
|
+
}
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
function migrateWorkspaceCatalogs(text, read, target, file) {
|
|
3256
|
+
if (read.kind === "unreadable") {
|
|
3257
|
+
return unreadableConfig(text, read.offset);
|
|
3258
|
+
}
|
|
3259
|
+
const findings = newFindings();
|
|
3260
|
+
for (const workspace of read.documents.filter(isMap)) {
|
|
3261
|
+
migrateCatalogs(workspace, target, findings);
|
|
3262
|
+
reportSections(workspace, file, findings);
|
|
3263
|
+
}
|
|
3264
|
+
const { edits, changes, manualSteps } = findings;
|
|
3265
|
+
return { text: applyTextEdits(text, edits), changes, manualSteps };
|
|
1268
3266
|
}
|
|
1269
|
-
|
|
1270
|
-
const
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
3267
|
+
|
|
3268
|
+
const YAML_COMMAND_KEYS = /* @__PURE__ */ new Set([
|
|
3269
|
+
"run",
|
|
3270
|
+
"script",
|
|
3271
|
+
"before_script",
|
|
3272
|
+
"after_script",
|
|
3273
|
+
"before_install",
|
|
3274
|
+
"install",
|
|
3275
|
+
"bash",
|
|
3276
|
+
"pwsh",
|
|
3277
|
+
"powershell",
|
|
3278
|
+
"command",
|
|
3279
|
+
"commands",
|
|
3280
|
+
"entrypoint"
|
|
3281
|
+
]);
|
|
3282
|
+
const ENVIRONMENT_KEYS = /* @__PURE__ */ new Set(["env"]);
|
|
3283
|
+
const isEnvironmentValue = (value) => value.path.length >= 2 && ENVIRONMENT_KEYS.has(String(value.path.at(-2)));
|
|
3284
|
+
function environmentVariables(text, strings) {
|
|
3285
|
+
return shellVariables(
|
|
3286
|
+
strings.filter(isEnvironmentValue).map((value) => ({ name: String(value.path.at(-1)), value: shellText(text, value) }))
|
|
3287
|
+
);
|
|
3288
|
+
}
|
|
3289
|
+
const DEPENDABOT_NAME_KEYS = /* @__PURE__ */ new Set([
|
|
3290
|
+
"dependency-name",
|
|
3291
|
+
"patterns",
|
|
3292
|
+
"exclude-patterns"
|
|
3293
|
+
]);
|
|
3294
|
+
const FOLDED_LINE_BREAK_RE = /(?<=\S[^\S\n]*)\n(?=[^\S\n]*\S)/g;
|
|
3295
|
+
const ENV_EXPRESSION_RE = /\$\{\{[ \t]*env\.([A-Za-z_][A-Za-z0-9_]*)[ \t]*\}\}/g;
|
|
3296
|
+
const asShellVariable = (expression, name) => `$${name}`.padEnd(expression.length, " ");
|
|
3297
|
+
function shellText(text, value) {
|
|
3298
|
+
const slice = text.slice(value.content.start, value.content.end);
|
|
3299
|
+
const unfolded = value.isFolded ? slice.replace(FOLDED_LINE_BREAK_RE, " ") : slice;
|
|
3300
|
+
return unfolded.replace(ENV_EXPRESSION_RE, asShellVariable);
|
|
3301
|
+
}
|
|
3302
|
+
const valuesOf = (strings, keys) => strings.filter((string) => keys.has(ownerKey(string.path) ?? ""));
|
|
3303
|
+
const EXEC_FORM_KEYS = /* @__PURE__ */ new Set(["command", "entrypoint"]);
|
|
3304
|
+
const isExecFormItem = (value) => typeof value.path.at(-1) === "number" && EXEC_FORM_KEYS.has(String(value.path.at(-2)));
|
|
3305
|
+
const listOf = (item) => item.path.slice(0, -1).join(".");
|
|
3306
|
+
const isNextItem = (previous, item) => listOf(previous) === listOf(item) && Number(item.path.at(-1)) > Number(previous.path.at(-1));
|
|
3307
|
+
function execFormCommands(text, items) {
|
|
3308
|
+
const commands = [];
|
|
3309
|
+
let previous;
|
|
3310
|
+
let command = [];
|
|
3311
|
+
for (const item of items) {
|
|
3312
|
+
if (previous === void 0 || !isNextItem(previous, item)) {
|
|
3313
|
+
command = [];
|
|
3314
|
+
commands.push(command);
|
|
3315
|
+
}
|
|
3316
|
+
const { start, end } = item.content;
|
|
3317
|
+
command.push({ start, end, value: item.value, raw: text.slice(start, end) });
|
|
3318
|
+
previous = item;
|
|
1274
3319
|
}
|
|
3320
|
+
return commands;
|
|
3321
|
+
}
|
|
3322
|
+
function valueFindings(text, value, locate, variables) {
|
|
3323
|
+
const { start } = value.content;
|
|
3324
|
+
const commands = shellCommands(shellText(text, value));
|
|
3325
|
+
const found = commandWordFindings(commands, (offset) => locate(start + offset), variables);
|
|
3326
|
+
return { ...found, edits: shiftedEdits(found.edits, start) };
|
|
3327
|
+
}
|
|
3328
|
+
const shiftedEdits = (edits, offset) => edits.map((edit) => ({ ...edit, start: edit.start + offset, end: edit.end + offset }));
|
|
3329
|
+
function appliedFindings(text, found) {
|
|
1275
3330
|
return {
|
|
1276
|
-
|
|
1277
|
-
|
|
3331
|
+
text: applyTextEdits(
|
|
3332
|
+
text,
|
|
3333
|
+
found.flatMap((findings) => findings.edits)
|
|
3334
|
+
),
|
|
3335
|
+
changes: found.flatMap((findings) => findings.changes),
|
|
3336
|
+
manualSteps: found.flatMap((findings) => findings.manualSteps)
|
|
1278
3337
|
};
|
|
1279
3338
|
}
|
|
1280
|
-
function
|
|
3339
|
+
function migrateYamlCommands(text, file, strings) {
|
|
3340
|
+
const values = valuesOf(strings, YAML_COMMAND_KEYS);
|
|
3341
|
+
const lineOf = lineLocator(text);
|
|
3342
|
+
const locate = (offset) => `${file}:${lineOf(offset)}`;
|
|
3343
|
+
const commandValues = new Set(values.filter((value) => !isExecFormItem(value)));
|
|
3344
|
+
const shellValues = strings.filter(
|
|
3345
|
+
(value) => commandValues.has(value) || isEnvironmentValue(value)
|
|
3346
|
+
);
|
|
3347
|
+
const variables = environmentVariables(text, strings);
|
|
3348
|
+
return appliedFindings(text, [
|
|
3349
|
+
...shellValues.map((value) => valueFindings(text, value, locate, variables)),
|
|
3350
|
+
commandWordFindings(execFormCommands(text, values.filter(isExecFormItem)), locate)
|
|
3351
|
+
]);
|
|
3352
|
+
}
|
|
3353
|
+
const SCRIPTS_KEY = "scripts";
|
|
3354
|
+
const isScript = (string) => string.path.length === 2 && string.path[0] === SCRIPTS_KEY;
|
|
3355
|
+
function migrateYamlScripts(text, strings) {
|
|
3356
|
+
const scripts = strings.filter(isScript);
|
|
3357
|
+
return appliedFindings(
|
|
3358
|
+
text,
|
|
3359
|
+
scripts.map((script) => valueFindings(text, script, () => script.path.join(".")))
|
|
3360
|
+
);
|
|
3361
|
+
}
|
|
3362
|
+
function migrateDependabotConfig(text, file, strings) {
|
|
1281
3363
|
const changes = [];
|
|
1282
|
-
const
|
|
1283
|
-
|
|
1284
|
-
const
|
|
1285
|
-
const
|
|
1286
|
-
const
|
|
1287
|
-
if (
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
if (section === void 0 || !CATALOG_SECTIONS.has(section)) {
|
|
1296
|
-
return rawLine;
|
|
1297
|
-
}
|
|
1298
|
-
const result = migrateEntry(line, version);
|
|
1299
|
-
changes.push(...result.change ? [result.change] : []);
|
|
1300
|
-
manualSteps.push(...result.manualStep ? [result.manualStep] : []);
|
|
1301
|
-
return restore(result.line);
|
|
1302
|
-
});
|
|
1303
|
-
return changes.length === 0 ? { ...unchanged(text), manualSteps } : { text: lines.join(LINE_FEED), changes, manualSteps };
|
|
3364
|
+
const edits = [];
|
|
3365
|
+
const lineOf = lineLocator(text);
|
|
3366
|
+
for (const value of valuesOf(strings, DEPENDABOT_NAME_KEYS)) {
|
|
3367
|
+
const from = value.value;
|
|
3368
|
+
const to = PACKAGE_RENAMES.get(from);
|
|
3369
|
+
if (to !== void 0) {
|
|
3370
|
+
const { start, end } = value.content;
|
|
3371
|
+
const location = `${file}:${lineOf(start)}`;
|
|
3372
|
+
changes.push({ kind: "package-name-renamed", location, from, to });
|
|
3373
|
+
edits.push({ start, end, text: to });
|
|
3374
|
+
}
|
|
3375
|
+
}
|
|
3376
|
+
return { text: applyTextEdits(text, edits), changes, manualSteps: [] };
|
|
1304
3377
|
}
|
|
1305
3378
|
|
|
1306
|
-
const
|
|
1307
|
-
const
|
|
3379
|
+
const SHELL_EXTENSIONS = /* @__PURE__ */ new Set([".sh", ".bash", ".zsh"]);
|
|
3380
|
+
const MAKEFILE_EXTENSION = ".mk";
|
|
3381
|
+
const YAML_EXTENSIONS = /* @__PURE__ */ new Set([".yml", ".yaml"]);
|
|
3382
|
+
const MAKEFILE_NAMES = /* @__PURE__ */ new Set(["Makefile", "makefile", "GNUmakefile"]);
|
|
3383
|
+
const SHELL_FILE_NAMES = /* @__PURE__ */ new Set(["justfile", "Justfile", ".justfile"]);
|
|
3384
|
+
const DOCKERFILE_NAMES = /* @__PURE__ */ new Set(["Dockerfile", "Containerfile"]);
|
|
3385
|
+
const HUSKY_FOLDER = ".husky/";
|
|
3386
|
+
const HUSKY_INTERNALS = ".husky/_/";
|
|
3387
|
+
const NX_PROJECT_FILE = "project.json";
|
|
3388
|
+
const DENO_CONFIG_FILES = /* @__PURE__ */ new Set(["deno.json", "deno.jsonc"]);
|
|
3389
|
+
const IMPORT_MAP_FILE = "import_map.json";
|
|
3390
|
+
const DEPENDABOT_FILES = /* @__PURE__ */ new Set([
|
|
3391
|
+
".github/dependabot.yml",
|
|
3392
|
+
".github/dependabot.yaml"
|
|
3393
|
+
]);
|
|
1308
3394
|
const WORKSPACE_FILE = "pnpm-workspace.yaml";
|
|
3395
|
+
const YARNRC_FILE_NAME = ".yarnrc.yml";
|
|
3396
|
+
const YAML_MANIFEST_FILES = /* @__PURE__ */ new Set(["package.yaml", "package.yml"]);
|
|
3397
|
+
const JSON5_MANIFEST_FILE = "package.json5";
|
|
1309
3398
|
const MANIFEST_FILE = "package.json";
|
|
1310
3399
|
const LOCK_FILES = /* @__PURE__ */ new Set(["pnpm-lock.yaml", "yarn.lock", "bun.lock"]);
|
|
1311
3400
|
function baseName(path) {
|
|
@@ -1316,31 +3405,121 @@ function extensionOf(path) {
|
|
|
1316
3405
|
const dot = name.lastIndexOf(".");
|
|
1317
3406
|
return dot <= 0 ? "" : name.slice(dot);
|
|
1318
3407
|
}
|
|
3408
|
+
|
|
3409
|
+
const isHuskyHook = (path) => (path.startsWith(HUSKY_FOLDER) || path.includes(`/${HUSKY_FOLDER}`)) && !path.includes(HUSKY_INTERNALS);
|
|
3410
|
+
const isDependabotConfig = (path) => [...DEPENDABOT_FILES].some((file) => path === file || path.endsWith(`/${file}`));
|
|
3411
|
+
const withSteps = (migration, steps) => ({
|
|
3412
|
+
...migration,
|
|
3413
|
+
manualSteps: [...migration.manualSteps, ...steps]
|
|
3414
|
+
});
|
|
3415
|
+
function migrateSource(text, path, context) {
|
|
3416
|
+
const strings = context.scanner.scan(text, path);
|
|
3417
|
+
const migrated = chain(
|
|
3418
|
+
migrateModuleSpecifiers(text, strings, context.version),
|
|
3419
|
+
(renamed) => context.rewriter.rewrite(renamed, path)
|
|
3420
|
+
);
|
|
3421
|
+
const imports = importedSpecifiers(text, strings);
|
|
3422
|
+
const undeclared = undeclaredImportSteps(imports, path, context.manifests);
|
|
3423
|
+
return withSteps(migrated, [...commandStringSteps(text, strings), ...undeclared]);
|
|
3424
|
+
}
|
|
1319
3425
|
function chain(first, next) {
|
|
1320
3426
|
const second = next(first.text);
|
|
1321
3427
|
const changes = [...first.changes, ...second.changes];
|
|
1322
3428
|
const manualSteps = [...first.manualSteps, ...second.manualSteps];
|
|
1323
3429
|
return { text: second.text, changes, manualSteps };
|
|
1324
3430
|
}
|
|
3431
|
+
function migrateConfig(text, read, steps) {
|
|
3432
|
+
const first = read(text);
|
|
3433
|
+
if (first.kind === "unreadable") {
|
|
3434
|
+
return unreadableConfig(text, first.offset);
|
|
3435
|
+
}
|
|
3436
|
+
return steps.reduce(
|
|
3437
|
+
(done, step) => chain(done, (current) => step(current, current === text ? first : read(current))),
|
|
3438
|
+
unchanged(text)
|
|
3439
|
+
);
|
|
3440
|
+
}
|
|
3441
|
+
const stringsStep = (migrate) => (text, read) => read.kind === "unreadable" ? unreadableConfig(text, read.offset) : migrate(text, documentStrings(read.documents));
|
|
3442
|
+
const jsonCommandsStep = (patterns) => stringsStep((text, strings) => migrateJsonCommands(text, strings, patterns));
|
|
3443
|
+
const jsonConfig = (steps) => (text, _path, context) => migrateConfig(text, (current) => context.readers.json.read(current), steps(context));
|
|
3444
|
+
const yamlConfig = (steps) => (text, path, context) => migrateConfig(text, (current) => context.readers.yaml.read(current), steps(context, path));
|
|
3445
|
+
const whenNamed = (migrate) => (text, path, context) => namesOriginalPackage(text) ? migrate(text, path, context) : unchanged(text);
|
|
1325
3446
|
const FILE_KINDS = [
|
|
1326
3447
|
{
|
|
1327
3448
|
matches: (path) => baseName(path) === MANIFEST_FILE,
|
|
1328
|
-
migrate: (
|
|
3449
|
+
migrate: jsonConfig(({ version, catalogs }) => [
|
|
3450
|
+
(text, read) => migratePackageManifest(text, read, { version, catalogs }),
|
|
3451
|
+
jsonCommandsStep(PACKAGE_SCRIPT_PATHS)
|
|
3452
|
+
])
|
|
3453
|
+
},
|
|
3454
|
+
{
|
|
3455
|
+
matches: (path) => YAML_MANIFEST_FILES.has(baseName(path)),
|
|
3456
|
+
migrate: yamlConfig(({ version, catalogs }) => [
|
|
3457
|
+
(text, read) => migratePackageManifest(text, read, { version, catalogs }),
|
|
3458
|
+
stringsStep(migrateYamlScripts)
|
|
3459
|
+
])
|
|
3460
|
+
},
|
|
3461
|
+
{
|
|
3462
|
+
matches: (path) => baseName(path) === JSON5_MANIFEST_FILE,
|
|
3463
|
+
migrate: unreadManifest
|
|
1329
3464
|
},
|
|
1330
3465
|
{
|
|
1331
3466
|
matches: (path) => baseName(path) === WORKSPACE_FILE,
|
|
1332
|
-
migrate: (
|
|
3467
|
+
migrate: yamlConfig(({ version, catalogs }) => [
|
|
3468
|
+
(text, read) => migrateWorkspaceCatalogs(text, read, { version, catalogs }, PNPM_WORKSPACE_FILE)
|
|
3469
|
+
])
|
|
3470
|
+
},
|
|
3471
|
+
{
|
|
3472
|
+
matches: (path) => baseName(path) === YARNRC_FILE_NAME,
|
|
3473
|
+
migrate: yamlConfig(({ version, catalogs }) => [
|
|
3474
|
+
(text, read) => migrateWorkspaceCatalogs(text, read, { version, catalogs }, YARNRC_FILE)
|
|
3475
|
+
])
|
|
1333
3476
|
},
|
|
1334
3477
|
{
|
|
1335
3478
|
matches: (path) => SOURCE_EXTENSIONS.has(extensionOf(path)),
|
|
1336
|
-
migrate:
|
|
1337
|
-
|
|
1338
|
-
|
|
3479
|
+
migrate: migrateSource
|
|
3480
|
+
},
|
|
3481
|
+
{
|
|
3482
|
+
matches: (path) => baseName(path) === NX_PROJECT_FILE,
|
|
3483
|
+
migrate: jsonConfig(() => [jsonCommandsStep(NX_COMMAND_PATHS)])
|
|
3484
|
+
},
|
|
3485
|
+
{
|
|
3486
|
+
matches: (path) => DENO_CONFIG_FILES.has(baseName(path)),
|
|
3487
|
+
migrate: jsonConfig(({ version }) => [
|
|
3488
|
+
(text, read) => migrateImportMap(text, read, version),
|
|
3489
|
+
jsonCommandsStep(DENO_TASK_PATHS)
|
|
3490
|
+
])
|
|
3491
|
+
},
|
|
3492
|
+
{
|
|
3493
|
+
matches: (path) => baseName(path) === IMPORT_MAP_FILE,
|
|
3494
|
+
migrate: jsonConfig(({ version }) => [(text, read) => migrateImportMap(text, read, version)])
|
|
3495
|
+
},
|
|
3496
|
+
{
|
|
3497
|
+
matches: isDependabotConfig,
|
|
3498
|
+
migrate: whenNamed(
|
|
3499
|
+
yamlConfig((_context, path) => [
|
|
3500
|
+
stringsStep((text, strings) => migrateDependabotConfig(text, path, strings))
|
|
3501
|
+
])
|
|
3502
|
+
)
|
|
3503
|
+
},
|
|
3504
|
+
{
|
|
3505
|
+
matches: (path) => YAML_EXTENSIONS.has(extensionOf(path)),
|
|
3506
|
+
migrate: whenNamed(
|
|
3507
|
+
yamlConfig((_context, path) => [
|
|
3508
|
+
stringsStep((text, strings) => migrateYamlCommands(text, path, strings))
|
|
3509
|
+
])
|
|
1339
3510
|
)
|
|
1340
3511
|
},
|
|
1341
3512
|
{
|
|
1342
|
-
matches: (path) =>
|
|
1343
|
-
migrate: (text, path) =>
|
|
3513
|
+
matches: (path) => DOCKERFILE_NAMES.has(baseName(path)),
|
|
3514
|
+
migrate: whenNamed((text, path) => migrateDockerfile(text, path))
|
|
3515
|
+
},
|
|
3516
|
+
{
|
|
3517
|
+
matches: (path) => MAKEFILE_NAMES.has(baseName(path)) || extensionOf(path) === MAKEFILE_EXTENSION,
|
|
3518
|
+
migrate: whenNamed((text, path) => migrateMakefile(text, path))
|
|
3519
|
+
},
|
|
3520
|
+
{
|
|
3521
|
+
matches: (path) => SHELL_EXTENSIONS.has(extensionOf(path)) || SHELL_FILE_NAMES.has(baseName(path)) || isHuskyHook(path),
|
|
3522
|
+
migrate: whenNamed((text, path) => migrateCommandLines(text, path))
|
|
1344
3523
|
}
|
|
1345
3524
|
];
|
|
1346
3525
|
function fileMigrationFor(path) {
|
|
@@ -1350,118 +3529,327 @@ function fileMigrationFor(path) {
|
|
|
1350
3529
|
return FILE_KINDS.find((kind) => kind.matches(path))?.migrate;
|
|
1351
3530
|
}
|
|
1352
3531
|
|
|
1353
|
-
|
|
3532
|
+
const readJson = (text, readers) => readers.json.read(text);
|
|
3533
|
+
const readYaml = (text, readers) => readers.yaml.read(text);
|
|
3534
|
+
function planSourceOf(path) {
|
|
3535
|
+
const name = baseName(path);
|
|
3536
|
+
if (name === MANIFEST_FILE) {
|
|
3537
|
+
return { role: "manifest", read: readJson };
|
|
3538
|
+
}
|
|
3539
|
+
if (DENO_CONFIG_FILES.has(name) || name === IMPORT_MAP_FILE) {
|
|
3540
|
+
return { role: "import-map", read: readJson };
|
|
3541
|
+
}
|
|
3542
|
+
if (YAML_MANIFEST_FILES.has(name)) {
|
|
3543
|
+
return { role: "manifest", read: readYaml };
|
|
3544
|
+
}
|
|
3545
|
+
if (name === JSON5_MANIFEST_FILE) {
|
|
3546
|
+
return { role: "manifest", read: void 0 };
|
|
3547
|
+
}
|
|
3548
|
+
const isCatalogFile = name === WORKSPACE_FILE || name === YARNRC_FILE_NAME;
|
|
3549
|
+
return isCatalogFile ? { role: "catalogs", read: readYaml } : void 0;
|
|
3550
|
+
}
|
|
3551
|
+
|
|
3552
|
+
async function planFile(path, dependencies, target) {
|
|
1354
3553
|
const migrate = fileMigrationFor(path);
|
|
1355
3554
|
if (migrate === void 0) {
|
|
1356
|
-
return
|
|
3555
|
+
return { kind: "not-migrated" };
|
|
1357
3556
|
}
|
|
1358
|
-
const
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
const migration = migrate(text, path, context);
|
|
1362
|
-
const isChanged = migration.text !== text;
|
|
1363
|
-
if (isChanged && input.isWrite) {
|
|
1364
|
-
await dependencies.files.write(path, migration.text);
|
|
3557
|
+
const read = await dependencies.files.read(path);
|
|
3558
|
+
if (read.kind === "skipped") {
|
|
3559
|
+
return read;
|
|
1365
3560
|
}
|
|
3561
|
+
const { rewriter, scanner, readers } = dependencies;
|
|
3562
|
+
const context = { ...target, rewriter, scanner, readers };
|
|
3563
|
+
const migration = migrate(read.text, path, context);
|
|
3564
|
+
const isChanged = migration.text !== read.text;
|
|
1366
3565
|
const { changes, manualSteps } = migration;
|
|
1367
3566
|
if (isChanged || manualSteps.length > 0) {
|
|
1368
3567
|
dependencies.logger.info("file_migrated", { path, isChanged, changes, manualSteps });
|
|
1369
3568
|
}
|
|
1370
|
-
|
|
3569
|
+
const report = { path, isChanged, changes, manualSteps };
|
|
3570
|
+
return { kind: "planned", file: { report, text: isChanged ? migration.text : void 0 } };
|
|
1371
3571
|
}
|
|
1372
|
-
async function
|
|
1373
|
-
const
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
3572
|
+
async function readForPlan(path, source, dependencies) {
|
|
3573
|
+
const read = await dependencies.files.read(path);
|
|
3574
|
+
if (read.kind !== "read") {
|
|
3575
|
+
return { namesOriginal: false, roots: void 0 };
|
|
3576
|
+
}
|
|
3577
|
+
const namesOriginal = namesOriginalPackage(read.text);
|
|
3578
|
+
if (!namesOriginal && !namesUninstalledPackage(read.text)) {
|
|
3579
|
+
return { namesOriginal, roots: [] };
|
|
3580
|
+
}
|
|
3581
|
+
const parsed = source.read?.(read.text, dependencies.readers);
|
|
3582
|
+
const roots = parsed?.kind === "read" ? parsed.documents.filter(isMap) : void 0;
|
|
3583
|
+
return { namesOriginal, roots };
|
|
3584
|
+
}
|
|
3585
|
+
const DECLARED_NAMES = { manifest: declaredNames, "import-map": importMapNames, catalogs: void 0 };
|
|
3586
|
+
async function planProjectTarget(paths, dependencies, version) {
|
|
3587
|
+
const documents = { manifest: [], catalogs: [] };
|
|
3588
|
+
const manifests = [];
|
|
1377
3589
|
for (const path of paths) {
|
|
1378
|
-
const
|
|
1379
|
-
if (
|
|
1380
|
-
|
|
3590
|
+
const source = planSourceOf(path);
|
|
3591
|
+
if (source === void 0) {
|
|
3592
|
+
continue;
|
|
3593
|
+
}
|
|
3594
|
+
const { namesOriginal, roots } = await readForPlan(path, source, dependencies);
|
|
3595
|
+
if (namesOriginal && source.role !== "import-map") {
|
|
3596
|
+
documents[source.role].push(...roots ?? []);
|
|
3597
|
+
}
|
|
3598
|
+
const names = DECLARED_NAMES[source.role];
|
|
3599
|
+
if (names !== void 0) {
|
|
3600
|
+
manifests.push({ path, names: roots && new Set(roots.flatMap(names)) });
|
|
1381
3601
|
}
|
|
1382
3602
|
}
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
3603
|
+
const catalogs = planCatalogs({
|
|
3604
|
+
manifests: documents.manifest,
|
|
3605
|
+
catalogFiles: documents.catalogs
|
|
1386
3606
|
});
|
|
1387
|
-
return {
|
|
3607
|
+
return { version, catalogs, manifests };
|
|
1388
3608
|
}
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
3609
|
+
async function planProject(paths, dependencies, version) {
|
|
3610
|
+
const files = [];
|
|
3611
|
+
const skipped = [];
|
|
3612
|
+
const target = await planProjectTarget(paths, dependencies, version);
|
|
3613
|
+
for (const path of paths) {
|
|
3614
|
+
const plan = await planFile(path, dependencies, target);
|
|
3615
|
+
if (plan.kind === "skipped") {
|
|
3616
|
+
skipped.push({ path, reason: plan.reason });
|
|
3617
|
+
dependencies.logger.warn("file_skipped", { path, reason: plan.reason });
|
|
3618
|
+
} else if (plan.kind === "planned") {
|
|
3619
|
+
const { report } = plan.file;
|
|
3620
|
+
const isReported = report.isChanged || report.manualSteps.length > 0;
|
|
3621
|
+
files.push(...isReported ? [plan.file] : []);
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
return { files, skipped };
|
|
3625
|
+
}
|
|
3626
|
+
async function writeFiles(planned, dependencies) {
|
|
3627
|
+
const changed = planned.flatMap(
|
|
3628
|
+
({ report, text }) => text === void 0 ? [] : [{ path: report.path, text }]
|
|
3629
|
+
);
|
|
3630
|
+
const written = [];
|
|
3631
|
+
for (const [index, { path, text }] of changed.entries()) {
|
|
3632
|
+
const result = await dependencies.files.write(path, text);
|
|
3633
|
+
if (result.kind === "failed") {
|
|
3634
|
+
dependencies.logger.warn("file_write_failed", { path, reason: result.reason, written });
|
|
3635
|
+
const notWritten = changed.slice(index + 1).map((file) => file.path);
|
|
3636
|
+
return { kind: "failed", written, failed: { path, reason: result.reason }, notWritten };
|
|
3637
|
+
}
|
|
3638
|
+
written.push(path);
|
|
3639
|
+
}
|
|
3640
|
+
return { kind: "written", paths: written };
|
|
3641
|
+
}
|
|
3642
|
+
async function migrateProject(dependencies, input) {
|
|
3643
|
+
const listing = await dependencies.files.listFiles();
|
|
3644
|
+
const paths = [...listing.files].sort();
|
|
3645
|
+
dependencies.logger.info("migration_started", { fileCount: paths.length, ...input });
|
|
3646
|
+
const plan = await planProject(paths, dependencies, input.version);
|
|
3647
|
+
const skipped = [...listing.skipped, ...plan.skipped];
|
|
3648
|
+
const write = input.isWrite ? await writeFiles(plan.files, dependencies) : { kind: "dry-run" };
|
|
3649
|
+
dependencies.logger.info("migration_finished", {
|
|
3650
|
+
changedFiles: plan.files.length,
|
|
3651
|
+
skippedFiles: skipped.length,
|
|
3652
|
+
write: write.kind
|
|
3653
|
+
});
|
|
3654
|
+
return { files: plan.files.map((file) => file.report), skipped, write };
|
|
1392
3655
|
}
|
|
1393
3656
|
|
|
1394
3657
|
const IR_CALLBACKS_GUIDE = "https://github.com/openapi-typesafe/openapi-typesafe/blob/main/docs/node.md#ir-callbacks";
|
|
1395
3658
|
const OPENAPI_FETCH_ALIAS_GUIDE = "https://github.com/openapi-typesafe/openapi-typesafe/blob/main/docs/fetch/index.md#libraries-built-on-openapi-fetch";
|
|
1396
|
-
function changeMessage(change) {
|
|
1397
|
-
switch (change.kind) {
|
|
1398
|
-
case "dependency-renamed":
|
|
1399
|
-
return `${change.field}: ${change.from} \u2192 ${change.to}@${change.spec}`;
|
|
1400
|
-
case "catalog-entry-renamed":
|
|
1401
|
-
return `catalog: ${change.from} \u2192 ${change.to}`;
|
|
1402
|
-
case "command-renamed":
|
|
1403
|
-
return `${change.location}: openapi-typescript \u2192 openapi-typesafe`;
|
|
1404
|
-
case "flag-renamed":
|
|
1405
|
-
return `${change.location}: ${change.from} \u2192 ${change.to}`;
|
|
1406
|
-
case "specifier-renamed":
|
|
1407
|
-
return `line ${change.line}: "${change.from}" \u2192 "${change.to}"`;
|
|
1408
|
-
case "transform-rewritten":
|
|
1409
|
-
return `line ${change.line}: transform returns type text; irCallbacks: true added`;
|
|
1410
|
-
default:
|
|
1411
|
-
return assertNever(change);
|
|
1412
|
-
}
|
|
1413
|
-
}
|
|
1414
3659
|
const KEEP_REASONS = {
|
|
1415
3660
|
"other-callbacks": "postTransform or transformProperty are set too, and they need TypeScript nodes",
|
|
1416
3661
|
"not-a-function": "it is not an inline function or a function of this file",
|
|
1417
3662
|
"shared-function": "the function it names is used elsewhere too",
|
|
3663
|
+
"shared-options": "its options object is used elsewhere too",
|
|
1418
3664
|
"unknown-return": "a return value is not a plain ts.factory type"
|
|
1419
3665
|
};
|
|
1420
3666
|
const PEER_NOTE = "users of this package must switch too (a breaking change for them)";
|
|
1421
3667
|
const PINNED_NOTE = "change it to openapi-typesafe@<version>";
|
|
3668
|
+
const OLD_FILE_NOTE = "is a file of openapi-typescript, which is no longer installed: run openapi-typesafe instead";
|
|
3669
|
+
const FLAGS_IN_VARIABLE_NOTE = "holds old flags of the generator and may serve other commands, so it was kept: rename";
|
|
3670
|
+
const NOT_ONLY_GENERATOR_NOTE = "runs openapi-typescript in some of its definitions in this file and another program in others, so the flags after it were kept; where it runs the generator, rename";
|
|
3671
|
+
const COMMAND_STRING_NOTE = "a string runs openapi-typescript, so it was kept; change it to";
|
|
3672
|
+
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}`;
|
|
3673
|
+
const MANIFEST_NOT_READ_NOTE = "is a JSON5 manifest, which the tool does not read: rename the packages and the generator command by hand, or write it as package.json and run the tool again";
|
|
1422
3674
|
const DEEP_IMPORT_NOTE = "imports a file inside the package; check that it exists";
|
|
3675
|
+
const DYNAMIC_NOTE = "builds its module name at run time from an old package name, so it was kept; rename it to";
|
|
1423
3676
|
const STRING_NOTE = "is a string, not a module specifier, so it was kept";
|
|
1424
3677
|
const STRING_FIX_NOTE = "if it names the package, change it to";
|
|
1425
3678
|
const KEPT_NOTE = `it works, with a deprecation warning. See ${IR_CALLBACKS_GUIDE}`;
|
|
1426
3679
|
const LEGACY_CALLBACK_NOTE = `receives TypeScript nodes (deprecated). See ${IR_CALLBACKS_GUIDE}`;
|
|
3680
|
+
const HELPER_TYPES_ENTRY = "openapi-typesafe-fetch/helpers";
|
|
3681
|
+
function packageNotDeclaredMessage(step) {
|
|
3682
|
+
const { line, name, installers } = step;
|
|
3683
|
+
return `line ${line}: no package.json or Deno import map at or above this file lists ${name}; ${installers.join(" and ")} installed it, the openapi-typesafe packages do not: add it to the dependencies, or import the types from ${HELPER_TYPES_ENTRY}`;
|
|
3684
|
+
}
|
|
1427
3685
|
function oldPeerMessage(step) {
|
|
1428
3686
|
const { dependent, peer, range } = step;
|
|
1429
3687
|
const successor = PACKAGE_RENAMES.get(peer) ?? peer;
|
|
1430
3688
|
return `${dependent} takes its client type from ${peer}@${range}, its peer dependency: alias ${peer} to ${successor} with overrides or resolutions, or passing it the ${successor} client fails to type-check. See ${OPENAPI_FETCH_ALIAS_GUIDE}`;
|
|
1431
3689
|
}
|
|
3690
|
+
function pinnedPackageMessage(step) {
|
|
3691
|
+
const successor = PACKAGE_RENAMES.get(step.name) ?? step.name;
|
|
3692
|
+
return `${step.location} names a pinned ${step.name}@<version>: change it to ${successor}@<version>`;
|
|
3693
|
+
}
|
|
3694
|
+
const flagRenames = (flags) => flags.map((flag) => `${flag} to ${FLAG_RENAMES.get(flag) ?? flag}`).join(", ");
|
|
3695
|
+
function flagsInVariableMessage(step) {
|
|
3696
|
+
return `${step.location}: ${step.variable} ${FLAGS_IN_VARIABLE_NOTE} ${flagRenames(step.flags)}`;
|
|
3697
|
+
}
|
|
3698
|
+
function notOnlyGeneratorMessage(step) {
|
|
3699
|
+
return `${step.location}: ${step.variable} ${NOT_ONLY_GENERATOR_NOTE} ${flagRenames(step.flags)}`;
|
|
3700
|
+
}
|
|
3701
|
+
const COMMAND_STEP_KINDS = /* @__PURE__ */ new Set([
|
|
3702
|
+
"versioned-command",
|
|
3703
|
+
"flags-in-variable",
|
|
3704
|
+
"variable-not-only-generator",
|
|
3705
|
+
"pinned-package",
|
|
3706
|
+
"old-package-file",
|
|
3707
|
+
"command-string"
|
|
3708
|
+
]);
|
|
3709
|
+
const isCommandStep = (step) => COMMAND_STEP_KINDS.has(step.kind);
|
|
3710
|
+
function commandStepMessage(step) {
|
|
3711
|
+
switch (step.kind) {
|
|
3712
|
+
case "versioned-command":
|
|
3713
|
+
return `${step.location} runs a pinned openapi-typescript@<version>: ${PINNED_NOTE}`;
|
|
3714
|
+
case "flags-in-variable":
|
|
3715
|
+
return flagsInVariableMessage(step);
|
|
3716
|
+
case "variable-not-only-generator":
|
|
3717
|
+
return notOnlyGeneratorMessage(step);
|
|
3718
|
+
case "pinned-package":
|
|
3719
|
+
return pinnedPackageMessage(step);
|
|
3720
|
+
case "old-package-file":
|
|
3721
|
+
return `${step.location}: ${step.path} ${OLD_FILE_NOTE}`;
|
|
3722
|
+
case "command-string":
|
|
3723
|
+
return `line ${step.line}: ${COMMAND_STRING_NOTE} "${step.to}"`;
|
|
3724
|
+
default:
|
|
3725
|
+
return assertNever(step);
|
|
3726
|
+
}
|
|
3727
|
+
}
|
|
3728
|
+
function uneditedStepMessage(step) {
|
|
3729
|
+
switch (step.kind) {
|
|
3730
|
+
case "config-unreadable":
|
|
3731
|
+
return step.position === void 0 ? "nests too deep to be read; migrate it by hand" : `cannot be parsed at line ${step.position.line}, column ${step.position.column}; migrate it by hand`;
|
|
3732
|
+
case "manifest-not-read":
|
|
3733
|
+
return MANIFEST_NOT_READ_NOTE;
|
|
3734
|
+
case "entry-not-edited":
|
|
3735
|
+
return `${step.field}: ${step.name} has a value the tool does not read; rename it by hand`;
|
|
3736
|
+
case "name-collision":
|
|
3737
|
+
return `${step.field} has both ${step.from} and ${step.to}, so ${step.from} was kept: remove one of them`;
|
|
3738
|
+
case "catalog-reference-kept":
|
|
3739
|
+
return `${step.field}: ${step.name} was kept, because catalog "${step.catalog}" keeps ${step.name}: rename the entry and the dependency together`;
|
|
3740
|
+
case "catalog-entry-in-use":
|
|
3741
|
+
return `${step.catalog}: ${step.name} was kept, because a dependency or an override still names it: rename the entry and what names it together`;
|
|
3742
|
+
default:
|
|
3743
|
+
return assertNever(step);
|
|
3744
|
+
}
|
|
3745
|
+
}
|
|
3746
|
+
function sourceStepMessage(step) {
|
|
3747
|
+
switch (step.kind) {
|
|
3748
|
+
case "options-not-inspected":
|
|
3749
|
+
return `line ${step.line}: ${OPTIONS_NOTE}`;
|
|
3750
|
+
case "deep-import":
|
|
3751
|
+
return `line ${step.line}: "${step.specifier}" ${DEEP_IMPORT_NOTE}`;
|
|
3752
|
+
case "dynamic-specifier":
|
|
3753
|
+
return `line ${step.line}: \`${step.value}\` ${DYNAMIC_NOTE} ${step.to}`;
|
|
3754
|
+
case "string-names-package":
|
|
3755
|
+
return `line ${step.line}: "${step.value}" ${STRING_NOTE}; ${STRING_FIX_NOTE} "${step.to}"`;
|
|
3756
|
+
case "transform-not-rewritten":
|
|
3757
|
+
return `line ${step.line}: transform kept (${KEEP_REASONS[step.reason]}); ${KEPT_NOTE}`;
|
|
3758
|
+
case "legacy-callback":
|
|
3759
|
+
return `line ${step.line}: ${step.callback} ${LEGACY_CALLBACK_NOTE}`;
|
|
3760
|
+
case "package-not-declared":
|
|
3761
|
+
return packageNotDeclaredMessage(step);
|
|
3762
|
+
default:
|
|
3763
|
+
return assertNever(step);
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
1432
3766
|
function manualStepMessage(step) {
|
|
3767
|
+
if (isCommandStep(step)) {
|
|
3768
|
+
return commandStepMessage(step);
|
|
3769
|
+
}
|
|
1433
3770
|
switch (step.kind) {
|
|
1434
|
-
case "
|
|
1435
|
-
|
|
3771
|
+
case "config-unreadable":
|
|
3772
|
+
case "manifest-not-read":
|
|
3773
|
+
case "entry-not-edited":
|
|
3774
|
+
case "name-collision":
|
|
3775
|
+
case "catalog-reference-kept":
|
|
3776
|
+
case "catalog-entry-in-use":
|
|
3777
|
+
return uneditedStepMessage(step);
|
|
1436
3778
|
case "peer-dependency-renamed":
|
|
1437
3779
|
return `${step.name} is a peer dependency: ${PEER_NOTE}`;
|
|
1438
3780
|
case "spec-kept":
|
|
1439
3781
|
return `${step.name} kept the spec "${step.spec}": point it at an openapi-typesafe version`;
|
|
1440
3782
|
case "override-names-package":
|
|
1441
3783
|
return `"${step.field}" names ${step.name}: update or remove that override`;
|
|
1442
|
-
case "
|
|
1443
|
-
return `${step.location} runs a pinned openapi-typescript@<version>: ${PINNED_NOTE}`;
|
|
3784
|
+
case "options-not-inspected":
|
|
1444
3785
|
case "deep-import":
|
|
1445
|
-
|
|
3786
|
+
case "dynamic-specifier":
|
|
1446
3787
|
case "string-names-package":
|
|
1447
|
-
return `line ${step.line}: "${step.value}" ${STRING_NOTE}; ${STRING_FIX_NOTE} "${step.to}"`;
|
|
1448
|
-
case "old-peer-needed":
|
|
1449
|
-
return oldPeerMessage(step);
|
|
1450
3788
|
case "transform-not-rewritten":
|
|
1451
|
-
return `line ${step.line}: transform kept (${KEEP_REASONS[step.reason]}); ${KEPT_NOTE}`;
|
|
1452
3789
|
case "legacy-callback":
|
|
1453
|
-
|
|
3790
|
+
case "package-not-declared":
|
|
3791
|
+
return sourceStepMessage(step);
|
|
3792
|
+
case "old-peer-needed":
|
|
3793
|
+
return oldPeerMessage(step);
|
|
1454
3794
|
default:
|
|
1455
3795
|
return assertNever(step);
|
|
1456
3796
|
}
|
|
1457
3797
|
}
|
|
3798
|
+
|
|
3799
|
+
function changeMessage(change) {
|
|
3800
|
+
switch (change.kind) {
|
|
3801
|
+
case "dependency-renamed":
|
|
3802
|
+
return `${change.field}: ${change.from} \u2192 ${change.to}@${change.spec}`;
|
|
3803
|
+
case "catalog-entry-renamed":
|
|
3804
|
+
return `${change.catalog}: ${change.from} \u2192 ${change.to}@${change.spec}`;
|
|
3805
|
+
case "import-renamed":
|
|
3806
|
+
return `${change.field}: "${change.from}" \u2192 "${change.to}" (${change.target})`;
|
|
3807
|
+
case "alias-renamed":
|
|
3808
|
+
return `${change.field}: ${change.name} \u2192 ${change.spec}`;
|
|
3809
|
+
case "command-renamed":
|
|
3810
|
+
return `${change.location}: openapi-typescript \u2192 openapi-typesafe`;
|
|
3811
|
+
case "flag-renamed":
|
|
3812
|
+
case "package-name-renamed":
|
|
3813
|
+
return `${change.location}: ${change.from} \u2192 ${change.to}`;
|
|
3814
|
+
case "specifier-renamed":
|
|
3815
|
+
return `line ${change.line}: "${change.from}" \u2192 "${change.to}"`;
|
|
3816
|
+
case "transform-rewritten":
|
|
3817
|
+
return `line ${change.line}: transform returns type text; irCallbacks: true added`;
|
|
3818
|
+
case "declaration-removed":
|
|
3819
|
+
return `line ${change.line}: ${change.name} removed; only the old transform returns used it`;
|
|
3820
|
+
default:
|
|
3821
|
+
return assertNever(change);
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
1458
3824
|
const SKIPPED_HEADING = "Skipped (not read, not written):";
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
3825
|
+
function skippedMessage(reason) {
|
|
3826
|
+
switch (reason.kind) {
|
|
3827
|
+
case "outside-root":
|
|
3828
|
+
return "it leads outside the project folder (a link, or a path in the git index)";
|
|
3829
|
+
case "not-utf-8":
|
|
3830
|
+
return "it is not UTF-8 text, so it was not changed; check it by hand";
|
|
3831
|
+
case "io-error":
|
|
3832
|
+
return `it could not be read (${reason.code})`;
|
|
3833
|
+
default:
|
|
3834
|
+
return assertNever(reason);
|
|
3835
|
+
}
|
|
3836
|
+
}
|
|
3837
|
+
function writeProblemMessage(reason) {
|
|
3838
|
+
switch (reason.kind) {
|
|
3839
|
+
case "outside-root":
|
|
3840
|
+
return "it leads outside the project folder";
|
|
3841
|
+
case "not-utf-8":
|
|
3842
|
+
return "its text is not UTF-8";
|
|
3843
|
+
case "io-error":
|
|
3844
|
+
return reason.code;
|
|
3845
|
+
default:
|
|
3846
|
+
return assertNever(reason);
|
|
3847
|
+
}
|
|
1464
3848
|
}
|
|
3849
|
+
const writeFailedMessage = (path, reason) => `Writing stopped: ${path} could not be written (${writeProblemMessage(reason)}).`;
|
|
3850
|
+
const WRITTEN_HEADING = "Written before it:";
|
|
3851
|
+
const NOT_WRITTEN_HEADING = "Not written (still as they were):";
|
|
3852
|
+
const NONE_WRITTEN = "Nothing was written before it.";
|
|
1465
3853
|
const NEXT_STEPS = [
|
|
1466
3854
|
"Install: run your package manager's install, so the lockfile has the new packages.",
|
|
1467
3855
|
"Regenerate your types and commit them once: the file header names openapi-typesafe now, so `--check` fails until you do.",
|
|
@@ -1495,10 +3883,29 @@ function skippedSection(skipped) {
|
|
|
1495
3883
|
if (skipped.length === 0) {
|
|
1496
3884
|
return [];
|
|
1497
3885
|
}
|
|
1498
|
-
const lines = skipped.map((file) => ` ${file.path}: ${
|
|
3886
|
+
const lines = skipped.map((file) => ` ${file.path}: ${skippedMessage(file.reason)}`);
|
|
1499
3887
|
return [SKIPPED_HEADING, ...lines, ""];
|
|
1500
3888
|
}
|
|
1501
|
-
|
|
3889
|
+
const listed = (paths) => paths.map((path) => ` ${path}`);
|
|
3890
|
+
function writeSection(write) {
|
|
3891
|
+
switch (write.kind) {
|
|
3892
|
+
case "dry-run":
|
|
3893
|
+
return [DRY_RUN_NOTE];
|
|
3894
|
+
case "written":
|
|
3895
|
+
return [WRITTEN_NOTE];
|
|
3896
|
+
case "failed":
|
|
3897
|
+
return [
|
|
3898
|
+
writeFailedMessage(write.failed.path, write.failed.reason),
|
|
3899
|
+
...write.written.length === 0 ? [NONE_WRITTEN] : [WRITTEN_HEADING],
|
|
3900
|
+
...listed(write.written),
|
|
3901
|
+
...write.notWritten.length === 0 ? [] : [NOT_WRITTEN_HEADING],
|
|
3902
|
+
...listed(write.notWritten)
|
|
3903
|
+
];
|
|
3904
|
+
default:
|
|
3905
|
+
return assertNever(write);
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3908
|
+
function renderReport(migration) {
|
|
1502
3909
|
const reports = migration.files;
|
|
1503
3910
|
const skipped = skippedSection(migration.skipped);
|
|
1504
3911
|
if (reports.length === 0) {
|
|
@@ -1512,7 +3919,7 @@ function renderReport(migration, isWrite) {
|
|
|
1512
3919
|
"Next steps:",
|
|
1513
3920
|
...NEXT_STEPS.map((step, index) => ` ${index + 1}. ${step}`),
|
|
1514
3921
|
"",
|
|
1515
|
-
|
|
3922
|
+
...writeSection(migration.write)
|
|
1516
3923
|
];
|
|
1517
3924
|
return `${lines.join("\n")}
|
|
1518
3925
|
`;
|
|
@@ -1556,7 +3963,8 @@ Options
|
|
|
1556
3963
|
|
|
1557
3964
|
Exit codes
|
|
1558
3965
|
0 Done
|
|
1559
|
-
1 The migration failed (the message and the log say why),
|
|
3966
|
+
1 The migration failed (the message and the log say why), a file could not be written
|
|
3967
|
+
(the report names what was written), or the log could not be created
|
|
1560
3968
|
2 Unknown option or argument
|
|
1561
3969
|
`;
|
|
1562
3970
|
const usageError = (message) => `${message}
|
|
@@ -1564,6 +3972,12 @@ Run openapi-typesafe-migrate --help for the options.
|
|
|
1564
3972
|
`;
|
|
1565
3973
|
const logNote = (path) => `Log: ${path}
|
|
1566
3974
|
`;
|
|
3975
|
+
const ROOT_PROBLEMS = {
|
|
3976
|
+
missing: "does not exist",
|
|
3977
|
+
"not-a-directory": "is not a folder; --root takes the project folder"
|
|
3978
|
+
};
|
|
3979
|
+
const rootError = (root, state) => `The project folder ${root} ${ROOT_PROBLEMS[state]}
|
|
3980
|
+
`;
|
|
1567
3981
|
const logError = (path, message) => `Cannot create the log ${path ?? "in the temporary directory"}: ${message}
|
|
1568
3982
|
`;
|
|
1569
3983
|
|
|
@@ -1588,6 +4002,20 @@ function openLog(request, dependencies) {
|
|
|
1588
4002
|
return void 0;
|
|
1589
4003
|
}
|
|
1590
4004
|
}
|
|
4005
|
+
async function migrateRoot(request, root, logger, dependencies) {
|
|
4006
|
+
const rootState = await dependencies.inspectRoot(root);
|
|
4007
|
+
if (rootState !== "directory") {
|
|
4008
|
+
logger.warn("root_unusable", { root, rootState });
|
|
4009
|
+
dependencies.writeStderr(rootError(root, rootState));
|
|
4010
|
+
return EXIT_CODES.failure;
|
|
4011
|
+
}
|
|
4012
|
+
const files = dependencies.createFiles(root, logger);
|
|
4013
|
+
const { rewriter, scanner, readers } = dependencies;
|
|
4014
|
+
const input = { version: dependencies.version, isWrite: request.isWrite };
|
|
4015
|
+
const migration = await migrateProject({ files, rewriter, scanner, readers, logger }, input);
|
|
4016
|
+
dependencies.writeStdout(renderReport(migration));
|
|
4017
|
+
return migration.write.kind === "failed" ? EXIT_CODES.failure : EXIT_CODES.success;
|
|
4018
|
+
}
|
|
1591
4019
|
async function runMigration(request, dependencies) {
|
|
1592
4020
|
const log = openLog(request, dependencies);
|
|
1593
4021
|
if (log === void 0) {
|
|
@@ -1596,12 +4024,7 @@ async function runMigration(request, dependencies) {
|
|
|
1596
4024
|
const { logger, path } = log;
|
|
1597
4025
|
const root = dependencies.resolvePath(dependencies.cwd, request.root ?? ".");
|
|
1598
4026
|
try {
|
|
1599
|
-
|
|
1600
|
-
const { rewriter, scanner } = dependencies;
|
|
1601
|
-
const input = { version: dependencies.version, isWrite: request.isWrite };
|
|
1602
|
-
const migration = await migrateProject({ files, rewriter, scanner, logger }, input);
|
|
1603
|
-
dependencies.writeStdout(renderReport(migration, request.isWrite));
|
|
1604
|
-
return EXIT_CODES.success;
|
|
4027
|
+
return await migrateRoot(request, root, logger, dependencies);
|
|
1605
4028
|
} catch (error) {
|
|
1606
4029
|
const message = errorMessage(error);
|
|
1607
4030
|
logger.warn("migration_failed", { root, message });
|
|
@@ -1640,15 +4063,23 @@ function createLogger(path) {
|
|
|
1640
4063
|
const logPath = isExclusive ? createTemporaryLogPath(tmpdir(), now()) : resolve(path);
|
|
1641
4064
|
return { logger: createJsonLinesLogger({ path: logPath, now, isExclusive }), path: logPath };
|
|
1642
4065
|
}
|
|
4066
|
+
const createGit = (root) => createGitFileList({
|
|
4067
|
+
environment: process.env,
|
|
4068
|
+
platform: process.platform,
|
|
4069
|
+
fileSystem: createNodeGitFileSystem(),
|
|
4070
|
+
projectRoot: root
|
|
4071
|
+
});
|
|
1643
4072
|
const dependencies = {
|
|
1644
4073
|
version: readVersion(),
|
|
1645
4074
|
cwd: process.cwd(),
|
|
1646
4075
|
writeStdout: (text) => process.stdout.write(text),
|
|
1647
4076
|
writeStderr: (text) => process.stderr.write(text),
|
|
1648
|
-
|
|
4077
|
+
inspectRoot: inspectProjectRoot,
|
|
4078
|
+
createFiles: (root, logger) => createNodeProjectFiles(root, { git: createGit(root), logger }),
|
|
1649
4079
|
createLogger,
|
|
1650
4080
|
rewriter: createTypescriptCallbackRewriter(),
|
|
1651
4081
|
scanner: createTypescriptStringScanner(),
|
|
4082
|
+
readers: { json: createJsonTextReader(), yaml: createYamlTextReader() },
|
|
1652
4083
|
resolvePath: resolve
|
|
1653
4084
|
};
|
|
1654
4085
|
runCli(process.argv.slice(2), dependencies).then((exitCode) => {
|