@volter/twin-standard 1.0.143 → 1.0.144
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/dist/src/conformance.js +5 -3
- package/dist/src/derive.js +8 -8
- package/dist/src/protocol-3.js +10 -7
- package/dist/src/walk/journey-kit.js +2 -1
- package/package.json +1 -1
- package/src/conformance.ts +5 -3
- package/src/derive.ts +8 -8
- package/src/protocol-3.ts +11 -7
- package/src/walk/journey-kit.ts +2 -1
package/dist/src/conformance.js
CHANGED
|
@@ -23,7 +23,9 @@ import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSy
|
|
|
23
23
|
import { createRequire } from 'node:module';
|
|
24
24
|
import { connect } from 'node:net';
|
|
25
25
|
import { tmpdir } from 'node:os';
|
|
26
|
-
import { basename, dirname, extname, join, matchesGlob, relative, resolve } from 'node:path';
|
|
26
|
+
import { basename, dirname, extname, join, matchesGlob, relative, resolve, sep } from 'node:path';
|
|
27
|
+
// A path relative to another with '/' between its parts on every platform (Windows' relative() joins with '\').
|
|
28
|
+
const rel = (from, to) => relative(from, to).split(sep).join('/');
|
|
27
29
|
import { citations, sourceHeld } from "./check-sources.js";
|
|
28
30
|
import { fileURLToPath } from 'node:url';
|
|
29
31
|
import { servedHttpFetch } from "./clients/harness.js";
|
|
@@ -34,7 +36,7 @@ const readJson = (p) => (existsSync(p) ? JSON.parse(readFileSync(p, 'utf8')) : u
|
|
|
34
36
|
function unitsIn(pack) {
|
|
35
37
|
return [pack, ...readdirSync(pack).sort().map((n) => join(pack, n)).filter((d) => !existsSync(join(d, 'package.json')) && existsSync(join(d, 'src', 'manifest.ts')))];
|
|
36
38
|
}
|
|
37
|
-
const nameOf = (pack, unit) =>
|
|
39
|
+
const nameOf = (pack, unit) => rel(resolve(pack, '..'), unit);
|
|
38
40
|
/** The files conformance reads from a unit: its manifest, its spec's record, its vendor's life and its decision table
|
|
39
41
|
* (a lane's journeys are its own `journeys/` or else its vendor's). A release publishes each (published()), and a
|
|
40
42
|
* pack's standing counts it in the form only when every unit holds them (twin-world's scripts/pack-standing.ts, by
|
|
@@ -192,7 +194,7 @@ function published(pack) {
|
|
|
192
194
|
}
|
|
193
195
|
const out = [];
|
|
194
196
|
for (const unit of unitsIn(pack)) {
|
|
195
|
-
const at =
|
|
197
|
+
const at = rel(pack, unit);
|
|
196
198
|
const of = (p) => (at ? `${at}/${p}` : p);
|
|
197
199
|
for (const need of UNIT_FILES) {
|
|
198
200
|
if (existsSync(join(unit, need)) && !files.has(of(need)))
|
package/dist/src/derive.js
CHANGED
|
@@ -16,7 +16,7 @@ import { parseExactJson, exactInteger } from '@volter/world-core/exact-json';
|
|
|
16
16
|
// Usage: twin-standard derive <pack dir> [--out <dir>] (twin-world: bun scripts/derive-pack.ts <vendor>)
|
|
17
17
|
import { parse as parseYaml } from 'yaml';
|
|
18
18
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
19
|
-
import { join } from 'node:path';
|
|
19
|
+
import { basename, join } from 'node:path';
|
|
20
20
|
import { gunzipSync } from 'node:zlib';
|
|
21
21
|
import { CRUD_CLASSES, fromOpenAPI, fromSmithy, fromSwagger2, slug } from "./spec-ir.js";
|
|
22
22
|
import { fromCommandReplies } from "./spec-ir-lines.js";
|
|
@@ -46,10 +46,10 @@ if (!specPath) {
|
|
|
46
46
|
console.error(`derive-pack: no spec at ${join(PKG, 'spec')}/openapi.{json,yaml}[.gz], smithy.json[.gz], rfcNNNN.txt, proto/, commands/ or client-ops.json`);
|
|
47
47
|
process.exit(1);
|
|
48
48
|
}
|
|
49
|
-
const rpcService = specPath
|
|
50
|
-
const severalDocuments = specPath
|
|
49
|
+
const rpcService = basename(specPath) === 'proto';
|
|
50
|
+
const severalDocuments = basename(specPath) === 'openapi';
|
|
51
51
|
// a command language's table, one vendored file per command (Redis's src/commands/*.json), with its version in SOURCE.md
|
|
52
|
-
const commandTable = specPath
|
|
52
|
+
const commandTable = basename(specPath) === 'commands';
|
|
53
53
|
const protoTree = (dir, at = '') => Object.fromEntries(readdirSync(join(dir, at), { withFileTypes: true }).flatMap((e) => (e.isDirectory() ? Object.entries(protoTree(dir, join(at, e.name))) : e.name.endsWith('.proto') ? [[join(at, e.name), readFileSync(join(dir, at, e.name), 'utf8')]] : [])));
|
|
54
54
|
const bytes = rpcService || commandTable || severalDocuments ? Buffer.from('') : readFileSync(specPath);
|
|
55
55
|
const text = (specPath.endsWith('.gz') ? gunzipSync(bytes) : bytes).toString('utf8');
|
|
@@ -195,7 +195,7 @@ mkdirSync(out, { recursive: true });
|
|
|
195
195
|
// name commands and cite the RFC.
|
|
196
196
|
if (lineProtocol || commandTable) {
|
|
197
197
|
const spec = doc;
|
|
198
|
-
writeFileSync(join(out, 'surface.gen.json'), `${JSON.stringify({ generatedBy: 'scripts/derive-pack.ts', spec: specPath
|
|
198
|
+
writeFileSync(join(out, 'surface.gen.json'), `${JSON.stringify({ generatedBy: 'scripts/derive-pack.ts', spec: basename(specPath), ...spec })}\n`);
|
|
199
199
|
const manifestPath = join(PKG, 'src', 'manifest.ts');
|
|
200
200
|
if (existsSync(manifestPath)) {
|
|
201
201
|
const { manifest } = (await import(__rewriteRelativeImportExtension(manifestPath)));
|
|
@@ -254,7 +254,7 @@ if (sdlPath) {
|
|
|
254
254
|
for (const o of gql.operations)
|
|
255
255
|
graphqlOperations.add(o.id);
|
|
256
256
|
mkdirSync(out, { recursive: true });
|
|
257
|
-
writeFileSync(join(out, 'graphql.gen.json'), `${JSON.stringify({ generatedBy: 'scripts/derive-pack.ts', spec: sdlPath
|
|
257
|
+
writeFileSync(join(out, 'graphql.gen.json'), `${JSON.stringify({ generatedBy: 'scripts/derive-pack.ts', spec: basename(sdlPath), ...gql })}\n`);
|
|
258
258
|
// the schema itself, for the pack's GraphQL wire to validate and execute against (imported lazily)
|
|
259
259
|
writeFileSync(join(out, 'graphql-sdl.gen.json'), `${JSON.stringify({ generatedBy: 'scripts/derive-pack.ts', sdl })}\n`);
|
|
260
260
|
const g = new Map();
|
|
@@ -266,7 +266,7 @@ if (sdlPath) {
|
|
|
266
266
|
if (rpcService)
|
|
267
267
|
writeFileSync(join(out, 'proto.gen.json'), `${JSON.stringify({ generatedBy: 'scripts/derive-pack.ts', ...protoSchema(doc) })}\n`);
|
|
268
268
|
// Data, not code: the surface is JSON so it adds nothing to a typecheck and ships as an asset.
|
|
269
|
-
writeFileSync(join(out, 'surface.gen.json'), `${JSON.stringify({ generatedBy: 'scripts/derive-pack.ts', spec: specPath
|
|
269
|
+
writeFileSync(join(out, 'surface.gen.json'), `${JSON.stringify({ generatedBy: 'scripts/derive-pack.ts', spec: basename(specPath), ...ir })}\n`);
|
|
270
270
|
// The event types a vendor delivers, when its spec enumerates them where a webhook subscription names the events it
|
|
271
271
|
// wants (a request body's `enabled_events`): the pack records an event only under a type the vendor sends.
|
|
272
272
|
const eventTypes = new Set();
|
|
@@ -280,7 +280,7 @@ for (const item of Object.values((doc?.paths ?? {}))) {
|
|
|
280
280
|
}
|
|
281
281
|
}
|
|
282
282
|
if (eventTypes.size)
|
|
283
|
-
writeFileSync(join(out, 'events.gen.json'), `${JSON.stringify({ generatedBy: 'scripts/derive-pack.ts', spec: specPath
|
|
283
|
+
writeFileSync(join(out, 'events.gen.json'), `${JSON.stringify({ generatedBy: 'scripts/derive-pack.ts', spec: basename(specPath), types: [...eventTypes].sort() })}\n`);
|
|
284
284
|
// What the pack's UI needs to know about each resource its manifest declares: where it is listed,
|
|
285
285
|
// read, created and moved, which values its state fields take and which moves are legal from which,
|
|
286
286
|
// and which fields name another resource. Data the browser bundle reads; it never imports the manifest.
|
package/dist/src/protocol-3.js
CHANGED
|
@@ -19,12 +19,15 @@ import { AsyncLocalStorage } from 'node:async_hooks';
|
|
|
19
19
|
import { createHash } from 'node:crypto';
|
|
20
20
|
import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync } from 'node:fs';
|
|
21
21
|
import { tmpdir } from 'node:os';
|
|
22
|
-
import { basename, dirname, join, relative } from 'node:path';
|
|
22
|
+
import { basename, dirname, join, relative, sep } from 'node:path';
|
|
23
23
|
import { gunzipSync } from 'node:zlib';
|
|
24
24
|
import ts from 'typescript';
|
|
25
25
|
import { fileURLToPath } from 'node:url';
|
|
26
26
|
import { sharingLanesIn } from "./lanes.js";
|
|
27
27
|
import { isPackArtifact } from "./pack-files.js";
|
|
28
|
+
// A path relative to another with '/' between its parts on every platform: git lists files that way, and every rule
|
|
29
|
+
// here compares and splits on '/'; Windows' relative() joins with '\', which hid every lane's files and spec.
|
|
30
|
+
const rel = (from, to) => relative(from, to).split(sep).join('/');
|
|
28
31
|
/** The lanes over the unit's vendor's state, when the unit is one of them ("Pack layout"), else none: a lane over its
|
|
29
32
|
* vendor's state takes the vendor's states, shared helpers, manifest and engine from the vendor's src/, and nothing of
|
|
30
33
|
* another lane. */
|
|
@@ -101,9 +104,9 @@ function filesUnder(dir, base = dir) {
|
|
|
101
104
|
files = [...new Set(String(run.stdout).split('\0').filter(Boolean))];
|
|
102
105
|
listing?.set(top, files);
|
|
103
106
|
}
|
|
104
|
-
const under =
|
|
107
|
+
const under = rel(top, dir);
|
|
105
108
|
const mine = under === '' ? files : files.filter((f) => f.startsWith(`${under}/`)).map((f) => f.slice(under.length + 1));
|
|
106
|
-
return mine.map((f) =>
|
|
109
|
+
return mine.map((f) => rel(base, join(dir, f))).filter((f) => existsSync(join(base, f)));
|
|
107
110
|
}
|
|
108
111
|
/** Disk files outside a checkout, or the complete output of the grader's own derivation. */
|
|
109
112
|
function allFilesUnder(dir, base = dir, excludeArtifacts = false) {
|
|
@@ -112,12 +115,12 @@ function allFilesUnder(dir, base = dir, excludeArtifacts = false) {
|
|
|
112
115
|
const out = [];
|
|
113
116
|
for (const name of readdirSync(dir)) {
|
|
114
117
|
const path = join(dir, name);
|
|
115
|
-
if (excludeArtifacts && isPackArtifact(
|
|
118
|
+
if (excludeArtifacts && isPackArtifact(rel(base, path)))
|
|
116
119
|
continue;
|
|
117
120
|
if (statSync(path).isDirectory())
|
|
118
121
|
out.push(...allFilesUnder(path, base, excludeArtifacts));
|
|
119
122
|
else
|
|
120
|
-
out.push(
|
|
123
|
+
out.push(rel(base, path));
|
|
121
124
|
}
|
|
122
125
|
return out;
|
|
123
126
|
}
|
|
@@ -229,7 +232,7 @@ const criteria = [
|
|
|
229
232
|
return fail('no vendored spec under spec/ (openapi, smithy, discovery, asyncapi or an RFC)');
|
|
230
233
|
if (!existsSync(join(unit.dir, 'spec', 'SOURCE.md')))
|
|
231
234
|
return fail('spec/SOURCE.md is missing');
|
|
232
|
-
return pass(files.length > 4 ? `${files.length} documents` : files.map((f) =>
|
|
235
|
+
return pass(files.length > 4 ? `${files.length} documents` : files.map((f) => rel(join(unit.dir, 'spec'), f)).join(', '));
|
|
233
236
|
},
|
|
234
237
|
},
|
|
235
238
|
{
|
|
@@ -764,7 +767,7 @@ const criteria = [
|
|
|
764
767
|
asks: "the pack's own tests pass",
|
|
765
768
|
check(unit) {
|
|
766
769
|
const pkg = packageOf(unit);
|
|
767
|
-
const scope = `./${
|
|
770
|
+
const scope = `./${rel(pkg, join(unit.dir, 'src'))}/`;
|
|
768
771
|
// T0's per-test timeout: the 5 s default goes red on a random pack under load (verify-pack.ts)
|
|
769
772
|
const suites = () => spawnSync('bun', ['test', '--timeout', '60000', scope], { cwd: pkg, stdio: ['ignore', 'pipe', 'pipe'], timeout: 900_000 });
|
|
770
773
|
let run = suites();
|
|
@@ -594,9 +594,10 @@ export function stateCoverage(lcov, files, readSource = () => undefined) {
|
|
|
594
594
|
let current;
|
|
595
595
|
for (const raw of lcov.split('\n')) {
|
|
596
596
|
const line = raw.trim();
|
|
597
|
+
// bun's lcov names a file with the platform's separator: Windows' '\' is read as '/', as every path here is
|
|
597
598
|
if (line.startsWith('SF:')) {
|
|
598
599
|
current = { fns: [], hits: new Map() };
|
|
599
|
-
records.set(line.slice(3), current);
|
|
600
|
+
records.set(line.slice(3).replaceAll('\\', '/'), current);
|
|
600
601
|
continue;
|
|
601
602
|
}
|
|
602
603
|
if (!current)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/twin-standard",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.144",
|
|
4
4
|
"description": "The Protocol 3 standard for Volter twin packs: the criteria a pack is graded by, the grader, the gate a pack repository and the twin catalog run on every change and submission, and the spec derivation the grade checks against. Pin a version: every place a pack is judged judges by that standard.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"twin",
|
package/src/conformance.ts
CHANGED
|
@@ -15,7 +15,9 @@ import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSy
|
|
|
15
15
|
import { createRequire } from 'node:module';
|
|
16
16
|
import { connect } from 'node:net';
|
|
17
17
|
import { tmpdir } from 'node:os';
|
|
18
|
-
import { basename, dirname, extname, join, matchesGlob, relative, resolve } from 'node:path';
|
|
18
|
+
import { basename, dirname, extname, join, matchesGlob, relative, resolve, sep } from 'node:path';
|
|
19
|
+
// A path relative to another with '/' between its parts on every platform (Windows' relative() joins with '\').
|
|
20
|
+
const rel = (from: string, to: string): string => relative(from, to).split(sep).join('/');
|
|
19
21
|
import type { TwinStream } from '@volter/world-core';
|
|
20
22
|
import { citations, sourceHeld, type SourceRecord } from './check-sources.ts';
|
|
21
23
|
import { fileURLToPath } from 'node:url';
|
|
@@ -41,7 +43,7 @@ const readJson = (p: string): unknown => (existsSync(p) ? JSON.parse(readFileSyn
|
|
|
41
43
|
function unitsIn(pack: string): string[] {
|
|
42
44
|
return [pack, ...readdirSync(pack).sort().map((n) => join(pack, n)).filter((d) => !existsSync(join(d, 'package.json')) && existsSync(join(d, 'src', 'manifest.ts')))];
|
|
43
45
|
}
|
|
44
|
-
const nameOf = (pack: string, unit: string): string =>
|
|
46
|
+
const nameOf = (pack: string, unit: string): string => rel(resolve(pack, '..'), unit);
|
|
45
47
|
|
|
46
48
|
/** The files conformance reads from a unit: its manifest, its spec's record, its vendor's life and its decision table
|
|
47
49
|
* (a lane's journeys are its own `journeys/` or else its vendor's). A release publishes each (published()), and a
|
|
@@ -182,7 +184,7 @@ function published(pack: string): string[] {
|
|
|
182
184
|
try { files = new Set(((JSON.parse(run.stdout) as Array<{ files: Array<{ path: string }> }>)[0]?.files ?? []).map((f) => f.path)); } catch { return [`npm pack lists no files (${String(run.stderr).trim().split('\n').at(-1) ?? ''})`]; }
|
|
183
185
|
const out: string[] = [];
|
|
184
186
|
for (const unit of unitsIn(pack)) {
|
|
185
|
-
const at =
|
|
187
|
+
const at = rel(pack, unit);
|
|
186
188
|
const of = (p: string): string => (at ? `${at}/${p}` : p);
|
|
187
189
|
for (const need of UNIT_FILES) {
|
|
188
190
|
if (existsSync(join(unit, need)) && !files.has(of(need))) out.push(`${nameOf(pack, unit)}: ${of(need)} is not published (package.json's files)`);
|
package/src/derive.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { parseExactJson, exactInteger } from '@volter/world-core/exact-json';
|
|
|
9
9
|
|
|
10
10
|
import { parse as parseYaml } from 'yaml';
|
|
11
11
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
12
|
-
import { join } from 'node:path';
|
|
12
|
+
import { basename, join } from 'node:path';
|
|
13
13
|
import { gunzipSync } from 'node:zlib';
|
|
14
14
|
import { CRUD_CLASSES, fromOpenAPI, fromSmithy, fromSwagger2, slug, type SpecIR } from './spec-ir.ts';
|
|
15
15
|
import { fromCommandReplies, type LineSpec } from './spec-ir-lines.ts';
|
|
@@ -40,10 +40,10 @@ if (!specPath) {
|
|
|
40
40
|
console.error(`derive-pack: no spec at ${join(PKG, 'spec')}/openapi.{json,yaml}[.gz], smithy.json[.gz], rfcNNNN.txt, proto/, commands/ or client-ops.json`);
|
|
41
41
|
process.exit(1);
|
|
42
42
|
}
|
|
43
|
-
const rpcService = specPath
|
|
44
|
-
const severalDocuments = specPath
|
|
43
|
+
const rpcService = basename(specPath) === 'proto';
|
|
44
|
+
const severalDocuments = basename(specPath) === 'openapi';
|
|
45
45
|
// a command language's table, one vendored file per command (Redis's src/commands/*.json), with its version in SOURCE.md
|
|
46
|
-
const commandTable = specPath
|
|
46
|
+
const commandTable = basename(specPath) === 'commands';
|
|
47
47
|
const protoTree = (dir: string, at = ''): Record<string, string> => Object.fromEntries(readdirSync(join(dir, at), { withFileTypes: true }).flatMap((e) => (e.isDirectory() ? Object.entries(protoTree(dir, join(at, e.name))) : e.name.endsWith('.proto') ? [[join(at, e.name), readFileSync(join(dir, at, e.name), 'utf8')]] : [])));
|
|
48
48
|
const bytes = rpcService || commandTable || severalDocuments ? Buffer.from('') : readFileSync(specPath);
|
|
49
49
|
const text = (specPath.endsWith('.gz') ? gunzipSync(bytes) : bytes).toString('utf8');
|
|
@@ -168,7 +168,7 @@ mkdirSync(out, { recursive: true });
|
|
|
168
168
|
// name commands and cite the RFC.
|
|
169
169
|
if (lineProtocol || commandTable) {
|
|
170
170
|
const spec = doc as LineSpec | CommandTableSpec;
|
|
171
|
-
writeFileSync(join(out, 'surface.gen.json'), `${JSON.stringify({ generatedBy: 'scripts/derive-pack.ts', spec: specPath
|
|
171
|
+
writeFileSync(join(out, 'surface.gen.json'), `${JSON.stringify({ generatedBy: 'scripts/derive-pack.ts', spec: basename(specPath), ...spec })}\n`);
|
|
172
172
|
const manifestPath = join(PKG, 'src', 'manifest.ts');
|
|
173
173
|
if (existsSync(manifestPath)) {
|
|
174
174
|
const { manifest } = (await import(manifestPath)) as { manifest: { resources: Record<string, { state?: Record<string, { transitions: Array<{ operation?: string; actor?: string; to?: string; source?: string }> }> }> } };
|
|
@@ -222,7 +222,7 @@ if (sdlPath) {
|
|
|
222
222
|
graphqlSchema = graphqlLib.buildSchema(sdl, { assumeValidSDL: true });
|
|
223
223
|
for (const o of gql.operations) graphqlOperations.add(o.id);
|
|
224
224
|
mkdirSync(out, { recursive: true });
|
|
225
|
-
writeFileSync(join(out, 'graphql.gen.json'), `${JSON.stringify({ generatedBy: 'scripts/derive-pack.ts', spec: sdlPath
|
|
225
|
+
writeFileSync(join(out, 'graphql.gen.json'), `${JSON.stringify({ generatedBy: 'scripts/derive-pack.ts', spec: basename(sdlPath), ...gql })}\n`);
|
|
226
226
|
// the schema itself, for the pack's GraphQL wire to validate and execute against (imported lazily)
|
|
227
227
|
writeFileSync(join(out, 'graphql-sdl.gen.json'), `${JSON.stringify({ generatedBy: 'scripts/derive-pack.ts', sdl })}\n`);
|
|
228
228
|
const g = new Map<string, number>();
|
|
@@ -233,7 +233,7 @@ if (sdlPath) {
|
|
|
233
233
|
// a proto unit's schema, for the kernel's protobuf codec and gRPC wire: fields by number, enums by number, rpcs by path
|
|
234
234
|
if (rpcService) writeFileSync(join(out, 'proto.gen.json'), `${JSON.stringify({ generatedBy: 'scripts/derive-pack.ts', ...protoSchema(doc) })}\n`);
|
|
235
235
|
// Data, not code: the surface is JSON so it adds nothing to a typecheck and ships as an asset.
|
|
236
|
-
writeFileSync(join(out, 'surface.gen.json'), `${JSON.stringify({ generatedBy: 'scripts/derive-pack.ts', spec: specPath
|
|
236
|
+
writeFileSync(join(out, 'surface.gen.json'), `${JSON.stringify({ generatedBy: 'scripts/derive-pack.ts', spec: basename(specPath), ...ir })}\n`);
|
|
237
237
|
|
|
238
238
|
// The event types a vendor delivers, when its spec enumerates them where a webhook subscription names the events it
|
|
239
239
|
// wants (a request body's `enabled_events`): the pack records an event only under a type the vendor sends.
|
|
@@ -245,7 +245,7 @@ for (const item of Object.values((doc?.paths ?? {}) as Record<string, Record<str
|
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
|
-
if (eventTypes.size) writeFileSync(join(out, 'events.gen.json'), `${JSON.stringify({ generatedBy: 'scripts/derive-pack.ts', spec: specPath
|
|
248
|
+
if (eventTypes.size) writeFileSync(join(out, 'events.gen.json'), `${JSON.stringify({ generatedBy: 'scripts/derive-pack.ts', spec: basename(specPath), types: [...eventTypes].sort() })}\n`);
|
|
249
249
|
|
|
250
250
|
// What the pack's UI needs to know about each resource its manifest declares: where it is listed,
|
|
251
251
|
// read, created and moved, which values its state fields take and which moves are legal from which,
|
package/src/protocol-3.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { AsyncLocalStorage } from 'node:async_hooks';
|
|
|
11
11
|
import { createHash } from 'node:crypto';
|
|
12
12
|
import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync } from 'node:fs';
|
|
13
13
|
import { tmpdir } from 'node:os';
|
|
14
|
-
import { basename, dirname, join, relative } from 'node:path';
|
|
14
|
+
import { basename, dirname, join, relative, sep } from 'node:path';
|
|
15
15
|
import { gunzipSync } from 'node:zlib';
|
|
16
16
|
import ts from 'typescript';
|
|
17
17
|
import { fileURLToPath } from 'node:url';
|
|
@@ -19,6 +19,10 @@ import { sharingLanesIn } from './lanes.ts';
|
|
|
19
19
|
import { isPackArtifact } from './pack-files.ts';
|
|
20
20
|
import type { Criterion, GradeUnit, Standard } from './types.ts';
|
|
21
21
|
|
|
22
|
+
// A path relative to another with '/' between its parts on every platform: git lists files that way, and every rule
|
|
23
|
+
// here compares and splits on '/'; Windows' relative() joins with '\', which hid every lane's files and spec.
|
|
24
|
+
const rel = (from: string, to: string): string => relative(from, to).split(sep).join('/');
|
|
25
|
+
|
|
22
26
|
/** The lanes over the unit's vendor's state, when the unit is one of them ("Pack layout"), else none: a lane over its
|
|
23
27
|
* vendor's state takes the vendor's states, shared helpers, manifest and engine from the vendor's src/, and nothing of
|
|
24
28
|
* another lane. */
|
|
@@ -87,9 +91,9 @@ function filesUnder(dir: string, base = dir): string[] {
|
|
|
87
91
|
files = [...new Set(String(run.stdout).split('\0').filter(Boolean))];
|
|
88
92
|
listing?.set(top, files);
|
|
89
93
|
}
|
|
90
|
-
const under =
|
|
94
|
+
const under = rel(top, dir);
|
|
91
95
|
const mine = under === '' ? files : files.filter((f) => f.startsWith(`${under}/`)).map((f) => f.slice(under.length + 1));
|
|
92
|
-
return mine.map((f) =>
|
|
96
|
+
return mine.map((f) => rel(base, join(dir, f))).filter((f) => existsSync(join(base, f)));
|
|
93
97
|
}
|
|
94
98
|
/** Disk files outside a checkout, or the complete output of the grader's own derivation. */
|
|
95
99
|
function allFilesUnder(dir: string, base = dir, excludeArtifacts = false): string[] {
|
|
@@ -97,9 +101,9 @@ function allFilesUnder(dir: string, base = dir, excludeArtifacts = false): strin
|
|
|
97
101
|
const out: string[] = [];
|
|
98
102
|
for (const name of readdirSync(dir)) {
|
|
99
103
|
const path = join(dir, name);
|
|
100
|
-
if (excludeArtifacts && isPackArtifact(
|
|
104
|
+
if (excludeArtifacts && isPackArtifact(rel(base, path))) continue;
|
|
101
105
|
if (statSync(path).isDirectory()) out.push(...allFilesUnder(path, base, excludeArtifacts));
|
|
102
|
-
else out.push(
|
|
106
|
+
else out.push(rel(base, path));
|
|
103
107
|
}
|
|
104
108
|
return out;
|
|
105
109
|
}
|
|
@@ -210,7 +214,7 @@ const criteria: Criterion[] = [
|
|
|
210
214
|
const files = specFiles(unit);
|
|
211
215
|
if (!files.length) return fail('no vendored spec under spec/ (openapi, smithy, discovery, asyncapi or an RFC)');
|
|
212
216
|
if (!existsSync(join(unit.dir, 'spec', 'SOURCE.md'))) return fail('spec/SOURCE.md is missing');
|
|
213
|
-
return pass(files.length > 4 ? `${files.length} documents` : files.map((f) =>
|
|
217
|
+
return pass(files.length > 4 ? `${files.length} documents` : files.map((f) => rel(join(unit.dir, 'spec'), f)).join(', '));
|
|
214
218
|
},
|
|
215
219
|
},
|
|
216
220
|
{
|
|
@@ -652,7 +656,7 @@ const criteria: Criterion[] = [
|
|
|
652
656
|
asks: "the pack's own tests pass",
|
|
653
657
|
check(unit) {
|
|
654
658
|
const pkg = packageOf(unit);
|
|
655
|
-
const scope = `./${
|
|
659
|
+
const scope = `./${rel(pkg, join(unit.dir, 'src'))}/`;
|
|
656
660
|
// T0's per-test timeout: the 5 s default goes red on a random pack under load (verify-pack.ts)
|
|
657
661
|
const suites = () => spawnSync('bun', ['test', '--timeout', '60000', scope], { cwd: pkg, stdio: ['ignore', 'pipe', 'pipe'], timeout: 900_000 });
|
|
658
662
|
let run = suites();
|
package/src/walk/journey-kit.ts
CHANGED
|
@@ -757,7 +757,8 @@ export function stateCoverage(lcov: string, files: string[], readSource: (file:
|
|
|
757
757
|
let current: Rec | undefined;
|
|
758
758
|
for (const raw of lcov.split('\n')) {
|
|
759
759
|
const line = raw.trim();
|
|
760
|
-
|
|
760
|
+
// bun's lcov names a file with the platform's separator: Windows' '\' is read as '/', as every path here is
|
|
761
|
+
if (line.startsWith('SF:')) { current = { fns: [], hits: new Map() }; records.set(line.slice(3).replaceAll('\\', '/'), current); continue; }
|
|
761
762
|
if (!current) continue;
|
|
762
763
|
if (line.startsWith('FN:')) { const [l, ...name] = line.slice(3).split(','); current.fns.push({ line: Number(l), name: name.join(',') }); continue; }
|
|
763
764
|
if (line.startsWith('FNF:')) { current.fnf = Number(line.slice(4)); continue; }
|