@assure-one/design-system 1.30.0 → 1.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -1
- package/codemods/0.2.0-radix-migration.mjs +315 -0
- package/codemods/README.md +305 -0
- package/codemods/lib/css-selectors.mjs +33 -0
- package/codemods/lib/css-values.mjs +223 -0
- package/codemods/lib/ds-stylesheet.mjs +168 -0
- package/codemods/lib/environment.mjs +72 -0
- package/codemods/lib/files.mjs +100 -0
- package/codemods/lib/jsx.mjs +0 -0
- package/codemods/lib/ledger.mjs +84 -0
- package/codemods/lib/registry.mjs +30 -0
- package/codemods/lib/report.mjs +119 -0
- package/codemods/lib/runner.mjs +164 -0
- package/codemods/run.mjs +161 -0
- package/codemods/transforms/cm-15-dom-selectors.mjs +573 -0
- package/codemods/transforms/cm-16-globals-css.mjs +487 -0
- package/dist/css/base.css +60 -0
- package/dist/css/legacy-aliases.css +489 -0
- package/dist/css/shadcn.css +155 -0
- package/dist/css/tailwind.css +233 -0
- package/dist/css/tokens.css +439 -0
- package/dist/index.d.ts +327 -33
- package/dist/index.js +1843 -706
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/testing/index.cjs +458 -0
- package/dist/testing/index.d.cts +253 -0
- package/dist/testing/index.d.ts +253 -0
- package/dist/testing/index.js +452 -0
- package/dist/testing/setup.cjs +123 -0
- package/dist/testing/setup.js +121 -0
- package/dist/testing/style-stub.cjs +7 -0
- package/dist/testing/style-stub.js +5 -0
- package/dist/tokens/index.d.ts +114 -96
- package/dist/tokens/index.js +67 -48
- package/dist/tokens/index.js.map +1 -1
- package/package.json +84 -14
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The codemod program (plan §29) in the order `upgrade` applies it. Only
|
|
3
|
+
* codemods listed here can be run by id; the sequence is the plan's.
|
|
4
|
+
*/
|
|
5
|
+
export const CODEMODS = [
|
|
6
|
+
{ id: "CM-15", module: "../transforms/cm-15-dom-selectors.mjs" },
|
|
7
|
+
{ id: "CM-16", module: "../transforms/cm-16-globals-css.mjs" },
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
export const findCodemod = (id) =>
|
|
11
|
+
CODEMODS.find((c) => c.id.toLowerCase() === String(id).toLowerCase()) ?? null;
|
|
12
|
+
|
|
13
|
+
export async function loadCodemod(id) {
|
|
14
|
+
const entry = findCodemod(id);
|
|
15
|
+
if (!entry) return null;
|
|
16
|
+
const mod = await import(new URL(entry.module, import.meta.url).href);
|
|
17
|
+
if (!mod.meta || typeof mod.transform !== "function") {
|
|
18
|
+
throw new Error(`${entry.id} does not export \`meta\` and \`transform\``);
|
|
19
|
+
}
|
|
20
|
+
if (mod.meta.id !== entry.id) {
|
|
21
|
+
throw new Error(`${entry.id} exports meta.id ${mod.meta.id}`);
|
|
22
|
+
}
|
|
23
|
+
return mod;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function loadAll() {
|
|
27
|
+
const out = [];
|
|
28
|
+
for (const { id } of CODEMODS) out.push(await loadCodemod(id));
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Report rendering. Every codemod produces the same JSON report (the machine
|
|
3
|
+
* form, `runner.mjs`) and the same Markdown rendering of it: what was found,
|
|
4
|
+
* what was changed and — the `--dry` requirement of plan §29 — what could not
|
|
5
|
+
* be transformed.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const bullet = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
|
|
9
|
+
|
|
10
|
+
export function renderMarkdown(report) {
|
|
11
|
+
const { codemod, summary } = report;
|
|
12
|
+
const lines = [];
|
|
13
|
+
lines.push(`# ${codemod.id} — ${codemod.title}`);
|
|
14
|
+
lines.push("");
|
|
15
|
+
lines.push(
|
|
16
|
+
`Class ${codemod.class}${codemod.class === "X" ? " (report only; no file is modified)" : ""} · ` +
|
|
17
|
+
`design system ${report.dsVersion} · ${report.dry ? "dry run" : "applied"} · ` +
|
|
18
|
+
`${report.paths.join(", ")} in ${report.root}`,
|
|
19
|
+
);
|
|
20
|
+
lines.push("");
|
|
21
|
+
lines.push("| Measure | Count |");
|
|
22
|
+
lines.push("| --- | ---: |");
|
|
23
|
+
lines.push(`| Files scanned | ${summary.filesScanned} |`);
|
|
24
|
+
lines.push(`| Findings | ${summary.findings} |`);
|
|
25
|
+
lines.push(`| … mapped to a registry contract | ${summary.registered} |`);
|
|
26
|
+
lines.push(`| … not in the registry yet | ${summary.unregistered} |`);
|
|
27
|
+
lines.push(`| Files ${report.dry ? "that would change" : "changed"} | ${summary.filesChanged} |`);
|
|
28
|
+
lines.push(`| Could not be transformed | ${summary.notTransformed} |`);
|
|
29
|
+
lines.push(`| Files with parse errors | ${summary.parseErrors} |`);
|
|
30
|
+
lines.push("");
|
|
31
|
+
|
|
32
|
+
if (report.warnings.length) {
|
|
33
|
+
lines.push("## Warnings", "");
|
|
34
|
+
for (const w of report.warnings) lines.push(`- ${w}`);
|
|
35
|
+
lines.push("");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (Object.keys(summary.byRegistryId).length) {
|
|
39
|
+
lines.push("## Findings by contract", "");
|
|
40
|
+
lines.push("| Contract | Findings | Files |");
|
|
41
|
+
lines.push("| --- | ---: | ---: |");
|
|
42
|
+
for (const [id, count] of Object.entries(summary.byRegistryId)) {
|
|
43
|
+
const files = new Set(
|
|
44
|
+
report.findings.filter((f) => (f.registryId ?? "unregistered") === id).map((f) => f.file),
|
|
45
|
+
);
|
|
46
|
+
lines.push(`| ${id} | ${count} | ${files.size} |`);
|
|
47
|
+
}
|
|
48
|
+
lines.push("");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (report.findings.length) {
|
|
52
|
+
lines.push("## Findings", "");
|
|
53
|
+
const groups = new Map();
|
|
54
|
+
for (const f of report.findings) {
|
|
55
|
+
const key = f.registryId ?? "unregistered";
|
|
56
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
57
|
+
groups.get(key).push(f);
|
|
58
|
+
}
|
|
59
|
+
for (const [key, items] of [...groups].sort(([a], [b]) => (a < b ? -1 : 1))) {
|
|
60
|
+
lines.push(`### ${key} — ${bullet(items.length, "finding")}`, "");
|
|
61
|
+
for (const f of items) {
|
|
62
|
+
const where = `${f.file}:${f.line ?? "?"}`;
|
|
63
|
+
const what = [
|
|
64
|
+
f.component,
|
|
65
|
+
f.rule,
|
|
66
|
+
f.severity && `severity ${f.severity}`,
|
|
67
|
+
f.gate && `gate ${f.gate}`,
|
|
68
|
+
f.confidence && `confidence ${f.confidence}`,
|
|
69
|
+
]
|
|
70
|
+
.filter(Boolean)
|
|
71
|
+
.join(" · ");
|
|
72
|
+
lines.push(`- \`${where}\` — ${what}`);
|
|
73
|
+
if (f.match) lines.push(` - \`${f.match.replace(/`/g, "'")}\``);
|
|
74
|
+
}
|
|
75
|
+
lines.push("");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (report.notTransformed.length) {
|
|
80
|
+
lines.push("## Could not be transformed", "");
|
|
81
|
+
for (const n of report.notTransformed) {
|
|
82
|
+
lines.push(
|
|
83
|
+
`- \`${n.file}${n.line ? `:${n.line}` : ""}\` — ${n.reason}${n.detail ? `: ${n.detail}` : ""}`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
lines.push("");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (report.parseErrors.length) {
|
|
90
|
+
lines.push("## Parse errors", "");
|
|
91
|
+
for (const e of report.parseErrors.slice(0, 50)) {
|
|
92
|
+
lines.push(`- \`${e.file}:${e.line ?? "?"}\` — ${e.message}`);
|
|
93
|
+
}
|
|
94
|
+
if (report.parseErrors.length > 50) {
|
|
95
|
+
lines.push(`- … and ${report.parseErrors.length - 50} more`);
|
|
96
|
+
}
|
|
97
|
+
lines.push("");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (report.changed.length) {
|
|
101
|
+
lines.push(`## Files ${report.dry ? "that would change" : "changed"}`, "");
|
|
102
|
+
for (const rel of report.changed) lines.push(`- \`${rel}\``);
|
|
103
|
+
lines.push("");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return `${lines.join("\n")}\n`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** One line per run, for the terminal. */
|
|
110
|
+
export function renderSummary(report) {
|
|
111
|
+
const s = report.summary;
|
|
112
|
+
return (
|
|
113
|
+
`${report.codemod.id}: ${s.findings} finding(s) in ${s.filesScanned} file(s) — ` +
|
|
114
|
+
`${s.registered} mapped to a contract, ${s.unregistered} unregistered; ` +
|
|
115
|
+
`${report.dry ? "would change" : "changed"} ${s.filesChanged} file(s)` +
|
|
116
|
+
(s.notTransformed ? `; ${s.notTransformed} could not be transformed` : "") +
|
|
117
|
+
(s.parseErrors ? `; ${s.parseErrors} parse error(s)` : "")
|
|
118
|
+
);
|
|
119
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The codemod runner: guards, file walk, transform, writes, ledger, report.
|
|
3
|
+
*
|
|
4
|
+
* One run = one codemod over one project. The runner owns everything the
|
|
5
|
+
* individual codemods must not repeat: the one-shot and prerequisite guards,
|
|
6
|
+
* which files are read, that class X never writes, and the report.
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
|
|
11
|
+
import { classifyFile, collectFiles } from "./files.mjs";
|
|
12
|
+
import {
|
|
13
|
+
compareVersions,
|
|
14
|
+
installedDsVersion,
|
|
15
|
+
loadPostcss,
|
|
16
|
+
loadTypeScript,
|
|
17
|
+
} from "./environment.mjs";
|
|
18
|
+
import { appliedIds, guardProblems, readLedger, recordApplied } from "./ledger.mjs";
|
|
19
|
+
import { loadCodemod } from "./registry.mjs";
|
|
20
|
+
|
|
21
|
+
export const REPORT_SCHEMA = 1;
|
|
22
|
+
|
|
23
|
+
export class Refused extends Error {
|
|
24
|
+
constructor(problems) {
|
|
25
|
+
super(problems.join("\n"));
|
|
26
|
+
this.name = "Refused";
|
|
27
|
+
this.problems = problems;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Runs one codemod.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} id codemod id, e.g. "CM-15"
|
|
35
|
+
* @param {object} options
|
|
36
|
+
* @param {string} options.root project root (holds package.json and the ledger)
|
|
37
|
+
* @param {string[]} [options.paths] files or directories (default: the root)
|
|
38
|
+
* @param {boolean} [options.dry] report only; never write files or the ledger
|
|
39
|
+
* @param {() => Date} [options.now] clock, for tests
|
|
40
|
+
* @returns {Promise<object>} the report
|
|
41
|
+
*/
|
|
42
|
+
export async function runCodemod(
|
|
43
|
+
id,
|
|
44
|
+
{ root, paths = [], dry = false, now = () => new Date() } = {},
|
|
45
|
+
) {
|
|
46
|
+
const mod = await loadCodemod(id);
|
|
47
|
+
if (!mod) throw new Refused([`Unknown codemod ${id}. \`run.mjs list\` shows the available ids.`]);
|
|
48
|
+
const meta = mod.meta;
|
|
49
|
+
const dsVersion = installedDsVersion();
|
|
50
|
+
const ledger = readLedger(root);
|
|
51
|
+
const problems = guardProblems(meta, ledger, dsVersion, compareVersions);
|
|
52
|
+
// A dry run may preview an already-applied one-shot codemod; missing
|
|
53
|
+
// prerequisites still refuse, because the preview would be wrong.
|
|
54
|
+
const blocking = dry ? problems.filter((p) => !p.includes("already applied")) : problems;
|
|
55
|
+
if (blocking.length) throw new Refused(blocking);
|
|
56
|
+
|
|
57
|
+
const ts = meta.usesTypeScript ? await loadTypeScript(root) : null;
|
|
58
|
+
const postcss = meta.usesPostcss ? await loadPostcss(root) : null;
|
|
59
|
+
const kinds = new Set(meta.parses ?? ["code"]);
|
|
60
|
+
const files = collectFiles(root, paths).filter((rel) => {
|
|
61
|
+
const { kind, test } = classifyFile(rel);
|
|
62
|
+
return kinds.has(kind) && (meta.includeTests || !test);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const findings = [];
|
|
66
|
+
const notTransformed = [];
|
|
67
|
+
const parseErrors = [];
|
|
68
|
+
const changed = [];
|
|
69
|
+
let scanned = 0;
|
|
70
|
+
|
|
71
|
+
for (const rel of files) {
|
|
72
|
+
const abs = join(root, rel);
|
|
73
|
+
const source = readFileSync(abs, "utf8");
|
|
74
|
+
const { kind, test } = classifyFile(rel);
|
|
75
|
+
const file = { rel, path: abs, source, kind, test };
|
|
76
|
+
let result;
|
|
77
|
+
try {
|
|
78
|
+
result = mod.transform(file, { ts, postcss, meta, root }) ?? {};
|
|
79
|
+
} catch (error) {
|
|
80
|
+
notTransformed.push({
|
|
81
|
+
file: rel,
|
|
82
|
+
line: null,
|
|
83
|
+
reason: "error",
|
|
84
|
+
detail: String(error.message ?? error),
|
|
85
|
+
});
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
scanned += 1;
|
|
89
|
+
const list = Array.isArray(result) ? result : (result.findings ?? []);
|
|
90
|
+
for (const f of list) findings.push({ file: rel, test, ...f });
|
|
91
|
+
for (const n of result.notTransformed ?? []) notTransformed.push({ file: rel, ...n });
|
|
92
|
+
for (const e of result.parseErrors ?? []) parseErrors.push({ file: rel, ...e });
|
|
93
|
+
const output = Array.isArray(result) ? undefined : result.output;
|
|
94
|
+
if (output !== undefined && output !== source) {
|
|
95
|
+
if (meta.class === "X") {
|
|
96
|
+
throw new Error(`${meta.id} is report-only (class X) but returned a changed ${rel}`);
|
|
97
|
+
}
|
|
98
|
+
changed.push(rel);
|
|
99
|
+
if (!dry) writeFileSync(abs, output);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
findings.sort(
|
|
104
|
+
(a, b) =>
|
|
105
|
+
a.file.localeCompare(b.file) ||
|
|
106
|
+
(a.line ?? 0) - (b.line ?? 0) ||
|
|
107
|
+
a.rule.localeCompare(b.rule) ||
|
|
108
|
+
String(a.match).localeCompare(String(b.match)),
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const applied = meta.class !== "X" && !dry && changed.length > 0;
|
|
112
|
+
const appliedAt = now().toISOString();
|
|
113
|
+
if (applied)
|
|
114
|
+
recordApplied(root, { id: meta.id, appliedAt, dsVersion, filesChanged: changed.length });
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
schema: REPORT_SCHEMA,
|
|
118
|
+
codemod: { id: meta.id, title: meta.title, class: meta.class, oneShot: Boolean(meta.oneShot) },
|
|
119
|
+
dsVersion,
|
|
120
|
+
root,
|
|
121
|
+
paths: paths.length ? paths : ["."],
|
|
122
|
+
dry,
|
|
123
|
+
generatedAt: appliedAt,
|
|
124
|
+
ledger: {
|
|
125
|
+
file: ".ds-migrations.json",
|
|
126
|
+
applied: [...appliedIds(ledger)].sort(),
|
|
127
|
+
recorded: applied,
|
|
128
|
+
},
|
|
129
|
+
summary: {
|
|
130
|
+
filesScanned: scanned,
|
|
131
|
+
filesChanged: changed.length,
|
|
132
|
+
findings: findings.length,
|
|
133
|
+
registered: findings.filter((f) => f.registryId).length,
|
|
134
|
+
unregistered: findings.filter((f) => !f.registryId).length,
|
|
135
|
+
notTransformed: notTransformed.length,
|
|
136
|
+
parseErrors: parseErrors.length,
|
|
137
|
+
byRegistryId: countBy(findings, (f) => f.registryId ?? "unregistered"),
|
|
138
|
+
byRule: countBy(findings, (f) => f.rule),
|
|
139
|
+
},
|
|
140
|
+
changed,
|
|
141
|
+
findings,
|
|
142
|
+
notTransformed,
|
|
143
|
+
parseErrors,
|
|
144
|
+
warnings: dry ? problems.filter((p) => p.includes("already applied")) : [],
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const countBy = (items, key) => {
|
|
149
|
+
const map = new Map();
|
|
150
|
+
for (const item of items) map.set(key(item), (map.get(key(item)) ?? 0) + 1);
|
|
151
|
+
return Object.fromEntries([...map].sort(([a], [b]) => (a < b ? -1 : 1)));
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/** Codemods a project has not applied yet, in program order. */
|
|
155
|
+
export async function pendingCodemods(root) {
|
|
156
|
+
const { CODEMODS } = await import("./registry.mjs");
|
|
157
|
+
const ids = appliedIds(readLedger(root));
|
|
158
|
+
const out = [];
|
|
159
|
+
for (const { id } of CODEMODS) {
|
|
160
|
+
const mod = await loadCodemod(id);
|
|
161
|
+
out.push({ ...mod.meta, applied: ids.has(id) });
|
|
162
|
+
}
|
|
163
|
+
return out.filter((m) => m.class !== "X" && !m.applied);
|
|
164
|
+
}
|
package/codemods/run.mjs
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Codemod runner for @assure-one/design-system (plan §29).
|
|
4
|
+
*
|
|
5
|
+
* From a consumer project:
|
|
6
|
+
*
|
|
7
|
+
* node node_modules/@assure-one/design-system/codemods/run.mjs list
|
|
8
|
+
* node node_modules/@assure-one/design-system/codemods/run.mjs CM-15 src --dry
|
|
9
|
+
* node node_modules/@assure-one/design-system/codemods/run.mjs upgrade --dry
|
|
10
|
+
*
|
|
11
|
+
* Options
|
|
12
|
+
* --dry report only; write neither files nor the ledger
|
|
13
|
+
* --root <dir> project root (default: nearest package.json above cwd)
|
|
14
|
+
* --report <prefix> write <prefix>.json and <prefix>.md
|
|
15
|
+
* --json <file> write the JSON report
|
|
16
|
+
* --md <file> write the Markdown report
|
|
17
|
+
* --print print the full Markdown report to stdout
|
|
18
|
+
* --quiet print nothing but errors
|
|
19
|
+
* --fail-on-findings exit 1 when a finding maps to a registry contract
|
|
20
|
+
*
|
|
21
|
+
* Exit codes: 0 done · 1 findings with --fail-on-findings · 2 usage error
|
|
22
|
+
* · 3 refused by a ledger or version guard.
|
|
23
|
+
*/
|
|
24
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
25
|
+
import { dirname, resolve } from "node:path";
|
|
26
|
+
|
|
27
|
+
import { findProjectRoot } from "./lib/files.mjs";
|
|
28
|
+
import { installedDsVersion } from "./lib/environment.mjs";
|
|
29
|
+
import { renderMarkdown, renderSummary } from "./lib/report.mjs";
|
|
30
|
+
import { CODEMODS, loadAll } from "./lib/registry.mjs";
|
|
31
|
+
import { Refused, pendingCodemods, runCodemod } from "./lib/runner.mjs";
|
|
32
|
+
|
|
33
|
+
const FLAGS = new Set(["--dry", "--print", "--quiet", "--fail-on-findings", "--help", "-h"]);
|
|
34
|
+
const VALUES = new Set(["--root", "--report", "--json", "--md"]);
|
|
35
|
+
|
|
36
|
+
function parseArgs(argv) {
|
|
37
|
+
const options = {};
|
|
38
|
+
const positional = [];
|
|
39
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
40
|
+
const arg = argv[i];
|
|
41
|
+
if (FLAGS.has(arg)) options[arg.replace(/^--?/, "")] = true;
|
|
42
|
+
else if (VALUES.has(arg)) {
|
|
43
|
+
const value = argv[i + 1];
|
|
44
|
+
if (!value) throw new UsageError(`${arg} needs a value`);
|
|
45
|
+
options[arg.slice(2)] = value;
|
|
46
|
+
i += 1;
|
|
47
|
+
} else if (arg.startsWith("-")) throw new UsageError(`Unknown option ${arg}`);
|
|
48
|
+
else positional.push(arg);
|
|
49
|
+
}
|
|
50
|
+
return { options, positional };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
class UsageError extends Error {}
|
|
54
|
+
|
|
55
|
+
const usage = () => `Usage:
|
|
56
|
+
run.mjs list
|
|
57
|
+
run.mjs <CM-ID> [paths…] [--dry] [--root dir] [--report prefix] [--print] [--fail-on-findings]
|
|
58
|
+
run.mjs upgrade [paths…] [--dry]
|
|
59
|
+
|
|
60
|
+
Codemods: ${CODEMODS.map((c) => c.id).join(", ")} (design system ${installedDsVersion()})
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
function writeReport(report, options) {
|
|
64
|
+
const targets = [];
|
|
65
|
+
if (options.report)
|
|
66
|
+
targets.push([`${options.report}.json`, "json"], [`${options.report}.md`, "md"]);
|
|
67
|
+
if (options.json) targets.push([options.json, "json"]);
|
|
68
|
+
if (options.md) targets.push([options.md, "md"]);
|
|
69
|
+
for (const [file, format] of targets) {
|
|
70
|
+
const path = resolve(file);
|
|
71
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
72
|
+
writeFileSync(
|
|
73
|
+
path,
|
|
74
|
+
format === "json" ? `${JSON.stringify(report, null, 2)}\n` : renderMarkdown(report),
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
return targets.map(([file]) => resolve(file));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function main(argv) {
|
|
81
|
+
const { options, positional } = parseArgs(argv);
|
|
82
|
+
if (options.help || options.h || !positional.length) {
|
|
83
|
+
process.stdout.write(usage());
|
|
84
|
+
return positional.length ? 0 : 2;
|
|
85
|
+
}
|
|
86
|
+
const say = (text) => {
|
|
87
|
+
if (!options.quiet) process.stdout.write(`${text}\n`);
|
|
88
|
+
};
|
|
89
|
+
const command = positional[0];
|
|
90
|
+
const root = findProjectRoot(options.root ? resolve(options.root) : process.cwd());
|
|
91
|
+
|
|
92
|
+
if (command === "list") {
|
|
93
|
+
const all = await loadAll();
|
|
94
|
+
say(`Codemods in @assure-one/design-system ${installedDsVersion()}:`);
|
|
95
|
+
for (const { meta } of all) {
|
|
96
|
+
const guards = [
|
|
97
|
+
meta.oneShot ? "one-shot" : null,
|
|
98
|
+
meta.requires?.codemods?.length ? `after ${meta.requires.codemods.join(", ")}` : null,
|
|
99
|
+
meta.requires?.dsVersion ? `needs ≥ ${meta.requires.dsVersion}` : null,
|
|
100
|
+
].filter(Boolean);
|
|
101
|
+
say(
|
|
102
|
+
` ${meta.id} [${meta.class}] ${meta.title}${guards.length ? ` (${guards.join("; ")})` : ""}`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
const pending = await pendingCodemods(root);
|
|
106
|
+
say(
|
|
107
|
+
pending.length
|
|
108
|
+
? `Not yet applied in ${root}: ${pending.map((m) => m.id).join(", ")}`
|
|
109
|
+
: `Nothing to apply in ${root}.`,
|
|
110
|
+
);
|
|
111
|
+
return 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (command === "upgrade") {
|
|
115
|
+
const pending = await pendingCodemods(root);
|
|
116
|
+
if (!pending.length) {
|
|
117
|
+
say(`Nothing to apply in ${root}; the ledger is up to date.`);
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
let code = 0;
|
|
121
|
+
for (const meta of pending) {
|
|
122
|
+
const report = await runCodemod(meta.id, {
|
|
123
|
+
root,
|
|
124
|
+
paths: positional.slice(1).map((p) => resolve(p)),
|
|
125
|
+
dry: Boolean(options.dry),
|
|
126
|
+
});
|
|
127
|
+
say(renderSummary(report));
|
|
128
|
+
if (options.print) process.stdout.write(renderMarkdown(report));
|
|
129
|
+
for (const path of writeReport(report, options)) say(`Report: ${path}`);
|
|
130
|
+
if (options["fail-on-findings"] && report.summary.registered) code = 1;
|
|
131
|
+
}
|
|
132
|
+
return code;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const report = await runCodemod(command, {
|
|
136
|
+
root,
|
|
137
|
+
paths: positional.slice(1).map((p) => resolve(p)),
|
|
138
|
+
dry: Boolean(options.dry),
|
|
139
|
+
});
|
|
140
|
+
if (options.print) process.stdout.write(renderMarkdown(report));
|
|
141
|
+
say(renderSummary(report));
|
|
142
|
+
for (const warning of report.warnings) say(`! ${warning}`);
|
|
143
|
+
for (const path of writeReport(report, options)) say(`Report: ${path}`);
|
|
144
|
+
if (report.ledger.recorded) say(`Recorded ${report.codemod.id} in ${root}/${report.ledger.file}`);
|
|
145
|
+
return options["fail-on-findings"] && report.summary.registered ? 1 : 0;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
process.exitCode = await main(process.argv.slice(2));
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (error instanceof Refused) {
|
|
152
|
+
process.stderr.write(`Refused:\n${error.problems.map((p) => ` - ${p}`).join("\n")}\n`);
|
|
153
|
+
process.exitCode = 3;
|
|
154
|
+
} else if (error instanceof UsageError) {
|
|
155
|
+
process.stderr.write(`${error.message}\n\n${usage()}`);
|
|
156
|
+
process.exitCode = 2;
|
|
157
|
+
} else {
|
|
158
|
+
process.stderr.write(`${error?.stack ?? error}\n`);
|
|
159
|
+
process.exitCode = 1;
|
|
160
|
+
}
|
|
161
|
+
}
|