stitchkit 0.81.0 → 0.82.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 +6552 -0
- package/README.md +4 -0
- package/dist/internal/upgrade-plan.d.ts +18 -0
- package/dist/internal/upgrade-plan.d.ts.map +1 -0
- package/dist/release/socket.d.ts +14 -1
- package/dist/release/socket.d.ts.map +1 -1
- package/dist/tracking/contract.d.ts +11 -0
- package/dist/tracking/contract.d.ts.map +1 -1
- package/dist/tracking-server.d.ts +1 -0
- package/dist/tracking-server.d.ts.map +1 -1
- package/dist/tracking.js +1 -0
- package/dist/upgrade-cli.d.ts +27 -0
- package/dist/upgrade-cli.d.ts.map +1 -0
- package/dist/upgrade-cli.js +177 -0
- package/llms-full.txt +73 -10
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -506,6 +506,10 @@ The full guide and API reference, in [`docs/`](./docs/README.md):
|
|
|
506
506
|
[multi-tenant](./docs/guide/multi-tenant.md) ·
|
|
507
507
|
[upgrading](./docs/guide/upgrading.md)
|
|
508
508
|
- **[API reference](./docs/api/reference.md)** — every export, by entrypoint.
|
|
509
|
+
- **Moving to a newer version?** Run `bunx stitchkit@latest upgrade` inside your
|
|
510
|
+
project. It reads the version you have from `node_modules` and the changelog
|
|
511
|
+
from inside the package, and prints every breaking change between the two —
|
|
512
|
+
oldest first, each with who must act. Nothing to clone, nobody to ask.
|
|
509
513
|
- **Coding agent?** The package ships **`llms.txt`** (a curated index) and
|
|
510
514
|
**`llms-full.txt`** (the whole guide inlined) — your agent reads them from
|
|
511
515
|
`node_modules/stitchkit/`. For Claude Code, the repo also provides a
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The consumer upgrade plan: every `### ⚠️ Breaking changes` section an
|
|
3
|
+
* installed → target range crosses, oldest first.
|
|
4
|
+
*
|
|
5
|
+
* Pure over the changelog text. The reader is `stitchkit upgrade`, the binary
|
|
6
|
+
* this package installs; the changelog it reads ships in the package, so a
|
|
7
|
+
* consumer recovers the plan without cloning the repository or being told the
|
|
8
|
+
* range by whoever cut the release.
|
|
9
|
+
*/
|
|
10
|
+
export interface UpgradeBreakingChange {
|
|
11
|
+
version: string;
|
|
12
|
+
whoMustAct: string;
|
|
13
|
+
markdown: string;
|
|
14
|
+
}
|
|
15
|
+
/** Extract every breaking section crossed by an exact `from` (exclusive) → `to` (inclusive) upgrade. */
|
|
16
|
+
export declare function planUpgrade(changelog: string, from: string, to: string): UpgradeBreakingChange[];
|
|
17
|
+
export declare function renderUpgradePlan(changes: readonly UpgradeBreakingChange[], from: string, to: string): string;
|
|
18
|
+
//# sourceMappingURL=upgrade-plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upgrade-plan.d.ts","sourceRoot":"","sources":["../../src/internal/upgrade-plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAsBD,wGAAwG;AACxG,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACT,qBAAqB,EAAE,CAgCzB;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,SAAS,qBAAqB,EAAE,EACzC,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACT,MAAM,CASR"}
|
package/dist/release/socket.d.ts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import type { ReleaseMarker } from './marker.js';
|
|
2
2
|
import type { ReleaseWatcher } from './watcher.js';
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* The little of a Socket.IO server this binding uses — structural, no peer
|
|
5
|
+
* import.
|
|
6
|
+
*
|
|
7
|
+
* `emit` is declared over `string` because the event name is configurable. A
|
|
8
|
+
* **typed** `Server<ClientToServer, ServerToClient>` narrows its own `emit` to
|
|
9
|
+
* the names in its map, so it satisfies this only once that map declares the
|
|
10
|
+
* event — `release: (payload: { buildId: string | null }) => void`, or whatever
|
|
11
|
+
* `options.event` renames it to. Until then the compiler reports a structural
|
|
12
|
+
* mismatch between two large socket.io types, which reads like a defect in this
|
|
13
|
+
* binding and is a missing line in the application's event map. Adding it is
|
|
14
|
+
* step 3 of the guide's adoption list for exactly this reason: the same
|
|
15
|
+
* declaration is what lets the application's own `on` know the event.
|
|
16
|
+
*/
|
|
4
17
|
export interface ReleaseSocketServer {
|
|
5
18
|
on(event: 'connection', handler: (socket: ReleaseSocketEmitter) => void): unknown;
|
|
6
19
|
emit(event: string, payload: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../../src/release/socket.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD
|
|
1
|
+
{"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../../src/release/socket.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,mBAAmB;IAClC,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,GAAG,OAAO,CAAC;IAClF,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC;CACnE;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC;CACnE;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC;IACnF,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC;CACtF;AAED,MAAM,WAAW,oBAAoB;IACnC,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,mBAAmB,EACvB,MAAM,EAAE,aAAa,EACrB,OAAO,GAAE,oBAAoB,GAAG;IAAE,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAAO,GACrE,MAAM,IAAI,CAcZ;AAED,qEAAqE;AACrE,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,qBAAqB,EAC7B,OAAO,EAAE,cAAc,EACvB,OAAO,GAAE,oBAAyB,GACjC,MAAM,IAAI,CAQZ"}
|
|
@@ -27,6 +27,7 @@ export type TrackingContractEndpoints<TSchemas extends TrackingSchemas<z.ZodObje
|
|
|
27
27
|
track: {
|
|
28
28
|
method: 'POST';
|
|
29
29
|
path: '/events';
|
|
30
|
+
expose: readonly ['HTTP'];
|
|
30
31
|
desc: string;
|
|
31
32
|
input: TSchemas['request'];
|
|
32
33
|
output: TSchemas['response'];
|
|
@@ -40,6 +41,16 @@ export type TrackingContractEndpoints<TSchemas extends TrackingSchemas<z.ZodObje
|
|
|
40
41
|
* `sendUnloadBeacon` with a string body — the only body a document that is
|
|
41
42
|
* being unloaded can deliver to another origin (ADR 0165) — and the server
|
|
42
43
|
* therefore needs an explicit `cors.origin` allow-list for it to arrive.
|
|
44
|
+
*
|
|
45
|
+
* **Both endpoints are HTTP-only, and that is not a formality.** An endpoint
|
|
46
|
+
* with no `expose` is a tool on MCP and AGENT by default, and this contract is
|
|
47
|
+
* built here rather than by the application's own contract factory — so a
|
|
48
|
+
* project that sets `toolExposure: 'explicit'` for everything it authors was
|
|
49
|
+
* still handed a `track` tool it never asked for. An agent has nothing to gain
|
|
50
|
+
* from a browser-event ingest and one thing to lose by having it: writes into
|
|
51
|
+
* the application's visitor data, under its own name, indistinguishable
|
|
52
|
+
* afterwards from a real visitor's. `bootstrap` said `['HTTP']` from the start;
|
|
53
|
+
* `track` not saying it was an omission, not a decision.
|
|
43
54
|
*/
|
|
44
55
|
export declare function createTrackingContract<TType extends string, TScope extends string>(config: TrackingContractConfig<TType, undefined, TScope>): ContractDef<TrackingContractEndpoints<TrackingSchemas<z.ZodObject<TrackingEventShape<TType>>>>, TScope>;
|
|
45
56
|
export declare function createTrackingContract<TType extends string, TExtras extends z.ZodRawShape, TScope extends string>(config: TrackingContractConfig<TType, TExtras, TScope>): ContractDef<TrackingContractEndpoints<TrackingSchemas<z.ZodObject<TrackingEventShape<TType> & TExtras>>>, TScope>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../src/tracking/contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EAAE,KAAK,WAAW,EAAkB,MAAM,aAAa,CAAC;AAC/D,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC3B,MAAM,WAAW,CAAC;AAEnB,MAAM,WAAW,sBAAsB,CACrC,KAAK,SAAS,MAAM,EACpB,OAAO,SAAS,CAAC,CAAC,WAAW,GAAG,SAAS,EACzC,MAAM,SAAS,MAAM,CACrB,SAAQ,qBAAqB,CAAC,KAAK,EAAE,OAAO,CAAC;IAC7C,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,yFAAyF;AACzF,MAAM,MAAM,yBAAyB,CAAC,QAAQ,SAAS,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI;IACrF,SAAS,EAAE;QACT,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,QAAQ,CAAC;QACf,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;QAC1B,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;QACzB,MAAM,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;KAC/B,CAAC;IACF,KAAK,EAAE;QACL,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,SAAS,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC3B,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC7B,cAAc,EAAE,IAAI,CAAC;QACrB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC;CACH,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../src/tracking/contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EAAE,KAAK,WAAW,EAAkB,MAAM,aAAa,CAAC;AAC/D,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC3B,MAAM,WAAW,CAAC;AAEnB,MAAM,WAAW,sBAAsB,CACrC,KAAK,SAAS,MAAM,EACpB,OAAO,SAAS,CAAC,CAAC,WAAW,GAAG,SAAS,EACzC,MAAM,SAAS,MAAM,CACrB,SAAQ,qBAAqB,CAAC,KAAK,EAAE,OAAO,CAAC;IAC7C,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,yFAAyF;AACzF,MAAM,MAAM,yBAAyB,CAAC,QAAQ,SAAS,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI;IACrF,SAAS,EAAE;QACT,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,QAAQ,CAAC;QACf,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;QAC1B,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;QACzB,MAAM,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;KAC/B,CAAC;IACF,KAAK,EAAE;QACL,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,SAAS,CAAC;QAChB,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;QAC1B,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC3B,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC7B,cAAc,EAAE,IAAI,CAAC;QACrB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC;CACH,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,SAAS,MAAM,EAAE,MAAM,SAAS,MAAM,EAChF,MAAM,EAAE,sBAAsB,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,GACvD,WAAW,CACZ,yBAAyB,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAClF,MAAM,CACP,CAAC;AACF,wBAAgB,sBAAsB,CACpC,KAAK,SAAS,MAAM,EACpB,OAAO,SAAS,CAAC,CAAC,WAAW,EAC7B,MAAM,SAAS,MAAM,EAErB,MAAM,EAAE,sBAAsB,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,GACrD,WAAW,CACZ,yBAAyB,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EAC5F,MAAM,CACP,CAAC"}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* back; `issueVisitLease` runs the visit algorithm over a store interface the
|
|
6
6
|
* application implements. No database, no schema, no domain. → ADR 0166.
|
|
7
7
|
*/
|
|
8
|
+
export type { TrackingDisposition, VisitBootstrapResponse, VisitEntryContext, } from './tracking/schemas.js';
|
|
8
9
|
export { type ActiveIntervalOptions, type ActiveTimeInterval, activeIntervalOf, } from './tracking/server/active-interval.js';
|
|
9
10
|
export { DEFAULT_BOT_USER_AGENT_PATTERN, isBotUserAgent } from './tracking/server/bot.js';
|
|
10
11
|
export { type DispositionEvent, type DispositionInput, type DispositionResult, dispositionTrackingBatch, type KnownVisit, } from './tracking/server/disposition.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracking-server.d.ts","sourceRoot":"","sources":["../src/tracking-server.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"tracking-server.d.ts","sourceRoot":"","sources":["../src/tracking-server.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAUH,YAAY,EACV,mBAAmB,EACnB,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,gBAAgB,GACjB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,8BAA8B,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvF,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,wBAAwB,EACxB,KAAK,UAAU,GAChB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EACL,sBAAsB,EACtB,KAAK,aAAa,EAClB,KAAK,gBAAgB,GACtB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACpB,MAAM,+BAA+B,CAAC"}
|
package/dist/tracking.js
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export interface UpgradeCliResult {
|
|
3
|
+
/** What to write to stdout. */
|
|
4
|
+
readonly output: string;
|
|
5
|
+
/** Process exit code — 0 when the plan (or "nothing to do") was produced. */
|
|
6
|
+
readonly code: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The whole command as a function of its arguments and the filesystem, so the
|
|
10
|
+
* gate can exercise the paths that matter — a project with no stitchkit
|
|
11
|
+
* installed, a range that runs backwards, a changelog the package did not ship.
|
|
12
|
+
*/
|
|
13
|
+
export declare function runUpgradeCli(argv: readonly string[]): UpgradeCliResult;
|
|
14
|
+
/**
|
|
15
|
+
* Was this file executed as a program, rather than imported?
|
|
16
|
+
*
|
|
17
|
+
* Through the real install path that is a **symlink** question, not a string
|
|
18
|
+
* one: npm and bun link `node_modules/.bin/stitchkit` at the built file, Node
|
|
19
|
+
* keeps the link in `argv[1]` and resolves `import.meta.url` to its target. A
|
|
20
|
+
* direct comparison of the two is therefore false for every consumer, and the
|
|
21
|
+
* binary becomes a silent no-op — exit 0, no output, nothing to notice — while
|
|
22
|
+
* running the same file in place works perfectly. Both arguments are passed in
|
|
23
|
+
* so this is decidable without launching a process: under Bun `argv[1]` is
|
|
24
|
+
* already resolved, so a spawned symlink proves nothing about Node.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isDirectInvocation(invoked: string | undefined, self: string): boolean;
|
|
27
|
+
//# sourceMappingURL=upgrade-cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upgrade-cli.d.ts","sourceRoot":"","sources":["../src/upgrade-cli.ts"],"names":[],"mappings":";AA4DA,MAAM,WAAW,gBAAgB;IAC/B,+BAA+B;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,gBAAgB,CAqCvE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CASrF"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/upgrade-cli.ts
|
|
4
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
5
|
+
import { dirname, join, resolve } from "node:path";
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
|
+
|
|
8
|
+
// src/internal/upgrade-plan.ts
|
|
9
|
+
function semverParts(version) {
|
|
10
|
+
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version);
|
|
11
|
+
if (!match)
|
|
12
|
+
throw new Error(`Expected an exact semver, received "${version}"`);
|
|
13
|
+
const [, major, minor, patch] = match;
|
|
14
|
+
if (major === undefined || minor === undefined || patch === undefined) {
|
|
15
|
+
throw new Error(`Expected an exact semver, received "${version}"`);
|
|
16
|
+
}
|
|
17
|
+
return [Number(major), Number(minor), Number(patch)];
|
|
18
|
+
}
|
|
19
|
+
function compareSemver(left, right) {
|
|
20
|
+
const a = semverParts(left);
|
|
21
|
+
const b = semverParts(right);
|
|
22
|
+
for (const index of [0, 1, 2]) {
|
|
23
|
+
const difference = a[index] - b[index];
|
|
24
|
+
if (difference !== 0)
|
|
25
|
+
return difference;
|
|
26
|
+
}
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
function planUpgrade(changelog, from, to) {
|
|
30
|
+
semverParts(from);
|
|
31
|
+
semverParts(to);
|
|
32
|
+
if (compareSemver(from, to) >= 0) {
|
|
33
|
+
throw new Error(`Upgrade range must increase: ${from} → ${to}`);
|
|
34
|
+
}
|
|
35
|
+
const releases = [...changelog.matchAll(/^## \[(\d+\.\d+\.\d+)\].*$/gm)];
|
|
36
|
+
const changes = [];
|
|
37
|
+
for (const [index, release] of releases.entries()) {
|
|
38
|
+
const version = release[1];
|
|
39
|
+
if (version === undefined)
|
|
40
|
+
continue;
|
|
41
|
+
if (compareSemver(version, from) <= 0 || compareSemver(version, to) > 0)
|
|
42
|
+
continue;
|
|
43
|
+
const bodyStart = (release.index ?? 0) + release[0].length;
|
|
44
|
+
const bodyEnd = releases[index + 1]?.index ?? changelog.length;
|
|
45
|
+
const body = changelog.slice(bodyStart, bodyEnd);
|
|
46
|
+
const breakingHeader = /^### ⚠️ Breaking changes\s*$/m.exec(body);
|
|
47
|
+
if (!breakingHeader)
|
|
48
|
+
continue;
|
|
49
|
+
const breakingStart = (breakingHeader.index ?? 0) + breakingHeader[0].length;
|
|
50
|
+
const afterHeader = body.slice(breakingStart);
|
|
51
|
+
const nextSection = /^### /m.exec(afterHeader);
|
|
52
|
+
const markdown = afterHeader.slice(0, nextSection?.index ?? afterHeader.length).trim();
|
|
53
|
+
const who = /^\*\*Who must act:\*\*\s*([\s\S]*?)(?=\n\s*\n|\n[-*] )/m.exec(markdown);
|
|
54
|
+
changes.push({
|
|
55
|
+
version,
|
|
56
|
+
whoMustAct: who?.[1]?.replace(/\s+/g, " ").trim() ?? "Not declared in this legacy changelog section.",
|
|
57
|
+
markdown
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return changes.sort((left, right) => compareSemver(left.version, right.version));
|
|
61
|
+
}
|
|
62
|
+
function renderUpgradePlan(changes, from, to) {
|
|
63
|
+
const header = `# Stitchkit upgrade ${from} → ${to}`;
|
|
64
|
+
if (changes.length === 0)
|
|
65
|
+
return `${header}
|
|
66
|
+
|
|
67
|
+
No breaking sections in this range.
|
|
68
|
+
`;
|
|
69
|
+
return `${header}
|
|
70
|
+
|
|
71
|
+
${changes.map((change) => `## ${change.version}
|
|
72
|
+
|
|
73
|
+
**Who must act:** ${change.whoMustAct}
|
|
74
|
+
|
|
75
|
+
${change.markdown.replace(/^\*\*Who must act:\*\*[\s\S]*?(?=\n\s*\n|\n[-*] )/m, "").trim()}`).join(`
|
|
76
|
+
|
|
77
|
+
`)}
|
|
78
|
+
`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/upgrade-cli.ts
|
|
82
|
+
var USAGE = `stitchkit upgrade — print every breaking change between the installed version and this one
|
|
83
|
+
|
|
84
|
+
bunx stitchkit@latest upgrade in a project that depends on stitchkit
|
|
85
|
+
npx stitchkit@latest upgrade
|
|
86
|
+
|
|
87
|
+
Options
|
|
88
|
+
--from <version> installed version (default: the stitchkit in ./node_modules)
|
|
89
|
+
--to <version> target version (default: the version of this package)
|
|
90
|
+
--cwd <dir> project to read the installed version from (default: .)
|
|
91
|
+
--changelog <path> changelog to plan from (default: the one in this package)
|
|
92
|
+
`;
|
|
93
|
+
function option(argv, name) {
|
|
94
|
+
const index = argv.indexOf(`--${name}`);
|
|
95
|
+
if (index < 0)
|
|
96
|
+
return;
|
|
97
|
+
const value = argv[index + 1];
|
|
98
|
+
if (value === undefined || value.startsWith("--")) {
|
|
99
|
+
throw new Error(`--${name} needs a value`);
|
|
100
|
+
}
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
function packageRoot() {
|
|
104
|
+
return resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
105
|
+
}
|
|
106
|
+
function versionOf(packageJsonPath) {
|
|
107
|
+
let text;
|
|
108
|
+
try {
|
|
109
|
+
text = readFileSync(packageJsonPath, "utf8");
|
|
110
|
+
} catch {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const parsed = JSON.parse(text);
|
|
114
|
+
if (typeof parsed !== "object" || parsed === null || !("version" in parsed))
|
|
115
|
+
return;
|
|
116
|
+
const { version } = parsed;
|
|
117
|
+
return typeof version === "string" ? version : undefined;
|
|
118
|
+
}
|
|
119
|
+
function runUpgradeCli(argv) {
|
|
120
|
+
if (argv.includes("--help") || argv.includes("-h") || argv.length === 0) {
|
|
121
|
+
return { output: USAGE, code: 0 };
|
|
122
|
+
}
|
|
123
|
+
const [command] = argv;
|
|
124
|
+
if (command !== "upgrade") {
|
|
125
|
+
return { output: `Unknown command "${command}".
|
|
126
|
+
|
|
127
|
+
${USAGE}`, code: 1 };
|
|
128
|
+
}
|
|
129
|
+
const root = packageRoot();
|
|
130
|
+
const to = option(argv, "to") ?? versionOf(join(root, "package.json"));
|
|
131
|
+
if (to === undefined) {
|
|
132
|
+
throw new Error(`Cannot read the version of the stitchkit at ${root}`);
|
|
133
|
+
}
|
|
134
|
+
const cwd = resolve(option(argv, "cwd") ?? process.cwd());
|
|
135
|
+
const from = option(argv, "from") ?? versionOf(join(cwd, "node_modules", "stitchkit", "package.json"));
|
|
136
|
+
if (from === undefined) {
|
|
137
|
+
throw new Error(`No stitchkit found in ${join(cwd, "node_modules")} — run this in the project that depends on it, or pass --from <installed version>`);
|
|
138
|
+
}
|
|
139
|
+
if (from === to) {
|
|
140
|
+
return { output: `Already on ${to}. Nothing to upgrade.
|
|
141
|
+
`, code: 0 };
|
|
142
|
+
}
|
|
143
|
+
const changelogPath = option(argv, "changelog") ?? join(root, "CHANGELOG.md");
|
|
144
|
+
let changelog;
|
|
145
|
+
try {
|
|
146
|
+
changelog = readFileSync(changelogPath, "utf8");
|
|
147
|
+
} catch {
|
|
148
|
+
throw new Error(`Cannot read the changelog at ${changelogPath}`);
|
|
149
|
+
}
|
|
150
|
+
return { output: renderUpgradePlan(planUpgrade(changelog, from, to), from, to), code: 0 };
|
|
151
|
+
}
|
|
152
|
+
function isDirectInvocation(invoked, self) {
|
|
153
|
+
if (invoked === undefined)
|
|
154
|
+
return false;
|
|
155
|
+
if (invoked === self || pathToFileURL(invoked).href === pathToFileURL(self).href)
|
|
156
|
+
return true;
|
|
157
|
+
try {
|
|
158
|
+
return realpathSync(invoked) === realpathSync(self);
|
|
159
|
+
} catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (isDirectInvocation(process.argv[1], fileURLToPath(import.meta.url))) {
|
|
164
|
+
try {
|
|
165
|
+
const { output, code } = runUpgradeCli(process.argv.slice(2));
|
|
166
|
+
process.stdout.write(output);
|
|
167
|
+
process.exit(code);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
170
|
+
`);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
export {
|
|
175
|
+
runUpgradeCli,
|
|
176
|
+
isDirectInvocation
|
|
177
|
+
};
|
package/llms-full.txt
CHANGED
|
@@ -67,7 +67,7 @@ own, recorded as an ADR.
|
|
|
67
67
|
| `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, plus the store and managed-resource conformance kits |
|
|
68
68
|
| `stitchkit/declaration` | browser + build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
|
|
69
69
|
| `stitchkit/react` | browser + server rendering | stable | `createCursorQuery`, `createCacheBridge`, QueryClient and `ApiError` retry policy |
|
|
70
|
-
| `stitchkit/agent-runtime` | server | evolving<br>_redefined in 11 of the
|
|
70
|
+
| `stitchkit/agent-runtime` | server | evolving<br>_redefined in 11 of the 27 minors since 0.56.2, most recently 0.75.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
|
|
71
71
|
| `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
|
|
72
72
|
| `stitchkit/agent-runtime/coding-tools` | server (Bun or Node) | evolving | bounded host-authorized direct file and shell tools; a root boundary, not an OS sandbox |
|
|
73
73
|
| `stitchkit/agent-runtime/openrouter` | server | evolving | isolated OpenRouter language-model adapter |
|
|
@@ -75,7 +75,7 @@ own, recorded as an ADR.
|
|
|
75
75
|
| `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
|
|
76
76
|
| `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
|
|
77
77
|
| `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
|
|
78
|
-
| `stitchkit/application` | browser + server | evolving<br>_redefined in 6 of the
|
|
78
|
+
| `stitchkit/application` | browser + server | evolving<br>_redefined in 6 of the 27 minors since 0.56.2, most recently 0.79.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
|
|
79
79
|
| `stitchkit/application/grammy` | server | evolving | isolated grammY polling and webhook lifecycle adapters |
|
|
80
80
|
| `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
|
|
81
81
|
| `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
|
|
@@ -350,13 +350,42 @@ records the verdict and reloads nothing.
|
|
|
350
350
|
|
|
351
351
|
## Adopting it
|
|
352
352
|
|
|
353
|
-
1. Bake the build id into the bundle
|
|
354
|
-
|
|
355
|
-
|
|
353
|
+
1. Bake the build id into the bundle, from **one** source. Without it the
|
|
354
|
+
watcher has no `own` and never reloads — and it never says so, which is the
|
|
355
|
+
part worth spending a paragraph on.
|
|
356
|
+
|
|
357
|
+
`NEXT_PUBLIC_BUILD_ID=$(git rev-parse --short HEAD)` in the build command is
|
|
358
|
+
the short version and it has a failure mode: on an immutable-release layout
|
|
359
|
+
that variable usually lives in a static environment file, so it is easy for
|
|
360
|
+
two releases to ship the same id. Then `own` equals what the server reports
|
|
361
|
+
on every response, the watcher is correct to stay quiet, and nothing
|
|
362
|
+
anywhere is red. A reload that never happens looks exactly like a reload
|
|
363
|
+
that was not needed.
|
|
364
|
+
|
|
365
|
+
So mint it once and let one value reach all three readers. In Next, that is
|
|
366
|
+
`next.config`:
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
const buildId = process.env.BUILD_ID ?? execSync('git rev-parse --short HEAD').toString().trim()
|
|
370
|
+
|
|
371
|
+
export default {
|
|
372
|
+
generateBuildId: () => buildId, // → .next/BUILD_ID, which the server marker reads
|
|
373
|
+
env: { NEXT_PUBLIC_BUILD_ID: buildId }, // → the bundle, which becomes `own`
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
The bundle's id and the file the server reports now come from the same
|
|
378
|
+
expression evaluated once, so they cannot drift apart per release. Any
|
|
379
|
+
arrangement with that property will do; the one to avoid is two places that
|
|
380
|
+
each decide the id and are expected to agree.
|
|
356
381
|
2. Point `read` at the id of the **active** release, not the process's cwd.
|
|
357
382
|
3. Add `release: (data: { buildId: string | null }) => void` to your
|
|
358
383
|
`ServerToClientEvents` map where you type your socket, so your own `on`
|
|
359
|
-
knows the event the binding emits.
|
|
384
|
+
knows the event the binding emits. This is also what makes a **typed**
|
|
385
|
+
`Server<…>` fit `bindReleaseToSocketServer`: its `emit` is narrowed to the
|
|
386
|
+
names in your map, so until `release` is one of them the call fails to
|
|
387
|
+
typecheck — as a long structural mismatch, which reads like a bug in the
|
|
388
|
+
binding and is this line instead.
|
|
360
389
|
4. Send `SIGUSR2` from the deploy step that activates a frontend without
|
|
361
390
|
restarting the backend, and name that step in the project's release steps.
|
|
362
391
|
|
|
@@ -10900,10 +10929,19 @@ A release that breaks a public API leads its `CHANGELOG.md` entry with a
|
|
|
10900
10929
|
**`### ⚠️ Breaking changes`** section (exact heading), each item carrying a
|
|
10901
10930
|
**before → after** snippet. A version with **no** such section is **purely
|
|
10902
10931
|
additive** — adopting it changes nothing in your code. (See
|
|
10903
|
-
[`AGENTS.md` → Breaking changes](../../AGENTS.md).)
|
|
10904
|
-
|
|
10905
|
-
|
|
10906
|
-
|
|
10932
|
+
[`AGENTS.md` → Breaking changes](../../AGENTS.md).) So the list is recoverable
|
|
10933
|
+
mechanically, and the package recovers it for you:
|
|
10934
|
+
|
|
10935
|
+
```bash
|
|
10936
|
+
bunx stitchkit@latest upgrade # or: npx stitchkit@latest upgrade
|
|
10937
|
+
```
|
|
10938
|
+
|
|
10939
|
+
Run it inside the project that depends on stitchkit. It reads your installed
|
|
10940
|
+
version out of `node_modules`, reads the changelog **shipped inside the
|
|
10941
|
+
package**, and prints every breaking section your range crosses, oldest first,
|
|
10942
|
+
each with its **Who must act** line — the list this guide's migrations expand
|
|
10943
|
+
on. Nothing to clone, nothing to be told: `--from` / `--to` override either end
|
|
10944
|
+
of the range if you want a different one.
|
|
10907
10945
|
|
|
10908
10946
|
So upgrading is: read the `### ⚠️ Breaking changes` of every version *above* your
|
|
10909
10947
|
current one *up to* your target, and apply each snippet.
|
|
@@ -10932,6 +10970,31 @@ makes one thing your job rather than the resolver's:
|
|
|
10932
10970
|
The mechanical part is identical either way. Only the *noticing* differs, and an
|
|
10933
10971
|
exact pin moves it onto you.
|
|
10934
10972
|
|
|
10973
|
+
## Released migration: 0.82.0
|
|
10974
|
+
|
|
10975
|
+
One thing, and only if you mount MCP or agent tools from a `createTrackingContract` contract.
|
|
10976
|
+
|
|
10977
|
+
```bash
|
|
10978
|
+
rg -n "createTrackingContract"
|
|
10979
|
+
```
|
|
10980
|
+
|
|
10981
|
+
The event ingest (`track`, `POST /events`) is now `expose: ['HTTP']`, which is
|
|
10982
|
+
what its sibling `bootstrap` always declared. Before, it declared nothing — and
|
|
10983
|
+
an endpoint with no `expose` is a tool on MCP and AGENT by default, so the
|
|
10984
|
+
mounted surface carried a `track_<prefix>` tool. Since this contract is built
|
|
10985
|
+
inside the framework, an application that had made every tool opt-in for the
|
|
10986
|
+
endpoints *it* authors could not have opted this one out.
|
|
10987
|
+
|
|
10988
|
+
**If you pin a tool-surface digest or count**, it moves by one; re-record it.
|
|
10989
|
+
**If you assert on tool names**, `track_<prefix>` is gone. **If nothing in your
|
|
10990
|
+
project mounts tools from this contract**, there is nothing to do — the HTTP
|
|
10991
|
+
routes, the client and the schemas are unchanged.
|
|
10992
|
+
|
|
10993
|
+
Un-declaring the tool was never the intent, so there is no option to keep it. If
|
|
10994
|
+
an agent in your system genuinely needs to record something, give it your own
|
|
10995
|
+
endpoint with your own schema: the browser ingest trusts a client-minted event
|
|
10996
|
+
id and a visit lease, and an agent holds neither.
|
|
10997
|
+
|
|
10935
10998
|
## Released migration: 0.81.0
|
|
10936
10999
|
|
|
10937
11000
|
**Path literals own string params.**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stitchkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.82.0",
|
|
4
4
|
"description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -34,6 +34,9 @@
|
|
|
34
34
|
"bun": ">=1.2.0",
|
|
35
35
|
"node": ">=22"
|
|
36
36
|
},
|
|
37
|
+
"bin": {
|
|
38
|
+
"stitchkit": "./dist/upgrade-cli.js"
|
|
39
|
+
},
|
|
37
40
|
"main": "./dist/index.js",
|
|
38
41
|
"types": "./dist/index.d.ts",
|
|
39
42
|
"files": [
|
|
@@ -42,6 +45,7 @@
|
|
|
42
45
|
"llms.txt",
|
|
43
46
|
"llms-full.txt",
|
|
44
47
|
"README.md",
|
|
48
|
+
"CHANGELOG.md",
|
|
45
49
|
"LICENSE"
|
|
46
50
|
],
|
|
47
51
|
"exports": {
|
|
@@ -186,7 +190,7 @@
|
|
|
186
190
|
"check": "bun x tsc --noEmit",
|
|
187
191
|
"build:js": "bun run build:entrypoints && bun scripts/preserve-webpack-ignore.mjs",
|
|
188
192
|
"build:types": "bun x tsc -p tsconfig.build.json --emitDeclarationOnly && bun scripts/rewrite-declaration-specifiers.mjs",
|
|
189
|
-
"build": "rm -rf dist && bun run build:js && bun run build:types && bun scripts/check-browser-clean.mjs && bun scripts/check-browser-executes.mjs && bun scripts/check-env-live.mjs && bun scripts/check-public-types.mjs && bun scripts/check-declarations-strict.mjs",
|
|
193
|
+
"build": "rm -rf dist && cp ../../CHANGELOG.md ./CHANGELOG.md && bun run build:js && chmod +x dist/upgrade-cli.js && bun run build:types && bun scripts/check-browser-clean.mjs && bun scripts/check-browser-executes.mjs && bun scripts/check-env-live.mjs && bun scripts/check-public-types.mjs && bun scripts/check-declarations-strict.mjs",
|
|
190
194
|
"dev": "bun run build:entrypoints -- --watch",
|
|
191
195
|
"prepack": "cp ../../README.md ./README.md && bun ../../scripts/gen-llms.ts && bun run build",
|
|
192
196
|
"test": "bun test",
|