@ontrails/trails 1.0.0-beta.4 → 1.0.0-beta.41
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/CHANGELOG.md +1104 -4
- package/README.md +27 -0
- package/package.json +31 -7
- package/src/app.ts +147 -3
- package/src/cli.ts +345 -11
- package/src/completions.ts +240 -0
- package/src/lifecycle-source-io.ts +33 -0
- package/src/load-app-mirror.ts +202 -0
- package/src/local-state-io.ts +173 -0
- package/src/mcp-app.ts +42 -0
- package/src/mcp-options.ts +91 -0
- package/src/mcp.ts +8 -0
- package/src/project-writes.ts +377 -0
- package/src/regrade/config.ts +152 -0
- package/src/regrade/history.ts +430 -0
- package/src/regrade/live-api-preserve.ts +8 -0
- package/src/regrade/plan-artifact.ts +285 -0
- package/src/release/bindings.ts +39 -0
- package/src/release/check.ts +844 -0
- package/src/release/config.ts +63 -0
- package/src/release/contract-facts.ts +425 -0
- package/src/release/index.ts +142 -0
- package/src/release/lock-roundtrip-smoke.ts +230 -0
- package/src/release/native-bun-publish.ts +651 -0
- package/src/release/native-bun-registry.ts +765 -0
- package/src/release/notes-cli.ts +171 -0
- package/src/release/notes.ts +390 -0
- package/src/release/pack-coherence.ts +455 -0
- package/src/release/packed-artifacts-smoke.ts +236 -0
- package/src/release/policy.ts +1687 -0
- package/src/release/semver.ts +104 -0
- package/src/release/smoke.ts +56 -0
- package/src/release/wayfinder-dogfood-smoke.ts +760 -0
- package/src/retired-topo-command.ts +36 -0
- package/src/run-adapter-check.ts +76 -0
- package/src/run-collision.ts +126 -0
- package/src/run-completions-install.ts +179 -0
- package/src/run-example.ts +149 -0
- package/src/run-examples.ts +148 -0
- package/src/run-quiet.ts +75 -0
- package/src/run-release-check.ts +74 -0
- package/src/run-schema.ts +74 -0
- package/src/run-trace.ts +273 -0
- package/src/run-warden.ts +39 -0
- package/src/run-watch.ts +432 -0
- package/src/run-wayfind-outline.ts +170 -0
- package/src/scaffold-version-sync.ts +183 -0
- package/src/scaffold-versions.generated.ts +12 -0
- package/src/trails/adapter-check.ts +244 -0
- package/src/trails/add-surface.ts +99 -45
- package/src/trails/add-trail.ts +84 -37
- package/src/trails/add-verify.ts +100 -30
- package/src/trails/compile.ts +58 -0
- package/src/trails/completions-complete.ts +165 -0
- package/src/trails/completions.ts +47 -0
- package/src/trails/create-adapter.ts +785 -0
- package/src/trails/create-scaffold.ts +401 -106
- package/src/trails/create-versions.ts +62 -0
- package/src/trails/create.ts +186 -72
- package/src/trails/deprecate.ts +59 -0
- package/src/trails/dev-clean.ts +82 -0
- package/src/trails/dev-reset.ts +50 -0
- package/src/trails/dev-stats.ts +72 -0
- package/src/trails/dev-support.ts +360 -0
- package/src/trails/doctor.ts +77 -0
- package/src/trails/draft-promote.ts +949 -0
- package/src/trails/guide.ts +70 -68
- package/src/trails/load-app.ts +1123 -15
- package/src/trails/operator-context.ts +66 -0
- package/src/trails/project.ts +17 -3
- package/src/trails/regrade.ts +3091 -0
- package/src/trails/release-check.ts +105 -0
- package/src/trails/release-smoke.ts +49 -0
- package/src/trails/revise.ts +53 -0
- package/src/trails/root-dir.ts +21 -0
- package/src/trails/run-example.ts +475 -0
- package/src/trails/run-examples.ts +129 -0
- package/src/trails/run.ts +434 -0
- package/src/trails/scaffold-json.ts +58 -0
- package/src/trails/survey.ts +877 -214
- package/src/trails/topo-activation.ts +14 -0
- package/src/trails/topo-constants.ts +2 -0
- package/src/trails/topo-history.ts +47 -0
- package/src/trails/topo-output-schemas.ts +259 -0
- package/src/trails/topo-pin.ts +38 -0
- package/src/trails/topo-read-support.ts +368 -0
- package/src/trails/topo-reports.ts +809 -0
- package/src/trails/topo-store-support.ts +325 -0
- package/src/trails/topo-support.ts +220 -0
- package/src/trails/topo-unpin.ts +61 -0
- package/src/trails/topo.ts +92 -0
- package/src/trails/validate.ts +27 -0
- package/src/trails/version-lifecycle-support.ts +936 -0
- package/src/trails/warden-guide.ts +134 -0
- package/src/trails/warden.ts +198 -58
- package/src/trails/wayfind-outline.ts +876 -0
- package/src/trails/wayfind.ts +1053 -0
- package/src/versions.ts +31 -0
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +0 -1
- package/__tests__/examples.test.ts +0 -6
- package/dist/bin/trails.d.ts +0 -3
- package/dist/bin/trails.d.ts.map +0 -1
- package/dist/bin/trails.js +0 -4
- package/dist/bin/trails.js.map +0 -1
- package/dist/src/app.d.ts +0 -2
- package/dist/src/app.d.ts.map +0 -1
- package/dist/src/app.js +0 -11
- package/dist/src/app.js.map +0 -1
- package/dist/src/clack.d.ts +0 -9
- package/dist/src/clack.d.ts.map +0 -1
- package/dist/src/clack.js +0 -84
- package/dist/src/clack.js.map +0 -1
- package/dist/src/cli.d.ts +0 -2
- package/dist/src/cli.d.ts.map +0 -1
- package/dist/src/cli.js +0 -13
- package/dist/src/cli.js.map +0 -1
- package/dist/src/trails/add-surface.d.ts +0 -13
- package/dist/src/trails/add-surface.d.ts.map +0 -1
- package/dist/src/trails/add-surface.js +0 -88
- package/dist/src/trails/add-surface.js.map +0 -1
- package/dist/src/trails/add-trail.d.ts +0 -10
- package/dist/src/trails/add-trail.d.ts.map +0 -1
- package/dist/src/trails/add-trail.js +0 -77
- package/dist/src/trails/add-trail.js.map +0 -1
- package/dist/src/trails/add-verify.d.ts +0 -10
- package/dist/src/trails/add-verify.d.ts.map +0 -1
- package/dist/src/trails/add-verify.js +0 -67
- package/dist/src/trails/add-verify.js.map +0 -1
- package/dist/src/trails/create-scaffold.d.ts +0 -15
- package/dist/src/trails/create-scaffold.d.ts.map +0 -1
- package/dist/src/trails/create-scaffold.js +0 -288
- package/dist/src/trails/create-scaffold.js.map +0 -1
- package/dist/src/trails/create.d.ts +0 -22
- package/dist/src/trails/create.d.ts.map +0 -1
- package/dist/src/trails/create.js +0 -121
- package/dist/src/trails/create.js.map +0 -1
- package/dist/src/trails/guide.d.ts +0 -11
- package/dist/src/trails/guide.d.ts.map +0 -1
- package/dist/src/trails/guide.js +0 -80
- package/dist/src/trails/guide.js.map +0 -1
- package/dist/src/trails/load-app.d.ts +0 -4
- package/dist/src/trails/load-app.d.ts.map +0 -1
- package/dist/src/trails/load-app.js +0 -24
- package/dist/src/trails/load-app.js.map +0 -1
- package/dist/src/trails/project.d.ts +0 -8
- package/dist/src/trails/project.d.ts.map +0 -1
- package/dist/src/trails/project.js +0 -43
- package/dist/src/trails/project.js.map +0 -1
- package/dist/src/trails/survey.d.ts +0 -31
- package/dist/src/trails/survey.d.ts.map +0 -1
- package/dist/src/trails/survey.js +0 -221
- package/dist/src/trails/survey.js.map +0 -1
- package/dist/src/trails/warden.d.ts +0 -19
- package/dist/src/trails/warden.d.ts.map +0 -1
- package/dist/src/trails/warden.js +0 -88
- package/dist/src/trails/warden.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/src/__tests__/create.test.ts +0 -349
- package/src/__tests__/guide.test.ts +0 -91
- package/src/__tests__/load-app.test.ts +0 -15
- package/src/__tests__/survey.test.ts +0 -159
- package/src/__tests__/warden.test.ts +0 -74
- package/tsconfig.json +0 -9
|
@@ -0,0 +1,760 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const repoRoot = resolve(process.cwd());
|
|
7
|
+
const trailsBin = join(repoRoot, 'apps/trails/bin/trails.ts');
|
|
8
|
+
|
|
9
|
+
type JsonObject = Record<string, unknown>;
|
|
10
|
+
|
|
11
|
+
export interface WayfinderDogfoodSmokeResult {
|
|
12
|
+
readonly check: 'wayfinder-dogfood';
|
|
13
|
+
readonly message: string;
|
|
14
|
+
readonly passed: true;
|
|
15
|
+
readonly trailCount: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const assertObject = (value: unknown, label: string): JsonObject => {
|
|
19
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
20
|
+
throw new Error(`${label} did not return a JSON object`);
|
|
21
|
+
}
|
|
22
|
+
return value as JsonObject;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const assertAlignedSource = (value: JsonObject, label: string): void => {
|
|
26
|
+
const drift = assertObject(value['drift'], `${label}.drift`);
|
|
27
|
+
if (drift['status'] !== 'aligned') {
|
|
28
|
+
throw new Error(`${label} did not read fresh artifacts`);
|
|
29
|
+
}
|
|
30
|
+
const source = assertObject(value['source'], `${label}.source`);
|
|
31
|
+
if (source['kind'] !== 'topoGraph') {
|
|
32
|
+
throw new Error(`${label} did not read the TopoGraph source`);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const parseJson = (stdout: string, label: string): JsonObject => {
|
|
37
|
+
try {
|
|
38
|
+
return assertObject(JSON.parse(stdout) as unknown, label);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
throw new Error(`${label} did not produce valid JSON`, { cause: error });
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const runCommandProcess = (
|
|
45
|
+
command: readonly string[]
|
|
46
|
+
): {
|
|
47
|
+
readonly exitCode: number;
|
|
48
|
+
readonly stderr: string;
|
|
49
|
+
readonly stdout: string;
|
|
50
|
+
} => {
|
|
51
|
+
const result = Bun.spawnSync({
|
|
52
|
+
cmd: [...command],
|
|
53
|
+
cwd: repoRoot,
|
|
54
|
+
env: { ...process.env, NO_COLOR: '1' } as Record<
|
|
55
|
+
string,
|
|
56
|
+
string | undefined
|
|
57
|
+
>,
|
|
58
|
+
stderr: 'pipe',
|
|
59
|
+
stdout: 'pipe',
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
exitCode: result.exitCode,
|
|
63
|
+
stderr: result.stderr.toString(),
|
|
64
|
+
stdout: result.stdout.toString(),
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const runCommand = (command: readonly string[], label: string): JsonObject => {
|
|
69
|
+
const result = runCommandProcess(command);
|
|
70
|
+
if (result.exitCode !== 0) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
[
|
|
73
|
+
`Wayfinder dogfood command failed: ${command.join(' ')}`,
|
|
74
|
+
`exitCode: ${result.exitCode}`,
|
|
75
|
+
`stdout: ${result.stdout}`,
|
|
76
|
+
`stderr: ${result.stderr}`,
|
|
77
|
+
].join('\n')
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return parseJson(result.stdout, label);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const runFailingCommand = (
|
|
84
|
+
command: readonly string[],
|
|
85
|
+
label: string
|
|
86
|
+
): JsonObject => {
|
|
87
|
+
const result = runCommandProcess(command);
|
|
88
|
+
if (result.exitCode === 0) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
[
|
|
91
|
+
`Wayfinder dogfood command unexpectedly succeeded: ${command.join(' ')}`,
|
|
92
|
+
`stdout: ${result.stdout}`,
|
|
93
|
+
].join('\n')
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
return parseJson(
|
|
97
|
+
result.stdout.length > 0 ? result.stdout : result.stderr,
|
|
98
|
+
label
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const runTrails = (tempRoot: string, args: readonly string[]): JsonObject => {
|
|
103
|
+
const command = [
|
|
104
|
+
process.execPath,
|
|
105
|
+
trailsBin,
|
|
106
|
+
...args,
|
|
107
|
+
'--root-dir',
|
|
108
|
+
tempRoot,
|
|
109
|
+
'--json',
|
|
110
|
+
];
|
|
111
|
+
return runCommand(command, `trails ${args.join(' ')}`);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const runFailingTrails = (
|
|
115
|
+
tempRoot: string,
|
|
116
|
+
args: readonly string[]
|
|
117
|
+
): JsonObject => {
|
|
118
|
+
const command = [
|
|
119
|
+
process.execPath,
|
|
120
|
+
trailsBin,
|
|
121
|
+
...args,
|
|
122
|
+
'--root-dir',
|
|
123
|
+
tempRoot,
|
|
124
|
+
'--json',
|
|
125
|
+
];
|
|
126
|
+
return runFailingCommand(command, `trails ${args.join(' ')}`);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const runWayfind = (tempRoot: string, args: readonly string[]): JsonObject =>
|
|
130
|
+
runTrails(tempRoot, ['wayfind', ...args]);
|
|
131
|
+
|
|
132
|
+
const runSchema = (args: readonly string[]): JsonObject =>
|
|
133
|
+
runCommand([process.execPath, trailsBin, 'schema', ...args], 'trails schema');
|
|
134
|
+
|
|
135
|
+
const writeOperatorAppWrapper = async (tempRoot: string): Promise<void> => {
|
|
136
|
+
const srcDir = join(tempRoot, 'src');
|
|
137
|
+
await mkdir(srcDir, { recursive: true });
|
|
138
|
+
const appModuleUrl = pathToFileURL(join(repoRoot, 'apps/trails/src/app.ts'));
|
|
139
|
+
await writeFile(
|
|
140
|
+
join(srcDir, 'app.ts'),
|
|
141
|
+
`export { app, trailsOverlays } from ${JSON.stringify(appModuleUrl.href)};\n`
|
|
142
|
+
);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const writeDemoAppWrapper = async (tempRoot: string): Promise<void> => {
|
|
146
|
+
const srcDir = join(tempRoot, 'src');
|
|
147
|
+
await mkdir(srcDir, { recursive: true });
|
|
148
|
+
const appModuleUrl = pathToFileURL(
|
|
149
|
+
join(repoRoot, 'apps/trails-demo/src/app.ts')
|
|
150
|
+
);
|
|
151
|
+
await writeFile(
|
|
152
|
+
join(srcDir, 'demo-app.ts'),
|
|
153
|
+
`export { graph as app } from ${JSON.stringify(appModuleUrl.href)};\n`
|
|
154
|
+
);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const writeJson = async (
|
|
158
|
+
path: string,
|
|
159
|
+
value: Readonly<Record<string, unknown>>
|
|
160
|
+
): Promise<void> => {
|
|
161
|
+
await mkdir(dirname(path), { recursive: true });
|
|
162
|
+
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const writeAdapterWorkspace = async (tempRoot: string): Promise<void> => {
|
|
166
|
+
await writeJson(join(tempRoot, 'package.json'), {
|
|
167
|
+
name: 'wayfinder-dogfood-root',
|
|
168
|
+
workspaces: ['packages/*', 'adapters/*'],
|
|
169
|
+
});
|
|
170
|
+
await writeJson(join(tempRoot, 'packages/http/package.json'), {
|
|
171
|
+
exports: {
|
|
172
|
+
'.': './src/index.ts',
|
|
173
|
+
'./package.json': './package.json',
|
|
174
|
+
'./testing': './src/testing.ts',
|
|
175
|
+
},
|
|
176
|
+
name: '@ontrails/http',
|
|
177
|
+
trails: {
|
|
178
|
+
adapterTargets: {
|
|
179
|
+
http: {
|
|
180
|
+
conformance: {
|
|
181
|
+
adapterType: 'HttpAdapterConformanceAdapter',
|
|
182
|
+
casesFactory: 'createHttpAdapterConformanceCases',
|
|
183
|
+
runner: 'runConformance',
|
|
184
|
+
},
|
|
185
|
+
placements: ['extracted'],
|
|
186
|
+
testingImport: '@ontrails/http/testing',
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
await mkdir(join(tempRoot, 'packages/http/src'), { recursive: true });
|
|
192
|
+
await writeFile(
|
|
193
|
+
join(tempRoot, 'packages/http/src/index.ts'),
|
|
194
|
+
'export const http = {};\n'
|
|
195
|
+
);
|
|
196
|
+
await writeFile(
|
|
197
|
+
join(tempRoot, 'packages/http/src/testing.ts'),
|
|
198
|
+
[
|
|
199
|
+
'export interface HttpAdapterConformanceAdapter {}',
|
|
200
|
+
'export const createHttpAdapterConformanceCases = () => [];',
|
|
201
|
+
'export const runConformance = () => undefined;',
|
|
202
|
+
'',
|
|
203
|
+
].join('\n')
|
|
204
|
+
);
|
|
205
|
+
await writeJson(join(tempRoot, 'adapters/hono/package.json'), {
|
|
206
|
+
dependencies: {
|
|
207
|
+
'@ontrails/core': 'workspace:^',
|
|
208
|
+
hono: '^4.7.0',
|
|
209
|
+
},
|
|
210
|
+
exports: {
|
|
211
|
+
'.': './src/index.ts',
|
|
212
|
+
'./package.json': './package.json',
|
|
213
|
+
},
|
|
214
|
+
name: '@ontrails/hono',
|
|
215
|
+
peerDependencies: {
|
|
216
|
+
'@ontrails/http': 'workspace:^',
|
|
217
|
+
},
|
|
218
|
+
trails: {
|
|
219
|
+
adapter: {
|
|
220
|
+
target: 'http',
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
await mkdir(join(tempRoot, 'adapters/hono/src/__tests__'), {
|
|
225
|
+
recursive: true,
|
|
226
|
+
});
|
|
227
|
+
await writeFile(
|
|
228
|
+
join(tempRoot, 'adapters/hono/src/index.ts'),
|
|
229
|
+
'export const honoAdapter = {};\n'
|
|
230
|
+
);
|
|
231
|
+
await writeFile(
|
|
232
|
+
join(tempRoot, 'adapters/hono/src/__tests__/conformance.test.ts'),
|
|
233
|
+
[
|
|
234
|
+
"import { createHttpAdapterConformanceCases, runConformance } from '@ontrails/http/testing';",
|
|
235
|
+
'',
|
|
236
|
+
"runConformance({ name: '@ontrails/hono' }, createHttpAdapterConformanceCases());",
|
|
237
|
+
'',
|
|
238
|
+
].join('\n')
|
|
239
|
+
);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const assertSearchFindsWayfinder = (search: JsonObject): void => {
|
|
243
|
+
const { matches } = search;
|
|
244
|
+
if (!Array.isArray(matches)) {
|
|
245
|
+
throw new TypeError('wayfind search did not return matches');
|
|
246
|
+
}
|
|
247
|
+
const ids = matches
|
|
248
|
+
.map((match) => assertObject(match, 'wayfind search match')['id'])
|
|
249
|
+
.filter((id): id is string => typeof id === 'string');
|
|
250
|
+
if (!ids.includes('wayfind.search')) {
|
|
251
|
+
throw new Error('wayfind search did not find wayfind.search');
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const assertDiffIsEmpty = (diffResult: JsonObject): void => {
|
|
256
|
+
const diff = assertObject(diffResult['diff'], 'wayfind diff');
|
|
257
|
+
if (diff['hasBreaking'] !== false) {
|
|
258
|
+
throw new Error(
|
|
259
|
+
'wayfind diff reported breaking changes for same-root diff'
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const assertTrailsFindsWayfinder = (trailsResult: JsonObject): void => {
|
|
265
|
+
const { trails } = trailsResult;
|
|
266
|
+
if (!Array.isArray(trails)) {
|
|
267
|
+
throw new TypeError('wayfind --trails did not return trails');
|
|
268
|
+
}
|
|
269
|
+
const ids = trails
|
|
270
|
+
.map((entry) => assertObject(entry, 'wayfind trail entry')['id'])
|
|
271
|
+
.filter((id): id is string => typeof id === 'string');
|
|
272
|
+
if (!ids.includes('wayfind.search')) {
|
|
273
|
+
throw new Error('wayfind --trails did not find wayfind.search');
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const assertRoutePaths = (
|
|
278
|
+
routes: unknown,
|
|
279
|
+
label: string,
|
|
280
|
+
expected: readonly string[]
|
|
281
|
+
): void => {
|
|
282
|
+
if (!Array.isArray(routes)) {
|
|
283
|
+
throw new TypeError(`${label} did not return command routes`);
|
|
284
|
+
}
|
|
285
|
+
const routePaths = new Set(
|
|
286
|
+
routes
|
|
287
|
+
.map((route) => assertObject(route, `${label} route`)['path'])
|
|
288
|
+
.filter(
|
|
289
|
+
(path): path is string[] =>
|
|
290
|
+
Array.isArray(path) &&
|
|
291
|
+
path.every((segment) => typeof segment === 'string')
|
|
292
|
+
)
|
|
293
|
+
.map((path) => path.join(' '))
|
|
294
|
+
);
|
|
295
|
+
for (const expectedPath of expected) {
|
|
296
|
+
if (!routePaths.has(expectedPath)) {
|
|
297
|
+
throw new Error(`${label} did not include ${expectedPath}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const assertSchemaForWayfind = (schema: JsonObject): void => {
|
|
303
|
+
const command = assertObject(schema['command'], 'schema command');
|
|
304
|
+
if (command['trailId'] !== 'wayfind.navigate') {
|
|
305
|
+
throw new Error('schema did not inspect wayfind.navigate');
|
|
306
|
+
}
|
|
307
|
+
assertRoutePaths(command['routes'], 'schema', ['wayfind']);
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const assertErrorsFindWayfinder = (errorsResult: JsonObject): void => {
|
|
311
|
+
const { errors } = errorsResult;
|
|
312
|
+
if (!Array.isArray(errors)) {
|
|
313
|
+
throw new TypeError('wayfind errors did not return errors');
|
|
314
|
+
}
|
|
315
|
+
const searchEntry = errors
|
|
316
|
+
.map((entry) => assertObject(entry, 'wayfind errors entry'))
|
|
317
|
+
.find((entry) => entry['trailId'] === 'wayfind.search');
|
|
318
|
+
if (searchEntry === undefined) {
|
|
319
|
+
throw new Error('wayfind errors did not include wayfind.search');
|
|
320
|
+
}
|
|
321
|
+
const completeness = assertObject(
|
|
322
|
+
searchEntry['completeness'],
|
|
323
|
+
'wayfind errors completeness'
|
|
324
|
+
);
|
|
325
|
+
const emitted = assertObject(
|
|
326
|
+
completeness['emitted'],
|
|
327
|
+
'wayfind errors emitted completeness'
|
|
328
|
+
);
|
|
329
|
+
if (emitted['status'] !== 'unknown') {
|
|
330
|
+
throw new Error('wayfind errors overclaimed emitted-error completeness');
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const assertDemoResources = (resourcesResult: JsonObject): void => {
|
|
335
|
+
const { resources } = resourcesResult;
|
|
336
|
+
if (!Array.isArray(resources)) {
|
|
337
|
+
throw new TypeError('demo wayfind --resources did not return resources');
|
|
338
|
+
}
|
|
339
|
+
const entityStore = resources
|
|
340
|
+
.map((entry) => assertObject(entry, 'demo resource entry'))
|
|
341
|
+
.find((entry) => entry['id'] === 'demo.entity-store');
|
|
342
|
+
if (entityStore === undefined) {
|
|
343
|
+
throw new Error('demo wayfind --resources did not find demo.entity-store');
|
|
344
|
+
}
|
|
345
|
+
const { usedBy } = entityStore;
|
|
346
|
+
if (!Array.isArray(usedBy) || !usedBy.includes('entity.add')) {
|
|
347
|
+
throw new Error('demo resource usage did not include entity.add');
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const assertDemoSignals = (signalsResult: JsonObject): void => {
|
|
352
|
+
const { signals } = signalsResult;
|
|
353
|
+
if (!Array.isArray(signals)) {
|
|
354
|
+
throw new TypeError('demo wayfind --signals did not return signals');
|
|
355
|
+
}
|
|
356
|
+
const entityUpdated = signals
|
|
357
|
+
.map((entry) => assertObject(entry, 'demo signal entry'))
|
|
358
|
+
.find((entry) => entry['id'] === 'entity.updated');
|
|
359
|
+
if (entityUpdated === undefined) {
|
|
360
|
+
throw new Error('demo wayfind --signals did not find entity.updated');
|
|
361
|
+
}
|
|
362
|
+
const { consumers, producers } = entityUpdated;
|
|
363
|
+
if (!Array.isArray(producers) || !producers.includes('entity.add')) {
|
|
364
|
+
throw new Error('entity.updated producers did not include entity.add');
|
|
365
|
+
}
|
|
366
|
+
if (
|
|
367
|
+
!Array.isArray(consumers) ||
|
|
368
|
+
!consumers.includes('entity.notify-updated')
|
|
369
|
+
) {
|
|
370
|
+
throw new Error(
|
|
371
|
+
'entity.updated consumers did not include entity.notify-updated'
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
const assertDemoErrors = (errorsResult: JsonObject): void => {
|
|
377
|
+
const { errors } = errorsResult;
|
|
378
|
+
if (!Array.isArray(errors)) {
|
|
379
|
+
throw new TypeError('demo wayfind --errors did not return errors');
|
|
380
|
+
}
|
|
381
|
+
const entityAdd = errors
|
|
382
|
+
.map((entry) => assertObject(entry, 'demo errors entry'))
|
|
383
|
+
.find((entry) => entry['trailId'] === 'entity.add');
|
|
384
|
+
if (entityAdd === undefined) {
|
|
385
|
+
throw new Error('demo wayfind --errors did not include entity.add');
|
|
386
|
+
}
|
|
387
|
+
const { facts } = entityAdd;
|
|
388
|
+
if (!Array.isArray(facts)) {
|
|
389
|
+
throw new TypeError('demo entity.add errors did not return facts');
|
|
390
|
+
}
|
|
391
|
+
const hasAlreadyExists = facts
|
|
392
|
+
.map((fact) => assertObject(fact, 'demo entity.add error fact'))
|
|
393
|
+
.some((fact) => {
|
|
394
|
+
const taxonomy = assertObject(
|
|
395
|
+
fact['taxonomy'],
|
|
396
|
+
'demo entity.add error taxonomy'
|
|
397
|
+
);
|
|
398
|
+
return taxonomy['name'] === 'AlreadyExistsError';
|
|
399
|
+
});
|
|
400
|
+
if (!hasAlreadyExists) {
|
|
401
|
+
throw new Error(
|
|
402
|
+
'demo entity.add errors did not include AlreadyExistsError'
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
const assertRelationIncludes = (
|
|
408
|
+
relationResult: JsonObject,
|
|
409
|
+
label: string,
|
|
410
|
+
expected: {
|
|
411
|
+
readonly from?: string;
|
|
412
|
+
readonly relation: string;
|
|
413
|
+
readonly to?: string;
|
|
414
|
+
}
|
|
415
|
+
): void => {
|
|
416
|
+
const { edges } = relationResult;
|
|
417
|
+
if (!Array.isArray(edges)) {
|
|
418
|
+
throw new TypeError(`${label} did not return relation edges`);
|
|
419
|
+
}
|
|
420
|
+
const found = edges
|
|
421
|
+
.map((edge) => assertObject(edge, `${label} edge`))
|
|
422
|
+
.some((edge) => {
|
|
423
|
+
const from = assertObject(edge['from'], `${label} edge from`);
|
|
424
|
+
const to = assertObject(edge['to'], `${label} edge to`);
|
|
425
|
+
return (
|
|
426
|
+
edge['relation'] === expected.relation &&
|
|
427
|
+
(expected.from === undefined || from['id'] === expected.from) &&
|
|
428
|
+
(expected.to === undefined || to['id'] === expected.to)
|
|
429
|
+
);
|
|
430
|
+
});
|
|
431
|
+
if (!found) {
|
|
432
|
+
throw new Error(`${label} did not include expected relation edge`);
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
const assertValidationMessage = (
|
|
437
|
+
failure: JsonObject,
|
|
438
|
+
expected: string
|
|
439
|
+
): void => {
|
|
440
|
+
const error = assertObject(failure['error'], 'expected validation error');
|
|
441
|
+
const { message } = error;
|
|
442
|
+
if (typeof message !== 'string' || !message.includes(expected)) {
|
|
443
|
+
throw new Error(`Validation error did not include: ${expected}`);
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
const assertAdapterPredicateWayfind = (wayfindResult: JsonObject): void => {
|
|
448
|
+
const result = assertObject(wayfindResult['result'], 'wayfind --adapter');
|
|
449
|
+
const { matches } = result;
|
|
450
|
+
if (!Array.isArray(matches)) {
|
|
451
|
+
throw new TypeError('wayfind --adapter did not return matches');
|
|
452
|
+
}
|
|
453
|
+
const includes = assertObject(wayfindResult['includes'], 'adapter includes');
|
|
454
|
+
const adaptersResult = assertObject(includes['adapters'], 'include adapters');
|
|
455
|
+
const { adapters } = adaptersResult;
|
|
456
|
+
if (!Array.isArray(adapters)) {
|
|
457
|
+
throw new TypeError('wayfind --include adapters did not return adapters');
|
|
458
|
+
}
|
|
459
|
+
const configuredHono = adapters
|
|
460
|
+
.map((adapter) => assertObject(adapter, 'wayfind adapter fact'))
|
|
461
|
+
.some(
|
|
462
|
+
(adapter) =>
|
|
463
|
+
adapter['kind'] === 'configured' &&
|
|
464
|
+
adapter['packageName'] === '@ontrails/hono' &&
|
|
465
|
+
adapter['target'] === 'http'
|
|
466
|
+
);
|
|
467
|
+
if (!configuredHono) {
|
|
468
|
+
throw new Error('wayfind --adapter did not include Hono adapter facts');
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
const assertContractForSearch = (contractResult: JsonObject): void => {
|
|
473
|
+
const contract = assertObject(contractResult['contract'], 'contract');
|
|
474
|
+
if (contract['id'] !== 'wayfind.search') {
|
|
475
|
+
throw new Error('wayfind contract did not inspect wayfind.search');
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
const unwrapWayfindResult = (value: JsonObject, label: string): JsonObject =>
|
|
480
|
+
assertObject(value['result'], `${label}.result`);
|
|
481
|
+
|
|
482
|
+
const assertResolvedTarget = (value: JsonObject, label: string): void => {
|
|
483
|
+
const target = assertObject(value['target'], `${label}.target`);
|
|
484
|
+
if (target['id'] !== 'wayfind.search') {
|
|
485
|
+
throw new Error(`${label} did not resolve wayfind.search`);
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
const assertLiveSourceFindsWayfinder = (value: JsonObject): void => {
|
|
490
|
+
const { matches } = value;
|
|
491
|
+
if (!Array.isArray(matches)) {
|
|
492
|
+
throw new TypeError('wayfind --source live did not return matches');
|
|
493
|
+
}
|
|
494
|
+
const found = matches
|
|
495
|
+
.map((match) => assertObject(match, 'wayfind --source live match'))
|
|
496
|
+
.some((match) => {
|
|
497
|
+
const detail = assertObject(
|
|
498
|
+
match['detail'],
|
|
499
|
+
'wayfind --source live match detail'
|
|
500
|
+
);
|
|
501
|
+
return detail['id'] === 'wayfind.search';
|
|
502
|
+
});
|
|
503
|
+
if (!found) {
|
|
504
|
+
throw new Error('wayfind --source live did not resolve wayfind.search');
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
const assertOutlineFindsOperatorApp = (outline: JsonObject): void => {
|
|
509
|
+
const features = assertObject(
|
|
510
|
+
outline['features'],
|
|
511
|
+
'wayfind outline features'
|
|
512
|
+
);
|
|
513
|
+
if (features['view'] !== 'default') {
|
|
514
|
+
throw new Error('wayfind outline did not echo the default feature view');
|
|
515
|
+
}
|
|
516
|
+
const { apps } = outline;
|
|
517
|
+
if (!Array.isArray(apps)) {
|
|
518
|
+
throw new TypeError('wayfind outline did not return app facts');
|
|
519
|
+
}
|
|
520
|
+
const appNames = apps
|
|
521
|
+
.map((entry) => assertObject(entry, 'wayfind outline app')['name'])
|
|
522
|
+
.filter((name): name is string => typeof name === 'string');
|
|
523
|
+
if (!appNames.includes('operatorApp')) {
|
|
524
|
+
throw new Error('wayfind outline did not find the operator app export');
|
|
525
|
+
}
|
|
526
|
+
if (outline['file'] !== 'apps/trails/src/app.ts') {
|
|
527
|
+
throw new Error('wayfind outline did not preserve the source file path');
|
|
528
|
+
}
|
|
529
|
+
const counts = assertObject(outline['counts'], 'wayfind outline counts');
|
|
530
|
+
if (counts['apps'] !== apps.length) {
|
|
531
|
+
throw new Error('wayfind outline counts diverged from app facts');
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
export const runWayfinderDogfoodSmoke =
|
|
536
|
+
async (): Promise<WayfinderDogfoodSmokeResult> => {
|
|
537
|
+
const tempRoot = await mkdtemp(join(tmpdir(), 'trails-wayfinder-dogfood-'));
|
|
538
|
+
|
|
539
|
+
try {
|
|
540
|
+
await writeOperatorAppWrapper(tempRoot);
|
|
541
|
+
await writeDemoAppWrapper(tempRoot);
|
|
542
|
+
await writeAdapterWorkspace(tempRoot);
|
|
543
|
+
runTrails(tempRoot, [
|
|
544
|
+
'compile',
|
|
545
|
+
'--module',
|
|
546
|
+
'./src/demo-app.ts',
|
|
547
|
+
'--permit',
|
|
548
|
+
'{"id":"wayfinder-dogfood","scopes":["topo:write"]}',
|
|
549
|
+
]);
|
|
550
|
+
|
|
551
|
+
const demoResources = unwrapWayfindResult(
|
|
552
|
+
runWayfind(tempRoot, ['--resources']),
|
|
553
|
+
'demo wayfind --resources'
|
|
554
|
+
);
|
|
555
|
+
assertAlignedSource(demoResources, 'demo wayfind --resources');
|
|
556
|
+
assertDemoResources(demoResources);
|
|
557
|
+
|
|
558
|
+
const demoSignals = unwrapWayfindResult(
|
|
559
|
+
runWayfind(tempRoot, ['--signals']),
|
|
560
|
+
'demo wayfind --signals'
|
|
561
|
+
);
|
|
562
|
+
assertAlignedSource(demoSignals, 'demo wayfind --signals');
|
|
563
|
+
assertDemoSignals(demoSignals);
|
|
564
|
+
|
|
565
|
+
const demoErrors = unwrapWayfindResult(
|
|
566
|
+
runWayfind(tempRoot, ['--errors']),
|
|
567
|
+
'demo wayfind --errors'
|
|
568
|
+
);
|
|
569
|
+
assertAlignedSource(demoErrors, 'demo wayfind --errors');
|
|
570
|
+
assertDemoErrors(demoErrors);
|
|
571
|
+
|
|
572
|
+
const signalImpact = unwrapWayfindResult(
|
|
573
|
+
runWayfind(tempRoot, ['entity.updated', '--impact']),
|
|
574
|
+
'demo wayfind signal impact'
|
|
575
|
+
);
|
|
576
|
+
assertAlignedSource(signalImpact, 'demo wayfind signal impact');
|
|
577
|
+
assertRelationIncludes(signalImpact, 'demo signal impact', {
|
|
578
|
+
from: 'entity.updated',
|
|
579
|
+
relation: 'consumed-by',
|
|
580
|
+
to: 'entity.notify-updated',
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
const trailDeps = unwrapWayfindResult(
|
|
584
|
+
runWayfind(tempRoot, ['entity.add', '--deps']),
|
|
585
|
+
'demo wayfind trail deps'
|
|
586
|
+
);
|
|
587
|
+
assertAlignedSource(trailDeps, 'demo wayfind trail deps');
|
|
588
|
+
assertRelationIncludes(trailDeps, 'demo trail deps', {
|
|
589
|
+
from: 'demo.entity-store',
|
|
590
|
+
relation: 'used-by',
|
|
591
|
+
to: 'entity.add',
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
assertValidationMessage(
|
|
595
|
+
runFailingTrails(tempRoot, ['wayfind', '*.ts']),
|
|
596
|
+
'wayfind pattern <glob>'
|
|
597
|
+
);
|
|
598
|
+
assertValidationMessage(
|
|
599
|
+
runFailingTrails(tempRoot, [
|
|
600
|
+
'wayfind',
|
|
601
|
+
'entity.add',
|
|
602
|
+
'--impact',
|
|
603
|
+
'--deps',
|
|
604
|
+
]),
|
|
605
|
+
'Provide only one relation flag'
|
|
606
|
+
);
|
|
607
|
+
|
|
608
|
+
assertValidationMessage(
|
|
609
|
+
runFailingTrails(tempRoot, [
|
|
610
|
+
'compile',
|
|
611
|
+
'--module',
|
|
612
|
+
'./src/app.ts',
|
|
613
|
+
'--permit',
|
|
614
|
+
'{"id":"wayfinder-dogfood","scopes":["topo:write"]}',
|
|
615
|
+
]),
|
|
616
|
+
'breaking change'
|
|
617
|
+
);
|
|
618
|
+
const resourcesAfterRejectedCompile = unwrapWayfindResult(
|
|
619
|
+
runWayfind(tempRoot, ['--resources']),
|
|
620
|
+
'demo wayfind after rejected compile'
|
|
621
|
+
);
|
|
622
|
+
assertAlignedSource(
|
|
623
|
+
resourcesAfterRejectedCompile,
|
|
624
|
+
'demo wayfind after rejected compile'
|
|
625
|
+
);
|
|
626
|
+
assertDemoResources(resourcesAfterRejectedCompile);
|
|
627
|
+
|
|
628
|
+
runTrails(tempRoot, [
|
|
629
|
+
'compile',
|
|
630
|
+
'--module',
|
|
631
|
+
'./src/app.ts',
|
|
632
|
+
'--force',
|
|
633
|
+
'--permit',
|
|
634
|
+
'{"id":"wayfinder-dogfood","scopes":["topo:write"]}',
|
|
635
|
+
]);
|
|
636
|
+
|
|
637
|
+
const overview = unwrapWayfindResult(
|
|
638
|
+
runWayfind(tempRoot, ['--overview']),
|
|
639
|
+
'wayfind overview'
|
|
640
|
+
);
|
|
641
|
+
assertAlignedSource(overview, 'wayfind overview');
|
|
642
|
+
const counts = assertObject(
|
|
643
|
+
overview['counts'],
|
|
644
|
+
'wayfind overview counts'
|
|
645
|
+
);
|
|
646
|
+
const trailCount = counts['trails'];
|
|
647
|
+
if (typeof trailCount !== 'number' || trailCount < 1) {
|
|
648
|
+
throw new Error('wayfind overview did not report trail counts');
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const trails = unwrapWayfindResult(
|
|
652
|
+
runWayfind(tempRoot, ['--trails', '--intent', 'read']),
|
|
653
|
+
'wayfind --trails'
|
|
654
|
+
);
|
|
655
|
+
assertAlignedSource(trails, 'wayfind --trails');
|
|
656
|
+
assertTrailsFindsWayfinder(trails);
|
|
657
|
+
|
|
658
|
+
assertSchemaForWayfind(runSchema(['wayfind']));
|
|
659
|
+
|
|
660
|
+
const search = unwrapWayfindResult(
|
|
661
|
+
runWayfind(tempRoot, ['pattern', 'wayfind.*']),
|
|
662
|
+
'wayfind pattern'
|
|
663
|
+
);
|
|
664
|
+
assertAlignedSource(search, 'wayfind pattern');
|
|
665
|
+
assertSearchFindsWayfinder(search);
|
|
666
|
+
|
|
667
|
+
const live = unwrapWayfindResult(
|
|
668
|
+
runWayfind(tempRoot, ['wayfind.search', '--source', 'live']),
|
|
669
|
+
'wayfind --source live'
|
|
670
|
+
);
|
|
671
|
+
assertLiveSourceFindsWayfinder(live);
|
|
672
|
+
|
|
673
|
+
const resources = unwrapWayfindResult(
|
|
674
|
+
runWayfind(tempRoot, ['--resources']),
|
|
675
|
+
'wayfind --resources'
|
|
676
|
+
);
|
|
677
|
+
assertAlignedSource(resources, 'wayfind --resources');
|
|
678
|
+
|
|
679
|
+
const errors = unwrapWayfindResult(
|
|
680
|
+
runWayfind(tempRoot, ['--errors']),
|
|
681
|
+
'wayfind --errors'
|
|
682
|
+
);
|
|
683
|
+
assertAlignedSource(errors, 'wayfind --errors');
|
|
684
|
+
assertErrorsFindWayfinder(errors);
|
|
685
|
+
|
|
686
|
+
const adapterFiltered = runWayfind(tempRoot, [
|
|
687
|
+
'--adapter',
|
|
688
|
+
'@ontrails/hono',
|
|
689
|
+
'--include',
|
|
690
|
+
'adapters',
|
|
691
|
+
]);
|
|
692
|
+
assertAdapterPredicateWayfind(adapterFiltered);
|
|
693
|
+
|
|
694
|
+
const contract = unwrapWayfindResult(
|
|
695
|
+
runWayfind(tempRoot, ['wayfind.search', '--contract']),
|
|
696
|
+
'wayfind contract'
|
|
697
|
+
);
|
|
698
|
+
assertAlignedSource(contract, 'wayfind contract');
|
|
699
|
+
assertContractForSearch(contract);
|
|
700
|
+
|
|
701
|
+
const nearby = unwrapWayfindResult(
|
|
702
|
+
runWayfind(tempRoot, ['wayfind.search']),
|
|
703
|
+
'wayfind nearby'
|
|
704
|
+
);
|
|
705
|
+
assertAlignedSource(nearby, 'wayfind nearby');
|
|
706
|
+
assertResolvedTarget(nearby, 'wayfind nearby');
|
|
707
|
+
|
|
708
|
+
const impact = unwrapWayfindResult(
|
|
709
|
+
runWayfind(tempRoot, ['wayfind.search', '--impact']),
|
|
710
|
+
'wayfind impact'
|
|
711
|
+
);
|
|
712
|
+
assertAlignedSource(impact, 'wayfind impact');
|
|
713
|
+
assertResolvedTarget(impact, 'wayfind impact');
|
|
714
|
+
|
|
715
|
+
const deps = unwrapWayfindResult(
|
|
716
|
+
runWayfind(tempRoot, ['wayfind.search', '--deps']),
|
|
717
|
+
'wayfind deps'
|
|
718
|
+
);
|
|
719
|
+
assertAlignedSource(deps, 'wayfind deps');
|
|
720
|
+
assertResolvedTarget(deps, 'wayfind deps');
|
|
721
|
+
|
|
722
|
+
const diff = runWayfind(tempRoot, [
|
|
723
|
+
'diff',
|
|
724
|
+
'--against-root-dir',
|
|
725
|
+
tempRoot,
|
|
726
|
+
]);
|
|
727
|
+
assertAlignedSource(diff, 'wayfind diff');
|
|
728
|
+
assertDiffIsEmpty(diff);
|
|
729
|
+
|
|
730
|
+
const outline = runCommand(
|
|
731
|
+
[
|
|
732
|
+
process.execPath,
|
|
733
|
+
trailsBin,
|
|
734
|
+
'wayfind',
|
|
735
|
+
'file',
|
|
736
|
+
'apps/trails/src/app.ts',
|
|
737
|
+
'--root-dir',
|
|
738
|
+
repoRoot,
|
|
739
|
+
'--outline',
|
|
740
|
+
'--json',
|
|
741
|
+
],
|
|
742
|
+
'trails wayfind file'
|
|
743
|
+
);
|
|
744
|
+
assertOutlineFindsOperatorApp(unwrapWayfindResult(outline, 'outline'));
|
|
745
|
+
|
|
746
|
+
return {
|
|
747
|
+
check: 'wayfinder-dogfood',
|
|
748
|
+
message: `Wayfinder dogfood smoke passed: ${String(trailCount)} trails inspected from saved operator topo artifacts.`,
|
|
749
|
+
passed: true,
|
|
750
|
+
trailCount,
|
|
751
|
+
};
|
|
752
|
+
} finally {
|
|
753
|
+
await rm(tempRoot, { force: true, recursive: true });
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
if (import.meta.main) {
|
|
758
|
+
const result = await runWayfinderDogfoodSmoke();
|
|
759
|
+
console.log(result.message);
|
|
760
|
+
}
|