Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

action-registry.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

action-registry.tsBlame126 lines · 1 contributor
abfa9adClaude1/**
2 * Built-in action registry for workflow engine v2 (Block C1 / Sprint 1 — Agent 8).
3 *
4 * Workflow YAML steps of the form `uses: gluecron/<name>@<version>` are
5 * resolved here. The registry is in-memory and populated eagerly at module
6 * load by calling `registerAll()` once. Re-registering is idempotent so
7 * repeated imports (e.g. in tests) do not error.
8 *
9 * The runner (Agent 5) is the only expected caller of `resolveAction`. It
10 * constructs an `ActionContext` from the running job's state and awaits
11 * `handler.run(ctx)`. Handlers MUST NOT throw — every built-in wraps its
12 * body in try/catch and returns `{ exitCode: 1, stderr }` on failure so the
13 * runner's control flow stays predictable.
14 */
15
16export type ActionContext = {
17 with: Record<string, unknown>;
18 env: Record<string, string>;
19 workspace: string; // absolute path to checked-out code
20 runId: string;
21 jobId: string;
22 repoId: string;
23 commitSha?: string | null;
24 ref?: string | null;
25};
26
27export type ActionResult = {
28 exitCode: number; // 0 = success
29 outputs?: Record<string, string>;
30 stdout?: string;
31 stderr?: string;
32};
33
34export type ActionHandler = {
35 name: string; // e.g. 'gluecron/gatetest'
36 version: string; // e.g. 'v1'
37 run: (ctx: ActionContext) => Promise<ActionResult>;
38};
39
40// Keyed by `${name}@${version}`. A secondary "latest" pointer per name is
41// maintained so `uses: gluecron/foo` (no version) still resolves.
42const handlers = new Map<string, ActionHandler>();
43const latestByName = new Map<string, string>(); // name -> version
44
45export function registerAction(handler: ActionHandler): void {
46 if (!handler || !handler.name || !handler.version) return;
47 const key = `${handler.name}@${handler.version}`;
48 handlers.set(key, handler);
49 // Simple latest-wins policy: last registration for a given name becomes
50 // the default. Built-ins register in deterministic order (see registerAll).
51 latestByName.set(handler.name, handler.version);
52}
53
54/**
55 * Parse `uses` into (name, version). `version` defaults to `v1` when
56 * omitted. Trailing whitespace tolerated; empty input yields nulls.
57 */
58function parseUses(uses: string): { name: string; version: string | null } | null {
59 if (!uses || typeof uses !== "string") return null;
60 const trimmed = uses.trim();
61 if (!trimmed) return null;
62 const at = trimmed.lastIndexOf("@");
63 if (at === -1) {
64 return { name: trimmed, version: null };
65 }
66 const name = trimmed.slice(0, at).trim();
67 const version = trimmed.slice(at + 1).trim();
68 if (!name) return null;
69 return { name, version: version || null };
70}
71
72export function resolveAction(uses: string): ActionHandler | null {
73 const parsed = parseUses(uses);
74 if (!parsed) return null;
75
76 // Explicit version first.
77 if (parsed.version) {
78 const key = `${parsed.name}@${parsed.version}`;
79 return handlers.get(key) ?? null;
80 }
81
82 // No version: try the latest registered, fall back to v1.
83 const latest = latestByName.get(parsed.name);
84 if (latest) {
85 const key = `${parsed.name}@${latest}`;
86 const h = handlers.get(key);
87 if (h) return h;
88 }
89 return handlers.get(`${parsed.name}@v1`) ?? null;
90}
91
92export function listActions(): { name: string; version: string }[] {
93 return Array.from(handlers.values()).map((h) => ({
94 name: h.name,
95 version: h.version,
96 }));
97}
98
99// -------------------------------------------------------------------------
100// Built-in registration
101// -------------------------------------------------------------------------
102
103import { checkoutAction } from "./actions/checkout-action";
104import { gatetestAction } from "./actions/gatetest-action";
105import { cacheAction } from "./actions/cache-action";
106import { uploadArtifactAction } from "./actions/upload-artifact-action";
107import { downloadArtifactAction } from "./actions/download-artifact-action";
108
109let registered = false;
110
111/**
112 * Register every built-in action. Idempotent: safe to call multiple times.
113 * Called once at module load below, but exported for tests that want to
114 * reset state explicitly.
115 */
116export function registerAll(): void {
117 if (registered) return;
118 registerAction(checkoutAction);
119 registerAction(gatetestAction);
120 registerAction(cacheAction);
121 registerAction(uploadArtifactAction);
122 registerAction(downloadArtifactAction);
123 registered = true;
124}
125
126registerAll();