@prestack/thread 0.0.1
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 +192 -0
- package/dist/attach.d.ts +1 -0
- package/dist/attach.js +1 -0
- package/dist/core/actor.d.ts +12 -0
- package/dist/core/actor.js +8 -0
- package/dist/core/asking.d.ts +180 -0
- package/dist/core/asking.js +1109 -0
- package/dist/core/attach.d.ts +54 -0
- package/dist/core/attach.js +510 -0
- package/dist/core/chain.d.ts +16 -0
- package/dist/core/chain.js +114 -0
- package/dist/core/cli.d.ts +2 -0
- package/dist/core/cli.js +1682 -0
- package/dist/core/condition.d.ts +52 -0
- package/dist/core/condition.js +198 -0
- package/dist/core/descend.d.ts +81 -0
- package/dist/core/descend.js +879 -0
- package/dist/core/finding.d.ts +12 -0
- package/dist/core/finding.js +23 -0
- package/dist/core/generators/meta.d.ts +2 -0
- package/dist/core/generators/meta.js +823 -0
- package/dist/core/init.d.ts +15 -0
- package/dist/core/init.js +134 -0
- package/dist/core/invoked.d.ts +9 -0
- package/dist/core/invoked.js +41 -0
- package/dist/core/round.d.ts +1 -0
- package/dist/core/round.js +57 -0
- package/dist/core/rows.d.ts +26 -0
- package/dist/core/rows.js +354 -0
- package/dist/core/scope.d.ts +6 -0
- package/dist/core/scope.js +12 -0
- package/dist/core/thread.d.ts +26 -0
- package/dist/core/thread.js +5 -0
- package/dist/core/tree.d.ts +12 -0
- package/dist/core/tree.js +56 -0
- package/dist/core/types.d.ts +49 -0
- package/dist/core/types.js +311 -0
- package/dist/derivation/attach.d.ts +2 -0
- package/dist/derivation/attach.js +61 -0
- package/dist/derivation/generators/build.d.ts +16 -0
- package/dist/derivation/generators/build.js +217 -0
- package/dist/derivation/generators/check.d.ts +18 -0
- package/dist/derivation/generators/check.js +78 -0
- package/dist/derivation/generators/derivation.d.ts +3 -0
- package/dist/derivation/generators/derivation.js +273 -0
- package/dist/derivation/generators/determinism.d.ts +20 -0
- package/dist/derivation/generators/determinism.js +110 -0
- package/dist/derivation/generators/release.d.ts +3 -0
- package/dist/derivation/generators/release.js +183 -0
- package/dist/derivation/makers/release.d.ts +1 -0
- package/dist/derivation/makers/release.js +107 -0
- package/dist/derivation/release.d.ts +10 -0
- package/dist/derivation/release.js +12 -0
- package/dist/integrations/attach.d.ts +6 -0
- package/dist/integrations/attach.js +177 -0
- package/dist/integrations/generators/read.d.ts +1 -0
- package/dist/integrations/generators/read.js +92 -0
- package/dist/integrations/generators/sdk.d.ts +1 -0
- package/dist/integrations/generators/sdk.js +148 -0
- package/dist/integrations/generators/secret.d.ts +1 -0
- package/dist/integrations/generators/secret.js +233 -0
- package/dist/integrations/generators/target.d.ts +1 -0
- package/dist/integrations/generators/target.js +765 -0
- package/dist/integrations/generators/vault.d.ts +1 -0
- package/dist/integrations/generators/vault.js +164 -0
- package/dist/integrations/sdk.d.ts +6 -0
- package/dist/integrations/sdk.js +151 -0
- package/dist/integrations/secret.d.ts +16 -0
- package/dist/integrations/secret.js +30 -0
- package/dist/integrations/target.d.ts +49 -0
- package/dist/integrations/target.js +225 -0
- package/dist/integrations/vault.d.ts +12 -0
- package/dist/integrations/vault.js +104 -0
- package/dist/integrity/attach.d.ts +2 -0
- package/dist/integrity/attach.js +213 -0
- package/dist/integrity/check-threads.d.ts +10 -0
- package/dist/integrity/check-threads.js +153 -0
- package/dist/integrity/constraint.d.ts +16 -0
- package/dist/integrity/constraint.js +1105 -0
- package/dist/integrity/generators/compiler.d.ts +1 -0
- package/dist/integrity/generators/compiler.js +130 -0
- package/dist/integrity/generators/gate.d.ts +1 -0
- package/dist/integrity/generators/gate.js +172 -0
- package/dist/integrity/generators/issues.d.ts +1 -0
- package/dist/integrity/generators/issues.js +100 -0
- package/dist/integrity/generators/publish.d.ts +1 -0
- package/dist/integrity/generators/publish.js +186 -0
- package/dist/integrity/generators/rule.d.ts +2 -0
- package/dist/integrity/generators/rule.js +213 -0
- package/dist/integrity/generators/seal.d.ts +4 -0
- package/dist/integrity/generators/seal.js +101 -0
- package/dist/integrity/generators/trial.d.ts +39 -0
- package/dist/integrity/generators/trial.js +254 -0
- package/dist/integrity/lint.d.ts +50 -0
- package/dist/integrity/lint.js +161 -0
- package/dist/integrity/lsp/bin.d.ts +2 -0
- package/dist/integrity/lsp/bin.js +35 -0
- package/dist/integrity/lsp/diagnostics.d.ts +25 -0
- package/dist/integrity/lsp/diagnostics.js +57 -0
- package/dist/integrity/lsp/findings.d.ts +20 -0
- package/dist/integrity/lsp/findings.js +39 -0
- package/dist/integrity/lsp/lint.d.ts +7 -0
- package/dist/integrity/lsp/lint.js +95 -0
- package/dist/integrity/lsp/protocol.d.ts +20 -0
- package/dist/integrity/lsp/protocol.js +57 -0
- package/dist/integrity/lsp/server.d.ts +15 -0
- package/dist/integrity/lsp/server.js +171 -0
- package/dist/integrity/sealed.d.ts +1 -0
- package/dist/integrity/sealed.js +56 -0
- package/dist/integrity/slopnator.d.ts +90 -0
- package/dist/integrity/slopnator.js +267 -0
- package/dist/integrity/slopnators/sl_broken_lineage.d.ts +2 -0
- package/dist/integrity/slopnators/sl_broken_lineage.js +35 -0
- package/dist/integrity/slopnators/sl_declared_elsewhere.d.ts +2 -0
- package/dist/integrity/slopnators/sl_declared_elsewhere.js +33 -0
- package/dist/integrity/slopnators/sl_derived_drift.d.ts +8 -0
- package/dist/integrity/slopnators/sl_derived_drift.js +91 -0
- package/dist/integrity/slopnators/sl_hardcoded_style.d.ts +2 -0
- package/dist/integrity/slopnators/sl_hardcoded_style.js +46 -0
- package/dist/integrity/slopnators/sl_inert_island.d.ts +2 -0
- package/dist/integrity/slopnators/sl_inert_island.js +85 -0
- package/dist/integrity/slopnators/sl_inert_slopnator.d.ts +2 -0
- package/dist/integrity/slopnators/sl_inert_slopnator.js +76 -0
- package/dist/integrity/slopnators/sl_missing_row.d.ts +2 -0
- package/dist/integrity/slopnators/sl_missing_row.js +180 -0
- package/dist/integrity/slopnators/sl_no_barrel.d.ts +2 -0
- package/dist/integrity/slopnators/sl_no_barrel.js +41 -0
- package/dist/integrity/slopnators/sl_no_child_process.d.ts +2 -0
- package/dist/integrity/slopnators/sl_no_child_process.js +61 -0
- package/dist/integrity/slopnators/sl_no_comment.d.ts +2 -0
- package/dist/integrity/slopnators/sl_no_comment.js +41 -0
- package/dist/integrity/slopnators/sl_no_dispatch_bypass.d.ts +2 -0
- package/dist/integrity/slopnators/sl_no_dispatch_bypass.js +301 -0
- package/dist/integrity/slopnators/sl_no_import_cycle.d.ts +2 -0
- package/dist/integrity/slopnators/sl_no_import_cycle.js +42 -0
- package/dist/integrity/slopnators/sl_no_such_command.d.ts +2 -0
- package/dist/integrity/slopnators/sl_no_such_command.js +55 -0
- package/dist/integrity/slopnators/sl_only_takes_a_literal.d.ts +2 -0
- package/dist/integrity/slopnators/sl_only_takes_a_literal.js +59 -0
- package/dist/integrity/slopnators/sl_secret_literal.d.ts +2 -0
- package/dist/integrity/slopnators/sl_secret_literal.js +89 -0
- package/dist/integrity/slopnators/sl_stale_path.d.ts +2 -0
- package/dist/integrity/slopnators/sl_stale_path.js +92 -0
- package/dist/integrity/slopnators/sl_twice_meant.d.ts +2 -0
- package/dist/integrity/slopnators/sl_twice_meant.js +30 -0
- package/dist/integrity/slopnators/sl_twice_parented.d.ts +2 -0
- package/dist/integrity/slopnators/sl_twice_parented.js +34 -0
- package/dist/integrity/slopnators/sl_undeclared_crossing.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_crossing.js +78 -0
- package/dist/integrity/slopnators/sl_undeclared_destination.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_destination.js +56 -0
- package/dist/integrity/slopnators/sl_undeclared_element.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_element.js +34 -0
- package/dist/integrity/slopnators/sl_undeclared_node.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_node.js +59 -0
- package/dist/integrity/slopnators/sl_undeclared_operation.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_operation.js +120 -0
- package/dist/integrity/slopnators/sl_undeclared_variant.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_variant.js +117 -0
- package/dist/integrity/slopnators/sl_undescribed.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undescribed.js +54 -0
- package/dist/integrity/slopnators/sl_ungoverned_island.d.ts +2 -0
- package/dist/integrity/slopnators/sl_ungoverned_island.js +58 -0
- package/dist/integrity/slopnators/sl_unkept_rows.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unkept_rows.js +104 -0
- package/dist/integrity/slopnators/sl_unnamed_island.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unnamed_island.js +37 -0
- package/dist/integrity/slopnators/sl_unpaired_member.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unpaired_member.js +114 -0
- package/dist/integrity/slopnators/sl_unpaired_slopnator.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unpaired_slopnator.js +60 -0
- package/dist/integrity/slopnators/sl_unprefixed_member.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unprefixed_member.js +126 -0
- package/dist/integrity/slopnators/sl_unseamed_reach.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unseamed_reach.js +73 -0
- package/dist/integrity/slopnators/sl_unthreaded.d.ts +56 -0
- package/dist/integrity/slopnators/sl_unthreaded.js +352 -0
- package/dist/integrity/slopnators/sl_untranslated_key.d.ts +2 -0
- package/dist/integrity/slopnators/sl_untranslated_key.js +157 -0
- package/dist/integrity/slopnators/sl_unversioned.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unversioned.js +31 -0
- package/dist/integrity/sweep.d.ts +24 -0
- package/dist/integrity/sweep.js +264 -0
- package/dist/knowledge/attach.d.ts +2 -0
- package/dist/knowledge/attach.js +339 -0
- package/dist/knowledge/generators/docsgen.d.ts +1 -0
- package/dist/knowledge/generators/docsgen.js +145 -0
- package/dist/knowledge/generators/glossary.d.ts +1 -0
- package/dist/knowledge/generators/glossary.js +56 -0
- package/dist/knowledge/generators/skills.d.ts +1 -0
- package/dist/knowledge/generators/skills.js +222 -0
- package/dist/knowledge/glossary.d.ts +6 -0
- package/dist/knowledge/glossary.js +1 -0
- package/dist/operations/access.d.ts +27 -0
- package/dist/operations/access.js +61 -0
- package/dist/operations/attach.d.ts +6 -0
- package/dist/operations/attach.js +414 -0
- package/dist/operations/capabilities.d.ts +37 -0
- package/dist/operations/capabilities.js +35 -0
- package/dist/operations/cost.d.ts +14 -0
- package/dist/operations/cost.js +37 -0
- package/dist/operations/dispatch.d.ts +14 -0
- package/dist/operations/dispatch.js +327 -0
- package/dist/operations/error-results.d.ts +4 -0
- package/dist/operations/error-results.js +25 -0
- package/dist/operations/errors/catalog.d.ts +58 -0
- package/dist/operations/errors/catalog.js +91 -0
- package/dist/operations/errors/classes.d.ts +73 -0
- package/dist/operations/errors/classes.js +22 -0
- package/dist/operations/errors/errors.d.ts +52 -0
- package/dist/operations/errors/errors.js +257 -0
- package/dist/operations/generators/capability.d.ts +5 -0
- package/dist/operations/generators/capability.js +603 -0
- package/dist/operations/generators/compile.d.ts +2 -0
- package/dist/operations/generators/compile.js +541 -0
- package/dist/operations/generators/grant.d.ts +1 -0
- package/dist/operations/generators/grant.js +267 -0
- package/dist/operations/generators/operation.d.ts +1 -0
- package/dist/operations/generators/operation.js +811 -0
- package/dist/operations/generators/permits.d.ts +1 -0
- package/dist/operations/generators/permits.js +196 -0
- package/dist/operations/generators/seed.d.ts +20 -0
- package/dist/operations/generators/seed.js +285 -0
- package/dist/operations/generators/trial.d.ts +53 -0
- package/dist/operations/generators/trial.js +376 -0
- package/dist/operations/http.d.ts +30 -0
- package/dist/operations/http.js +121 -0
- package/dist/operations/makers/capability.d.ts +1 -0
- package/dist/operations/makers/capability.js +117 -0
- package/dist/operations/makers/trial.d.ts +1 -0
- package/dist/operations/makers/trial.js +413 -0
- package/dist/operations/memory.d.ts +104 -0
- package/dist/operations/memory.js +365 -0
- package/dist/operations/metering.d.ts +55 -0
- package/dist/operations/metering.js +247 -0
- package/dist/operations/nodes.d.ts +35 -0
- package/dist/operations/nodes.js +15 -0
- package/dist/operations/operations.d.ts +18 -0
- package/dist/operations/operations.js +5 -0
- package/dist/operations/queue-policy.d.ts +3 -0
- package/dist/operations/queue-policy.js +8 -0
- package/dist/operations/result.d.ts +19 -0
- package/dist/operations/result.js +10 -0
- package/dist/operations/seams.d.ts +239 -0
- package/dist/operations/seams.js +45 -0
- package/dist/operations/store.d.ts +14 -0
- package/dist/operations/store.js +1 -0
- package/package.json +16 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { named } from "../../core/scope.js";
|
|
3
|
+
export function sortedBy(items, key) {
|
|
4
|
+
return [...items].sort((a, b) => compare(key(a), key(b)));
|
|
5
|
+
}
|
|
6
|
+
export function compare(a, b) {
|
|
7
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
8
|
+
}
|
|
9
|
+
export function sortedEntries(record) {
|
|
10
|
+
return Object.keys(record)
|
|
11
|
+
.sort(compare)
|
|
12
|
+
.map((key) => [key, record[key]]);
|
|
13
|
+
}
|
|
14
|
+
const BANNER = `GENERATED by ${named('derivation')} — do not edit by hand.`;
|
|
15
|
+
const HASH_LABEL = 'codegen-hash:';
|
|
16
|
+
function headerSyntax(path) {
|
|
17
|
+
if (path.endsWith('.md')) {
|
|
18
|
+
return {
|
|
19
|
+
wrap: (text) => `<!-- ${text} -->`,
|
|
20
|
+
unwrap: (line) => line.startsWith('<!-- ') && line.endsWith(' -->') ? line.slice(5, -4) : null,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const prefix = path.endsWith('.sql') ? '--' : path.endsWith('.ts') || path.endsWith('.tsx') ? '//' : null;
|
|
24
|
+
if (prefix === null) {
|
|
25
|
+
throw new Error(`${named('derivation')} emits only .ts, .sql and .md, received "${path}". ` +
|
|
26
|
+
'A format with no comment syntax cannot carry a header, and an unheadered generated ' +
|
|
27
|
+
'file is one somebody edits by hand.');
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
wrap: (text) => `${prefix} ${text}`,
|
|
31
|
+
unwrap: (line) => (line.startsWith(`${prefix} `) ? line.slice(prefix.length + 1) : null),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function bodyHash(body) {
|
|
35
|
+
return createHash('sha256').update(body, 'utf8').digest('hex');
|
|
36
|
+
}
|
|
37
|
+
const HEADER_LINES = 5;
|
|
38
|
+
export function withHeader(path, source, body) {
|
|
39
|
+
const { wrap } = headerSyntax(path);
|
|
40
|
+
const normalized = body.endsWith('\n') ? body : `${body}\n`;
|
|
41
|
+
const header = [
|
|
42
|
+
wrap(BANNER),
|
|
43
|
+
wrap(`Source: ${source}`),
|
|
44
|
+
wrap('Edit the island and re-run `thread generate`. CI fails when this file is stale.'),
|
|
45
|
+
wrap(`${HASH_LABEL} ${bodyHash(normalized)}`),
|
|
46
|
+
'',
|
|
47
|
+
];
|
|
48
|
+
return { path, contents: `${header.join('\n')}\n${normalized}` };
|
|
49
|
+
}
|
|
50
|
+
export function parseGenerated(path, contents) {
|
|
51
|
+
let unwrap;
|
|
52
|
+
try {
|
|
53
|
+
unwrap = headerSyntax(path).unwrap;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const lines = contents.split('\n');
|
|
59
|
+
if (unwrap(lines[0] ?? '') !== BANNER)
|
|
60
|
+
return null;
|
|
61
|
+
const sourceLine = unwrap(lines[1] ?? '');
|
|
62
|
+
const hashLine = unwrap(lines[3] ?? '');
|
|
63
|
+
if (sourceLine === null || !sourceLine.startsWith('Source: '))
|
|
64
|
+
return null;
|
|
65
|
+
if (hashLine === null || !hashLine.startsWith(`${HASH_LABEL} `))
|
|
66
|
+
return null;
|
|
67
|
+
return {
|
|
68
|
+
source: sourceLine.slice('Source: '.length),
|
|
69
|
+
declaredHash: hashLine.slice(HASH_LABEL.length + 1),
|
|
70
|
+
body: lines.slice(HEADER_LINES).join('\n'),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function lines(...parts) {
|
|
74
|
+
return parts.flat().join('\n').replace(/[ \t]+$/gm, '');
|
|
75
|
+
}
|
|
76
|
+
export function tsString(value) {
|
|
77
|
+
return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n')}'`;
|
|
78
|
+
}
|
|
79
|
+
export function stableJson(value, indent = 0) {
|
|
80
|
+
const pad = ' '.repeat(indent);
|
|
81
|
+
const padInner = ' '.repeat(indent + 1);
|
|
82
|
+
if (value === null)
|
|
83
|
+
return 'null';
|
|
84
|
+
if (Array.isArray(value)) {
|
|
85
|
+
if (value.length === 0)
|
|
86
|
+
return '[]';
|
|
87
|
+
return `[\n${value.map((item) => `${padInner}${stableJson(item, indent + 1)}`).join(',\n')}\n${pad}]`;
|
|
88
|
+
}
|
|
89
|
+
if (typeof value === 'object') {
|
|
90
|
+
const keys = Object.keys(value).sort();
|
|
91
|
+
if (keys.length === 0)
|
|
92
|
+
return '{}';
|
|
93
|
+
return `{\n${keys
|
|
94
|
+
.map((key) => `${padInner}${JSON.stringify(key)}: ${stableJson(value[key], indent + 1)}`)
|
|
95
|
+
.join(',\n')}\n${pad}}`;
|
|
96
|
+
}
|
|
97
|
+
return JSON.stringify(value);
|
|
98
|
+
}
|
|
99
|
+
export function pascal(id) {
|
|
100
|
+
return id
|
|
101
|
+
.replace(/^(ent_|fld_|rel_|wf_|role_)/, '')
|
|
102
|
+
.split(/[_.]/)
|
|
103
|
+
.filter(Boolean)
|
|
104
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
105
|
+
.join('');
|
|
106
|
+
}
|
|
107
|
+
export function camel(id) {
|
|
108
|
+
const p = pascal(id);
|
|
109
|
+
return p.charAt(0).toLowerCase() + p.slice(1);
|
|
110
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { argv, stderr, stdout } from 'node:process';
|
|
12
|
+
import { pathToFileURL } from 'node:url';
|
|
13
|
+
import { marked, wordsIn } from "../../core/asking.js";
|
|
14
|
+
import { exit, invoked } from "../../core/invoked.js";
|
|
15
|
+
import { NAMED } from "../../core/scope.js";
|
|
16
|
+
import { findTree, inTree } from "../../core/tree.js";
|
|
17
|
+
import { CHECKED_BY, KINDS, LISTENS, SAID, WRITES, WRITTEN_BY, kindOf } from "../release.js";
|
|
18
|
+
const TREE = findTree();
|
|
19
|
+
const mark = (one) => marked([one.id ?? '', one.at ?? '']);
|
|
20
|
+
async function releases() {
|
|
21
|
+
const at = inTree(TREE, 'derivation.ts');
|
|
22
|
+
if (!existsSync(at))
|
|
23
|
+
return [];
|
|
24
|
+
try {
|
|
25
|
+
const held = (await import(__rewriteRelativeImportExtension(pathToFileURL(at).href)));
|
|
26
|
+
return held.DERIVATION?.derives ?? [];
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function named() {
|
|
33
|
+
const at = inTree(TREE, 'secrets.ts');
|
|
34
|
+
if (!existsSync(at))
|
|
35
|
+
return [];
|
|
36
|
+
try {
|
|
37
|
+
const held = (await import(__rewriteRelativeImportExtension(pathToFileURL(at).href)));
|
|
38
|
+
return (held.SECRETS ?? []).map((one) => one.id.toUpperCase()).sort();
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function scripted() {
|
|
45
|
+
try {
|
|
46
|
+
const held = JSON.parse(readFileSync(join(TREE.at, 'package.json'), 'utf8'));
|
|
47
|
+
const scripts = held.scripts ?? {};
|
|
48
|
+
return {
|
|
49
|
+
build: scripts['build'] === undefined ? 'npm run build' : 'npm run build',
|
|
50
|
+
start: scripts['start'] === undefined ? 'npm start' : 'npm start',
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return { build: 'npm run build', start: 'npm start' };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function dockerfile(build, start) {
|
|
58
|
+
return [
|
|
59
|
+
'# GENERATED by thread — do not edit. It is derived from what this app declares.',
|
|
60
|
+
'',
|
|
61
|
+
'FROM node:22-slim AS build',
|
|
62
|
+
'WORKDIR /app',
|
|
63
|
+
'COPY package.json package-lock.json* ./',
|
|
64
|
+
'RUN npm ci --omit=dev --ignore-scripts || npm install --omit=dev --ignore-scripts',
|
|
65
|
+
'COPY . .',
|
|
66
|
+
'RUN npm install --ignore-scripts',
|
|
67
|
+
`RUN ${build}`,
|
|
68
|
+
'',
|
|
69
|
+
'FROM node:22-slim',
|
|
70
|
+
'WORKDIR /app',
|
|
71
|
+
'ENV NODE_ENV=production',
|
|
72
|
+
'COPY --from=build /app ./',
|
|
73
|
+
`EXPOSE ${String(LISTENS)}`,
|
|
74
|
+
`CMD ${JSON.stringify(start.split(' '))}`,
|
|
75
|
+
'',
|
|
76
|
+
].join('\n');
|
|
77
|
+
}
|
|
78
|
+
function compose(id, secrets) {
|
|
79
|
+
return [
|
|
80
|
+
'# GENERATED by thread — do not edit. It is derived from what this app declares.',
|
|
81
|
+
'',
|
|
82
|
+
'services:',
|
|
83
|
+
` ${id}:`,
|
|
84
|
+
' build: .',
|
|
85
|
+
' ports:',
|
|
86
|
+
` - "${String(LISTENS)}:${String(LISTENS)}"`,
|
|
87
|
+
...(secrets.length === 0
|
|
88
|
+
? []
|
|
89
|
+
: [
|
|
90
|
+
' environment:',
|
|
91
|
+
...secrets.map((one) => ` ${one}: \${${one}}`),
|
|
92
|
+
]),
|
|
93
|
+
'',
|
|
94
|
+
].join('\n');
|
|
95
|
+
}
|
|
96
|
+
async function made() {
|
|
97
|
+
const found = new Map();
|
|
98
|
+
const rows = await releases();
|
|
99
|
+
const secrets = await named();
|
|
100
|
+
const { build, start } = scripted();
|
|
101
|
+
for (const one of rows) {
|
|
102
|
+
const id = one.id;
|
|
103
|
+
if (id === undefined)
|
|
104
|
+
continue;
|
|
105
|
+
const kind = kindOf(id);
|
|
106
|
+
if (kind === undefined)
|
|
107
|
+
continue;
|
|
108
|
+
if (kind === 'docker') {
|
|
109
|
+
found.set('Dockerfile', dockerfile(build, start));
|
|
110
|
+
found.set('compose.yaml', compose(id, secrets));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return found;
|
|
114
|
+
}
|
|
115
|
+
const SHIPPED = NAMED;
|
|
116
|
+
async function run() {
|
|
117
|
+
const rows = await releases();
|
|
118
|
+
if (rows.length === 0) {
|
|
119
|
+
stdout.write(`release: this app declares no release, so there is nowhere for it to go yet. Declare one with ` +
|
|
120
|
+
`\`thread create release\` — it is ${KINDS.join(' or ')}, and the prefix on its id says which.\n`);
|
|
121
|
+
return 0;
|
|
122
|
+
}
|
|
123
|
+
const words = wordsIn(['read']);
|
|
124
|
+
if (words[0] === 'read') {
|
|
125
|
+
const which = words[1];
|
|
126
|
+
const one = rows.find((row) => mark(row) === which);
|
|
127
|
+
if (one === undefined) {
|
|
128
|
+
stderr.write(`release: \`${which ?? '(nothing)'}\` is not a mark this tree issued. This app declares ` +
|
|
129
|
+
`${rows.map((row) => `[${mark(row)}] ${String(row.id)}`).join(', ')} — read one of those, or run ` +
|
|
130
|
+
'`thread release` to write it, or `thread create release` to declare another.\n');
|
|
131
|
+
return 1;
|
|
132
|
+
}
|
|
133
|
+
const kind = kindOf(one.id ?? '');
|
|
134
|
+
stdout.write(`${String(one.id)} — ${kind === undefined ? 'a release of a kind this generator does not recognise' : SAID[kind]}\n`);
|
|
135
|
+
for (const file of kind === undefined ? [] : WRITES[kind]) {
|
|
136
|
+
stdout.write(`\n${file}\n`);
|
|
137
|
+
try {
|
|
138
|
+
stdout.write(readFileSync(join(TREE.at, file), 'utf8'));
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
stdout.write('(nothing is there)\n');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
const files = await made();
|
|
147
|
+
const checking = argv.includes('--check');
|
|
148
|
+
for (const [at, text] of files) {
|
|
149
|
+
if (text.includes(SHIPPED)) {
|
|
150
|
+
stderr.write(`release: ${at} names a \`${SHIPPED}\` module, and what a host runs may hold nothing of this ` +
|
|
151
|
+
'framework. It writes the format that host already reads and ships none of itself. Nothing was written.\n');
|
|
152
|
+
return 1;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (checking) {
|
|
156
|
+
for (const [at, text] of files) {
|
|
157
|
+
let current = '';
|
|
158
|
+
try {
|
|
159
|
+
current = readFileSync(join(TREE.at, at), 'utf8');
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
current = '';
|
|
163
|
+
}
|
|
164
|
+
if (current === text)
|
|
165
|
+
continue;
|
|
166
|
+
stderr.write(current === ''
|
|
167
|
+
? `release: ${at} is declared and not on disk. Run \`${WRITTEN_BY}\` and commit what it writes.\n`
|
|
168
|
+
: `release: ${at} is not what the declarations produce. A derived file that differs from its ` +
|
|
169
|
+
`generator is a hand edit wearing a generated name. Run \`${WRITTEN_BY}\` and commit what it writes.\n`);
|
|
170
|
+
return 1;
|
|
171
|
+
}
|
|
172
|
+
stdout.write(`release: ${[...files.keys()].join(', ')} ${files.size === 1 ? 'is' : 'are'} what the declarations produce\n`);
|
|
173
|
+
return 0;
|
|
174
|
+
}
|
|
175
|
+
for (const [at, text] of files)
|
|
176
|
+
writeFileSync(join(TREE.at, at), text, 'utf8');
|
|
177
|
+
stdout.write(`release: wrote ${[...files.keys()].join(', ')} — built and started by what this app's own manifest ` +
|
|
178
|
+
`declares, listening on ${String(LISTENS)}, naming ${String((await named()).length)} secret(s) and holding none of them.\n`);
|
|
179
|
+
return 0;
|
|
180
|
+
}
|
|
181
|
+
if (invoked(import.meta.url))
|
|
182
|
+
exit(await run());
|
|
183
|
+
export { CHECKED_BY, WRITES, made };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import { argv, exit, stdout } from 'node:process';
|
|
11
|
+
import { pathToFileURL } from 'node:url';
|
|
12
|
+
import { RECEIPT, answer, answering, asking, given, guidance, issue, named, only, prompted, refuse, slug, spend, wordsIn } from "../../core/asking.js";
|
|
13
|
+
import { listBounds, withRow } from "../../core/rows.js";
|
|
14
|
+
import { named as packageOf } from "../../core/scope.js";
|
|
15
|
+
import { findTree, inTree } from "../../core/tree.js";
|
|
16
|
+
import { CHECKED_BY, KINDS, PREFIXES, SAID, WRITES, WRITTEN_BY } from "../release.js";
|
|
17
|
+
const TREE = findTree();
|
|
18
|
+
const SHEET = 'derivation.ts';
|
|
19
|
+
const KEBAB = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
20
|
+
const NAME = { at: 'name', guide: 'lowercase words joined by dashes' };
|
|
21
|
+
const KIND = { at: 'kind', guide: `one of ${KINDS.join(', ')} — the prefix on the id says which` };
|
|
22
|
+
const ASKS = [NAME, KIND];
|
|
23
|
+
const at = inTree(TREE, SHEET);
|
|
24
|
+
if (!existsSync(at)) {
|
|
25
|
+
refuse(`there is no ${SHEET} here, so a release has nowhere to go. Run \`npx thread attach ${packageOf('derivation')}\` — ` +
|
|
26
|
+
"the sheet is the package's to write, not yours.");
|
|
27
|
+
}
|
|
28
|
+
const held = (await import(__rewriteRelativeImportExtension(pathToFileURL(at).href)));
|
|
29
|
+
const already = held.DERIVATION?.derives ?? [];
|
|
30
|
+
const listing = [
|
|
31
|
+
already.length === 0
|
|
32
|
+
? 'no releases yet, so this app declares nowhere to go.'
|
|
33
|
+
: `${String(already.length)} release(s):`,
|
|
34
|
+
...already.map((one) => ` ${String(one.id)} — writes ${String(one.at)} — ${String(one.describes)}`),
|
|
35
|
+
'',
|
|
36
|
+
'what a release is:',
|
|
37
|
+
'',
|
|
38
|
+
...KINDS.map((one) => ` ${one} → ${PREFIXES[one]}<name>, writing ${WRITES[one].join(' and ')}`),
|
|
39
|
+
...KINDS.flatMap((one) => ['', ` ${SAID[one]}`]),
|
|
40
|
+
].join('\n');
|
|
41
|
+
const words = wordsIn(['new']);
|
|
42
|
+
const piped = given();
|
|
43
|
+
only(piped, named(ASKS), 'a release');
|
|
44
|
+
const { ask, shut } = asking(piped);
|
|
45
|
+
const nonce = issue(TREE.at, listing);
|
|
46
|
+
if (words[0] !== 'new') {
|
|
47
|
+
stdout.write(`${listing}\n\n`);
|
|
48
|
+
stdout.write(` npx thread create release new <name> ${RECEIPT} ${nonce}\n\n`);
|
|
49
|
+
stdout.write(`${answering('release').join('\n')}\n\n`);
|
|
50
|
+
stdout.write(`${guidance(ASKS).join('\n')}\n\n`);
|
|
51
|
+
stdout.write(` ${RECEIPT} is spent by one run and refused if ${SHEET} changed after it was issued — what is\n` +
|
|
52
|
+
' listed above is what you are writing against, and handing one back says you read it.\n');
|
|
53
|
+
exit(1);
|
|
54
|
+
}
|
|
55
|
+
spend(TREE.at, listing, argv[argv.indexOf(RECEIPT) + 1], SHEET);
|
|
56
|
+
const name = slug(piped.length > 0 ? (answer(piped, NAME.at) ?? '') : await ask(NAME.at, prompted(NAME)));
|
|
57
|
+
if (name === '' || !KEBAB.test(name)) {
|
|
58
|
+
shut();
|
|
59
|
+
refuse(`"${name}" cannot be the name of a release — it is written in kebab-case, so main or blue-green.`);
|
|
60
|
+
}
|
|
61
|
+
const said = piped.length > 0 ? (answer(piped, KIND.at) ?? '') : await ask(KIND.at, prompted(KIND));
|
|
62
|
+
const kind = KINDS.find((one) => one === said);
|
|
63
|
+
if (kind === undefined) {
|
|
64
|
+
shut();
|
|
65
|
+
refuse(`"${said}" is not something this can release to. It is one of ${KINDS.join(', ')}.`);
|
|
66
|
+
}
|
|
67
|
+
shut();
|
|
68
|
+
const id = `${PREFIXES[kind]}${name.replace(/-/g, '_')}`;
|
|
69
|
+
if (already.some((one) => one.id === id))
|
|
70
|
+
refuse(`\`${id}\` is already declared in ${SHEET}.`);
|
|
71
|
+
const text = readFileSync(at, 'utf8');
|
|
72
|
+
const bounds = listBounds(text, 'derives');
|
|
73
|
+
if (bounds === undefined) {
|
|
74
|
+
refuse(`${SHEET} states no \`derives\` list on its node, so this app was attached before releases existed. ` +
|
|
75
|
+
`Run \`npx thread attach ${packageOf('derivation')}\` again — it adds the list without touching what is in the sheet.`);
|
|
76
|
+
}
|
|
77
|
+
const [first, ...rest] = WRITES[kind];
|
|
78
|
+
const row = [
|
|
79
|
+
' {',
|
|
80
|
+
` id: '${id}',`,
|
|
81
|
+
` at: '${String(first)}',`,
|
|
82
|
+
` describes: '${SAID[kind].replace(/\\/g, '\\\\').replace(/'/g, "\\'")}',`,
|
|
83
|
+
' version: 1,',
|
|
84
|
+
` by: '${WRITTEN_BY}',`,
|
|
85
|
+
` runs: '${CHECKED_BY}',`,
|
|
86
|
+
...(rest.length === 0
|
|
87
|
+
? []
|
|
88
|
+
: [
|
|
89
|
+
' derives: [',
|
|
90
|
+
...rest.flatMap((one) => [
|
|
91
|
+
' {',
|
|
92
|
+
` id: '${id}_${one.split('.')[0] ?? one}',`,
|
|
93
|
+
` at: '${one}',`,
|
|
94
|
+
` describes: 'What runs the image this release builds, named beside it because one row states one address.',`,
|
|
95
|
+
' version: 1,',
|
|
96
|
+
` by: '${WRITTEN_BY}',`,
|
|
97
|
+
` runs: '${CHECKED_BY}',`,
|
|
98
|
+
' },',
|
|
99
|
+
]),
|
|
100
|
+
' ],',
|
|
101
|
+
]),
|
|
102
|
+
' },',
|
|
103
|
+
].join('\n');
|
|
104
|
+
writeFileSync(at, withRow(text, bounds, row), 'utf8');
|
|
105
|
+
stdout.write(`create: declared ${id} in ${SHEET} — it writes ${WRITES[kind].join(' and ')}.\n`);
|
|
106
|
+
stdout.write(`create: run \`${WRITTEN_BY}\`, and the gate checks it from here on without a line written for it.\n`);
|
|
107
|
+
exit(0);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const PREFIXES: {
|
|
2
|
+
readonly docker: "dk_";
|
|
3
|
+
};
|
|
4
|
+
export declare const KINDS: readonly ["docker"];
|
|
5
|
+
export declare const WRITES: Readonly<Record<(typeof KINDS)[number], readonly string[]>>;
|
|
6
|
+
export declare const LISTENS = 3000;
|
|
7
|
+
export declare const SAID: Readonly<Record<(typeof KINDS)[number], string>>;
|
|
8
|
+
export declare const kindOf: (id: string) => (typeof KINDS)[number] | undefined;
|
|
9
|
+
export declare const WRITTEN_BY = "thread release";
|
|
10
|
+
export declare const CHECKED_BY = "thread release --check";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const PREFIXES = { docker: 'dk_' };
|
|
2
|
+
export const KINDS = ['docker'];
|
|
3
|
+
export const WRITES = {
|
|
4
|
+
docker: ['Dockerfile', 'compose.yaml'],
|
|
5
|
+
};
|
|
6
|
+
export const LISTENS = 3000;
|
|
7
|
+
export const SAID = {
|
|
8
|
+
docker: 'A container image built from this app and the compose file that runs it. It is the one that needs no account and no network to be proven, so it is what an app can be taken end to end with before anybody has chosen where it really lives. What it holds is derived: the build and start commands this app already declares, the port its own server listens on, and the name of every secret it says it must be given — the names only, because a value is typed by a person and a row has never held one.',
|
|
9
|
+
};
|
|
10
|
+
export const kindOf = (id) => KINDS.find((one) => id.startsWith(PREFIXES[one]));
|
|
11
|
+
export const WRITTEN_BY = 'thread release';
|
|
12
|
+
export const CHECKED_BY = 'thread release --check';
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { named } from "../core/scope.js";
|
|
2
|
+
const VERSION = 1;
|
|
3
|
+
const DESCRIBES = 'The system that states every place this app reaches and every credential it reaches with. A target is a destination and the prefix on its id is how it is reached; what it accepts is entities if it keeps them, calls if it is called over https, and the methods or subcommands it answers to if it is an npm package or a program; what it requires is secrets, and where the reach has an address one of them is it. Nothing else in this app names a host, a connection string or an environment variable, so an operation says which place it needs and the rest is worked out from the declaration.';
|
|
4
|
+
const TARGETS = 'Where this app states the places it reaches and what each one accepts. Declare one with `thread create target`, which asks how it is reached and puts the matching prefix on its id — `pg_` for a postgres over the network, `pl_` for one in this process, `zu_` for a store in the browser, `ht_` for something called over https, `sdk_` for an npm package this app installs and calls, `cli_` for a program it runs at a command line — and writes what it accepts and the secrets reaching it needs. A place that keeps things accepts entities; a place that is called accepts the calls declared beside it; a package or a program says in `uses` what it is and in `offers` what it responds to, and takes nothing, because a method name belongs to something outside this tree and `takes` is a demand this tree has to be able to meet. This is the one place the split between what reaches a server and what stays local is written down.';
|
|
5
|
+
const SECRETS = 'Where this app states every value it must be given and may not hold. A row here is a name and a sentence and never a value: `sec_` for one only a server may ever hold, `pub_` for one that may reach a browser, and that prefix is the whole of what decides where the target requiring it can be reached from. A secret with a `sends` goes into a header on every call; the one without is the address of the target that requires it.';
|
|
6
|
+
const VAULT = 'What opens a secret\'s value at the moment of use, written once and then yours. It reads the names off _threads/secrets.ts and opens the values sealed beside the tree, refusing any other name; add a secret with `thread create secret`, which is the only thing that ever takes a value. To open values from somewhere else in production, replace what opens them here.';
|
|
7
|
+
const SECRET = 'A value this app must be given and may never hold — a key, a token, a connection string. The row names it and says what it is for, and there is no field on it for the value, because a row is read by everything and a credential is read by one thing at one moment. The prefix says who may ever hold it: `sec_` a server and nothing else, `pub_` anybody including whoever opens the page. That one letter is what refuses a server credential its way into a browser bundle, so it is on the id rather than in a field somebody could forget.';
|
|
8
|
+
const CALL = 'One thing a target can be asked to do — the verb and the path, named so that a node names the call and never writes a url. It is a row rather than a field because it is a thing with a name and a sentence like everything else here, and because what a vendor offers is a list that grows: an app reaches one more endpoint by declaring one more of these, not by editing a body.';
|
|
9
|
+
const REACH = 'How a target is got at — over a network to a postgres, inside this process, in the browser\'s own memory, over https, as an npm package this app installs and calls directly, or as a program run at a command line. It is the prefix on the target\'s id and never a field, because how a thing is reached decides everything else that may be said about it: what it accepts, whether it can be reached from a page at all, whether anything has to say where it is, and whether anything has to arrive on the machine before it can be reached at all. A field beside the id would be a second answer to a question the id already answers.';
|
|
10
|
+
export const CONCEPTS = [
|
|
11
|
+
{ id: 'secret', describes: SECRET },
|
|
12
|
+
{ id: 'call', describes: CALL },
|
|
13
|
+
{ id: 'reach', describes: REACH },
|
|
14
|
+
];
|
|
15
|
+
import { quoted } from "../core/rows.js";
|
|
16
|
+
const concept = (one) => [
|
|
17
|
+
' {',
|
|
18
|
+
` id: '${one.id}',`,
|
|
19
|
+
" at: '_threads/concepts.ts',",
|
|
20
|
+
` describes: ${quoted(one.describes)},`,
|
|
21
|
+
` version: ${String(VERSION)},`,
|
|
22
|
+
' cites: [',
|
|
23
|
+
" { on: '2026-08-29', said: 'SSOT for the integrations, as a whole meta inside thread core' },",
|
|
24
|
+
" { on: '2026-08-29', said: 'seams declare BOTH target AND secrets' },",
|
|
25
|
+
' ],',
|
|
26
|
+
' },',
|
|
27
|
+
].join('\n');
|
|
28
|
+
const INTEGRATIONS_SOURCE = `import type { Threaded } from '../core/thread.ts'
|
|
29
|
+
|
|
30
|
+
export const INTEGRATIONS: Threaded = {
|
|
31
|
+
id: 'integrations',
|
|
32
|
+
at: '_threads/integrations.ts',
|
|
33
|
+
describes: ${quoted(DESCRIBES)},
|
|
34
|
+
version: ${String(VERSION)},
|
|
35
|
+
declares: [
|
|
36
|
+
{
|
|
37
|
+
id: 'targets',
|
|
38
|
+
at: '_threads/targets.ts',
|
|
39
|
+
describes: ${quoted(TARGETS)},
|
|
40
|
+
version: ${String(VERSION)},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 'secrets',
|
|
44
|
+
at: '_threads/secrets.ts',
|
|
45
|
+
describes: ${quoted(SECRETS)},
|
|
46
|
+
version: ${String(VERSION)},
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
}
|
|
50
|
+
`;
|
|
51
|
+
const TARGETS_SOURCE = `import type { Call, Target } from './target.ts'
|
|
52
|
+
|
|
53
|
+
export const TARGETS: readonly Target[] = []
|
|
54
|
+
|
|
55
|
+
export const CALLS: readonly Call[] = []
|
|
56
|
+
`;
|
|
57
|
+
const SECRETS_SOURCE = `import type { Secret } from './secret.ts'
|
|
58
|
+
|
|
59
|
+
export const SECRETS: readonly Secret[] = []
|
|
60
|
+
`;
|
|
61
|
+
const OWN = [
|
|
62
|
+
...CONCEPTS.map((one) => ({
|
|
63
|
+
into: 'concepts.ts',
|
|
64
|
+
list: 'CONCEPTS',
|
|
65
|
+
id: one.id,
|
|
66
|
+
source: concept(one),
|
|
67
|
+
})),
|
|
68
|
+
{
|
|
69
|
+
into: 'integrations.ts',
|
|
70
|
+
list: 'declares',
|
|
71
|
+
id: 'vault',
|
|
72
|
+
source: [
|
|
73
|
+
' {',
|
|
74
|
+
" id: 'vault',",
|
|
75
|
+
" at: 'src/vault.ts',",
|
|
76
|
+
` describes: ${quoted(VAULT)},`,
|
|
77
|
+
` version: ${String(VERSION)},`,
|
|
78
|
+
" by: 'thread vault',",
|
|
79
|
+
" runs: 'thread vault --check',",
|
|
80
|
+
' },',
|
|
81
|
+
].join('\n'),
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
const SEEDED = {
|
|
85
|
+
attaches: ['@prestack/thread/derivation/attach'],
|
|
86
|
+
id: 'integrations',
|
|
87
|
+
declares: [
|
|
88
|
+
' {',
|
|
89
|
+
' id: \'integrations\',',
|
|
90
|
+
' at: \'_threads/integrations.ts\',',
|
|
91
|
+
' describes: \'The system that states every place this app reaches and every credential it reaches with. A target is a destination and the prefix on its id is how it is reached; what it accepts is entities if it keeps them, calls if it is called over https, and the methods or subcommands it answers to if it is an npm package or a program; what it requires is secrets, and where the reach has an address one of them is it. Nothing else in this app names a host, a connection string or an environment variable, so an operation says which place it needs and the rest is worked out from the declaration.\',',
|
|
92
|
+
' version: 1,',
|
|
93
|
+
' },',
|
|
94
|
+
].join('\n'),
|
|
95
|
+
};
|
|
96
|
+
const SHIPPED = [
|
|
97
|
+
{
|
|
98
|
+
into: 'derivation.ts',
|
|
99
|
+
list: 'GENERATORS',
|
|
100
|
+
id: 'target-generator',
|
|
101
|
+
source: " {\n id: 'target-generator',\n at: 'node_modules/@prestack/thread/dist/integrations/generators/target.js',\n describes: 'The command that declares a place this app reaches. Asked for nothing it lists every target already here with what it accepts and what it requires, every way a target may be reached and what each way can hold, and every secret this app declares. It then asks what the place is called and how it is reached, puts the prefix on the id, asks which secret is its address and which go into a header, and writes the row — so a target arrives declared with its credential named rather than with an environment variable typed into whatever needed it.',\n version: 1,\n by: 'thread create target',\n },",
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
into: 'derivation.ts',
|
|
105
|
+
list: 'GENERATORS',
|
|
106
|
+
id: 'secret-generator',
|
|
107
|
+
source: " {\n id: 'secret-generator',\n at: 'node_modules/@prestack/thread/dist/integrations/generators/secret.js',\n describes: 'The command that declares a value this app must be given, and the only thing that ever takes one. It asks what the secret is for and who may hold it, puts `sec_` or `pub_` on the id accordingly, and writes the row. Then it asks a person for the value, with the terminal\\'s echo off, and seals it into the store beside the tree under a key it generates and never prints. The value goes from the keyboard into the sealed store and nowhere else: not into the row, not into the output, not into whatever transcript is being kept of the session that ran it.',\n version: 1,\n by: 'thread create secret',\n },",
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
into: 'derivation.ts',
|
|
111
|
+
list: 'GENERATORS',
|
|
112
|
+
id: 'vault-generator',
|
|
113
|
+
source: " {\n id: 'vault-generator',\n at: 'node_modules/@prestack/thread/dist/integrations/generators/vault.js',\n describes: 'The writer of the file that opens this app\\'s sealed store. It reads the secrets this app declares and writes one function that resolves them by name, refusing any name no row declares — so a value can be asked for only by something that declared it needed it, and a secret taken off the tree stops resolving rather than lingering as a name that still works.',\n version: 1,\n by: 'thread vault',\n runs: 'thread vault --check',\n },",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
into: 'derivation.ts',
|
|
117
|
+
list: 'GENERATORS',
|
|
118
|
+
id: 'inspect-generator',
|
|
119
|
+
source: " {\n id: 'inspect-generator',\n at: 'node_modules/@prestack/thread/dist/integrations/generators/read.js',\n describes: 'The one command for reading what this app declares. Asked for nothing it prints every sheet and what each holds; asked for a word it prints every row whose id or sentence carries it. It reads the tree rather than the files, which is the whole reason it exists: a row is a nested TypeScript literal and a search over lines answers questions about text while the questions people actually ask are about rows.',\n version: 1,\n by: 'thread inspect',\n },",
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
into: 'derivation.ts',
|
|
123
|
+
list: 'GENERATORS',
|
|
124
|
+
id: 'call-noun',
|
|
125
|
+
source: " {\n id: 'call-noun',\n at: 'node_modules/@prestack/thread/dist/integrations/generators/target.js',\n describes: 'The command that makes an already-declared call happen. `thread run call <id>` asks for whatever its path leaves to be filled in — `{like_this}` — by name, and for a call that writes (POST, PUT, PATCH) a JSON body, then resolves the target\\'s address and every credential it requires through the vault at the moment of the call and never before. Nothing here ever names a vendor: a call already states its target, its method and its path, and which one runs is read off its own id, exactly as `thread run trial` reads an operation\\'s.',\n version: 1,\n by: 'thread run call',\n },",
|
|
126
|
+
},
|
|
127
|
+
];
|
|
128
|
+
export const ATTACH = {
|
|
129
|
+
attaches: SEEDED.attaches,
|
|
130
|
+
id: SEEDED.id,
|
|
131
|
+
declares: SEEDED.declares,
|
|
132
|
+
writes: [
|
|
133
|
+
{ file: 'integrations.ts', source: INTEGRATIONS_SOURCE },
|
|
134
|
+
{ file: 'targets.ts', source: TARGETS_SOURCE },
|
|
135
|
+
{ file: 'secrets.ts', source: SECRETS_SOURCE },
|
|
136
|
+
],
|
|
137
|
+
rows: [...OWN, ...SHIPPED],
|
|
138
|
+
retires: [
|
|
139
|
+
{
|
|
140
|
+
into: 'derivation.ts',
|
|
141
|
+
list: 'GENERATORS',
|
|
142
|
+
id: 'read-generator',
|
|
143
|
+
why: 'renamed to `inspect-generator`. `thread read` is a verb of the CLI itself now — it reads FILES, and this package reads ROWS, which is `thread inspect`. Both were called read, and only one of them could answer.',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
into: 'integrations.ts',
|
|
147
|
+
list: 'declares',
|
|
148
|
+
id: 'trace',
|
|
149
|
+
made: `src/integrations.trace.${'jsonl'}`,
|
|
150
|
+
why: 'the record of what went out through an integration is gone. It sealed every line against the one before it, and a chain nobody reads guards nothing. What an integration did is what the tool itself reports, at the moment it runs',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
into: 'derivation.ts',
|
|
154
|
+
list: 'GENERATORS',
|
|
155
|
+
id: 'trace-generator',
|
|
156
|
+
why: 'the record it wrote is gone, so there is nothing for it to make and nothing for `--check` to read',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
into: 'derivation.ts',
|
|
160
|
+
list: 'GENERATORS',
|
|
161
|
+
id: 'sdk-generator',
|
|
162
|
+
why: '`sdk` is a way a target is reached now, not a noun beside one — `thread create target` declares an SDK with ' +
|
|
163
|
+
'`--reach sdk`, which installs it, reads what it answers to, and writes one row instead of three. The noun ' +
|
|
164
|
+
'also raised the collision guard against any app declaring a meta called `sdk`, which made every other ' +
|
|
165
|
+
'withdrawal this package ships undeliverable to the trees that needed them.',
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
into: 'concepts.ts',
|
|
169
|
+
list: 'CONCEPTS',
|
|
170
|
+
id: 'sdk',
|
|
171
|
+
why: '`sdk` is no longer a noun beside a target — it is one of the ways a target is reached, and its meaning is ' +
|
|
172
|
+
`now \`SAID.sdk\` in \`${named('integrations')}/target\`, beside \`https\` and every other reach, none of which is ` +
|
|
173
|
+
'coined here either. It also collided with an app declaring a meta called `sdk`, which coins the word: two ' +
|
|
174
|
+
'meanings for one word is what `twice-meant` refuses, and it was holding the glossary and the docs red.',
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|