@prestack/thread 0.1.21 → 0.1.23
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 +227 -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 +69 -0
- package/dist/derivation/generators/build.d.ts +3 -1
- package/dist/derivation/generators/build.js +104 -22
- package/dist/derivation/generators/release.js +74 -25
- package/dist/derivation/release.js +3 -3
- package/dist/integrations/cli.d.ts +0 -1
- package/dist/integrations/cli.js +69 -11
- package/dist/integrations/declared.d.ts +2 -0
- package/dist/integrations/declared.js +6 -1
- 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 +237 -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,701 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from '../../integrations/disk.ts'
|
|
2
|
-
import { resolve } from 'node:path'
|
|
3
|
-
import { argv, stdout } from 'node:process'
|
|
4
|
-
|
|
5
|
-
import ts from 'typescript'
|
|
6
|
-
|
|
7
|
-
import { ROOT, descend } from '../../core/descend.ts'
|
|
8
|
-
import { runChecker, type Finding } from '../../core/finding.ts'
|
|
9
|
-
import { exit, invoked } from '../../core/invoked.ts'
|
|
10
|
-
import { inCode, valuesIn } from '../../core/rows.ts'
|
|
11
|
-
|
|
12
|
-
import { credentialsOf, holdsOf, reachOf, type Call, type Target } from '../../integrations/target.ts'
|
|
13
|
-
import type { Secret } from '../../integrations/secret.ts'
|
|
14
|
-
|
|
15
|
-
import { DISPATCH_AT, dispatched } from '../dispatch.ts'
|
|
16
|
-
import { beside, blockIn, builtFrom, operations, problem, quoted, runtime, types } from './seed.ts'
|
|
17
|
-
|
|
18
|
-
async function reaching(): Promise<Readonly<Record<string, ReturnType<typeof reached>[string]>>> {
|
|
19
|
-
const held = async <T>(file: string, list: string): Promise<readonly T[]> => {
|
|
20
|
-
const path = resolve(ROOT, `_threads/${file}`)
|
|
21
|
-
if (!existsSync(path)) return []
|
|
22
|
-
try {
|
|
23
|
-
const taken = valuesIn(readFileSync(path, 'utf8')) as Record<string, readonly T[] | undefined>
|
|
24
|
-
return taken[list] ?? []
|
|
25
|
-
} catch {
|
|
26
|
-
return []
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
const targets = await held<Target>('targets.ts', 'TARGETS')
|
|
30
|
-
const calls = await held<Call>('targets.ts', 'CALLS')
|
|
31
|
-
const secrets = await held<Secret>('secrets.ts', 'SECRETS')
|
|
32
|
-
return reached(targets, calls, secrets)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function reached(
|
|
36
|
-
targets: readonly Target[],
|
|
37
|
-
calls: readonly Call[],
|
|
38
|
-
secrets: readonly Secret[],
|
|
39
|
-
): Readonly<
|
|
40
|
-
Record<
|
|
41
|
-
string,
|
|
42
|
-
{
|
|
43
|
-
readonly reach: string
|
|
44
|
-
readonly address?: string
|
|
45
|
-
readonly headers: Readonly<Record<string, string>>
|
|
46
|
-
readonly sends: readonly { readonly secret: string; readonly header: string; readonly prefix: string }[]
|
|
47
|
-
readonly signs: readonly { readonly secret: string; readonly header: string; readonly prefix: string }[]
|
|
48
|
-
readonly calls: Readonly<Record<string, { readonly method: string | null; readonly path: string }>>
|
|
49
|
-
}
|
|
50
|
-
>
|
|
51
|
-
> {
|
|
52
|
-
const out: Record<string, ReturnType<typeof reached>[string]> = {}
|
|
53
|
-
for (const one of targets) {
|
|
54
|
-
const mine = calls.filter((each) => each.of === one.id && one.takes.includes(each.id))
|
|
55
|
-
out[one.id] = {
|
|
56
|
-
reach: reachOf(one.id) ?? 'nothing known',
|
|
57
|
-
address: one.address,
|
|
58
|
-
headers: one.headers ?? {},
|
|
59
|
-
signs: credentialsOf(one, secrets)
|
|
60
|
-
.filter((each) => each.signs !== undefined)
|
|
61
|
-
.map((each) => ({
|
|
62
|
-
secret: each.id,
|
|
63
|
-
header: each.signs?.header ?? '',
|
|
64
|
-
prefix: each.signs?.prefix ?? '',
|
|
65
|
-
})),
|
|
66
|
-
sends: credentialsOf(one, secrets).filter((each) => each.sends !== undefined).map((each) => ({
|
|
67
|
-
secret: each.id,
|
|
68
|
-
header: each.sends?.header ?? '',
|
|
69
|
-
prefix: each.sends?.prefix ?? '',
|
|
70
|
-
})),
|
|
71
|
-
|
|
72
|
-
calls: Object.fromEntries(
|
|
73
|
-
mine.map((each) => [each.id.replace(/^cl_/, ''), { method: each.method ?? null, path: each.path }]),
|
|
74
|
-
),
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return out
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const OWN = '$'
|
|
81
|
-
|
|
82
|
-
const OPENED = 'opened'
|
|
83
|
-
|
|
84
|
-
const ENTERED = `${OWN}at`
|
|
85
|
-
|
|
86
|
-
const PLAIN = /^(?:[A-Za-z_][A-Za-z0-9_]*|'(?:[^'\\]|\\.)*')$/
|
|
87
|
-
|
|
88
|
-
export function parts(text: string): readonly string[] {
|
|
89
|
-
const code = inCode(text)
|
|
90
|
-
const found: string[] = []
|
|
91
|
-
let depth = 0
|
|
92
|
-
let from = 0
|
|
93
|
-
for (let at = 0; at < text.length; at += 1) {
|
|
94
|
-
if (code[at] !== true) continue
|
|
95
|
-
const one = text[at]
|
|
96
|
-
if (one === '(' || one === '[' || one === '{') depth += 1
|
|
97
|
-
else if (one === ')' || one === ']' || one === '}') depth -= 1
|
|
98
|
-
else if (one === ',' && depth === 0) {
|
|
99
|
-
found.push(text.slice(from, at))
|
|
100
|
-
from = at + 1
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
found.push(text.slice(from))
|
|
104
|
-
return found.map((one) => one.trim()).filter((one) => one !== '')
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const inside = (text: string, open: string, shut: string): string | undefined => {
|
|
108
|
-
const said = text.trim()
|
|
109
|
-
return said.startsWith(open) && said.endsWith(shut) ? said.slice(1, -1) : undefined
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function keyed(text: string): { readonly key: string; readonly held: string } | undefined {
|
|
113
|
-
const code = inCode(text)
|
|
114
|
-
let depth = 0
|
|
115
|
-
for (let at = 0; at < text.length; at += 1) {
|
|
116
|
-
if (code[at] !== true) continue
|
|
117
|
-
const one = text[at]
|
|
118
|
-
if (one === '(' || one === '[' || one === '{') depth += 1
|
|
119
|
-
else if (one === ')' || one === ']' || one === '}') depth -= 1
|
|
120
|
-
else if (one === ':' && depth === 0) return { key: text.slice(0, at).trim(), held: text.slice(at + 1).trim() }
|
|
121
|
-
}
|
|
122
|
-
return undefined
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const OPENS = 'const $ = '
|
|
126
|
-
|
|
127
|
-
const ROLES = {
|
|
128
|
-
block: ['opened', 'held'],
|
|
129
|
-
over: ['held'],
|
|
130
|
-
item: ['item', 'opened', 'held'],
|
|
131
|
-
again: ['pass', 'carried', 'opened', 'held'],
|
|
132
|
-
} as const satisfies Readonly<Record<string, readonly string[]>>
|
|
133
|
-
|
|
134
|
-
interface Read {
|
|
135
|
-
readonly arrow: ts.ArrowFunction
|
|
136
|
-
readonly held: string | undefined
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function arrowIn(text: string, roles: readonly string[]): Read | string {
|
|
140
|
-
const file = ts.createSourceFile('block.ts', `${OPENS}${text}`, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
|
|
141
|
-
const first = file.statements[0]
|
|
142
|
-
if (first === undefined || !ts.isVariableStatement(first)) return 'is not written as a function where it is used'
|
|
143
|
-
const held = first.declarationList.declarations[0]?.initializer
|
|
144
|
-
if (held === undefined || !ts.isArrowFunction(held)) return 'is not an arrow function written where it is used'
|
|
145
|
-
if (held.parameters.length > roles.length) {
|
|
146
|
-
return `takes ${String(held.parameters.length)} things, and this kind of block is handed ${roles.join(', ')}`
|
|
147
|
-
}
|
|
148
|
-
for (const one of held.parameters) {
|
|
149
|
-
if (!ts.isIdentifier(one.name)) return 'names a parameter by taking it apart, and this needs the name itself'
|
|
150
|
-
}
|
|
151
|
-
const last = held.parameters.length === roles.length ? held.parameters[roles.length - 1] : undefined
|
|
152
|
-
const named = last !== undefined && ts.isIdentifier(last.name) ? last.name.text : undefined
|
|
153
|
-
return { arrow: held, held: named }
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const BINDS = new Set<ts.SyntaxKind>([
|
|
157
|
-
ts.SyntaxKind.Parameter,
|
|
158
|
-
ts.SyntaxKind.VariableDeclaration,
|
|
159
|
-
ts.SyntaxKind.BindingElement,
|
|
160
|
-
ts.SyntaxKind.FunctionDeclaration,
|
|
161
|
-
ts.SyntaxKind.ClassDeclaration,
|
|
162
|
-
])
|
|
163
|
-
|
|
164
|
-
function bound(node: ts.Node, found: Set<string>): void {
|
|
165
|
-
if (BINDS.has(node.kind)) {
|
|
166
|
-
const said = (node as { readonly name?: ts.Node }).name
|
|
167
|
-
if (said !== undefined && ts.isIdentifier(said)) found.add(said.text)
|
|
168
|
-
}
|
|
169
|
-
if (ts.isCatchClause(node) && node.variableDeclaration !== undefined && ts.isIdentifier(node.variableDeclaration.name)) {
|
|
170
|
-
found.add(node.variableDeclaration.name.text)
|
|
171
|
-
}
|
|
172
|
-
node.forEachChild((one) => {
|
|
173
|
-
bound(one, found)
|
|
174
|
-
})
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function answers(node: ts.Node, found: ts.ReturnStatement[]): void {
|
|
178
|
-
if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node)) return
|
|
179
|
-
if (ts.isReturnStatement(node)) found.push(node)
|
|
180
|
-
node.forEachChild((one) => {
|
|
181
|
-
answers(one, found)
|
|
182
|
-
})
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
interface Spliced {
|
|
186
|
-
readonly form: 'expression' | 'statements' | 'call'
|
|
187
|
-
|
|
188
|
-
readonly text: string
|
|
189
|
-
|
|
190
|
-
readonly whole: string
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function spliced(text: string, shape: Shape, roles: readonly string[]): Spliced | string {
|
|
194
|
-
const { names, mine } = shape
|
|
195
|
-
const read = arrowIn(text, roles)
|
|
196
|
-
if (typeof read === 'string') return read
|
|
197
|
-
|
|
198
|
-
const inner = new Set<string>()
|
|
199
|
-
bound(read.arrow.body, inner)
|
|
200
|
-
|
|
201
|
-
if (read.held !== undefined && inner.has(read.held)) {
|
|
202
|
-
return `declares its own \`${read.held}\` inside itself as well as taking one, and which of the two a \`${read.held}.something\` means cannot be told apart from here`
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const shadows = new Set<string>(inner)
|
|
206
|
-
for (const one of read.arrow.parameters) {
|
|
207
|
-
if (ts.isIdentifier(one.name) && one.name.text !== read.held) shadows.add(one.name.text)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
for (const one of shadows) {
|
|
211
|
-
if (mine.has(one)) {
|
|
212
|
-
return `uses the name \`${one}\` for something of its own, and that is also what a node of this operation is called — once both are locals one would shadow the other`
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
interface Edit {
|
|
217
|
-
readonly from: number
|
|
218
|
-
readonly to: number
|
|
219
|
-
readonly text: string
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const edits: Edit[] = []
|
|
223
|
-
let because: string | undefined
|
|
224
|
-
|
|
225
|
-
const walk = (node: ts.Node): void => {
|
|
226
|
-
if (because !== undefined) return
|
|
227
|
-
if (read.held !== undefined && ts.isIdentifier(node) && node.text === read.held) {
|
|
228
|
-
const up: ts.Node | undefined = node.parent
|
|
229
|
-
if (up !== undefined && ts.isPropertyAccessExpression(up) && up.expression === node) {
|
|
230
|
-
const said = up.name.text
|
|
231
|
-
if (said !== 'input' && !names.has(said)) {
|
|
232
|
-
|
|
233
|
-
because =
|
|
234
|
-
`reads \`${read.held}.${said}\`, and no node before it answers to that name. ` +
|
|
235
|
-
`\`${read.held}\` holds \`input\`, which is what this operation was called with, ` +
|
|
236
|
-
(names.size === 0
|
|
237
|
-
? 'and nothing else — this is the first node, so `input` is all there is'
|
|
238
|
-
: `and one name per node that has already answered: ${[...names].map((one) => `\`${one}\``).join(', ')}`)
|
|
239
|
-
return
|
|
240
|
-
}
|
|
241
|
-
edits.push({ from: up.getStart(), to: up.getEnd(), text: said })
|
|
242
|
-
shape.heard.add(said)
|
|
243
|
-
return
|
|
244
|
-
}
|
|
245
|
-
because = `reaches for \`${read.held}\` itself rather than one thing on it, and once every node is a local there is no such thing to hand it`
|
|
246
|
-
return
|
|
247
|
-
}
|
|
248
|
-
node.forEachChild(walk)
|
|
249
|
-
}
|
|
250
|
-
walk(read.arrow.body)
|
|
251
|
-
if (because !== undefined) return because
|
|
252
|
-
|
|
253
|
-
const held = read.arrow.parameters[roles.length - 1]
|
|
254
|
-
const before = read.arrow.parameters[roles.length - 2]
|
|
255
|
-
if (held !== undefined) {
|
|
256
|
-
edits.push({ from: before?.getEnd() ?? held.getStart(), to: held.getEnd(), text: '' })
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
let said = `${OPENS}${text}`
|
|
260
|
-
for (const one of [...edits].sort((a, b) => b.from - a.from)) {
|
|
261
|
-
said = `${said.slice(0, one.from)}${one.text}${said.slice(one.to)}`
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const whole = said.slice(OPENS.length)
|
|
265
|
-
const again = arrowIn(whole, roles.slice(0, -1))
|
|
266
|
-
if (typeof again === 'string') return again
|
|
267
|
-
const body = again.arrow.body
|
|
268
|
-
|
|
269
|
-
if (again.arrow.parameters.length > 1) return { form: 'call', text: whole, whole }
|
|
270
|
-
|
|
271
|
-
const opens = again.arrow.parameters[0]
|
|
272
|
-
if (opens !== undefined && ts.isIdentifier(opens.name) && opens.name.text !== OPENED) {
|
|
273
|
-
return { form: 'call', text: whole, whole }
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (!ts.isBlock(body)) return { form: 'expression', text: body.getText(), whole }
|
|
277
|
-
|
|
278
|
-
const found: ts.ReturnStatement[] = []
|
|
279
|
-
answers(body, found)
|
|
280
|
-
const last = body.statements[body.statements.length - 1]
|
|
281
|
-
if (found.length === 1 && last !== undefined && found[0] === last && ts.isReturnStatement(last)) {
|
|
282
|
-
const kept = body.statements.slice(0, -1).map((one) => one.getText())
|
|
283
|
-
return { form: 'statements', text: [...kept, `${ANSWERS} = ${last.expression?.getText() ?? 'undefined'}`].join('\n'), whole }
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
return { form: 'call', text: whole, whole }
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const ANSWERS = `${OWN}ANSWER`
|
|
290
|
-
|
|
291
|
-
interface Made {
|
|
292
|
-
readonly runs: readonly string[]
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
interface Shape {
|
|
296
|
-
readonly all: string
|
|
297
|
-
readonly reads: string
|
|
298
|
-
readonly names: ReadonlySet<string>
|
|
299
|
-
readonly mine: ReadonlySet<string>
|
|
300
|
-
readonly heard: Set<string>
|
|
301
|
-
readonly unread: ReadonlySet<string>
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const linesOf = (one: Spliced, name: string, unread: boolean): readonly string[] =>
|
|
305
|
-
(unread ? `${one.text.replace(`${ANSWERS} = `, 'void (')})` : one.text.replace(ANSWERS, name)).split('\n')
|
|
306
|
-
|
|
307
|
-
function answered(name: string, one: Spliced, opened: string, kind: 'const' | 'let', unread: boolean): readonly string[] {
|
|
308
|
-
if (one.form === 'expression') return [unread ? `void (${one.text})` : `${kind} ${name}: unknown = ${one.text}`]
|
|
309
|
-
if (one.form === 'statements') {
|
|
310
|
-
return [...(unread ? [] : [`let ${name}: unknown`]), '{', ...linesOf(one, name, unread).map((line) => ` ${line}`), '}']
|
|
311
|
-
}
|
|
312
|
-
return [`${unread ? '' : `${kind} ${name}: unknown = `}await ${asFn(one.whole, opened)}(opened)`]
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
const asFn = (text: string, opened: string, more: string = ''): string =>
|
|
316
|
-
`((${text}) as (${more}opened: ${opened}) => Promise<unknown>)`
|
|
317
|
-
|
|
318
|
-
function forked(name: string, said: readonly string[], shape: Shape): Made | string {
|
|
319
|
-
if (said.length !== 2) return `takes a chooser and the ways it chooses between, and was handed ${String(said.length)} thing(s)`
|
|
320
|
-
|
|
321
|
-
const chose = spliced(said[0] ?? '', shape, ROLES.block)
|
|
322
|
-
if (typeof chose === 'string') return `chooses with a block that ${chose}`
|
|
323
|
-
|
|
324
|
-
const held = inside(said[1] ?? '', '{', '}')
|
|
325
|
-
const ways = held === undefined ? undefined : parts(held).map((one) => keyed(one))
|
|
326
|
-
const named = ways?.every((one) => one !== undefined && PLAIN.test(one.key)) === true ? ways : undefined
|
|
327
|
-
if (named === undefined || named.length === 0) return 'names its ways in a way this cannot read — write each one as `name: block`'
|
|
328
|
-
|
|
329
|
-
const unread = shape.unread.has(name)
|
|
330
|
-
const picked = `${OWN}${name}_said`
|
|
331
|
-
const runs = [
|
|
332
|
-
...(unread ? [] : [`let ${name}: unknown`]),
|
|
333
|
-
chose.form === 'expression'
|
|
334
|
-
? `const ${picked}: string = await (${chose.text})`
|
|
335
|
-
: `const ${picked}: string = await ((${said[0] ?? ''}) as (opened: ${shape.reads}) => Promise<string>)(opened)`,
|
|
336
|
-
`switch (${picked}) {`,
|
|
337
|
-
]
|
|
338
|
-
|
|
339
|
-
for (const way of named) {
|
|
340
|
-
const each = spliced(way?.held ?? '', shape, ROLES.block)
|
|
341
|
-
if (typeof each === 'string') return `has a way \`${way?.key ?? ''}\` that ${each}`
|
|
342
|
-
runs.push(` case ${(way?.key ?? '').startsWith("'") ? (way?.key ?? '') : quoted(way?.key ?? '')}: {`)
|
|
343
|
-
if (each.form === 'expression') runs.push(unread ? ` void (${each.text})` : ` ${name} = ${each.text}`)
|
|
344
|
-
else if (each.form === 'statements') runs.push(...linesOf(each, name, unread).map((line) => ` ${line}`))
|
|
345
|
-
else runs.push(` ${unread ? '' : `${name} = `}await ${asFn(each.whole, shape.all)}(opened)`)
|
|
346
|
-
runs.push(' break')
|
|
347
|
-
runs.push(' }')
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
runs.push(` default: throw new Error(\`${name} chose \${${picked}}, which names no way forward\`)`)
|
|
351
|
-
runs.push('}')
|
|
352
|
-
return { runs }
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const SHAPES: Readonly<Record<string, (name: string, said: readonly string[], shape: Shape) => Made | string>> = {
|
|
356
|
-
nd_read: (name, said, shape) => plainly(name, said, shape, shape.reads, false),
|
|
357
|
-
nd_action: (name, said, shape) => plainly(name, said, shape, shape.all, false),
|
|
358
|
-
nd_stop: (name, said, shape) => plainly(name, said, shape, shape.reads, true),
|
|
359
|
-
|
|
360
|
-
nd_each: (name, said, shape) => {
|
|
361
|
-
if (said.length !== 2) return `takes what to run over and the block to run, and was handed ${String(said.length)} thing(s)`
|
|
362
|
-
const over = spliced(said[0] ?? '', shape, ROLES.over)
|
|
363
|
-
const block = spliced(said[1] ?? '', shape, ROLES.item)
|
|
364
|
-
if (typeof block === 'string') return `runs a block that ${block}`
|
|
365
|
-
if (typeof over === 'string') return `runs over a block that ${over}`
|
|
366
|
-
const unread = shape.unread.has(name)
|
|
367
|
-
const item = `${OWN}${name}_item`
|
|
368
|
-
const all = `${OWN}${name}_all`
|
|
369
|
-
const each = `${OWN}${name}_each`
|
|
370
|
-
return {
|
|
371
|
-
runs: [
|
|
372
|
-
...(unread ? [] : [`const ${all}: unknown[] = []`]),
|
|
373
|
-
`const ${each} = (${block.whole}) as (item: unknown, opened: ${shape.all}) => Promise<unknown>`,
|
|
374
|
-
`for (const ${item} of ${over.form === 'expression' ? `(${over.text})` : `((${over.whole}) as () => readonly unknown[])()`} as readonly unknown[]) {`,
|
|
375
|
-
unread ? ` await ${each}(${item}, opened)` : ` ${all}.push(await ${each}(${item}, opened))`,
|
|
376
|
-
'}',
|
|
377
|
-
...(unread ? [] : [`const ${name}: unknown = ${all}`]),
|
|
378
|
-
],
|
|
379
|
-
}
|
|
380
|
-
},
|
|
381
|
-
|
|
382
|
-
nd_parallel: (name, said, shape) => {
|
|
383
|
-
if (said.length !== 1) return `takes one list of blocks, and was handed ${String(said.length)} thing(s)`
|
|
384
|
-
const held = inside(said[0] ?? '', '[', ']')
|
|
385
|
-
if (held === undefined) return 'takes a list of blocks written out where it is called, and this is not one'
|
|
386
|
-
const every = parts(held)
|
|
387
|
-
if (every.length === 0) return 'was handed no blocks to run at once'
|
|
388
|
-
|
|
389
|
-
const calls: string[] = []
|
|
390
|
-
for (const [order, one] of every.entries()) {
|
|
391
|
-
const each = spliced(one, shape, ROLES.block)
|
|
392
|
-
if (typeof each === 'string') return `runs a block at position ${String(order + 1)} that ${each}`
|
|
393
|
-
calls.push(each.form === 'expression' ? `(async () => ${each.text})()` : `${asFn(each.whole, shape.all)}(opened)`)
|
|
394
|
-
}
|
|
395
|
-
return { runs: [`${shape.unread.has(name) ? '' : `const ${name}: unknown = `}await Promise.all([${calls.join(', ')}])`] }
|
|
396
|
-
},
|
|
397
|
-
|
|
398
|
-
nd_repeat: (name, said, shape) => {
|
|
399
|
-
if (said.length !== 2) return `takes a ceiling and the block to repeat, and was handed ${String(said.length)} thing(s)`
|
|
400
|
-
const block = spliced(said[1] ?? '', shape, ROLES.again)
|
|
401
|
-
if (typeof block === 'string') return `repeats a block that ${block}`
|
|
402
|
-
const ceiling = `${OWN}${name}_ceiling`
|
|
403
|
-
const again = `${OWN}${name}_again`
|
|
404
|
-
const carried = `${OWN}${name}_carried`
|
|
405
|
-
const done = `${OWN}${name}_done`
|
|
406
|
-
const pass = `${OWN}${name}_pass`
|
|
407
|
-
const answer = `${OWN}${name}_said`
|
|
408
|
-
const unread = shape.unread.has(name)
|
|
409
|
-
return {
|
|
410
|
-
runs: [
|
|
411
|
-
...(unread ? [] : [`let ${name}: unknown`]),
|
|
412
|
-
`const ${ceiling}: number = ${said[0] ?? ''}`,
|
|
413
|
-
`const ${again} = (${block.whole}) as (pass: number, carried: unknown, opened: ${shape.all}) => Promise<{ readonly done: boolean; readonly value: unknown }>`,
|
|
414
|
-
`let ${carried}: unknown`,
|
|
415
|
-
`let ${done} = false`,
|
|
416
|
-
`for (let ${pass} = 0; ${pass} < ${ceiling}; ${pass} += 1) {`,
|
|
417
|
-
` const ${answer} = await ${again}(${pass}, ${carried}, opened)`,
|
|
418
|
-
` if (${answer}.done) { ${unread ? '' : `${name} = ${answer}.value; `}${done} = true; break }`,
|
|
419
|
-
` ${carried} = ${answer}.value`,
|
|
420
|
-
'}',
|
|
421
|
-
`if (!${done}) throw new Error(\`${name} reached its ceiling of \${String(${ceiling})} passes without answering done\`)`,
|
|
422
|
-
],
|
|
423
|
-
}
|
|
424
|
-
},
|
|
425
|
-
|
|
426
|
-
nd_branch: forked,
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
const STOPS = "Symbol.for('thread.operation.stopped')"
|
|
430
|
-
|
|
431
|
-
function plainly(name: string, said: readonly string[], shape: Shape, opened: string, ends: boolean): Made | string {
|
|
432
|
-
if (said.length !== 1) return `takes one block, and was handed ${String(said.length)} thing(s)`
|
|
433
|
-
const one = spliced(said[0] ?? '', shape, ROLES.block)
|
|
434
|
-
if (typeof one === 'string') return one
|
|
435
|
-
|
|
436
|
-
const unread = shape.unread.has(name)
|
|
437
|
-
if (!ends) return { runs: answered(name, one, opened, 'const', unread) }
|
|
438
|
-
|
|
439
|
-
return {
|
|
440
|
-
runs: [
|
|
441
|
-
...answered(name, one, opened, 'const', unread),
|
|
442
|
-
`throw Object.assign(new Error(${quoted(`${name} ended the operation`)}), { [${STOPS}]: true, value: ${name} })`,
|
|
443
|
-
],
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
const saidIn = (held: ts.Expression | undefined): string | undefined =>
|
|
447
|
-
held !== undefined && ts.isStringLiteral(held) ? held.text : undefined
|
|
448
|
-
|
|
449
|
-
const sourceIn = (held: ts.Expression | undefined): string | undefined => held?.getText()
|
|
450
|
-
|
|
451
|
-
function compiled(
|
|
452
|
-
at: string,
|
|
453
|
-
id: string,
|
|
454
|
-
one: ReturnType<typeof operations> extends ReadonlyMap<string, infer W> ? W : never,
|
|
455
|
-
known: ReadonlySet<string>,
|
|
456
|
-
unread?: ReadonlySet<string>,
|
|
457
|
-
): { readonly text?: string; readonly because?: string } {
|
|
458
|
-
const because = problem(id, one)
|
|
459
|
-
if (because !== undefined) return { because }
|
|
460
|
-
|
|
461
|
-
const takes = one.takes[0]?.at
|
|
462
|
-
if (takes === undefined) return { because: `\`${id}\` seeds what it takes and states no file for it` }
|
|
463
|
-
|
|
464
|
-
const { timeout } = one
|
|
465
|
-
|
|
466
|
-
const fields = descend().stated.get(id)?.fields
|
|
467
|
-
const entitled = saidIn(fields?.get('entitled'))
|
|
468
|
-
const rate = sourceIn(fields?.get('rate'))
|
|
469
|
-
const quota = sourceIn(fields?.get('quota'))
|
|
470
|
-
if (timeout === undefined) {
|
|
471
|
-
return {
|
|
472
|
-
because:
|
|
473
|
-
`\`${id}\` states no \`timeout\`, and an operation says how long it waits. Add \`timeout: 10_000\` to its row in ` +
|
|
474
|
-
'_threads/operations.ts, or change it with `thread edit ' + id + ' --timeout <milliseconds>`. How long a call may ' +
|
|
475
|
-
'run is part of the operation, not something the place it reaches decides',
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
const only = one.requires.length === 0 ? 'never' : one.requires.map((each) => quoted(each)).join(' | ')
|
|
480
|
-
const mine = new Set<string>(['input', ...one.nodes.map((row) => String(row.id))])
|
|
481
|
-
|
|
482
|
-
const runs: string[] = []
|
|
483
|
-
const names = new Set<string>()
|
|
484
|
-
const heard = new Set<string>()
|
|
485
|
-
let last: string | undefined
|
|
486
|
-
|
|
487
|
-
for (const [order, row] of one.nodes.entries()) {
|
|
488
|
-
const name = row.id
|
|
489
|
-
const from = builtFrom(row, known)
|
|
490
|
-
|
|
491
|
-
if (from === 'nd_stop' && order !== one.nodes.length - 1) {
|
|
492
|
-
const after = one.nodes.slice(order + 1).map((each) => `\`${String(each.id)}\``)
|
|
493
|
-
return {
|
|
494
|
-
because:
|
|
495
|
-
`the node \`${String(name)}\` ends \`${id}\`, and ${after.join(', ')} ${after.length === 1 ? 'is' : 'are'} declared after it. ` +
|
|
496
|
-
'Nothing runs after the node that ends an operation — move it last, or take off what follows it',
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
if (name === undefined) return { because: `\`${id}\` declares a node with no id of its own` }
|
|
501
|
-
if (row.at === undefined) return { because: `the node \`${name}\` names no file of its own` }
|
|
502
|
-
if (from === undefined) return { because: `the node \`${name}\` names no type this app declares` }
|
|
503
|
-
|
|
504
|
-
const path = resolve(ROOT, row.at)
|
|
505
|
-
if (!existsSync(path)) return { because: `the node \`${name}\` seeds ${row.at} and nothing is there — run \`thread seed\`` }
|
|
506
|
-
const held = blockIn(readFileSync(path, 'utf8'))
|
|
507
|
-
if (held === undefined) {
|
|
508
|
-
return { because: `the node \`${name}\` states no block this can read in ${row.at} — run \`thread seed\` and write it again` }
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
const shape: Shape = { all: `Opened<${only}>`, reads: `Reads<Opened<${only}>>`, names, mine, heard, unread: unread ?? new Set<string>() }
|
|
512
|
-
const made = (SHAPES[from] ?? ownType)(name, parts(held), shape)
|
|
513
|
-
if (typeof made === 'string') return { because: `the node \`${name}\` ${made}` }
|
|
514
|
-
|
|
515
|
-
runs.push(` ${ENTERED} = ${quoted(name)}`)
|
|
516
|
-
for (const line of made.runs) runs.push(` ${line}`)
|
|
517
|
-
names.add(name)
|
|
518
|
-
last = from === 'nd_stop' ? undefined : name
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
if (unread === undefined) {
|
|
522
|
-
const final = one.nodes.at(-1)?.id
|
|
523
|
-
return compiled(at, id, one, known, new Set([...mine].filter((name) => name !== final && !heard.has(name))))
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
const typed = ['Opened', 'Reads'].filter((word) => runs.some((line) => line.includes(`${word}<`)))
|
|
527
|
-
|
|
528
|
-
return {
|
|
529
|
-
text: [
|
|
530
|
-
"import type { z } from 'zod'",
|
|
531
|
-
'',
|
|
532
|
-
`import type { Operation } from ${quoted(runtime(at, '../operations.ts'))}`,
|
|
533
|
-
...(typed.length === 0 ? [] : [`import type { ${typed.join(', ')} } from ${quoted(runtime(at, '../operations.ts'))}`]),
|
|
534
|
-
'',
|
|
535
|
-
`import { input, resource } from ${quoted(beside(at, takes))}`,
|
|
536
|
-
'',
|
|
537
|
-
`export const operation: Operation<z.infer<typeof input>, ${only}> = {`,
|
|
538
|
-
` id: ${quoted(id)},`,
|
|
539
|
-
' input,',
|
|
540
|
-
` timeout: ${String(timeout)},`,
|
|
541
|
-
' resource,',
|
|
542
|
-
` requires: [${one.requires.map((each) => quoted(each)).join(', ')}],`,
|
|
543
|
-
...(entitled === undefined ? [] : [` entitled: ${quoted(entitled)},`]),
|
|
544
|
-
...(rate === undefined ? [] : [` rate: ${rate},`]),
|
|
545
|
-
...(quota === undefined ? [] : [` quota: ${quota},`]),
|
|
546
|
-
'',
|
|
547
|
-
' run: async (opened, held) => {',
|
|
548
|
-
...(unread.has('input') ? [] : [' const input = held.input']),
|
|
549
|
-
` let ${ENTERED} = ''`,
|
|
550
|
-
' try {',
|
|
551
|
-
...runs,
|
|
552
|
-
...(last === undefined ? [] : [` return ${last}`]),
|
|
553
|
-
' } catch (thrown) {',
|
|
554
|
-
` if (typeof thrown === 'object' && thrown !== null) Object.assign(thrown, { node: ${ENTERED} })`,
|
|
555
|
-
' throw thrown',
|
|
556
|
-
' }',
|
|
557
|
-
' },',
|
|
558
|
-
'}',
|
|
559
|
-
'',
|
|
560
|
-
].join('\n'),
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
const ownType = (name: string, said: readonly string[], shape: Shape): Made | string =>
|
|
565
|
-
plainly(name, said, shape, shape.all, false)
|
|
566
|
-
|
|
567
|
-
interface Wanted {
|
|
568
|
-
readonly at: string
|
|
569
|
-
readonly text?: string
|
|
570
|
-
readonly because?: string
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
async function planned(): Promise<readonly Wanted[]> {
|
|
574
|
-
const pass = descend()
|
|
575
|
-
const known = types(pass)
|
|
576
|
-
const reach = await reaching()
|
|
577
|
-
const found: Wanted[] = []
|
|
578
|
-
|
|
579
|
-
const answers = Object.keys(reach).filter(
|
|
580
|
-
(one) => Object.keys(reach[one]?.calls ?? {}).length > 0 || holdsOf(one)?.takes !== 'calls',
|
|
581
|
-
)
|
|
582
|
-
const may = [...answers]
|
|
583
|
-
for (const [id, one] of operations(pass, known)) {
|
|
584
|
-
if (one.body === undefined) continue
|
|
585
|
-
const silent = one.requires.filter((each) => reach[each] !== undefined && !answers.includes(each))
|
|
586
|
-
if (silent.length > 0) {
|
|
587
|
-
found.push({
|
|
588
|
-
at: one.body,
|
|
589
|
-
because: silent
|
|
590
|
-
.map((each) => {
|
|
591
|
-
const how = reach[each]?.reach ?? 'nothing known'
|
|
592
|
-
const instead = 'declare what it answers with `thread create target call ' + each + ' <name>`'
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
return (
|
|
597
|
-
'`' + id + '` requires `' + each + '`, which is reached over ' + how + ' and answers no call, so there ' +
|
|
598
|
-
'is nothing for a node to be handed. ' + instead + ', ' +
|
|
599
|
-
'or take the word off that operation\'s row'
|
|
600
|
-
)
|
|
601
|
-
})
|
|
602
|
-
.join('; '),
|
|
603
|
-
})
|
|
604
|
-
continue
|
|
605
|
-
}
|
|
606
|
-
const stray = one.requires.filter((each) => !may.includes(each))
|
|
607
|
-
if (stray.length > 0) {
|
|
608
|
-
found.push({
|
|
609
|
-
at: one.body,
|
|
610
|
-
because:
|
|
611
|
-
`\`${id}\` requires ${stray.map((each) => `\`${each}\``).join(', ')}, and this app has no such capability and ` +
|
|
612
|
-
`declares no such target. What may be required is ${may.join(', ')} — declare the target with ` +
|
|
613
|
-
'`thread create target`, or take the word off that operation\'s row',
|
|
614
|
-
})
|
|
615
|
-
continue
|
|
616
|
-
}
|
|
617
|
-
found.push({ at: one.body, ...compiled(one.body, id, one, known) })
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
if (found.length > 0) found.push({ at: DISPATCH_AT, text: dispatched(reach) })
|
|
621
|
-
return found
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
function checked(plan: readonly Wanted[]): void {
|
|
625
|
-
if (plan.length === 0) {
|
|
626
|
-
stdout.write('compile: no operation here states a body to compile, so there is nothing to check.\n')
|
|
627
|
-
exit(0)
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
const findings: Finding[] = plan.flatMap((one): Finding[] => {
|
|
631
|
-
const path = resolve(ROOT, one.at)
|
|
632
|
-
|
|
633
|
-
if (one.because !== undefined) {
|
|
634
|
-
return [
|
|
635
|
-
{
|
|
636
|
-
file: one.at,
|
|
637
|
-
detail: one.because,
|
|
638
|
-
instead: 'declare it properly — this file is compiled from those rows and cannot be written without them',
|
|
639
|
-
},
|
|
640
|
-
]
|
|
641
|
-
}
|
|
642
|
-
if (!existsSync(path)) {
|
|
643
|
-
return [{ file: one.at, detail: 'a row declares this file and nothing is there', instead: 'run `thread compile`' }]
|
|
644
|
-
}
|
|
645
|
-
if (readFileSync(path, 'utf8') === one.text) return []
|
|
646
|
-
|
|
647
|
-
if (one.at === DISPATCH_AT) {
|
|
648
|
-
return [
|
|
649
|
-
{
|
|
650
|
-
file: one.at,
|
|
651
|
-
detail: 'this is the one way every operation of this app runs, and it is no longer the bytes that write it',
|
|
652
|
-
instead:
|
|
653
|
-
'run `thread compile`. Dispatch has no author: it is the same for every app, so a change made here is a change nothing else in this framework knows about and the next compile takes it back',
|
|
654
|
-
},
|
|
655
|
-
]
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
return [
|
|
659
|
-
{
|
|
660
|
-
file: one.at,
|
|
661
|
-
detail: 'this operation is compiled from its nodes, and this is no longer the bytes that compile it',
|
|
662
|
-
instead:
|
|
663
|
-
'run `thread compile`. A compiled operation has no author: to change what it does, change the rows that declare its nodes, and to change what a node does, write it in the file that node seeds',
|
|
664
|
-
},
|
|
665
|
-
]
|
|
666
|
-
})
|
|
667
|
-
|
|
668
|
-
runChecker('compile', findings, `${String(plan.length)} file(s) compiled`)
|
|
669
|
-
exit(findings.length === 0 ? 0 : 1)
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
export async function compile(say: boolean = true): Promise<number> {
|
|
673
|
-
const plan = await planned()
|
|
674
|
-
let written = 0
|
|
675
|
-
let refused = 0
|
|
676
|
-
for (const one of plan) {
|
|
677
|
-
if (one.text === undefined) {
|
|
678
|
-
refused += 1
|
|
679
|
-
if (say) stdout.write(`compile: ${one.at} was not written — ${one.because ?? 'the rows behind it are incomplete'}\n`)
|
|
680
|
-
continue
|
|
681
|
-
}
|
|
682
|
-
const path = resolve(ROOT, one.at)
|
|
683
|
-
if (existsSync(path) && readFileSync(path, 'utf8') === one.text) continue
|
|
684
|
-
writeFileSync(path, one.text, 'utf8')
|
|
685
|
-
written += 1
|
|
686
|
-
if (say) stdout.write(`compile: wrote ${one.at}\n`)
|
|
687
|
-
}
|
|
688
|
-
if (say) {
|
|
689
|
-
stdout.write(
|
|
690
|
-
`compile: ${String(written)} of ${String(plan.length)} file(s) written, ${String(plan.length - written - refused)} already as compiled` +
|
|
691
|
-
`${refused === 0 ? '' : `, ${String(refused)} refused`}.\n`,
|
|
692
|
-
)
|
|
693
|
-
}
|
|
694
|
-
if (refused > 0) exit(1)
|
|
695
|
-
return written
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
if (invoked(import.meta.url)) {
|
|
699
|
-
if (argv.includes('--check')) checked(await planned())
|
|
700
|
-
else await compile()
|
|
701
|
-
}
|