readyup 0.0.0 → 0.13.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/LICENSE +21 -0
- package/README.md +123 -0
- package/bin/rdy.js +11 -0
- package/dist/esm/.cache +1 -0
- package/dist/esm/assertIsPreflightCollection.d.ts +2 -0
- package/dist/esm/assertIsPreflightCollection.js +27 -0
- package/dist/esm/assertIsRdyKit.d.ts +2 -0
- package/dist/esm/assertIsRdyKit.js +27 -0
- package/dist/esm/authoring.d.ts +6 -0
- package/dist/esm/authoring.js +22 -0
- package/dist/esm/bin/preflight.d.ts +1 -0
- package/dist/esm/bin/preflight.js +12 -0
- package/dist/esm/bin/rdy.d.ts +1 -0
- package/dist/esm/bin/rdy.js +12 -0
- package/dist/esm/bin/route.d.ts +1 -0
- package/dist/esm/bin/route.js +202 -0
- package/dist/esm/check-utils/filesystem.d.ts +4 -0
- package/dist/esm/check-utils/filesystem.js +26 -0
- package/dist/esm/check-utils/index.d.ts +3 -0
- package/dist/esm/check-utils/index.js +14 -0
- package/dist/esm/check-utils/package-json.d.ts +6 -0
- package/dist/esm/check-utils/package-json.js +40 -0
- package/dist/esm/check-utils/semver.d.ts +1 -0
- package/dist/esm/check-utils/semver.js +12 -0
- package/dist/esm/cli.d.ts +36 -0
- package/dist/esm/cli.js +285 -0
- package/dist/esm/compile/compileCommand.d.ts +1 -0
- package/dist/esm/compile/compileCommand.js +121 -0
- package/dist/esm/compile/compileConfig.d.ts +5 -0
- package/dist/esm/compile/compileConfig.js +52 -0
- package/dist/esm/compile/validateCompiledOutput.d.ts +1 -0
- package/dist/esm/compile/validateCompiledOutput.js +29 -0
- package/dist/esm/config.d.ts +2 -0
- package/dist/esm/config.js +28 -0
- package/dist/esm/expandGitHubShorthand.d.ts +1 -0
- package/dist/esm/expandGitHubShorthand.js +26 -0
- package/dist/esm/formatCombinedSummary.d.ts +2 -0
- package/dist/esm/formatCombinedSummary.js +40 -0
- package/dist/esm/formatJsonError.d.ts +1 -0
- package/dist/esm/formatJsonError.js +6 -0
- package/dist/esm/formatJsonReport.d.ts +10 -0
- package/dist/esm/formatJsonReport.js +90 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +37 -0
- package/dist/esm/init/initCommand.d.ts +6 -0
- package/dist/esm/init/initCommand.js +33 -0
- package/dist/esm/init/scaffold.d.ts +11 -0
- package/dist/esm/init/scaffold.js +15 -0
- package/dist/esm/init/templates.d.ts +2 -0
- package/dist/esm/init/templates.js +37 -0
- package/dist/esm/isRecord.d.ts +1 -0
- package/dist/esm/isRecord.js +6 -0
- package/dist/esm/jitiImport.d.ts +1 -0
- package/dist/esm/jitiImport.js +23 -0
- package/dist/esm/loadConfig.d.ts +2 -0
- package/dist/esm/loadConfig.js +74 -0
- package/dist/esm/loadRemoteCollection.d.ts +6 -0
- package/dist/esm/loadRemoteCollection.js +40 -0
- package/dist/esm/loadRemoteKit.d.ts +6 -0
- package/dist/esm/loadRemoteKit.js +40 -0
- package/dist/esm/parseArgs.d.ts +15 -0
- package/dist/esm/parseArgs.js +93 -0
- package/dist/esm/reportPreflight.d.ts +7 -0
- package/dist/esm/reportPreflight.js +105 -0
- package/dist/esm/reportRdy.d.ts +7 -0
- package/dist/esm/reportRdy.js +105 -0
- package/dist/esm/resolveCollectionExports.d.ts +1 -0
- package/dist/esm/resolveCollectionExports.js +20 -0
- package/dist/esm/resolveGitHubToken.d.ts +1 -0
- package/dist/esm/resolveGitHubToken.js +22 -0
- package/dist/esm/resolveKitExports.d.ts +1 -0
- package/dist/esm/resolveKitExports.js +20 -0
- package/dist/esm/resolveRequestedNames.d.ts +2 -0
- package/dist/esm/resolveRequestedNames.js +38 -0
- package/dist/esm/runPreflight.d.ts +7 -0
- package/dist/esm/runPreflight.js +157 -0
- package/dist/esm/runRdy.d.ts +7 -0
- package/dist/esm/runRdy.js +157 -0
- package/dist/esm/terminal.d.ts +6 -0
- package/dist/esm/terminal.js +55 -0
- package/dist/esm/types.d.ts +139 -0
- package/dist/esm/types.js +10 -0
- package/dist/esm/validateCollection.d.ts +2 -0
- package/dist/esm/validateCollection.js +27 -0
- package/dist/esm/validateKit.d.ts +2 -0
- package/dist/esm/validateKit.js +27 -0
- package/dist/esm/version.d.ts +1 -0
- package/dist/esm/version.js +4 -0
- package/dist/esm/writeFileWithCheck.d.ts +10 -0
- package/dist/esm/writeFileWithCheck.js +41 -0
- package/package.json +56 -10
package/dist/esm/cli.js
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { loadRdyKit } from "./config.js";
|
|
4
|
+
import { formatCombinedSummary } from "./formatCombinedSummary.js";
|
|
5
|
+
import { formatJsonError } from "./formatJsonError.js";
|
|
6
|
+
import { formatJsonReport } from "./formatJsonReport.js";
|
|
7
|
+
import { loadRemoteKit } from "./loadRemoteKit.js";
|
|
8
|
+
import { parseArgs } from "./parseArgs.js";
|
|
9
|
+
import { reportRdy } from "./reportRdy.js";
|
|
10
|
+
import { resolveGitHubToken } from "./resolveGitHubToken.js";
|
|
11
|
+
import { resolveRequestedNames } from "./resolveRequestedNames.js";
|
|
12
|
+
import { meetsThreshold, runRdy } from "./runRdy.js";
|
|
13
|
+
const VALID_SEVERITIES = /* @__PURE__ */ new Set(["error", "warn", "recommend"]);
|
|
14
|
+
const runFlagSchema = {
|
|
15
|
+
file: { long: "--file", type: "string", short: "-f" },
|
|
16
|
+
github: { long: "--github", type: "string", short: "-g" },
|
|
17
|
+
url: { long: "--url", type: "string", short: "-u" },
|
|
18
|
+
kit: { long: "--kit", type: "string", short: "-k" },
|
|
19
|
+
local: { long: "--local", type: "string", short: "-l" },
|
|
20
|
+
json: { long: "--json", type: "boolean", short: "-j" },
|
|
21
|
+
failOn: { long: "--fail-on", type: "string", short: "-F" },
|
|
22
|
+
reportOn: { long: "--report-on", type: "string", short: "-R" }
|
|
23
|
+
};
|
|
24
|
+
function assertNoExistingSource(existing) {
|
|
25
|
+
if (existing !== void 0) {
|
|
26
|
+
throw new Error("Cannot combine --file, --github, --local, and --url flags");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function parseGitHubArg(value) {
|
|
30
|
+
const atIndex = value.lastIndexOf("@");
|
|
31
|
+
if (atIndex === -1) {
|
|
32
|
+
return { repo: value, ref: "main" };
|
|
33
|
+
}
|
|
34
|
+
const repo = value.slice(0, atIndex);
|
|
35
|
+
const ref = value.slice(atIndex + 1);
|
|
36
|
+
if (ref === "") {
|
|
37
|
+
throw new Error(`Invalid --github value: ref after '@' must not be empty in "${value}"`);
|
|
38
|
+
}
|
|
39
|
+
return { repo, ref };
|
|
40
|
+
}
|
|
41
|
+
function parseSeverityFlag(flagName, value) {
|
|
42
|
+
if (!VALID_SEVERITIES.has(value)) {
|
|
43
|
+
throw new Error(`${flagName} must be one of: error, warn, recommend (got "${value}")`);
|
|
44
|
+
}
|
|
45
|
+
if (value === "error") return "error";
|
|
46
|
+
if (value === "warn") return "warn";
|
|
47
|
+
return "recommend";
|
|
48
|
+
}
|
|
49
|
+
const KITS_DIR = ".rdy/kits";
|
|
50
|
+
function buildGitHubKitUrl(repo, ref, kit) {
|
|
51
|
+
return `https://raw.githubusercontent.com/${repo}/${ref}/${KITS_DIR}/${kit}.js`;
|
|
52
|
+
}
|
|
53
|
+
const flagErrorHints = {
|
|
54
|
+
"--kit": "--kit requires a kit name",
|
|
55
|
+
"--fail-on": "--fail-on requires a severity level (error, warn, recommend)",
|
|
56
|
+
"--file": "--file requires a path argument",
|
|
57
|
+
"--github": "--github requires a repository argument (org/repo[@ref])",
|
|
58
|
+
"--local": "--local requires a path to a local repository",
|
|
59
|
+
"--report-on": "--report-on requires a severity level (error, warn, recommend)",
|
|
60
|
+
"--url": "--url requires a URL argument"
|
|
61
|
+
};
|
|
62
|
+
function translateParseError(error) {
|
|
63
|
+
if (error instanceof Error) {
|
|
64
|
+
const match = error.message.match(/^(--\S+) requires a value$/);
|
|
65
|
+
if (match?.[1] !== void 0) {
|
|
66
|
+
const hint = flagErrorHints[match[1]];
|
|
67
|
+
if (hint !== void 0) {
|
|
68
|
+
throw new Error(hint);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
function parseRunArgs(flags) {
|
|
75
|
+
let result;
|
|
76
|
+
try {
|
|
77
|
+
result = parseArgs(flags, runFlagSchema);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
translateParseError(error);
|
|
80
|
+
}
|
|
81
|
+
const { flags: parsed, positionals } = result;
|
|
82
|
+
let sourceType;
|
|
83
|
+
if (parsed.file !== void 0) {
|
|
84
|
+
assertNoExistingSource(sourceType);
|
|
85
|
+
sourceType = "file";
|
|
86
|
+
}
|
|
87
|
+
if (parsed.github !== void 0) {
|
|
88
|
+
assertNoExistingSource(sourceType);
|
|
89
|
+
sourceType = "github";
|
|
90
|
+
}
|
|
91
|
+
if (parsed.local !== void 0) {
|
|
92
|
+
assertNoExistingSource(sourceType);
|
|
93
|
+
sourceType = "local";
|
|
94
|
+
}
|
|
95
|
+
if (parsed.url !== void 0) {
|
|
96
|
+
assertNoExistingSource(sourceType);
|
|
97
|
+
sourceType = "url";
|
|
98
|
+
}
|
|
99
|
+
const failOn = parsed.failOn !== void 0 ? parseSeverityFlag("--fail-on", parsed.failOn) : void 0;
|
|
100
|
+
const reportOn = parsed.reportOn !== void 0 ? parseSeverityFlag("--report-on", parsed.reportOn) : void 0;
|
|
101
|
+
const parsedArgs = {
|
|
102
|
+
kitName: parsed.kit,
|
|
103
|
+
filePath: parsed.file,
|
|
104
|
+
githubValue: parsed.github,
|
|
105
|
+
json: parsed.json,
|
|
106
|
+
localValue: parsed.local,
|
|
107
|
+
names: positionals,
|
|
108
|
+
urlValue: parsed.url
|
|
109
|
+
};
|
|
110
|
+
if (failOn !== void 0) parsedArgs.failOn = failOn;
|
|
111
|
+
if (reportOn !== void 0) parsedArgs.reportOn = reportOn;
|
|
112
|
+
return parsedArgs;
|
|
113
|
+
}
|
|
114
|
+
function resolveKitSource({
|
|
115
|
+
filePath,
|
|
116
|
+
githubValue,
|
|
117
|
+
localValue,
|
|
118
|
+
urlValue,
|
|
119
|
+
kitName,
|
|
120
|
+
internalDir,
|
|
121
|
+
internalExtension
|
|
122
|
+
}) {
|
|
123
|
+
if (filePath !== void 0) {
|
|
124
|
+
if (kitName !== void 0) {
|
|
125
|
+
throw new Error("--kit cannot be used with --file");
|
|
126
|
+
}
|
|
127
|
+
return { path: filePath };
|
|
128
|
+
}
|
|
129
|
+
if (githubValue !== void 0) {
|
|
130
|
+
const name2 = kitName ?? "default";
|
|
131
|
+
const { repo, ref } = parseGitHubArg(githubValue);
|
|
132
|
+
return { url: buildGitHubKitUrl(repo, ref, name2) };
|
|
133
|
+
}
|
|
134
|
+
if (localValue !== void 0) {
|
|
135
|
+
const name2 = kitName ?? "default";
|
|
136
|
+
const resolvedBase = path.resolve(process.cwd(), localValue);
|
|
137
|
+
return { path: path.join(resolvedBase, KITS_DIR, `${name2}.js`) };
|
|
138
|
+
}
|
|
139
|
+
if (urlValue !== void 0) {
|
|
140
|
+
if (kitName !== void 0) {
|
|
141
|
+
throw new Error("--kit cannot be used with --url");
|
|
142
|
+
}
|
|
143
|
+
return { url: urlValue };
|
|
144
|
+
}
|
|
145
|
+
const name = kitName ?? "default";
|
|
146
|
+
return { path: path.join(KITS_DIR, internalDir, `${name}${internalExtension}`) };
|
|
147
|
+
}
|
|
148
|
+
function resolveFixLocation(checklist, kitDefault) {
|
|
149
|
+
return checklist.fixLocation ?? kitDefault ?? "end";
|
|
150
|
+
}
|
|
151
|
+
function summarizeReport(name, report, reportOn) {
|
|
152
|
+
let passed = 0;
|
|
153
|
+
let failed = 0;
|
|
154
|
+
let skipped = 0;
|
|
155
|
+
for (const r of report.results) {
|
|
156
|
+
if (!meetsThreshold(r.severity, reportOn)) continue;
|
|
157
|
+
if (r.status === "passed") passed++;
|
|
158
|
+
else if (r.status === "failed") failed++;
|
|
159
|
+
else skipped++;
|
|
160
|
+
}
|
|
161
|
+
return { name, passed, failed, skipped, allPassed: report.passed, durationMs: report.durationMs };
|
|
162
|
+
}
|
|
163
|
+
function resolveThresholds(kit, cliFailOn, cliReportOn) {
|
|
164
|
+
return {
|
|
165
|
+
defaultSeverity: kit.defaultSeverity ?? "error",
|
|
166
|
+
failOn: cliFailOn ?? kit.failOn ?? "error",
|
|
167
|
+
reportOn: cliReportOn ?? kit.reportOn ?? "recommend"
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
async function loadKit(source) {
|
|
171
|
+
if ("url" in source) {
|
|
172
|
+
const options = { url: source.url };
|
|
173
|
+
if (source.url.includes("raw.githubusercontent.com")) {
|
|
174
|
+
const token = resolveGitHubToken();
|
|
175
|
+
if (token !== void 0) {
|
|
176
|
+
options.token = token;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return loadRemoteKit(options);
|
|
180
|
+
}
|
|
181
|
+
return loadRdyKit(source.path);
|
|
182
|
+
}
|
|
183
|
+
async function runCommand({ names, kitSource, json, failOn, reportOn }) {
|
|
184
|
+
let kit;
|
|
185
|
+
try {
|
|
186
|
+
kit = await loadKit(kitSource);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
189
|
+
if (json) {
|
|
190
|
+
process.stdout.write(formatJsonError(message) + "\n");
|
|
191
|
+
} else {
|
|
192
|
+
process.stderr.write(`Error: ${message}
|
|
193
|
+
`);
|
|
194
|
+
}
|
|
195
|
+
return 1;
|
|
196
|
+
}
|
|
197
|
+
return runSingleKit(kit, names, json, failOn, reportOn);
|
|
198
|
+
}
|
|
199
|
+
async function runSingleKit(kit, names, json, failOn, reportOn) {
|
|
200
|
+
let resolvedNames;
|
|
201
|
+
try {
|
|
202
|
+
resolvedNames = resolveRequestedNames(names, kit);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
205
|
+
if (json) {
|
|
206
|
+
process.stdout.write(formatJsonError(message) + "\n");
|
|
207
|
+
} else {
|
|
208
|
+
process.stderr.write(`Error: ${message}
|
|
209
|
+
`);
|
|
210
|
+
}
|
|
211
|
+
return 1;
|
|
212
|
+
}
|
|
213
|
+
const checklistByName = new Map(kit.checklists.map((c) => [c.name, c]));
|
|
214
|
+
const checklists = resolvedNames.flatMap((name) => {
|
|
215
|
+
const checklist = checklistByName.get(name);
|
|
216
|
+
return checklist !== void 0 ? [checklist] : [];
|
|
217
|
+
});
|
|
218
|
+
const thresholds = resolveThresholds(kit, failOn, reportOn);
|
|
219
|
+
if (json) {
|
|
220
|
+
return runJsonMode(checklists, thresholds);
|
|
221
|
+
}
|
|
222
|
+
return runHumanMode(checklists, kit, thresholds);
|
|
223
|
+
}
|
|
224
|
+
async function runJsonMode(checklists, thresholds) {
|
|
225
|
+
const entries = [];
|
|
226
|
+
let allPassed = true;
|
|
227
|
+
try {
|
|
228
|
+
for (const checklist of checklists) {
|
|
229
|
+
const report = await runRdy(checklist, {
|
|
230
|
+
defaultSeverity: thresholds.defaultSeverity,
|
|
231
|
+
failOn: thresholds.failOn
|
|
232
|
+
});
|
|
233
|
+
entries.push({ name: checklist.name, report });
|
|
234
|
+
if (!report.passed) allPassed = false;
|
|
235
|
+
}
|
|
236
|
+
} catch (error) {
|
|
237
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
238
|
+
process.stdout.write(formatJsonError(message) + "\n");
|
|
239
|
+
return 1;
|
|
240
|
+
}
|
|
241
|
+
process.stdout.write(formatJsonReport(entries, { reportOn: thresholds.reportOn }) + "\n");
|
|
242
|
+
return allPassed ? 0 : 1;
|
|
243
|
+
}
|
|
244
|
+
async function runHumanMode(checklists, kit, thresholds) {
|
|
245
|
+
const showHeader = checklists.length > 1;
|
|
246
|
+
let allPassed = true;
|
|
247
|
+
const summaries = [];
|
|
248
|
+
try {
|
|
249
|
+
for (const checklist of checklists) {
|
|
250
|
+
if (showHeader) {
|
|
251
|
+
process.stdout.write(`
|
|
252
|
+
--- ${checklist.name} ---
|
|
253
|
+
|
|
254
|
+
`);
|
|
255
|
+
}
|
|
256
|
+
const report = await runRdy(checklist, {
|
|
257
|
+
defaultSeverity: thresholds.defaultSeverity,
|
|
258
|
+
failOn: thresholds.failOn
|
|
259
|
+
});
|
|
260
|
+
const fixLocation = resolveFixLocation(checklist, kit.fixLocation);
|
|
261
|
+
const output = reportRdy(report, { fixLocation, reportOn: thresholds.reportOn });
|
|
262
|
+
process.stdout.write(output + "\n");
|
|
263
|
+
if (!report.passed) {
|
|
264
|
+
allPassed = false;
|
|
265
|
+
}
|
|
266
|
+
if (showHeader) {
|
|
267
|
+
summaries.push(summarizeReport(checklist.name, report, thresholds.reportOn));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
} catch (error) {
|
|
271
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
272
|
+
process.stderr.write(`Error: ${message}
|
|
273
|
+
`);
|
|
274
|
+
return 1;
|
|
275
|
+
}
|
|
276
|
+
if (summaries.length > 1) {
|
|
277
|
+
process.stdout.write("\n" + formatCombinedSummary(summaries) + "\n");
|
|
278
|
+
}
|
|
279
|
+
return allPassed ? 0 : 1;
|
|
280
|
+
}
|
|
281
|
+
export {
|
|
282
|
+
parseRunArgs,
|
|
283
|
+
resolveKitSource,
|
|
284
|
+
runCommand
|
|
285
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function compileCommand(args: string[]): Promise<number>;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import picomatch from "picomatch";
|
|
5
|
+
import { loadConfig } from "../loadConfig.js";
|
|
6
|
+
import { parseArgs, translateParseError } from "../parseArgs.js";
|
|
7
|
+
import { compileConfig } from "./compileConfig.js";
|
|
8
|
+
import { validateCompiledOutput } from "./validateCompiledOutput.js";
|
|
9
|
+
const compileFlagSchema = {
|
|
10
|
+
output: { long: "--output", type: "string", short: "-o" }
|
|
11
|
+
};
|
|
12
|
+
async function compileCommand(args) {
|
|
13
|
+
let parsed;
|
|
14
|
+
try {
|
|
15
|
+
parsed = parseArgs(args, compileFlagSchema);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
18
|
+
if (message === "--output requires a value") {
|
|
19
|
+
process.stderr.write("Error: --output requires a path argument\n");
|
|
20
|
+
} else {
|
|
21
|
+
process.stderr.write(`Error: ${translateParseError(error)}
|
|
22
|
+
`);
|
|
23
|
+
}
|
|
24
|
+
return 1;
|
|
25
|
+
}
|
|
26
|
+
const outputPath = parsed.flags.output;
|
|
27
|
+
const positionals = parsed.positionals;
|
|
28
|
+
if (positionals.length > 1) {
|
|
29
|
+
process.stderr.write("Error: Too many arguments. Expected a single input file.\n");
|
|
30
|
+
return 1;
|
|
31
|
+
}
|
|
32
|
+
const inputPath = positionals[0];
|
|
33
|
+
if (inputPath !== void 0) {
|
|
34
|
+
try {
|
|
35
|
+
const result = await compileConfig(inputPath, outputPath);
|
|
36
|
+
await validateCompiledOutput(result.outputPath);
|
|
37
|
+
const relInput = path.relative(process.cwd(), path.resolve(inputPath));
|
|
38
|
+
const relOutput = path.relative(process.cwd(), result.outputPath);
|
|
39
|
+
process.stdout.write("Compiling kit:\n");
|
|
40
|
+
process.stdout.write(formatResultLine(relInput, relOutput, result.changed));
|
|
41
|
+
return 0;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
44
|
+
process.stderr.write(`Error: ${message}
|
|
45
|
+
`);
|
|
46
|
+
return 1;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (outputPath !== void 0) {
|
|
50
|
+
process.stderr.write("Error: --output requires an input file\n");
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
return compileBatch();
|
|
54
|
+
}
|
|
55
|
+
function collectSourceFiles(srcDir, includeGlob) {
|
|
56
|
+
const entries = readdirSync(srcDir, { recursive: true, encoding: "utf8" });
|
|
57
|
+
const isMatch = includeGlob !== void 0 ? picomatch(includeGlob) : void 0;
|
|
58
|
+
return entries.filter((name) => name.endsWith(".ts") && (isMatch === void 0 || isMatch(name))).sort();
|
|
59
|
+
}
|
|
60
|
+
async function compileBatch() {
|
|
61
|
+
let config;
|
|
62
|
+
try {
|
|
63
|
+
config = await loadConfig();
|
|
64
|
+
} catch (error) {
|
|
65
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
66
|
+
process.stderr.write(`Error: ${message}
|
|
67
|
+
`);
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
const srcDir = path.resolve(process.cwd(), config.compile.srcDir);
|
|
71
|
+
const outDir = path.resolve(process.cwd(), config.compile.outDir);
|
|
72
|
+
if (!existsSync(srcDir)) {
|
|
73
|
+
process.stderr.write(`Error: Source directory not found: ${srcDir}
|
|
74
|
+
`);
|
|
75
|
+
return 1;
|
|
76
|
+
}
|
|
77
|
+
let tsFiles;
|
|
78
|
+
try {
|
|
79
|
+
tsFiles = collectSourceFiles(srcDir, config.compile.include);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
82
|
+
process.stderr.write(`Error: Failed to read source directory: ${message}
|
|
83
|
+
`);
|
|
84
|
+
return 1;
|
|
85
|
+
}
|
|
86
|
+
if (tsFiles.length === 0) {
|
|
87
|
+
process.stderr.write(`Error: No .ts files found in ${srcDir}
|
|
88
|
+
`);
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
const relSrcDir = path.relative(process.cwd(), srcDir);
|
|
92
|
+
const relOutDir = path.relative(process.cwd(), outDir);
|
|
93
|
+
const header = srcDir === outDir ? `Compiling kits in ${relSrcDir}:
|
|
94
|
+
` : `Compiling kits from ${relSrcDir} to ${relOutDir}:
|
|
95
|
+
`;
|
|
96
|
+
process.stdout.write(header);
|
|
97
|
+
for (const fileName of tsFiles) {
|
|
98
|
+
const srcFile = path.join(srcDir, fileName);
|
|
99
|
+
const outFile = path.join(outDir, fileName.replace(/\.ts$/, ".js"));
|
|
100
|
+
try {
|
|
101
|
+
const result = await compileConfig(srcFile, outFile);
|
|
102
|
+
await validateCompiledOutput(result.outputPath);
|
|
103
|
+
const outName = fileName.replace(/\.ts$/, ".js");
|
|
104
|
+
process.stdout.write(formatResultLine(fileName, outName, result.changed));
|
|
105
|
+
} catch (error) {
|
|
106
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
107
|
+
process.stderr.write(`Error compiling ${fileName}: ${message}
|
|
108
|
+
`);
|
|
109
|
+
return 1;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
function formatResultLine(srcName, outName, changed) {
|
|
115
|
+
return changed ? ` \u{1F4E6} ${srcName} \u2192 ${outName}
|
|
116
|
+
` : ` \u26AA ${srcName} \u2014 no changes
|
|
117
|
+
`;
|
|
118
|
+
}
|
|
119
|
+
export {
|
|
120
|
+
compileCommand
|
|
121
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const GENERATED_HEADER = [
|
|
4
|
+
"/** @noformat \u2014 @generated. Do not edit. Compiled by rdy. */",
|
|
5
|
+
"/* eslint-disable */",
|
|
6
|
+
""
|
|
7
|
+
].join("\n");
|
|
8
|
+
function deriveOutputPath(inputPath) {
|
|
9
|
+
const ext = path.extname(inputPath);
|
|
10
|
+
if (ext === ".ts" || ext === ".mts" || ext === ".cts") {
|
|
11
|
+
return inputPath.slice(0, -ext.length) + ".js";
|
|
12
|
+
}
|
|
13
|
+
return `${inputPath}.js`;
|
|
14
|
+
}
|
|
15
|
+
async function compileConfig(inputPath, outputPath) {
|
|
16
|
+
const resolvedInput = path.resolve(inputPath);
|
|
17
|
+
const resolvedOutput = path.resolve(outputPath ?? deriveOutputPath(inputPath));
|
|
18
|
+
let esbuild;
|
|
19
|
+
try {
|
|
20
|
+
esbuild = await import("esbuild");
|
|
21
|
+
} catch (error) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
"esbuild is required for the compile command but is not installed. Install it with: pnpm add --save-dev esbuild",
|
|
24
|
+
{ cause: error }
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
const result = await esbuild.build({
|
|
28
|
+
entryPoints: [resolvedInput],
|
|
29
|
+
bundle: true,
|
|
30
|
+
format: "esm",
|
|
31
|
+
platform: "node",
|
|
32
|
+
target: "es2022",
|
|
33
|
+
external: ["node:*"],
|
|
34
|
+
banner: { js: GENERATED_HEADER },
|
|
35
|
+
write: false
|
|
36
|
+
});
|
|
37
|
+
const outputFile = result.outputFiles[0];
|
|
38
|
+
if (outputFile === void 0) {
|
|
39
|
+
throw new Error(`esbuild produced no output for ${resolvedInput}`);
|
|
40
|
+
}
|
|
41
|
+
const compiled = Buffer.from(outputFile.contents);
|
|
42
|
+
const existing = existsSync(resolvedOutput) ? readFileSync(resolvedOutput) : void 0;
|
|
43
|
+
const changed = existing === void 0 || !compiled.equals(existing);
|
|
44
|
+
if (changed) {
|
|
45
|
+
mkdirSync(path.dirname(resolvedOutput), { recursive: true });
|
|
46
|
+
writeFileSync(resolvedOutput, compiled);
|
|
47
|
+
}
|
|
48
|
+
return { outputPath: resolvedOutput, changed };
|
|
49
|
+
}
|
|
50
|
+
export {
|
|
51
|
+
compileConfig
|
|
52
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function validateCompiledOutput(outputPath: string): Promise<void>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { rmSync } from "node:fs";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import { assertIsRdyKit } from "../assertIsRdyKit.js";
|
|
4
|
+
import { isRecord } from "../isRecord.js";
|
|
5
|
+
import { resolveKitExports } from "../resolveKitExports.js";
|
|
6
|
+
import { validateKit } from "../validateKit.js";
|
|
7
|
+
async function validateCompiledOutput(outputPath) {
|
|
8
|
+
const fileUrl = `${pathToFileURL(outputPath).href}?t=${Date.now()}`;
|
|
9
|
+
let imported;
|
|
10
|
+
try {
|
|
11
|
+
imported = await import(fileUrl);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
rmSync(outputPath, { force: true });
|
|
14
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
15
|
+
throw new Error(`Failed to load compiled output for validation: ${detail}`);
|
|
16
|
+
}
|
|
17
|
+
const moduleRecord = isRecord(imported) ? imported : {};
|
|
18
|
+
try {
|
|
19
|
+
const resolved = resolveKitExports(moduleRecord);
|
|
20
|
+
assertIsRdyKit(resolved);
|
|
21
|
+
validateKit(resolved);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
rmSync(outputPath, { force: true });
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
validateCompiledOutput
|
|
29
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { assertIsRdyKit } from "./assertIsRdyKit.js";
|
|
4
|
+
import { jitiImport } from "./jitiImport.js";
|
|
5
|
+
import { resolveKitExports } from "./resolveKitExports.js";
|
|
6
|
+
import { validateKit } from "./validateKit.js";
|
|
7
|
+
async function loadRdyKit(kitPath) {
|
|
8
|
+
const resolvedPath = path.resolve(process.cwd(), kitPath);
|
|
9
|
+
if (!existsSync(resolvedPath)) {
|
|
10
|
+
if (kitPath.startsWith(".rdy/kits/")) {
|
|
11
|
+
const baseName = path.basename(kitPath, ".ts");
|
|
12
|
+
throw new Error(`Kit "${baseName}" not found. Run 'rdy init' to create one.`);
|
|
13
|
+
}
|
|
14
|
+
throw new Error(`Kit not found: ${kitPath}`);
|
|
15
|
+
}
|
|
16
|
+
const imported = await jitiImport(
|
|
17
|
+
resolvedPath,
|
|
18
|
+
"Uncompiled kits require the package to be installed as a project dependency. Alternatively, run 'rdy compile' to produce a self-contained bundle that does not need a local install.",
|
|
19
|
+
"Kit file"
|
|
20
|
+
);
|
|
21
|
+
const resolved = resolveKitExports(imported);
|
|
22
|
+
assertIsRdyKit(resolved);
|
|
23
|
+
validateKit(resolved);
|
|
24
|
+
return resolved;
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
loadRdyKit
|
|
28
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function expandGitHubShorthand(shorthand: string): string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
function expandGitHubShorthand(shorthand) {
|
|
2
|
+
const atIndex = shorthand.lastIndexOf("@");
|
|
3
|
+
let pathPart;
|
|
4
|
+
let ref;
|
|
5
|
+
if (atIndex === -1) {
|
|
6
|
+
pathPart = shorthand;
|
|
7
|
+
ref = "main";
|
|
8
|
+
} else {
|
|
9
|
+
pathPart = shorthand.slice(0, atIndex);
|
|
10
|
+
ref = shorthand.slice(atIndex + 1);
|
|
11
|
+
if (ref === "") {
|
|
12
|
+
throw new Error(`Invalid GitHub shorthand: ref after '@' must not be empty in "${shorthand}"`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const segments = pathPart.split("/");
|
|
16
|
+
if (segments.length < 3) {
|
|
17
|
+
throw new Error(`Invalid GitHub shorthand: expected at least "org/repo/file", got "${shorthand}"`);
|
|
18
|
+
}
|
|
19
|
+
const org = segments[0];
|
|
20
|
+
const repo = segments[1];
|
|
21
|
+
const filePath = segments.slice(2).join("/");
|
|
22
|
+
return `https://raw.githubusercontent.com/${org}/${repo}/${ref}/${filePath}`;
|
|
23
|
+
}
|
|
24
|
+
export {
|
|
25
|
+
expandGitHubShorthand
|
|
26
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { formatSummaryCounts } from "./reportRdy.js";
|
|
2
|
+
const ICON_PASSED = "\u{1F7E2}";
|
|
3
|
+
const ICON_FAILED = "\u{1F534}";
|
|
4
|
+
function formatDuration(ms) {
|
|
5
|
+
return `${Math.round(ms)}ms`;
|
|
6
|
+
}
|
|
7
|
+
function formatRowCounts(summary) {
|
|
8
|
+
const parts = [];
|
|
9
|
+
if (summary.passed > 0) parts.push(`${summary.passed} passed`);
|
|
10
|
+
if (summary.failed > 0) parts.push(`${summary.failed} failed`);
|
|
11
|
+
if (summary.skipped > 0) parts.push(`${summary.skipped} skipped`);
|
|
12
|
+
return parts.join(", ");
|
|
13
|
+
}
|
|
14
|
+
function formatCombinedSummary(summaries) {
|
|
15
|
+
const HEADER = "\u2500\u2500 Summary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500";
|
|
16
|
+
const lines = [HEADER];
|
|
17
|
+
const maxNameLen = Math.max(...summaries.map((s) => s.name.length));
|
|
18
|
+
const maxDurationLen = Math.max(...summaries.map((s) => formatDuration(s.durationMs).length));
|
|
19
|
+
for (const summary of summaries) {
|
|
20
|
+
const icon = summary.allPassed ? ICON_PASSED : ICON_FAILED;
|
|
21
|
+
const name = summary.name.padEnd(maxNameLen);
|
|
22
|
+
const duration = formatDuration(summary.durationMs).padStart(maxDurationLen);
|
|
23
|
+
const counts = formatRowCounts(summary);
|
|
24
|
+
lines.push(`${icon} ${name} ${duration} ${counts}`);
|
|
25
|
+
}
|
|
26
|
+
lines.push(
|
|
27
|
+
"\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
|
|
28
|
+
);
|
|
29
|
+
const totalPassed = summaries.reduce((sum, s) => sum + s.passed, 0);
|
|
30
|
+
const totalFailed = summaries.reduce((sum, s) => sum + s.failed, 0);
|
|
31
|
+
const totalSkipped = summaries.reduce((sum, s) => sum + s.skipped, 0);
|
|
32
|
+
const totalDuration = summaries.reduce((sum, s) => sum + s.durationMs, 0);
|
|
33
|
+
lines.push(
|
|
34
|
+
`Total: ${formatSummaryCounts(totalPassed, totalFailed, totalSkipped)} (${formatDuration(totalDuration)})`
|
|
35
|
+
);
|
|
36
|
+
return lines.join("\n");
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
formatCombinedSummary
|
|
40
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function formatJsonError(message: string): string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RdyReport, Severity } from './types.ts';
|
|
2
|
+
interface ChecklistEntry {
|
|
3
|
+
name: string;
|
|
4
|
+
report: RdyReport;
|
|
5
|
+
}
|
|
6
|
+
export interface FormatJsonReportOptions {
|
|
7
|
+
reportOn?: Severity;
|
|
8
|
+
}
|
|
9
|
+
export declare function formatJsonReport(entries: ChecklistEntry[], options?: FormatJsonReportOptions): string;
|
|
10
|
+
export {};
|