@olivervea/pipelines 2.0.0-beta.2 → 2.0.0-beta.20

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.
Files changed (54) hide show
  1. package/README.md +125 -62
  2. package/manifest.schema.json +208 -11
  3. package/package.json +6 -4
  4. package/src/assets.d.ts +2 -2
  5. package/src/build/build.ts +121 -51
  6. package/src/build/cli.ts +29 -22
  7. package/src/build/elf.ts +8 -7
  8. package/src/build/hashes.ts +82 -20
  9. package/src/build/project.ts +95 -28
  10. package/src/builder/pipeline.ts +344 -88
  11. package/src/builder/refs.ts +15 -5
  12. package/src/core/brands.ts +7 -5
  13. package/src/core/config-refs.ts +103 -0
  14. package/src/core/config.ts +224 -0
  15. package/src/core/external.ts +86 -0
  16. package/src/core/handlers.ts +61 -15
  17. package/src/core/models.ts +32 -15
  18. package/src/core/origin.ts +23 -0
  19. package/src/core/secrets.ts +2 -2
  20. package/src/core/steps.ts +89 -38
  21. package/src/core/types.ts +5 -1
  22. package/src/core/webhooks.ts +21 -7
  23. package/src/ctx/files.ts +111 -40
  24. package/src/ctx/index.ts +84 -31
  25. package/src/ctx/sh.ts +105 -44
  26. package/src/ctx/types.ts +12 -4
  27. package/src/describe/describe.ts +701 -133
  28. package/src/describe/manifest-schema.ts +258 -61
  29. package/src/describe/mapper-source.ts +249 -104
  30. package/src/describe/record.ts +67 -37
  31. package/src/dev/cli.ts +72 -35
  32. package/src/harness/env.ts +45 -25
  33. package/src/harness/execute.ts +58 -29
  34. package/src/harness/index.ts +22 -9
  35. package/src/harness/main.ts +239 -78
  36. package/src/harness/result.ts +38 -22
  37. package/src/harness/validate.ts +77 -15
  38. package/src/index.ts +124 -11
  39. package/src/library/checks.ts +28 -0
  40. package/src/library/compose-deploy.ts +113 -0
  41. package/src/library/github-source.ts +100 -47
  42. package/src/library/helm-deploy-ssh.ts +452 -108
  43. package/src/library/helm-deploy.ts +449 -0
  44. package/src/library/http-health.ts +7 -5
  45. package/src/library/image-build.ts +499 -105
  46. package/src/library/index.ts +48 -12
  47. package/src/library/models.ts +33 -9
  48. package/src/library/ssh.ts +67 -0
  49. package/src/testing/index.ts +74 -36
  50. package/src/util/canonical.ts +7 -4
  51. package/src/util/handler-hash.ts +17 -7
  52. package/src/util/hash.ts +5 -5
  53. package/src/util/tar.ts +86 -36
  54. package/src/library/script.ts +0 -85
@@ -47,6 +47,42 @@
47
47
  "const": "2.0",
48
48
  "type": "string"
49
49
  },
50
+ "config": {
51
+ "description": "Config entries used by steps, sorted by name (omitted when none). Fields of kind secretRef ({secret: NAME}) name a secret.",
52
+ "items": {
53
+ "additionalProperties": false,
54
+ "properties": {
55
+ "description": {
56
+ "type": "string"
57
+ },
58
+ "name": {
59
+ "description": "Config entry name; a key of the pipeline's config document.",
60
+ "pattern": "^[a-z][a-zA-Z0-9]*$",
61
+ "type": "string"
62
+ },
63
+ "required": {
64
+ "description": "False when the schema accepts a missing value (optional, or a default).",
65
+ "type": "boolean"
66
+ },
67
+ "schema": {
68
+ "additionalProperties": {},
69
+ "description": "z.input view: validates the config document's value for this entry (defaults listed).",
70
+ "propertyNames": {
71
+ "type": "string"
72
+ },
73
+ "type": "object"
74
+ }
75
+ },
76
+ "required": [
77
+ "name",
78
+ "description",
79
+ "required",
80
+ "schema"
81
+ ],
82
+ "type": "object"
83
+ },
84
+ "type": "array"
85
+ },
50
86
  "description": {
51
87
  "type": "string"
52
88
  },
@@ -92,7 +128,7 @@
92
128
  "type": "string"
93
129
  },
94
130
  "sdk": {
95
- "description": "@olve/pipelines package version.",
131
+ "description": "@olivervea/pipelines package version.",
96
132
  "type": "string"
97
133
  },
98
134
  "sdkHash": {
@@ -129,6 +165,28 @@
129
165
  "minLength": 1,
130
166
  "type": "string"
131
167
  },
168
+ "retention": {
169
+ "additionalProperties": false,
170
+ "description": "Retention overrides for this pipeline (omitted when none). Each field replaces the instance setting of the same meaning.",
171
+ "properties": {
172
+ "buildGraceDays": {
173
+ "maximum": 9007199254740991,
174
+ "minimum": 0,
175
+ "type": "integer"
176
+ },
177
+ "keepLatestRuns": {
178
+ "maximum": 9007199254740991,
179
+ "minimum": 0,
180
+ "type": "integer"
181
+ },
182
+ "minRunAgeDays": {
183
+ "maximum": 9007199254740991,
184
+ "minimum": 0,
185
+ "type": "integer"
186
+ }
187
+ },
188
+ "type": "object"
189
+ },
132
190
  "secrets": {
133
191
  "description": "All declared secrets (steps + poll triggers), sorted by name.",
134
192
  "items": {
@@ -297,28 +355,173 @@
297
355
  "stages"
298
356
  ],
299
357
  "type": "object"
358
+ },
359
+ {
360
+ "additionalProperties": false,
361
+ "properties": {
362
+ "cases": {
363
+ "additionalProperties": {
364
+ "items": {
365
+ "$ref": "#/$defs/Stage"
366
+ },
367
+ "type": "array"
368
+ },
369
+ "description": "Discriminator value → stages. Exactly one case runs; a step name may repeat across cases, nowhere else.",
370
+ "propertyNames": {
371
+ "type": "string"
372
+ },
373
+ "type": "object"
374
+ },
375
+ "discriminator": {
376
+ "description": "The union's literal field whose value names the case.",
377
+ "minLength": 1,
378
+ "type": "string"
379
+ },
380
+ "select": {
381
+ "description": "Config reference (`<entry>.<field>…`) to a discriminated union; the run's config snapshot picks the case.",
382
+ "minLength": 1,
383
+ "type": "string"
384
+ }
385
+ },
386
+ "required": [
387
+ "select",
388
+ "discriminator",
389
+ "cases"
390
+ ],
391
+ "type": "object"
300
392
  }
301
393
  ]
302
394
  },
303
395
  "Step": {
304
396
  "additionalProperties": false,
305
397
  "properties": {
398
+ "clusterAccess": {
399
+ "const": true,
400
+ "description": "The step pod gets the instance's step ServiceAccount token (Kubernetes:StepServiceAccount), if the instance grants one. Omitted = no token.",
401
+ "type": "boolean"
402
+ },
306
403
  "config": {
307
404
  "additionalProperties": {
308
405
  "$ref": "#/$defs/Json"
309
406
  },
310
- "description": "Library-step configuration (display only; already covered by handlerHash).",
407
+ "description": "The step's settings (display only; already covered by handlerHash). A config reference is recorded as {\"$config\": \"<entry>.<field>…\"}; the entry is in configEntries.",
311
408
  "propertyNames": {
312
409
  "type": "string"
313
410
  },
314
411
  "type": "object"
315
412
  },
413
+ "configEntries": {
414
+ "description": "Config entries the step receives (sorted; omitted when none). The service delivers them in input.json under '$config'.",
415
+ "items": {
416
+ "description": "Config entry name; a key of the pipeline's config document.",
417
+ "pattern": "^[a-z][a-zA-Z0-9]*$",
418
+ "type": "string"
419
+ },
420
+ "type": "array"
421
+ },
316
422
  "description": {
317
423
  "description": "What the step does, shown in the UI (may be empty).",
318
424
  "type": "string"
319
425
  },
426
+ "externalInputs": {
427
+ "additionalProperties": {
428
+ "additionalProperties": false,
429
+ "properties": {
430
+ "description": {
431
+ "type": "string"
432
+ },
433
+ "from": {
434
+ "description": "Who or what may answer.",
435
+ "items": {
436
+ "oneOf": [
437
+ {
438
+ "additionalProperties": false,
439
+ "properties": {
440
+ "kind": {
441
+ "const": "person",
442
+ "type": "string"
443
+ }
444
+ },
445
+ "required": [
446
+ "kind"
447
+ ],
448
+ "type": "object"
449
+ },
450
+ {
451
+ "additionalProperties": false,
452
+ "properties": {
453
+ "kind": {
454
+ "const": "callback",
455
+ "type": "string"
456
+ }
457
+ },
458
+ "required": [
459
+ "kind"
460
+ ],
461
+ "type": "object"
462
+ },
463
+ {
464
+ "additionalProperties": false,
465
+ "properties": {
466
+ "intervalSeconds": {
467
+ "maximum": 9007199254740991,
468
+ "minimum": 10,
469
+ "type": "integer"
470
+ },
471
+ "kind": {
472
+ "const": "poll",
473
+ "type": "string"
474
+ },
475
+ "url": {
476
+ "description": "GET while waiting; {run.id} and {run.number} are filled in.",
477
+ "type": "string"
478
+ },
479
+ "valuePath": {
480
+ "description": "Dot path to the value; '.' is the whole body.",
481
+ "minLength": 1,
482
+ "type": "string"
483
+ }
484
+ },
485
+ "required": [
486
+ "kind",
487
+ "url",
488
+ "valuePath",
489
+ "intervalSeconds"
490
+ ],
491
+ "type": "object"
492
+ }
493
+ ]
494
+ },
495
+ "minItems": 1,
496
+ "type": "array"
497
+ },
498
+ "model": {
499
+ "description": "The value is validated against this model's input view.",
500
+ "pattern": "^[A-Za-z][A-Za-z0-9_]*$",
501
+ "type": "string"
502
+ },
503
+ "title": {
504
+ "description": "What is asked, shown to whoever answers.",
505
+ "minLength": 1,
506
+ "type": "string"
507
+ }
508
+ },
509
+ "required": [
510
+ "model",
511
+ "title",
512
+ "description",
513
+ "from"
514
+ ],
515
+ "type": "object"
516
+ },
517
+ "description": "Inputs answered from outside the run (omitted when none): the step waits until every one has a value, which the service writes into input.json under its key.",
518
+ "propertyNames": {
519
+ "type": "string"
520
+ },
521
+ "type": "object"
522
+ },
320
523
  "handlerHash": {
321
- "description": "Implementation identity: handler bundle (contract files excluded) + harness (bun + sdkHash); for library steps kind + config + harness.",
524
+ "description": "Implementation identity: handler bundle (contract files excluded) + harness (bun + sdkHash); for a step that carries its implementation kind + config + harness + the hash of the code of its kind.",
322
525
  "pattern": "^sha256:[0-9a-f]{64}$",
323
526
  "type": "string"
324
527
  },
@@ -348,14 +551,8 @@
348
551
  "type": "object"
349
552
  },
350
553
  "kind": {
351
- "description": "'custom' = handlers/<name>.ts; others are SDK library steps.",
352
- "enum": [
353
- "custom",
354
- "script",
355
- "gitHubSource",
356
- "imageBuild",
357
- "helmDeployOverSsh"
358
- ],
554
+ "description": "'custom' = the handler is a handlers/*.ts file; any other kind is a step that carries its implementation (an SDK or package step). A label: the service accepts any kind.",
555
+ "minLength": 1,
359
556
  "type": "string"
360
557
  },
361
558
  "name": {
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@olivervea/pipelines",
3
- "version": "2.0.0-beta.2",
3
+ "version": "2.0.0-beta.20",
4
4
  "description": "Olve.Pipelines v2 SDK: typed pipeline authoring, the step harness, and the build tool.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Oliver Vea",
8
- "homepage": "https://github.com/OliverVea/Olve.Pipelines/tree/v2/sdk#readme",
8
+ "homepage": "https://github.com/OliverVea/Olve.Pipelines/tree/main/sdk#readme",
9
9
  "repository": {
10
10
  "type": "git",
11
11
  "url": "git+https://github.com/OliverVea/Olve.Pipelines.git",
12
- "directory": "sdk"
12
+ "directory": "src/sdk"
13
13
  },
14
14
  "bugs": {
15
15
  "url": "https://github.com/OliverVea/Olve.Pipelines/issues"
@@ -50,13 +50,15 @@
50
50
  "test": "bun test",
51
51
  "check-compile-fail": "bun scripts/check-compile-fail.ts",
52
52
  "gen-schema": "bun scripts/gen-manifest-schema.ts",
53
- "check": "tsc --noEmit -p . && bun scripts/check-compile-fail.ts && bun test"
53
+ "check": "prettier --check . && tsc --noEmit -p . && bun scripts/check-compile-fail.ts && bun test",
54
+ "format": "prettier --write ."
54
55
  },
55
56
  "dependencies": {
56
57
  "zod": "4.6.7"
57
58
  },
58
59
  "devDependencies": {
59
60
  "@types/bun": "1.4.3",
61
+ "prettier": "^3.5.0",
60
62
  "typescript": "7.0.2"
61
63
  }
62
64
  }
package/src/assets.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  // `import script from "./scripts/x.sh" with { type: "file" }` yields a path string
2
2
  // (inside the compiled binary it points into the embedded /$bunfs filesystem).
3
- declare module "*.sh" {
3
+ declare module '*.sh' {
4
4
  const path: string;
5
5
  export default path;
6
6
  }
7
- declare module "*.bash" {
7
+ declare module '*.bash' {
8
8
  const path: string;
9
9
  export default path;
10
10
  }
@@ -1,21 +1,35 @@
1
- // olve-build: pipeline dir → single executable + step.tar.gz + manifest.json.
2
- import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
3
- import { join, resolve } from "node:path";
4
- import { canonicalJson } from "../util/canonical.ts";
5
- import { sha256File } from "../util/hash.ts";
6
- import type { HarnessInfo } from "../util/handler-hash.ts";
7
- import { writeTarGz } from "../util/tar.ts";
8
- import { MANIFEST_BEGIN, MANIFEST_END, uploadFile } from "../harness/result.ts";
9
- import { GLIBC_INTERP, MUSL_INTERP, readInterpreter } from "./elf.ts";
10
- import { handlerHashes, type HandlerHash } from "./hashes.ts";
11
- import { harnessInfo, loadProject, relPosix, type Project } from "./project.ts";
1
+ // olve-build: pipeline dir → single executable + step.tar.zst + manifest.json.
2
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { join, resolve } from 'node:path';
4
+ import { canonicalJson } from '../util/canonical.ts';
5
+ import { sha256File } from '../util/hash.ts';
6
+ import type { HarnessInfo } from '../util/handler-hash.ts';
7
+ import { writeTarZst } from '../util/tar.ts';
8
+ import { MANIFEST_BEGIN, MANIFEST_END, uploadFile } from '../harness/result.ts';
9
+ import { GLIBC_INTERP, MUSL_INTERP, readInterpreter } from './elf.ts';
10
+ import { handlerHashes, kindHashes, type HandlerHash } from './hashes.ts';
11
+ import {
12
+ harnessInfo,
13
+ loadPipeline,
14
+ loadProject,
15
+ relPosix,
16
+ type Project,
17
+ } from './project.ts';
12
18
 
13
- export const BUILD_FLAGS = ["--no-compile-autoload-dotenv", "--no-compile-autoload-bunfig", "--bytecode", "--format=esm"] as const;
14
- export type Target = "bun-linux-x64" | "bun-linux-x64-musl";
19
+ export const BUILD_FLAGS = [
20
+ '--no-compile-autoload-dotenv',
21
+ '--no-compile-autoload-bunfig',
22
+ '--bytecode',
23
+ '--format=esm',
24
+ ] as const;
25
+ export type Target = 'bun-linux-x64' | 'bun-linux-x64-musl';
15
26
 
16
27
  export class BuildError extends Error {
17
- override name = "BuildError";
18
- constructor(message: string, readonly exitCode = 1) {
28
+ override name = 'BuildError';
29
+ constructor(
30
+ message: string,
31
+ readonly exitCode = 1,
32
+ ) {
19
33
  super(message);
20
34
  }
21
35
  }
@@ -23,7 +37,7 @@ export class BuildError extends Error {
23
37
  export interface BuildOptions {
24
38
  out: string;
25
39
  target?: Target;
26
- /** Pre-signed PUT for step.tar.gz. */
40
+ /** Pre-signed PUT for the step archive. */
27
41
  uploadUrl?: string;
28
42
  /** Print the manifest between sentinels on stdout (for the build Job's pod log). */
29
43
  sentinels?: boolean;
@@ -41,84 +55,140 @@ export interface BuildResult {
41
55
  }
42
56
 
43
57
  /** Writes `<dir>/.olve/gen/{handlers,entry}.ts`: the literal-import handler registry + binary entry. */
44
- export function generateEntry(project: Project, hashes: Record<string, string>, harness: HarnessInfo): string {
45
- const gen = join(project.dir, ".olve", "gen");
58
+ export function generateEntry(
59
+ project: Project,
60
+ hashes: Record<string, string>,
61
+ harness: HarnessInfo,
62
+ kinds: Record<string, string> = {},
63
+ ): string {
64
+ const gen = join(project.dir, '.olve', 'gen');
46
65
  mkdirSync(gen, { recursive: true });
47
- const registry = project.handlers.map(([name, file]) => ` ${JSON.stringify(name)}: () => import(${JSON.stringify(relPosix(gen, file))}),`).join("\n");
66
+ const registry = project.handlers
67
+ .map(
68
+ ([name, file]) =>
69
+ ` ${JSON.stringify(name)}: () => import(${JSON.stringify(relPosix(gen, file))}),`,
70
+ )
71
+ .join('\n');
48
72
  writeFileSync(
49
- join(gen, "handlers.ts"),
73
+ join(gen, 'handlers.ts'),
50
74
  `// GENERATED by olve-build — do not edit.\n` +
51
- `import type { HandlerRegistry, HarnessInfo } from "@olve/pipelines/harness";\n\n` +
75
+ `import type { HandlerRegistry, HarnessInfo } from "@olivervea/pipelines/harness";\n\n` +
52
76
  `export const handlers: HandlerRegistry = {\n${registry}\n};\n\n` +
53
77
  `export const handlerHashes: Record<string, string> = ${JSON.stringify(hashes, null, 2)};\n\n` +
78
+ `export const kindHashes: Record<string, string> = ${JSON.stringify(kinds, null, 2)};\n\n` +
54
79
  `export const harness: HarnessInfo = ${JSON.stringify(harness, null, 2)};\n`,
55
80
  );
56
- const entry = join(gen, "entry.ts");
81
+ const entry = join(gen, 'entry.ts');
57
82
  writeFileSync(
58
83
  entry,
59
84
  `// GENERATED by olve-build — do not edit. Entry point of the step binary.\n` +
60
85
  `import pipeline from ${JSON.stringify(relPosix(gen, project.pipelineFile))};\n` +
61
- `import { runHarness } from "@olve/pipelines/harness";\n` +
62
- `import { handlers, handlerHashes, harness } from "./handlers.ts";\n\n` +
63
- `process.exit(await runHarness({ pipeline, handlers, handlerHashes, harness }, process.argv.slice(2)));\n`,
86
+ `import { runHarness } from "@olivervea/pipelines/harness";\n` +
87
+ `import { handlers, handlerHashes, kindHashes, harness } from "./handlers.ts";\n\n` +
88
+ `process.exit(await runHarness({ pipeline, handlers, handlerHashes, kindHashes, harness }, process.argv.slice(2)));\n`,
64
89
  );
65
90
  return entry;
66
91
  }
67
92
 
68
93
  export async function build(dir: string, opts: BuildOptions): Promise<BuildResult> {
69
- const log = opts.log ?? ((l: string) => process.stderr.write(l + "\n"));
70
- const stdout = opts.stdout ?? (async (t: string) => void (await Bun.write(Bun.stdout, t)));
71
- const target = opts.target ?? "bun-linux-x64";
72
- const project = loadProject(dir);
94
+ const log = opts.log ?? ((l: string) => process.stderr.write(l + '\n'));
95
+ const stdout =
96
+ opts.stdout ?? (async (t: string) => void (await Bun.write(Bun.stdout, t)));
97
+ const target = opts.target ?? 'bun-linux-x64';
98
+ const project = await loadProject(dir);
73
99
  const out = resolve(opts.out);
74
100
  mkdirSync(out, { recursive: true });
75
101
  const harness = harnessInfo();
76
102
 
77
103
  const t0 = performance.now();
78
104
  const hashes = await handlerHashes(project, harness);
79
- const entry = generateEntry(project, Object.fromEntries(Object.entries(hashes).map(([k, v]) => [k, v.handlerHash])), harness);
80
- log(`olve-build: ${project.handlers.length} handler(s) hashed in ${((performance.now() - t0) / 1000).toFixed(2)}s`);
105
+ const kinds = await kindHashes(project, await loadPipeline(project));
106
+ const entry = generateEntry(
107
+ project,
108
+ Object.fromEntries(Object.entries(hashes).map(([k, v]) => [k, v.handlerHash])),
109
+ harness,
110
+ kinds,
111
+ );
112
+ log(
113
+ `olve-build: ${project.handlers.length} handler(s) hashed in ${((performance.now() - t0) / 1000).toFixed(2)}s`,
114
+ );
81
115
 
82
116
  // 1. compile
83
- const binary = join(out, "step");
117
+ const binary = join(out, 'step');
84
118
  rmSync(binary, { force: true });
85
119
  const t1 = performance.now();
86
- const p = Bun.spawnSync([process.execPath, "build", "--compile", `--target=${target}`, ...BUILD_FLAGS, entry, `--outfile=${binary}`], {
87
- cwd: project.dir,
88
- stdout: "pipe",
89
- stderr: "pipe",
90
- env: { ...process.env, NO_COLOR: "1" },
91
- });
120
+ const p = Bun.spawnSync(
121
+ [
122
+ process.execPath,
123
+ 'build',
124
+ '--compile',
125
+ `--target=${target}`,
126
+ ...BUILD_FLAGS,
127
+ entry,
128
+ `--outfile=${binary}`,
129
+ ],
130
+ {
131
+ cwd: project.dir,
132
+ stdout: 'pipe',
133
+ stderr: 'pipe',
134
+ env: { ...process.env, NO_COLOR: '1' },
135
+ },
136
+ );
92
137
  if (p.exitCode !== 0 || !existsSync(binary))
93
- throw new BuildError(`bun build --compile failed (exit ${p.exitCode}):\n${p.stdout.toString()}${p.stderr.toString()}`);
94
- log(`olve-build: compiled ${target} in ${((performance.now() - t1) / 1000).toFixed(2)}s`);
138
+ throw new BuildError(
139
+ `bun build --compile failed (exit ${p.exitCode}):\n${p.stdout.toString()}${p.stderr.toString()}`,
140
+ );
141
+ log(
142
+ `olve-build: compiled ${target} in ${((performance.now() - t1) / 1000).toFixed(2)}s`,
143
+ );
95
144
 
96
145
  // 2. loader check: the harness launches the binary through /olve/lib/<loader>; a musl binary
97
146
  // (e.g. compiled on alpine, where bun-linux-x64 silently yields musl) would not start.
98
- const expected = target === "bun-linux-x64" ? GLIBC_INTERP : MUSL_INTERP;
147
+ const expected = target === 'bun-linux-x64' ? GLIBC_INTERP : MUSL_INTERP;
99
148
  const interp = readInterpreter(binary);
100
149
  if (interp !== expected)
101
- throw new BuildError(`compiled binary has PT_INTERP ${interp ?? "(none)"}, expected ${expected} — compile on a glibc Bun image (oven/bun:<version>-slim), not alpine`);
150
+ throw new BuildError(
151
+ `compiled binary has PT_INTERP ${interp ?? '(none)'}, expected ${expected} — compile on a glibc Bun image (oven/bun:<version>-slim), not alpine`,
152
+ );
102
153
 
103
154
  // 3. describe (runs the real binary: the manifest is exactly what the step pods will see)
104
- const d = Bun.spawnSync([binary, "describe"], { cwd: out, stdout: "pipe", stderr: "pipe", env: { PATH: process.env.PATH ?? "/usr/bin:/bin" } });
105
- if (d.exitCode !== 0) throw new BuildError(`describe failed (exit ${d.exitCode}):\n${d.stderr.toString()}`, d.exitCode ?? 1);
155
+ const d = Bun.spawnSync([binary, 'describe'], {
156
+ cwd: out,
157
+ stdout: 'pipe',
158
+ stderr: 'pipe',
159
+ env: { PATH: process.env.PATH ?? '/usr/bin:/bin' },
160
+ });
161
+ if (d.exitCode !== 0)
162
+ throw new BuildError(
163
+ `describe failed (exit ${d.exitCode}):\n${d.stderr.toString()}`,
164
+ d.exitCode ?? 1,
165
+ );
106
166
  const manifest = d.stdout.toString();
107
- const manifestFile = join(out, "manifest.json");
167
+ const manifestFile = join(out, 'manifest.json');
108
168
  writeFileSync(manifestFile, manifest);
109
169
 
110
- // 4. step.tar.gz (deterministic: `step` at the root, 0755, mtime 0)
111
- const archive = join(out, "step.tar.gz");
112
- await writeTarGz(archive, [{ path: "step", type: "file", mode: 0o755, source: binary }]);
170
+ // 4. step.tar.zst (deterministic: `step` at the root, 0755, mtime 0)
171
+ const archive = join(out, 'step.tar.zst');
172
+ await writeTarZst(archive, [
173
+ { path: 'step', type: 'file', mode: 0o755, source: binary },
174
+ ]);
113
175
  writeFileSync(
114
- join(out, "build.json"),
115
- canonicalJson({ target, flags: BUILD_FLAGS, harness, handlers: hashes, binarySha256: sha256File(binary), archiveSha256: sha256File(archive) }),
176
+ join(out, 'build.json'),
177
+ canonicalJson({
178
+ target,
179
+ flags: BUILD_FLAGS,
180
+ harness,
181
+ handlers: hashes,
182
+ kinds,
183
+ binarySha256: sha256File(binary),
184
+ archiveSha256: sha256File(archive),
185
+ }),
116
186
  );
117
187
  log(`olve-build: wrote ${archive}`);
118
188
 
119
189
  if (opts.uploadUrl) {
120
190
  await uploadFile(opts.uploadUrl, archive, log);
121
- log("olve-build: uploaded step.tar.gz");
191
+ log('olve-build: uploaded step.tar.zst');
122
192
  }
123
193
  if (opts.sentinels) await stdout(`${MANIFEST_BEGIN}\n${manifest}${MANIFEST_END}\n`);
124
194
  return { binary, archive, manifestFile, manifest, hashes, harness };
package/src/build/cli.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env bun
2
2
  // olve-build <pipelineDir> --out <dir> [--target bun-linux-x64|bun-linux-x64-musl] [--upload-url <url>] [--sentinels] [--hash-only]
3
- import { parseArgs } from "node:util";
4
- import { canonicalJson } from "../util/canonical.ts";
5
- import { build, BuildError, type Target } from "./build.ts";
6
- import { handlerHashes } from "./hashes.ts";
7
- import { harnessInfo, loadProject, ProjectError } from "./project.ts";
3
+ import { parseArgs } from 'node:util';
4
+ import { canonicalJson } from '../util/canonical.ts';
5
+ import { build, BuildError, type Target } from './build.ts';
6
+ import { handlerHashes } from './hashes.ts';
7
+ import { harnessInfo, loadProject, ProjectError } from './project.ts';
8
8
 
9
- const USAGE = "usage: olve-build <pipelineDir> --out <dir> [--target bun-linux-x64|bun-linux-x64-musl] [--upload-url <url>] [--sentinels]\n olve-build <pipelineDir> --hash-only";
9
+ const USAGE =
10
+ 'usage: olve-build <pipelineDir> --out <dir> [--target bun-linux-x64|bun-linux-x64-musl] [--upload-url <url>] [--sentinels]\n olve-build <pipelineDir> --hash-only';
10
11
 
11
12
  async function main(): Promise<number> {
12
13
  let parsed;
@@ -14,12 +15,12 @@ async function main(): Promise<number> {
14
15
  parsed = parseArgs({
15
16
  allowPositionals: true,
16
17
  options: {
17
- out: { type: "string" },
18
- target: { type: "string" },
19
- "upload-url": { type: "string" },
20
- sentinels: { type: "boolean", default: false },
21
- "hash-only": { type: "boolean", default: false },
22
- help: { type: "boolean", short: "h", default: false },
18
+ out: { type: 'string' },
19
+ target: { type: 'string' },
20
+ 'upload-url': { type: 'string' },
21
+ sentinels: { type: 'boolean', default: false },
22
+ 'hash-only': { type: 'boolean', default: false },
23
+ help: { type: 'boolean', short: 'h', default: false },
23
24
  },
24
25
  });
25
26
  } catch (e) {
@@ -27,23 +28,29 @@ async function main(): Promise<number> {
27
28
  return 64;
28
29
  }
29
30
  const { values, positionals } = parsed;
30
- if (values.help) return console.log(USAGE), 0;
31
- if (positionals.length !== 1) return console.error(USAGE), 64;
31
+ if (values.help) return (console.log(USAGE), 0);
32
+ if (positionals.length !== 1) return (console.error(USAGE), 64);
32
33
  const dir = positionals[0]!;
33
34
  try {
34
- if (values["hash-only"]) {
35
- const h = await handlerHashes(loadProject(dir), harnessInfo());
35
+ if (values['hash-only']) {
36
+ const h = await handlerHashes(await loadProject(dir), harnessInfo());
36
37
  await Bun.write(Bun.stdout, canonicalJson({ harness: harnessInfo(), handlers: h }));
37
38
  return 0;
38
39
  }
39
- if (!values.out) return console.error(`--out is required\n${USAGE}`), 64;
40
- const target = (values.target ?? "bun-linux-x64") as Target;
41
- if (target !== "bun-linux-x64" && target !== "bun-linux-x64-musl") return console.error(`unsupported target ${target}`), 64;
42
- await build(dir, { out: values.out, target, uploadUrl: values["upload-url"], sentinels: values.sentinels });
40
+ if (!values.out) return (console.error(`--out is required\n${USAGE}`), 64);
41
+ const target = (values.target ?? 'bun-linux-x64') as Target;
42
+ if (target !== 'bun-linux-x64' && target !== 'bun-linux-x64-musl')
43
+ return (console.error(`unsupported target ${target}`), 64);
44
+ await build(dir, {
45
+ out: values.out,
46
+ target,
47
+ uploadUrl: values['upload-url'],
48
+ sentinels: values.sentinels,
49
+ });
43
50
  return 0;
44
51
  } catch (e) {
45
- if (e instanceof BuildError) return console.error(e.message), e.exitCode;
46
- if (e instanceof ProjectError) return console.error(e.message), 78;
52
+ if (e instanceof BuildError) return (console.error(e.message), e.exitCode);
53
+ if (e instanceof ProjectError) return (console.error(e.message), 78);
47
54
  console.error((e as Error)?.stack ?? String(e));
48
55
  return 1;
49
56
  }