@silvermoon-work/task-builder 0.0.0 → 0.1.0-canary-261109-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 ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 shazhou-ww
4
+ Copyright (c) 2026 silvermoon-work contributors
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md CHANGED
@@ -1,6 +1,286 @@
1
1
  # @silvermoon-work/task-builder
2
2
 
3
- > Bootstrap placeholder only.
3
+ Build Task Bundles with esbuild from a read-only file tree into another read-only
4
+ file tree. No pguide command protocol, Gulp, Vinyl, CAR, network, publication, or
5
+ output-directory management is part of the transformation.
6
+ Separate `./init` and `./car` exports provide project templates and offline CAR
7
+ streams without turning build into a multi-output operation. The root entry
8
+ does not re-export or load these modules; init/CAR do not load esbuild.
9
+ Subpaths isolate module loading, not npm dependency installation.
10
+ This package has no CLI binary, process signal handlers or host filesystem
11
+ adapter. The independent
12
+ [@silvermoon-work/task-builder-cli](../task-builder-cli/README.md) library owns
13
+ Commander-based interaction and local I/O. `./tree` exposes the shared pure VFS
14
+ contract, factories and stream helpers; the old `./node` entry has moved to
15
+ `@silvermoon-work/task-builder-cli/node`.
16
+ These new subpaths are not in the published `0.1.0-canary-261109-1` artifact yet;
17
+ use a checkout-built tarball until a new release is explicitly authorized.
4
18
 
5
- Placeholder package published to reserve the `@silvermoon-work/task-builder` name on npm.
6
- Real releases will start from `0.1.0` and be published via CI Trusted Publishing.
19
+ ## Transformation
20
+
21
+ ```ts
22
+ import {
23
+ buildTaskBundle,
24
+ type ReadonlyFileTree,
25
+ } from "@silvermoon-work/task-builder";
26
+
27
+ const output: ReadonlyFileTree = await buildTaskBundle(input, {
28
+ name: "Echo",
29
+ runtime: { minVersion: "22.13.0" },
30
+ source: {
31
+ entry: "src/main.ts",
32
+ contract: {
33
+ input: { type: "object", properties: { text: { type: "string" } } },
34
+ result: { type: "string" },
35
+ message: false,
36
+ },
37
+ mode: "explicit",
38
+ },
39
+ include: [{ from: "assets/**", to: "resources/" }],
40
+ binary: ["resources/images/**"],
41
+ signal,
42
+ });
43
+
44
+ const entries = await output.list("");
45
+ const resource = await output.read("resources/config.json", { signal });
46
+ ```
47
+
48
+ The input variable is a caller-supplied tree, not a host path:
49
+
50
+ ```ts
51
+ interface ReadonlyFileTree {
52
+ list(path: string, options?: { signal?: AbortSignal }): Promise<readonly {
53
+ name: string;
54
+ kind: "file" | "directory";
55
+ target?: string;
56
+ }[]>;
57
+ read(path: string, options?: {
58
+ signal?: AbortSignal;
59
+ }): Promise<ReadableStream<Uint8Array>>;
60
+ }
61
+ ```
62
+
63
+ `""` lists the root. Listings contain direct children, with names rather than
64
+ full paths. Logical paths use `/` on every OS; absolute paths, traversal,
65
+ backslashes, colons and control characters are rejected. Missing paths, wrong
66
+ file/directory kinds, invalid entries and input failures are explicit errors.
67
+ Every read must return a new stream of the same bytes. The caller must keep the
68
+ input version and its lifetime stable while using the output.
69
+ An optional `target` is a canonical path inside the same tree for an alias;
70
+ ordinary virtual trees need not provide it. The directory adapter supplies it
71
+ for confined links so package imports resolve from their real dependency
72
+ location, including pnpm's virtual store.
73
+
74
+ ## Streaming and ownership
75
+
76
+ The generated primary Schemas are `schemas/input.json`, `schemas/result.json`
77
+ and `schemas/message.json`; the manifest references those exact paths.
78
+ Additional contract Schemas use a name-to-Schema object, for example
79
+ `schemas: { user: UserSchema, address: AddressSchema }`, and become
80
+ `schemas/shared/user.json` and `schemas/shared/address.json`. Names must be
81
+ single portable file-name stems with no traversal, separators, reserved device
82
+ names, non-NFC spellings or case-insensitive collisions. Array declarations are
83
+ rejected rather than automatically numbered. Named Schemas are sorted by name
84
+ for deterministic output, independent of object insertion order.
85
+ Author-supplied `$id` and `$ref` values are preserved. Primary fallback IDs
86
+ remain unchanged; shared definitions without an `$id` receive
87
+ `https://task.invalid/schema/shared/<URI-encoded-name>`. References use Schema
88
+ identities, not object keys; missing references or conflicting identities fail.
89
+ Existing output directories are not renamed automatically; rebuild into a new
90
+ directory to obtain the new layout.
91
+
92
+ - Module source, package metadata and contracts are aggregated **per file**
93
+ because esbuild's `onLoad` requires complete bytes. Source reads and the module
94
+ graph have separate byte/file/path budgets; this is not a fully streaming JS
95
+ compiler.
96
+ - Building reads resource listings, but does not open resource contents.
97
+ `output.read()` itself does not open the input resource either: first demand
98
+ does. Resource chunks pass through with backpressure, without whole-resource
99
+ aggregation or a temporary snapshot. Normalization buffers at most an upstream
100
+ chunk plus its transformed bytes and UTF-8 decoder state. Input adapters should
101
+ emit reasonably sized chunks; the Node adapter uses 64 KiB.
102
+ - Text validates incremental UTF-8 and normalizes CRLF/lone CR to LF, including
103
+ CRLF and UTF-8 sequences split across chunks. BOM and all other bytes are
104
+ preserved. Explicit binary resource globs preserve bytes exactly.
105
+ - Cancel or abort of a resource stream cancels its upstream reader. Each read
106
+ has independent normalization and cancellation state. The build signal applies
107
+ only to construction; output reads use their own signal.
108
+ - Generated Worker, manifest and Schema bytes are owned by the output. Readers
109
+ receive copies. Resources retain a reference to the input, not a byte snapshot;
110
+ changing or closing the input changes or breaks subsequent reads.
111
+ - Manifest, path collisions, Schema closure and generated bytes are validated
112
+ before build returns. Resource UTF-8, input failures and byte capacities can
113
+ fail later during reading, including at EOF. **A returned tree is not proof
114
+ that all resource bytes have been validated.** Consume every file to EOF before
115
+ treating it as a distributable Bundle. Partial reads do not validate the tail.
116
+ - Resource capacity accounting tracks the maximum observed normalized length per
117
+ logical file across reads, so repeated/concurrent reads do not double-count.
118
+ Protocol defaults remain 64 MiB per file/total, 256 files and 512 paths.
119
+
120
+ ## Modules and contracts
121
+
122
+ An esbuild namespace confines module loading to the input tree. `enhanced-resolve`
123
+ uses a tree-backed filesystem for relative imports, extension resolution,
124
+ `.js` to `.ts` aliases, `node_modules`, package exports/imports and Node
125
+ import/require conditions. Only Node builtins remain external. Missing
126
+ dependencies never fall back to the builder's own node_modules.
127
+
128
+ Dependencies must be present in the input tree. There is no install, network
129
+ resolver, automatic tsconfig discovery, tsconfig path mapping, arbitrary asset
130
+ loader or externally supplied esbuild plugin in this first implementation.
131
+ Compilation targets Node 22 CommonJS and rejects non-static import/require calls.
132
+ The build host must use Node >=22.13; execution runtime eligibility belongs to
133
+ the Engine.
134
+
135
+ `source.mode` defaults to `explicit`: the entry calls the SDK's `runTask`
136
+ itself. `callback` wraps a lazily imported callback; `exportName` defaults to
137
+ `default` and is only accepted in that mode. `source.sdk` defaults to
138
+ `@silvermoon-work/task-sdk` for callback wrappers and can explicitly select an
139
+ SDK already present in the tree. The SDK has not been migrated into this
140
+ repository; existing projects can supply `@potential-guide/task-sdk`.
141
+ Recognized SDK `binding.js`/`binding.ts` modules are replaced with the resolved
142
+ Bundle declaration without evaluating the Worker or author callback.
143
+
144
+ `source.contract` accepts an object, a tree-relative JSON file, or a trusted
145
+ TS/JS module default-exporting `{ input, result, message, schemas? }`. Objects
146
+ can contain plain JSON Schema or supported TypeBox 0.34.52 schemas; transforms
147
+ and unsupported metadata/format semantics are rejected. Code contracts require
148
+ `allowContractExecution: true`. They are compiled using the same tree resolver,
149
+ then **executed as trusted Node code, not in a sandbox**. Builtins and global
150
+ APIs can have side effects; a read-only tree is not an execution security boundary.
151
+ Their `__filename`, `__dirname` and `import.meta.url` are logical `task-tree:///`
152
+ locations, not host source paths. Use JSON contracts for non-executing builds.
153
+
154
+ For example, a JSON contract can share one definition between input and result:
155
+
156
+ ```json
157
+ {
158
+ "input": {
159
+ "type": "object",
160
+ "properties": {
161
+ "user": { "$ref": "https://example.invalid/schemas/user" }
162
+ }
163
+ },
164
+ "result": { "$ref": "https://example.invalid/schemas/user" },
165
+ "message": false,
166
+ "schemas": {
167
+ "user": {
168
+ "$id": "https://example.invalid/schemas/user",
169
+ "type": "object",
170
+ "properties": { "name": { "type": "string" } }
171
+ }
172
+ }
173
+ }
174
+ ```
175
+
176
+ Text contracts reject duplicate JSON/YAML keys during strict parsing, including
177
+ escaped JSON keys with the same decoded spelling. JS/TS objects can overwrite
178
+ duplicate keys before evaluation returns; that information cannot be recovered
179
+ by the builder. Case-insensitive name collisions are still checked after
180
+ evaluation. The build YAML and final `task.yaml` protocol structure do not change;
181
+ the latter only lists the newly named files in `include`.
182
+
183
+ ## Additional transformations
184
+
185
+ ### Init: configuration to a read-only template tree
186
+
187
+ ```ts
188
+ import { createTaskProject } from "@silvermoon-work/task-builder/init";
189
+
190
+ const project = createTaskProject({
191
+ name: "@example/my-task",
192
+ taskName: "Echo",
193
+ });
194
+ const entries = await project.list("");
195
+ const source = await project.read("src/main.ts");
196
+ ```
197
+
198
+ The pure synchronous function returns an owned, repeatable file tree with a
199
+ private npm package, a JSON
200
+ contract with a named shared text Schema, typed SDK entry/helper and a streamed
201
+ greeting resource. It performs no I/O, install or ancestor package modification.
202
+ The default SDK is the explicitly external `@potential-guide/task-sdk@0.1.0-rc.2`.
203
+ `sdkVersion`, `minRuntimeVersion`, `taskName` and npm `name` can be supplied.
204
+ It has no CLI dependency, npm scripts, package-manager selection or pnpm
205
+ installation policy. The CLI composes this tree with its own scripts,
206
+ dependency versions and esbuild approvals before writing it to disk.
207
+
208
+ ### CAR: file-tree streams to a bounded offline archive
209
+
210
+ ```ts
211
+ import {
212
+ encodeTaskBundleCar,
213
+ readTaskBundleCar,
214
+ } from "@silvermoon-work/task-builder/car";
215
+ const encoded = await encodeTaskBundleCar(bundleTree, { signal });
216
+ try {
217
+ await consumeCarStream(encoded.stream);
218
+ console.log(encoded.cid);
219
+ } finally {
220
+ await encoded.close();
221
+ }
222
+
223
+ const decoded = await readTaskBundleCar(carStream, {
224
+ expectedCid,
225
+ signal,
226
+ });
227
+ try {
228
+ console.log(decoded.cid, decoded.declaration, decoded.bytes, decoded.files);
229
+ const resource = await decoded.tree.read("resources/greeting.txt");
230
+ // The caller owns this offline content source; it structurally matches Engine's read port.
231
+ const ipfsClient = { block: decoded.block };
232
+ } finally {
233
+ await decoded.close();
234
+ }
235
+ ```
236
+
237
+ CAR v1 requires the root CID in its header. Encoding therefore consumes each
238
+ input file once into bounded staged UnixFS blocks, validates the staged Bundle,
239
+ then returns `{ cid, stream, retainedBytes, close() }`. It does not collect
240
+ whole resource files or the complete archive. The CAR stream is single-use,
241
+ demand-driven and emits at most 64 KiB per output chunk. It releases staged
242
+ blocks at EOF or cancellation; call `close()` when abandoning an unread result.
243
+ The encode signal spans staging and output, unlike the build signal's
244
+ construction-only lifetime. Output no longer depends on the input after staging.
245
+
246
+ Decoding consumes the full source stream before returning, verifies SHA-256
247
+ blocks, one CAR v1 root, exact reachable DAG (no duplicate/unreachable blocks),
248
+ canonical UnixFS layout, exact manifest, UTF-8/LF, Schema closure and all resources.
249
+ It returns a repeatable read-only `tree`, declaration/resolved Schema, statistics
250
+ and a read-only `block.get(cid, {signal?})` port. Reads return owned byte copies;
251
+ closing clears memory and makes subsequent tree/block reads fail explicitly.
252
+ The read signal applies to acquisition; subsequent tree/block reads have their
253
+ own cancellation signals. This is not a persistent node, pin or upload service.
254
+
255
+ `TaskCarOptions` supports `maxCarBytes` (default 72 MiB), `retainedBytes`
256
+ (default 72 MiB), `maxBlocks` (default 16,384), `maxBlockBytes` (default 2 MiB)
257
+ and reduced `fileLimits`. Retention counts unique block bytes, not whole-file
258
+ buffers. Peak memory also includes input/parser chunks, one encoded record and
259
+ aggregated manifest/Schema control files; these are not an RSS limit. Malformed
260
+ CAR records are bounded by the archive limit before decoded block-size checking.
261
+ Text is validated, not normalized during CAR encoding: pass a built Bundle tree.
262
+
263
+ The deterministic layout preserves the existing CID contract: CIDv1, raw leaves,
264
+ 256 KiB fixed chunks, balanced layout (174 children), wrapped directory, no
265
+ sharding, sorted paths and no mtime/mode metadata.
266
+
267
+ `consumeCarStream` and `carStream` above are caller-provided sinks/sources.
268
+ For host directories, use the independent
269
+ [CLI Node adapters](../task-builder-cli/README.md#node-adapters), not a core
270
+ filesystem API. See the [end-to-end guide](../../docs/end-to-end-verification.md)
271
+ for CLI init, build, CAR, real Server execution and recovery.
272
+
273
+ The included [Echo task](./examples/echo/task.build.yaml) remains an author-input
274
+ example. A built SDK Worker is meant to be started by a Task Engine, not executed
275
+ directly as a standalone script.
276
+
277
+ ## Validation
278
+
279
+ ```sh
280
+ pnpm --filter @silvermoon-work/task-builder... build
281
+ pnpm --filter @silvermoon-work/task-builder test
282
+ pnpm check
283
+ ```
284
+
285
+ The contract conversion and SDK binding behavior were extracted from
286
+ potential-guide's MIT-licensed author tooling; its license notice is retained.
package/dist/car.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ import { readTaskBundle, type TaskBundleFileLimits } from "@silvermoon-work/task-protocol";
2
+ import { type ReadonlyFileTree, type ReadOptions } from "./tree.js";
3
+ export type { ReadonlyFileTree, ReadOptions } from "./tree.js";
4
+ export interface TaskCarOptions extends ReadOptions {
5
+ readonly maxCarBytes?: number;
6
+ readonly retainedBytes?: number;
7
+ readonly maxBlocks?: number;
8
+ readonly maxBlockBytes?: number;
9
+ readonly fileLimits?: Partial<TaskBundleFileLimits>;
10
+ }
11
+ export interface EncodedTaskBundleCar {
12
+ readonly cid: string;
13
+ readonly stream: ReadableStream<Uint8Array>;
14
+ readonly retainedBytes: number;
15
+ close(): Promise<void>;
16
+ }
17
+ /** Stage bounded UnixFS blocks from file streams, then emit CAR v1 records on demand. */
18
+ export declare function encodeTaskBundleCar(input: ReadonlyFileTree, options?: TaskCarOptions): Promise<EncodedTaskBundleCar>;
19
+ export interface ReadTaskBundleCarOptions extends TaskCarOptions {
20
+ readonly expectedCid?: string;
21
+ }
22
+ export interface ReadTaskBundleCar {
23
+ readonly cid: string;
24
+ readonly tree: ReadonlyFileTree;
25
+ readonly declaration: ReturnType<typeof readTaskBundle>["declaration"];
26
+ readonly resolved: ReturnType<typeof readTaskBundle>["resolved"];
27
+ readonly bytes: number;
28
+ readonly files: number;
29
+ readonly retainedBytes: number;
30
+ readonly block: {
31
+ get(cid: string, options?: ReadOptions): Promise<Uint8Array>;
32
+ };
33
+ close(): Promise<void>;
34
+ }
35
+ /** Read a complete CAR stream and verify hashes, exact DAG, canonical tree and Bundle bytes. */
36
+ export declare function readTaskBundleCar(input: ReadableStream<Uint8Array>, options?: ReadTaskBundleCarOptions): Promise<ReadTaskBundleCar>;
37
+ //# sourceMappingURL=car.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"car.d.ts","sourceRoot":"","sources":["../src/car.ts"],"names":[],"mappings":"AASA,OAAO,EAIL,cAAc,EAId,KAAK,oBAAoB,EAC1B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAOL,KAAK,gBAAgB,EACrB,KAAK,WAAW,EACjB,MAAM,WAAW,CAAC;AAEnB,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE/D,MAAM,WAAW,cAAe,SAAQ,WAAW;IACjD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;CACrD;AAkSD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IAC5C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,yFAAyF;AACzF,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,gBAAgB,EACvB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,oBAAoB,CAAC,CA8C/B;AAED,MAAM,WAAW,wBAAyB,SAAQ,cAAc;IAC9D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC,aAAa,CAAC,CAAC;IACvE,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC,UAAU,CAAC,CAAC;IACjE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE;QAAE,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAA;KAAE,CAAC;IACjF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,gGAAgG;AAChG,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,cAAc,CAAC,UAAU,CAAC,EACjC,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,iBAAiB,CAAC,CAgF5B"}