create-substrat 0.6.1 → 0.6.3
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/index.js
CHANGED
|
@@ -21,16 +21,23 @@ import { DEV_SERVERS } from './dev-servers.js';
|
|
|
21
21
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
22
22
|
const TEMPLATE = join(HERE, 'template');
|
|
23
23
|
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
// Substrat is 0.x, so these are caret ranges on the current minor — a caret on 0.x
|
|
25
|
+
// locks the minor, which is what makes a stale pin here a scaffold four minors behind
|
|
26
|
+
// rather than a scaffold that quietly upgrades.
|
|
27
|
+
//
|
|
28
|
+
// EMITTED, NOT EDITED. `pnpm lint:pins` writes this block from each package's own
|
|
29
|
+
// version and `--check` fails CI on drift; `version-packages` runs the write, so a
|
|
30
|
+
// bump and its pins land in the same PR. Hand-maintained, they went stale twice: once
|
|
31
|
+
// resolving nothing (`^0.3.37` after workorder moved to 0.4.x), and once resolving
|
|
32
|
+
// fine but freezing the template against packages nobody ships, which is worse — the
|
|
33
|
+
// scaffold's own tests stayed green while the surface moved underneath them.
|
|
34
|
+
//
|
|
35
|
+
// The runtime packages release together off one version line (the changesets `fixed`
|
|
36
|
+
// group), so one constant is right for all of them. Engines do NOT share a line —
|
|
37
|
+
// each versions on its own, so one pin per engine, deliberately.
|
|
38
|
+
const SUBSTRAT = '^0.76.0';
|
|
39
|
+
const ENGINE_WORKORDER = '^0.6.3';
|
|
40
|
+
const ENGINE_INVOICING = '^0.7.3';
|
|
34
41
|
const BOUNDARY_LINT = '^0.0.7';
|
|
35
42
|
|
|
36
43
|
const DOCS = 'https://substrat.net';
|
package/package.json
CHANGED
|
@@ -19,8 +19,13 @@
|
|
|
19
19
|
* This file lives in `.substrat/` — the tool-neutral home, next to `playbook.md` —
|
|
20
20
|
* not in `.claude/`. `.claude/settings.json` is a three-line adapter that runs it,
|
|
21
21
|
* and any other client that grows a session hook binds the same way. It also
|
|
22
|
-
* means the plugin distribution (#753)
|
|
23
|
-
* forking it.
|
|
22
|
+
* means the plugin distribution (#753) ships this script unchanged rather than
|
|
23
|
+
* forking it: `plugin/substrat/scripts/session-start.mjs` is emitted from this
|
|
24
|
+
* file byte-for-byte, and `pnpm lint:plugin --check` fails if the two diverge.
|
|
25
|
+
*
|
|
26
|
+
* The plugin copy is what reaches a project scaffolded before this hook existed.
|
|
27
|
+
* When a project owns its own copy, the plugin's stays silent — see
|
|
28
|
+
* `isPluginCopy()` below — so a scaffolded project announces itself once.
|
|
24
29
|
*
|
|
25
30
|
* ## Deliberately silent, and deliberately offline
|
|
26
31
|
*
|
|
@@ -33,8 +38,9 @@
|
|
|
33
38
|
*
|
|
34
39
|
* Opt out by creating `.substrat/no-session-context`.
|
|
35
40
|
*/
|
|
36
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
37
|
-
import { dirname, join } from 'node:path';
|
|
41
|
+
import { existsSync, readFileSync, realpathSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
42
|
+
import { dirname, join, relative, isAbsolute } from 'node:path';
|
|
43
|
+
import { fileURLToPath } from 'node:url';
|
|
38
44
|
|
|
39
45
|
const DOCS = 'https://substrat.net';
|
|
40
46
|
const KERNEL = '@substrat-run/kernel';
|
|
@@ -42,6 +48,33 @@ const KERNEL = '@substrat-run/kernel';
|
|
|
42
48
|
/** Where the project is. Claude Code sets this; fall back to the cwd it ran us in. */
|
|
43
49
|
const root = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
44
50
|
|
|
51
|
+
/** Where this script is. The two copies differ only in where they sit on disk. */
|
|
52
|
+
const self = fileURLToPath(import.meta.url);
|
|
53
|
+
|
|
54
|
+
/** Symlinks resolved, so a project reached through one is not mistaken for elsewhere. */
|
|
55
|
+
function real(path) {
|
|
56
|
+
try {
|
|
57
|
+
return realpathSync(path);
|
|
58
|
+
} catch {
|
|
59
|
+
return path;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* True when we are the plugin's copy rather than the project's own.
|
|
65
|
+
*
|
|
66
|
+
* Asked positionally instead of by a flag, because the two copies must stay
|
|
67
|
+
* byte-identical for `lint:plugin` to have anything to check: a copy that is
|
|
68
|
+
* invoked differently is a copy that can be edited differently. Both sides are
|
|
69
|
+
* resolved first — on macOS a project under `/tmp` is handed to us as
|
|
70
|
+
* `/private/tmp`, and an unresolved compare would read the project's own copy as
|
|
71
|
+
* foreign and silence the very hook that should speak.
|
|
72
|
+
*/
|
|
73
|
+
function isPluginCopy() {
|
|
74
|
+
const inside = relative(real(root), real(self));
|
|
75
|
+
return inside.startsWith('..') || isAbsolute(inside);
|
|
76
|
+
}
|
|
77
|
+
|
|
45
78
|
/** The marker: which kernel version this project last announced. */
|
|
46
79
|
const MARKER = join(root, '.substrat', '.docs-pin');
|
|
47
80
|
const OPT_OUT = join(root, '.substrat', 'no-session-context');
|
|
@@ -67,6 +100,11 @@ function main() {
|
|
|
67
100
|
// file to invent, and nothing to keep in sync.
|
|
68
101
|
if (!pkg?.substrat) silent();
|
|
69
102
|
|
|
103
|
+
// The project's own copy wins. It ships beside the playbook it points at, so it
|
|
104
|
+
// is the one that matches what is actually checked out here; the plugin's exists
|
|
105
|
+
// for projects scaffolded before this hook did, and must not double-announce.
|
|
106
|
+
if (isPluginCopy() && existsSync(join(root, '.substrat', 'hooks', 'session-start.mjs'))) silent();
|
|
107
|
+
|
|
70
108
|
if (existsSync(OPT_OUT)) silent();
|
|
71
109
|
|
|
72
110
|
// What is actually installed beats what package.json asked for: a caret range on
|
package/template/src/worker.ts
CHANGED
|
@@ -164,8 +164,15 @@ mountPlatformSurface<Env>(app, {
|
|
|
164
164
|
hostFor,
|
|
165
165
|
roles: ROLES,
|
|
166
166
|
ownerRoleKey: OWNER_ROLE_KEY,
|
|
167
|
-
|
|
168
|
-
|
|
167
|
+
// Both hooks are `Promise<void>`: the roster's own count is its business, and
|
|
168
|
+
// returning it here would make the platform's response shape depend on what a
|
|
169
|
+
// vertical happens to hand back. Await, discard.
|
|
170
|
+
onProvision: async (env, b) => {
|
|
171
|
+
await sweeper(env).noteScope(b.tenantId, b.scopeId);
|
|
172
|
+
},
|
|
173
|
+
onDeleteScope: async (env, s) => {
|
|
174
|
+
await sweeper(env).forgetScope(s);
|
|
175
|
+
},
|
|
169
176
|
});
|
|
170
177
|
|
|
171
178
|
// Unmatched /api/* fails as JSON; everything else gets a pointer, not a UI —
|
|
@@ -160,14 +160,18 @@ describe('bike-shop scenario', () => {
|
|
|
160
160
|
expect(underlag[0]!.status).toBe('open');
|
|
161
161
|
expect(underlag[0]!.total).toBe('336.5');
|
|
162
162
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
163
|
+
// Provenance is two-level: `document_*` is which delivery produced the line
|
|
164
|
+
// (this work order), `source_*` is what the line itself is (time vs material).
|
|
165
|
+
// Both come from the engine's own event payload — the vertical never joins
|
|
166
|
+
// into invoicing's tables to find out.
|
|
167
|
+
const detail = await greta.invoke<
|
|
168
|
+
{ lines: { document_id: string; document_type: string; source_type: string | null }[] }
|
|
169
|
+
>('invoicing/get', { underlagId: underlag[0]!.id });
|
|
167
170
|
expect(detail.lines).toHaveLength(2);
|
|
168
|
-
expect(
|
|
169
|
-
|
|
170
|
-
);
|
|
171
|
+
expect(
|
|
172
|
+
detail.lines.every((l) => l.document_type === 'workorder' && l.document_id === repairId),
|
|
173
|
+
).toBe(true);
|
|
174
|
+
expect(detail.lines.map((l) => l.source_type).sort()).toEqual(['material', 'time']);
|
|
171
175
|
});
|
|
172
176
|
|
|
173
177
|
it('7. portal isolation: Lisbeth sees her repair, Otto sees nothing', async () => {
|