@voeu/cli 0.1.0 → 0.1.1
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 +4 -0
- package/dist/build-info.json +4 -3
- package/dist/ts-loader-hooks.js +186 -0
- package/dist/voeu.js +130 -33
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -124,6 +124,10 @@ That seam is deliberate: a harness that supplies its own session store proves on
|
|
|
124
124
|
it supplied works. A partly-filled adapter still gives honest answers, because scenarios it cannot
|
|
125
125
|
exercise report UNPROVEN rather than passing.
|
|
126
126
|
|
|
127
|
+
A TypeScript adapter runs as written: `verify` loads `.ts` modules itself, following `.js`-suffixed
|
|
128
|
+
imports, extensionless ones and tsconfig `paths` the way your compiler would, with no build step and
|
|
129
|
+
no type checking. In CI it reads `DATABASE_URL` when `voeu.config.json` names no database.
|
|
130
|
+
|
|
127
131
|
`npx @voeu/cli verify --reference` runs the whole suite against a built-in application, so you can see
|
|
128
132
|
the output before writing anything.
|
|
129
133
|
|
package/dist/build-info.json
CHANGED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { createRequire as __voeuCreateRequire } from 'node:module';
|
|
2
|
+
const require = __voeuCreateRequire(import.meta.url);
|
|
3
|
+
|
|
4
|
+
// packages/cli/src/ts-loader-hooks.ts
|
|
5
|
+
import { readFileSync, statSync } from "node:fs";
|
|
6
|
+
import { dirname, extname, isAbsolute, join, resolve as resolvePath } from "node:path";
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
|
+
import ts from "ts-parser";
|
|
9
|
+
var repoRoot = "";
|
|
10
|
+
function initialize(data) {
|
|
11
|
+
repoRoot = data.repoRoot;
|
|
12
|
+
}
|
|
13
|
+
var TS_SOURCE = /\.(ts|tsx|mts|cts)$/;
|
|
14
|
+
var SIBLINGS = {
|
|
15
|
+
".js": [".ts", ".tsx"],
|
|
16
|
+
".mjs": [".mts"],
|
|
17
|
+
".cjs": [".cts"],
|
|
18
|
+
".jsx": [".tsx"]
|
|
19
|
+
};
|
|
20
|
+
var EXTENSIONS = [".ts", ".tsx", ".mts", ".js", ".mjs", ".cjs", ".json"];
|
|
21
|
+
var INDEXES = EXTENSIONS.map((e) => `index${e}`);
|
|
22
|
+
var NOT_FOUND = /* @__PURE__ */ new Set(["ERR_MODULE_NOT_FOUND", "ERR_UNSUPPORTED_DIR_IMPORT"]);
|
|
23
|
+
function isFile(path) {
|
|
24
|
+
try {
|
|
25
|
+
return statSync(path).isFile();
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function probe(base) {
|
|
31
|
+
if (isFile(base)) return base;
|
|
32
|
+
const ext = extname(base);
|
|
33
|
+
for (const sibling of SIBLINGS[ext] ?? []) {
|
|
34
|
+
const candidate = base.slice(0, -ext.length) + sibling;
|
|
35
|
+
if (isFile(candidate)) return candidate;
|
|
36
|
+
}
|
|
37
|
+
for (const extension of EXTENSIONS) if (isFile(base + extension)) return base + extension;
|
|
38
|
+
for (const index of INDEXES) if (isFile(join(base, index))) return join(base, index);
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
var pathMaps = /* @__PURE__ */ new Map();
|
|
42
|
+
function pathMapFor(dir) {
|
|
43
|
+
const cached = pathMaps.get(dir);
|
|
44
|
+
if (cached !== void 0) return cached;
|
|
45
|
+
const visited = [];
|
|
46
|
+
let current = dir;
|
|
47
|
+
let found = null;
|
|
48
|
+
for (; ; ) {
|
|
49
|
+
visited.push(current);
|
|
50
|
+
const file = join(current, "tsconfig.json");
|
|
51
|
+
if (isFile(file)) {
|
|
52
|
+
found = readPathMap(file);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
const parent = dirname(current);
|
|
56
|
+
if (current === repoRoot || parent === current) break;
|
|
57
|
+
current = parent;
|
|
58
|
+
}
|
|
59
|
+
for (const d of visited) pathMaps.set(d, found);
|
|
60
|
+
return found;
|
|
61
|
+
}
|
|
62
|
+
function readPathMap(file) {
|
|
63
|
+
const read = ts.readConfigFile(file, ts.sys.readFile);
|
|
64
|
+
if (read.error || typeof read.config !== "object" || read.config === null) return null;
|
|
65
|
+
const raw = { ...read.config, files: [], include: [] };
|
|
66
|
+
const parsed = ts.parseJsonConfigFileContent(raw, ts.sys, dirname(file), void 0, file);
|
|
67
|
+
const options = parsed.options;
|
|
68
|
+
const paths = options.paths ?? {};
|
|
69
|
+
const patterns = Object.entries(paths).map(([key, targets]) => ({ key, targets })).sort((a, b) => b.key.length - a.key.length);
|
|
70
|
+
if (patterns.length === 0 && options.baseUrl === void 0) return null;
|
|
71
|
+
return { base: options.baseUrl ?? options.pathsBasePath ?? dirname(file), patterns };
|
|
72
|
+
}
|
|
73
|
+
function mapThroughPaths(specifier, map) {
|
|
74
|
+
const out = [];
|
|
75
|
+
for (const { key, targets } of map.patterns) {
|
|
76
|
+
const star = key.indexOf("*");
|
|
77
|
+
if (star === -1) {
|
|
78
|
+
if (specifier === key) out.push(...targets.map((t) => resolvePath(map.base, t)));
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const prefix = key.slice(0, star);
|
|
82
|
+
const suffix = key.slice(star + 1);
|
|
83
|
+
if (!specifier.startsWith(prefix) || !specifier.endsWith(suffix)) continue;
|
|
84
|
+
if (specifier.length < prefix.length + suffix.length) continue;
|
|
85
|
+
const matched = specifier.slice(prefix.length, specifier.length - suffix.length);
|
|
86
|
+
out.push(...targets.map((t) => resolvePath(map.base, t.replace("*", matched))));
|
|
87
|
+
}
|
|
88
|
+
if (out.length === 0 && map.patterns.length === 0) out.push(resolvePath(map.base, specifier));
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
function parentPath(parentURL) {
|
|
92
|
+
if (!parentURL || !parentURL.startsWith("file:")) return null;
|
|
93
|
+
const path = fileURLToPath(parentURL);
|
|
94
|
+
if (path.split(/[\\/]/).includes("node_modules")) return null;
|
|
95
|
+
return path;
|
|
96
|
+
}
|
|
97
|
+
function findTypeScriptTarget(specifier, parent) {
|
|
98
|
+
if (specifier.startsWith("./") || specifier.startsWith("../") || specifier === "." || specifier === "..") {
|
|
99
|
+
return probe(resolvePath(dirname(parent), specifier));
|
|
100
|
+
}
|
|
101
|
+
if (specifier.startsWith("file:")) return probe(fileURLToPath(specifier));
|
|
102
|
+
if (isAbsolute(specifier)) return probe(specifier);
|
|
103
|
+
if (specifier.startsWith("node:") || specifier.startsWith("data:")) return null;
|
|
104
|
+
const map = pathMapFor(dirname(parent));
|
|
105
|
+
if (map === null) return null;
|
|
106
|
+
for (const candidate of mapThroughPaths(specifier, map)) {
|
|
107
|
+
const found = probe(candidate);
|
|
108
|
+
if (found !== null) return found;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
async function resolve(specifier, context, nextResolve) {
|
|
113
|
+
try {
|
|
114
|
+
return await nextResolve(specifier, context);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
const code = error.code ?? "";
|
|
117
|
+
if (!NOT_FOUND.has(code)) throw error;
|
|
118
|
+
const parent = parentPath(context.parentURL);
|
|
119
|
+
if (parent === null) throw error;
|
|
120
|
+
const found = findTypeScriptTarget(specifier, parent);
|
|
121
|
+
if (found === null) throw error;
|
|
122
|
+
return { url: pathToFileURL(found).href, shortCircuit: true };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
var packageTypes = /* @__PURE__ */ new Map();
|
|
126
|
+
function packageType(dir) {
|
|
127
|
+
const cached = packageTypes.get(dir);
|
|
128
|
+
if (cached !== void 0) return cached;
|
|
129
|
+
let type = null;
|
|
130
|
+
const file = join(dir, "package.json");
|
|
131
|
+
if (isFile(file)) {
|
|
132
|
+
try {
|
|
133
|
+
type = JSON.parse(readFileSync(file, "utf8")).type ?? "commonjs";
|
|
134
|
+
} catch {
|
|
135
|
+
type = null;
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
const parent = dirname(dir);
|
|
139
|
+
type = parent === dir ? null : packageType(parent);
|
|
140
|
+
}
|
|
141
|
+
packageTypes.set(dir, type);
|
|
142
|
+
return type;
|
|
143
|
+
}
|
|
144
|
+
var ESM_SYNTAX = /^\s*(?:import\s+[\w{*"'\s]|import\s*\(|export\s+)/m;
|
|
145
|
+
function formatFor(path, source) {
|
|
146
|
+
if (path.endsWith(".mts")) return "module";
|
|
147
|
+
if (path.endsWith(".cts")) return "commonjs";
|
|
148
|
+
if (packageType(dirname(path)) === "module") return "module";
|
|
149
|
+
return ESM_SYNTAX.test(source) ? "module" : "commonjs";
|
|
150
|
+
}
|
|
151
|
+
function transpile(path, source, format) {
|
|
152
|
+
return ts.transpileModule(source, {
|
|
153
|
+
fileName: path,
|
|
154
|
+
reportDiagnostics: false,
|
|
155
|
+
compilerOptions: {
|
|
156
|
+
module: format === "module" ? ts.ModuleKind.ESNext : ts.ModuleKind.CommonJS,
|
|
157
|
+
target: ts.ScriptTarget.ES2022,
|
|
158
|
+
jsx: ts.JsxEmit.ReactJSX,
|
|
159
|
+
esModuleInterop: true,
|
|
160
|
+
isolatedModules: true,
|
|
161
|
+
// Type-only imports vanish; an import used only in type positions is
|
|
162
|
+
// elided too, so a module that exports a type and nothing else is never
|
|
163
|
+
// asked for a runtime binding it does not have.
|
|
164
|
+
verbatimModuleSyntax: false,
|
|
165
|
+
useDefineForClassFields: true,
|
|
166
|
+
inlineSourceMap: true,
|
|
167
|
+
inlineSources: false
|
|
168
|
+
}
|
|
169
|
+
}).outputText;
|
|
170
|
+
}
|
|
171
|
+
async function load(url, context, nextLoad) {
|
|
172
|
+
if (!url.startsWith("file:") || !TS_SOURCE.test(new URL(url).pathname)) return nextLoad(url, context);
|
|
173
|
+
const path = fileURLToPath(url);
|
|
174
|
+
const source = readFileSync(path, "utf8");
|
|
175
|
+
const format = formatFor(path, source);
|
|
176
|
+
return { format, source: transpile(path, source, format), shortCircuit: true };
|
|
177
|
+
}
|
|
178
|
+
export {
|
|
179
|
+
findTypeScriptTarget,
|
|
180
|
+
formatFor,
|
|
181
|
+
initialize,
|
|
182
|
+
load,
|
|
183
|
+
probe,
|
|
184
|
+
resolve,
|
|
185
|
+
transpile
|
|
186
|
+
};
|
package/dist/voeu.js
CHANGED
|
@@ -159,6 +159,8 @@ function snapshotFromFiles(files, options = {}) {
|
|
|
159
159
|
byPath: new Map(parsed.map((f) => [f.path, f])),
|
|
160
160
|
packageJson: options.packageJson ?? null,
|
|
161
161
|
lockfileHash: options.lockfileHash ?? null,
|
|
162
|
+
// A test that hands over a lockfile by content means the lockfile exists.
|
|
163
|
+
lockfile: options.lockfile ?? LOCKFILE_NAMES.find((name) => files.has(name)) ?? null,
|
|
162
164
|
truncated: options.truncated ?? false,
|
|
163
165
|
unparsed,
|
|
164
166
|
nestedPackages: [...options.nestedPackages ?? []]
|
|
@@ -226,9 +228,11 @@ function loadRepo(root) {
|
|
|
226
228
|
}
|
|
227
229
|
}
|
|
228
230
|
let lockfileHash = null;
|
|
231
|
+
let lockfile = null;
|
|
229
232
|
for (const name of LOCKFILE_NAMES) {
|
|
230
233
|
try {
|
|
231
234
|
lockfileHash = hashString(readFileSync(join(root, name), "utf8"));
|
|
235
|
+
lockfile = name;
|
|
232
236
|
break;
|
|
233
237
|
} catch {
|
|
234
238
|
}
|
|
@@ -239,6 +243,7 @@ function loadRepo(root) {
|
|
|
239
243
|
byPath: new Map(files.map((f) => [f.path, f])),
|
|
240
244
|
packageJson,
|
|
241
245
|
lockfileHash,
|
|
246
|
+
lockfile,
|
|
242
247
|
truncated,
|
|
243
248
|
unparsed,
|
|
244
249
|
nestedPackages
|
|
@@ -2020,6 +2025,22 @@ var SCENARIOS = [
|
|
|
2020
2025
|
|
|
2021
2026
|
// packages/harness/src/run.ts
|
|
2022
2027
|
var DEFAULT_START = Date.parse("2026-09-08T12:00:00.000Z");
|
|
2028
|
+
var DEFAULT_SCENARIO_TIMEOUT_MS = 6e4;
|
|
2029
|
+
var STOP_TIMEOUT_MS = 5e3;
|
|
2030
|
+
var Timeout = class extends Error {
|
|
2031
|
+
constructor(message) {
|
|
2032
|
+
super(message);
|
|
2033
|
+
this.name = "Timeout";
|
|
2034
|
+
}
|
|
2035
|
+
};
|
|
2036
|
+
function within(ms, work, what) {
|
|
2037
|
+
let timer;
|
|
2038
|
+
const expiry = new Promise((_, reject) => {
|
|
2039
|
+
timer = setTimeout(() => reject(new Timeout(what)), ms);
|
|
2040
|
+
});
|
|
2041
|
+
work.catch(() => void 0);
|
|
2042
|
+
return Promise.race([work, expiry]).finally(() => clearTimeout(timer));
|
|
2043
|
+
}
|
|
2023
2044
|
var OPTIONAL_CAPABILITIES = [
|
|
2024
2045
|
"peekQueue",
|
|
2025
2046
|
"withSecondProcess",
|
|
@@ -2030,6 +2051,7 @@ var OPTIONAL_CAPABILITIES = [
|
|
|
2030
2051
|
async function runSuite(options) {
|
|
2031
2052
|
const scenarios = options.scenarios ?? SCENARIOS;
|
|
2032
2053
|
const outcomes = [];
|
|
2054
|
+
const budget = options.scenarioTimeoutMs ?? DEFAULT_SCENARIO_TIMEOUT_MS;
|
|
2033
2055
|
for (const scenario of scenarios) {
|
|
2034
2056
|
let clock = options.startedAt ?? DEFAULT_START;
|
|
2035
2057
|
const now = () => clock;
|
|
@@ -2041,18 +2063,33 @@ async function runSuite(options) {
|
|
|
2041
2063
|
const url = await simulator.listen();
|
|
2042
2064
|
const app = await options.makeApp();
|
|
2043
2065
|
try {
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2066
|
+
const started = Date.now();
|
|
2067
|
+
const outcome = await within(
|
|
2068
|
+
budget,
|
|
2069
|
+
(async () => {
|
|
2070
|
+
await app.start({ simulatorUrl: url, databaseUrl: options.databaseUrl ?? "", now });
|
|
2071
|
+
options.observe?.(app);
|
|
2072
|
+
const capabilities = new Set(
|
|
2073
|
+
OPTIONAL_CAPABILITIES.filter((name) => typeof app[name] === "function")
|
|
2074
|
+
);
|
|
2075
|
+
return runScenario(scenario, {
|
|
2076
|
+
app,
|
|
2077
|
+
simulator,
|
|
2078
|
+
advance,
|
|
2079
|
+
now,
|
|
2080
|
+
faults: simulator.faults,
|
|
2081
|
+
capabilities
|
|
2082
|
+
});
|
|
2083
|
+
})(),
|
|
2084
|
+
scenario.id
|
|
2085
|
+
).catch((error) => {
|
|
2086
|
+
if (!(error instanceof Timeout)) throw error;
|
|
2087
|
+
return {
|
|
2088
|
+
scenario,
|
|
2089
|
+
verdict: "not_verified",
|
|
2090
|
+
detail: `The scenario was still running after ${Math.round(budget / 1e3)} s and was abandoned. Scenarios move a simulated clock, so real time goes only to the application's own I/O; a scenario that does not finish is stuck on it - most often a queue or database client waiting for a connection that will never come. Nothing was established either way.`,
|
|
2091
|
+
durationMs: Date.now() - started
|
|
2092
|
+
};
|
|
2056
2093
|
});
|
|
2057
2094
|
const internal = simulator.requests.filter((r) => r.outcome === "internal");
|
|
2058
2095
|
outcomes.push(
|
|
@@ -2070,7 +2107,7 @@ async function runSuite(options) {
|
|
|
2070
2107
|
durationMs: 0
|
|
2071
2108
|
});
|
|
2072
2109
|
} finally {
|
|
2073
|
-
await app.stop().catch(() => void 0);
|
|
2110
|
+
await within(STOP_TIMEOUT_MS, Promise.resolve().then(() => app.stop()), `${scenario.id} stop`).catch(() => void 0);
|
|
2074
2111
|
await simulator.close();
|
|
2075
2112
|
}
|
|
2076
2113
|
}
|
|
@@ -4867,6 +4904,22 @@ function renderHtml(run) {
|
|
|
4867
4904
|
`;
|
|
4868
4905
|
}
|
|
4869
4906
|
|
|
4907
|
+
// packages/cli/src/ts-loader.ts
|
|
4908
|
+
import { existsSync } from "node:fs";
|
|
4909
|
+
import { register } from "node:module";
|
|
4910
|
+
import { fileURLToPath } from "node:url";
|
|
4911
|
+
var registered = false;
|
|
4912
|
+
function registerTypeScriptLoader(repoRoot) {
|
|
4913
|
+
if (registered) return { registered: true, reason: null };
|
|
4914
|
+
const hooks = new URL("./ts-loader-hooks.js", import.meta.url);
|
|
4915
|
+
if (!existsSync(fileURLToPath(hooks))) {
|
|
4916
|
+
return { registered: false, reason: `no ${fileURLToPath(hooks)} beside the CLI, so TypeScript adapters load only if the host can` };
|
|
4917
|
+
}
|
|
4918
|
+
register(hooks, { parentURL: import.meta.url, data: { repoRoot } });
|
|
4919
|
+
registered = true;
|
|
4920
|
+
return { registered: true, reason: null };
|
|
4921
|
+
}
|
|
4922
|
+
|
|
4870
4923
|
// packages/fixer/src/diff.ts
|
|
4871
4924
|
var MAX_CELLS = 4e6;
|
|
4872
4925
|
function lcsOps(before, after) {
|
|
@@ -5756,6 +5809,24 @@ async function runVerifyStripe(args) {
|
|
|
5756
5809
|
emit(run, args, null);
|
|
5757
5810
|
return verdictExit(run);
|
|
5758
5811
|
}
|
|
5812
|
+
async function recordingBackgroundErrors(work) {
|
|
5813
|
+
const errors = [];
|
|
5814
|
+
const describe2 = (error) => error instanceof Error ? error.message : String(error);
|
|
5815
|
+
const onRejection = (reason) => {
|
|
5816
|
+
errors.push(`unhandled rejection: ${describe2(reason)}`);
|
|
5817
|
+
};
|
|
5818
|
+
const onException = (error) => {
|
|
5819
|
+
errors.push(`uncaught exception: ${describe2(error)}`);
|
|
5820
|
+
};
|
|
5821
|
+
process.on("unhandledRejection", onRejection);
|
|
5822
|
+
process.on("uncaughtException", onException);
|
|
5823
|
+
try {
|
|
5824
|
+
return { result: await work(), errors };
|
|
5825
|
+
} finally {
|
|
5826
|
+
process.off("unhandledRejection", onRejection);
|
|
5827
|
+
process.off("uncaughtException", onException);
|
|
5828
|
+
}
|
|
5829
|
+
}
|
|
5759
5830
|
async function runVerify(args) {
|
|
5760
5831
|
if (args.provider === "stripe") return runVerifyStripe(args);
|
|
5761
5832
|
if (args.reference) {
|
|
@@ -5778,27 +5849,54 @@ async function runVerify(args) {
|
|
|
5778
5849
|
);
|
|
5779
5850
|
return EXIT.UNSUPPORTED;
|
|
5780
5851
|
}
|
|
5852
|
+
let loader;
|
|
5853
|
+
try {
|
|
5854
|
+
loader = registerTypeScriptLoader(root);
|
|
5855
|
+
} catch (error) {
|
|
5856
|
+
process.stderr.write(
|
|
5857
|
+
`voeu: could not start the TypeScript loader: ${error instanceof Error ? error.message : error}
|
|
5858
|
+
`
|
|
5859
|
+
);
|
|
5860
|
+
return EXIT.INFRASTRUCTURE;
|
|
5861
|
+
}
|
|
5781
5862
|
let makeApp;
|
|
5782
5863
|
try {
|
|
5783
5864
|
makeApp = await loadAdapter(root, config.adapter);
|
|
5784
5865
|
} catch (error) {
|
|
5785
5866
|
process.stderr.write(
|
|
5786
5867
|
`voeu: could not load adapter ${config.adapter}: ${error instanceof Error ? error.message : error}
|
|
5787
|
-
`
|
|
5868
|
+
${loader.registered ? "" : ` (${loader.reason})
|
|
5869
|
+
`}`
|
|
5788
5870
|
);
|
|
5789
5871
|
return EXIT.UNSUPPORTED;
|
|
5790
5872
|
}
|
|
5791
5873
|
try {
|
|
5792
|
-
const
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5874
|
+
const { result: verified, errors } = await recordingBackgroundErrors(
|
|
5875
|
+
() => verify({
|
|
5876
|
+
makeApp,
|
|
5877
|
+
group: "independent",
|
|
5878
|
+
targetName: args.name ?? basename(root),
|
|
5879
|
+
targetPath: root,
|
|
5880
|
+
// The generated workflow starts Postgres as a job service and names it in
|
|
5881
|
+
// DATABASE_URL; an adapter written against the reference reads
|
|
5882
|
+
// `env.databaseUrl` and would otherwise be handed "" in the one place it
|
|
5883
|
+
// matters most. The config still wins when it says.
|
|
5884
|
+
databaseUrl: config.databaseUrl ?? process.env["DATABASE_URL"],
|
|
5885
|
+
// A verify run reported from CI is matched to the hosted inspect of the
|
|
5886
|
+
// same commit; without this it arrives as a run about no commit at all.
|
|
5887
|
+
baseCommit: commitOf(root)
|
|
5888
|
+
})
|
|
5889
|
+
);
|
|
5890
|
+
const run = errors.length === 0 ? verified : {
|
|
5891
|
+
...verified,
|
|
5892
|
+
limitations: [
|
|
5893
|
+
...verified.limitations,
|
|
5894
|
+
{
|
|
5895
|
+
scope: "application",
|
|
5896
|
+
reason: `The application raised ${errors.length} error(s) outside any scenario's control flow - an unhandled promise rejection or an uncaught exception - which would have ended this process before the report existed. Recorded rather than fatal: the scenarios decided what they decided, and the errors are the application's to explain. First: ${errors[0]}`
|
|
5897
|
+
}
|
|
5898
|
+
]
|
|
5899
|
+
};
|
|
5802
5900
|
emit(run, args, null);
|
|
5803
5901
|
return verdictExit(run);
|
|
5804
5902
|
} catch (error) {
|
|
@@ -6176,13 +6274,12 @@ async function runCli(argv) {
|
|
|
6176
6274
|
}
|
|
6177
6275
|
|
|
6178
6276
|
// packages/cli/src/bin.ts
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6277
|
+
function exit(code) {
|
|
6278
|
+
process.exitCode = code;
|
|
6279
|
+
process.stdout.write("", () => process.exit(code));
|
|
6280
|
+
}
|
|
6281
|
+
runCli(process.argv.slice(2)).then(exit, (error) => {
|
|
6282
|
+
process.stderr.write(`voeu: ${error instanceof Error ? error.message : String(error)}
|
|
6185
6283
|
`);
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
);
|
|
6284
|
+
exit(3);
|
|
6285
|
+
});
|
package/package.json
CHANGED