@mittwald/flow-codemods 1.1.0-next.10
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 +106 -0
- package/dist/catalog/entries.d.ts +17 -0
- package/dist/catalog/entries.d.ts.map +1 -0
- package/dist/catalog/entries.js +9 -0
- package/dist/catalog/select.d.ts +28 -0
- package/dist/catalog/select.d.ts.map +1 -0
- package/dist/catalog/select.js +27 -0
- package/dist/catalog/types.d.ts +48 -0
- package/dist/catalog/types.d.ts.map +1 -0
- package/dist/catalog/types.js +0 -0
- package/dist/checks/context.d.ts +16 -0
- package/dist/checks/context.d.ts.map +1 -0
- package/dist/checks/context.js +72 -0
- package/dist/checks/load.d.ts +13 -0
- package/dist/checks/load.d.ts.map +1 -0
- package/dist/checks/load.js +53 -0
- package/dist/checks/types.d.ts +49 -0
- package/dist/checks/types.d.ts.map +1 -0
- package/dist/checks/types.js +0 -0
- package/dist/cli/args.d.ts +22 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +56 -0
- package/dist/cli/choose.d.ts +31 -0
- package/dist/cli/choose.d.ts.map +1 -0
- package/dist/cli/choose.js +31 -0
- package/dist/cli/codemod.d.ts +24 -0
- package/dist/cli/codemod.d.ts.map +1 -0
- package/dist/cli/codemod.js +76 -0
- package/dist/cli/detect.d.ts +35 -0
- package/dist/cli/detect.d.ts.map +1 -0
- package/dist/cli/detect.js +62 -0
- package/dist/cli/list.d.ts +71 -0
- package/dist/cli/list.d.ts.map +1 -0
- package/dist/cli/list.js +293 -0
- package/dist/cli/upgrade.d.ts +34 -0
- package/dist/cli/upgrade.d.ts.map +1 -0
- package/dist/cli/upgrade.js +217 -0
- package/dist/cli/verify.d.ts +77 -0
- package/dist/cli/verify.d.ts.map +1 -0
- package/dist/cli/verify.js +148 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +117 -0
- package/dist/detect/align-to-combine.d.ts +8 -0
- package/dist/detect/align-to-combine.d.ts.map +1 -0
- package/dist/detect/align-to-combine.js +9 -0
- package/dist/detect/overlay-controller-add-on-close-return-type.d.ts +9 -0
- package/dist/detect/overlay-controller-add-on-close-return-type.d.ts.map +1 -0
- package/dist/detect/overlay-controller-add-on-close-return-type.js +10 -0
- package/dist/detect/tooltip-trigger-delay-type.d.ts +11 -0
- package/dist/detect/tooltip-trigger-delay-type.d.ts.map +1 -0
- package/dist/detect/tooltip-trigger-delay-type.js +12 -0
- package/dist/flowPackages.generated.d.ts +3 -0
- package/dist/flowPackages.generated.d.ts.map +1 -0
- package/dist/flowPackages.generated.js +18 -0
- package/dist/git.d.ts +13 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +41 -0
- package/dist/install.d.ts +37 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +71 -0
- package/dist/manifest.d.ts +45 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +88 -0
- package/dist/migrations.generated.d.ts +4 -0
- package/dist/migrations.generated.d.ts.map +1 -0
- package/dist/migrations.generated.js +213 -0
- package/dist/resolve/range.d.ts +50 -0
- package/dist/resolve/range.d.ts.map +1 -0
- package/dist/resolve/range.js +173 -0
- package/dist/resolve/registry.d.ts +33 -0
- package/dist/resolve/registry.d.ts.map +1 -0
- package/dist/resolve/registry.js +69 -0
- package/dist/resolve/target.d.ts +63 -0
- package/dist/resolve/target.d.ts.map +1 -0
- package/dist/resolve/target.js +115 -0
- package/dist/run/jscodeshift.d.ts +42 -0
- package/dist/run/jscodeshift.d.ts.map +1 -0
- package/dist/run/jscodeshift.js +88 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/verify/align-to-combine.d.ts +11 -0
- package/dist/verify/align-to-combine.d.ts.map +1 -0
- package/dist/verify/align-to-combine.js +22 -0
- package/dist/verify/overlay-controller-add-on-close-return-type.d.ts +14 -0
- package/dist/verify/overlay-controller-add-on-close-return-type.d.ts.map +1 -0
- package/dist/verify/overlay-controller-add-on-close-return-type.js +21 -0
- package/dist/verify/tooltip-trigger-delay-type.d.ts +13 -0
- package/dist/verify/tooltip-trigger-delay-type.d.ts.map +1 -0
- package/dist/verify/tooltip-trigger-delay-type.js +18 -0
- package/package.json +41 -0
- package/src/migrations/accent-box-color-to-background-color/transform.ts +218 -0
- package/src/migrations/action-prop-to-on-action/transform.ts +110 -0
- package/src/migrations/align-to-combine/transform.ts +212 -0
- package/src/migrations/button-color-accent-to-success/transform.ts +164 -0
- package/src/migrations/button-props-interfaces/transform.ts +229 -0
- package/src/migrations/color-primary-to-default/transform.ts +173 -0
- package/src/migrations/imports-to-package-root/transform.ts +107 -0
- package/src/migrations/muted-action-error-to-abort-action-error/transform.ts +260 -0
- package/src/migrations/password-tools-rule/transform.ts +219 -0
- package/src/migrations/password-tools-subpath-renamed/transform.ts +61 -0
- package/src/tools/to-remote-package.ts +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# @mittwald/flow-codemods
|
|
2
|
+
|
|
3
|
+
Codemods and an upgrade CLI for consumers of [Flow](https://flow.mittwald.de),
|
|
4
|
+
mittwald's design system. Run it with `npx` — there is no reason to install it
|
|
5
|
+
as a dependency.
|
|
6
|
+
|
|
7
|
+
```shell
|
|
8
|
+
npx @mittwald/flow-codemods@latest upgrade
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Commands
|
|
12
|
+
|
|
13
|
+
### `upgrade [revision]`
|
|
14
|
+
|
|
15
|
+
Bumps every `@mittwald/flow-*` dependency in `package.json` to a resolved
|
|
16
|
+
target, installs, then runs the codemod of every migration up to that target.
|
|
17
|
+
|
|
18
|
+
`revision` is one of:
|
|
19
|
+
|
|
20
|
+
- `patch` — stays inside the current minor
|
|
21
|
+
- `minor` (default) — stays inside the current major
|
|
22
|
+
- `major` — no ceiling
|
|
23
|
+
- a dist-tag, e.g. `latest` or `next`
|
|
24
|
+
- an exact version, e.g. `1.4.0`
|
|
25
|
+
|
|
26
|
+
The target is resolved from the versions every declared Flow dependency has
|
|
27
|
+
actually published — not just one of them — so the command never writes a
|
|
28
|
+
version some dependency lacks.
|
|
29
|
+
|
|
30
|
+
`dependencies`, `devDependencies` and `optionalDependencies` are rewritten.
|
|
31
|
+
**`peerDependencies` are reported and left alone**: a peer range states which
|
|
32
|
+
versions your package supports, not one it installs, and narrowing `^1.0.0` to
|
|
33
|
+
`^1.0.14` would change what _your_ consumers are allowed to install. That is
|
|
34
|
+
your call, not the command's.
|
|
35
|
+
|
|
36
|
+
After installing, `upgrade` runs the codemod of every migration whose `since` is
|
|
37
|
+
at or below the target and prints the ones with no codemod, for you to apply by
|
|
38
|
+
hand.
|
|
39
|
+
|
|
40
|
+
**There is deliberately no lower bound.** Nothing records which migrations a
|
|
41
|
+
project already performed, so `upgrade` offers all of them rather than guessing
|
|
42
|
+
from the version you happen to be on — a project that never ran the command can
|
|
43
|
+
catch up. Re-running a codemod is safe: every transform is tested for it.
|
|
44
|
+
|
|
45
|
+
Options:
|
|
46
|
+
|
|
47
|
+
- `-y`, `--yes` — accept every default (which codemods to run). Implied when
|
|
48
|
+
stdin is not a TTY, so CI and agent runs never block on a prompt.
|
|
49
|
+
- `--dry` — resolve the target and print what would change, but write nothing
|
|
50
|
+
and skip the install. Codemods still run in this mode, against whatever is
|
|
51
|
+
currently installed rather than the target — their output is indicative, not
|
|
52
|
+
exact.
|
|
53
|
+
- `--allow-dirty` — run even though the working tree has uncommitted changes.
|
|
54
|
+
Codemods rewrite files in place; without this flag, `upgrade` refuses on a
|
|
55
|
+
dirty tree so a bad run is still `git checkout`-able.
|
|
56
|
+
- `--path <dir>` — sources to run the codemods against. Defaults to `./src` when
|
|
57
|
+
that directory exists, otherwise the project root. Give it explicitly if your
|
|
58
|
+
sources live somewhere else — an unrelated `src/` next to them would otherwise
|
|
59
|
+
win.
|
|
60
|
+
- `--print` — print each codemod's transformed output.
|
|
61
|
+
|
|
62
|
+
### `list [revision]`
|
|
63
|
+
|
|
64
|
+
Shows migrations — codemod and by-hand alike — without touching the project. The
|
|
65
|
+
read-only planning entry point: run it before `upgrade` to see what a bump would
|
|
66
|
+
involve.
|
|
67
|
+
|
|
68
|
+
- `list` (no argument) — the whole catalogue. Offline: reads no manifest, hits
|
|
69
|
+
no network.
|
|
70
|
+
- `list [revision]` — the same manifest read, registry fetch, and revision
|
|
71
|
+
resolution `upgrade [revision]` does, showing exactly the range it would act
|
|
72
|
+
on, without writing anything. `revision` takes the same values as `upgrade`'s
|
|
73
|
+
— `patch` | `minor` | `major` | a dist-tag | an exact version — and there is
|
|
74
|
+
no default: only a given revision switches `list` into this form.
|
|
75
|
+
|
|
76
|
+
Options:
|
|
77
|
+
|
|
78
|
+
- `--json` — machine-readable output: an object with `range`
|
|
79
|
+
(`current`/`target`, or `null` for the offline whole-catalogue form) and
|
|
80
|
+
`migrations`, each entry carrying `catchUp`
|
|
81
|
+
|
|
82
|
+
### `<id> [path]`
|
|
83
|
+
|
|
84
|
+
Runs a single codemod by its catalogue id (see `list`) against `path` — which
|
|
85
|
+
defaults to `./src` when that directory exists, otherwise the project root.
|
|
86
|
+
|
|
87
|
+
Options: `--dry`, `--print` — same meaning as under `upgrade`.
|
|
88
|
+
|
|
89
|
+
### `to-remote-package`
|
|
90
|
+
|
|
91
|
+
A port, not a migration: it rewrites every `@mittwald/flow-react-components`
|
|
92
|
+
import to `@mittwald/flow-remote-react-components`. It has no catalogue entry —
|
|
93
|
+
no version range calls for it, and it never shows up in `list` or `upgrade`. Run
|
|
94
|
+
it deliberately when moving an app into an mStudio extension; on a normal app it
|
|
95
|
+
rewrites every Flow import.
|
|
96
|
+
|
|
97
|
+
```shell
|
|
98
|
+
npx @mittwald/flow-codemods@latest to-remote-package src
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Exit codes
|
|
102
|
+
|
|
103
|
+
`0` on success, `1` on a refusal — dirty tree, unresolvable revision, failed
|
|
104
|
+
install, an unknown id, or an id whose migration has no codemod — and `1` when a
|
|
105
|
+
codemod reports an error, is declined for every file it looked at, or found
|
|
106
|
+
nothing under `path` to process.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { MigrationEntry } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* A catalogue entry as the CLI sees it — no body.
|
|
4
|
+
*
|
|
5
|
+
* This is the only module that imports the generated file, so nothing else
|
|
6
|
+
* depends on how it is shaped.
|
|
7
|
+
*/
|
|
8
|
+
export type CatalogEntry = Omit<MigrationEntry, "body">;
|
|
9
|
+
export declare const allEntries: CatalogEntry[];
|
|
10
|
+
/**
|
|
11
|
+
* Shared refusal for an id that names no codemod — `runSingleCodemod` (against
|
|
12
|
+
* the catalogue) and `runCodemod` (against `src/migrations`/`src/tools` on
|
|
13
|
+
* disk) each hit this independently, so the message is defined once and reused
|
|
14
|
+
* rather than kept in sync by hand in both places.
|
|
15
|
+
*/
|
|
16
|
+
export declare const unknownCodemodMessage: (id: string) => string;
|
|
17
|
+
//# sourceMappingURL=entries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entries.d.ts","sourceRoot":"","sources":["../../src/catalog/entries.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;AAExD,eAAO,MAAM,UAAU,EAAE,YAAY,EAAe,CAAC;AAErD;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,OAAQ,MAAM,KAAG,MAC+C,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { migrations } from "../migrations.generated.js";
|
|
2
|
+
export const allEntries = migrations;
|
|
3
|
+
/**
|
|
4
|
+
* Shared refusal for an id that names no codemod — `runSingleCodemod` (against
|
|
5
|
+
* the catalogue) and `runCodemod` (against `src/migrations`/`src/tools` on
|
|
6
|
+
* disk) each hit this independently, so the message is defined once and reused
|
|
7
|
+
* rather than kept in sync by hand in both places.
|
|
8
|
+
*/
|
|
9
|
+
export const unknownCodemodMessage = (id) => `"${id}" is not a codemod in this package. Run \`flow-codemods list\` to see the available ids.`;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { CatalogEntry } from "./entries.js";
|
|
2
|
+
/**
|
|
3
|
+
* Oldest first — the order the changes shipped, which is the order to apply
|
|
4
|
+
* them.
|
|
5
|
+
*/
|
|
6
|
+
export declare const sortBySince: (entries: CatalogEntry[]) => CatalogEntry[];
|
|
7
|
+
/**
|
|
8
|
+
* The entries that a move to `target` calls for.
|
|
9
|
+
*
|
|
10
|
+
* The gate is the exact version, not a major/minor/patch granularity: a
|
|
11
|
+
* revision keyword bounds `target`, and the set falls out of that. So `upgrade
|
|
12
|
+
* patch` — target = highest patch of the current minor — selects exactly the
|
|
13
|
+
* entries of the active minor, and `upgrade minor` selects every entry of the
|
|
14
|
+
* active major.
|
|
15
|
+
*
|
|
16
|
+
* One rule, for every entry: `since <= target`. There used to be a second,
|
|
17
|
+
* lower bound (`current < since`) for entries with no codemod, on the
|
|
18
|
+
* assumption that a consumer already applied everything behind their version.
|
|
19
|
+
* That was a guess, not a fact — nothing records which migrations a project has
|
|
20
|
+
* performed — and it was wrong for the case this tool exists for: someone who
|
|
21
|
+
* never ran it never did the manual steps either. Dropping it for codemods
|
|
22
|
+
* (idempotent — see `transformCoverage.test.ts`) but keeping it for manual
|
|
23
|
+
* entries only hid exactly the migrations that consumer needed to see. The
|
|
24
|
+
* `catch-up` rendering in `src/cli/list.ts` now carries the distinction instead
|
|
25
|
+
* of the gate hiding it.
|
|
26
|
+
*/
|
|
27
|
+
export declare const selectEntries: (entries: CatalogEntry[], target: string) => CatalogEntry[];
|
|
28
|
+
//# sourceMappingURL=select.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../src/catalog/select.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD;;;GAGG;AACH,eAAO,MAAM,WAAW,YAAa,YAAY,EAAE,KAAG,YAAY,EAG/D,CAAC;AAEJ;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,aAAa,YACf,YAAY,EAAE,UACf,MAAM,KACb,YAAY,EACmD,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { compare, lte } from "semver";
|
|
2
|
+
/**
|
|
3
|
+
* Oldest first — the order the changes shipped, which is the order to apply
|
|
4
|
+
* them.
|
|
5
|
+
*/
|
|
6
|
+
export const sortBySince = (entries) => entries.toSorted((a, b) => compare(a.since, b.since) || a.id.localeCompare(b.id));
|
|
7
|
+
/**
|
|
8
|
+
* The entries that a move to `target` calls for.
|
|
9
|
+
*
|
|
10
|
+
* The gate is the exact version, not a major/minor/patch granularity: a
|
|
11
|
+
* revision keyword bounds `target`, and the set falls out of that. So `upgrade
|
|
12
|
+
* patch` — target = highest patch of the current minor — selects exactly the
|
|
13
|
+
* entries of the active minor, and `upgrade minor` selects every entry of the
|
|
14
|
+
* active major.
|
|
15
|
+
*
|
|
16
|
+
* One rule, for every entry: `since <= target`. There used to be a second,
|
|
17
|
+
* lower bound (`current < since`) for entries with no codemod, on the
|
|
18
|
+
* assumption that a consumer already applied everything behind their version.
|
|
19
|
+
* That was a guess, not a fact — nothing records which migrations a project has
|
|
20
|
+
* performed — and it was wrong for the case this tool exists for: someone who
|
|
21
|
+
* never ran it never did the manual steps either. Dropping it for codemods
|
|
22
|
+
* (idempotent — see `transformCoverage.test.ts`) but keeping it for manual
|
|
23
|
+
* entries only hid exactly the migrations that consumer needed to see. The
|
|
24
|
+
* `catch-up` rendering in `src/cli/list.ts` now carries the distinction instead
|
|
25
|
+
* of the gate hiding it.
|
|
26
|
+
*/
|
|
27
|
+
export const selectEntries = (entries, target) => sortBySince(entries.filter((entry) => lte(entry.since, target)));
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What kind of change the entry describes.
|
|
3
|
+
*
|
|
4
|
+
* - `migration` — the old path is gone, at runtime or in the types.
|
|
5
|
+
* - `deprecation` — the old path still works and warns.
|
|
6
|
+
*
|
|
7
|
+
* Descriptive only: it is rendered as a label and does **not** affect
|
|
8
|
+
* selection. It used to — a `deprecation` was offered as soon as its
|
|
9
|
+
* replacement existed and a `migration` only once the consumer crossed `since`
|
|
10
|
+
* — but the gate is one rule now (`since <= target`, see `selectEntries`), so
|
|
11
|
+
* both behave alike. The distinction still tells a reader whether their code
|
|
12
|
+
* compiles today, which is why the field stays.
|
|
13
|
+
*/
|
|
14
|
+
export type MigrationKind = "migration" | "deprecation";
|
|
15
|
+
/**
|
|
16
|
+
* What has to happen.
|
|
17
|
+
*
|
|
18
|
+
* - `codemod` — `src/migrations/<id>/transform.ts` does it.
|
|
19
|
+
* - `manual` — a person or an agent has to change code.
|
|
20
|
+
* - `none` — behaviour changed, no code change required. An agent needs this
|
|
21
|
+
* spelled out, or it keeps looking for something to edit.
|
|
22
|
+
*/
|
|
23
|
+
export type MigrationAction = "codemod" | "manual" | "none";
|
|
24
|
+
export interface MigrationEntry {
|
|
25
|
+
/**
|
|
26
|
+
* Dashed and lowercase. Doubles as the `src/migrations/<id>` directory name
|
|
27
|
+
* and, when `action` is `codemod`, that directory's `transform.ts` file, and
|
|
28
|
+
* as the `MIGRATION.md` heading anchor.
|
|
29
|
+
*/
|
|
30
|
+
id: string;
|
|
31
|
+
/** The exact version the change shipped in. */
|
|
32
|
+
since: string;
|
|
33
|
+
/** Heading text for the guide. */
|
|
34
|
+
title: string;
|
|
35
|
+
kind: MigrationKind;
|
|
36
|
+
action: MigrationAction;
|
|
37
|
+
/**
|
|
38
|
+
* Whether the entry also applies to `@mittwald/flow-remote-react-components`.
|
|
39
|
+
* Held to that package's real export surface by `remoteScope.test.ts` — do
|
|
40
|
+
* not guess it.
|
|
41
|
+
*/
|
|
42
|
+
remotePackage: boolean;
|
|
43
|
+
/** What to change, imperative and specific enough to execute. */
|
|
44
|
+
apply: string;
|
|
45
|
+
/** The guide entry as Markdown. Headings inside it start at level 4. */
|
|
46
|
+
body: string;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/catalog/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,aAAa,CAAC;AAExD;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE5D,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,EAAE,EAAE,MAAM,CAAC;IACX,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,eAAe,CAAC;IACxB;;;;OAIG;IACH,aAAa,EAAE,OAAO,CAAC;IACvB,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;CACd"}
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CheckContext } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Ripgrep's `-t ts` type: `.ts`, `.tsx`, `.cts`, `.mts`. Named once here so the
|
|
4
|
+
* 21 detect/verify modules that translate a `rg -t ts '...'` field do not each
|
|
5
|
+
* repeat the list.
|
|
6
|
+
*/
|
|
7
|
+
export declare const tsExtensions: string[];
|
|
8
|
+
/**
|
|
9
|
+
* The shared `CheckContext` implementation — the only place under `src/checks`,
|
|
10
|
+
* `src/detect` or `src/verify` that touches the filesystem. Pure Node
|
|
11
|
+
* (`node:fs/promises`, regexes): no `rg`, `grep`, `tsc`, or any other
|
|
12
|
+
* subprocess, so this runs the same on Windows, in a minimal CI container, or
|
|
13
|
+
* without ripgrep installed.
|
|
14
|
+
*/
|
|
15
|
+
export declare const createCheckContext: (path: string) => CheckContext;
|
|
16
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/checks/context.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAW,MAAM,YAAY,CAAC;AAExD;;;;GAIG;AACH,eAAO,MAAM,YAAY,UAAkC,CAAC;AA0B5D;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,SAAU,MAAM,KAAG,YA0CjD,CAAC"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
2
|
+
import { extname, join } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Ripgrep's `-t ts` type: `.ts`, `.tsx`, `.cts`, `.mts`. Named once here so the
|
|
5
|
+
* 21 detect/verify modules that translate a `rg -t ts '...'` field do not each
|
|
6
|
+
* repeat the list.
|
|
7
|
+
*/
|
|
8
|
+
export const tsExtensions = [".ts", ".tsx", ".cts", ".mts"];
|
|
9
|
+
/**
|
|
10
|
+
* Directories a check must never walk into.
|
|
11
|
+
*
|
|
12
|
+
* Not optional: `upgrade` runs `verify` after an install, so a walk without
|
|
13
|
+
* this excludes a freshly populated `node_modules` — the exact omission that
|
|
14
|
+
* was a Critical on this branch for the codemod runner (see `ignorePattern` in
|
|
15
|
+
* `src/run/jscodeshift.ts`). `dist` and `.git` are excluded for the same
|
|
16
|
+
* reason: build output and VCS internals are not the consumer's code.
|
|
17
|
+
*/
|
|
18
|
+
const excludedDirs = new Set(["node_modules", "dist", ".git"]);
|
|
19
|
+
const walk = async (dir) => {
|
|
20
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
21
|
+
const found = await Promise.all(entries.map(async (entry) => {
|
|
22
|
+
if (entry.isDirectory()) {
|
|
23
|
+
return excludedDirs.has(entry.name) ? [] : walk(join(dir, entry.name));
|
|
24
|
+
}
|
|
25
|
+
return entry.isFile() ? [join(dir, entry.name)] : [];
|
|
26
|
+
}));
|
|
27
|
+
return found.flat();
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* The shared `CheckContext` implementation — the only place under `src/checks`,
|
|
31
|
+
* `src/detect` or `src/verify` that touches the filesystem. Pure Node
|
|
32
|
+
* (`node:fs/promises`, regexes): no `rg`, `grep`, `tsc`, or any other
|
|
33
|
+
* subprocess, so this runs the same on Windows, in a minimal CI container, or
|
|
34
|
+
* without ripgrep installed.
|
|
35
|
+
*/
|
|
36
|
+
export const createCheckContext = (path) => {
|
|
37
|
+
const files = async (extensions) => {
|
|
38
|
+
const all = await walk(path);
|
|
39
|
+
return extensions === undefined
|
|
40
|
+
? all
|
|
41
|
+
: all.filter((file) => extensions.includes(extname(file)));
|
|
42
|
+
};
|
|
43
|
+
const read = (file) => readFile(file, "utf8");
|
|
44
|
+
const search = async (pattern, extensions) => {
|
|
45
|
+
const candidates = await files(extensions);
|
|
46
|
+
// A fresh, non-global copy of `pattern`: `.test()` on a `g`-flagged regex
|
|
47
|
+
// is stateful (it advances `lastIndex`), which would silently skip matches
|
|
48
|
+
// on later lines of the same file, or later files, depending on what the
|
|
49
|
+
// caller passed in.
|
|
50
|
+
const matcher = new RegExp(pattern.source, pattern.flags.replace("g", ""));
|
|
51
|
+
const findings = [];
|
|
52
|
+
for (const file of candidates) {
|
|
53
|
+
let content;
|
|
54
|
+
try {
|
|
55
|
+
content = await read(file);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Unreadable — permissions, a broken symlink, binary content a strict
|
|
59
|
+
// decode chokes on. A check skips that one file rather than failing
|
|
60
|
+
// the whole run over it.
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
content.split("\n").forEach((line, index) => {
|
|
64
|
+
if (matcher.test(line)) {
|
|
65
|
+
findings.push({ file, line: index + 1, text: line.trim() });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return findings;
|
|
70
|
+
};
|
|
71
|
+
return { path, files, read, search };
|
|
72
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Detector, Verifier } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Loads the detector for a catalogue id, or `undefined` when none exists yet.
|
|
4
|
+
*
|
|
5
|
+
* Only a fraction of the catalogue has a module so far — the rest follow in
|
|
6
|
+
* later work. `detect`/`verify` (the CLI commands) must treat a missing module
|
|
7
|
+
* as "nothing to run for this id" rather than a failure, so callers can loop
|
|
8
|
+
* over the whole catalogue without special-casing which ids are covered.
|
|
9
|
+
*/
|
|
10
|
+
export declare const loadDetector: (id: string) => Promise<Detector | undefined>;
|
|
11
|
+
/** The `verify` counterpart of {@link loadDetector}. */
|
|
12
|
+
export declare const loadVerifier: (id: string) => Promise<Verifier | undefined>;
|
|
13
|
+
//# sourceMappingURL=load.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../../src/checks/load.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAuBrD;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,OACnB,MAAM,KACT,OAAO,CAAC,QAAQ,GAAG,SAAS,CAiB9B,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,YAAY,OACnB,MAAM,KACT,OAAO,CAAC,QAAQ,GAAG,SAAS,CAW9B,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
/**
|
|
4
|
+
* `<packageRoot>/{src,dist}/detect` and `.../verify`, resolved from wherever
|
|
5
|
+
* this module itself runs — `src/checks/load.ts` under vitest (the `.ts`
|
|
6
|
+
* sources), `dist/checks/load.js` from the built binary (the compiled `.js`).
|
|
7
|
+
* Both mirror the same directory depth, so one relative path serves both.
|
|
8
|
+
*
|
|
9
|
+
* Unlike `src/transforms` (see the equivalent comment on `transformsDir` in
|
|
10
|
+
* `src/run/jscodeshift.ts`), detect and verify modules compile into `dist`
|
|
11
|
+
* normally: nothing here needs jscodeshift's babel pipeline, so there is no
|
|
12
|
+
* reason to keep them as raw `.ts`.
|
|
13
|
+
*/
|
|
14
|
+
const detectDir = fileURLToPath(new URL("../detect", import.meta.url));
|
|
15
|
+
const verifyDir = fileURLToPath(new URL("../verify", import.meta.url));
|
|
16
|
+
/**
|
|
17
|
+
* Whether `<dir>/<id>` exists as either a compiled `.js` (the built binary) or
|
|
18
|
+
* a source `.ts` (running under vitest, straight from `src`).
|
|
19
|
+
*/
|
|
20
|
+
const exists = (dir, id) => existsSync(`${dir}/${id}.js`) || existsSync(`${dir}/${id}.ts`);
|
|
21
|
+
/**
|
|
22
|
+
* Loads the detector for a catalogue id, or `undefined` when none exists yet.
|
|
23
|
+
*
|
|
24
|
+
* Only a fraction of the catalogue has a module so far — the rest follow in
|
|
25
|
+
* later work. `detect`/`verify` (the CLI commands) must treat a missing module
|
|
26
|
+
* as "nothing to run for this id" rather than a failure, so callers can loop
|
|
27
|
+
* over the whole catalogue without special-casing which ids are covered.
|
|
28
|
+
*/
|
|
29
|
+
export const loadDetector = async (id) => {
|
|
30
|
+
if (!exists(detectDir, id)) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
// `@vite-ignore`: the id is only known at runtime, so this can never be a
|
|
34
|
+
// static specifier a bundler could pre-resolve — under plain Node (the
|
|
35
|
+
// built binary) that is a non-issue, but vitest runs test files through
|
|
36
|
+
// Vite's own module graph, and without the comment Vite tries to rewrite
|
|
37
|
+
// this into a glob import and fails with "Unknown variable dynamic
|
|
38
|
+
// import". The comment tells it to leave the import alone and let Node's
|
|
39
|
+
// loader resolve it, same as the built binary does.
|
|
40
|
+
const detectModule = (await import(
|
|
41
|
+
/* @vite-ignore */ `../detect/${id}.js`));
|
|
42
|
+
return detectModule.detector;
|
|
43
|
+
};
|
|
44
|
+
/** The `verify` counterpart of {@link loadDetector}. */
|
|
45
|
+
export const loadVerifier = async (id) => {
|
|
46
|
+
if (!exists(verifyDir, id)) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
// See the matching comment in `loadDetector` above.
|
|
50
|
+
const verifyModule = (await import(
|
|
51
|
+
/* @vite-ignore */ `../verify/${id}.js`));
|
|
52
|
+
return verifyModule.verifier;
|
|
53
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** Where a check found something, so the reader can go look. */
|
|
2
|
+
export interface Finding {
|
|
3
|
+
file: string;
|
|
4
|
+
line: number;
|
|
5
|
+
text: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* What a check gets. Pure Node — no subprocess, so this works on Windows, in a
|
|
9
|
+
* minimal container, and without ripgrep installed.
|
|
10
|
+
*/
|
|
11
|
+
export interface CheckContext {
|
|
12
|
+
/** Absolute path to the consumer's sources. */
|
|
13
|
+
path: string;
|
|
14
|
+
/** Files under `path`, with node_modules / dist / .git excluded. */
|
|
15
|
+
files(extensions?: string[]): Promise<string[]>;
|
|
16
|
+
read(file: string): Promise<string>;
|
|
17
|
+
/** Every match across those files. Most checks need only this. */
|
|
18
|
+
search(pattern: RegExp, extensions?: string[]): Promise<Finding[]>;
|
|
19
|
+
}
|
|
20
|
+
export interface Detector {
|
|
21
|
+
detect(context: CheckContext): Promise<Finding[]>;
|
|
22
|
+
}
|
|
23
|
+
export interface VerifyResult {
|
|
24
|
+
/** False when something the check _can_ decide is still wrong. */
|
|
25
|
+
ok: boolean;
|
|
26
|
+
findings: Finding[];
|
|
27
|
+
/**
|
|
28
|
+
* What a person must still judge — beyond just running the typechecker.
|
|
29
|
+
* Printed under the migration's name. An empty array means this module's own
|
|
30
|
+
* check is the whole story (aside from the typecheck below): nothing further
|
|
31
|
+
* for a person to weigh.
|
|
32
|
+
*/
|
|
33
|
+
hints: string[];
|
|
34
|
+
/**
|
|
35
|
+
* The `tsc --noEmit` reminder, kept separate from `hints` so a command can
|
|
36
|
+
* tell "still needs a person's judgement" apart from "still needs the routine
|
|
37
|
+
* typecheck" instead of string-sniffing prose. Running it is a global check
|
|
38
|
+
* and this CLI wraps no commands, so it never becomes a `Finding`. Present
|
|
39
|
+
* whenever this migration's correctness depends in some way on the
|
|
40
|
+
* typechecker — including to explain that it does _not_ catch a particular
|
|
41
|
+
* class of mistake here, which is itself worth telling a person running `tsc
|
|
42
|
+
* --noEmit` out of habit.
|
|
43
|
+
*/
|
|
44
|
+
typecheckHint?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface Verifier {
|
|
47
|
+
verify(context: CheckContext): Promise<VerifyResult>;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/checks/types.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,KAAK,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAChD,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACpC,kEAAkE;IAClE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;CACpE;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;CACnD;AAED,MAAM,WAAW,YAAY;IAC3B,kEAAkE;IAClE,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB;;;;;OAKG;IACH,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CACtD"}
|
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type Command = "upgrade" | "list" | "codemod" | "help" | "version";
|
|
2
|
+
export interface ParsedCommand {
|
|
3
|
+
command: Command;
|
|
4
|
+
/**
|
|
5
|
+
* `upgrade` always has one (defaulted below when not given explicitly).
|
|
6
|
+
* `list` has one only when given — that presence/absence is what
|
|
7
|
+
* distinguishes "the whole catalogue" from "the range this revision would
|
|
8
|
+
* touch". `patch` | `minor` | `major` | a dist-tag | an exact version.
|
|
9
|
+
*/
|
|
10
|
+
revision?: string;
|
|
11
|
+
/** `codemod` only. */
|
|
12
|
+
id?: string;
|
|
13
|
+
/** Sources to transform. Unset means "decide at run time". */
|
|
14
|
+
path?: string;
|
|
15
|
+
json: boolean;
|
|
16
|
+
yes: boolean;
|
|
17
|
+
dry: boolean;
|
|
18
|
+
print: boolean;
|
|
19
|
+
allowDirty: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare const parseArguments: (argv: string[]) => ParsedCommand;
|
|
22
|
+
//# sourceMappingURL=args.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;AAE1E,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sBAAsB;IACtB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,OAAO,CAAC;CACrB;AAQD,eAAO,MAAM,cAAc,SAAU,MAAM,EAAE,KAAG,aAqD/C,CAAC"}
|
package/dist/cli/args.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
/**
|
|
3
|
+
* The default revision. `minor` stays inside the current major, so the command
|
|
4
|
+
* never crosses a breaking boundary without being asked to.
|
|
5
|
+
*/
|
|
6
|
+
const defaultRevision = "minor";
|
|
7
|
+
export const parseArguments = (argv) => {
|
|
8
|
+
const { values, positionals } = parseArgs({
|
|
9
|
+
args: argv,
|
|
10
|
+
allowPositionals: true,
|
|
11
|
+
strict: true,
|
|
12
|
+
options: {
|
|
13
|
+
help: { type: "boolean", short: "h" },
|
|
14
|
+
version: { type: "boolean", short: "V" },
|
|
15
|
+
yes: { type: "boolean", short: "y" },
|
|
16
|
+
json: { type: "boolean" },
|
|
17
|
+
dry: { type: "boolean" },
|
|
18
|
+
print: { type: "boolean" },
|
|
19
|
+
"allow-dirty": { type: "boolean" },
|
|
20
|
+
path: { type: "string" },
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
const flags = {
|
|
24
|
+
json: values.json === true,
|
|
25
|
+
yes: values.yes === true,
|
|
26
|
+
dry: values.dry === true,
|
|
27
|
+
print: values.print === true,
|
|
28
|
+
allowDirty: values["allow-dirty"] === true,
|
|
29
|
+
};
|
|
30
|
+
const [first, second] = positionals;
|
|
31
|
+
if (values.version === true) {
|
|
32
|
+
return { command: "version", ...flags };
|
|
33
|
+
}
|
|
34
|
+
if (values.help === true || first === undefined) {
|
|
35
|
+
return { command: "help", ...flags };
|
|
36
|
+
}
|
|
37
|
+
if (first === "upgrade") {
|
|
38
|
+
return {
|
|
39
|
+
command: "upgrade",
|
|
40
|
+
revision: second ?? defaultRevision,
|
|
41
|
+
path: values.path,
|
|
42
|
+
...flags,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (first === "list") {
|
|
46
|
+
// No default here, unlike `upgrade`: `revision` unset is what makes a
|
|
47
|
+
// bare `list` the catalogue browser rather than `list minor`.
|
|
48
|
+
return { command: "list", revision: second, ...flags };
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
command: "codemod",
|
|
52
|
+
id: first,
|
|
53
|
+
path: second ?? values.path,
|
|
54
|
+
...flags,
|
|
55
|
+
};
|
|
56
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { CatalogEntry } from "../catalog/entries.js";
|
|
2
|
+
export interface CreateChooseInput {
|
|
3
|
+
/** `-y` — accept every default. */
|
|
4
|
+
yes: boolean;
|
|
5
|
+
/** Whether stdin is a TTY. */
|
|
6
|
+
isTTY: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Whether this is an unattended runner.
|
|
9
|
+
*
|
|
10
|
+
* Injected rather than read from `process.env` in here: a hidden environment
|
|
11
|
+
* read underneath the caller's explicit parameters makes the function behave
|
|
12
|
+
* differently in CI than in a test that sets every input it can see — which
|
|
13
|
+
* is exactly how the cancel case below passed locally and failed on the
|
|
14
|
+
* runner, where `CI` is set and the prompt therefore never ran.
|
|
15
|
+
*/
|
|
16
|
+
isCI: boolean;
|
|
17
|
+
/** Resolves to the ids the user picked. */
|
|
18
|
+
prompt: (entries: CatalogEntry[]) => Promise<string[]>;
|
|
19
|
+
/** Reports that the prompt was cancelled. */
|
|
20
|
+
onCancel: (message: string) => void;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Which codemods to run — interactively, or with everything passed through.
|
|
24
|
+
*
|
|
25
|
+
* `-y` and a non-TTY stdin both skip the prompt: an agent or CI run has nobody
|
|
26
|
+
* to answer it. `isCI` is checked too, on top of `isTTY` — a `docker run -t`
|
|
27
|
+
* allocates a TTY with nobody watching it, so that flag is what actually
|
|
28
|
+
* distinguishes a human from an unattended runner there.
|
|
29
|
+
*/
|
|
30
|
+
export declare const createChoose: ({ yes, isTTY, isCI, prompt, onCancel, }: CreateChooseInput) => ((entries: CatalogEntry[]) => Promise<CatalogEntry[]>);
|
|
31
|
+
//# sourceMappingURL=choose.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"choose.d.ts","sourceRoot":"","sources":["../../src/cli/choose.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,MAAM,WAAW,iBAAiB;IAChC,mCAAmC;IACnC,GAAG,EAAE,OAAO,CAAC;IACb,8BAA8B;IAC9B,KAAK,EAAE,OAAO,CAAC;IACf;;;;;;;;OAQG;IACH,IAAI,EAAE,OAAO,CAAC;IACd,2CAA2C;IAC3C,MAAM,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACvD,6CAA6C;IAC7C,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,4CAMtB,iBAAiB,KAAG,CAAC,CACtB,OAAO,EAAE,YAAY,EAAE,KACpB,OAAO,CAAC,YAAY,EAAE,CAAC,CAwB3B,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which codemods to run — interactively, or with everything passed through.
|
|
3
|
+
*
|
|
4
|
+
* `-y` and a non-TTY stdin both skip the prompt: an agent or CI run has nobody
|
|
5
|
+
* to answer it. `isCI` is checked too, on top of `isTTY` — a `docker run -t`
|
|
6
|
+
* allocates a TTY with nobody watching it, so that flag is what actually
|
|
7
|
+
* distinguishes a human from an unattended runner there.
|
|
8
|
+
*/
|
|
9
|
+
export const createChoose = ({ yes, isTTY, isCI, prompt, onCancel, }) => {
|
|
10
|
+
const interactive = !yes && isTTY && !isCI;
|
|
11
|
+
return async (entries) => {
|
|
12
|
+
if (!interactive || entries.length === 0) {
|
|
13
|
+
return entries;
|
|
14
|
+
}
|
|
15
|
+
let ids;
|
|
16
|
+
try {
|
|
17
|
+
ids = await prompt(entries);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// Ctrl+C here used to reject straight out of `runUpgrade`, so the last
|
|
21
|
+
// thing the user saw was an inquirer stack trace — after the manifest had
|
|
22
|
+
// been written and the install had run. Those cannot be taken back, so
|
|
23
|
+
// treat the cancel as "no codemods" and let the caller report what did
|
|
24
|
+
// happen. Any prompt failure lands here; none of them is a reason to lose
|
|
25
|
+
// the bump.
|
|
26
|
+
onCancel("Cancelled at the codemod prompt. The dependency bump and the install already happened — no codemods were run.");
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
return entries.filter((entry) => ids.includes(entry.id));
|
|
30
|
+
};
|
|
31
|
+
};
|