modern-cmdk 1.4.0 → 1.5.0
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/README.md +31 -27
- package/THIRD-PARTY-NOTICES.md +303 -0
- package/dist/codemod/bin.mjs +14 -0
- package/dist/codemod/chunk-jscodeshift.mjs +36210 -0
- package/dist/codemod/chunk-print-options.mjs +43 -0
- package/dist/codemod/chunk-worker.mjs +476 -0
- package/dist/codemod/cli.mjs +297 -43660
- package/dist/codemod/transforms/data-attrs.mjs +551 -38
- package/dist/codemod/transforms/forward-ref.mjs +197 -32
- package/dist/codemod/transforms/import-rewrite.mjs +389 -30
- package/dist/codemod/transforms/should-filter.mjs +199 -10
- package/dist/codemod/worker.mjs +1 -0
- package/dist/core/index.d.mts +6 -1
- package/dist/core/index.mjs +12 -4
- package/dist/react/dialog.mjs +0 -1
- package/dist/react/index.d.mts +21 -3
- package/dist/react/index.mjs +45 -16
- package/package.json +5 -7
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// ../modern-cmdk-codemod/src/print-options.ts
|
|
2
|
+
const TOKEN = /\/\/[^\n]*|\/\*[\s\S]*?\*\/|`(?:[^`\\]|\\[\s\S])*`|(?<s>'(?:[^'\\\n]|\\.)*'|"(?:[^"\\\n]|\\.)*")/g;
|
|
3
|
+
const SPECIFIER_CONTEXT = /(?:\bfrom|\bimport|\brequire\s*\(|\bimport\s*\()\s*$/;
|
|
4
|
+
function quoteOf(raw) {
|
|
5
|
+
if (raw.startsWith("'")) return "single";
|
|
6
|
+
if (raw.startsWith("\"")) return "double";
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Print options that reproduce the source file's line terminator and quote style.
|
|
10
|
+
*
|
|
11
|
+
* - lineTerminator: CRLF when the source contains a CRLF, else LF.
|
|
12
|
+
* - quote: the quote of `sampleRaw` when it starts with one; otherwise the quote of the first
|
|
13
|
+
* import, export-from, require or dynamic-import specifier; otherwise the more frequent quote
|
|
14
|
+
* of the file's string literals, a tie giving double.
|
|
15
|
+
*/
|
|
16
|
+
function printOptions(source, sampleRaw) {
|
|
17
|
+
const lineTerminator = source.includes("\r\n") ? "\r\n" : "\n";
|
|
18
|
+
const sampled = sampleRaw === void 0 ? void 0 : quoteOf(sampleRaw);
|
|
19
|
+
if (sampled !== void 0) return {
|
|
20
|
+
quote: sampled,
|
|
21
|
+
lineTerminator
|
|
22
|
+
};
|
|
23
|
+
let single = 0;
|
|
24
|
+
let double = 0;
|
|
25
|
+
let specifier;
|
|
26
|
+
for (const match of source.matchAll(TOKEN)) {
|
|
27
|
+
const raw = match.groups?.s;
|
|
28
|
+
if (raw === void 0) continue;
|
|
29
|
+
const quote = raw.startsWith("'") ? "single" : "double";
|
|
30
|
+
if (quote === "single") single++;
|
|
31
|
+
else double++;
|
|
32
|
+
if (specifier === void 0 && SPECIFIER_CONTEXT.test(source.slice(Math.max(0, match.index - 32), match.index))) specifier = quote;
|
|
33
|
+
}
|
|
34
|
+
if (specifier !== void 0) return {
|
|
35
|
+
quote: specifier,
|
|
36
|
+
lineTerminator
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
quote: single > double ? "single" : "double",
|
|
40
|
+
lineTerminator
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export { printOptions as t };
|
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import { n as __toESM, t as require_jscodeshift } from "./chunk-jscodeshift.mjs";
|
|
2
|
+
import { access, constants, glob, lstat, readFile, realpath, stat } from "node:fs/promises";
|
|
3
|
+
import { basename, delimiter, dirname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { isMainThread, parentPort, workerData } from "node:worker_threads";
|
|
5
|
+
import { execFile } from "node:child_process";
|
|
6
|
+
// ../modern-cmdk-codemod/src/files.ts
|
|
7
|
+
const PARSERS = /* @__PURE__ */ new Map([
|
|
8
|
+
[".ts", "ts"],
|
|
9
|
+
[".mts", "ts"],
|
|
10
|
+
[".cts", "ts"],
|
|
11
|
+
[".tsx", "tsx"],
|
|
12
|
+
[".js", "babel"],
|
|
13
|
+
[".jsx", "babel"],
|
|
14
|
+
[".mjs", "babel"],
|
|
15
|
+
[".cjs", "babel"]
|
|
16
|
+
]);
|
|
17
|
+
const STYLESHEETS = /* @__PURE__ */ new Set([".css", ".scss"]);
|
|
18
|
+
function extensionOf(path) {
|
|
19
|
+
const dot = path.lastIndexOf(".");
|
|
20
|
+
return dot > Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")) ? path.slice(dot).toLowerCase() : "";
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The jscodeshift parser for a script file: `ts` for .ts/.mts/.cts, `tsx` for .tsx, `babel` (which
|
|
24
|
+
* parses JSX) for .js/.jsx/.mjs/.cjs. `undefined` for anything that is not a script.
|
|
25
|
+
*/
|
|
26
|
+
function parserFor(path) {
|
|
27
|
+
return PARSERS.get(extensionOf(path));
|
|
28
|
+
}
|
|
29
|
+
function routeFor(path) {
|
|
30
|
+
if (PARSERS.has(extensionOf(path))) return "script";
|
|
31
|
+
return STYLESHEETS.has(extensionOf(path)) ? "stylesheet" : "skip";
|
|
32
|
+
}
|
|
33
|
+
/** What a directory argument expands to, relative to that directory. */
|
|
34
|
+
const DIRECTORY_GLOB = "**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs,css,scss}";
|
|
35
|
+
/**
|
|
36
|
+
* On win32 every backslash of a command-line pattern becomes '/' (glob syntax has no other use for
|
|
37
|
+
* it there). Elsewhere a backslash is an escape and stays. Braces are never escaped: the directory
|
|
38
|
+
* form needs them.
|
|
39
|
+
*/
|
|
40
|
+
function normalizePattern(arg, platform = process.platform) {
|
|
41
|
+
return platform === "win32" ? arg.replaceAll("\\", "/") : arg;
|
|
42
|
+
}
|
|
43
|
+
const LEGACY_EXCLUDES = /* @__PURE__ */ new Set([
|
|
44
|
+
"node_modules",
|
|
45
|
+
"dist",
|
|
46
|
+
"build",
|
|
47
|
+
".next"
|
|
48
|
+
]);
|
|
49
|
+
const CODEMOD_EXCLUDES = /* @__PURE__ */ new Set([
|
|
50
|
+
...LEGACY_EXCLUDES,
|
|
51
|
+
".git",
|
|
52
|
+
"coverage",
|
|
53
|
+
".turbo",
|
|
54
|
+
"out",
|
|
55
|
+
"storybook-static",
|
|
56
|
+
".vercel"
|
|
57
|
+
]);
|
|
58
|
+
/**
|
|
59
|
+
* The directory that holds the `.git/modules` a gitdir target lies under, else `undefined`. A
|
|
60
|
+
* submodule's `.git` file reads `gitdir: <super>/.git/modules/<name>`; a linked worktree's reads
|
|
61
|
+
* `gitdir: <main>/.git/worktrees/<name>`. `target` is a realpath.
|
|
62
|
+
*/
|
|
63
|
+
function superprojectOf(target) {
|
|
64
|
+
const segments = target.split(sep);
|
|
65
|
+
const at = segments.findLastIndex((segment, index) => segment === ".git" && segments[index + 1] === "modules");
|
|
66
|
+
return at < 1 ? void 0 : segments.slice(0, at).join(sep);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The trust boundary of one operation: the realpath of the nearest directory, from `realpath(cwd)`
|
|
70
|
+
* upward, that holds a `.git` entry (a directory, or the file a worktree or submodule has), else
|
|
71
|
+
* `realpath(cwd)` when no ancestor holds one. It is the work tree `git rev-parse --show-toplevel`
|
|
72
|
+
* reports, computed without spawning git, and realpath'd so that a linked cwd (macOS's `/var` ->
|
|
73
|
+
* `/private/var`) or a short Windows path (`RUNNER~1`) compares equal to what git prints. Not the
|
|
74
|
+
* cwd, which may be a subdirectory of the tree whose other directories a planted git can sit in.
|
|
75
|
+
* Not the outermost work tree either: a dotfiles repository at the home directory would exclude
|
|
76
|
+
* every PATH entry under the home (shims, `~/.local/bin`) and leave the user with no git.
|
|
77
|
+
* Three cases make a `.git` entry reach further. A `.git` directory (an ordinary repository, a
|
|
78
|
+
* nested one included) stops the climb. A `.git` file whose `gitdir:` target lies under a
|
|
79
|
+
* `.git/modules` directory is a submodule: the superproject around it came from the same clone,
|
|
80
|
+
* so the climb goes on from there, and again while that superproject is itself a submodule. A `.git`
|
|
81
|
+
* file for a linked worktree (target under `.git/worktrees`), or one that cannot be read or
|
|
82
|
+
* resolved, stops the climb: the main repository of a worktree is the user's own.
|
|
83
|
+
*/
|
|
84
|
+
async function trustBoundary(cwd) {
|
|
85
|
+
const start = await realpath(cwd);
|
|
86
|
+
let at = start;
|
|
87
|
+
for (;;) {
|
|
88
|
+
const entry = await lstat(join(at, ".git")).catch(() => void 0);
|
|
89
|
+
if (entry === void 0) {
|
|
90
|
+
if (dirname(at) === at) return start;
|
|
91
|
+
at = dirname(at);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const superproject = entry.isFile() ? await superprojectAbove(at) : void 0;
|
|
95
|
+
if (superproject === void 0) return at;
|
|
96
|
+
at = superproject;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** The superproject of the submodule checked out at `at`, from its `.git` file; `undefined` if it is not one. */
|
|
100
|
+
async function superprojectAbove(at) {
|
|
101
|
+
try {
|
|
102
|
+
const link = /^gitdir:\s*(.+?)\s*$/m.exec(await readFile(join(at, ".git"), "utf8"));
|
|
103
|
+
return link?.[1] === void 0 ? void 0 : superprojectOf(await realpath(resolve(at, link[1])));
|
|
104
|
+
} catch {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** True when `path` is `boundary` or lies under it. Written once, for gitSearchDirs and resolveGit. */
|
|
109
|
+
function inside(boundary, path) {
|
|
110
|
+
const rel = relative(boundary, path);
|
|
111
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The directories of a PATH value that may hold git: absolute entries outside `boundary`, the work
|
|
115
|
+
* tree being migrated. Empty and relative entries are dropped (they name the working directory), and
|
|
116
|
+
* so is any entry equal to or under `boundary`, which may carry a planted executable. Order is kept.
|
|
117
|
+
* This is the syntactic filter; resolveGit also compares real paths.
|
|
118
|
+
*/
|
|
119
|
+
function gitSearchDirs(pathValue, boundary) {
|
|
120
|
+
return (pathValue ?? "").split(delimiter).filter((entry) => entry !== "" && isAbsolute(entry) && !inside(boundary, entry));
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* True when `candidate` is a regular file (a directory named git is not git) that the process may
|
|
124
|
+
* execute (`access` with X_OK is a plain existence check on Windows) and whose own realpath lies
|
|
125
|
+
* outside `boundary`, so a link into the tree is refused.
|
|
126
|
+
*/
|
|
127
|
+
async function isGitOutside(candidate, boundary) {
|
|
128
|
+
try {
|
|
129
|
+
if (!(await stat(candidate)).isFile()) return false;
|
|
130
|
+
await access(candidate, constants.X_OK);
|
|
131
|
+
return !inside(boundary, await realpath(candidate));
|
|
132
|
+
} catch {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const resolvedGit = /* @__PURE__ */ new Map();
|
|
137
|
+
/**
|
|
138
|
+
* The absolute path of the first git executable in PATH's `gitSearchDirs`, so the spawn does no
|
|
139
|
+
* search of its own. A kept entry is skipped when its realpath is inside `boundary` (a link or an
|
|
140
|
+
* alias into the tree) or does not resolve. Looks for `git.exe` then `git.com` on win32 (the names
|
|
141
|
+
* libuv tries) and `git` elsewhere; isGitOutside says what a candidate must be. `boundary` comes
|
|
142
|
+
* from trustBoundary and is the same for every spawn of one operation, so a submodule's directory
|
|
143
|
+
* or a subdirectory cwd never narrows it. The answer is kept per boundary and PATH value for the
|
|
144
|
+
* life of the process. Rejects with ENOENT, naming `boundary`, when there is none.
|
|
145
|
+
*/
|
|
146
|
+
function resolveGit(boundary) {
|
|
147
|
+
const pathValue = process.env.PATH;
|
|
148
|
+
const cacheKey = JSON.stringify([boundary, pathValue]);
|
|
149
|
+
const cached = resolvedGit.get(cacheKey);
|
|
150
|
+
if (cached !== void 0) return cached;
|
|
151
|
+
const names = process.platform === "win32" ? ["git.exe", "git.com"] : ["git"];
|
|
152
|
+
const found = (async () => {
|
|
153
|
+
for (const searchDir of gitSearchDirs(pathValue, boundary)) {
|
|
154
|
+
const realDir = await realpath(searchDir).catch(() => void 0);
|
|
155
|
+
if (realDir === void 0 || inside(boundary, realDir)) continue;
|
|
156
|
+
for (const name of names) {
|
|
157
|
+
const candidate = join(searchDir, name);
|
|
158
|
+
if (await isGitOutside(candidate, boundary)) return candidate;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
throw Object.assign(/* @__PURE__ */ new Error(`git was not found on PATH outside ${boundary}`), { code: "ENOENT" });
|
|
162
|
+
})();
|
|
163
|
+
resolvedGit.set(cacheKey, found);
|
|
164
|
+
found.catch(() => resolvedGit.delete(cacheKey));
|
|
165
|
+
return found;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Run git in `cwd` with execFile on the absolute path resolveGit finds outside `boundary`. The two
|
|
169
|
+
* differ on purpose: `cwd` is where git runs (the repository root, a submodule's directory or a
|
|
170
|
+
* subdirectory); `boundary` is the operation's trustBoundary, what PATH is filtered against. Resolves
|
|
171
|
+
* on any exit code; rejects on a spawn failure, and with ENOENT when git is missing. Reads
|
|
172
|
+
* process.env.PATH at each call, so tests steer it with vi.stubEnv.
|
|
173
|
+
*/
|
|
174
|
+
async function runGit(args, cwd, boundary, input = "") {
|
|
175
|
+
const executable = await resolveGit(boundary);
|
|
176
|
+
const { promise, resolve: done, reject } = Promise.withResolvers();
|
|
177
|
+
const child = execFile(executable, [...args], {
|
|
178
|
+
cwd,
|
|
179
|
+
encoding: "utf8",
|
|
180
|
+
maxBuffer: 1024 ** 3,
|
|
181
|
+
windowsHide: true
|
|
182
|
+
}, (error, stdout, stderr) => {
|
|
183
|
+
if (error === null) return done({
|
|
184
|
+
code: 0,
|
|
185
|
+
stdout,
|
|
186
|
+
stderr
|
|
187
|
+
});
|
|
188
|
+
const code = error.code;
|
|
189
|
+
if (typeof code === "number") return done({
|
|
190
|
+
code,
|
|
191
|
+
stdout,
|
|
192
|
+
stderr
|
|
193
|
+
});
|
|
194
|
+
reject(error);
|
|
195
|
+
});
|
|
196
|
+
child.stdin?.on("error", () => {});
|
|
197
|
+
child.stdin?.end(input);
|
|
198
|
+
return promise;
|
|
199
|
+
}
|
|
200
|
+
async function locateRepo(cwd, boundary) {
|
|
201
|
+
try {
|
|
202
|
+
const { code, stdout } = await runGit(["rev-parse", "--show-toplevel"], cwd, boundary);
|
|
203
|
+
return code === 0 ? {
|
|
204
|
+
kind: "repo",
|
|
205
|
+
root: resolve(stdout.trim())
|
|
206
|
+
} : { kind: "none" };
|
|
207
|
+
} catch (error) {
|
|
208
|
+
if (error.code === "ENOENT") return { kind: "missing" };
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/** Paths of `files` relative to the repo root (forward slashes), `undefined` for those outside it. */
|
|
213
|
+
async function relativeToRoot(files, cwd, root) {
|
|
214
|
+
const prefix = relative(await realpath(root), await realpath(cwd));
|
|
215
|
+
return files.map((file) => {
|
|
216
|
+
const rel = normalize(join(prefix, relative(cwd, file)));
|
|
217
|
+
return rel.startsWith("..") || isAbsolute(rel) ? void 0 : rel.replaceAll("\\", "/");
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
const key = (path) => process.platform === "win32" ? path.toLowerCase() : path;
|
|
221
|
+
/**
|
|
222
|
+
* Root-relative paths of the gitlinks (mode 160000: submodules) in the repository at `root`. One
|
|
223
|
+
* level is enough: a submodule nested inside a submodule is not looked for.
|
|
224
|
+
*/
|
|
225
|
+
async function gitlinksOf(root, boundary) {
|
|
226
|
+
const { code, stdout, stderr } = await runGit([
|
|
227
|
+
"ls-files",
|
|
228
|
+
"-s",
|
|
229
|
+
"-z"
|
|
230
|
+
], root, boundary);
|
|
231
|
+
if (code !== 0) throw new Error(`git ls-files failed (exit ${code}): ${stderr.trim()}`);
|
|
232
|
+
return stdout.split("\0").filter((entry) => entry.startsWith("160000 ")).map((entry) => entry.slice(entry.indexOf(" ") + 1));
|
|
233
|
+
}
|
|
234
|
+
/** The paths of `rels` that `git check-ignore` run in `owner` reports as ignored. */
|
|
235
|
+
async function ignoredIn(owner, rels, boundary) {
|
|
236
|
+
const { code, stdout, stderr } = await runGit([
|
|
237
|
+
"check-ignore",
|
|
238
|
+
"-z",
|
|
239
|
+
"--stdin"
|
|
240
|
+
], owner, boundary, rels.join("\0"));
|
|
241
|
+
if (code !== 0 && code !== 1) throw new Error(`git check-ignore failed (exit ${code}): ${stderr.trim()}`);
|
|
242
|
+
return new Set(stdout.split("\0").filter((p) => p !== ""));
|
|
243
|
+
}
|
|
244
|
+
async function dropGitIgnored(files, cwd, boundary, warn) {
|
|
245
|
+
const repo = await locateRepo(cwd, boundary);
|
|
246
|
+
if (repo.kind === "missing") {
|
|
247
|
+
warn("warning: git not found; .gitignore is not applied");
|
|
248
|
+
return files;
|
|
249
|
+
}
|
|
250
|
+
if (repo.kind === "none") return files;
|
|
251
|
+
const rels = await relativeToRoot(files, cwd, repo.root);
|
|
252
|
+
if (!rels.some((rel) => rel !== void 0)) return files;
|
|
253
|
+
const gitlinks = (await gitlinksOf(repo.root, boundary)).map(key);
|
|
254
|
+
const groups = /* @__PURE__ */ new Map();
|
|
255
|
+
rels.forEach((rel, index) => {
|
|
256
|
+
if (rel === void 0) return;
|
|
257
|
+
const link = gitlinks.filter((g) => key(rel).startsWith(`${g}/`)).reduce((deepest, g) => g.length > deepest.length ? g : deepest, "");
|
|
258
|
+
const group = groups.get(link) ?? {
|
|
259
|
+
root: link === "" ? repo.root : join(repo.root, rel.slice(0, link.length)),
|
|
260
|
+
indexes: [],
|
|
261
|
+
paths: []
|
|
262
|
+
};
|
|
263
|
+
group.indexes.push(index);
|
|
264
|
+
group.paths.push(link === "" ? rel : rel.slice(link.length + 1));
|
|
265
|
+
groups.set(link, group);
|
|
266
|
+
});
|
|
267
|
+
const drop = /* @__PURE__ */ new Set();
|
|
268
|
+
for (const group of groups.values()) {
|
|
269
|
+
const ignored = await ignoredIn(group.root, group.paths, boundary);
|
|
270
|
+
group.paths.forEach((path, i) => {
|
|
271
|
+
if (ignored.has(path)) drop.add(group.indexes[i]);
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
return files.filter((_, index) => !drop.has(index));
|
|
275
|
+
}
|
|
276
|
+
/** realpath of a file; for one deleted since discovery, its parent's realpath plus its name. */
|
|
277
|
+
async function realpathOrParent(file) {
|
|
278
|
+
try {
|
|
279
|
+
return await realpath(file);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
if (error.code !== "ENOENT") throw error;
|
|
282
|
+
return join(await realpath(dirname(file)), basename(file));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* `git status --porcelain -z` in `cwd`, intersected with `files`. Untracked files count as dirty
|
|
287
|
+
* (a codemod write to one cannot be undone); ignored files were already dropped by discovery. A
|
|
288
|
+
* modified submodule (status runs with `--ignore-submodules=none`, so modified or untracked files in
|
|
289
|
+
* it count) or an untracked directory entry (a nested repository, which git never descends into, or
|
|
290
|
+
* any untracked directory) makes every matched file under it dirty.
|
|
291
|
+
*/
|
|
292
|
+
async function findDirtyFiles(files, cwd) {
|
|
293
|
+
const boundary = await trustBoundary(cwd);
|
|
294
|
+
const repo = await locateRepo(cwd, boundary);
|
|
295
|
+
if (repo.kind === "missing") return { kind: "no-git" };
|
|
296
|
+
if (repo.kind === "none") return { kind: "no-repo" };
|
|
297
|
+
const { code, stdout, stderr } = await runGit([
|
|
298
|
+
"--no-optional-locks",
|
|
299
|
+
"status",
|
|
300
|
+
"--porcelain",
|
|
301
|
+
"-z",
|
|
302
|
+
"--untracked-files=all",
|
|
303
|
+
"--ignore-submodules=none"
|
|
304
|
+
], cwd, boundary);
|
|
305
|
+
if (code !== 0) throw new Error(`git status failed (exit ${code}): ${stderr.trim()}`);
|
|
306
|
+
const dirty = /* @__PURE__ */ new Set();
|
|
307
|
+
const dirtyDirs = [];
|
|
308
|
+
const entries = stdout.split("\0");
|
|
309
|
+
for (let i = 0; i < entries.length; i++) {
|
|
310
|
+
const entry = entries[i];
|
|
311
|
+
if (entry.length < 4) continue;
|
|
312
|
+
const path = entry.slice(3);
|
|
313
|
+
if (path.endsWith("/")) dirtyDirs.push(key(path));
|
|
314
|
+
else {
|
|
315
|
+
dirty.add(key(path));
|
|
316
|
+
if ((await lstat(join(repo.root, path)).catch(() => void 0))?.isDirectory()) dirtyDirs.push(`${key(path)}/`);
|
|
317
|
+
}
|
|
318
|
+
if (/[RC]/.test(entry.slice(0, 2))) {
|
|
319
|
+
const origin = entries[++i];
|
|
320
|
+
if (origin !== void 0) dirty.add(key(origin));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const realRoot = await realpath(repo.root);
|
|
324
|
+
const outside = [];
|
|
325
|
+
for (const file of files) {
|
|
326
|
+
const r = relative(realRoot, await realpathOrParent(file));
|
|
327
|
+
if (r.startsWith("..") || isAbsolute(r)) outside.push(file);
|
|
328
|
+
}
|
|
329
|
+
if (outside.length > 0) return {
|
|
330
|
+
kind: "outside",
|
|
331
|
+
paths: outside
|
|
332
|
+
};
|
|
333
|
+
const rels = await relativeToRoot(files, cwd, repo.root);
|
|
334
|
+
const paths = files.filter((_, index) => {
|
|
335
|
+
const rel = rels[index];
|
|
336
|
+
return rel !== void 0 && (dirty.has(key(rel)) || dirtyDirs.some((d) => key(rel).startsWith(d)));
|
|
337
|
+
});
|
|
338
|
+
return paths.length === 0 ? { kind: "clean" } : {
|
|
339
|
+
kind: "dirty",
|
|
340
|
+
paths
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
/** True when a directory between cwd and `file` is a symbolic link (or junction). */
|
|
344
|
+
async function reachedThroughSymlink(file, cwd) {
|
|
345
|
+
const rel = relative(cwd, file);
|
|
346
|
+
if (rel.startsWith("..") || isAbsolute(rel)) return false;
|
|
347
|
+
let at = cwd;
|
|
348
|
+
for (const part of rel.split(sep).slice(0, -1)) {
|
|
349
|
+
at = join(at, part);
|
|
350
|
+
if ((await lstat(at)).isSymbolicLink()) return true;
|
|
351
|
+
}
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
async function isDirectory(path) {
|
|
355
|
+
try {
|
|
356
|
+
return (await stat(path)).isDirectory();
|
|
357
|
+
} catch {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Resolve command-line patterns to absolute, de-duplicated, sorted file paths. A directory argument
|
|
363
|
+
* expands to its script and stylesheet files. `modern-cmdk-codemod` also skips more build
|
|
364
|
+
* directories and the paths .gitignore covers. `**` descends no symlinked directory in either program;
|
|
365
|
+
* modern-cmdk-codemod also drops every file reached through a symlinked directory below cwd.
|
|
366
|
+
*/
|
|
367
|
+
async function discoverFiles(patterns, { program, cwd, warn }) {
|
|
368
|
+
const dirs = [];
|
|
369
|
+
const globs = [];
|
|
370
|
+
for (const arg of patterns) {
|
|
371
|
+
const pattern = normalizePattern(arg);
|
|
372
|
+
const abs = resolve(cwd, pattern);
|
|
373
|
+
if (await isDirectory(abs)) dirs.push(abs);
|
|
374
|
+
else globs.push(pattern);
|
|
375
|
+
}
|
|
376
|
+
const excluded = program === "modern-cmdk-codemod" ? CODEMOD_EXCLUDES : LEGACY_EXCLUDES;
|
|
377
|
+
const exclude = (d) => excluded.has(d.name);
|
|
378
|
+
const entries = [...globs.length > 0 ? await Array.fromAsync(glob(globs, {
|
|
379
|
+
cwd,
|
|
380
|
+
withFileTypes: true,
|
|
381
|
+
exclude
|
|
382
|
+
})) : [], ...(await Promise.all(dirs.map((d) => Array.fromAsync(glob(DIRECTORY_GLOB, {
|
|
383
|
+
cwd: d,
|
|
384
|
+
withFileTypes: true,
|
|
385
|
+
exclude
|
|
386
|
+
}))))).flat()];
|
|
387
|
+
const files = [...new Set(entries.filter((d) => d.isFile()).map((d) => resolve(d.parentPath, d.name)))].filter((file) => !relative(cwd, file).split(sep).some((segment) => excluded.has(segment))).sort();
|
|
388
|
+
if (program !== "modern-cmdk-codemod") return files;
|
|
389
|
+
const through = await Promise.all(files.map((file) => reachedThroughSymlink(file, cwd)));
|
|
390
|
+
return dropGitIgnored(files.filter((_, index) => !through[index]), cwd, await trustBoundary(cwd), warn);
|
|
391
|
+
}
|
|
392
|
+
// ../modern-cmdk-codemod/src/worker.ts
|
|
393
|
+
var import_jscodeshift = /* @__PURE__ */ __toESM(require_jscodeshift(), 1);
|
|
394
|
+
/** One jscodeshift API per parser; the API object itself is per file so reports carry the file. */
|
|
395
|
+
function createApiFactory() {
|
|
396
|
+
const parsers = /* @__PURE__ */ new Map();
|
|
397
|
+
return (parser, report) => {
|
|
398
|
+
let j = parsers.get(parser);
|
|
399
|
+
if (j === void 0) {
|
|
400
|
+
j = import_jscodeshift.default.withParser(parser);
|
|
401
|
+
parsers.set(parser, j);
|
|
402
|
+
}
|
|
403
|
+
return {
|
|
404
|
+
jscodeshift: j,
|
|
405
|
+
j,
|
|
406
|
+
stats: () => {},
|
|
407
|
+
report
|
|
408
|
+
};
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
/** Transform one file; a thrown error becomes `error`, with the reports made before it. */
|
|
412
|
+
async function transformFile(path, mod, apiForParser, keepSource) {
|
|
413
|
+
const reports = [];
|
|
414
|
+
const report = (message) => void reports.push(message);
|
|
415
|
+
try {
|
|
416
|
+
const route = routeFor(path);
|
|
417
|
+
if (route === "skip" || route === "stylesheet" && mod.transformStylesheet === void 0) return {
|
|
418
|
+
path,
|
|
419
|
+
changed: false,
|
|
420
|
+
skipped: true,
|
|
421
|
+
reports
|
|
422
|
+
};
|
|
423
|
+
const source = await readFile(path, "utf-8");
|
|
424
|
+
const output = route === "script" ? mod.default({
|
|
425
|
+
path,
|
|
426
|
+
source
|
|
427
|
+
}, apiForParser(parserFor(path), report)) : mod.transformStylesheet(source, report);
|
|
428
|
+
if (output === source) return {
|
|
429
|
+
path,
|
|
430
|
+
changed: false,
|
|
431
|
+
reports
|
|
432
|
+
};
|
|
433
|
+
return {
|
|
434
|
+
path,
|
|
435
|
+
changed: true,
|
|
436
|
+
output,
|
|
437
|
+
...keepSource ? { source } : {},
|
|
438
|
+
reports
|
|
439
|
+
};
|
|
440
|
+
} catch (err) {
|
|
441
|
+
return {
|
|
442
|
+
path,
|
|
443
|
+
changed: false,
|
|
444
|
+
error: err instanceof Error ? err.message : String(err),
|
|
445
|
+
reports
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
function isWorkerInit(value) {
|
|
450
|
+
return typeof value === "object" && value !== null && value.kind === "modern-cmdk-codemod-worker";
|
|
451
|
+
}
|
|
452
|
+
if (!isMainThread && parentPort !== null && isWorkerInit(workerData)) {
|
|
453
|
+
const port = parentPort;
|
|
454
|
+
const { transform, keepSource } = workerData;
|
|
455
|
+
const apiForParser = createApiFactory();
|
|
456
|
+
const loaded = import(
|
|
457
|
+
/* @vite-ignore */
|
|
458
|
+
new URL(`./transforms/${transform}.mjs`, import.meta.url).href
|
|
459
|
+
);
|
|
460
|
+
loaded.catch(() => {});
|
|
461
|
+
port.on("message", async (path) => {
|
|
462
|
+
let outcome;
|
|
463
|
+
try {
|
|
464
|
+
outcome = await transformFile(path, await loaded, apiForParser, keepSource);
|
|
465
|
+
} catch (err) {
|
|
466
|
+
outcome = {
|
|
467
|
+
path,
|
|
468
|
+
changed: false,
|
|
469
|
+
error: `cannot load transform "${transform}": ${err instanceof Error ? err.message : String(err)}`,
|
|
470
|
+
reports: []
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
port.postMessage(outcome);
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
export { findDirtyFiles as i, transformFile as n, discoverFiles as r, createApiFactory as t };
|