@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
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ParsedCommand } from "./args.js";
|
|
2
|
+
import { runCodemod } from "../run/jscodeshift.js";
|
|
3
|
+
/**
|
|
4
|
+
* Which sources to transform, resolved against `cwd`.
|
|
5
|
+
*
|
|
6
|
+
* `src` is the default because that is where a Flow consumer's components live,
|
|
7
|
+
* and the working directory is the fallback. The caller prints the result
|
|
8
|
+
* either way — a codemod that silently ran over the wrong tree is worse than
|
|
9
|
+
* one that refused.
|
|
10
|
+
*
|
|
11
|
+
* Always returns a path rooted at `cwd` (an absolute `--path` is left alone).
|
|
12
|
+
* jscodeshift resolves a relative path against `process.cwd()`, not against
|
|
13
|
+
* whatever `cwd` a caller injected — a bare `"src"` would only be correct when
|
|
14
|
+
* the two coincide, which is true in production but not in a test that injects
|
|
15
|
+
* a different `cwd`.
|
|
16
|
+
*/
|
|
17
|
+
export declare const resolveSourcePath: (explicit: string | undefined, cwd: string, exists?: (path: string) => boolean) => string;
|
|
18
|
+
export interface CodemodCommandDeps {
|
|
19
|
+
cwd: string;
|
|
20
|
+
log: (message: string) => void;
|
|
21
|
+
run?: typeof runCodemod;
|
|
22
|
+
}
|
|
23
|
+
export declare const runSingleCodemod: (parsed: ParsedCommand, { cwd, log, run }: CodemodCommandDeps) => Promise<number>;
|
|
24
|
+
//# sourceMappingURL=codemod.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codemod.d.ts","sourceRoot":"","sources":["../../src/cli/codemod.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAmB,MAAM,uBAAuB,CAAC;AAEpE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,iBAAiB,aAClB,MAAM,GAAG,SAAS,OACvB,MAAM,WACH,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,KAChC,MAKF,CAAC;AAEF,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,GAAG,CAAC,EAAE,OAAO,UAAU,CAAC;CACzB;AAED,eAAO,MAAM,gBAAgB,WACnB,aAAa,qBACW,kBAAkB,KACjD,OAAO,CAAC,MAAM,CA6DhB,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, join } from "node:path";
|
|
3
|
+
import { allEntries, unknownCodemodMessage } from "../catalog/entries.js";
|
|
4
|
+
import { runCodemod, transformExists } from "../run/jscodeshift.js";
|
|
5
|
+
/**
|
|
6
|
+
* Which sources to transform, resolved against `cwd`.
|
|
7
|
+
*
|
|
8
|
+
* `src` is the default because that is where a Flow consumer's components live,
|
|
9
|
+
* and the working directory is the fallback. The caller prints the result
|
|
10
|
+
* either way — a codemod that silently ran over the wrong tree is worse than
|
|
11
|
+
* one that refused.
|
|
12
|
+
*
|
|
13
|
+
* Always returns a path rooted at `cwd` (an absolute `--path` is left alone).
|
|
14
|
+
* jscodeshift resolves a relative path against `process.cwd()`, not against
|
|
15
|
+
* whatever `cwd` a caller injected — a bare `"src"` would only be correct when
|
|
16
|
+
* the two coincide, which is true in production but not in a test that injects
|
|
17
|
+
* a different `cwd`.
|
|
18
|
+
*/
|
|
19
|
+
export const resolveSourcePath = (explicit, cwd, exists = existsSync) => {
|
|
20
|
+
if (explicit !== undefined) {
|
|
21
|
+
return isAbsolute(explicit) ? explicit : join(cwd, explicit);
|
|
22
|
+
}
|
|
23
|
+
return exists(join(cwd, "src")) ? join(cwd, "src") : cwd;
|
|
24
|
+
};
|
|
25
|
+
export const runSingleCodemod = async (parsed, { cwd, log, run = runCodemod }) => {
|
|
26
|
+
const id = parsed.id ?? "";
|
|
27
|
+
const entry = allEntries.find((candidate) => candidate.id === id);
|
|
28
|
+
// No catalogue entry is not the same as unknown: `to-remote-package` is a
|
|
29
|
+
// transform deliberately kept out of the catalogue (it is a port, not a
|
|
30
|
+
// migration — see `notAMigration` in `src/tests/remoteScope.test.ts`), and it
|
|
31
|
+
// still has to be reachable by id. Only fall back to the transform file when
|
|
32
|
+
// the catalogue does not know the id at all; a catalogued id whose action is
|
|
33
|
+
// "manual" or "none" still has no transform to run, regardless of what is on
|
|
34
|
+
// disk.
|
|
35
|
+
if (entry === undefined) {
|
|
36
|
+
if (!transformExists(id)) {
|
|
37
|
+
log(unknownCodemodMessage(id));
|
|
38
|
+
return 1;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else if (entry.action !== "codemod") {
|
|
42
|
+
log(`"${id}" has no codemod — it is a ${entry.action === "none" ? "behaviour change" : "manual change"}.\n\napply: ${entry.apply}`);
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
const path = resolveSourcePath(parsed.path, cwd);
|
|
46
|
+
log(`Running ${id} over ${path}`);
|
|
47
|
+
const result = await run({ id, path, dry: parsed.dry, print: parsed.print });
|
|
48
|
+
if (result.errors > 0) {
|
|
49
|
+
log(`${id}: ${result.errors} file(s) failed to transform.`);
|
|
50
|
+
return 1;
|
|
51
|
+
}
|
|
52
|
+
// Not the same as "0 files changed": jscodeshift reports an empty path and a
|
|
53
|
+
// dead worker identically, so say what happened rather than implying success.
|
|
54
|
+
if (result.processedNothing) {
|
|
55
|
+
log(`${id}: no files under ${path} were processed. Is the path right?`);
|
|
56
|
+
return 1;
|
|
57
|
+
}
|
|
58
|
+
// The same trap as `processedNothing`, one field over: a transform that
|
|
59
|
+
// declines a file by returning nothing counts as `skipped`, not `unmodified`.
|
|
60
|
+
// If every file was skipped and none changed, "0 file(s) changed" would read
|
|
61
|
+
// as a clean no-op run when in fact the transform bailed on everything.
|
|
62
|
+
if (result.changed === 0 && result.skipped > 0) {
|
|
63
|
+
log(`${id}: the transform declined all ${result.skipped} file(s) it looked at, and changed none.`);
|
|
64
|
+
return 1;
|
|
65
|
+
}
|
|
66
|
+
const skipped = result.skipped > 0 ? `, ${result.skipped} skipped` : "";
|
|
67
|
+
const summary = `${id}: ${result.changed} file(s) changed, ${result.unmodified} unchanged${skipped}.`;
|
|
68
|
+
// Only a catalogued id has a migration guide entry to point at — a transform
|
|
69
|
+
// like `to-remote-package` with no catalogue entry has no anchor in
|
|
70
|
+
// `MIGRATION.md` to link, so pointing there would be a dead link.
|
|
71
|
+
const pointer = entry === undefined
|
|
72
|
+
? ""
|
|
73
|
+
: `\nSee https://github.com/mittwald/flow/blob/main/packages/components/MIGRATION.md#${id} for what's left.`;
|
|
74
|
+
log(`${summary}${pointer}`);
|
|
75
|
+
return 0;
|
|
76
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type CatalogEntry } from "../catalog/entries.js";
|
|
2
|
+
import { loadDetector } from "../checks/load.js";
|
|
3
|
+
import type { Finding } from "../checks/types.js";
|
|
4
|
+
export interface DetectEntryResult {
|
|
5
|
+
entry: CatalogEntry;
|
|
6
|
+
findings: Finding[];
|
|
7
|
+
}
|
|
8
|
+
export interface RunDetectDeps {
|
|
9
|
+
/** Which entries to try. Defaults to the whole catalogue. */
|
|
10
|
+
entries?: CatalogEntry[];
|
|
11
|
+
/** Injectable for tests — defaults to the real module loader. */
|
|
12
|
+
load?: typeof loadDetector;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Runs every available detector over `path` and returns only the entries that
|
|
16
|
+
* found something.
|
|
17
|
+
*
|
|
18
|
+
* Only a fraction of the catalogue has a detector module so far (see
|
|
19
|
+
* `checks/load.ts`); an id with none is skipped rather than treated as a
|
|
20
|
+
* failure — the remaining detectors are mechanical follow-up work, not a bug in
|
|
21
|
+
* this command.
|
|
22
|
+
*/
|
|
23
|
+
export declare const runDetect: (path: string, { entries, load }?: RunDetectDeps) => Promise<DetectEntryResult[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Renders `runDetect`'s result as text — the presentation half kept separate
|
|
26
|
+
* from the async file-reading half, the same split `renderList` uses, so
|
|
27
|
+
* formatting is testable without touching a file. Reuses `renderList`'s
|
|
28
|
+
* vocabulary (`painter`, `wrap`, `indent`) rather than a second
|
|
29
|
+
* implementation.
|
|
30
|
+
*/
|
|
31
|
+
export declare const renderDetect: (results: DetectEntryResult[], path: string, { color, width }?: {
|
|
32
|
+
color?: boolean;
|
|
33
|
+
width?: number;
|
|
34
|
+
}) => string;
|
|
35
|
+
//# sourceMappingURL=detect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../src/cli/detect.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAEtE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAGlD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,YAAY,CAAC;IACpB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,iEAAiE;IACjE,IAAI,CAAC,EAAE,OAAO,YAAY,CAAC;CAC5B;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,SACd,MAAM,sBACmC,aAAa,KAC3D,OAAO,CAAC,iBAAiB,EAAE,CAgB7B,CAAC;AAeF;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,YACd,iBAAiB,EAAE,QACtB,MAAM,qBACmB;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,KACjE,MAuBF,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import colors from "picocolors";
|
|
2
|
+
import { allEntries } from "../catalog/entries.js";
|
|
3
|
+
import { createCheckContext } from "../checks/context.js";
|
|
4
|
+
import { loadDetector } from "../checks/load.js";
|
|
5
|
+
import { indent, painter, wrap } from "./list.js";
|
|
6
|
+
/**
|
|
7
|
+
* Runs every available detector over `path` and returns only the entries that
|
|
8
|
+
* found something.
|
|
9
|
+
*
|
|
10
|
+
* Only a fraction of the catalogue has a detector module so far (see
|
|
11
|
+
* `checks/load.ts`); an id with none is skipped rather than treated as a
|
|
12
|
+
* failure — the remaining detectors are mechanical follow-up work, not a bug in
|
|
13
|
+
* this command.
|
|
14
|
+
*/
|
|
15
|
+
export const runDetect = async (path, { entries = allEntries, load = loadDetector } = {}) => {
|
|
16
|
+
const context = createCheckContext(path);
|
|
17
|
+
const results = [];
|
|
18
|
+
for (const entry of entries) {
|
|
19
|
+
const detector = await load(entry.id);
|
|
20
|
+
if (detector === undefined) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const findings = await detector.detect(context);
|
|
24
|
+
if (findings.length > 0) {
|
|
25
|
+
results.push({ entry, findings });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return results;
|
|
29
|
+
};
|
|
30
|
+
/** One `file:line text` row, wrapped and indented like `renderList`'s rows. */
|
|
31
|
+
const findingLine = (finding, width, paint) => {
|
|
32
|
+
const location = paint.dim(`${finding.file}:${finding.line}`);
|
|
33
|
+
const body = wrap(finding.text, Math.max(width - indent.length, 20));
|
|
34
|
+
return body.map((line, index) => index === 0 ? `${indent}${location} ${line}` : `${indent} ${line}`);
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Renders `runDetect`'s result as text — the presentation half kept separate
|
|
38
|
+
* from the async file-reading half, the same split `renderList` uses, so
|
|
39
|
+
* formatting is testable without touching a file. Reuses `renderList`'s
|
|
40
|
+
* vocabulary (`painter`, `wrap`, `indent`) rather than a second
|
|
41
|
+
* implementation.
|
|
42
|
+
*/
|
|
43
|
+
export const renderDetect = (results, path, { color = false, width = 80 } = {}) => {
|
|
44
|
+
const paint = painter(color);
|
|
45
|
+
if (results.length === 0) {
|
|
46
|
+
return `No migrations under ${path} were found by the available detectors.\n`;
|
|
47
|
+
}
|
|
48
|
+
const noun = results.length === 1 ? "migration" : "migrations";
|
|
49
|
+
const verb = results.length === 1 ? "touches" : "touch";
|
|
50
|
+
const header = `${paint.bold(`${results.length} ${noun}`)} ${verb} ${path}\n`;
|
|
51
|
+
const body = results
|
|
52
|
+
.map(({ entry, findings }) => {
|
|
53
|
+
const mark = color ? colors.green("●") : "*";
|
|
54
|
+
const lines = [
|
|
55
|
+
`${mark} ${paint.bold(entry.id)}`,
|
|
56
|
+
...findings.flatMap((finding) => findingLine(finding, width, paint)),
|
|
57
|
+
];
|
|
58
|
+
return lines.join("\n");
|
|
59
|
+
})
|
|
60
|
+
.join("\n\n");
|
|
61
|
+
return `${header}\n${body}\n`;
|
|
62
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { type CatalogEntry } from "../catalog/entries.js";
|
|
2
|
+
import { type RangeDeps } from "../resolve/range.js";
|
|
3
|
+
import type { ParsedCommand } from "./args.js";
|
|
4
|
+
export interface RenderListInput {
|
|
5
|
+
entries: CatalogEntry[];
|
|
6
|
+
/**
|
|
7
|
+
* The version range to show migrations for. Both bounds always arrive
|
|
8
|
+
* together — there is no partial range — so omitting it entirely is what
|
|
9
|
+
* lists the whole catalogue.
|
|
10
|
+
*/
|
|
11
|
+
range?: {
|
|
12
|
+
from: string;
|
|
13
|
+
to: string;
|
|
14
|
+
};
|
|
15
|
+
json: boolean;
|
|
16
|
+
/** Emit ANSI colour. Off by default so a test sees plain text. */
|
|
17
|
+
color?: boolean;
|
|
18
|
+
/** Terminal width to wrap prose to. */
|
|
19
|
+
width?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Render the frame around the entries: the context on top (the range, the
|
|
22
|
+
* catch-up legend) and the summary at the bottom (the counts). On by default.
|
|
23
|
+
* `upgrade` turns it off for its by-hand section: it already printed its own
|
|
24
|
+
* heading and its own aggregate ("N codemods run, N changed something"), and
|
|
25
|
+
* this renderer's frame would just repeat both — see `runUpgrade`. Named for
|
|
26
|
+
* the whole frame, not just the top half, since it now gates both. The
|
|
27
|
+
* per-entry catch-up mark itself is unaffected — it lives in the body, not
|
|
28
|
+
* the frame.
|
|
29
|
+
*/
|
|
30
|
+
frame?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export declare const stripAnsi: (text: string) => string;
|
|
33
|
+
/**
|
|
34
|
+
* The migrations for a version range, as text or JSON.
|
|
35
|
+
*
|
|
36
|
+
* Read-only by design: this is what an agent can call to plan before it changes
|
|
37
|
+
* anything. `--json` carries `apply` through unchanged, because that is the
|
|
38
|
+
* field it acts on — and it returns before any styling, so no escape sequence
|
|
39
|
+
* can ever reach a parser.
|
|
40
|
+
*
|
|
41
|
+
* `color` and `width` are arguments rather than read from the environment here,
|
|
42
|
+
* so the rendering is deterministic in a test — `painter` builds its palette
|
|
43
|
+
* from `color` alone, never from the terminal. `cli.ts` supplies them.
|
|
44
|
+
*/
|
|
45
|
+
export declare const renderList: ({ entries, range, json, color, width, frame, }: RenderListInput) => string;
|
|
46
|
+
export interface ListDeps extends RangeDeps {
|
|
47
|
+
/**
|
|
48
|
+
* Raw output writer — matches `process.stdout.write`'s own contract: no
|
|
49
|
+
* newline is appended automatically.
|
|
50
|
+
*/
|
|
51
|
+
write: (text: string) => void;
|
|
52
|
+
color?: boolean;
|
|
53
|
+
width?: number;
|
|
54
|
+
}
|
|
55
|
+
export declare const defaultListDeps: (cwd: string) => ListDeps;
|
|
56
|
+
/**
|
|
57
|
+
* `list [revision]` — the catalogue browser (no argument) or a dry run of
|
|
58
|
+
* `upgrade <revision>` (with one).
|
|
59
|
+
*
|
|
60
|
+
* The two forms are distinguished by whether a revision was given, not by a
|
|
61
|
+
* default: unlike `upgrade`, `list` has none, because a bare `list` is a
|
|
62
|
+
* deliberately different thing — the whole catalogue, offline, no manifest
|
|
63
|
+
* read. Given a revision, this shares `resolveRange` with `upgrade`, so the
|
|
64
|
+
* range shown is exactly what `upgrade <revision>` would act on. Unlike
|
|
65
|
+
* `upgrade`, an unresolved-but-not-an-upgrade target (e.g. an exact version at
|
|
66
|
+
* or below current) is not a refusal here — `resolveRange` reports it as `ok:
|
|
67
|
+
* true`, and `list` shows it like any other range (which is typically empty,
|
|
68
|
+
* and prints "Nothing to migrate in that range.").
|
|
69
|
+
*/
|
|
70
|
+
export declare const runList: (parsed: ParsedCommand, deps: ListDeps) => Promise<number>;
|
|
71
|
+
//# sourceMappingURL=list.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/cli/list.ts"],"names":[],"mappings":"AAEA,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAEtE,OAAO,EAGL,KAAK,SAAS,EACf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB;;;;OAIG;IACH,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IACrC,IAAI,EAAE,OAAO,CAAC;IACd,kEAAkE;IAClE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;;;OASG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAkCD,eAAO,MAAM,SAAS,SAAU,MAAM,KAAG,MAAgC,CAAC;AAoO1E;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,UAAU,mDAOpB,eAAe,KAAG,MA0DpB,CAAC;AAEF,MAAM,WAAW,QAAS,SAAQ,SAAS;IACzC;;;OAGG;IACH,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,eAAe,QAAS,MAAM,KAAG,QAG5C,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,OAAO,WACV,aAAa,QACf,QAAQ,KACb,OAAO,CAAC,MAAM,CA+BhB,CAAC"}
|
package/dist/cli/list.js
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import colors from "picocolors";
|
|
2
|
+
import { lte } from "semver";
|
|
3
|
+
import { allEntries } from "../catalog/entries.js";
|
|
4
|
+
import { selectEntries, sortBySince } from "../catalog/select.js";
|
|
5
|
+
import { defaultRangeDeps, resolveRange, } from "../resolve/range.js";
|
|
6
|
+
/**
|
|
7
|
+
* What the reader has to do, and the colour that says it at a glance.
|
|
8
|
+
*
|
|
9
|
+
* `plural` only differs for `codemod`, which is a countable thing; "by hand"
|
|
10
|
+
* and "no code change" describe how, not how many. `tone` names a key into the
|
|
11
|
+
* `Painter` a call site already built with `painter(color)` — not a bound
|
|
12
|
+
* colour function — so which palette applies is decided once, by the `color`
|
|
13
|
+
* argument, not baked into this module-level table. See `painter` for why.
|
|
14
|
+
*/
|
|
15
|
+
const actions = {
|
|
16
|
+
codemod: { label: "codemod", plural: "codemods", tone: "green" },
|
|
17
|
+
manual: { label: "by hand", plural: "by hand", tone: "yellow" },
|
|
18
|
+
none: {
|
|
19
|
+
label: "no code change",
|
|
20
|
+
plural: "no code change",
|
|
21
|
+
tone: "blue",
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* ANSI escapes have no width; measuring must ignore them.
|
|
26
|
+
*
|
|
27
|
+
* Built from a char code rather than written as a literal, so no control
|
|
28
|
+
* character sits in the source — which is also what `no-control-regex` wants.
|
|
29
|
+
*/
|
|
30
|
+
const ansi = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
31
|
+
export const stripAnsi = (text) => text.replace(ansi, "");
|
|
32
|
+
const visibleWidth = (text) => stripAnsi(text).length;
|
|
33
|
+
/**
|
|
34
|
+
* Wraps to `width`, measuring visible width so already-coloured text still
|
|
35
|
+
* breaks in the right place.
|
|
36
|
+
*/
|
|
37
|
+
const wrap = (text, width) => {
|
|
38
|
+
const lines = [];
|
|
39
|
+
let line = "";
|
|
40
|
+
for (const word of text.split(/\s+/).filter((part) => part !== "")) {
|
|
41
|
+
if (line === "") {
|
|
42
|
+
line = word;
|
|
43
|
+
}
|
|
44
|
+
else if (visibleWidth(line) + 1 + visibleWidth(word) <= width) {
|
|
45
|
+
line = `${line} ${word}`;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
lines.push(line);
|
|
49
|
+
line = word;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (line !== "") {
|
|
53
|
+
lines.push(line);
|
|
54
|
+
}
|
|
55
|
+
return lines.length > 0 ? lines : [""];
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Renders `code` spans as colour instead of literal backticks.
|
|
59
|
+
*
|
|
60
|
+
* The catalogue bodies are Markdown, so its prose fields carry backticks. In a
|
|
61
|
+
* terminal those are noise, but the emphasis they mark is exactly what a reader
|
|
62
|
+
* scans for — a symbol name or a command.
|
|
63
|
+
*/
|
|
64
|
+
const inlineCode = (text, paint) => text.replace(/`([^`]+)`/g, (_, code) => paint.code(code));
|
|
65
|
+
/**
|
|
66
|
+
* Builds a palette from `color`, and only from `color`.
|
|
67
|
+
*
|
|
68
|
+
* `colors` (the module-level `picocolors` default export) auto-detects a TTY
|
|
69
|
+
* and disables itself under one — reading `colors.green` etc. directly would
|
|
70
|
+
* make the _environment_, not this argument, the real decider, silently
|
|
71
|
+
* disagreeing with `cli.ts`'s own TTY/`NO_COLOR`/`--json` check. `createColors`
|
|
72
|
+
* returns a palette with colour forced on or off, so `color` is the only input
|
|
73
|
+
* to how this renders.
|
|
74
|
+
*/
|
|
75
|
+
const painter = (color) => {
|
|
76
|
+
const palette = colors.createColors(color);
|
|
77
|
+
return {
|
|
78
|
+
bold: palette.bold,
|
|
79
|
+
dim: palette.dim,
|
|
80
|
+
code: palette.cyan,
|
|
81
|
+
green: palette.green,
|
|
82
|
+
yellow: palette.yellow,
|
|
83
|
+
blue: palette.blue,
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
const indent = " ";
|
|
87
|
+
const labelWidth = 8;
|
|
88
|
+
/** One `apply` row: dim label, wrapped body beside it. */
|
|
89
|
+
const field = (label, value, width, paint) => {
|
|
90
|
+
const gutter = indent + " ".repeat(labelWidth);
|
|
91
|
+
const body = wrap(inlineCode(value, paint), Math.max(width - gutter.length, 20));
|
|
92
|
+
return body.map((line, index) => index === 0
|
|
93
|
+
? `${indent}${paint.dim(label.padEnd(labelWidth))}${line}`
|
|
94
|
+
: `${gutter}${line}`);
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Whether `entry` shipped at or before `current` \u2014 selected because the
|
|
98
|
+
* gate no longer has a lower bound (`since <= target` alone, see
|
|
99
|
+
* `selectEntries`), not because the range newly crosses it. Only meaningful for
|
|
100
|
+
* a range-bounded list \u2014 the whole-catalogue browse has no `current` to
|
|
101
|
+
* compare against.
|
|
102
|
+
*
|
|
103
|
+
* This is what may already be done, not what is done: nothing records which
|
|
104
|
+
* migrations a project has actually performed, codemod or manual alike \u2014
|
|
105
|
+
* see `selectEntries`.
|
|
106
|
+
*/
|
|
107
|
+
const isCatchUp = (entry, current) => current !== undefined && lte(entry.since, current);
|
|
108
|
+
const renderEntry = (entry, width, color, catchUp) => {
|
|
109
|
+
const paint = painter(color);
|
|
110
|
+
const action = actions[entry.action];
|
|
111
|
+
// Hollow vs filled: catch-up shipped at or before `current`, so it may
|
|
112
|
+
// already be handled, unlike something the upgrade is newly bringing in \u2014
|
|
113
|
+
// see the legend line in `renderContext`.
|
|
114
|
+
const symbol = catchUp ? "\u25CB" : "\u25CF";
|
|
115
|
+
const mark = color ? paint[action.tone](symbol) : catchUp ? "o" : "*";
|
|
116
|
+
const meta = [entry.kind, action.label];
|
|
117
|
+
if (entry.remotePackage) {
|
|
118
|
+
meta.push("also in flow-remote-react-components");
|
|
119
|
+
}
|
|
120
|
+
if (catchUp) {
|
|
121
|
+
meta.push("catch-up");
|
|
122
|
+
}
|
|
123
|
+
const lines = [
|
|
124
|
+
`${mark} ${paint.bold(entry.id)} ${paint.dim(entry.since)}`,
|
|
125
|
+
indent + paint.dim(meta.join(" \u00B7 ")),
|
|
126
|
+
"",
|
|
127
|
+
...field("apply", entry.apply, width, paint),
|
|
128
|
+
];
|
|
129
|
+
if (entry.action === "codemod") {
|
|
130
|
+
lines.push("", `${indent}${paint.dim("$")} ${paint.code(`npx @mittwald/flow-codemods@latest ${entry.id} src`)}`);
|
|
131
|
+
}
|
|
132
|
+
return lines.join("\n");
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* What a reader needs _before_ the entries: what range this is, and — when it
|
|
136
|
+
* explains a mark they're about to see below — the catch-up legend.
|
|
137
|
+
*
|
|
138
|
+
* "" for a bare `list` (no range): there is nothing to decode, so nothing
|
|
139
|
+
* renders. Otherwise "from X to Y", or for a zero-width range "nothing newer
|
|
140
|
+
* than X" (`from`/`to` is the wrong form once they're equal — that isn't a
|
|
141
|
+
* range, it's "you're already there"), plus — when any entry below is marked
|
|
142
|
+
* catch-up — the legend for the mark. The gate has no lower bound (see
|
|
143
|
+
* `selectEntries`), so a range-bounded list can show every entry in the
|
|
144
|
+
* catalogue, not just the ones the range newly crosses; the legend says what
|
|
145
|
+
* the mark means — and, just as much, what it does not: catch-up is "may
|
|
146
|
+
* already be done", never "already done".
|
|
147
|
+
*/
|
|
148
|
+
const renderContext = (selected, range, color, width) => {
|
|
149
|
+
if (range === undefined) {
|
|
150
|
+
return "";
|
|
151
|
+
}
|
|
152
|
+
const catchUpCount = selected.filter((entry) => isCatchUp(entry, range.from)).length;
|
|
153
|
+
const rangeText = range.from === range.to
|
|
154
|
+
? catchUpCount > 0
|
|
155
|
+
? `nothing newer than ${range.to}; entries below are catch-up`
|
|
156
|
+
: `nothing newer than ${range.to}`
|
|
157
|
+
: `from ${range.from} to ${range.to}`;
|
|
158
|
+
if (catchUpCount === 0) {
|
|
159
|
+
return rangeText;
|
|
160
|
+
}
|
|
161
|
+
const paint = painter(color);
|
|
162
|
+
const mark = color ? paint[actions.codemod.tone]("○") : "o";
|
|
163
|
+
// Wrapped like every other line: unwrapped it ran to 150 characters and broke
|
|
164
|
+
// hard in any terminal narrower than that.
|
|
165
|
+
const legendGutter = " ";
|
|
166
|
+
const legend = wrap(`catch-up: shipped at or before ${range.from} — you may already have done this. Re-running a codemod is a safe no-op; a manual step needs your own check.`, Math.max(width - legendGutter.length, 20)).map((line, index) => index === 0 ? `${mark} ${line}` : `${legendGutter}${line}`);
|
|
167
|
+
return [rangeText, ...legend].join("\n");
|
|
168
|
+
};
|
|
169
|
+
/**
|
|
170
|
+
* What the reader has, after reading the entries: "N migrations · N codemods ·
|
|
171
|
+
* N by hand · N no code change".
|
|
172
|
+
*
|
|
173
|
+
* Nothing is hidden any more (see `selectEntries`), so this is just the entry
|
|
174
|
+
* count and the per-action breakdown — no separate hidden-count line.
|
|
175
|
+
*/
|
|
176
|
+
const renderSummary = (selected, color) => {
|
|
177
|
+
const paint = painter(color);
|
|
178
|
+
const noun = selected.length === 1 ? "migration" : "migrations";
|
|
179
|
+
const counts = Object.keys(actions)
|
|
180
|
+
.map((action) => ({
|
|
181
|
+
action,
|
|
182
|
+
count: selected.filter((entry) => entry.action === action).length,
|
|
183
|
+
}))
|
|
184
|
+
.filter(({ count }) => count > 0)
|
|
185
|
+
.map(({ action, count }) => {
|
|
186
|
+
const { label, plural, tone } = actions[action];
|
|
187
|
+
const text = `${count} ${count === 1 ? label : plural}`;
|
|
188
|
+
return color ? paint[tone](text) : text;
|
|
189
|
+
});
|
|
190
|
+
// The counts carry their own colour, so no dim around them — nesting the two
|
|
191
|
+
// makes both weaker.
|
|
192
|
+
return [paint.bold(`${selected.length} ${noun}`), ...counts].join(paint.dim(" · "));
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* The migrations for a version range, as text or JSON.
|
|
196
|
+
*
|
|
197
|
+
* Read-only by design: this is what an agent can call to plan before it changes
|
|
198
|
+
* anything. `--json` carries `apply` through unchanged, because that is the
|
|
199
|
+
* field it acts on — and it returns before any styling, so no escape sequence
|
|
200
|
+
* can ever reach a parser.
|
|
201
|
+
*
|
|
202
|
+
* `color` and `width` are arguments rather than read from the environment here,
|
|
203
|
+
* so the rendering is deterministic in a test — `painter` builds its palette
|
|
204
|
+
* from `color` alone, never from the terminal. `cli.ts` supplies them.
|
|
205
|
+
*/
|
|
206
|
+
export const renderList = ({ entries, range, json, color = false, width = 80, frame = true, }) => {
|
|
207
|
+
// The two paths differ in their bounds, deliberately: unbounded, this is a
|
|
208
|
+
// plain catalogue browse — every entry, sorted, regardless of whether it
|
|
209
|
+
// would apply to any given range. Bounded, it answers "what does this
|
|
210
|
+
// version range require of me", which `selectEntries` computes per entry
|
|
211
|
+
// from `since`. `range`'s two fields always arrive together (there is no
|
|
212
|
+
// partial bound any more — see `resolveRange`), so there is no sentinel to
|
|
213
|
+
// fill a missing side with; the branch below is the only thing that decides
|
|
214
|
+
// "whole catalogue" vs. "this range".
|
|
215
|
+
const selected = range === undefined
|
|
216
|
+
? sortBySince(entries)
|
|
217
|
+
: selectEntries(entries, range.to);
|
|
218
|
+
if (json) {
|
|
219
|
+
// An object, not the bare array: the agent-facing form has to say which
|
|
220
|
+
// range it describes, or `list minor` and `list` are indistinguishable in
|
|
221
|
+
// it — and it carries `catchUp` per entry for the same reason the human
|
|
222
|
+
// form marks it.
|
|
223
|
+
return `${JSON.stringify({
|
|
224
|
+
range: range === undefined
|
|
225
|
+
? null
|
|
226
|
+
: { current: range.from, target: range.to },
|
|
227
|
+
migrations: selected.map((entry) => ({
|
|
228
|
+
...entry,
|
|
229
|
+
catchUp: isCatchUp(entry, range?.from),
|
|
230
|
+
})),
|
|
231
|
+
}, null, 2)}\n`;
|
|
232
|
+
}
|
|
233
|
+
if (selected.length === 0) {
|
|
234
|
+
return "Nothing to migrate in that range.\n";
|
|
235
|
+
}
|
|
236
|
+
const body = selected
|
|
237
|
+
.map((entry) => renderEntry(entry, width, color, isCatchUp(entry, range?.from)))
|
|
238
|
+
.join("\n\n");
|
|
239
|
+
if (!frame) {
|
|
240
|
+
return `${body}\n`;
|
|
241
|
+
}
|
|
242
|
+
// Context (range, legend) leads, because a reader needs both before the
|
|
243
|
+
// entries; the summary (counts) trails, because a 22-entry list scrolls the
|
|
244
|
+
// top out of sight well before the reader reaches the end — exactly when
|
|
245
|
+
// they want the counts. `context` is "" for a bare `list`, so no blank line
|
|
246
|
+
// gets left dangling above the first entry.
|
|
247
|
+
const context = renderContext(selected, range, color, width);
|
|
248
|
+
const summary = renderSummary(selected, color);
|
|
249
|
+
return `${context === "" ? "" : `${context}\n\n`}${body}\n\n${summary}\n`;
|
|
250
|
+
};
|
|
251
|
+
export const defaultListDeps = (cwd) => ({
|
|
252
|
+
...defaultRangeDeps(cwd),
|
|
253
|
+
write: (text) => process.stdout.write(text),
|
|
254
|
+
});
|
|
255
|
+
/**
|
|
256
|
+
* `list [revision]` — the catalogue browser (no argument) or a dry run of
|
|
257
|
+
* `upgrade <revision>` (with one).
|
|
258
|
+
*
|
|
259
|
+
* The two forms are distinguished by whether a revision was given, not by a
|
|
260
|
+
* default: unlike `upgrade`, `list` has none, because a bare `list` is a
|
|
261
|
+
* deliberately different thing — the whole catalogue, offline, no manifest
|
|
262
|
+
* read. Given a revision, this shares `resolveRange` with `upgrade`, so the
|
|
263
|
+
* range shown is exactly what `upgrade <revision>` would act on. Unlike
|
|
264
|
+
* `upgrade`, an unresolved-but-not-an-upgrade target (e.g. an exact version at
|
|
265
|
+
* or below current) is not a refusal here — `resolveRange` reports it as `ok:
|
|
266
|
+
* true`, and `list` shows it like any other range (which is typically empty,
|
|
267
|
+
* and prints "Nothing to migrate in that range.").
|
|
268
|
+
*/
|
|
269
|
+
export const runList = async (parsed, deps) => {
|
|
270
|
+
const { revision } = parsed;
|
|
271
|
+
if (revision === undefined) {
|
|
272
|
+
deps.write(renderList({
|
|
273
|
+
entries: allEntries,
|
|
274
|
+
json: parsed.json,
|
|
275
|
+
color: deps.color,
|
|
276
|
+
width: deps.width,
|
|
277
|
+
}));
|
|
278
|
+
return 0;
|
|
279
|
+
}
|
|
280
|
+
const resolved = await resolveRange(revision, deps);
|
|
281
|
+
if (!resolved.ok) {
|
|
282
|
+
deps.write(`${resolved.reason}\n`);
|
|
283
|
+
return 1;
|
|
284
|
+
}
|
|
285
|
+
deps.write(renderList({
|
|
286
|
+
entries: allEntries,
|
|
287
|
+
range: { from: resolved.current, to: resolved.target },
|
|
288
|
+
json: parsed.json,
|
|
289
|
+
color: deps.color,
|
|
290
|
+
width: deps.width,
|
|
291
|
+
}));
|
|
292
|
+
return 0;
|
|
293
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type CatalogEntry } from "../catalog/entries.js";
|
|
2
|
+
import { type InstallRunner } from "../install.js";
|
|
3
|
+
import { fetchVersions } from "../resolve/registry.js";
|
|
4
|
+
import { runCodemod } from "../run/jscodeshift.js";
|
|
5
|
+
import type { ParsedCommand } from "./args.js";
|
|
6
|
+
export interface UpgradeDeps {
|
|
7
|
+
cwd: string;
|
|
8
|
+
fetchVersions: typeof fetchVersions;
|
|
9
|
+
install: InstallRunner;
|
|
10
|
+
runCodemod: typeof runCodemod;
|
|
11
|
+
/** Which codemods to apply. `-y` and a non-TTY pass everything through. */
|
|
12
|
+
choose: (entries: CatalogEntry[]) => Promise<CatalogEntry[]>;
|
|
13
|
+
isDirty: (cwd: string) => boolean;
|
|
14
|
+
readInstalledVersion: (cwd: string, name: string) => string | undefined;
|
|
15
|
+
log: (message: string) => void;
|
|
16
|
+
}
|
|
17
|
+
export declare const defaultUpgradeDeps: (cwd: string) => UpgradeDeps;
|
|
18
|
+
/**
|
|
19
|
+
* Bump every Flow dependency, install, then run the codemods the resolved range
|
|
20
|
+
* calls for — and end by naming what no codemod covers.
|
|
21
|
+
*
|
|
22
|
+
* The order is not cosmetic: the codemods run against the installed target, so
|
|
23
|
+
* `tsc` can be green when the command returns. `--dry` skips both the manifest
|
|
24
|
+
* write and the install, so the codemods it still runs act against whatever is
|
|
25
|
+
* currently installed rather than the target — their output is indicative, not
|
|
26
|
+
* exact.
|
|
27
|
+
*
|
|
28
|
+
* Selection has no lower bound at all (see `selectEntries`) — every entry with
|
|
29
|
+
* `since <= target` is in, codemod or manual — so a project already on `target`
|
|
30
|
+
* still gets a full pass: it skips only the write and the install, which
|
|
31
|
+
* genuinely have nothing to do.
|
|
32
|
+
*/
|
|
33
|
+
export declare const runUpgrade: (parsed: ParsedCommand, deps: UpgradeDeps) => Promise<number>;
|
|
34
|
+
//# sourceMappingURL=upgrade.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upgrade.d.ts","sourceRoot":"","sources":["../../src/cli/upgrade.ts"],"names":[],"mappings":"AAGA,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAItE,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD,OAAO,EAAE,UAAU,EAAsB,MAAM,uBAAuB,CAAC;AACvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAI/C,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,OAAO,aAAa,CAAC;IACpC,OAAO,EAAE,aAAa,CAAC;IACvB,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,2EAA2E;IAC3E,MAAM,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IAC7D,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IAClC,oBAAoB,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACxE,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAED,eAAO,MAAM,kBAAkB,QAAS,MAAM,KAAG,WAS/C,CAAC;AA2BH;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,UAAU,WACb,aAAa,QACf,WAAW,KAChB,OAAO,CAAC,MAAM,CAwMhB,CAAC"}
|