@intx/tool-packaging 0.2.2

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.
@@ -0,0 +1,5 @@
1
+ export { type ClosureResolver, type ClosureResolverConfig, type MaterializedRef, type Packument, type PackumentFetcher, type PackumentVersion, type PeerDependencyViolation, type RegistryConfig, type RegistrySource, type ScopeRoute, AssetRegistrySource, HttpRegistrySource, ManifestInvalidError, createClosureResolver, parsePin, } from "./resolver.js";
2
+ export { type TarballCache, type TarballCacheConfig, TarballIntegrityMismatchError, createTarballCache, } from "./cache.js";
3
+ export { type HostPlatform, type LoadManifestArgs, type LoadedDirectorFactory, type LoadedToolFactory, type LoadedToolPackage, type LoaderConfig, type TarballFetcher, type ToolLoader, ToolLoaderError, createToolLoader, } from "./loader.js";
4
+ export { type ApplyAtomicArgs, type ApplyAtomicFailure, type ApplyAtomicResult, type ApplyAtomicSuccess, applyAtomic, } from "./atomic-apply.js";
5
+ export { type ExtractPackageJSONOutcome, extractTarballPackageJSON, } from "./package-json-extract.js";
package/dist/index.js ADDED
@@ -0,0 +1,30 @@
1
+ // @intx/tool-packaging — the single API boundary the rest of the tree
2
+ // uses to flow tool packages through the system.
3
+ //
4
+ // Owns every npm-team dependency (`npm-registry-fetch`,
5
+ // `npm-package-arg`, `npm-pick-manifest`, `semver`, `tar`, `ssri`). No
6
+ // other package in this monorepo imports those directly — consumers
7
+ // reach for `@intx/tool-packaging`'s clean surface instead. If the
8
+ // underlying npm tooling ever needs to be swapped or vendored, this
9
+ // package is the only boundary that has to change.
10
+ //
11
+ // Two surface areas:
12
+ //
13
+ // - Hub-side (deploy assembly):
14
+ // createClosureResolver({ registries, scopeRouting? })
15
+ // → resolveClosure(pins) → ToolPackageManifest
16
+ //
17
+ // - Sidecar-side (deploy apply):
18
+ // createTarballCache({ rootDir, maxBytes }) — content-addressable
19
+ // store keyed by SRI integrity.
20
+ // createToolLoader({ cache, registries, host, … }) — fetches,
21
+ // extracts, and dynamic-imports each pinned package.
22
+ // applyAtomic({ manifest, loader, … }) — per-deploy-id apply
23
+ // protocol that stages each deploy into its own never-renamed
24
+ // directory and maps every loader failure category onto an
25
+ // `ApplyAtomicFailure` the caller surfaces as a rejected apply.
26
+ export { AssetRegistrySource, HttpRegistrySource, ManifestInvalidError, createClosureResolver, parsePin, } from "./resolver.js";
27
+ export { TarballIntegrityMismatchError, createTarballCache, } from "./cache.js";
28
+ export { ToolLoaderError, createToolLoader, } from "./loader.js";
29
+ export { applyAtomic, } from "./atomic-apply.js";
30
+ export { extractTarballPackageJSON, } from "./package-json-extract.js";
@@ -0,0 +1,189 @@
1
+ import type { AnnotatedDirectorFactory, AnnotatedPluginFactory, AnnotatedToolFactory, BaseEnv } from "@intx/agent";
2
+ import type { DeployApplyErrorCategory } from "@intx/types/sidecar";
3
+ import type { ToolPackageManifest, ToolPackageManifestEntry } from "@intx/types/tool-packages";
4
+ import type { TarballCache } from "./cache.js";
5
+ import type { RegistryConfig } from "./resolver.js";
6
+ /**
7
+ * Loaded factory shape. We re-export `AnnotatedToolFactory<BaseEnv>` so
8
+ * callers receive the canonical agent type without re-deriving it; the
9
+ * loader still performs the structural check at import time so a
10
+ * package emitting something that does not satisfy this shape is
11
+ * rejected with `package.entry.invalid`.
12
+ */
13
+ export type LoadedToolFactory = AnnotatedToolFactory<BaseEnv>;
14
+ /**
15
+ * Loaded director factory shape. Same erased-Config storage as the
16
+ * registry uses: the loader walks `interchange.directors` and surfaces
17
+ * every export the structural check accepts. Downstream consumers feed
18
+ * these into `createDirectorRegistry` alongside the built-in defaults.
19
+ */
20
+ export type LoadedDirectorFactory = AnnotatedDirectorFactory<unknown, BaseEnv>;
21
+ /** One pinned package after materialization and entry-module import. */
22
+ export interface LoadedToolPackage {
23
+ readonly name: string;
24
+ readonly version: string;
25
+ readonly factories: readonly LoadedToolFactory[];
26
+ /**
27
+ * Plugin factories the package's `interchange.tools` module exported
28
+ * (via `definePlugin`). Plugins are instantiated by the loader before
29
+ * tool factories are run; their results are passed via `env.plugins`
30
+ * to the tool factories that read them.
31
+ */
32
+ readonly plugins: readonly AnnotatedPluginFactory[];
33
+ /**
34
+ * Director factories the package's `interchange.directors` module
35
+ * exported (via `defineDirector`). The capability walk resolves
36
+ * `DirectorRef.id` against these at deploy time; the agent runtime
37
+ * resolves them again at instantiation. Empty when the package's
38
+ * `package.json` omits the `interchange.directors` field — the
39
+ * walker treats absence as a no-op so a tools-only package stays
40
+ * valid.
41
+ */
42
+ readonly directors: readonly LoadedDirectorFactory[];
43
+ }
44
+ export interface HostPlatform {
45
+ readonly os: string;
46
+ readonly cpu: string;
47
+ }
48
+ export interface LoaderConfig {
49
+ readonly cache: TarballCache;
50
+ /**
51
+ * Registry identifier → registry config. The key is the same
52
+ * `registry` identifier manifest entries point at and the resolver
53
+ * keyed its own registries map under. The loader resolves each
54
+ * `kind: "registry"` entry by looking up this map.
55
+ */
56
+ readonly registries: ReadonlyMap<string, RegistryConfig>;
57
+ readonly host: HostPlatform;
58
+ /**
59
+ * Hard cap on the byte length of a tarball fetched from an HTTP
60
+ * registry. The default fetcher honors both the upstream
61
+ * `Content-Length` header (rejecting up front when the header value
62
+ * exceeds the cap) and the realized body byte count (aborting the
63
+ * read once the running total crosses the cap). Asset-sourced
64
+ * tarballs do not go through this path; their containment is the
65
+ * substrate's upload-time cap on the hub side.
66
+ *
67
+ * The default mirrors the hub's `HUB_MAX_TARBALL_BYTES` cap so a
68
+ * tarball legitimately accepted by the hub-side upload route is
69
+ * also legitimately fetchable from a registry mirror seeded from
70
+ * that hub. An operator pointing the sidecar at a third-party
71
+ * registry whose curated tarballs run larger should raise the cap
72
+ * explicitly rather than relying on the runtime to grow.
73
+ */
74
+ readonly maxRegistryTarballBytes?: number;
75
+ /**
76
+ * Deadline in milliseconds for a single HTTP-registry tarball fetch,
77
+ * spanning the request and the streamed body read. A stalled registry
78
+ * cannot block the fetch -- and the deploy's tool materialization
79
+ * awaiting it -- past this bound. Defaults to
80
+ * `DEFAULT_REGISTRY_FETCH_TIMEOUT_MS`. Asset-sourced tarballs read from
81
+ * the local filesystem and are not subject to it.
82
+ */
83
+ readonly registryFetchTimeoutMs?: number;
84
+ /**
85
+ * Test seam for tarball fetching. Production omits this and the
86
+ * loader uses npm-registry-fetch + filesystem reads.
87
+ */
88
+ readonly fetchTarball?: TarballFetcher;
89
+ /**
90
+ * Test seam for dynamic import. Production omits this and the loader
91
+ * uses the native dynamic-import expression. The argument is the URL
92
+ * the loader hands to `import()`: a `file://` URL with an
93
+ * `integrity=<sri>` query string the loader appends to bust Node's
94
+ * ESM module cache across applies that swap bytes under the same
95
+ * `(name, version)` pair.
96
+ */
97
+ readonly importModule?: (importUrl: string) => Promise<unknown>;
98
+ }
99
+ /**
100
+ * Default cap on a single HTTP-registry tarball fetch. Matches the
101
+ * hub's `DEFAULT_HUB_MAX_TARBALL_BYTES` so a tarball the hub accepted
102
+ * on upload is one the sidecar can also fetch back when a registry
103
+ * mirror replays it.
104
+ */
105
+ export declare const DEFAULT_MAX_REGISTRY_TARBALL_BYTES: number;
106
+ /**
107
+ * Default deadline for a single HTTP-registry tarball fetch, covering
108
+ * both the request and the streamed body read. `readResponseWithLimit`
109
+ * consumes the body through a manual reader loop, so the byte cap bounds
110
+ * size but nothing bounds time: a registry that accepts the connection
111
+ * and then stalls mid-stream would block the fetch -- and the deploy's
112
+ * tool materialization awaiting it -- indefinitely.
113
+ * The deadline is generous so a legitimately large tarball on a slow
114
+ * link still completes within it. Callers that need a different bound
115
+ * pass `registryFetchTimeoutMs` to `createToolLoader`.
116
+ */
117
+ export declare const DEFAULT_REGISTRY_FETCH_TIMEOUT_MS: number;
118
+ export type TarballFetcher = (entry: ToolPackageManifestEntry, ctx: {
119
+ registries: ReadonlyMap<string, RegistryConfig>;
120
+ assetRoot: string;
121
+ assetMounts: ReadonlyMap<string, string>;
122
+ }) => Promise<Uint8Array>;
123
+ export interface LoadManifestArgs {
124
+ readonly manifest: ToolPackageManifest;
125
+ readonly instanceScratchDir: string;
126
+ /**
127
+ * Filesystem root that `assetMounts` paths are joined against. Mount
128
+ * paths from the deploy pack are workspace-relative; the loader
129
+ * resolves them against `assetRoot` to get the absolute tarball
130
+ * location for `kind: "asset"` entries.
131
+ */
132
+ readonly assetRoot: string;
133
+ /**
134
+ * Maps a `source.assetId` from a manifest entry to a
135
+ * workspace-relative mount path. The session service emits this map
136
+ * into the deploy pack as `deploy/asset-mounts.json`; the sidecar
137
+ * threads it through to here. Empty map is valid when no entry
138
+ * sources from an asset.
139
+ */
140
+ readonly assetMounts: ReadonlyMap<string, string>;
141
+ }
142
+ export interface ToolLoader {
143
+ loadManifest(args: LoadManifestArgs): Promise<LoadedToolPackage[]>;
144
+ }
145
+ export declare class ToolLoaderError extends Error {
146
+ readonly category: DeployApplyErrorCategory;
147
+ readonly package: {
148
+ readonly name: string;
149
+ readonly version: string;
150
+ } | undefined;
151
+ constructor(opts: {
152
+ category: DeployApplyErrorCategory;
153
+ message: string;
154
+ package?: {
155
+ name: string;
156
+ version: string;
157
+ };
158
+ });
159
+ }
160
+ export declare function createToolLoader(config: LoaderConfig): ToolLoader;
161
+ export declare function buildRegistryFetchOpts(registry: RegistryConfig): Record<string, unknown>;
162
+ /**
163
+ * Read an HTTP-registry tarball response into a Uint8Array while enforcing
164
+ * `maxBytes`. Two guards:
165
+ *
166
+ * 1. If the upstream sent a `Content-Length` header, parse it (digit-
167
+ * only, per RFC 9110 §8.6) and reject up front when the declared
168
+ * length exceeds the cap. A header that fails the digit shape is
169
+ * also rejected so a header like `1e9` cannot read as 1e9 against
170
+ * `Number()` while a digit-only cap check would pass.
171
+ * 2. Stream the body chunk-by-chunk, tallying byte length, and abort
172
+ * the read when the running total crosses the cap. This catches
173
+ * the missing-or-lying header case.
174
+ *
175
+ * An optional `signal` adds a time guard: when it aborts (the caller's
176
+ * fetch deadline), the in-flight read is cancelled and the call rejects,
177
+ * so a registry that streams the body slowly or stalls mid-stream cannot
178
+ * outlast the deadline while staying under the byte cap.
179
+ *
180
+ * All rejections surface as `registry.fetch.failed` so the apply layer
181
+ * routes them the same as any other registry-side fetch defect.
182
+ *
183
+ * Exported for direct unit testing.
184
+ */
185
+ export declare function readResponseWithLimit(res: Response, maxBytes: number, ctx: {
186
+ readonly registry: string;
187
+ readonly name: string;
188
+ readonly version: string;
189
+ }, signal?: AbortSignal): Promise<Uint8Array>;