@orkestrel/scaffold 0.0.107 → 0.0.109
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/agents/skills/orkestrel-dispatch/scripts/bench.js +326 -91
- package/dist/agents/skills/orkestrel-dispatch/scripts/brief.js +110 -14
- package/dist/agents/skills/orkestrel-dispatch/scripts/chain.js +280 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/cite.js +188 -26
- package/dist/agents/skills/orkestrel-dispatch/scripts/launch.js +142 -43
- package/dist/agents/skills/orkestrel-dispatch/scripts/login.js +240 -62
- package/dist/agents/skills/orkestrel-dispatch/scripts/patch.js +323 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/result.js +169 -32
- package/dist/agents/skills/orkestrel-dispatch/scripts/sweep.js +368 -27
- package/dist/agents/skills/orkestrel-falsify/scripts/mutate.js +560 -0
- package/dist/agents/skills/orkestrel-harden/scripts/discovery.js +1052 -205
- package/dist/agents/skills/orkestrel-harden/scripts/measure.js +361 -0
- package/dist/agents/skills/orkestrel-harden/scripts/titles.js +455 -80
- package/dist/agents/skills/orkestrel-parallel/scripts/cases.js +327 -0
- package/dist/agents/skills/orkestrel-publish/scripts/compare.js +298 -51
- package/dist/agents/skills/orkestrel-publish/scripts/pins.js +170 -13
- package/dist/agents/skills/orkestrel-publish/scripts/swap.js +956 -191
- package/dist/agents/skills/orkestrel-publish/scripts/wave.js +707 -112
- package/dist/agents/skills/orkestrel-publish/scripts/window.js +235 -41
- package/dist/agents/skills/orkestrel-scout/scripts/map.js +341 -54
- package/dist/agents/templates/brief.md +1 -1
- package/dist/bin/main.js +51 -46
- package/dist/bin/main.js.map +1 -1
- package/dist/host/AGENTS.md +26 -20
- package/dist/host/agents/orchestration.md +98 -58
- package/dist/host/agents/platforms/posix.md +15 -0
- package/dist/host/agents/platforms/windows.md +37 -0
- package/dist/host/agents/skills/orkestrel-align/SKILL.md +10 -13
- package/dist/host/agents/skills/orkestrel-align/references/fleet.md +4 -10
- package/dist/host/agents/skills/orkestrel-align/references/integration.md +1 -1
- package/dist/host/agents/skills/orkestrel-build/SKILL.md +16 -48
- package/dist/host/agents/skills/orkestrel-build/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +19 -16
- package/dist/host/agents/skills/orkestrel-debrief/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +15 -28
- package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +5 -6
- package/dist/host/agents/skills/orkestrel-debrief/references/retention.md +12 -14
- package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +36 -24
- package/dist/host/agents/skills/orkestrel-dispatch/references/launch.md +14 -11
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/bench.ts +366 -75
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/brief.ts +126 -16
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/chain.ts +323 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/cite.ts +220 -31
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/launch.ts +176 -33
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/login.ts +294 -59
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/patch.ts +401 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/result.ts +199 -34
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/sweep.ts +428 -27
- package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +10 -8
- package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +1 -5
- package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +1 -1
- package/dist/host/agents/skills/orkestrel-falsify/scripts/mutate.ts +701 -0
- package/dist/host/agents/skills/orkestrel-harden/SKILL.md +16 -12
- package/dist/host/agents/skills/orkestrel-harden/references/bindings.md +23 -81
- package/dist/host/agents/skills/orkestrel-harden/references/centralization.md +4 -5
- package/dist/host/agents/skills/orkestrel-harden/references/contract.md +2 -0
- package/dist/host/agents/skills/orkestrel-harden/references/document.md +33 -0
- package/dist/host/agents/skills/orkestrel-harden/references/equivalence.md +6 -3
- package/dist/host/agents/skills/orkestrel-harden/references/hardening.md +8 -4
- package/dist/host/agents/skills/orkestrel-harden/references/research.md +2 -0
- package/dist/host/agents/skills/orkestrel-harden/scripts/discovery.ts +1272 -279
- package/dist/host/agents/skills/orkestrel-harden/scripts/measure.ts +425 -0
- package/dist/host/agents/skills/orkestrel-harden/scripts/titles.ts +603 -98
- package/dist/host/agents/skills/orkestrel-journey/SKILL.md +75 -98
- package/dist/host/agents/skills/orkestrel-journey/references/captures.md +54 -17
- package/dist/host/agents/skills/orkestrel-journey/references/decide.md +1 -20
- package/dist/host/agents/skills/orkestrel-journey/references/layer.md +21 -31
- package/dist/host/agents/skills/orkestrel-journey/references/styles.md +1 -1
- package/dist/host/agents/skills/orkestrel-parallel/SKILL.md +91 -30
- package/dist/host/agents/skills/orkestrel-parallel/agents/openai.yaml +2 -2
- package/dist/host/agents/skills/orkestrel-parallel/references/claude.md +34 -0
- package/dist/host/agents/skills/orkestrel-parallel/references/codex.md +29 -0
- package/dist/host/agents/skills/orkestrel-parallel/references/cursor.md +29 -0
- package/dist/host/agents/skills/orkestrel-parallel/references/t3.md +31 -0
- package/dist/host/agents/skills/orkestrel-parallel/scripts/cases.ts +389 -0
- package/dist/host/agents/skills/orkestrel-polish/SKILL.md +16 -20
- package/dist/host/agents/skills/orkestrel-polish/references/capture-harness.md +16 -26
- package/dist/host/agents/skills/orkestrel-publish/SKILL.md +12 -26
- package/dist/host/agents/skills/orkestrel-publish/agents/openai.yaml +2 -2
- package/dist/host/agents/skills/orkestrel-publish/references/adopt.md +23 -0
- package/dist/host/agents/skills/orkestrel-publish/references/release.md +4 -4
- package/dist/host/agents/skills/orkestrel-publish/references/wave.md +12 -10
- package/dist/host/agents/skills/orkestrel-publish/references/window.md +20 -25
- package/dist/host/agents/skills/orkestrel-publish/scripts/compare.ts +348 -59
- package/dist/host/agents/skills/orkestrel-publish/scripts/pins.ts +202 -22
- package/dist/host/agents/skills/orkestrel-publish/scripts/swap.ts +1061 -191
- package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +812 -140
- package/dist/host/agents/skills/orkestrel-publish/scripts/window.ts +280 -38
- package/dist/host/agents/skills/orkestrel-scout/SKILL.md +3 -3
- package/dist/host/agents/skills/orkestrel-scout/scripts/map.ts +428 -70
- package/dist/host/agents/templates/brief.md +1 -1
- package/dist/host/agents/transports/claude-cli.md +3 -3
- package/dist/host/agents/transports/codex.md +3 -8
- package/dist/host/agents/transports/cursor.md +2 -3
- package/dist/host/claude/AGENTS.md +7 -10
- package/dist/host/claude/agents/analyst.md +3 -3
- package/dist/host/claude/agents/astra.md +3 -3
- package/dist/host/claude/agents/builder.md +1 -1
- package/dist/host/claude/agents/checker.md +1 -1
- package/dist/host/claude/agents/distiller.md +1 -1
- package/dist/host/claude/agents/grok.md +1 -1
- package/dist/host/claude/agents/opus.md +2 -2
- package/dist/host/claude/agents/orkestrel.md +57 -90
- package/dist/host/claude/agents/researcher.md +1 -1
- package/dist/host/claude/agents/reviewer.md +4 -0
- package/dist/host/claude/agents/scout.md +1 -1
- package/dist/host/claude/agents/verifier.md +1 -1
- package/dist/host/claude/rules/application.md +14 -21
- package/dist/host/claude/rules/architecture.md +34 -44
- package/dist/host/claude/rules/browser.md +3 -4
- package/dist/host/claude/rules/campaign.md +55 -0
- package/dist/host/claude/rules/documentation.md +26 -11
- package/dist/host/claude/rules/names.md +9 -4
- package/dist/host/claude/rules/patterns.md +6 -7
- package/dist/host/claude/rules/portability.md +2 -3
- package/dist/host/claude/rules/quality.md +4 -5
- package/dist/host/claude/rules/styles.md +2 -2
- package/dist/host/claude/rules/tests.md +57 -114
- package/dist/host/claude/rules/typescript.md +8 -10
- package/dist/host/claude/rules/workspace.md +21 -19
- package/dist/host/claude/rules/writing.md +3 -3
- package/dist/host/claude/skills/orkestrel-dispatch/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-harden/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-parallel/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-publish/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-scout/SKILL.md +1 -1
- package/dist/host/codex/agents/analyst.toml +9 -3
- package/dist/host/codex/agents/astra.toml +3 -4
- package/dist/host/codex/agents/builder.toml +1 -1
- package/dist/host/codex/agents/opus.toml +3 -4
- package/dist/host/codex/agents/orkestrel.toml +1 -1
- package/dist/host/codex/agents/researcher.toml +2 -2
- package/dist/host/codex/agents/verifier.toml +1 -1
- package/dist/host/codex/config.toml +3 -3
- package/dist/host/configs/helpers.ts +21 -1
- package/dist/host/configs/policy.ts +405 -16
- package/dist/host/cursor/rules/orchestration.mdc +1 -0
- package/dist/host/dotfiles/oxlintrc.json +141 -5
- package/dist/host/guides/agent.md +111 -134
- package/dist/host/guides/capture.md +801 -0
- package/dist/host/guides/probe.md +27 -8
- package/dist/host/guides/scaffold.md +152 -19
- package/dist/host/guides/study.md +534 -440
- package/dist/host/guides/test.md +111 -44
- package/dist/host/manifest.json +199 -242
- package/dist/host/tests/config.test.ts +1558 -219
- package/dist/host/tests/policy.test.ts +127 -1
- package/dist/host/tests/setupPolicy.ts +1389 -39
- package/dist/src/core/index.cjs +239 -87
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +123 -39
- package/dist/src/core/index.d.ts +123 -39
- package/dist/src/core/index.js +234 -86
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +20 -27
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.js +21 -28
- package/dist/src/server/index.js.map +1 -1
- package/package.json +11 -11
- package/dist/agents/skills/orkestrel-dispatch/scripts/helpers.js +0 -283
- package/dist/host/agents/skills/orkestrel-dispatch/references/bench.md +0 -25
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/helpers.ts +0 -333
- package/dist/host/agents/skills/orkestrel-parallel/references/harnesses.md +0 -19
- package/dist/host/guides/supervisor.md +0 -4890
|
@@ -5,62 +5,283 @@
|
|
|
5
5
|
// compares against a local .tgz instead of fetching. Sourcemaps are excluded, a text file differs
|
|
6
6
|
// only when it differs with all whitespace removed, and a binary file differs on any byte. The
|
|
7
7
|
// summary lists the paths added, removed, and changed. Exit 0 when nothing material differs, 3 when
|
|
8
|
-
// something does, 2 when the tarball cannot be read, 64 on usage
|
|
9
|
-
|
|
10
|
-
import {
|
|
8
|
+
// something does, 2 when the tarball cannot be read, 64 on usage, including a flag the script does
|
|
9
|
+
// not read and the `--flag=value` form.
|
|
10
|
+
import type { SpawnSyncReturns } from 'node:child_process'
|
|
11
|
+
import { spawnSync } from 'node:child_process'
|
|
12
|
+
import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from 'node:fs'
|
|
13
|
+
import { dirname, join, relative } from 'node:path'
|
|
11
14
|
import { gunzipSync } from 'node:zlib'
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
|
|
16
|
+
/** Holds the byte size of one tar block. */
|
|
17
|
+
export const BLOCK = 512
|
|
18
|
+
|
|
19
|
+
/** Names the directory every npm tarball entry sits under. */
|
|
20
|
+
export const PACKAGE_PREFIX = 'package/'
|
|
21
|
+
|
|
22
|
+
/** Names the directories every tree walk skips by default. */
|
|
23
|
+
export const SKIPPED_DIRECTORIES: ReadonlySet<string> = new Set([
|
|
24
|
+
'node_modules',
|
|
25
|
+
'.git',
|
|
26
|
+
'dist',
|
|
27
|
+
'tmp',
|
|
28
|
+
'.orkestrel',
|
|
29
|
+
'.idea',
|
|
30
|
+
])
|
|
31
|
+
|
|
32
|
+
/** Holds the empty skip set, because a dist tree holds nothing a walk skips, so every emitted file counts. */
|
|
33
|
+
export const NOTHING_SKIPPED: ReadonlySet<string> = new Set()
|
|
34
|
+
|
|
35
|
+
/** Lists every flag the script reads, value flags and switches alike. */
|
|
36
|
+
export const FLAGS: readonly string[] = ['--package', '--version', '--tarball', '--dist', '--json']
|
|
37
|
+
|
|
38
|
+
/** Holds the usage line the script prints on a usage refusal. */
|
|
39
|
+
export const USAGE =
|
|
40
|
+
'usage: compare.ts [--package NAME] [--version V] [--tarball PATH] [--dist dist] [--json]'
|
|
41
|
+
|
|
42
|
+
/** Describes the name and version a package manifest declares. */
|
|
43
|
+
export interface Manifest {
|
|
25
44
|
readonly name: string | undefined
|
|
26
45
|
readonly version: string | undefined
|
|
27
46
|
}
|
|
28
47
|
|
|
29
|
-
|
|
48
|
+
/** Describes the tarball a run compares against, or why none could be read. */
|
|
49
|
+
export interface Source {
|
|
30
50
|
readonly archive: Buffer | undefined
|
|
31
51
|
readonly version: string | undefined
|
|
32
52
|
readonly failure: string | undefined
|
|
33
53
|
}
|
|
34
54
|
|
|
35
|
-
|
|
55
|
+
/** Describes the reader's position and the files found so far in one tar archive. */
|
|
56
|
+
export interface TarState {
|
|
36
57
|
readonly next: number
|
|
37
58
|
readonly name: string | undefined
|
|
38
59
|
readonly files: ReadonlyArray<readonly [string, Buffer]>
|
|
39
60
|
readonly done: boolean
|
|
40
61
|
}
|
|
41
62
|
|
|
42
|
-
|
|
43
|
-
|
|
63
|
+
/** Describes one file a tree walk found, with the modification time the walk read. */
|
|
64
|
+
export interface WalkedFile {
|
|
65
|
+
readonly path: string
|
|
66
|
+
readonly modified: number
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Describes how to spawn npm on this host without a shell where the CLI entry is present. */
|
|
70
|
+
export interface NpmCommand {
|
|
71
|
+
readonly file: string
|
|
72
|
+
readonly prefix: readonly string[]
|
|
73
|
+
readonly shell: boolean
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Normalizes a host path to forward slashes.
|
|
78
|
+
*
|
|
79
|
+
* @param path - A host path.
|
|
80
|
+
* @returns The same path with every backslash replaced by a slash.
|
|
81
|
+
*/
|
|
82
|
+
export function normalizeSlashes(path: string): string {
|
|
83
|
+
return path.split('\\').join('/')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Reads the value that follows a flag.
|
|
88
|
+
*
|
|
89
|
+
* @remarks
|
|
90
|
+
* Reads `--flag value` pairs: the first occurrence of a flag wins, a following value that opens
|
|
91
|
+
* with `--` is a missing value, and the `--flag=value` form is not read.
|
|
92
|
+
*
|
|
93
|
+
* @param argv - The arguments after the script path.
|
|
94
|
+
* @param name - The flag, such as `--package`.
|
|
95
|
+
* @returns The value after the flag, or undefined when the flag is absent, last, or followed by
|
|
96
|
+
* another flag.
|
|
97
|
+
*/
|
|
98
|
+
export function readOption(argv: readonly string[], name: string): string | undefined {
|
|
99
|
+
const index = argv.indexOf(name)
|
|
100
|
+
const value = index === -1 ? undefined : argv[index + 1]
|
|
101
|
+
return value === undefined || value.startsWith('--') ? undefined : value
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Names the value flags a caller gave with no value.
|
|
106
|
+
*
|
|
107
|
+
* @param argv - The arguments after the script path.
|
|
108
|
+
* @param names - The flags that take a value.
|
|
109
|
+
* @returns Each named flag that appears last or is followed by another flag, in argument order.
|
|
110
|
+
*/
|
|
111
|
+
export function readMissingFlags(
|
|
112
|
+
argv: readonly string[],
|
|
113
|
+
names: readonly string[],
|
|
114
|
+
): readonly string[] {
|
|
115
|
+
return names.filter((name) => argv.includes(name) && readOption(argv, name) === undefined)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Names the flags a caller gave that the script does not read.
|
|
120
|
+
*
|
|
121
|
+
* @remarks
|
|
122
|
+
* Reads the arguments before a bare `--`, which opens a command a script passes on. A `--flag=value`
|
|
123
|
+
* argument is unknown, because the script reads no such form.
|
|
124
|
+
*
|
|
125
|
+
* @param argv - The arguments after the script path.
|
|
126
|
+
* @param flags - Every flag the script reads, value flags and switches alike.
|
|
127
|
+
* @returns Each argument that opens with `--` and is not a read flag, in argument order.
|
|
128
|
+
*/
|
|
129
|
+
export function readUnknownFlags(
|
|
130
|
+
argv: readonly string[],
|
|
131
|
+
flags: readonly string[],
|
|
132
|
+
): readonly string[] {
|
|
133
|
+
const end = argv.indexOf('--')
|
|
134
|
+
return (end === -1 ? argv : argv.slice(0, end)).filter(
|
|
135
|
+
(argument) => argument.startsWith('--') && !flags.includes(argument),
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Lists every regular file beneath a directory, skipping the directories named.
|
|
141
|
+
*
|
|
142
|
+
* @remarks
|
|
143
|
+
* A skipped name matches a directory at any depth, exactly and case-sensitively.
|
|
144
|
+
*
|
|
145
|
+
* @param directory - The directory to walk.
|
|
146
|
+
* @param skipped - The directory names to skip. Default: {@link SKIPPED_DIRECTORIES}.
|
|
147
|
+
* @returns Each file's forward-slash path and modification time, in walk order.
|
|
148
|
+
* @throws Thrown when `directory` does not exist or cannot be read; check `existsSync` first.
|
|
149
|
+
*/
|
|
150
|
+
export function listFiles(
|
|
151
|
+
directory: string,
|
|
152
|
+
skipped: ReadonlySet<string> = SKIPPED_DIRECTORIES,
|
|
153
|
+
): readonly WalkedFile[] {
|
|
154
|
+
const files: WalkedFile[] = []
|
|
155
|
+
const pending = [directory]
|
|
156
|
+
while (pending.length > 0) {
|
|
157
|
+
const current = pending.pop()
|
|
158
|
+
if (current === undefined) break
|
|
159
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
160
|
+
if (skipped.has(entry.name)) continue
|
|
161
|
+
const path = join(current, entry.name)
|
|
162
|
+
if (entry.isDirectory()) pending.push(path)
|
|
163
|
+
else if (entry.isFile()) {
|
|
164
|
+
files.push({ path: normalizeSlashes(path), modified: statSync(path).mtimeMs })
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return files
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Parses a whole text as one JSON object.
|
|
173
|
+
*
|
|
174
|
+
* @param text - A JSON document, formatted or not, such as a manifest.
|
|
175
|
+
* @returns The parsed object, or undefined when the text is not a JSON object.
|
|
176
|
+
*/
|
|
177
|
+
export function parseJsonRecord(text: string): Record<string, unknown> | undefined {
|
|
178
|
+
try {
|
|
179
|
+
const parsed: unknown = JSON.parse(text)
|
|
180
|
+
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
181
|
+
return Object.fromEntries(Object.entries(parsed))
|
|
182
|
+
}
|
|
183
|
+
} catch {}
|
|
184
|
+
return undefined
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Reads a string member of a record.
|
|
189
|
+
*
|
|
190
|
+
* @param record - The record, or undefined.
|
|
191
|
+
* @param key - The member name.
|
|
192
|
+
* @returns The string, or undefined when the member is absent or not a string.
|
|
193
|
+
*/
|
|
194
|
+
export function readString(
|
|
195
|
+
record: Record<string, unknown> | undefined,
|
|
196
|
+
key: string,
|
|
197
|
+
): string | undefined {
|
|
198
|
+
const value = record?.[key]
|
|
199
|
+
return typeof value === 'string' ? value : undefined
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Resolves the npm command for this host.
|
|
204
|
+
*
|
|
205
|
+
* @remarks
|
|
206
|
+
* The `npm-cli.js` entry beside the running Node binary spawns without a shell on every host; the
|
|
207
|
+
* `npm` shim on Windows is a `.cmd` file that needs one, so that path is the fallback alone.
|
|
208
|
+
*
|
|
209
|
+
* @returns The file to spawn, the arguments that precede npm's own, and whether a shell is needed.
|
|
210
|
+
*/
|
|
211
|
+
export function resolveNpm(): NpmCommand {
|
|
212
|
+
const cli = join(dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js')
|
|
213
|
+
if (existsSync(cli)) return { file: process.execPath, prefix: [cli], shell: false }
|
|
214
|
+
return { file: 'npm', prefix: [], shell: process.platform === 'win32' }
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Runs npm with the given arguments and returns the finished child.
|
|
219
|
+
*
|
|
220
|
+
* @param args - The npm arguments, such as `['view', '@orkestrel/scaffold', 'version']`.
|
|
221
|
+
* @param cwd - The working directory. Default: the current directory.
|
|
222
|
+
* @returns The spawn result with `utf8` stdout and stderr.
|
|
223
|
+
*/
|
|
224
|
+
export function runNpm(args: readonly string[], cwd?: string): SpawnSyncReturns<string> {
|
|
225
|
+
const npm = resolveNpm()
|
|
226
|
+
return spawnSync(npm.file, [...npm.prefix, ...args], {
|
|
227
|
+
encoding: 'utf8',
|
|
228
|
+
windowsHide: true,
|
|
229
|
+
shell: npm.shell,
|
|
230
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
231
|
+
...(cwd === undefined ? {} : { cwd }),
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Reads the name and version a directory's package manifest declares.
|
|
237
|
+
*
|
|
238
|
+
* @param directory - The directory that holds `package.json`.
|
|
239
|
+
* @returns The name and version, each undefined when the manifest is absent, unreadable, or lacks it.
|
|
240
|
+
*/
|
|
241
|
+
export function readManifest(directory: string): Manifest {
|
|
242
|
+
const path = join(directory, 'package.json')
|
|
243
|
+
if (!existsSync(path)) return { name: undefined, version: undefined }
|
|
44
244
|
try {
|
|
45
|
-
const record = parseJsonRecord(readFileSync(
|
|
245
|
+
const record = parseJsonRecord(readFileSync(path, 'utf8'))
|
|
46
246
|
return { name: readString(record, 'name'), version: readString(record, 'version') }
|
|
47
247
|
} catch {
|
|
48
248
|
return { name: undefined, version: undefined }
|
|
49
249
|
}
|
|
50
250
|
}
|
|
51
251
|
|
|
52
|
-
|
|
252
|
+
/**
|
|
253
|
+
* Reads one value npm printed as JSON.
|
|
254
|
+
*
|
|
255
|
+
* @param text - npm's output with the surrounding whitespace removed.
|
|
256
|
+
* @returns The text without its enclosing quotes, or undefined when nothing remains.
|
|
257
|
+
*/
|
|
258
|
+
export function readField(text: string): string | undefined {
|
|
53
259
|
const stripped = text.replace(/^"|"$/gu, '')
|
|
54
260
|
return stripped === '' ? undefined : stripped
|
|
55
261
|
}
|
|
56
262
|
|
|
57
|
-
|
|
263
|
+
/**
|
|
264
|
+
* Reads a NUL-terminated text field out of a tar header block.
|
|
265
|
+
*
|
|
266
|
+
* @param header - The header block.
|
|
267
|
+
* @param start - The field's first byte.
|
|
268
|
+
* @param length - The field's byte length.
|
|
269
|
+
* @returns The field's text up to its first NUL.
|
|
270
|
+
*/
|
|
271
|
+
export function readHeaderText(header: Buffer, start: number, length: number): string {
|
|
58
272
|
const slice = header.subarray(start, start + length)
|
|
59
273
|
const end = slice.indexOf(0)
|
|
60
274
|
return slice.subarray(0, end === -1 ? slice.length : end).toString('utf8')
|
|
61
275
|
}
|
|
62
276
|
|
|
63
|
-
|
|
277
|
+
/**
|
|
278
|
+
* Reads the `path` record out of a pax extended header.
|
|
279
|
+
*
|
|
280
|
+
* @param data - The header's data blocks, each record written as `LENGTH KEY=VALUE` and a line feed.
|
|
281
|
+
* @param offset - The byte where the search starts. Default: 0.
|
|
282
|
+
* @returns The path the first `path=` record names, or undefined when no record does or one is malformed.
|
|
283
|
+
*/
|
|
284
|
+
export function readPaxPath(data: Buffer, offset = 0): string | undefined {
|
|
64
285
|
if (offset >= data.length) return undefined
|
|
65
286
|
const space = data.indexOf(0x20, offset)
|
|
66
287
|
if (space === -1) return undefined
|
|
@@ -72,9 +293,20 @@ function readPaxPath(data: Buffer, offset = 0): string | undefined {
|
|
|
72
293
|
: readPaxPath(data, offset + length)
|
|
73
294
|
}
|
|
74
295
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
296
|
+
/**
|
|
297
|
+
* Advances a tar reader by the block at one offset.
|
|
298
|
+
*
|
|
299
|
+
* @remarks
|
|
300
|
+
* A tar entry occupies a header block and the data blocks its size names, so a step ignores every
|
|
301
|
+
* block offset an earlier entry already consumed.
|
|
302
|
+
*
|
|
303
|
+
* @param archive - The uncompressed archive.
|
|
304
|
+
* @param state - The reader's state before this block.
|
|
305
|
+
* @param offset - The block's first byte.
|
|
306
|
+
* @returns The state after the block: a regular file added, a pax or GNU long name remembered for the
|
|
307
|
+
* next entry, or the reader done at the first all-zero block.
|
|
308
|
+
*/
|
|
309
|
+
export function stepTar(archive: Buffer, state: TarState, offset: number): TarState {
|
|
78
310
|
if (state.done || offset < state.next) return state
|
|
79
311
|
const header = archive.subarray(offset, offset + BLOCK)
|
|
80
312
|
if (header.every((byte) => byte === 0)) return { ...state, done: true }
|
|
@@ -100,12 +332,18 @@ function stepTar(archive: Buffer, state: TarState, offset: number): TarState {
|
|
|
100
332
|
}
|
|
101
333
|
}
|
|
102
334
|
|
|
103
|
-
|
|
335
|
+
/**
|
|
336
|
+
* Reads the regular files out of an uncompressed tar archive.
|
|
337
|
+
*
|
|
338
|
+
* @param archive - The uncompressed archive.
|
|
339
|
+
* @returns Each file's path and bytes; a later entry of the same name replaces an earlier one at the
|
|
340
|
+
* earlier position, as a map write does.
|
|
341
|
+
*/
|
|
342
|
+
export function readTar(archive: Buffer): ReadonlyMap<string, Buffer> {
|
|
104
343
|
const offsets = Array.from(
|
|
105
344
|
{ length: Math.floor(archive.length / BLOCK) },
|
|
106
345
|
(_, index) => index * BLOCK,
|
|
107
346
|
)
|
|
108
|
-
// A later entry of the same name replaces an earlier one at the earlier position, as a map write does.
|
|
109
347
|
return new Map(
|
|
110
348
|
offsets.reduce<TarState>((state, offset) => stepTar(archive, state, offset), {
|
|
111
349
|
next: 0,
|
|
@@ -116,7 +354,13 @@ function readTar(archive: Buffer): ReadonlyMap<string, Buffer> {
|
|
|
116
354
|
)
|
|
117
355
|
}
|
|
118
356
|
|
|
119
|
-
|
|
357
|
+
/**
|
|
358
|
+
* Unpacks a gzip-compressed tar archive.
|
|
359
|
+
*
|
|
360
|
+
* @param archive - The compressed archive.
|
|
361
|
+
* @returns The regular files it holds, or the error that stopped the unpacking.
|
|
362
|
+
*/
|
|
363
|
+
export function unpackTar(archive: Buffer): ReadonlyMap<string, Buffer> | Error {
|
|
120
364
|
try {
|
|
121
365
|
return readTar(gunzipSync(archive))
|
|
122
366
|
} catch (error) {
|
|
@@ -124,7 +368,42 @@ function unpackTar(archive: Buffer): ReadonlyMap<string, Buffer> | Error {
|
|
|
124
368
|
}
|
|
125
369
|
}
|
|
126
370
|
|
|
127
|
-
|
|
371
|
+
/**
|
|
372
|
+
* Reads one value from npm's view of the registry.
|
|
373
|
+
*
|
|
374
|
+
* @param args - The npm arguments, such as `['view', 'name', 'version', '--json']`.
|
|
375
|
+
* @returns The value npm printed, or undefined when npm exited nonzero or printed nothing.
|
|
376
|
+
*/
|
|
377
|
+
export function readNpmValue(args: readonly string[]): string | undefined {
|
|
378
|
+
const result = runNpm(args)
|
|
379
|
+
return result.status === 0 ? readField(result.stdout.trim()) : undefined
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Fetches a tarball over HTTP.
|
|
384
|
+
*
|
|
385
|
+
* @param url - The tarball's address.
|
|
386
|
+
* @returns The response body, or undefined when the request failed or answered a non-success status.
|
|
387
|
+
*/
|
|
388
|
+
export async function fetchTarball(url: string): Promise<Buffer | undefined> {
|
|
389
|
+
try {
|
|
390
|
+
const response = await fetch(url)
|
|
391
|
+
if (!response.ok) return undefined
|
|
392
|
+
return Buffer.from(await response.arrayBuffer())
|
|
393
|
+
} catch {
|
|
394
|
+
return undefined
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Reads the tarball a run compares against.
|
|
400
|
+
*
|
|
401
|
+
* @param name - The package name to look up when no tarball file is given.
|
|
402
|
+
* @param requested - The version to compare, or undefined for the one the registry serves.
|
|
403
|
+
* @param tarball - A local `.tgz` path, or undefined to fetch from the registry.
|
|
404
|
+
* @returns The archive with its version, or the reason none could be read.
|
|
405
|
+
*/
|
|
406
|
+
export async function readSource(
|
|
128
407
|
name: string,
|
|
129
408
|
requested: string | undefined,
|
|
130
409
|
tarball: string | undefined,
|
|
@@ -152,30 +431,50 @@ async function readSource(
|
|
|
152
431
|
: { archive: fetched, version, failure: undefined }
|
|
153
432
|
}
|
|
154
433
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
function isMaterial(path: string): boolean {
|
|
434
|
+
/**
|
|
435
|
+
* Checks whether a published path counts in the comparison.
|
|
436
|
+
*
|
|
437
|
+
* @param path - A path inside the tarball or the dist tree.
|
|
438
|
+
* @returns True if the path is not a sourcemap; false otherwise.
|
|
439
|
+
*/
|
|
440
|
+
export function isMaterial(path: string): boolean {
|
|
164
441
|
return !path.endsWith('.map')
|
|
165
442
|
}
|
|
166
443
|
|
|
167
|
-
|
|
444
|
+
/**
|
|
445
|
+
* Checks whether two versions of one file differ materially.
|
|
446
|
+
*
|
|
447
|
+
* @param left - One version's bytes.
|
|
448
|
+
* @param right - The other version's bytes.
|
|
449
|
+
* @returns True if a file holding a NUL byte differs on any byte, or a text file differs with all
|
|
450
|
+
* whitespace removed; false otherwise.
|
|
451
|
+
*/
|
|
452
|
+
export function differs(left: Buffer, right: Buffer): boolean {
|
|
168
453
|
if (left.includes(0) || right.includes(0)) return !left.equals(right)
|
|
169
454
|
return left.toString('utf8').replace(/\s+/gu, '') !== right.toString('utf8').replace(/\s+/gu, '')
|
|
170
455
|
}
|
|
171
456
|
|
|
172
|
-
|
|
457
|
+
/**
|
|
458
|
+
* Runs the comparison and prints its summary.
|
|
459
|
+
*
|
|
460
|
+
* @param argv - The arguments after the script path.
|
|
461
|
+
* @returns The exit code: 0 when nothing material differs, 3 when something does, 2 when the tarball
|
|
462
|
+
* cannot be read, 64 on usage.
|
|
463
|
+
*/
|
|
464
|
+
export async function main(argv: readonly string[]): Promise<number> {
|
|
465
|
+
const unknown = readUnknownFlags(argv, FLAGS)
|
|
466
|
+
if (unknown.length > 0) {
|
|
467
|
+
console.error(
|
|
468
|
+
`compare: unknown flag ${unknown.join(', ')}; give each flag and its value as separate arguments\n${USAGE}`,
|
|
469
|
+
)
|
|
470
|
+
return 64
|
|
471
|
+
}
|
|
173
472
|
const missing = readMissingFlags(argv, ['--package', '--version', '--tarball', '--dist'])
|
|
174
473
|
if (missing.length > 0) {
|
|
175
474
|
console.error(`compare: ${missing.join(', ')} given with no value`)
|
|
176
475
|
return 64
|
|
177
476
|
}
|
|
178
|
-
const manifest = readManifest()
|
|
477
|
+
const manifest = readManifest('.')
|
|
179
478
|
const name = readOption(argv, '--package') ?? manifest.name
|
|
180
479
|
const dist = readOption(argv, '--dist') ?? 'dist'
|
|
181
480
|
const tarball = readOption(argv, '--tarball')
|
|
@@ -184,9 +483,7 @@ async function main(argv: readonly string[]): Promise<number> {
|
|
|
184
483
|
!existsSync(dist) ||
|
|
185
484
|
(argv.includes('--tarball') && tarball === undefined)
|
|
186
485
|
) {
|
|
187
|
-
console.error(
|
|
188
|
-
'usage: compare.ts [--package NAME] [--version V] [--tarball PATH] [--dist dist] [--json]',
|
|
189
|
-
)
|
|
486
|
+
console.error(USAGE)
|
|
190
487
|
return 64
|
|
191
488
|
}
|
|
192
489
|
const source = await readSource(name, readOption(argv, '--version'), tarball)
|
|
@@ -210,7 +507,7 @@ async function main(argv: readonly string[]): Promise<number> {
|
|
|
210
507
|
)
|
|
211
508
|
const local = new Map(
|
|
212
509
|
listFiles(dist, NOTHING_SKIPPED).flatMap(({ path }) => {
|
|
213
|
-
const key = relative(dist, path)
|
|
510
|
+
const key = normalizeSlashes(relative(dist, path))
|
|
214
511
|
return isMaterial(key) ? [[key, readFileSync(path)] as const] : []
|
|
215
512
|
}),
|
|
216
513
|
)
|
|
@@ -247,16 +544,8 @@ async function main(argv: readonly string[]): Promise<number> {
|
|
|
247
544
|
return material ? 3 : 0
|
|
248
545
|
}
|
|
249
546
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
return Buffer.from(await response.arrayBuffer())
|
|
255
|
-
} catch {
|
|
256
|
-
return undefined
|
|
257
|
-
}
|
|
547
|
+
if (process.argv[1] !== undefined && realpathSync(process.argv[1]) === import.meta.filename) {
|
|
548
|
+
main(process.argv.slice(2)).then((code) => {
|
|
549
|
+
process.exitCode = code
|
|
550
|
+
})
|
|
258
551
|
}
|
|
259
|
-
|
|
260
|
-
main(process.argv.slice(2)).then((code) => {
|
|
261
|
-
process.exitCode = code
|
|
262
|
-
})
|