@prestack/thread 0.0.2 → 0.1.0
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 +98 -0
- package/dist/core/{asking.d.ts → answers.d.ts} +6 -14
- package/dist/core/{asking.js → answers.js} +13 -332
- package/dist/core/{asking.ts → answers.ts} +14 -357
- package/dist/core/attach.js +2 -2
- package/dist/core/attach.ts +2 -3
- package/dist/core/chain.js +4 -11
- package/dist/core/chain.ts +4 -3
- package/dist/core/client.d.ts +57 -0
- package/dist/core/client.js +186 -0
- package/dist/core/client.ts +227 -0
- package/dist/core/descend.d.ts +2 -0
- package/dist/core/descend.js +109 -16
- package/dist/core/descend.ts +120 -16
- package/dist/core/files.d.ts +45 -0
- package/dist/core/files.js +95 -0
- package/dist/core/files.ts +137 -0
- package/dist/core/init.d.ts +1 -0
- package/dist/core/init.js +10 -2
- package/dist/core/init.ts +8 -2
- package/dist/core/invoked.js +1 -1
- package/dist/core/invoked.ts +1 -1
- package/dist/core/rows.d.ts +2 -0
- package/dist/core/rows.js +65 -0
- package/dist/core/rows.ts +58 -0
- package/dist/core/tree.js +1 -1
- package/dist/core/tree.ts +1 -1
- package/dist/derivation/attach.js +0 -12
- package/dist/derivation/attach.ts +0 -12
- package/dist/derivation/generators/build.js +8 -7
- package/dist/derivation/generators/build.ts +8 -7
- package/dist/derivation/generators/release.d.ts +1 -1
- package/dist/derivation/generators/release.js +15 -23
- package/dist/derivation/generators/release.ts +15 -15
- package/dist/integrations/attach.js +1 -19
- package/dist/integrations/attach.ts +1 -19
- package/dist/integrations/generators/read.js +2 -2
- package/dist/integrations/generators/read.ts +4 -4
- package/dist/integrations/generators/vault.js +4 -12
- package/dist/integrations/generators/vault.ts +4 -4
- package/dist/integrations/module.js +5 -3
- package/dist/integrations/module.ts +5 -3
- package/dist/integrations/vault.js +5 -5
- package/dist/integrations/vault.ts +5 -5
- package/dist/integrity/attach.js +0 -12
- package/dist/integrity/attach.ts +0 -12
- package/dist/integrity/check-threads.js +1 -1
- package/dist/integrity/check-threads.ts +1 -1
- package/dist/integrity/constraint.d.ts +11 -2
- package/dist/integrity/constraint.js +163 -20
- package/dist/integrity/constraint.ts +174 -22
- package/dist/integrity/generators/compiler.js +1 -1
- package/dist/integrity/generators/compiler.ts +1 -1
- package/dist/integrity/generators/gate.js +8 -16
- package/dist/integrity/generators/gate.ts +8 -7
- package/dist/integrity/generators/publish.js +14 -26
- package/dist/integrity/generators/publish.ts +14 -17
- package/dist/integrity/generators/trial.d.ts +2 -2
- package/dist/integrity/generators/trial.js +108 -50
- package/dist/integrity/generators/trial.ts +107 -48
- package/dist/integrity/lint.js +3 -3
- package/dist/integrity/lint.ts +3 -3
- package/dist/integrity/lsp/diagnostics.js +1 -1
- package/dist/integrity/lsp/diagnostics.ts +1 -1
- package/dist/integrity/lsp/lint.js +1 -1
- package/dist/integrity/lsp/lint.ts +1 -1
- package/dist/integrity/slopnator.d.ts +1 -14
- package/dist/integrity/slopnator.js +8 -40
- package/dist/integrity/slopnator.ts +9 -51
- package/dist/integrity/slopnators/sl_broken_lineage.js +4 -2
- package/dist/integrity/slopnators/sl_broken_lineage.ts +4 -2
- package/dist/integrity/slopnators/sl_declared_elsewhere.js +24 -3
- package/dist/integrity/slopnators/sl_declared_elsewhere.ts +24 -3
- package/dist/integrity/slopnators/sl_derived_drift.js +10 -4
- package/dist/integrity/slopnators/sl_derived_drift.ts +10 -4
- package/dist/integrity/slopnators/sl_inert_island.js +7 -13
- package/dist/integrity/slopnators/sl_inert_island.ts +7 -16
- package/dist/integrity/slopnators/sl_inert_slopnator.js +8 -8
- package/dist/integrity/slopnators/sl_inert_slopnator.ts +8 -8
- package/dist/integrity/slopnators/sl_missing_row.js +14 -9
- package/dist/integrity/slopnators/sl_missing_row.ts +14 -10
- package/dist/integrity/slopnators/sl_no_child_process.js +8 -8
- package/dist/integrity/slopnators/sl_no_child_process.ts +8 -8
- package/dist/integrity/slopnators/sl_no_import_cycle.js +1 -1
- package/dist/integrity/slopnators/sl_no_import_cycle.ts +1 -1
- package/dist/integrity/slopnators/sl_no_such_command.js +20 -5
- package/dist/integrity/slopnators/sl_no_such_command.ts +22 -5
- package/dist/integrity/slopnators/sl_only_takes_a_literal.js +8 -8
- package/dist/integrity/slopnators/sl_only_takes_a_literal.ts +8 -8
- package/dist/integrity/slopnators/sl_stale_path.js +91 -18
- package/dist/integrity/slopnators/sl_stale_path.ts +97 -18
- package/dist/integrity/slopnators/sl_twice_meant.js +25 -2
- package/dist/integrity/slopnators/sl_twice_meant.ts +28 -2
- package/dist/integrity/slopnators/sl_twice_parented.js +26 -5
- package/dist/integrity/slopnators/sl_twice_parented.ts +26 -5
- package/dist/integrity/slopnators/sl_undeclared_node.js +18 -2
- package/dist/integrity/slopnators/sl_undeclared_node.ts +18 -2
- package/dist/integrity/slopnators/sl_undeclared_operation.js +3 -1
- package/dist/integrity/slopnators/sl_undeclared_operation.ts +3 -1
- package/dist/integrity/slopnators/sl_undeclared_variant.js +6 -2
- package/dist/integrity/slopnators/sl_undeclared_variant.ts +6 -2
- package/dist/integrity/slopnators/sl_ungoverned_island.js +3 -4
- package/dist/integrity/slopnators/sl_ungoverned_island.ts +3 -4
- package/dist/integrity/slopnators/sl_unkept_rows.js +1 -1
- package/dist/integrity/slopnators/sl_unkept_rows.ts +1 -1
- package/dist/integrity/slopnators/sl_unpaired_member.js +13 -8
- package/dist/integrity/slopnators/sl_unpaired_member.ts +16 -8
- package/dist/integrity/slopnators/sl_unpaired_slopnator.js +16 -3
- package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +16 -3
- package/dist/integrity/slopnators/sl_unthreaded.d.ts +1 -0
- package/dist/integrity/slopnators/sl_unthreaded.js +18 -10
- package/dist/integrity/slopnators/sl_unthreaded.ts +19 -11
- package/dist/integrity/slopnators/sl_untranslated_key.js +1 -1
- package/dist/integrity/slopnators/sl_untranslated_key.ts +1 -1
- package/dist/integrity/slopnators/sl_unversioned.js +16 -2
- package/dist/integrity/slopnators/sl_unversioned.ts +16 -2
- package/dist/integrity/sweep.js +17 -3
- package/dist/integrity/sweep.ts +20 -3
- package/dist/knowledge/attach.js +0 -6
- package/dist/knowledge/attach.ts +0 -6
- package/dist/knowledge/generators/docsgen.js +12 -3
- package/dist/knowledge/generators/docsgen.ts +12 -2
- package/dist/knowledge/generators/glossary.js +1 -1
- package/dist/knowledge/generators/glossary.ts +1 -1
- package/dist/operations/attach.js +2 -26
- package/dist/operations/attach.ts +2 -27
- package/dist/operations/dispatch.js +2 -3
- package/dist/operations/dispatch.ts +2 -3
- package/dist/operations/generators/capability.js +4 -7
- package/dist/operations/generators/capability.ts +3 -6
- package/dist/operations/generators/compile.js +8 -13
- package/dist/operations/generators/compile.ts +8 -4
- package/dist/operations/generators/permits.js +4 -13
- package/dist/operations/generators/permits.ts +3 -3
- package/dist/operations/generators/seed.js +5 -2
- package/dist/operations/generators/seed.ts +6 -2
- package/dist/operations/generators/trial.js +10 -10
- package/dist/operations/generators/trial.ts +10 -10
- package/dist/operations/http.js +4 -1
- package/dist/operations/http.ts +6 -1
- package/package.json +2 -2
- package/dist/core/cli.d.ts +0 -2
- package/dist/core/cli.js +0 -1761
- package/dist/core/cli.ts +0 -2004
- package/dist/core/generators/meta.d.ts +0 -2
- package/dist/core/generators/meta.js +0 -829
- package/dist/core/generators/meta.ts +0 -1006
- package/dist/derivation/generators/derivation.d.ts +0 -3
- package/dist/derivation/generators/derivation.js +0 -273
- package/dist/derivation/generators/derivation.ts +0 -347
- package/dist/derivation/makers/release.d.ts +0 -1
- package/dist/derivation/makers/release.js +0 -107
- package/dist/derivation/makers/release.ts +0 -135
- package/dist/integrations/generators/secret.d.ts +0 -1
- package/dist/integrations/generators/secret.js +0 -233
- package/dist/integrations/generators/secret.ts +0 -289
- package/dist/integrations/generators/target.d.ts +0 -1
- package/dist/integrations/generators/target.js +0 -775
- package/dist/integrations/generators/target.ts +0 -944
- package/dist/integrity/generators/rule.d.ts +0 -2
- package/dist/integrity/generators/rule.js +0 -213
- package/dist/integrity/generators/rule.ts +0 -271
- package/dist/integrity/generators/seal.d.ts +0 -4
- package/dist/integrity/generators/seal.js +0 -101
- package/dist/integrity/generators/seal.ts +0 -117
- package/dist/integrity/sealed.d.ts +0 -1
- package/dist/integrity/sealed.js +0 -56
- package/dist/integrity/sealed.ts +0 -56
- package/dist/knowledge/generators/skills.d.ts +0 -1
- package/dist/knowledge/generators/skills.js +0 -222
- package/dist/knowledge/generators/skills.ts +0 -263
- package/dist/operations/generators/grant.d.ts +0 -1
- package/dist/operations/generators/grant.js +0 -267
- package/dist/operations/generators/grant.ts +0 -336
- package/dist/operations/generators/operation.d.ts +0 -1
- package/dist/operations/generators/operation.js +0 -811
- package/dist/operations/generators/operation.ts +0 -972
- package/dist/operations/makers/capability.d.ts +0 -1
- package/dist/operations/makers/capability.js +0 -117
- package/dist/operations/makers/capability.ts +0 -166
- package/dist/operations/makers/trial.d.ts +0 -1
- package/dist/operations/makers/trial.js +0 -413
- package/dist/operations/makers/trial.ts +0 -520
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { dirname, isAbsolute, relative, resolve } from 'node:path';
|
|
3
|
+
import { argv, cwd, env, stderr, stdout } from 'node:process';
|
|
4
|
+
import { kind, readFileSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "./files.js";
|
|
5
|
+
import { exit, invoked } from "./invoked.js";
|
|
6
|
+
import { THING } from "./scope.js";
|
|
7
|
+
const HERE = cwd();
|
|
8
|
+
const REASONS = {
|
|
9
|
+
ENOENT: 'is not there',
|
|
10
|
+
EACCES: 'is there, and this machine does not allow it',
|
|
11
|
+
EPERM: 'is there, and this machine does not allow it',
|
|
12
|
+
EISDIR: 'is a folder, and that order is for a file',
|
|
13
|
+
ENOTDIR: 'has a file part-way along its path where a folder was expected',
|
|
14
|
+
ENOTEMPTY: 'still holds something',
|
|
15
|
+
};
|
|
16
|
+
const HOME = (() => {
|
|
17
|
+
try {
|
|
18
|
+
return realpathSync(HERE);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return HERE;
|
|
22
|
+
}
|
|
23
|
+
})();
|
|
24
|
+
function deepest(at) {
|
|
25
|
+
let found = at;
|
|
26
|
+
while (kind(found) === 'none') {
|
|
27
|
+
const up = dirname(found);
|
|
28
|
+
if (up === found)
|
|
29
|
+
return found;
|
|
30
|
+
found = up;
|
|
31
|
+
}
|
|
32
|
+
return found;
|
|
33
|
+
}
|
|
34
|
+
function inside(at) {
|
|
35
|
+
let real;
|
|
36
|
+
try {
|
|
37
|
+
real = realpathSync(deepest(resolve(HERE, at)));
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
const step = relative(HOME, real);
|
|
43
|
+
return step === '' || (!step.startsWith('..') && !isAbsolute(step));
|
|
44
|
+
}
|
|
45
|
+
function rootly(at) {
|
|
46
|
+
const asked = resolve(HERE, at);
|
|
47
|
+
if (kind(asked) === 'none')
|
|
48
|
+
return false;
|
|
49
|
+
try {
|
|
50
|
+
return realpathSync(asked) === HOME;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const LOOPBACK = new Set(['localhost', '127.0.0.1', '[::1]', '::1']);
|
|
57
|
+
function local(at) {
|
|
58
|
+
try {
|
|
59
|
+
return LOOPBACK.has(new URL(at).hostname);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function why(at, thrown) {
|
|
66
|
+
const held = thrown instanceof Error ? Reflect.get(thrown, 'code') : undefined;
|
|
67
|
+
const said = typeof held === 'string' ? REASONS[held] : undefined;
|
|
68
|
+
if (said !== undefined)
|
|
69
|
+
return `${at} ${said}.`;
|
|
70
|
+
return `${at} could not be reached — ${thrown instanceof Error ? thrown.message : String(thrown)}.`;
|
|
71
|
+
}
|
|
72
|
+
export function carry(one) {
|
|
73
|
+
if (!inside(one.at)) {
|
|
74
|
+
return {
|
|
75
|
+
answered: 'refused',
|
|
76
|
+
because: `${one.at} is outside ${HERE} once its links are followed, and this answers only for what is really inside the repository it was run in.`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (one.order === 'remove' && rootly(one.at)) {
|
|
80
|
+
return {
|
|
81
|
+
answered: 'refused',
|
|
82
|
+
because: `${one.at} is the repository this was run in, and this carries orders inside it rather than taking it away.`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
if (one.order === 'kind')
|
|
87
|
+
return { answered: 'kind', held: kind(one.at) };
|
|
88
|
+
if (one.order === 'read')
|
|
89
|
+
return { answered: 'read', said: readFileSync(one.at) };
|
|
90
|
+
if (one.order === 'list') {
|
|
91
|
+
const held = readdirSync(one.at, { withFileTypes: true }).map((each) => ({
|
|
92
|
+
name: each.name,
|
|
93
|
+
kind: each.isFile() ? 'file' : each.isDirectory() ? 'folder' : 'other',
|
|
94
|
+
}));
|
|
95
|
+
return { answered: 'list', held };
|
|
96
|
+
}
|
|
97
|
+
if (one.order === 'stat') {
|
|
98
|
+
const held = statSync(one.at);
|
|
99
|
+
return { answered: 'stat', held: { size: held.size, modified: held.mtimeMs } };
|
|
100
|
+
}
|
|
101
|
+
if (one.order === 'remove') {
|
|
102
|
+
rmSync(one.at, { recursive: true, force: true });
|
|
103
|
+
return { answered: 'removed' };
|
|
104
|
+
}
|
|
105
|
+
if (one.order === 'real')
|
|
106
|
+
return { answered: 'real', at: realpathSync(one.at) };
|
|
107
|
+
writeFileSync(one.at, one.said);
|
|
108
|
+
return { answered: 'written' };
|
|
109
|
+
}
|
|
110
|
+
catch (thrown) {
|
|
111
|
+
return { answered: 'refused', because: why(one.at, thrown) };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const AT = 'THREAD_SERVER';
|
|
115
|
+
const KEY = 'THREAD_KEY';
|
|
116
|
+
const ROUNDS = 100;
|
|
117
|
+
async function sending(at, key, body) {
|
|
118
|
+
const said = await fetch(at, {
|
|
119
|
+
method: 'POST',
|
|
120
|
+
headers: { 'content-type': 'application/json', authorization: `Bearer ${key}` },
|
|
121
|
+
body: JSON.stringify(body),
|
|
122
|
+
});
|
|
123
|
+
if (said.status === 401 || said.status === 403)
|
|
124
|
+
throw new Error(`the key in ${KEY} is not one ${at} accepts`);
|
|
125
|
+
if (!said.ok)
|
|
126
|
+
throw new Error(`${at} answered ${String(said.status)}`);
|
|
127
|
+
return (await said.json());
|
|
128
|
+
}
|
|
129
|
+
export async function talking(at, key, message) {
|
|
130
|
+
let turn = await sending(at, key, { said: message });
|
|
131
|
+
for (let spun = 0; spun < ROUNDS; spun += 1) {
|
|
132
|
+
if (turn.say !== undefined && turn.say !== '')
|
|
133
|
+
stdout.write(`${turn.say}\n`);
|
|
134
|
+
if (turn.done === true)
|
|
135
|
+
return 0;
|
|
136
|
+
turn = await sending(at, key, { of: turn.of, answered: (turn.orders ?? []).map(carry) });
|
|
137
|
+
}
|
|
138
|
+
stderr.write(`${THING}: this went ${String(ROUNDS)} rounds without finishing, so it was stopped there. What is already\n` +
|
|
139
|
+
`in this repository stands, and nothing half-written was left behind — every change is one order, carried\n` +
|
|
140
|
+
`and answered before the next one is asked for.\n`);
|
|
141
|
+
return 1;
|
|
142
|
+
}
|
|
143
|
+
export async function run(said) {
|
|
144
|
+
if (said.length === 0) {
|
|
145
|
+
stdout.write(`${THING}: say what you want built, in one sentence, in quotes.\n\n ${THING} "take payments with stripe"\n\n` +
|
|
146
|
+
`That sentence is the whole of what this takes — there is no command here to look up. Nothing on this\n` +
|
|
147
|
+
`machine declares a row, writes a file or runs a check. What comes back is written into this repository\n` +
|
|
148
|
+
`for you, and every check this framework runs has to pass before it stays.\n`);
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
151
|
+
const message = said.length === 1 ? (said[0] ?? '') : '';
|
|
152
|
+
if (!message.includes(' ')) {
|
|
153
|
+
stderr.write(`${THING}: there is no \`${said.join(' ')}\` here, and no other command to find instead. This takes one\n` +
|
|
154
|
+
`sentence, in quotes, and nothing else — ${THING} "take payments with stripe". Say what you want and it\n` +
|
|
155
|
+
`is answered where the work happens, never on this machine.\n`);
|
|
156
|
+
return 1;
|
|
157
|
+
}
|
|
158
|
+
const at = env[AT];
|
|
159
|
+
if (at === undefined || at === '') {
|
|
160
|
+
stderr.write(`${THING}: there is nowhere to send that yet. This copy has no server to answer it — ${AT} names one, and\n` +
|
|
161
|
+
`nothing here is set to one.\n`);
|
|
162
|
+
return 1;
|
|
163
|
+
}
|
|
164
|
+
if (!local(at)) {
|
|
165
|
+
stderr.write(`${THING}: ${AT} names ${at}, which is not on this machine, and this only ever talks to a server running\n` +
|
|
166
|
+
`here. Set ${AT} to a http://localhost address and say it again. Nothing was sent.\n`);
|
|
167
|
+
return 1;
|
|
168
|
+
}
|
|
169
|
+
const key = env[KEY];
|
|
170
|
+
if (key === undefined || key === '') {
|
|
171
|
+
stderr.write(`${THING}: nothing here says who is asking, and ${at} answers no one it cannot name. ${KEY} carries your\n` +
|
|
172
|
+
`key, and nothing on this machine is set to one. Set it in this terminal and say it again — it is read\n` +
|
|
173
|
+
`from there, never written into this repository and never sent anywhere but ${at}.\n`);
|
|
174
|
+
return 1;
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
return await talking(at, key, message);
|
|
178
|
+
}
|
|
179
|
+
catch (thrown) {
|
|
180
|
+
stderr.write(`${THING}: ${at} could not answer — ${thrown instanceof Error ? thrown.message : String(thrown)}. Nothing was\n` +
|
|
181
|
+
`written, so this repository is as it was.\n`);
|
|
182
|
+
return 1;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (invoked(import.meta.url))
|
|
186
|
+
exit(await run(argv.slice(2)));
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { dirname, isAbsolute, relative, resolve } from 'node:path'
|
|
3
|
+
import { argv, cwd, env, stderr, stdout } from 'node:process'
|
|
4
|
+
import { kind, readFileSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync, type Kind, type Listed, type Measured } from './files.ts'
|
|
5
|
+
import { exit, invoked } from './invoked.ts'
|
|
6
|
+
import { THING } from './scope.ts'
|
|
7
|
+
|
|
8
|
+
export type Order =
|
|
9
|
+
| { readonly order: 'kind'; readonly at: string }
|
|
10
|
+
| { readonly order: 'read'; readonly at: string }
|
|
11
|
+
| { readonly order: 'list'; readonly at: string }
|
|
12
|
+
| { readonly order: 'stat'; readonly at: string }
|
|
13
|
+
| { readonly order: 'remove'; readonly at: string }
|
|
14
|
+
| { readonly order: 'real'; readonly at: string }
|
|
15
|
+
| { readonly order: 'write'; readonly at: string; readonly said: string }
|
|
16
|
+
|
|
17
|
+
export type Answered =
|
|
18
|
+
| { readonly answered: 'kind'; readonly held: Kind }
|
|
19
|
+
| { readonly answered: 'read'; readonly said: string }
|
|
20
|
+
| { readonly answered: 'list'; readonly held: readonly Listed[] }
|
|
21
|
+
| { readonly answered: 'stat'; readonly held: Measured }
|
|
22
|
+
| { readonly answered: 'written' }
|
|
23
|
+
| { readonly answered: 'removed' }
|
|
24
|
+
| { readonly answered: 'real'; readonly at: string }
|
|
25
|
+
| { readonly answered: 'refused'; readonly because: string }
|
|
26
|
+
|
|
27
|
+
const HERE = cwd()
|
|
28
|
+
|
|
29
|
+
const REASONS: Readonly<Record<string, string>> = {
|
|
30
|
+
ENOENT: 'is not there',
|
|
31
|
+
EACCES: 'is there, and this machine does not allow it',
|
|
32
|
+
EPERM: 'is there, and this machine does not allow it',
|
|
33
|
+
EISDIR: 'is a folder, and that order is for a file',
|
|
34
|
+
ENOTDIR: 'has a file part-way along its path where a folder was expected',
|
|
35
|
+
ENOTEMPTY: 'still holds something',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const HOME = ((): string => {
|
|
39
|
+
try {
|
|
40
|
+
return realpathSync(HERE)
|
|
41
|
+
} catch {
|
|
42
|
+
return HERE
|
|
43
|
+
}
|
|
44
|
+
})()
|
|
45
|
+
|
|
46
|
+
function deepest(at: string): string {
|
|
47
|
+
let found = at
|
|
48
|
+
while (kind(found) === 'none') {
|
|
49
|
+
const up = dirname(found)
|
|
50
|
+
if (up === found) return found
|
|
51
|
+
found = up
|
|
52
|
+
}
|
|
53
|
+
return found
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function inside(at: string): boolean {
|
|
57
|
+
let real: string
|
|
58
|
+
try {
|
|
59
|
+
real = realpathSync(deepest(resolve(HERE, at)))
|
|
60
|
+
} catch {
|
|
61
|
+
return false
|
|
62
|
+
}
|
|
63
|
+
const step = relative(HOME, real)
|
|
64
|
+
return step === '' || (!step.startsWith('..') && !isAbsolute(step))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function rootly(at: string): boolean {
|
|
68
|
+
const asked = resolve(HERE, at)
|
|
69
|
+
if (kind(asked) === 'none') return false
|
|
70
|
+
try {
|
|
71
|
+
return realpathSync(asked) === HOME
|
|
72
|
+
} catch {
|
|
73
|
+
return false
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const LOOPBACK = new Set(['localhost', '127.0.0.1', '[::1]', '::1'])
|
|
78
|
+
|
|
79
|
+
function local(at: string): boolean {
|
|
80
|
+
try {
|
|
81
|
+
return LOOPBACK.has(new URL(at).hostname)
|
|
82
|
+
} catch {
|
|
83
|
+
return false
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function why(at: string, thrown: unknown): string {
|
|
88
|
+
const held: unknown = thrown instanceof Error ? Reflect.get(thrown, 'code') : undefined
|
|
89
|
+
const said = typeof held === 'string' ? REASONS[held] : undefined
|
|
90
|
+
if (said !== undefined) return `${at} ${said}.`
|
|
91
|
+
return `${at} could not be reached — ${thrown instanceof Error ? thrown.message : String(thrown)}.`
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function carry(one: Order): Answered {
|
|
95
|
+
if (!inside(one.at)) {
|
|
96
|
+
return {
|
|
97
|
+
answered: 'refused',
|
|
98
|
+
because: `${one.at} is outside ${HERE} once its links are followed, and this answers only for what is really inside the repository it was run in.`,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (one.order === 'remove' && rootly(one.at)) {
|
|
102
|
+
return {
|
|
103
|
+
answered: 'refused',
|
|
104
|
+
because: `${one.at} is the repository this was run in, and this carries orders inside it rather than taking it away.`,
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
if (one.order === 'kind') return { answered: 'kind', held: kind(one.at) }
|
|
109
|
+
if (one.order === 'read') return { answered: 'read', said: readFileSync(one.at) }
|
|
110
|
+
if (one.order === 'list') {
|
|
111
|
+
const held = readdirSync(one.at, { withFileTypes: true }).map((each) => ({
|
|
112
|
+
name: each.name,
|
|
113
|
+
kind: each.isFile() ? ('file' as const) : each.isDirectory() ? ('folder' as const) : ('other' as const),
|
|
114
|
+
}))
|
|
115
|
+
return { answered: 'list', held }
|
|
116
|
+
}
|
|
117
|
+
if (one.order === 'stat') {
|
|
118
|
+
const held = statSync(one.at)
|
|
119
|
+
return { answered: 'stat', held: { size: held.size, modified: held.mtimeMs } }
|
|
120
|
+
}
|
|
121
|
+
if (one.order === 'remove') {
|
|
122
|
+
rmSync(one.at, { recursive: true, force: true })
|
|
123
|
+
return { answered: 'removed' }
|
|
124
|
+
}
|
|
125
|
+
if (one.order === 'real') return { answered: 'real', at: realpathSync(one.at) }
|
|
126
|
+
writeFileSync(one.at, one.said)
|
|
127
|
+
return { answered: 'written' }
|
|
128
|
+
} catch (thrown) {
|
|
129
|
+
return { answered: 'refused', because: why(one.at, thrown) }
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface Turn {
|
|
134
|
+
readonly of: string
|
|
135
|
+
readonly orders?: readonly Order[]
|
|
136
|
+
readonly say?: string
|
|
137
|
+
readonly done?: boolean
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const AT = 'THREAD_SERVER'
|
|
141
|
+
|
|
142
|
+
const KEY = 'THREAD_KEY'
|
|
143
|
+
|
|
144
|
+
const ROUNDS = 100
|
|
145
|
+
|
|
146
|
+
async function sending(at: string, key: string, body: unknown): Promise<Turn> {
|
|
147
|
+
const said = await fetch(at, {
|
|
148
|
+
method: 'POST',
|
|
149
|
+
headers: { 'content-type': 'application/json', authorization: `Bearer ${key}` },
|
|
150
|
+
body: JSON.stringify(body),
|
|
151
|
+
})
|
|
152
|
+
if (said.status === 401 || said.status === 403) throw new Error(`the key in ${KEY} is not one ${at} accepts`)
|
|
153
|
+
if (!said.ok) throw new Error(`${at} answered ${String(said.status)}`)
|
|
154
|
+
return (await said.json()) as Turn
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function talking(at: string, key: string, message: string): Promise<number> {
|
|
158
|
+
let turn = await sending(at, key, { said: message })
|
|
159
|
+
for (let spun = 0; spun < ROUNDS; spun += 1) {
|
|
160
|
+
if (turn.say !== undefined && turn.say !== '') stdout.write(`${turn.say}\n`)
|
|
161
|
+
if (turn.done === true) return 0
|
|
162
|
+
turn = await sending(at, key, { of: turn.of, answered: (turn.orders ?? []).map(carry) })
|
|
163
|
+
}
|
|
164
|
+
stderr.write(
|
|
165
|
+
`${THING}: this went ${String(ROUNDS)} rounds without finishing, so it was stopped there. What is already\n` +
|
|
166
|
+
`in this repository stands, and nothing half-written was left behind — every change is one order, carried\n` +
|
|
167
|
+
`and answered before the next one is asked for.\n`,
|
|
168
|
+
)
|
|
169
|
+
return 1
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export async function run(said: readonly string[]): Promise<number> {
|
|
173
|
+
if (said.length === 0) {
|
|
174
|
+
stdout.write(
|
|
175
|
+
`${THING}: say what you want built, in one sentence, in quotes.\n\n ${THING} "take payments with stripe"\n\n` +
|
|
176
|
+
`That sentence is the whole of what this takes — there is no command here to look up. Nothing on this\n` +
|
|
177
|
+
`machine declares a row, writes a file or runs a check. What comes back is written into this repository\n` +
|
|
178
|
+
`for you, and every check this framework runs has to pass before it stays.\n`,
|
|
179
|
+
)
|
|
180
|
+
return 0
|
|
181
|
+
}
|
|
182
|
+
const message = said.length === 1 ? (said[0] ?? '') : ''
|
|
183
|
+
if (!message.includes(' ')) {
|
|
184
|
+
stderr.write(
|
|
185
|
+
`${THING}: there is no \`${said.join(' ')}\` here, and no other command to find instead. This takes one\n` +
|
|
186
|
+
`sentence, in quotes, and nothing else — ${THING} "take payments with stripe". Say what you want and it\n` +
|
|
187
|
+
`is answered where the work happens, never on this machine.\n`,
|
|
188
|
+
)
|
|
189
|
+
return 1
|
|
190
|
+
}
|
|
191
|
+
const at = env[AT]
|
|
192
|
+
if (at === undefined || at === '') {
|
|
193
|
+
stderr.write(
|
|
194
|
+
`${THING}: there is nowhere to send that yet. This copy has no server to answer it — ${AT} names one, and\n` +
|
|
195
|
+
`nothing here is set to one.\n`,
|
|
196
|
+
)
|
|
197
|
+
return 1
|
|
198
|
+
}
|
|
199
|
+
if (!local(at)) {
|
|
200
|
+
stderr.write(
|
|
201
|
+
`${THING}: ${AT} names ${at}, which is not on this machine, and this only ever talks to a server running\n` +
|
|
202
|
+
`here. Set ${AT} to a http://localhost address and say it again. Nothing was sent.\n`,
|
|
203
|
+
)
|
|
204
|
+
return 1
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const key = env[KEY]
|
|
208
|
+
if (key === undefined || key === '') {
|
|
209
|
+
stderr.write(
|
|
210
|
+
`${THING}: nothing here says who is asking, and ${at} answers no one it cannot name. ${KEY} carries your\n` +
|
|
211
|
+
`key, and nothing on this machine is set to one. Set it in this terminal and say it again — it is read\n` +
|
|
212
|
+
`from there, never written into this repository and never sent anywhere but ${at}.\n`,
|
|
213
|
+
)
|
|
214
|
+
return 1
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
return await talking(at, key, message)
|
|
218
|
+
} catch (thrown) {
|
|
219
|
+
stderr.write(
|
|
220
|
+
`${THING}: ${at} could not answer — ${thrown instanceof Error ? thrown.message : String(thrown)}. Nothing was\n` +
|
|
221
|
+
`written, so this repository is as it was.\n`,
|
|
222
|
+
)
|
|
223
|
+
return 1
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (invoked(import.meta.url)) exit(await run(argv.slice(2)))
|
package/dist/core/descend.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export declare const TREE: string;
|
|
|
5
5
|
export declare const THREAD_AT: string;
|
|
6
6
|
export declare function unkept(at: string): boolean;
|
|
7
7
|
export declare const LISTS: readonly ["declares", "derives", "seeds"];
|
|
8
|
+
export declare const ROW_LISTS: readonly ["declares", "derives", "seeds", "holds"];
|
|
8
9
|
export interface Row {
|
|
9
10
|
readonly at?: string;
|
|
10
11
|
readonly id?: string;
|
|
@@ -46,6 +47,7 @@ export declare function configured(): {
|
|
|
46
47
|
readonly from: string;
|
|
47
48
|
readonly trouble?: string;
|
|
48
49
|
};
|
|
50
|
+
export declare function hosting(options: ts.CompilerOptions): ts.CompilerHost;
|
|
49
51
|
export declare function uncompiled(): readonly Finding[];
|
|
50
52
|
export declare function read(at: string): string | undefined;
|
|
51
53
|
export declare function shapesNamed(): ReadonlyMap<string, readonly string[]>;
|
package/dist/core/descend.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from
|
|
2
|
+
import { attached, existsSync, kind, readdirSync, readFileSync, realpathSync, statSync, writeFileSync } from "./files.js";
|
|
3
3
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
4
4
|
import ts from 'typescript';
|
|
5
5
|
import { ignored } from "./init.js";
|
|
@@ -26,12 +26,17 @@ function rooted() {
|
|
|
26
26
|
export const ROOT = TREE_AT?.at ?? NOWHERE;
|
|
27
27
|
export const TREE = `${TREE_AT?.name ?? NOWHERE}/`;
|
|
28
28
|
export const THREAD_AT = `${TREE_AT?.name ?? NOWHERE}/thread.ts`;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
let ignores;
|
|
30
|
+
let readIgnores = false;
|
|
31
|
+
function ownIgnores() {
|
|
32
|
+
if (readIgnores)
|
|
33
|
+
return ignores;
|
|
34
|
+
readIgnores = true;
|
|
35
|
+
if (!attached() && spawnSync('git', ['rev-parse', '--is-inside-work-tree'], { cwd: ROOT, stdio: 'ignore' }).status === 0) {
|
|
36
|
+
return ignores;
|
|
32
37
|
}
|
|
33
38
|
try {
|
|
34
|
-
|
|
39
|
+
ignores = readFileSync(join(ROOT, '.gitignore'), 'utf8')
|
|
35
40
|
.split('\n')
|
|
36
41
|
.map((one) => one.trim())
|
|
37
42
|
.filter((one) => one !== '' && !one.startsWith('#') && !one.startsWith('!'))
|
|
@@ -39,21 +44,24 @@ const OWN_IGNORES = (() => {
|
|
|
39
44
|
.filter((one) => one !== '' && !one.includes('*'));
|
|
40
45
|
}
|
|
41
46
|
catch {
|
|
42
|
-
|
|
47
|
+
ignores = [];
|
|
43
48
|
}
|
|
44
|
-
|
|
49
|
+
return ignores;
|
|
50
|
+
}
|
|
45
51
|
const asked = new Map();
|
|
46
52
|
export function unkept(at) {
|
|
47
53
|
const held = asked.get(at);
|
|
48
54
|
if (held !== undefined)
|
|
49
55
|
return held;
|
|
50
|
-
const
|
|
56
|
+
const own = ownIgnores();
|
|
57
|
+
const found = own === undefined
|
|
51
58
|
? spawnSync('git', ['check-ignore', '-q', '--', at], { cwd: ROOT, stdio: 'ignore' }).status === 0
|
|
52
|
-
:
|
|
59
|
+
: own.some((one) => at === one || at.startsWith(`${one}/`) || at.split('/').includes(one));
|
|
53
60
|
asked.set(at, found);
|
|
54
61
|
return found;
|
|
55
62
|
}
|
|
56
63
|
export const LISTS = ['declares', 'derives', 'seeds'];
|
|
64
|
+
export const ROW_LISTS = [...LISTS, 'holds'];
|
|
57
65
|
const SOURCE = /\.(ts|tsx|mts|cts)$/;
|
|
58
66
|
const RUNNER = /^(?:pnpm|npm|yarn) (?:run )?([\w:.-]+)/;
|
|
59
67
|
export function under(pass, id, root, seen = new Set()) {
|
|
@@ -148,12 +156,67 @@ const INVARIANT = {
|
|
|
148
156
|
allowJs: true,
|
|
149
157
|
jsx: ts.JsxEmit.ReactJSX,
|
|
150
158
|
};
|
|
159
|
+
const NEVER = ['node_modules', 'bower_components', 'jspm_packages'];
|
|
160
|
+
function bounds(from, one) {
|
|
161
|
+
const star = one.indexOf('*');
|
|
162
|
+
if (star === -1)
|
|
163
|
+
return { under: resolve(from, one), ending: '' };
|
|
164
|
+
const head = one.slice(0, star);
|
|
165
|
+
const cut = head.lastIndexOf('/');
|
|
166
|
+
const tail = one.slice(star);
|
|
167
|
+
const dot = tail.lastIndexOf('.');
|
|
168
|
+
const end = dot === -1 ? '' : tail.slice(dot);
|
|
169
|
+
return {
|
|
170
|
+
under: resolve(from, cut === -1 ? '' : head.slice(0, cut)),
|
|
171
|
+
ending: end.includes('/') || end.includes('*') ? '' : end,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function wanted(at, only) {
|
|
175
|
+
if (only.length === 0)
|
|
176
|
+
return true;
|
|
177
|
+
return only.some((one) => (at === one.under || at.startsWith(`${one.under}/`)) && at.endsWith(one.ending));
|
|
178
|
+
}
|
|
179
|
+
function listing(at, take, skip, only, deep) {
|
|
180
|
+
const found = [];
|
|
181
|
+
if (deep < 0 || !existsSync(at))
|
|
182
|
+
return found;
|
|
183
|
+
for (const one of readdirSync(at, { withFileTypes: true })) {
|
|
184
|
+
const full = join(at, one.name);
|
|
185
|
+
if (skip.some((each) => full === each || full.startsWith(`${each}/`)))
|
|
186
|
+
continue;
|
|
187
|
+
if (one.isDirectory()) {
|
|
188
|
+
if (NEVER.includes(one.name))
|
|
189
|
+
continue;
|
|
190
|
+
found.push(...listing(full, take, skip, only, deep - 1));
|
|
191
|
+
}
|
|
192
|
+
else if (take.some((each) => one.name.endsWith(each)) && wanted(full, only)) {
|
|
193
|
+
found.push(full);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return found;
|
|
197
|
+
}
|
|
198
|
+
function parsing(from) {
|
|
199
|
+
return {
|
|
200
|
+
useCaseSensitiveFileNames: true,
|
|
201
|
+
fileExists: (at) => existsSync(at),
|
|
202
|
+
readFile: (at) => {
|
|
203
|
+
try {
|
|
204
|
+
return readFileSync(at);
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
readDirectory: (at, take, skip, only, deep) => listing(at, take, (skip ?? []).map((one) => resolve(from, one.endsWith('/**') ? one.slice(0, -3) : one)), only.map((one) => bounds(from, one)), deep ?? Number.MAX_SAFE_INTEGER),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
151
213
|
export function configured() {
|
|
152
214
|
for (const name of CONFIGS) {
|
|
153
215
|
const at = join(ROOT, name);
|
|
154
|
-
if (!
|
|
216
|
+
if (!existsSync(at))
|
|
155
217
|
continue;
|
|
156
|
-
const
|
|
218
|
+
const host = parsing(ROOT);
|
|
219
|
+
const held = ts.readConfigFile(at, host.readFile);
|
|
157
220
|
if (held.error !== undefined) {
|
|
158
221
|
return {
|
|
159
222
|
options: { ...FALLBACK, ...INVARIANT },
|
|
@@ -161,7 +224,7 @@ export function configured() {
|
|
|
161
224
|
trouble: `${name} is there and cannot be read: ${ts.flattenDiagnosticMessageText(held.error.messageText, ' ')}`,
|
|
162
225
|
};
|
|
163
226
|
}
|
|
164
|
-
const parsed = ts.parseJsonConfigFileContent(held.config,
|
|
227
|
+
const parsed = ts.parseJsonConfigFileContent(held.config, host, dirname(at));
|
|
165
228
|
const complained = parsed.errors[0];
|
|
166
229
|
if (complained !== undefined) {
|
|
167
230
|
return {
|
|
@@ -174,13 +237,37 @@ export function configured() {
|
|
|
174
237
|
}
|
|
175
238
|
return { options: { ...FALLBACK, ...INVARIANT }, from: '' };
|
|
176
239
|
}
|
|
240
|
+
export function hosting(options) {
|
|
241
|
+
const host = ts.createCompilerHost(options, true);
|
|
242
|
+
const lib = host.getDefaultLibLocation?.();
|
|
243
|
+
const toolchain = (at) => lib !== undefined && (at === lib || at.startsWith(`${lib}/`));
|
|
244
|
+
return {
|
|
245
|
+
...host,
|
|
246
|
+
fileExists: (at) => (toolchain(at) ? host.fileExists(at) : kind(at) === 'file'),
|
|
247
|
+
directoryExists: (at) => (toolchain(at) ? (host.directoryExists?.(at) ?? false) : kind(at) === 'folder'),
|
|
248
|
+
readFile: (at) => (toolchain(at) ? host.readFile(at) : kind(at) === 'file' ? readFileSync(at) : undefined),
|
|
249
|
+
getSourceFile: (at, version) => {
|
|
250
|
+
if (toolchain(at))
|
|
251
|
+
return host.getSourceFile(at, version);
|
|
252
|
+
if (kind(at) !== 'file')
|
|
253
|
+
return undefined;
|
|
254
|
+
return ts.createSourceFile(at, readFileSync(at), version, true);
|
|
255
|
+
},
|
|
256
|
+
getDirectories: (at) => kind(at) === 'folder' ? readdirSync(at, { withFileTypes: true }).filter((one) => one.isDirectory()).map((one) => one.name) : [],
|
|
257
|
+
realpath: (at) => (toolchain(at) ? (host.realpath?.(at) ?? at) : realpathSync(at)),
|
|
258
|
+
writeFile: (at, said) => {
|
|
259
|
+
writeFileSync(at, said);
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
}
|
|
177
263
|
let built;
|
|
178
264
|
function compiled() {
|
|
179
265
|
rooted();
|
|
180
266
|
if (built !== undefined)
|
|
181
267
|
return built;
|
|
182
268
|
const roots = [THREAD_AT, ...sources(), ...shipped()].filter((one) => SOURCE.test(one)).map((one) => resolve(ROOT, one));
|
|
183
|
-
const
|
|
269
|
+
const options = configured().options;
|
|
270
|
+
const program = ts.createProgram([...new Set(roots)], options, hosting(options));
|
|
184
271
|
built = { program, checker: program.getTypeChecker() };
|
|
185
272
|
return built;
|
|
186
273
|
}
|
|
@@ -585,6 +672,9 @@ export function forget() {
|
|
|
585
672
|
scanned = undefined;
|
|
586
673
|
commands = undefined;
|
|
587
674
|
source.clear();
|
|
675
|
+
asked.clear();
|
|
676
|
+
ignores = undefined;
|
|
677
|
+
readIgnores = false;
|
|
588
678
|
}
|
|
589
679
|
export function descend() {
|
|
590
680
|
rooted();
|
|
@@ -762,7 +852,10 @@ export function descend() {
|
|
|
762
852
|
continue;
|
|
763
853
|
}
|
|
764
854
|
for (const one of value.elements) {
|
|
765
|
-
|
|
855
|
+
const declaration = ts.isIdentifier(one) ? checker.getSymbolAtLocation(one)?.valueDeclaration : undefined;
|
|
856
|
+
const initial = declaration !== undefined && ts.isVariableDeclaration(declaration) ? unwrap(declaration.initializer) : undefined;
|
|
857
|
+
const object = ts.isObjectLiteralExpression(one) ? one : initial !== undefined && ts.isObjectLiteralExpression(initial) ? initial : undefined;
|
|
858
|
+
if (object === undefined) {
|
|
766
859
|
findings.push({
|
|
767
860
|
file: at,
|
|
768
861
|
detail: `\`${by}\` holds something in \`${list}\` that is not a row written out here`,
|
|
@@ -773,7 +866,7 @@ export function descend() {
|
|
|
773
866
|
rows += 1;
|
|
774
867
|
const wanted = checker.getContextualType(one);
|
|
775
868
|
const kind = wanted === undefined ? undefined : elementOf(checker, wanted);
|
|
776
|
-
const row = rowOf(
|
|
869
|
+
const row = rowOf(object, kind === undefined ? type : checker.typeToString(kind), at, kind);
|
|
777
870
|
coin(row.id, row.describes, under, by);
|
|
778
871
|
judge(row, by, list, false);
|
|
779
872
|
if (row.at !== undefined) {
|
|
@@ -782,7 +875,7 @@ export function descend() {
|
|
|
782
875
|
derived.set(row.id, row);
|
|
783
876
|
}
|
|
784
877
|
if (LISTS.some((name) => row.fields.has(name)))
|
|
785
|
-
into(
|
|
878
|
+
into(object, row.type, at, under, by, true);
|
|
786
879
|
if (row.at !== undefined)
|
|
787
880
|
open(row.at, wanted === undefined ? undefined : elementOf(checker, wanted), under, row.id ?? by, by);
|
|
788
881
|
}
|