@prestack/thread 0.1.21 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +109 -0
- package/dist/core/attach.js +21 -0
- package/dist/core/descend.js +6 -3
- package/dist/core/generators/commit.js +55 -3
- package/dist/core/generators/dev.d.ts +1 -0
- package/dist/core/generators/dev.js +68 -0
- package/dist/core/tree.d.ts +1 -0
- package/dist/core/tree.js +7 -1
- package/dist/derivation/generators/build.d.ts +3 -1
- package/dist/derivation/generators/build.js +103 -22
- package/dist/derivation/generators/release.js +74 -26
- package/dist/derivation/release.js +3 -3
- package/dist/integrations/cli.js +69 -10
- package/dist/integrations/pglite.js +13 -3
- package/dist/integrations/target.d.ts +3 -0
- package/dist/integrity/attach.js +1 -5
- package/dist/integrity/slopnators/sl_stale_path.js +10 -0
- package/dist/operations/access.js +3 -2
- package/dist/operations/capabilities.d.ts +2 -0
- package/dist/operations/capabilities.js +2 -0
- package/dist/operations/generators/capability.js +234 -24
- package/dist/operations/generators/compile.js +150 -50
- package/dist/operations/generators/seed.d.ts +3 -0
- package/dist/operations/generators/seed.js +25 -3
- package/dist/operations/nodes.d.ts +2 -2
- package/package.json +1 -1
- package/dist/core/actor.ts +0 -21
- package/dist/core/attach.ts +0 -613
- package/dist/core/chain.ts +0 -145
- package/dist/core/condition.ts +0 -227
- package/dist/core/descend.ts +0 -1185
- package/dist/core/errors/catalogue.ts +0 -314
- package/dist/core/errors/classes.ts +0 -51
- package/dist/core/errors/error.ts +0 -152
- package/dist/core/errors/namespaces.ts +0 -19
- package/dist/core/finding.ts +0 -37
- package/dist/core/generators/commit.ts +0 -157
- package/dist/core/init.ts +0 -222
- package/dist/core/invoked.ts +0 -52
- package/dist/core/round.ts +0 -74
- package/dist/core/rows.ts +0 -506
- package/dist/core/scope.ts +0 -20
- package/dist/core/thread.ts +0 -29
- package/dist/core/tree.ts +0 -90
- package/dist/core/types.ts +0 -351
- package/dist/derivation/attach.ts +0 -57
- package/dist/derivation/generators/build.ts +0 -371
- package/dist/derivation/generators/check.ts +0 -118
- package/dist/derivation/generators/determinism.ts +0 -145
- package/dist/derivation/generators/release.ts +0 -236
- package/dist/derivation/release.ts +0 -43
- package/dist/integrations/attach.ts +0 -187
- package/dist/integrations/cli.ts +0 -606
- package/dist/integrations/declared.ts +0 -34
- package/dist/integrations/disk.ts +0 -466
- package/dist/integrations/generators/read.ts +0 -127
- package/dist/integrations/generators/vault.ts +0 -210
- package/dist/integrations/https.ts +0 -216
- package/dist/integrations/pglite.ts +0 -206
- package/dist/integrations/secret.ts +0 -54
- package/dist/integrations/target.ts +0 -672
- package/dist/integrations/vault.ts +0 -148
- package/dist/integrity/attach.ts +0 -150
- package/dist/integrity/check-threads.ts +0 -166
- package/dist/integrity/constraint.ts +0 -1246
- package/dist/integrity/generators/compiler.ts +0 -138
- package/dist/integrity/generators/gate.ts +0 -227
- package/dist/integrity/generators/issues.ts +0 -160
- package/dist/integrity/generators/publish.ts +0 -233
- package/dist/integrity/generators/trial.ts +0 -359
- package/dist/integrity/lint.ts +0 -214
- package/dist/integrity/lsp/bin.ts +0 -44
- package/dist/integrity/lsp/diagnostics.ts +0 -92
- package/dist/integrity/lsp/findings.ts +0 -63
- package/dist/integrity/lsp/lint.ts +0 -124
- package/dist/integrity/lsp/protocol.ts +0 -78
- package/dist/integrity/lsp/server.ts +0 -224
- package/dist/integrity/slopnator.ts +0 -324
- package/dist/integrity/slopnators/sl_broken_lineage.ts +0 -38
- package/dist/integrity/slopnators/sl_declared_elsewhere.ts +0 -60
- package/dist/integrity/slopnators/sl_derived_drift.ts +0 -101
- package/dist/integrity/slopnators/sl_door_in_a_node.ts +0 -250
- package/dist/integrity/slopnators/sl_hardcoded_style.ts +0 -55
- package/dist/integrity/slopnators/sl_inert_island.ts +0 -84
- package/dist/integrity/slopnators/sl_inert_slopnator.ts +0 -93
- package/dist/integrity/slopnators/sl_missing_row.ts +0 -140
- package/dist/integrity/slopnators/sl_no_barrel.ts +0 -48
- package/dist/integrity/slopnators/sl_no_child_process.ts +0 -100
- package/dist/integrity/slopnators/sl_no_comment.ts +0 -43
- package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +0 -276
- package/dist/integrity/slopnators/sl_no_import_cycle.ts +0 -66
- package/dist/integrity/slopnators/sl_no_such_command.ts +0 -81
- package/dist/integrity/slopnators/sl_only_takes_a_literal.ts +0 -63
- package/dist/integrity/slopnators/sl_outside_an_operation.ts +0 -25
- package/dist/integrity/slopnators/sl_screen_holds_only_declared.ts +0 -70
- package/dist/integrity/slopnators/sl_secret_literal.ts +0 -68
- package/dist/integrity/slopnators/sl_stale_path.ts +0 -178
- package/dist/integrity/slopnators/sl_twice_meant.ts +0 -57
- package/dist/integrity/slopnators/sl_twice_parented.ts +0 -82
- package/dist/integrity/slopnators/sl_undeclared_crossing.ts +0 -481
- package/dist/integrity/slopnators/sl_undeclared_destination.ts +0 -113
- package/dist/integrity/slopnators/sl_undeclared_element.ts +0 -44
- package/dist/integrity/slopnators/sl_undeclared_list.ts +0 -358
- package/dist/integrity/slopnators/sl_undeclared_node.ts +0 -75
- package/dist/integrity/slopnators/sl_undeclared_operation.ts +0 -257
- package/dist/integrity/slopnators/sl_undeclared_variant.ts +0 -152
- package/dist/integrity/slopnators/sl_undeclared_webhook.ts +0 -108
- package/dist/integrity/slopnators/sl_underived_route.ts +0 -72
- package/dist/integrity/slopnators/sl_undescribed.ts +0 -65
- package/dist/integrity/slopnators/sl_ungoverned_island.ts +0 -60
- package/dist/integrity/slopnators/sl_unlisted_program.ts +0 -46
- package/dist/integrity/slopnators/sl_unnamed_island.ts +0 -41
- package/dist/integrity/slopnators/sl_unpaired_member.ts +0 -139
- package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +0 -71
- package/dist/integrity/slopnators/sl_unprefixed_member.ts +0 -110
- package/dist/integrity/slopnators/sl_unthreaded.ts +0 -421
- package/dist/integrity/slopnators/sl_untranslated_key.ts +0 -140
- package/dist/integrity/slopnators/sl_unvaulted_secret.ts +0 -132
- package/dist/integrity/slopnators/sl_unversioned.ts +0 -52
- package/dist/integrity/sweep.ts +0 -395
- package/dist/knowledge/attach.ts +0 -327
- package/dist/knowledge/generators/docsgen.ts +0 -186
- package/dist/knowledge/generators/glossary.ts +0 -73
- package/dist/knowledge/glossary.ts +0 -6
- package/dist/operations/access.ts +0 -131
- package/dist/operations/attach.ts +0 -408
- package/dist/operations/capabilities.ts +0 -88
- package/dist/operations/dispatch.ts +0 -294
- package/dist/operations/error-results.ts +0 -31
- package/dist/operations/generators/capability.ts +0 -1054
- package/dist/operations/generators/compile.ts +0 -701
- package/dist/operations/generators/permits.ts +0 -229
- package/dist/operations/generators/seed.ts +0 -359
- package/dist/operations/generators/trial.ts +0 -666
- package/dist/operations/nodes.ts +0 -53
- package/dist/operations/operations.ts +0 -91
- package/dist/operations/result.ts +0 -28
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto'
|
|
2
|
-
import { join } from 'node:path'
|
|
3
|
-
|
|
4
|
-
import type { Env } from '../core/actor.ts'
|
|
5
|
-
import { existsSync, readFileSync, writeFileSync } from './disk.ts'
|
|
6
|
-
|
|
7
|
-
export const VAULT_DIR = '.thread'
|
|
8
|
-
|
|
9
|
-
export const SEALED_NAME = 'vault.sealed.json'
|
|
10
|
-
|
|
11
|
-
export const KEY_NAME = 'vault.key'
|
|
12
|
-
|
|
13
|
-
export const SEALED_AT = `${VAULT_DIR}/${SEALED_NAME}`
|
|
14
|
-
|
|
15
|
-
export const KEY_AT = `${VAULT_DIR}/${KEY_NAME}`
|
|
16
|
-
|
|
17
|
-
export const ALGORITHM = 'aes-256-gcm'
|
|
18
|
-
|
|
19
|
-
export const KEY_BYTES = 32
|
|
20
|
-
|
|
21
|
-
const IV_BYTES = 12
|
|
22
|
-
|
|
23
|
-
const noKey = (root: string): Error =>
|
|
24
|
-
new Error(
|
|
25
|
-
`there is no vault key at ${join(root, KEY_AT)}, so nothing here can open a sealed value. It is deliberately not ` +
|
|
26
|
-
'in version control — whoever set this app up has it. Ask them for it, or run `thread create secret`, which ' +
|
|
27
|
-
'generates one when there is none.',
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
const unsealable = (named: string, why: string): Error =>
|
|
31
|
-
new Error(
|
|
32
|
-
`"${named}" is in the sealed store and would not open: ${why}. Either the key is not the one it was sealed under, ` +
|
|
33
|
-
'or the store has been edited by hand — it is ciphertext, and there is nothing in it to edit.',
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
const undeclared = (named: string, known: readonly string[]): Error =>
|
|
37
|
-
new Error(
|
|
38
|
-
`nothing may ask for a secret this app does not declare, and "${named}" is not one of ` +
|
|
39
|
-
`${known.length === 0 ? '(this app declares none)' : known.join(', ')}. A value is asked for by something that ` +
|
|
40
|
-
'declared it needed it, or not at all.',
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
const unset = (named: string, env: Env): Error =>
|
|
44
|
-
new Error(
|
|
45
|
-
`"${named}" is declared and has no value sealed for \`${env}\`. Run \`thread create secret set ${named}\` and give it one — a ` +
|
|
46
|
-
'declared secret nothing was ever given is refused here rather than resolved to an empty string.',
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
type Store = Record<string, Partial<Record<Env, string>>>
|
|
50
|
-
|
|
51
|
-
const store = (root: string): Store => {
|
|
52
|
-
const at = join(root, SEALED_AT)
|
|
53
|
-
if (!existsSync(at)) return {}
|
|
54
|
-
try {
|
|
55
|
-
const held: unknown = JSON.parse(readFileSync(at, 'utf8'))
|
|
56
|
-
return typeof held === 'object' && held !== null ? (held as Store) : {}
|
|
57
|
-
} catch {
|
|
58
|
-
return {}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export const keyOf = (root: string): Buffer | undefined => {
|
|
63
|
-
const at = join(root, KEY_AT)
|
|
64
|
-
if (!existsSync(at)) return undefined
|
|
65
|
-
try {
|
|
66
|
-
const held = Buffer.from(readFileSync(at, 'utf8').trim(), 'base64')
|
|
67
|
-
return held.length === KEY_BYTES ? held : undefined
|
|
68
|
-
} catch {
|
|
69
|
-
return undefined
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function keyed(root: string): Buffer {
|
|
74
|
-
const held = keyOf(root)
|
|
75
|
-
if (held !== undefined) return held
|
|
76
|
-
const made = randomBytes(KEY_BYTES)
|
|
77
|
-
writeFileSync(join(root, KEY_AT), `${made.toString('base64')}\n`, { mode: 0o600 })
|
|
78
|
-
return made
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export function seal(root: string, named: string, env: Env, value: string): void {
|
|
82
|
-
const key = keyed(root)
|
|
83
|
-
const iv = randomBytes(IV_BYTES)
|
|
84
|
-
const cipher = createCipheriv(ALGORITHM, key, iv)
|
|
85
|
-
const body = Buffer.concat([cipher.update(value, 'utf8'), cipher.final()])
|
|
86
|
-
const held = store(root)
|
|
87
|
-
held[named] = { ...held[named], [env]: [iv, cipher.getAuthTag(), body].map((one) => one.toString('base64')).join('.') }
|
|
88
|
-
const sorted = Object.fromEntries(Object.keys(held).sort().map((one) => [one, held[one]]))
|
|
89
|
-
const at = join(root, SEALED_AT)
|
|
90
|
-
writeFileSync(at, `${JSON.stringify(sorted, null, 2)}\n`)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export const sealedIn = (root: string, env: Env): readonly string[] =>
|
|
94
|
-
Object.entries(store(root))
|
|
95
|
-
.filter(([, held]) => held[env] !== undefined)
|
|
96
|
-
.map(([named]) => named)
|
|
97
|
-
.sort()
|
|
98
|
-
|
|
99
|
-
function unsealed(key: Buffer, named: string, held: string): string {
|
|
100
|
-
const parts = held.split('.')
|
|
101
|
-
if (parts.length !== 3) throw unsealable(named, 'it is not three base64 parts separated by dots')
|
|
102
|
-
const [iv, tag, body] = parts.map((one) => Buffer.from(one, 'base64')) as [Buffer, Buffer, Buffer]
|
|
103
|
-
try {
|
|
104
|
-
const decipher = createDecipheriv(ALGORITHM, key, iv)
|
|
105
|
-
decipher.setAuthTag(tag)
|
|
106
|
-
return `${decipher.update(body).toString('utf8')}${decipher.final('utf8')}`
|
|
107
|
-
} catch (thrown) {
|
|
108
|
-
throw unsealable(named, thrown instanceof Error ? thrown.message : String(thrown))
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export function opened(
|
|
113
|
-
root: string,
|
|
114
|
-
declared: readonly string[],
|
|
115
|
-
env: Env,
|
|
116
|
-
): (refs: readonly string[]) => Promise<Readonly<Record<string, string>>> {
|
|
117
|
-
const known = new Set(declared)
|
|
118
|
-
return async (refs) => {
|
|
119
|
-
if (refs.length === 0) return {}
|
|
120
|
-
const key = keyOf(root)
|
|
121
|
-
if (key === undefined) throw noKey(root)
|
|
122
|
-
const held = store(root)
|
|
123
|
-
const out: Record<string, string> = {}
|
|
124
|
-
for (const named of refs) {
|
|
125
|
-
if (!known.has(named)) throw undeclared(named, [...known].sort())
|
|
126
|
-
const sealed = held[named]?.[env]
|
|
127
|
-
if (sealed === undefined) throw unset(named, env)
|
|
128
|
-
out[named] = unsealed(key, named, sealed)
|
|
129
|
-
}
|
|
130
|
-
return out
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export function unseal(root: string, named: string, envs: readonly Env[]): readonly Env[] {
|
|
135
|
-
const held = store(root)
|
|
136
|
-
const kept = held[named]
|
|
137
|
-
if (kept === undefined) return []
|
|
138
|
-
const taken = envs.filter((env) => kept[env] !== undefined)
|
|
139
|
-
if (taken.length === 0) return []
|
|
140
|
-
const left = Object.fromEntries(Object.entries(kept).filter(([env]) => !envs.includes(env as Env)))
|
|
141
|
-
if (Object.keys(left).length === 0) delete held[named]
|
|
142
|
-
else held[named] = left as Partial<Record<Env, string>>
|
|
143
|
-
const sorted = Object.fromEntries(Object.keys(held).sort().map((each) => [each, held[each]]))
|
|
144
|
-
writeFileSync(join(root, SEALED_AT), `${JSON.stringify(sorted, null, 2)}\n`)
|
|
145
|
-
return taken
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export const IGNORES: readonly string[] = [KEY_AT]
|
package/dist/integrity/attach.ts
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import type { Attaching } from '../core/attach.ts'
|
|
2
|
-
import { SHAPED } from '../core/init.ts'
|
|
3
|
-
import { NAMED } from '../core/scope.ts'
|
|
4
|
-
|
|
5
|
-
import { quoted } from '../core/rows.ts'
|
|
6
|
-
|
|
7
|
-
const DESCRIBES =
|
|
8
|
-
'What this app refuses, and the checks that make each refusal actually happen. Every check has one owner, so none of them can be switched off somewhere nobody is looking.'
|
|
9
|
-
|
|
10
|
-
const CONSTRAINT =
|
|
11
|
-
'The rules the framework enforces on every app that installs it. They are read from the installed package rather than from this tree, so there is nothing here to weaken.'
|
|
12
|
-
|
|
13
|
-
const RULES =
|
|
14
|
-
'What this app refuses on top of what the framework already does. `thread create rule` asks what to ban, where it applies and what to do instead, then writes the row and the check beside it.'
|
|
15
|
-
|
|
16
|
-
const HOME = `node_modules/${NAMED}/dist/integrity`
|
|
17
|
-
|
|
18
|
-
const GATE =
|
|
19
|
-
'Every surface that can refuse, each naming where it lives and the command that runs it, so the gate is derived from what this system declares rather than kept as a string beside it.'
|
|
20
|
-
|
|
21
|
-
const COMPILER =
|
|
22
|
-
'The compiler run over the files this app declares and no others. A type error is the plainest kind of broken build, so it is refused here rather than found by whoever runs the app next.'
|
|
23
|
-
|
|
24
|
-
const INTEGRITY_SOURCE = `import type { Threaded } from '${SHAPED}'
|
|
25
|
-
|
|
26
|
-
export const INTEGRITY: Threaded = {
|
|
27
|
-
id: 'integrity',
|
|
28
|
-
at: '_threads/integrity.ts',
|
|
29
|
-
describes: ${quoted(DESCRIBES)},
|
|
30
|
-
version: 1,
|
|
31
|
-
declares: [
|
|
32
|
-
{
|
|
33
|
-
id: 'compiler',
|
|
34
|
-
at: 'tsconfig.json',
|
|
35
|
-
describes: ${quoted(COMPILER)},
|
|
36
|
-
version: 1,
|
|
37
|
-
by: 'thread compiler',
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
id: 'constraint',
|
|
41
|
-
at: '${HOME}/constraint.js',
|
|
42
|
-
describes: ${quoted(CONSTRAINT)},
|
|
43
|
-
version: 1,
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
id: 'rules',
|
|
47
|
-
at: '_threads/rules.ts',
|
|
48
|
-
describes: ${quoted(RULES)},
|
|
49
|
-
version: 1,
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
id: 'gate',
|
|
53
|
-
at: '_threads/gate.ts',
|
|
54
|
-
describes: ${quoted(GATE)},
|
|
55
|
-
version: 1,
|
|
56
|
-
},
|
|
57
|
-
],
|
|
58
|
-
}
|
|
59
|
-
`
|
|
60
|
-
|
|
61
|
-
const RULES_SOURCE = `import type { Constraint } from '${NAMED}/integrity/constraint'
|
|
62
|
-
|
|
63
|
-
export const RULES: readonly Constraint[] = []
|
|
64
|
-
`
|
|
65
|
-
|
|
66
|
-
const GATE_SOURCE = `import type { Threaded } from '${SHAPED}'
|
|
67
|
-
|
|
68
|
-
export const GATE: Threaded = {
|
|
69
|
-
id: 'gate',
|
|
70
|
-
at: '_threads/gate.ts',
|
|
71
|
-
describes: ${quoted(GATE)},
|
|
72
|
-
version: 1,
|
|
73
|
-
declares: [],
|
|
74
|
-
}
|
|
75
|
-
`
|
|
76
|
-
|
|
77
|
-
const SEEDED = {
|
|
78
|
-
attaches: ['@prestack/thread/derivation/attach'] as const,
|
|
79
|
-
id: 'integrity',
|
|
80
|
-
declares: [
|
|
81
|
-
' {',
|
|
82
|
-
' id: \'integrity\',',
|
|
83
|
-
' at: \'_threads/integrity.ts\',',
|
|
84
|
-
' describes: \'What this app refuses, and the checks that make each refusal actually happen. Every check has one owner, so none of them can be switched off somewhere nobody is looking.\',',
|
|
85
|
-
' version: 1,',
|
|
86
|
-
' },',
|
|
87
|
-
].join('\n'),
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const SHIPPED: readonly { readonly into: string; readonly list: string; readonly id: string; readonly source: string }[] = [
|
|
91
|
-
{
|
|
92
|
-
into: 'derivation.ts',
|
|
93
|
-
list: 'GENERATORS',
|
|
94
|
-
id: 'gate-generator',
|
|
95
|
-
source: " {\n id: 'gate-generator',\n at: 'node_modules/@prestack/thread/dist/integrity/generators/gate.js',\n describes: 'The generator of the gate chain — it reads every row that names a command and refuses one naming none, rather than writing a shorter gate.',\n version: 1,\n by: 'thread gate',\n },",
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
into: 'derivation.ts',
|
|
99
|
-
list: 'GENERATORS',
|
|
100
|
-
id: 'compiler-generator',
|
|
101
|
-
source: " {\n id: 'compiler-generator',\n at: 'node_modules/@prestack/thread/dist/integrity/generators/compiler.js',\n describes: 'The generator of the compiler config — it walks this tree, leaves out every directory with nothing declared beneath it, and writes that list into the config as what the compiler may not read. It checks the same list before letting the compiler run, because a compile that was pointed at the wrong code is not a compile that passed.',\n version: 1,\n by: 'thread compiler',\n runs: 'thread compiler --check',\n },",
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
into: 'derivation.ts',
|
|
105
|
-
list: 'GENERATORS',
|
|
106
|
-
id: 'descent-runner',
|
|
107
|
-
source: " {\n id: 'descent-runner',\n at: 'node_modules/@prestack/thread/dist/integrity/check-threads.js',\n describes: 'The walk over everything this app tracks, refusing any file no row names. It runs before anything is built, so a file nothing declares stops the build rather than reaching a person.',\n version: 1,\n by: 'thread descent',\n runs: 'thread descent',\n },",
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
into: 'derivation.ts',
|
|
111
|
-
list: 'GENERATORS',
|
|
112
|
-
id: 'sweep-runner',
|
|
113
|
-
source: " {\n id: 'sweep-runner',\n at: 'node_modules/@prestack/thread/dist/integrity/sweep.js',\n describes: 'Every rule — the framework\\'s and this app\\'s own — run over the files it governs. A rule that could not run is named rather than counted as one that passed.',\n version: 1,\n by: 'thread sweep',\n runs: 'thread sweep',\n },",
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
into: 'derivation.ts',
|
|
117
|
-
list: 'GENERATORS',
|
|
118
|
-
id: 'issues-reader',
|
|
119
|
-
source: " {\n id: 'issues-reader',\n at: 'node_modules/@prestack/thread/dist/integrity/generators/issues.js',\n describes: 'Everything wrong with this tree in one listing: files no row declares, files that will not compile, and what the rules refuse. It only reports, so it answers even when every other check has stopped.',\n version: 1,\n by: 'thread issues',\n runs: 'thread issues --check',\n },",
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
into: 'derivation.ts',
|
|
123
|
-
list: 'GENERATORS',
|
|
124
|
-
id: 'publish-runner',
|
|
125
|
-
source: " {\n id: 'publish-runner',\n at: 'node_modules/@prestack/thread/dist/integrity/generators/publish.js',\n describes: 'Sends this repository\\'s packages to a registry. It derives what each package says about itself first, then builds, then refuses to send anything until the gate, the typecheck and the seal have each passed, and commits what it derived once the packages are out. The seal is checked rather than regenerated, because a release that rewrites its own seal can never fail it.',\n version: 1,\n by: 'thread publish',\n },",
|
|
126
|
-
},
|
|
127
|
-
]
|
|
128
|
-
export const ATTACH: Attaching = {
|
|
129
|
-
attaches: SEEDED.attaches,
|
|
130
|
-
id: SEEDED.id,
|
|
131
|
-
declares: SEEDED.declares,
|
|
132
|
-
writes: [
|
|
133
|
-
{ file: 'integrity.ts', source: INTEGRITY_SOURCE },
|
|
134
|
-
{ file: 'rules.ts', source: RULES_SOURCE },
|
|
135
|
-
{ file: 'gate.ts', source: GATE_SOURCE },
|
|
136
|
-
],
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
ignores: ['.thread/', '.next/', 'node_modules/', 'tsconfig.tsbuildinfo'],
|
|
140
|
-
scripts: {
|
|
141
|
-
build: 'next build',
|
|
142
|
-
start: 'next start',
|
|
143
|
-
},
|
|
144
|
-
|
|
145
|
-
needs: {
|
|
146
|
-
'@types/node': '^24.0.0',
|
|
147
|
-
typescript: '^6.0.0',
|
|
148
|
-
},
|
|
149
|
-
rows: SHIPPED,
|
|
150
|
-
}
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import { readdirSync } from '../integrations/disk.ts'
|
|
2
|
-
import { dirname, join } from 'node:path'
|
|
3
|
-
import { argv, stdout } from 'node:process'
|
|
4
|
-
|
|
5
|
-
import { exit, invoked } from '../core/invoked.ts'
|
|
6
|
-
|
|
7
|
-
import { ROOT, THREAD_AT, TREE, configured, descend, unkept, type Finding, type Pass } from '../core/descend.ts'
|
|
8
|
-
import { runChecker } from '../core/finding.ts'
|
|
9
|
-
|
|
10
|
-
export { ROOT, THREAD_AT, TREE, descend }
|
|
11
|
-
export type { Finding, Pass }
|
|
12
|
-
|
|
13
|
-
const ELSEWHERE = new Set(['node_modules', '.git'])
|
|
14
|
-
|
|
15
|
-
export function trackedFiles(): readonly string[] {
|
|
16
|
-
const found: string[] = []
|
|
17
|
-
|
|
18
|
-
const walk = (at: string, under: string): void => {
|
|
19
|
-
for (const one of readdirSync(at, { withFileTypes: true })) {
|
|
20
|
-
if (ELSEWHERE.has(one.name)) continue
|
|
21
|
-
const here = under === '' ? one.name : `${under}/${one.name}`
|
|
22
|
-
if (one.isDirectory()) {
|
|
23
|
-
if (unkept(here)) continue
|
|
24
|
-
walk(join(at, one.name), here)
|
|
25
|
-
continue
|
|
26
|
-
}
|
|
27
|
-
if (unkept(here)) continue
|
|
28
|
-
found.push(here)
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
walk(ROOT, '')
|
|
33
|
-
return found.sort()
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function unthreaded(pass: Pass, files: readonly string[]): readonly string[] {
|
|
37
|
-
return files.filter((one) => one !== THREAD_AT && !pass.threaded.has(one))
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function twiceParented(pass: Pass): readonly { readonly at: string; readonly parents: readonly string[] }[] {
|
|
41
|
-
const found: { at: string; parents: readonly string[] }[] = []
|
|
42
|
-
for (const [at, held] of pass.threaded) {
|
|
43
|
-
const parents = held.filter((one) => one.list === 'declares').map((one) => one.by)
|
|
44
|
-
if (parents.length > 1) found.push({ at, parents })
|
|
45
|
-
}
|
|
46
|
-
return found
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function refusals(pass: Pass, files: readonly string[]): readonly Finding[] {
|
|
50
|
-
const found: Finding[] = [...pass.broken, ...pass.findings]
|
|
51
|
-
|
|
52
|
-
const held = configured()
|
|
53
|
-
if (held.trouble !== undefined) {
|
|
54
|
-
found.push({
|
|
55
|
-
file: held.trouble.split(' ')[0] ?? 'tsconfig.json',
|
|
56
|
-
detail: `the gate fell back to its own compiler options because ${held.trouble}`,
|
|
57
|
-
instead:
|
|
58
|
-
'fix the config, or point the gate at one it can read. Falling back silently means declarations are judged by options nobody chose, and the run says none was found when one was',
|
|
59
|
-
})
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return withFiles(found, pass, files)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function withFiles(found: Finding[], pass: Pass, files: readonly string[]): readonly Finding[] {
|
|
66
|
-
|
|
67
|
-
for (const one of unthreaded(pass, files)) {
|
|
68
|
-
const by = pass.mentioned.get(one) ?? []
|
|
69
|
-
if (by.length === 0) {
|
|
70
|
-
found.push({
|
|
71
|
-
file: one,
|
|
72
|
-
detail: 'nothing declares this — the descent from the root never reaches it, so it is not wired',
|
|
73
|
-
instead:
|
|
74
|
-
'add a row naming this path to the node that owns it, with all four fields, or delete the file. A file that nothing names is either somebody\'s child nobody wrote down, or it is nobody\'s',
|
|
75
|
-
})
|
|
76
|
-
continue
|
|
77
|
-
}
|
|
78
|
-
const cut = by.filter((one) => one !== THREAD_AT && !pass.threaded.has(one))
|
|
79
|
-
if (cut.length > 0) {
|
|
80
|
-
found.push({
|
|
81
|
-
file: one,
|
|
82
|
-
detail: `broken thread — ${cut.join(' and ')} names this, and nothing declares ${cut.length === 1 ? 'it' : 'them'}, so the descent never arrives`,
|
|
83
|
-
instead:
|
|
84
|
-
'thread the parent. The row is written and the file is where it says, but the line back to the root is cut above it, and nothing below the cut is reached',
|
|
85
|
-
})
|
|
86
|
-
continue
|
|
87
|
-
}
|
|
88
|
-
found.push({
|
|
89
|
-
file: one,
|
|
90
|
-
detail: `broken thread — ${by.join(' and ')} names this and is itself declared, and the descent still never arrived, so the row was read and never walked`,
|
|
91
|
-
instead:
|
|
92
|
-
'the cut is inside the parent, not above it. Its node answered its own parent with a shape that row does not state, so the descent refused it and stopped there — fix the shape the parent states, and everything under it joins up',
|
|
93
|
-
})
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
for (const one of twiceParented(pass)) {
|
|
97
|
-
found.push({
|
|
98
|
-
file: one.at,
|
|
99
|
-
detail: `declared by ${one.parents.join(' and ')} — declares is exactly one parent ever, so its owner, its shape and its origin each have two answers and the tree gives none`,
|
|
100
|
-
instead:
|
|
101
|
-
'keep the row that owns it and delete the other. If both are true, one of them is a `derives` or a `seeds`, which may have many parents because neither of them owns the child',
|
|
102
|
-
})
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return found
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (invoked(import.meta.url)) {
|
|
109
|
-
const pass = await descend()
|
|
110
|
-
const tracked = trackedFiles()
|
|
111
|
-
const findings = refusals(pass, tracked)
|
|
112
|
-
|
|
113
|
-
if (argv.includes('--json')) {
|
|
114
|
-
stdout.write(`${JSON.stringify(findings)}\n`)
|
|
115
|
-
exit(0)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const fixed = [...pass.threaded.keys()]
|
|
119
|
-
const reach = new Map<string, number>()
|
|
120
|
-
for (const one of fixed) reach.set(dirname(one), (reach.get(dirname(one)) ?? 0) + 1)
|
|
121
|
-
|
|
122
|
-
const held = configured()
|
|
123
|
-
stdout.write(
|
|
124
|
-
`threads: descended ${String(pass.declarations)} declaration(s) over ${String(pass.rows)} row(s), naming ${String(fixed.length)} file(s) and coining ${String(pass.coined.size)} word(s).\n`,
|
|
125
|
-
)
|
|
126
|
-
stdout.write(
|
|
127
|
-
held.trouble !== undefined
|
|
128
|
-
? `threads: compiler options fallen back to — ${held.trouble}\n`
|
|
129
|
-
: held.from === ''
|
|
130
|
-
? 'threads: compiler options fallen back to — nothing at the root states a tsconfig, so what the build compiles with may differ from what judged this\n'
|
|
131
|
-
: `threads: compiler options taken from ${held.from} at the root — what a package builds with may still differ from this\n`,
|
|
132
|
-
)
|
|
133
|
-
const here = new Set(tracked)
|
|
134
|
-
for (const [dir, held] of [...reach].sort()) {
|
|
135
|
-
const present = tracked.filter((one) => dirname(one) === dir).length
|
|
136
|
-
const mine = [...pass.threaded.keys()].filter((one) => dirname(one) === dir && here.has(one)).length
|
|
137
|
-
stdout.write(
|
|
138
|
-
present === 0
|
|
139
|
-
? `threads: ${dir} — ${String(held)} file(s) named, none of them tracked here\n`
|
|
140
|
-
: `threads: ${dir} — ${String(mine)} of ${String(present)} tracked file(s) named\n`,
|
|
141
|
-
)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const named = [...pass.threaded.keys()].filter((one) => here.has(one)).length
|
|
145
|
-
|
|
146
|
-
runChecker(
|
|
147
|
-
'threads',
|
|
148
|
-
findings,
|
|
149
|
-
`${String(pass.declarations)} declaration(s), ${String(pass.rows)} row(s), ${String(named)} of ${String(tracked.length)} tracked file(s) named`,
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
const warned = findings.filter((one) => one.severity === 'warning')
|
|
153
|
-
const refused = findings.filter((one) => one.severity !== 'warning')
|
|
154
|
-
|
|
155
|
-
if (warned.length > 0) {
|
|
156
|
-
stdout.write(
|
|
157
|
-
`threads: ${String(warned.length)} of these is a derivation nothing has produced yet — the row states what a ` +
|
|
158
|
-
'generator would write and no file is there. A generator that refuses to write an empty file is doing its ' +
|
|
159
|
-
'job, so this is said rather than refused. A derived file that is there and differs from what its generator ' +
|
|
160
|
-
'writes is the other case, and the command its row names in `runs` refuses it at the gate.\n',
|
|
161
|
-
)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (refused.length > 0) exit(1)
|
|
165
|
-
exit(0)
|
|
166
|
-
}
|