@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.
@@ -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) => relative(resolve(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 = relative(pack, unit);
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)))
@@ -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.endsWith('/proto');
50
- const severalDocuments = specPath.endsWith('/openapi');
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.endsWith('/commands');
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.split('/').pop(), ...spec })}\n`);
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.split('/').pop(), ...gql })}\n`);
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.split('/').pop(), ...ir })}\n`);
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.split('/').pop(), types: [...eventTypes].sort() })}\n`);
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.
@@ -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 = relative(top, dir);
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) => relative(base, join(dir, f))).filter((f) => existsSync(join(base, 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(relative(base, path)))
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(relative(base, path));
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) => relative(join(unit.dir, 'spec'), f)).join(', '));
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 = `./${relative(pkg, join(unit.dir, 'src'))}/`;
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.143",
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",
@@ -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 => relative(resolve(pack, '..'), unit);
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 = relative(pack, unit);
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.endsWith('/proto');
44
- const severalDocuments = specPath.endsWith('/openapi');
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.endsWith('/commands');
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.split('/').pop(), ...spec })}\n`);
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.split('/').pop(), ...gql })}\n`);
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.split('/').pop(), ...ir })}\n`);
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.split('/').pop(), types: [...eventTypes].sort() })}\n`);
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 = relative(top, dir);
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) => relative(base, join(dir, f))).filter((f) => existsSync(join(base, 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(relative(base, path))) continue;
104
+ if (excludeArtifacts && isPackArtifact(rel(base, path))) continue;
101
105
  if (statSync(path).isDirectory()) out.push(...allFilesUnder(path, base, excludeArtifacts));
102
- else out.push(relative(base, path));
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) => relative(join(unit.dir, 'spec'), f)).join(', '));
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 = `./${relative(pkg, join(unit.dir, 'src'))}/`;
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();
@@ -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
- if (line.startsWith('SF:')) { current = { fns: [], hits: new Map() }; records.set(line.slice(3), current); continue; }
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; }