@mittwald/flow-codemods 1.2.17 → 1.2.18
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 +3 -2
- package/dist/cli/codemod.js +8 -3
- package/dist/cli/upgrade.js +6 -4
- package/dist/run/jscodeshift.d.ts +8 -1
- package/dist/run/jscodeshift.js +65 -8
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -131,5 +131,6 @@ npx @mittwald/flow-codemods@latest to-remote-package src
|
|
|
131
131
|
|
|
132
132
|
`0` on success, `1` on a refusal — dirty tree, unresolvable revision, failed
|
|
133
133
|
install, an unknown id, or an id whose migration has no codemod — and `1` when a
|
|
134
|
-
codemod reports an error,
|
|
135
|
-
|
|
134
|
+
codemod reports an error, found nothing under `path` to process, or declined
|
|
135
|
+
every file it looked at. "Every" means every: a decline among files the
|
|
136
|
+
transform did read is a per-file fact, not a failed run.
|
package/dist/cli/codemod.js
CHANGED
|
@@ -78,14 +78,19 @@ export const runSingleCodemod = async (parsed, { cwd, log, run = runCodemod }) =
|
|
|
78
78
|
}
|
|
79
79
|
// The same trap as `processedNothing`, one field over: a transform that
|
|
80
80
|
// declines a file by returning nothing counts as `skipped`, not `unmodified`.
|
|
81
|
-
// If every file was
|
|
81
|
+
// If every file was declined and none changed, "0 file(s) changed" would read
|
|
82
82
|
// as a clean no-op run when in fact the transform bailed on everything.
|
|
83
|
-
|
|
83
|
+
//
|
|
84
|
+
// "All" means all: `unmodified > 0` says the transform read those files and
|
|
85
|
+
// handed them back, so a decline among them is a per-file fact, not a failed
|
|
86
|
+
// run.
|
|
87
|
+
if (result.changed === 0 && result.unmodified === 0 && result.skipped > 0) {
|
|
84
88
|
log(`${id}: the transform declined all ${result.skipped} file(s) it looked at, and changed none.`);
|
|
85
89
|
return 1;
|
|
86
90
|
}
|
|
87
91
|
const skipped = result.skipped > 0 ? `, ${result.skipped} skipped` : "";
|
|
88
|
-
const
|
|
92
|
+
const empty = result.empty > 0 ? `, ${result.empty} empty` : "";
|
|
93
|
+
const summary = `${id}: ${result.changed} file(s) changed, ${result.unmodified} unchanged${skipped}${empty}.`;
|
|
89
94
|
// Only a catalogued id has a migration guide entry to point at — a transform
|
|
90
95
|
// like `to-remote-package` with no catalogue entry has no anchor in
|
|
91
96
|
// `MIGRATION.md` to link, so pointing there would be a dead link.
|
package/dist/cli/upgrade.js
CHANGED
|
@@ -168,9 +168,9 @@ export const runUpgrade = async (parsed, deps) => {
|
|
|
168
168
|
if (result.changed > 0) {
|
|
169
169
|
changedCount += 1;
|
|
170
170
|
}
|
|
171
|
-
// Same three-way distinction as the single-codemod command
|
|
172
|
-
//
|
|
173
|
-
//
|
|
171
|
+
// Same three-way distinction as the single-codemod command, and the same
|
|
172
|
+
// reading of "declined all": `unmodified > 0` proves the transform read
|
|
173
|
+
// files and handed them back, so it did not bail on the tree.
|
|
174
174
|
if (result.errors > 0) {
|
|
175
175
|
hadFailure = true;
|
|
176
176
|
log(` ${entry.id}: ${result.errors} file(s) failed to transform`);
|
|
@@ -179,7 +179,9 @@ export const runUpgrade = async (parsed, deps) => {
|
|
|
179
179
|
hadFailure = true;
|
|
180
180
|
log(` ${entry.id}: no files under ${path} were processed`);
|
|
181
181
|
}
|
|
182
|
-
else if (result.changed === 0 &&
|
|
182
|
+
else if (result.changed === 0 &&
|
|
183
|
+
result.unmodified === 0 &&
|
|
184
|
+
result.skipped > 0) {
|
|
183
185
|
hadFailure = true;
|
|
184
186
|
log(` ${entry.id}: declined all ${result.skipped} file(s) it looked at`);
|
|
185
187
|
}
|
|
@@ -16,8 +16,15 @@ export interface CodemodOptions {
|
|
|
16
16
|
export interface CodemodResult {
|
|
17
17
|
changed: number;
|
|
18
18
|
unmodified: number;
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Files the transform declined by returning nothing — empty ones excluded.
|
|
21
|
+
*
|
|
22
|
+
* Both come back from jscodeshift as `skip`. A caller may report this field
|
|
23
|
+
* as "the transform bailed on these"; it must not report `empty` that way.
|
|
24
|
+
*/
|
|
20
25
|
skipped: number;
|
|
26
|
+
/** Files with no content, which no transform can do anything with. */
|
|
27
|
+
empty: number;
|
|
21
28
|
errors: number;
|
|
22
29
|
/** True when jscodeshift accounted for no file at all — see below. */
|
|
23
30
|
processedNothing: boolean;
|
package/dist/run/jscodeshift.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { extname, join } from "node:path";
|
|
2
3
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
3
4
|
import { unknownCodemodMessage } from "../catalog/entries.js";
|
|
4
5
|
// jscodeshift ships no types for its Runner — `allowJs` in this repo's shared
|
|
@@ -54,6 +55,61 @@ const transformPath = (id) => findTransform(id) ?? `${packageRoot}src/tools/${id
|
|
|
54
55
|
* module's own `existsSync` check below.
|
|
55
56
|
*/
|
|
56
57
|
export const transformExists = (id) => existsSync(transformPath(id));
|
|
58
|
+
/**
|
|
59
|
+
* The extensions the walk takes, and the directories it stays out of.
|
|
60
|
+
*
|
|
61
|
+
* Both are handed to jscodeshift below — without them its walk takes every file
|
|
62
|
+
* under the path, and since `upgrade` runs codemods after the install,
|
|
63
|
+
* `node_modules` is freshly populated underneath; non-JS files then fail to
|
|
64
|
+
* parse, which shows up as `errors > 0` and hides the real change count.
|
|
65
|
+
*
|
|
66
|
+
* `emptySourceFiles` reads the same two constants so that its walk and
|
|
67
|
+
* jscodeshift's agree by construction.
|
|
68
|
+
*/
|
|
69
|
+
const extensions = ["js", "jsx", "ts", "tsx", "cjs", "mjs", "cts", "mts"];
|
|
70
|
+
const ignoredDirectories = ["node_modules", "dist", ".git"];
|
|
71
|
+
const sourceExtensions = new Set(extensions.map((extension) => `.${extension}`));
|
|
72
|
+
const ignoredDirectorySet = new Set(ignoredDirectories);
|
|
73
|
+
/**
|
|
74
|
+
* How many source files under `path` hold nothing at all.
|
|
75
|
+
*
|
|
76
|
+
* Jscodeshift's worker tests a transform's result for truthiness
|
|
77
|
+
* (`updateStatus(out ? 'nochange' : 'skip', file)`), and on an empty file both
|
|
78
|
+
* `root.toSource()` and `fileInfo.source` are `""` — so an empty file lands in
|
|
79
|
+
* `skip`, indistinguishable there from a transform that declined it.
|
|
80
|
+
*
|
|
81
|
+
* Counted with a walk of our own rather than filtered out of jscodeshift's
|
|
82
|
+
* input: this number only ever explains a count. If the two walks disagree, the
|
|
83
|
+
* worst outcome is a slightly wrong explanation, never a file that should have
|
|
84
|
+
* been transformed and was not.
|
|
85
|
+
*/
|
|
86
|
+
const emptySourceFiles = (path) => {
|
|
87
|
+
let found = 0;
|
|
88
|
+
const visit = (candidate) => {
|
|
89
|
+
let stats;
|
|
90
|
+
try {
|
|
91
|
+
stats = statSync(candidate);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// A broken symlink or a file removed mid-walk. jscodeshift skips these
|
|
95
|
+
// too ("Skipping path … which does not exist").
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (stats.isDirectory()) {
|
|
99
|
+
for (const child of readdirSync(candidate)) {
|
|
100
|
+
if (!ignoredDirectorySet.has(child)) {
|
|
101
|
+
visit(join(candidate, child));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (stats.size === 0 && sourceExtensions.has(extname(candidate))) {
|
|
107
|
+
found += 1;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
visit(path);
|
|
111
|
+
return found;
|
|
112
|
+
};
|
|
57
113
|
/**
|
|
58
114
|
* Runs one codemod over a path.
|
|
59
115
|
*
|
|
@@ -105,21 +161,22 @@ export const runCodemod = async ({ id, path, dry = false, print = false, }) => {
|
|
|
105
161
|
silent: true,
|
|
106
162
|
dry,
|
|
107
163
|
print,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// underneath — and non-JS files fail to parse, which shows up as
|
|
111
|
-
// `errors > 0` and hides the real change count.
|
|
112
|
-
extensions: "js,jsx,ts,tsx,cjs,mjs,cts,mts",
|
|
113
|
-
ignorePattern: ["**/node_modules/**", "**/dist/**", "**/.git/**"],
|
|
164
|
+
extensions: extensions.join(","),
|
|
165
|
+
ignorePattern: ignoredDirectories.map((name) => `**/${name}/**`),
|
|
114
166
|
}));
|
|
115
167
|
}
|
|
116
168
|
catch (error) {
|
|
117
169
|
throw new Error(`Running ${id} failed: ${error instanceof Error ? error.message : error}`, { cause: error });
|
|
118
170
|
}
|
|
171
|
+
// Clamped rather than subtracted outright: the two walks are independent, so
|
|
172
|
+
// a file that appeared or emptied between them must not push `skipped` below
|
|
173
|
+
// zero.
|
|
174
|
+
const empty = Math.min(emptySourceFiles(path), stats.skip);
|
|
119
175
|
return {
|
|
120
176
|
changed: stats.ok,
|
|
121
177
|
unmodified: stats.nochange,
|
|
122
|
-
skipped: stats.skip,
|
|
178
|
+
skipped: stats.skip - empty,
|
|
179
|
+
empty,
|
|
123
180
|
errors: stats.error,
|
|
124
181
|
processedNothing: stats.ok + stats.nochange + stats.skip + stats.error === 0,
|
|
125
182
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mittwald/flow-codemods",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Codemods and an upgrade CLI for consumers of Flow, mittwald's design system",
|
|
6
6
|
"homepage": "https://flow.mittwald.de",
|
|
@@ -38,5 +38,5 @@
|
|
|
38
38
|
"vitest": "^4.1.11",
|
|
39
39
|
"yaml": "^2.8.1"
|
|
40
40
|
},
|
|
41
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "f5f05b9f490f10b0f4b1298efc7c8bf8fa8be555"
|
|
42
42
|
}
|