@olivervea/pipelines 2.0.0-beta.12 → 2.0.0-beta.14
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 +5 -3
- package/manifest.schema.json +50 -0
- package/package.json +1 -1
- package/src/builder/pipeline.ts +3 -1
- package/src/builder/refs.ts +5 -1
- package/src/core/external.ts +43 -0
- package/src/core/models.ts +3 -1
- package/src/core/steps.ts +8 -4
- package/src/describe/describe.ts +18 -2
- package/src/describe/manifest-schema.ts +12 -0
- package/src/harness/validate.ts +2 -1
- package/src/index.ts +1 -0
package/README.md
CHANGED
|
@@ -74,7 +74,7 @@ Contract files hold contracts only. Describe evaluates them without loading hand
|
|
|
74
74
|
### Models, secrets, steps
|
|
75
75
|
|
|
76
76
|
```ts
|
|
77
|
-
import { model, z, fileRef, optional, secret, step, steps, Source } from "@olivervea/pipelines";
|
|
77
|
+
import { model, z, fileRef, optional, external, secret, step, steps, Source } from "@olivervea/pipelines";
|
|
78
78
|
|
|
79
79
|
export const BuildArtifact = model("BuildArtifact", z.object({
|
|
80
80
|
version: z.string(),
|
|
@@ -87,7 +87,7 @@ export const deployBeta = step({
|
|
|
87
87
|
name: "deploy-beta", // identity: lower-case kebab, ≤ 63 chars
|
|
88
88
|
description: "Deploys to beta.", // required (may be ""); shown in the UI, not versioned
|
|
89
89
|
image: "alpine:latest", // any image; the harness brings its own runtime
|
|
90
|
-
input: { artifact: BuildArtifact }, // model | optional(model) | plain zod schema
|
|
90
|
+
input: { artifact: BuildArtifact }, // model | optional(model) | plain zod schema | external(model, …)
|
|
91
91
|
output: BetaDeployment, // a model, or omitted (no output)
|
|
92
92
|
secrets: [SSH_PRIVATE_KEY], // only these are mounted; ctx.secrets is typed to them
|
|
93
93
|
timeoutSeconds: 900, // optional
|
|
@@ -103,6 +103,7 @@ Rules:
|
|
|
103
103
|
- **Trigger mappers** produce `z.input`, so fields with defaults may be left out. **Handlers, mapper refs and `when`** see `z.output`.
|
|
104
104
|
- A **plain-schema input** has no model to match on, so the step needs a mapper.
|
|
105
105
|
- **`optional(M)`** means "maybe skipped": `M` must still have an earlier producer.
|
|
106
|
+
- **`external(M, { title, description?, from? })`** is answered from outside the run (`from`: `person()`, the default). It needs no producer and no mapper entry; the step waits until it is answered, then the value (validated against `M`) is in its input like any other. Failure handlers can't have one.
|
|
106
107
|
|
|
107
108
|
### Config
|
|
108
109
|
|
|
@@ -169,7 +170,7 @@ Library steps carry their own implementation and image; they take typed settings
|
|
|
169
170
|
|
|
170
171
|
| Helper | What it does |
|
|
171
172
|
|---|---|
|
|
172
|
-
| `httpHealthCheck(ctx, url, { attempts?, delaySeconds?, headers? })` | GETs `url` until it returns 2xx (redirects followed), retrying network errors and non-2xx statuses; defaults 5 attempts, 5 s apart
|
|
173
|
+
| `httpHealthCheck(ctx, url, { attempts?, delaySeconds?, headers? })` | GETs `url` until it returns 2xx (redirects followed), retrying network errors and non-2xx statuses; defaults 5 attempts, 5 s apart. Throws the last error when the attempts run out. Runs in the harness, so the image needs no curl. `helmDeployOverSsh`'s `healthUrl` uses it. |
|
|
173
174
|
| `serviceUrl(release)` | `http://<release>.<namespace>.svc.cluster.local` for a `HelmRelease` (charts that name the Service after the release). |
|
|
174
175
|
| `checkSpa(ctx, url)` | `GET <url>/` must reference an `/assets/*.js` bundle (Vite's layout) and the bundle must be served as JavaScript; returns `{ html, bundle }` for app-specific checks. |
|
|
175
176
|
| `sshSession(ctx, { host, key })` | Installs openssh-client when the image has no ssh (apk or apt-get), writes the key to `<tmpDir>/ssh/id` (trailing newline, mode 600; host key not verified) and returns `{ run(remote), text(remote), copy(local, remote), shell(remote) }`; `shell` is the quoted ssh command for pipes (`ctx.sh(`printf '%s' "$V" \| ${s.shell("kubectl apply -f -")}`)`). `helmDeployOverSsh` uses it. |
|
|
@@ -383,6 +384,7 @@ Exact format: [`manifest.schema.json`](manifest.schema.json) (JSON Schema 2020-1
|
|
|
383
384
|
- a nested object of mappings.
|
|
384
385
|
|
|
385
386
|
An object whose only key is `ref` (with a string value) or `const` is a tag. Describe never emits a nested object with exactly that key set. Model-matched inputs are emitted already resolved (`{"ref": "build"}`).
|
|
387
|
+
- **Step `externalInputs`** (when present) maps each external input key to `{model, title, description, from: [{kind: "person"}]}`. Those keys are not in `input` or `inputTypes`; the service merges the answers into `input.json`.
|
|
386
388
|
- **`inputTypes`** declares each input key: `{model, optional}`, or `{schema: {output, input?}}` for a plain schema.
|
|
387
389
|
- **`handlerHash`** is the step's implementation identity. A step version can be computed from image + `inputTypes`/model versions + `output` model version + `secrets` + `configEntries` (and those entries' schemas) + `input` mappings + `handlerHash`. Stage placement, `when` and the step's `description` are not part of it.
|
|
388
390
|
- Custom steps: `sha256(canonical {bundle, harness})`. `bundle` hashes the handler bundled on its own, with `pipeline.ts`, `models.ts` and `@olivervea/pipelines` external. It is tree-shaken and includes embedded scripts, so it changes only when that handler's code (or code it uses) changes.
|
package/manifest.schema.json
CHANGED
|
@@ -401,6 +401,56 @@
|
|
|
401
401
|
"description": "What the step does, shown in the UI (may be empty).",
|
|
402
402
|
"type": "string"
|
|
403
403
|
},
|
|
404
|
+
"externalInputs": {
|
|
405
|
+
"additionalProperties": {
|
|
406
|
+
"additionalProperties": false,
|
|
407
|
+
"properties": {
|
|
408
|
+
"description": {
|
|
409
|
+
"type": "string"
|
|
410
|
+
},
|
|
411
|
+
"from": {
|
|
412
|
+
"description": "Who or what may answer.",
|
|
413
|
+
"items": {
|
|
414
|
+
"additionalProperties": false,
|
|
415
|
+
"properties": {
|
|
416
|
+
"kind": {
|
|
417
|
+
"const": "person",
|
|
418
|
+
"type": "string"
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
"required": [
|
|
422
|
+
"kind"
|
|
423
|
+
],
|
|
424
|
+
"type": "object"
|
|
425
|
+
},
|
|
426
|
+
"minItems": 1,
|
|
427
|
+
"type": "array"
|
|
428
|
+
},
|
|
429
|
+
"model": {
|
|
430
|
+
"description": "The value is validated against this model's input view.",
|
|
431
|
+
"pattern": "^[A-Za-z][A-Za-z0-9_]*$",
|
|
432
|
+
"type": "string"
|
|
433
|
+
},
|
|
434
|
+
"title": {
|
|
435
|
+
"description": "What is asked, shown to whoever answers.",
|
|
436
|
+
"minLength": 1,
|
|
437
|
+
"type": "string"
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
"required": [
|
|
441
|
+
"model",
|
|
442
|
+
"title",
|
|
443
|
+
"description",
|
|
444
|
+
"from"
|
|
445
|
+
],
|
|
446
|
+
"type": "object"
|
|
447
|
+
},
|
|
448
|
+
"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.",
|
|
449
|
+
"propertyNames": {
|
|
450
|
+
"type": "string"
|
|
451
|
+
},
|
|
452
|
+
"type": "object"
|
|
453
|
+
},
|
|
404
454
|
"handlerHash": {
|
|
405
455
|
"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.",
|
|
406
456
|
"pattern": "^sha256:[0-9a-f]{64}$",
|
package/package.json
CHANGED
package/src/builder/pipeline.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { isStep } from "../core/steps.ts";
|
|
|
6
6
|
import type { WebhookEvent, WebhookVerify } from "../core/webhooks.ts";
|
|
7
7
|
import type { IsUnion, OrError, Simplify } from "../core/types.ts";
|
|
8
8
|
import type { Optional } from "../core/models.ts";
|
|
9
|
+
import type { External } from "../core/external.ts";
|
|
9
10
|
import type { FailureContext } from "../library/models.ts";
|
|
10
11
|
import type { MapperResult, Ref } from "./refs.ts";
|
|
11
12
|
import { isConfigRef, narrowConfigRef, unionAt, type ConfigNode, type ConfigRef, type DiscriminatorOf, type VariantOf } from "../core/config.ts";
|
|
@@ -33,7 +34,8 @@ type DefiniteProducersOf<A extends AvailMap, MN> = {
|
|
|
33
34
|
}[keyof A];
|
|
34
35
|
|
|
35
36
|
type EntryError<SN extends string, Key extends string, E, A extends AvailMap> =
|
|
36
|
-
E extends
|
|
37
|
+
E extends External ? never
|
|
38
|
+
: E extends Optional<infer M>
|
|
37
39
|
? [ProducersOf<A, M["name"]>] extends [never]
|
|
38
40
|
? `❌ ${M["name"]} (input '${Key}' of step '${SN}') is not available at this stage`
|
|
39
41
|
: IsUnion<ProducersOf<A, M["name"]>> extends true
|
package/src/builder/refs.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { RefBrand } from "../core/brands.ts";
|
|
2
2
|
import type { AnyStep, InferEntry } from "../core/steps.ts";
|
|
3
|
+
import type { External } from "../core/external.ts";
|
|
3
4
|
import type { Primitive } from "../core/types.ts";
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -23,4 +24,7 @@ export type Mappable<T> =
|
|
|
23
24
|
? never
|
|
24
25
|
: { [K in keyof T]: Mappable<T[K]> } & { readonly [RefBrand]?: never }); // a Ref of another model can't sneak in structurally
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
/** What a mapper provides: every input except the external ones, which come from outside the run. */
|
|
28
|
+
export type MapperResult<S extends AnyStep> = {
|
|
29
|
+
[K in keyof S["input"] as S["input"][K] extends External ? never : K]: Mappable<InferEntry<S["input"][K]>>;
|
|
30
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { isModel, type AnyModel } from "./models.ts";
|
|
2
|
+
|
|
3
|
+
/** Who or what may supply an external input's value. */
|
|
4
|
+
export type ExternalSource = { readonly kind: "person" };
|
|
5
|
+
|
|
6
|
+
/** A signed-in user answers, in the UI or with `pl input`. */
|
|
7
|
+
export function person(): ExternalSource {
|
|
8
|
+
return Object.freeze({ kind: "person" as const });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A step input whose value comes from outside the run. The step waits until it is answered (the value is
|
|
13
|
+
* validated against `model`), then runs with the value in its input like any other.
|
|
14
|
+
*/
|
|
15
|
+
export interface External<M extends AnyModel = AnyModel> {
|
|
16
|
+
readonly kind: "external";
|
|
17
|
+
readonly model: M;
|
|
18
|
+
readonly title: string;
|
|
19
|
+
readonly description: string;
|
|
20
|
+
readonly from: readonly ExternalSource[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ExternalOptions {
|
|
24
|
+
/** What is asked, shown to whoever answers. */
|
|
25
|
+
title: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
/** Who may answer (default: a person). */
|
|
28
|
+
from?: readonly ExternalSource[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function external<M extends AnyModel>(model: M, options: ExternalOptions): External<M> {
|
|
32
|
+
return Object.freeze({
|
|
33
|
+
kind: "external" as const,
|
|
34
|
+
model,
|
|
35
|
+
title: options.title,
|
|
36
|
+
description: options.description ?? "",
|
|
37
|
+
from: Object.freeze([...(options.from ?? [person()])]),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isExternal(v: unknown): v is External {
|
|
42
|
+
return typeof v === "object" && v !== null && (v as { kind?: unknown }).kind === "external" && isModel((v as { model?: unknown }).model);
|
|
43
|
+
}
|
package/src/core/models.ts
CHANGED
|
@@ -36,11 +36,13 @@ export function isModel(v: unknown): v is AnyModel {
|
|
|
36
36
|
const SEGMENT = String.raw`(?!\.\.?(?:/|$))[^/\\\x00]+`;
|
|
37
37
|
export const FILE_PATH_PATTERN = `^(?:${SEGMENT}/)*${SEGMENT}/?$`;
|
|
38
38
|
export const FILE_PATH = new RegExp(FILE_PATH_PATTERN);
|
|
39
|
+
/** Step names: lower-case kebab, ≤ 63 chars (usable as K8s label values, URL segments and storage paths). */
|
|
40
|
+
export const STEP_NAME = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
39
41
|
|
|
40
42
|
const FileRefSchema = z
|
|
41
43
|
.object({
|
|
42
44
|
run: z.number().int().nonnegative(),
|
|
43
|
-
step: z.string().
|
|
45
|
+
step: z.string().regex(STEP_NAME),
|
|
44
46
|
attempt: z.number().int().positive(),
|
|
45
47
|
path: z.string().regex(FILE_PATH),
|
|
46
48
|
sha256: z.string().regex(/^[0-9a-f]{64}$/),
|
package/src/core/steps.ts
CHANGED
|
@@ -2,17 +2,22 @@ import type { z } from "zod";
|
|
|
2
2
|
import { ORIGIN, STEP, type StepBrand } from "./brands.ts";
|
|
3
3
|
import { callerFile } from "./origin.ts";
|
|
4
4
|
import type { AnyModel, Infer, Optional } from "./models.ts";
|
|
5
|
+
import type { External } from "./external.ts";
|
|
5
6
|
import type { AnyConfigEntry } from "./config.ts";
|
|
6
7
|
import { configRefsIn } from "./config-refs.ts";
|
|
7
8
|
import type { Secret } from "./secrets.ts";
|
|
8
9
|
import type { Simplify } from "./types.ts";
|
|
9
10
|
|
|
10
|
-
/**
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* A step input entry: a model (matchable by model), `optional(model)`, a plain zod schema (mapper required),
|
|
13
|
+
* or `external(model, …)`, answered from outside the run.
|
|
14
|
+
*/
|
|
15
|
+
export type InputEntry = AnyModel | Optional | External | z.ZodType;
|
|
12
16
|
export type InputSpec = { readonly [key: string]: InputEntry };
|
|
13
17
|
|
|
14
18
|
export type InferEntry<E> =
|
|
15
19
|
E extends Optional<infer M> ? Infer<M> | undefined
|
|
20
|
+
: E extends External<infer M> ? Infer<M>
|
|
16
21
|
: E extends AnyModel ? Infer<E>
|
|
17
22
|
: E extends z.ZodType ? z.output<E>
|
|
18
23
|
: never;
|
|
@@ -62,8 +67,7 @@ export type OutputValue<S extends AnyStep> = S["output"] extends AnyModel ? Infe
|
|
|
62
67
|
/** Step names must be literals (identity, duplicate detection and mapper keys depend on it). */
|
|
63
68
|
export type LiteralName<N extends string> = N & (string extends N ? "❌ step name must be a string literal" : unknown);
|
|
64
69
|
|
|
65
|
-
|
|
66
|
-
export const STEP_NAME = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
70
|
+
export { STEP_NAME } from "./models.ts";
|
|
67
71
|
/** Names that cannot be steps: `input` is the pipeline input's root in mappings. */
|
|
68
72
|
export const RESERVED_STEP_NAMES: readonly string[] = ["input"];
|
|
69
73
|
|
package/src/describe/describe.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { CONFIG_NAME, isConfigEntry, SECRET_REF_KIND } from "../core/config.ts";
|
|
|
5
5
|
import { CONFIG_CASE_KEY, CONFIG_REF_KEY } from "../core/config-refs.ts";
|
|
6
6
|
import { configRefPath, configRefTarget, isConfigRef, unionAt } from "../core/config.ts";
|
|
7
7
|
import { isModel, isOptional, MODEL_NAME, type AnyModel } from "../core/models.ts";
|
|
8
|
+
import { isExternal } from "../core/external.ts";
|
|
8
9
|
import { SECRET_NAME, type Secret } from "../core/secrets.ts";
|
|
9
10
|
import { RESERVED_STEP_NAMES, STEP_NAME, isStep, type AnyStep } from "../core/steps.ts";
|
|
10
11
|
import { isImplementation } from "../core/handlers.ts";
|
|
@@ -189,7 +190,21 @@ export function describePipeline(pipeline: unknown, opts: DescribeOptions): Mani
|
|
|
189
190
|
if (typeof s.image !== "string" || s.image.trim() === "") problems.push(`${at}: image must be a non-empty string`);
|
|
190
191
|
|
|
191
192
|
const inputTypes: StepDescriptorT["inputTypes"] = {};
|
|
192
|
-
const
|
|
193
|
+
const externalInputs: NonNullable<StepDescriptorT["externalInputs"]> = {};
|
|
194
|
+
const allEntries = Object.entries(s.input as Record<string, unknown>);
|
|
195
|
+
for (const [key, e] of allEntries) {
|
|
196
|
+
if (!isExternal(e)) continue;
|
|
197
|
+
if (failureHandler) problems.push(`${at}: failure handlers run unattended and cannot wait for input ('${key}')`);
|
|
198
|
+
if (typeof e.title !== "string" || e.title.trim() === "") problems.push(`${at} external input '${key}': title must not be empty`);
|
|
199
|
+
if (e.from.length === 0) problems.push(`${at} external input '${key}': from must list at least one source`);
|
|
200
|
+
externalInputs[key] = {
|
|
201
|
+
model: useModel(e.model, `${at} external input '${key}'`),
|
|
202
|
+
title: e.title,
|
|
203
|
+
description: e.description,
|
|
204
|
+
from: e.from.map((f) => ({ kind: f.kind })),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
const entries = allEntries.filter(([, e]) => !isExternal(e));
|
|
193
208
|
for (const [key, e] of entries) {
|
|
194
209
|
if (isOptional(e)) inputTypes[key] = { model: useModel(e.model, `${at} input '${key}'`), optional: true };
|
|
195
210
|
else if (isModel(e)) inputTypes[key] = { model: useModel(e, `${at} input '${key}'`), optional: false };
|
|
@@ -298,6 +313,7 @@ export function describePipeline(pipeline: unknown, opts: DescribeOptions): Mani
|
|
|
298
313
|
...(settings !== undefined ? { config: settings as Record<string, z.core.util.JSONType> } : {}),
|
|
299
314
|
...(configNames.length ? { configEntries: configNames } : {}),
|
|
300
315
|
...(s.clusterAccess === true ? { clusterAccess: true as const } : {}),
|
|
316
|
+
...(Object.keys(externalInputs).length ? { externalInputs } : {}),
|
|
301
317
|
};
|
|
302
318
|
if (s.clusterAccess !== undefined && typeof s.clusterAccess !== "boolean") problems.push(`${at}: clusterAccess must be a boolean`);
|
|
303
319
|
if (s.output && !isModel(s.output)) problems.push(`${at}: output must be a model`);
|
|
@@ -321,7 +337,7 @@ export function describePipeline(pipeline: unknown, opts: DescribeOptions): Mani
|
|
|
321
337
|
else problems.push(`${at}: mapper threw: ${(e as Error)?.message ?? String(e)}`);
|
|
322
338
|
return {};
|
|
323
339
|
}
|
|
324
|
-
const declared = s.input as Record<string, unknown
|
|
340
|
+
const declared = Object.fromEntries(Object.entries(s.input as Record<string, unknown>).filter(([, e]) => !isExternal(e)));
|
|
325
341
|
for (const k of Object.keys(declared)) if (!(k in recorded)) problems.push(`${at}: mapper does not provide input '${k}'`);
|
|
326
342
|
for (const k of Object.keys(recorded)) if (!(k in declared)) problems.push(`${at}: mapper provides '${k}', which is not an input of the step`);
|
|
327
343
|
for (const [k, m] of Object.entries(recorded)) checkMapping(`${at} input '${k}'`, m, avail, isOptional(declared[k]), true);
|
|
@@ -72,6 +72,18 @@ export const StepDescriptor = z
|
|
|
72
72
|
.literal(true)
|
|
73
73
|
.optional()
|
|
74
74
|
.describe("The step pod gets the instance's step ServiceAccount token (Kubernetes:StepServiceAccount), if the instance grants one. Omitted = no token."),
|
|
75
|
+
externalInputs: z
|
|
76
|
+
.record(
|
|
77
|
+
z.string(),
|
|
78
|
+
z.strictObject({
|
|
79
|
+
model: modelName.describe("The value is validated against this model's input view."),
|
|
80
|
+
title: z.string().min(1).describe("What is asked, shown to whoever answers."),
|
|
81
|
+
description: z.string(),
|
|
82
|
+
from: z.array(z.strictObject({ kind: z.literal("person") })).min(1).describe("Who or what may answer."),
|
|
83
|
+
}),
|
|
84
|
+
)
|
|
85
|
+
.optional()
|
|
86
|
+
.describe("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."),
|
|
75
87
|
})
|
|
76
88
|
.meta({ id: "Step" });
|
|
77
89
|
|
package/src/harness/validate.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { isModel, isOptional } from "../core/models.ts";
|
|
3
|
+
import { isExternal } from "../core/external.ts";
|
|
3
4
|
import type { AnyStep } from "../core/steps.ts";
|
|
4
5
|
|
|
5
6
|
export type Validation<T> = { ok: true; value: T } | { ok: false; issues: string[] };
|
|
@@ -39,7 +40,7 @@ export function parseStrict(schema: z.ZodType, value: unknown, prefix: string):
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
function entrySchema(e: unknown): z.ZodType {
|
|
42
|
-
if (isOptional(e)) return e.model.schema;
|
|
43
|
+
if (isOptional(e) || isExternal(e)) return e.model.schema;
|
|
43
44
|
if (isModel(e)) return e.schema;
|
|
44
45
|
return e as z.ZodType;
|
|
45
46
|
}
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { secret, type Secret, type DeclaredSecrets } from "./core/secrets.ts";
|
|
|
6
6
|
export { config, secretRef, isConfigRef, type ConfigEntry, type AnyConfigEntry, type ConfigValues, type ConfigRef, type ConfigNode, type Configurable, type DiscriminatorOf, type VariantOf } from "./core/config.ts";
|
|
7
7
|
export { hmacSha256, githubSignature, bearer, type WebhookVerify, type WebhookEvent } from "./core/webhooks.ts";
|
|
8
8
|
export { resolveConfigRefs, isLiteral, type Resolved } from "./core/config-refs.ts";
|
|
9
|
+
export { external, person, type External, type ExternalOptions, type ExternalSource } from "./core/external.ts";
|
|
9
10
|
export { step, type Step, type AnyStep, type StepDef, type StepInput, type StepKind, type ConfigOf, type InputEntry, type InputSpec, type LiteralName, type OutputValue } from "./core/steps.ts";
|
|
10
11
|
export type { Simplify } from "./core/types.ts";
|
|
11
12
|
export { implement, type Implementation, type Handler, type HandlerResult, type Skipped } from "./core/handlers.ts";
|