@lablup/ui-common-cli 0.2.0-alpha.15
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/LICENSE +202 -0
- package/NOTICE +8 -0
- package/README.md +28 -0
- package/bin/ui-common.mjs +12 -0
- package/cli/agents.mjs +335 -0
- package/cli/diff.mjs +125 -0
- package/cli/lab-peer.mjs +226 -0
- package/cli/main.mjs +97 -0
- package/cli/passthrough.mjs +153 -0
- package/cli/paths.mjs +290 -0
- package/cli/report.mjs +157 -0
- package/cli/resolve.mjs +257 -0
- package/cli/rewrite.mjs +133 -0
- package/cli/semver.mjs +278 -0
- package/cli/shadow.mjs +161 -0
- package/cli/sync-astryx.mjs +417 -0
- package/cli/upgrade.mjs +776 -0
- package/codemods/0.2/components.mjs +903 -0
- package/codemods/0.2/elements.mjs +745 -0
- package/codemods/0.2/index.mjs +68 -0
- package/codemods/0.2/legacy-classes.json +292 -0
- package/codemods/0.2/local-modules.mjs +510 -0
- package/codemods/0.2/map.mjs +178 -0
- package/codemods/0.2/package-json.mjs +333 -0
- package/codemods/0.2/scan.mjs +397 -0
- package/codemods/0.2/stylesheets.mjs +479 -0
- package/codemods/0.2/theme.mjs +325 -0
- package/codemods/lib/jsx.mjs +352 -0
- package/codemods/lib/todo.mjs +151 -0
- package/codemods/registry.mjs +95 -0
- package/codemods/upstream.mjs +155 -0
- package/migration/0.1-to-0.2.json +629 -0
- package/package.json +50 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leave `TODO(ui-common-upgrade)` markers where a codemod could not prove a
|
|
3
|
+
* rewrite safe. Where the marker goes:
|
|
4
|
+
* - a JSX child gets a `{/* … *\/}` line right above it, or right before it
|
|
5
|
+
* when it shares its line with other children;
|
|
6
|
+
* - an element that is a `return` value or a parenthesised expression gets a
|
|
7
|
+
* `//` line right above it, inside the parentheses;
|
|
8
|
+
* - anything else gets a `//` line above its statement.
|
|
9
|
+
* A marker already there (from an earlier run) is not added twice.
|
|
10
|
+
* `ui-common upgrade` lists every marker in its report, with its line.
|
|
11
|
+
*
|
|
12
|
+
* A file whose transform calls `addTodo` is printed with `printSource`: the
|
|
13
|
+
* JSX-child markers are written into the printed text, not into the parent's
|
|
14
|
+
* `children`. Adding a child makes recast reprint the parent, and recast's
|
|
15
|
+
* reprint drops the leading whitespace of every text child
|
|
16
|
+
* (`{n} items` -> `{n}items`).
|
|
17
|
+
*/
|
|
18
|
+
import { TODO_TAG } from "./jsx.mjs";
|
|
19
|
+
|
|
20
|
+
/** The markers waiting on a JSX child, written by `printSource`. */
|
|
21
|
+
const PENDING = Symbol("ui-common-upgrade.todo");
|
|
22
|
+
const SENTINEL = "__ui_common_upgrade_todo_";
|
|
23
|
+
|
|
24
|
+
/** @param {any} node */
|
|
25
|
+
function isStatement(node) {
|
|
26
|
+
return (
|
|
27
|
+
node &&
|
|
28
|
+
typeof node.type === "string" &&
|
|
29
|
+
/(Statement|Declaration)$/.test(node.type) &&
|
|
30
|
+
!node.type.startsWith("TS")
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** @param {any[] | undefined} comments @param {string} text */
|
|
35
|
+
function hasComment(comments, text) {
|
|
36
|
+
return (comments ?? []).some((c) => String(c.value).trim() === text);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** @param {any} node @param {string} text */
|
|
40
|
+
function carries(node, text) {
|
|
41
|
+
return (
|
|
42
|
+
hasComment(node?.comments, text) ||
|
|
43
|
+
hasComment(node?.innerComments, text) ||
|
|
44
|
+
hasComment(node?.leadingComments, text)
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param {any} j jscodeshift
|
|
50
|
+
* @param {any} path NodePath of the node the message is about
|
|
51
|
+
* @param {string} message
|
|
52
|
+
*/
|
|
53
|
+
export function addTodo(j, path, message) {
|
|
54
|
+
const text = `${TODO_TAG}: ${message}`;
|
|
55
|
+
const node = path.node;
|
|
56
|
+
const parent = path.parent?.node;
|
|
57
|
+
const isElement = node.type === "JSXElement" || node.type === "JSXFragment";
|
|
58
|
+
|
|
59
|
+
if (
|
|
60
|
+
isElement &&
|
|
61
|
+
parent &&
|
|
62
|
+
(parent.type === "JSXElement" || parent.type === "JSXFragment") &&
|
|
63
|
+
Array.isArray(parent.children)
|
|
64
|
+
) {
|
|
65
|
+
const index = parent.children.indexOf(node);
|
|
66
|
+
if (index === -1) return;
|
|
67
|
+
// Walk back over whitespace and earlier markers looking for this one.
|
|
68
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
69
|
+
const sibling = parent.children[i];
|
|
70
|
+
if (sibling.type === "JSXText" && sibling.value.trim() === "") continue;
|
|
71
|
+
if (
|
|
72
|
+
sibling.type === "JSXExpressionContainer" &&
|
|
73
|
+
sibling.expression.type === "JSXEmptyExpression"
|
|
74
|
+
) {
|
|
75
|
+
if (carries(sibling.expression, text)) return;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
const pending = (node[PENDING] ??= []);
|
|
81
|
+
if (!pending.includes(text)) pending.push(text);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (isElement && (parent?.type === "ReturnStatement" || node.extra?.parenthesized)) {
|
|
86
|
+
if (carries(node, text)) return;
|
|
87
|
+
node.comments = [...(node.comments ?? []), j.commentLine(` ${text}`, true, false)];
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let p = path;
|
|
92
|
+
while (p && !isStatement(p.node)) p = p.parent;
|
|
93
|
+
if (!p) return;
|
|
94
|
+
// An exported declaration carries the comment on the export, so it prints
|
|
95
|
+
// above `export`, not between `export` and the declaration.
|
|
96
|
+
if (p.parent && /^Export(Named|Default)Declaration$/.test(p.parent.node.type))
|
|
97
|
+
p = p.parent;
|
|
98
|
+
if (carries(p.node, text)) return;
|
|
99
|
+
p.node.comments = [
|
|
100
|
+
...(p.node.comments ?? []),
|
|
101
|
+
j.commentLine(` ${text}`, true, false),
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* `root.toSource(options)`, with the JSX-child markers `addTodo` left
|
|
107
|
+
* pending written in. Each marked element carries a sentinel block comment
|
|
108
|
+
* while it prints: a comment changes the element alone, so recast reprints
|
|
109
|
+
* neither the parent nor its text. The sentinel then becomes the marker:
|
|
110
|
+
* a `{/* … *\/}` line above an element that starts its line, or a
|
|
111
|
+
* `{/* … *\/}` right before one that follows other children on its line,
|
|
112
|
+
* with no whitespace added (the text renders as it did).
|
|
113
|
+
*
|
|
114
|
+
* @param {any} j jscodeshift
|
|
115
|
+
* @param {any} root the file's Collection
|
|
116
|
+
* @param {object} [options] recast print options
|
|
117
|
+
* @returns {string}
|
|
118
|
+
*/
|
|
119
|
+
export function printSource(j, root, options) {
|
|
120
|
+
/** @type {Map<string, string[]>} */
|
|
121
|
+
const markers = new Map();
|
|
122
|
+
for (const type of [j.JSXElement, j.JSXFragment]) {
|
|
123
|
+
root.find(type).forEach((/** @type {any} */ path) => {
|
|
124
|
+
const texts = path.node[PENDING];
|
|
125
|
+
if (!texts?.length) return;
|
|
126
|
+
const id = `${SENTINEL}${markers.size}__`;
|
|
127
|
+
markers.set(id, texts);
|
|
128
|
+
path.node.comments = [
|
|
129
|
+
...(path.node.comments ?? []),
|
|
130
|
+
j.commentBlock(id, true, false),
|
|
131
|
+
];
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
let out = root.toSource(options);
|
|
135
|
+
for (const [id, texts] of markers) {
|
|
136
|
+
const comment = `/*${id}*/`;
|
|
137
|
+
const at = out.indexOf(comment);
|
|
138
|
+
if (at === -1) throw new Error(`ui-common upgrade: lost the TODO marker ${id}`);
|
|
139
|
+
// Recast breaks the line after a leading comment; the element follows.
|
|
140
|
+
const after = at + comment.length;
|
|
141
|
+
const gap = /^\s*/.exec(out.slice(after))?.[0] ?? "";
|
|
142
|
+
const lineStart = out.lastIndexOf("\n", at - 1) + 1;
|
|
143
|
+
const indent = out.slice(lineStart, at);
|
|
144
|
+
const blocks = texts.map((text) => `{/* ${text} */}`);
|
|
145
|
+
const insert = /^[ \t]*$/.test(indent)
|
|
146
|
+
? blocks.map((b) => `${b}\n${indent}`).join("")
|
|
147
|
+
: blocks.join("");
|
|
148
|
+
out = out.slice(0, at) + insert + out.slice(after + gap.length);
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ui-common upgrade registry: codemod steps keyed by the ui-common version
|
|
3
|
+
* that introduced the change. `ui-common upgrade --from A --to B` runs every
|
|
4
|
+
* step with A < version <= B, in version order (semver precedence, so
|
|
5
|
+
* prerelease keys work: 0.1.0-alpha.19 < 0.2.0-alpha.0 < 0.2.0).
|
|
6
|
+
*
|
|
7
|
+
* Two kinds of step:
|
|
8
|
+
* - hand-written ones, listed in STATIC_STEPS (0.1 -> 0.2);
|
|
9
|
+
* - `codemods/<version>/upstream.json`, written by `ui-common sync-astryx`
|
|
10
|
+
* when ui-common moves to a new Astryx: the Astryx codemods a consumer needs
|
|
11
|
+
* for that bump, run through ./upstream.mjs.
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
14
|
+
import { dirname, join } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
|
|
17
|
+
import { compare, parse } from "../cli/semver.mjs";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {object} Transform
|
|
21
|
+
* @property {string} id
|
|
22
|
+
* @property {string} title
|
|
23
|
+
* @property {string[]} extensions
|
|
24
|
+
* @property {boolean} [parse] re-parse the output as a script before accepting it
|
|
25
|
+
* @property {(file: {path: string, source: string}, api: {jscodeshift: any}, ctx: any) => string | null | undefined} run
|
|
26
|
+
*
|
|
27
|
+
* @typedef {object} Step
|
|
28
|
+
* @property {string} title
|
|
29
|
+
* @property {Transform[]} transforms
|
|
30
|
+
* @property {(ctx: any, api: {jscodeshift: any}) => void} [afterTransforms] project-level edits once every file is transformed
|
|
31
|
+
* @property {(text: string, ctx: any) => string | undefined} [packageJson]
|
|
32
|
+
* @property {(files: Array<[string, string]>) => any} [prepareScan] a context for `scan`, from every file it will read ([relative path, content])
|
|
33
|
+
* @property {(file: string, source: string, context?: any) => Array<{category: string, file: string, line: number, text: string, detail?: string}>} [scan]
|
|
34
|
+
* @property {(ctx: any, rel: (file: string) => string) => Array<{category: string, file: string, line: number, text: string, detail?: string}>} [findings] report findings the transforms collected, after they ran
|
|
35
|
+
* @property {Record<string, {title: string, help: string}>} [categories]
|
|
36
|
+
* @property {string[] | ((ctx: any) => string[])} [notes] report notes, or a function of the run
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
export const CODEMODS_DIR = dirname(fileURLToPath(import.meta.url));
|
|
40
|
+
|
|
41
|
+
/** @type {Array<{version: string, load: () => Promise<Step>}>} */
|
|
42
|
+
const STATIC_STEPS = [
|
|
43
|
+
{
|
|
44
|
+
version: "0.2.0-alpha.0",
|
|
45
|
+
load: async () => (await import("./0.2/index.mjs")).default,
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
/** Upstream steps recorded by `sync-astryx`: `codemods/<version>/upstream.json`. */
|
|
50
|
+
export function upstreamManifests() {
|
|
51
|
+
if (!existsSync(CODEMODS_DIR)) return [];
|
|
52
|
+
return readdirSync(CODEMODS_DIR, { withFileTypes: true })
|
|
53
|
+
.filter((e) => e.isDirectory() && parse(e.name) && /^\d+\.\d+\.\d+/.test(e.name))
|
|
54
|
+
.map((e) => ({
|
|
55
|
+
version: e.name,
|
|
56
|
+
file: join(CODEMODS_DIR, e.name, "upstream.json"),
|
|
57
|
+
}))
|
|
58
|
+
.filter((m) => existsSync(m.file));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Every registered version, ascending. */
|
|
62
|
+
export function registeredVersions() {
|
|
63
|
+
const versions = new Set([
|
|
64
|
+
...STATIC_STEPS.map((s) => s.version),
|
|
65
|
+
...upstreamManifests().map((m) => m.version),
|
|
66
|
+
]);
|
|
67
|
+
return [...versions].sort(compare);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The steps to run for `from` (exclusive) to `to` (inclusive).
|
|
72
|
+
*
|
|
73
|
+
* @param {string} from
|
|
74
|
+
* @param {string} to
|
|
75
|
+
* @returns {Promise<Array<{version: string, step: Step}>>}
|
|
76
|
+
*/
|
|
77
|
+
export async function stepsBetween(from, to) {
|
|
78
|
+
/** @type {Array<{version: string, step: Step}>} */
|
|
79
|
+
const out = [];
|
|
80
|
+
for (const version of registeredVersions()) {
|
|
81
|
+
if (compare(version, from) <= 0 || compare(version, to) > 0) continue;
|
|
82
|
+
for (const s of STATIC_STEPS.filter((x) => x.version === version)) {
|
|
83
|
+
out.push({ version, step: await s.load() });
|
|
84
|
+
}
|
|
85
|
+
const upstream = upstreamManifests().find((m) => m.version === version);
|
|
86
|
+
if (upstream) {
|
|
87
|
+
const { upstreamStep } = await import("./upstream.mjs");
|
|
88
|
+
out.push({
|
|
89
|
+
version,
|
|
90
|
+
step: await upstreamStep(JSON.parse(readFileSync(upstream.file, "utf8"))),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run Astryx's own codemods on a ui-common consumer.
|
|
3
|
+
*
|
|
4
|
+
* Astryx codemods match `@astryxdesign/*` import specifiers; a ui-common
|
|
5
|
+
* consumer imports the same modules as `@lablup/ui-common/*`. So each
|
|
6
|
+
* transform sees the file with the module specifiers swapped to Astryx's, and
|
|
7
|
+
* its output's module specifiers are swapped back. Only specifiers are
|
|
8
|
+
* swapped (imports, re-exports, `import()`, `require()`): a comment or string
|
|
9
|
+
* that names either package is left as written. A file the transform does not
|
|
10
|
+
* change is left alone.
|
|
11
|
+
*
|
|
12
|
+
* `codemods/<ui-common version>/upstream.json` (written by
|
|
13
|
+
* `ui-common sync-astryx`) names the Astryx version range and codemod ids.
|
|
14
|
+
*/
|
|
15
|
+
import { importAstryxInternal } from "../cli/paths.mjs";
|
|
16
|
+
import { rewriteSpecifiers } from "../cli/rewrite.mjs";
|
|
17
|
+
|
|
18
|
+
const DEFAULT_EXTENSIONS = [".tsx", ".ts", ".jsx", ".js", ".mjs", ".cjs"];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* `@lablup/ui-common/…` -> the `@astryxdesign/…` specifier Astryx's codemods
|
|
22
|
+
* look for. The inverse is rewriteSpecifiers.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} text
|
|
25
|
+
*/
|
|
26
|
+
export function toAstryxSpecifiers(text) {
|
|
27
|
+
return text
|
|
28
|
+
.replace(/@lablup\/ui-common\/lab(?=[/"'`\s),;:]|$)/g, "@astryxdesign/lab")
|
|
29
|
+
.replace(
|
|
30
|
+
/@lablup\/ui-common\/theme\/neutral(?=[/"'`\s),;:]|$)/g,
|
|
31
|
+
"@astryxdesign/theme-neutral",
|
|
32
|
+
)
|
|
33
|
+
.replace(/@lablup\/ui-common(?=[/"'`\s),;:]|$)/g, "@astryxdesign/core");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Apply `swap` to the module specifier strings of a script and nothing else.
|
|
38
|
+
* A source jscodeshift cannot parse (a stylesheet an Astryx codemod targets)
|
|
39
|
+
* is swapped as a whole.
|
|
40
|
+
*
|
|
41
|
+
* @param {any} j
|
|
42
|
+
* @param {string} source
|
|
43
|
+
* @param {(text: string) => string} swap
|
|
44
|
+
*/
|
|
45
|
+
export function swapModuleSpecifiers(j, source, swap) {
|
|
46
|
+
let root;
|
|
47
|
+
try {
|
|
48
|
+
root = j(source);
|
|
49
|
+
} catch {
|
|
50
|
+
return swap(source);
|
|
51
|
+
}
|
|
52
|
+
/** @type {Array<{start: number, end: number}>} */
|
|
53
|
+
const ranges = [];
|
|
54
|
+
const add = (/** @type {any} */ node) => {
|
|
55
|
+
const isString =
|
|
56
|
+
node?.type === "StringLiteral" ||
|
|
57
|
+
(node?.type === "Literal" && typeof node.value === "string");
|
|
58
|
+
if (isString && typeof node.start === "number" && typeof node.end === "number")
|
|
59
|
+
ranges.push({ start: node.start, end: node.end });
|
|
60
|
+
};
|
|
61
|
+
root.find(j.ImportDeclaration).forEach((/** @type {any} */ p) => add(p.node.source));
|
|
62
|
+
root
|
|
63
|
+
.find(j.ExportNamedDeclaration)
|
|
64
|
+
.forEach((/** @type {any} */ p) => add(p.node.source));
|
|
65
|
+
root
|
|
66
|
+
.find(j.ExportAllDeclaration)
|
|
67
|
+
.forEach((/** @type {any} */ p) => add(p.node.source));
|
|
68
|
+
root.find(j.CallExpression).forEach((/** @type {any} */ p) => {
|
|
69
|
+
const callee = p.node.callee;
|
|
70
|
+
const isImport = callee.type === "Import";
|
|
71
|
+
const isRequire = callee.type === "Identifier" && callee.name === "require";
|
|
72
|
+
if (isImport || isRequire) add(p.node.arguments[0]);
|
|
73
|
+
});
|
|
74
|
+
if (j.ImportExpression)
|
|
75
|
+
root.find(j.ImportExpression).forEach((/** @type {any} */ p) => add(p.node.source));
|
|
76
|
+
if (j.TSImportType)
|
|
77
|
+
root.find(j.TSImportType).forEach((/** @type {any} */ p) => {
|
|
78
|
+
add(p.node.argument?.literal ?? p.node.argument);
|
|
79
|
+
});
|
|
80
|
+
let out = source;
|
|
81
|
+
for (const { start, end } of ranges.sort((a, b) => b.start - a.start)) {
|
|
82
|
+
out = `${out.slice(0, start)}${swap(out.slice(start, end))}${out.slice(end)}`;
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @typedef {{astryx: {from: string, to: string}, codemods: Array<{id: string, version: string, title?: string}>}} UpstreamManifest
|
|
89
|
+
*/
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {{name: string, transform: Function, meta: {title: string, fileExtensions?: string[], codemodType?: string}}} entry
|
|
93
|
+
* @param {string} version Astryx version the codemod belongs to
|
|
94
|
+
* @returns {import('./registry.mjs').Transform}
|
|
95
|
+
*/
|
|
96
|
+
export function wrapAstryxTransform(entry, version) {
|
|
97
|
+
const extensions = entry.meta.fileExtensions ?? DEFAULT_EXTENSIONS;
|
|
98
|
+
return {
|
|
99
|
+
id: `astryx:${entry.name}`,
|
|
100
|
+
title: `Astryx ${version}: ${entry.meta.title}`,
|
|
101
|
+
extensions,
|
|
102
|
+
parse: extensions.some((e) => DEFAULT_EXTENSIONS.includes(e)),
|
|
103
|
+
run(file, api) {
|
|
104
|
+
const j = api.jscodeshift;
|
|
105
|
+
const swapped = swapModuleSpecifiers(j, file.source, toAstryxSpecifiers);
|
|
106
|
+
const out = entry.transform(
|
|
107
|
+
{ path: file.path, source: swapped },
|
|
108
|
+
{ jscodeshift: j, stats: () => {}, report: () => {} },
|
|
109
|
+
);
|
|
110
|
+
if (out == null || out === swapped) return undefined;
|
|
111
|
+
return swapModuleSpecifiers(j, out, rewriteSpecifiers);
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @param {UpstreamManifest} manifest
|
|
118
|
+
* @returns {Promise<import('./registry.mjs').Step>}
|
|
119
|
+
*/
|
|
120
|
+
export async function upstreamStep(manifest) {
|
|
121
|
+
const { getTransformsBetween } = await importAstryxInternal(
|
|
122
|
+
"assets/codemods/registry.mjs",
|
|
123
|
+
);
|
|
124
|
+
const groups = await getTransformsBetween(manifest.astryx.from, manifest.astryx.to);
|
|
125
|
+
/** @type {Map<string, {entry: any, version: string}>} */
|
|
126
|
+
const byName = new Map();
|
|
127
|
+
for (const group of groups) {
|
|
128
|
+
for (const entry of group.transforms)
|
|
129
|
+
byName.set(entry.name, { entry, version: group.version });
|
|
130
|
+
}
|
|
131
|
+
const notes = [];
|
|
132
|
+
/** @type {import('./registry.mjs').Transform[]} */
|
|
133
|
+
const transforms = [];
|
|
134
|
+
for (const codemod of manifest.codemods) {
|
|
135
|
+
const found = byName.get(codemod.id);
|
|
136
|
+
if (!found) {
|
|
137
|
+
notes.push(
|
|
138
|
+
`Astryx codemod "${codemod.id}" (${codemod.version}) is not in the installed @astryxdesign/cli; run it by hand if you use what it migrates.`,
|
|
139
|
+
);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (found.entry.meta?.codemodType === "config") {
|
|
143
|
+
notes.push(
|
|
144
|
+
`Astryx codemod "${codemod.id}" edits astryx.config.*; run \`ui-common astryx upgrade\` if you keep one.`,
|
|
145
|
+
);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
transforms.push(wrapAstryxTransform(found.entry, found.version));
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
title: `Astryx ${manifest.astryx.from} → ${manifest.astryx.to} codemods`,
|
|
152
|
+
transforms,
|
|
153
|
+
notes,
|
|
154
|
+
};
|
|
155
|
+
}
|