@volter/twin-standard 1.0.155 → 1.0.157
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/cli.js +11 -11
- package/dist/src/conformance.js +2 -2
- package/dist/src/consumer.js +13 -2
- package/dist/src/walk/serve-pack.d.ts +1 -0
- package/package.json +1 -1
- package/src/cli.ts +11 -11
- package/src/conformance.ts +2 -2
- package/src/consumer.ts +12 -2
- package/src/walk/serve-pack.ts +1 -0
package/dist/src/cli.js
CHANGED
|
@@ -113,7 +113,7 @@ else if (command === 'grade') {
|
|
|
113
113
|
else if (command === 'create') {
|
|
114
114
|
if (args.includes('--init') && !args.includes('--package'))
|
|
115
115
|
throw new Error('Name your scoped package with --package');
|
|
116
|
-
await (await import(
|
|
116
|
+
await (await import('./create-pack.js')).createPack(args);
|
|
117
117
|
}
|
|
118
118
|
else if (command === 'prepare-consumer') {
|
|
119
119
|
if (!args[0])
|
|
@@ -128,7 +128,7 @@ else if (command === 'prepare-consumer') {
|
|
|
128
128
|
const out = resolve(value('--out'));
|
|
129
129
|
if (existsSync(out))
|
|
130
130
|
throw new Error('Prepared consumer output already exists');
|
|
131
|
-
const { prepareConsumerApp } = await import(
|
|
131
|
+
const { prepareConsumerApp } = await import('./consumer.js');
|
|
132
132
|
const candidateAt = args.indexOf('--candidate');
|
|
133
133
|
const artifact = candidateAt >= 0 ? resolve(value('--candidate')) : undefined;
|
|
134
134
|
const fixture = prepareConsumerApp(resolve(args[0]), appDir, { cli, artifact });
|
|
@@ -146,7 +146,7 @@ else if (command === 'walk-consumer') {
|
|
|
146
146
|
if (outputAt >= 0 && !args[outputAt + 1])
|
|
147
147
|
usage();
|
|
148
148
|
const consumer = JSON.parse(readFileSync(resolve(args[at + 1]), 'utf8'));
|
|
149
|
-
const { runConsumerWorkflow } = await import(
|
|
149
|
+
const { runConsumerWorkflow } = await import('./consumer.js');
|
|
150
150
|
const report = await runConsumerWorkflow(resolve(args[0]), consumer);
|
|
151
151
|
const text = `${JSON.stringify(report, null, 2)}\n`;
|
|
152
152
|
if (outputAt >= 0) {
|
|
@@ -160,7 +160,7 @@ else if (command === 'walk-consumer') {
|
|
|
160
160
|
else if (command === 'assess') {
|
|
161
161
|
if (!args[0])
|
|
162
162
|
usage();
|
|
163
|
-
const { assessPack } = await import(
|
|
163
|
+
const { assessPack } = await import('./assessment.js');
|
|
164
164
|
const consumerAt = args.indexOf('--prepared-consumer');
|
|
165
165
|
if (consumerAt >= 0 && !args[consumerAt + 1])
|
|
166
166
|
usage();
|
|
@@ -180,7 +180,7 @@ else if (command === 'assess') {
|
|
|
180
180
|
else if (command === 'facts') {
|
|
181
181
|
if (!args[0])
|
|
182
182
|
usage();
|
|
183
|
-
const { compilePackFacts, renderPackFacts } = await import(
|
|
183
|
+
const { compilePackFacts, renderPackFacts } = await import('./pack-facts.js');
|
|
184
184
|
const { mkdirSync, writeFileSync } = await import('node:fs');
|
|
185
185
|
const { join, dirname } = await import('node:path');
|
|
186
186
|
const dir = resolve(args[0]);
|
|
@@ -203,25 +203,25 @@ else if (command === 'walk' || command === 'examples' || command === 'coverage'
|
|
|
203
203
|
usage();
|
|
204
204
|
const tool = { walk: 'score-pack', examples: 'vendor-examples', coverage: 'life-coverage', 'judge-brief': 'judge-brief' }[command];
|
|
205
205
|
process.argv = [process.argv[0], process.argv[1], ...args];
|
|
206
|
-
await import(__rewriteRelativeImportExtension((await import('./tools/packs-root.
|
|
206
|
+
await import(__rewriteRelativeImportExtension((await import('./tools/packs-root.js')).toolPath(tool)));
|
|
207
207
|
}
|
|
208
208
|
else if (command === 'derive') {
|
|
209
209
|
if (!args[0])
|
|
210
210
|
usage();
|
|
211
211
|
// the derivation is a script over process.argv: it reads the unit from its first argument
|
|
212
212
|
process.argv = [process.argv[0], process.argv[1], ...args];
|
|
213
|
-
await import('./derive.
|
|
213
|
+
await import('./derive.js');
|
|
214
214
|
}
|
|
215
215
|
else if (command === 'check-sources') {
|
|
216
216
|
if (!args[0])
|
|
217
217
|
usage();
|
|
218
|
-
await (await import('./check-sources.
|
|
218
|
+
await (await import('./check-sources.js')).checkSources(args);
|
|
219
219
|
}
|
|
220
220
|
else if (command === 'read-page') {
|
|
221
221
|
// a documentation page's text, read as the citation check reads it: where an author finds the vendor's examples and words
|
|
222
222
|
if (!args[0])
|
|
223
223
|
usage();
|
|
224
|
-
const { status, readable, raw, error } = await (await import('./check-sources.
|
|
224
|
+
const { status, readable, raw, error } = await (await import('./check-sources.js')).readPage(args[0]);
|
|
225
225
|
if (status !== 200) {
|
|
226
226
|
console.error(`read-page: ${args[0]} answered ${status || 'nothing'}${error ? `: ${error}` : ''}`);
|
|
227
227
|
process.exit(1);
|
|
@@ -241,8 +241,8 @@ else if (command === 'conform') {
|
|
|
241
241
|
// `.tgz` the tarball `npm pack` makes of one, the bytes a publish would send
|
|
242
242
|
const published = target.endsWith('.tgz') || (!existsSync(target) && /^@?[^@]+@[^@]+$/.test(target));
|
|
243
243
|
const results = published
|
|
244
|
-
? await (await import('./published.
|
|
245
|
-
: await (await import('./conformance.
|
|
244
|
+
? await (await import('./published.js')).conformPublished(target, at >= 0 ? { registry: args[at + 1] } : {})
|
|
245
|
+
: await (await import('./conformance.js')).conformance(target, { onCheck: (id) => console.error(` … ${id} (${new Date().toISOString().slice(11, 19)})`) });
|
|
246
246
|
for (const r of results) {
|
|
247
247
|
console.log(`${r.failures.length ? '✗' : '✓'} ${r.id.padEnd(15)} ${r.asks}${r.note ? ` (${r.note})` : ''}`);
|
|
248
248
|
for (const f of r.failures)
|
package/dist/src/conformance.js
CHANGED
|
@@ -611,8 +611,8 @@ async function lifeServed(pack) {
|
|
|
611
611
|
}, server.managedDatabaseUrl ? { managedDatabaseUrl: server.managedDatabaseUrl } : {}),
|
|
612
612
|
// a socket opened as an application opens it: a WebSocket to the served port, the vendor's host in its Host header
|
|
613
613
|
socket: (url, headers) => new WebSocket(`ws://127.0.0.1:${port}${url}`, { headers }),
|
|
614
|
-
//
|
|
615
|
-
stream: lineConnection(server.port),
|
|
614
|
+
// TCP to the native wire: a separate listener beside HTTP, or the primary protocol port.
|
|
615
|
+
stream: lineConnection(server.wirePort ?? server.port),
|
|
616
616
|
// bounded: Bun's server.stop(true) never settles once a WebSocket peer has closed under it (discord's life, Bun
|
|
617
617
|
// 1.3.11: the walk ended and the stop spun at full CPU for 45 minutes), so the check waits 5 s and goes on
|
|
618
618
|
stop: async () => { await within(5000, Promise.resolve(server.stop())); },
|
package/dist/src/consumer.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createHash } from 'node:crypto';
|
|
|
3
3
|
import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
5
|
import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
6
|
-
import { spawn } from 'node:child_process';
|
|
6
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import { standardPackageFile } from "./package-file.js";
|
|
9
9
|
import { dependencyLock } from "./dependency-lock.js";
|
|
@@ -91,7 +91,7 @@ export function prepareConsumerApp(packDir, appDir, installation) {
|
|
|
91
91
|
throw new Error(`${name}: customer SDK dependencies require exact registry versions`);
|
|
92
92
|
}
|
|
93
93
|
const files = Object.entries(recipe.files).map(([path, content]) => {
|
|
94
|
-
if (isAbsolute(path) || path.includes('\\') || path.split('/').some((part) => !part || part === '.' || part === '..') || path.split('/').includes('node_modules') || ['.env', '.npmrc', '.volter/world.json'].includes(path))
|
|
94
|
+
if (isAbsolute(path) || path.includes('\\') || path.split('/').some((part) => !part || part === '.' || part === '..' || part === '.git') || path.split('/').includes('node_modules') || ['.env', '.npmrc', '.volter/world.json'].includes(path))
|
|
95
95
|
throw new Error(`Invalid consumer file: ${path}`);
|
|
96
96
|
if (typeof content !== 'string')
|
|
97
97
|
throw new Error('Consumer files must contain text');
|
|
@@ -113,6 +113,17 @@ export function prepareConsumerApp(packDir, appDir, installation) {
|
|
|
113
113
|
mkdirSync(dirname(join(appDir, path)), { recursive: true });
|
|
114
114
|
writeFileSync(join(appDir, path), content);
|
|
115
115
|
}
|
|
116
|
+
// World init reads committed example values. The recipe is this new application's source.
|
|
117
|
+
const gitEnv = Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith('GIT_')));
|
|
118
|
+
for (const args of [
|
|
119
|
+
['init', '--quiet', '--template='],
|
|
120
|
+
['add', '--force', '--', ...files.map(([path]) => path)],
|
|
121
|
+
['-c', 'user.name=World consumer', '-c', 'user.email=consumer@world.test', '-c', 'commit.gpgsign=false', '-c', `core.hooksPath=${join(appDir, '.git/hooks')}`, 'commit', '--quiet', '-m', 'Materialize customer application'],
|
|
122
|
+
]) {
|
|
123
|
+
const result = spawnSync('git', ['-C', appDir, ...args], { env: gitEnv, encoding: 'utf8' });
|
|
124
|
+
if (result.error || result.status !== 0)
|
|
125
|
+
throw new Error(`Consumer source ${args[0]} failed: ${result.error?.message ?? result.stderr.trim()}`);
|
|
126
|
+
}
|
|
116
127
|
return { recipeSha256: createHash('sha256').update(JSON.stringify(recipe)).digest('hex'), dependencies: app.dependencies ?? {} };
|
|
117
128
|
}
|
|
118
129
|
export async function runConsumerWorkflow(packDir, options) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/twin-standard",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.157",
|
|
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/cli.ts
CHANGED
|
@@ -78,7 +78,7 @@ if (command === '--version') {
|
|
|
78
78
|
});
|
|
79
79
|
} else if (command === 'create') {
|
|
80
80
|
if (args.includes('--init') && !args.includes('--package')) throw new Error('Name your scoped package with --package');
|
|
81
|
-
await (await import('./create-pack.
|
|
81
|
+
await (await import('./create-pack.js')).createPack(args);
|
|
82
82
|
} else if (command === 'prepare-consumer') {
|
|
83
83
|
if (!args[0]) usage();
|
|
84
84
|
const value = (name: string) => { const at = args.indexOf(name); if (at < 0 || !args[at + 1]) usage(); return args[at + 1]!; };
|
|
@@ -88,7 +88,7 @@ if (command === '--version') {
|
|
|
88
88
|
if (!/^sha512-[A-Za-z0-9+/]{86}==$/.test(artifactIntegrity)) throw new Error('Consumer preparation requires candidate SHA-512 integrity');
|
|
89
89
|
const out = resolve(value('--out'));
|
|
90
90
|
if (existsSync(out)) throw new Error('Prepared consumer output already exists');
|
|
91
|
-
const { prepareConsumerApp } = await import('./consumer.
|
|
91
|
+
const { prepareConsumerApp } = await import('./consumer.js');
|
|
92
92
|
const candidateAt = args.indexOf('--candidate');
|
|
93
93
|
const artifact = candidateAt >= 0 ? resolve(value('--candidate')) : undefined;
|
|
94
94
|
const fixture = prepareConsumerApp(resolve(args[0]!), appDir, { cli, artifact });
|
|
@@ -102,7 +102,7 @@ if (command === '--version') {
|
|
|
102
102
|
const outputAt = args.indexOf('--out');
|
|
103
103
|
if (outputAt >= 0 && !args[outputAt + 1]) usage();
|
|
104
104
|
const consumer = JSON.parse(readFileSync(resolve(args[at + 1]!), 'utf8'));
|
|
105
|
-
const { runConsumerWorkflow } = await import('./consumer.
|
|
105
|
+
const { runConsumerWorkflow } = await import('./consumer.js');
|
|
106
106
|
const report = await runConsumerWorkflow(resolve(args[0]!), consumer);
|
|
107
107
|
const text = `${JSON.stringify(report, null, 2)}\n`;
|
|
108
108
|
if (outputAt >= 0) {
|
|
@@ -112,7 +112,7 @@ if (command === '--version') {
|
|
|
112
112
|
process.exit(report.passed ? 0 : 1);
|
|
113
113
|
} else if (command === 'assess') {
|
|
114
114
|
if (!args[0]) usage();
|
|
115
|
-
const { assessPack } = await import('./assessment.
|
|
115
|
+
const { assessPack } = await import('./assessment.js');
|
|
116
116
|
const consumerAt = args.indexOf('--prepared-consumer');
|
|
117
117
|
if (consumerAt >= 0 && !args[consumerAt + 1]) usage();
|
|
118
118
|
const consumer = consumerAt >= 0 ? JSON.parse(readFileSync(resolve(args[consumerAt + 1]!), 'utf8')) : undefined;
|
|
@@ -126,7 +126,7 @@ if (command === '--version') {
|
|
|
126
126
|
process.exit(report.ready ? 0 : 1);
|
|
127
127
|
} else if (command === 'facts') {
|
|
128
128
|
if (!args[0]) usage();
|
|
129
|
-
const { compilePackFacts, renderPackFacts } = await import('./pack-facts.
|
|
129
|
+
const { compilePackFacts, renderPackFacts } = await import('./pack-facts.js');
|
|
130
130
|
const { mkdirSync, writeFileSync } = await import('node:fs');
|
|
131
131
|
const { join, dirname } = await import('node:path');
|
|
132
132
|
const dir = resolve(args[0]!);
|
|
@@ -145,19 +145,19 @@ if (command === '--version') {
|
|
|
145
145
|
if (!args[0] || args[0].startsWith('-')) usage();
|
|
146
146
|
const tool = { walk: 'score-pack', examples: 'vendor-examples', coverage: 'life-coverage', 'judge-brief': 'judge-brief' }[command];
|
|
147
147
|
process.argv = [process.argv[0]!, process.argv[1]!, ...args];
|
|
148
|
-
await import((await import('./tools/packs-root.
|
|
148
|
+
await import((await import('./tools/packs-root.js')).toolPath(tool));
|
|
149
149
|
} else if (command === 'derive') {
|
|
150
150
|
if (!args[0]) usage();
|
|
151
151
|
// the derivation is a script over process.argv: it reads the unit from its first argument
|
|
152
152
|
process.argv = [process.argv[0]!, process.argv[1]!, ...args];
|
|
153
|
-
await import('./derive.
|
|
153
|
+
await import('./derive.js');
|
|
154
154
|
} else if (command === 'check-sources') {
|
|
155
155
|
if (!args[0]) usage();
|
|
156
|
-
await (await import('./check-sources.
|
|
156
|
+
await (await import('./check-sources.js')).checkSources(args);
|
|
157
157
|
} else if (command === 'read-page') {
|
|
158
158
|
// a documentation page's text, read as the citation check reads it: where an author finds the vendor's examples and words
|
|
159
159
|
if (!args[0]) usage();
|
|
160
|
-
const { status, readable, raw, error } = await (await import('./check-sources.
|
|
160
|
+
const { status, readable, raw, error } = await (await import('./check-sources.js')).readPage(args[0]);
|
|
161
161
|
if (status !== 200) { console.error(`read-page: ${args[0]} answered ${status || 'nothing'}${error ? `: ${error}` : ''}`); process.exit(1); }
|
|
162
162
|
// --raw: the page's own bytes, as fetched (a spec writer reads its tables, which the readable text drops)
|
|
163
163
|
if (args.includes('--raw')) process.stdout.write(raw); else console.log(readable);
|
|
@@ -169,8 +169,8 @@ if (command === '--version') {
|
|
|
169
169
|
// `.tgz` the tarball `npm pack` makes of one, the bytes a publish would send
|
|
170
170
|
const published = target.endsWith('.tgz') || (!existsSync(target) && /^@?[^@]+@[^@]+$/.test(target));
|
|
171
171
|
const results = published
|
|
172
|
-
? await (await import('./published.
|
|
173
|
-
: await (await import('./conformance.
|
|
172
|
+
? await (await import('./published.js')).conformPublished(target, at >= 0 ? { registry: args[at + 1]! } : {})
|
|
173
|
+
: await (await import('./conformance.js')).conformance(target, { onCheck: (id) => console.error(` … ${id} (${new Date().toISOString().slice(11, 19)})`) });
|
|
174
174
|
for (const r of results) {
|
|
175
175
|
console.log(`${r.failures.length ? '✗' : '✓'} ${r.id.padEnd(15)} ${r.asks}${r.note ? ` (${r.note})` : ''}`);
|
|
176
176
|
for (const f of r.failures) console.log(` ${f}`);
|
package/src/conformance.ts
CHANGED
|
@@ -499,8 +499,8 @@ async function lifeServed(pack: string): Promise<string[]> {
|
|
|
499
499
|
}, server.managedDatabaseUrl ? { managedDatabaseUrl: server.managedDatabaseUrl } : {}),
|
|
500
500
|
// a socket opened as an application opens it: a WebSocket to the served port, the vendor's host in its Host header
|
|
501
501
|
socket: (url: string, headers: Record<string, string>) => new WebSocket(`ws://127.0.0.1:${port}${url}`, { headers } as unknown as string[]),
|
|
502
|
-
//
|
|
503
|
-
stream: lineConnection(server.port),
|
|
502
|
+
// TCP to the native wire: a separate listener beside HTTP, or the primary protocol port.
|
|
503
|
+
stream: lineConnection(server.wirePort ?? server.port),
|
|
504
504
|
// bounded: Bun's server.stop(true) never settles once a WebSocket peer has closed under it (discord's life, Bun
|
|
505
505
|
// 1.3.11: the walk ended and the stop spun at full CPU for 45 minutes), so the check waits 5 s and goes on
|
|
506
506
|
stop: async () => { await within(5000, Promise.resolve(server.stop())); },
|
package/src/consumer.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { createHash } from 'node:crypto';
|
|
|
3
3
|
import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
5
|
import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
6
|
-
import { spawn } from 'node:child_process';
|
|
6
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import { standardPackageFile } from './package-file.ts';
|
|
9
9
|
import { dependencyLock } from './dependency-lock.ts';
|
|
@@ -87,7 +87,7 @@ export function prepareConsumerApp(packDir: string, appDir: string, installation
|
|
|
87
87
|
if (typeof version !== 'string' || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version)) throw new Error(`${name}: customer SDK dependencies require exact registry versions`);
|
|
88
88
|
}
|
|
89
89
|
const files = Object.entries(recipe.files).map(([path, content]) => {
|
|
90
|
-
if (isAbsolute(path) || path.includes('\\') || path.split('/').some((part) => !part || part === '.' || part === '..') || path.split('/').includes('node_modules') || ['.env', '.npmrc', '.volter/world.json'].includes(path)) throw new Error(`Invalid consumer file: ${path}`);
|
|
90
|
+
if (isAbsolute(path) || path.includes('\\') || path.split('/').some((part) => !part || part === '.' || part === '..' || part === '.git') || path.split('/').includes('node_modules') || ['.env', '.npmrc', '.volter/world.json'].includes(path)) throw new Error(`Invalid consumer file: ${path}`);
|
|
91
91
|
if (typeof content !== 'string') throw new Error('Consumer files must contain text');
|
|
92
92
|
return [path, content] as [string, string];
|
|
93
93
|
});
|
|
@@ -105,6 +105,16 @@ export function prepareConsumerApp(packDir: string, appDir: string, installation
|
|
|
105
105
|
mkdirSync(dirname(join(appDir, path)), { recursive: true });
|
|
106
106
|
writeFileSync(join(appDir, path), content);
|
|
107
107
|
}
|
|
108
|
+
// World init reads committed example values. The recipe is this new application's source.
|
|
109
|
+
const gitEnv = Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith('GIT_')));
|
|
110
|
+
for (const args of [
|
|
111
|
+
['init', '--quiet', '--template='],
|
|
112
|
+
['add', '--force', '--', ...files.map(([path]) => path)],
|
|
113
|
+
['-c', 'user.name=World consumer', '-c', 'user.email=consumer@world.test', '-c', 'commit.gpgsign=false', '-c', `core.hooksPath=${join(appDir, '.git/hooks')}`, 'commit', '--quiet', '-m', 'Materialize customer application'],
|
|
114
|
+
]) {
|
|
115
|
+
const result = spawnSync('git', ['-C', appDir, ...args], { env: gitEnv, encoding: 'utf8' });
|
|
116
|
+
if (result.error || result.status !== 0) throw new Error(`Consumer source ${args[0]} failed: ${result.error?.message ?? result.stderr.trim()}`);
|
|
117
|
+
}
|
|
108
118
|
return { recipeSha256: createHash('sha256').update(JSON.stringify(recipe)).digest('hex'), dependencies: app.dependencies ?? {} };
|
|
109
119
|
}
|
|
110
120
|
|
package/src/walk/serve-pack.ts
CHANGED