@olivervea/pipelines 2.0.0-beta.1 → 2.0.0-beta.11
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/README.md +122 -62
- package/manifest.schema.json +161 -11
- package/package.json +2 -2
- package/src/build/build.ts +19 -17
- package/src/build/cli.ts +1 -1
- package/src/build/hashes.ts +32 -3
- package/src/build/project.ts +33 -5
- package/src/builder/pipeline.ts +92 -10
- package/src/core/brands.ts +2 -0
- package/src/core/config-refs.ts +77 -0
- package/src/core/config.ts +147 -0
- package/src/core/handlers.ts +16 -10
- package/src/core/origin.ts +20 -0
- package/src/core/steps.ts +53 -22
- package/src/core/webhooks.ts +30 -0
- package/src/ctx/index.ts +3 -0
- package/src/ctx/sh.ts +2 -1
- package/src/ctx/types.ts +4 -1
- package/src/describe/describe.ts +213 -27
- package/src/describe/manifest-schema.ts +53 -5
- package/src/dev/cli.ts +16 -9
- package/src/harness/execute.ts +7 -4
- package/src/harness/index.ts +1 -1
- package/src/harness/main.ts +68 -17
- package/src/harness/validate.ts +19 -1
- package/src/index.ts +7 -3
- package/src/library/checks.ts +23 -0
- package/src/library/compose-deploy.ts +96 -0
- package/src/library/github-source.ts +61 -38
- package/src/library/helm-deploy-ssh.ts +306 -99
- package/src/library/helm-deploy.ts +271 -0
- package/src/library/image-build.ts +350 -66
- package/src/library/index.ts +11 -7
- package/src/library/models.ts +17 -1
- package/src/library/ssh.ts +50 -0
- package/src/testing/index.ts +11 -15
- package/src/util/handler-hash.ts +4 -4
- package/src/util/tar.ts +13 -0
- package/src/library/script.ts +0 -85
package/src/util/handler-hash.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { sha256Hex } from "./hash.ts";
|
|
|
5
5
|
export interface HarnessInfo {
|
|
6
6
|
/** Bun version (runtime compiled into the binary). */
|
|
7
7
|
bun: string;
|
|
8
|
-
/** @
|
|
8
|
+
/** @olivervea/pipelines package version. */
|
|
9
9
|
sdk: string;
|
|
10
10
|
/** `sha256:` hash over the SDK runtime sources and the zod version. */
|
|
11
11
|
sdkHash: string;
|
|
@@ -16,7 +16,7 @@ export function composeHandlerHash(bundleHash: string, harness: HarnessInfo): st
|
|
|
16
16
|
return "sha256:" + sha256Hex(canonicalJson({ bundle: bundleHash, harness }));
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
/**
|
|
20
|
-
export function libraryHandlerHash(kind: string, config: unknown, harness: HarnessInfo): string {
|
|
21
|
-
return "sha256:" + sha256Hex(canonicalJson({ library: kind, config, harness }));
|
|
19
|
+
/** A step that carries its implementation: kind + canonical settings + harness, and the hash of the code that implements the kind. */
|
|
20
|
+
export function libraryHandlerHash(kind: string, config: unknown, harness: HarnessInfo, code?: string): string {
|
|
21
|
+
return "sha256:" + sha256Hex(canonicalJson({ library: kind, config, harness, ...(code ? { code } : {}) }));
|
|
22
22
|
}
|
package/src/util/tar.ts
CHANGED
|
@@ -147,6 +147,19 @@ export async function writeTarGz(out: string, entries: readonly TarEntry[], leve
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
/** Writes a deterministic zstd-compressed tar (level 3: fast to write, and unpacks far faster than gzip). */
|
|
151
|
+
export async function writeTarZst(out: string, entries: readonly TarEntry[], level = 3): Promise<void> {
|
|
152
|
+
const tar = `${out}.tar-tmp`;
|
|
153
|
+
const tmp = `${out}.partial`;
|
|
154
|
+
try {
|
|
155
|
+
writeTar(tar, entries);
|
|
156
|
+
await Bun.write(tmp, await Bun.zstdCompress(await Bun.file(tar).bytes(), { level }));
|
|
157
|
+
renameSync(tmp, out);
|
|
158
|
+
} finally {
|
|
159
|
+
rmSync(tar, { force: true });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
150
163
|
/** Extracts a tar or tar.gz file into `dir`. */
|
|
151
164
|
export async function extractTar(archive: string, dir: string): Promise<void> {
|
|
152
165
|
const a = new Bun.Archive(await Bun.file(archive).bytes());
|
package/src/library/script.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import type { Ctx, RunInfo } from "../ctx/types.ts";
|
|
4
|
-
import type { AnyModel } from "../core/models.ts";
|
|
5
|
-
import type { Secret } from "../core/secrets.ts";
|
|
6
|
-
import { makeStep, type InputSpec, type LibraryStep, type Step, type StepDef, type StepInput } from "../core/steps.ts";
|
|
7
|
-
import { sha256Hex } from "../util/hash.ts";
|
|
8
|
-
|
|
9
|
-
/** A config value that may depend on the run: a constant or a function of the step's inputs and run info. */
|
|
10
|
-
export type RunValue<I, T = string> = T | ((input: StepInput<I>, run: RunInfo) => T);
|
|
11
|
-
|
|
12
|
-
export interface ScriptDef<N extends string, I extends InputSpec, O extends AnyModel | null, Sec extends readonly Secret[]>
|
|
13
|
-
extends StepDef<N, I, O, Sec> {
|
|
14
|
-
/** An embedded script: `import s from "./scripts/x.sh" with { type: "file" }`. */
|
|
15
|
-
script: string;
|
|
16
|
-
/** Extra environment; values may be functions of the inputs (evaluated in the step, not at describe). */
|
|
17
|
-
env?: Record<string, RunValue<I>>;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** `deployTo` → `INPUT_DEPLOY_TO`. */
|
|
21
|
-
export function inputEnvName(key: string): string {
|
|
22
|
-
return "INPUT_" + key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").toUpperCase();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Runs a shell script (the zero-TypeScript path). Contract for the script:
|
|
27
|
-
* - `$OLVE_INPUT`: path of the validated input as JSON; each input key is also `INPUT_<KEY>`
|
|
28
|
-
* (strings raw, everything else JSON).
|
|
29
|
-
* - `$OLVE_OUTPUT`: if the step has an output model, write the output JSON there. Objects of the
|
|
30
|
-
* form `{"$file": "<path>"}` / `{"$dir": "<path>"}` are turned into file references.
|
|
31
|
-
* - Declared secrets are in the environment under their names.
|
|
32
|
-
*/
|
|
33
|
-
export function script<
|
|
34
|
-
const N extends string,
|
|
35
|
-
const I extends InputSpec = {},
|
|
36
|
-
O extends AnyModel | null = null,
|
|
37
|
-
const Sec extends readonly Secret[] = [],
|
|
38
|
-
>(def: ScriptDef<N, I, O, Sec>): Step<N, I, O, Sec[number]["name"]> & LibraryStep {
|
|
39
|
-
const env = def.env ?? {};
|
|
40
|
-
return makeStep("script", def as never, {
|
|
41
|
-
manifestConfig: () => ({
|
|
42
|
-
scriptSha256: "sha256:" + sha256Hex(readFileSync(def.script)),
|
|
43
|
-
env: Object.fromEntries(Object.entries(env).filter(([, v]) => typeof v === "string")),
|
|
44
|
-
computedEnv: Object.keys(env).filter((k) => typeof env[k] === "function").sort(),
|
|
45
|
-
}),
|
|
46
|
-
async run(input: Record<string, unknown>, ctx: Ctx) {
|
|
47
|
-
mkdirSync(ctx.tmpDir, { recursive: true });
|
|
48
|
-
const dir = mkdtempSync(join(ctx.tmpDir, "script-"));
|
|
49
|
-
const inputPath = join(dir, "input.json");
|
|
50
|
-
const outputPath = join(dir, "output.json");
|
|
51
|
-
writeFileSync(inputPath, JSON.stringify(input));
|
|
52
|
-
const vars: Record<string, string> = { OLVE_INPUT: inputPath };
|
|
53
|
-
for (const [k, v] of Object.entries(input)) {
|
|
54
|
-
if (v !== undefined) vars[inputEnvName(k)] = typeof v === "string" ? v : JSON.stringify(v);
|
|
55
|
-
}
|
|
56
|
-
for (const [k, v] of Object.entries(env)) {
|
|
57
|
-
vars[k] = typeof v === "function" ? (v as (i: unknown, r: RunInfo) => string)(input, ctx.run) : v;
|
|
58
|
-
}
|
|
59
|
-
if (def.output) vars.OLVE_OUTPUT = outputPath;
|
|
60
|
-
await ctx.sh({ script: def.script }, { env: vars });
|
|
61
|
-
if (!def.output) return undefined;
|
|
62
|
-
let raw: unknown;
|
|
63
|
-
try {
|
|
64
|
-
raw = JSON.parse(readFileSync(outputPath, "utf8"));
|
|
65
|
-
} catch (e) {
|
|
66
|
-
throw new Error(`script did not write valid JSON to $OLVE_OUTPUT: ${(e as Error).message}`);
|
|
67
|
-
}
|
|
68
|
-
return resolveFilePlaceholders(raw, ctx);
|
|
69
|
-
},
|
|
70
|
-
}) as never;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async function resolveFilePlaceholders(v: unknown, ctx: Ctx): Promise<unknown> {
|
|
74
|
-
if (Array.isArray(v)) return Promise.all(v.map((x) => resolveFilePlaceholders(x, ctx)));
|
|
75
|
-
if (typeof v === "object" && v !== null) {
|
|
76
|
-
const o = v as Record<string, unknown>;
|
|
77
|
-
const keys = Object.keys(o);
|
|
78
|
-
if (keys.length === 1 && typeof o.$file === "string") return ctx.output.file(o.$file);
|
|
79
|
-
if (keys.length === 1 && typeof o.$dir === "string") return ctx.output.dir(o.$dir);
|
|
80
|
-
const out: Record<string, unknown> = {};
|
|
81
|
-
for (const k of keys) out[k] = await resolveFilePlaceholders(o[k], ctx);
|
|
82
|
-
return out;
|
|
83
|
-
}
|
|
84
|
-
return v;
|
|
85
|
-
}
|