@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.
- package/LICENSE +176 -0
- package/README.md +116 -0
- package/dist/atomic-apply.d.ts +65 -0
- package/dist/atomic-apply.js +223 -0
- package/dist/cache.d.ts +89 -0
- package/dist/cache.js +750 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +30 -0
- package/dist/loader.d.ts +189 -0
- package/dist/loader.js +1387 -0
- package/dist/package-json-extract.d.ts +56 -0
- package/dist/package-json-extract.js +125 -0
- package/dist/resolver.d.ts +241 -0
- package/dist/resolver.js +821 -0
- package/package.json +39 -0
package/dist/index.d.ts
ADDED
|
@@ -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";
|
package/dist/loader.d.ts
ADDED
|
@@ -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>;
|