@cloud-cli/on 1.2.2 → 1.2.4

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 (50) hide show
  1. package/README.md +24 -20
  2. package/dist/db-client.d.ts +14 -0
  3. package/dist/drivers/index.d.ts +2 -0
  4. package/dist/drivers/standard-process.driver.d.ts +10 -0
  5. package/dist/drivers/systemd.driver.d.ts +6 -0
  6. package/dist/index.d.ts +4 -0
  7. package/dist/log-redactor.d.ts +6 -0
  8. package/dist/on.js +9240 -0
  9. package/dist/parser/include-resolver.d.ts +9 -0
  10. package/dist/parser/matrix-expander.d.ts +5 -0
  11. package/dist/parser/yaml-loader.d.ts +6 -0
  12. package/dist/plugins/github-status.plugin.d.ts +7 -0
  13. package/dist/plugins/manager.d.ts +7 -0
  14. package/dist/queue.d.ts +38 -0
  15. package/dist/reporters/html.reporter.d.ts +14 -0
  16. package/dist/reporters/json-file.reporter.d.ts +9 -0
  17. package/dist/reporters/slack.reporter.d.ts +15 -0
  18. package/dist/safe-eval.d.ts +28 -0
  19. package/dist/secrets.d.ts +15 -0
  20. package/dist/server/preprocessors/github.d.ts +5 -0
  21. package/dist/server/server.d.ts +32 -0
  22. package/dist/types.d.ts +181 -0
  23. package/dist/worker.d.ts +8 -0
  24. package/package.json +24 -20
  25. package/dist/config.js +0 -15
  26. package/dist/db-client.js +0 -38
  27. package/dist/drivers/index.js +0 -11
  28. package/dist/drivers/standard-process.driver.js +0 -156
  29. package/dist/drivers/systemd.driver.js +0 -151
  30. package/dist/evaluator/safe-eval.js +0 -213
  31. package/dist/index.js +0 -145
  32. package/dist/ingress/preprocessors/github.js +0 -30
  33. package/dist/ingress/server.js +0 -241
  34. package/dist/logging/redactor.js +0 -20
  35. package/dist/parser/include-resolver.js +0 -47
  36. package/dist/parser/matrix-expander.js +0 -43
  37. package/dist/parser/yaml-loader.js +0 -45
  38. package/dist/plugins/github-status.plugin.js +0 -19
  39. package/dist/plugins/manager.js +0 -21
  40. package/dist/plugins/types.js +0 -1
  41. package/dist/queue/dispatcher.js +0 -112
  42. package/dist/reporters/html.reporter.js +0 -108
  43. package/dist/reporters/json-file.reporter.js +0 -16
  44. package/dist/reporters/slack.reporter.js +0 -25
  45. package/dist/reporters/types.js +0 -1
  46. package/dist/runner/step-runner.js +0 -42
  47. package/dist/secrets/store.js +0 -40
  48. package/dist/types.js +0 -1
  49. package/dist/worker.js +0 -249
  50. /package/dist/{ingress/types.js → runner/step-runner.d.ts} +0 -0
@@ -0,0 +1,9 @@
1
+ export declare class WorkflowIncludeResolver {
2
+ private baseDir;
3
+ private maxDepth;
4
+ constructor(baseDir: string, maxDepth?: number);
5
+ /**
6
+ * Recursively loads and merges YAML workflows while guarding against cycles.
7
+ */
8
+ resolve(filePath: string, visited?: Set<string>, depth?: number): any;
9
+ }
@@ -0,0 +1,5 @@
1
+ import { ParsedWorkflow } from '../types';
2
+ /**
3
+ * Expands a workflow definition containing a `strategy.matrix` into dynamic single-instance workflow jobs.
4
+ */
5
+ export declare function expandMatrix(workflow: ParsedWorkflow): ParsedWorkflow[];
@@ -0,0 +1,6 @@
1
+ import { WorkflowIncludeResolver } from './include-resolver.js';
2
+ import { WorkflowDefinition } from '../types.js';
3
+ export declare class YamlLoader {
4
+ static from(path: string): Promise<WorkflowDefinition[]>;
5
+ static loadFile(path: string, resolver: WorkflowIncludeResolver): WorkflowDefinition[];
6
+ }
@@ -0,0 +1,7 @@
1
+ import { WorkflowPlugin, WorkflowContext } from '../types.js';
2
+ export declare class GitHubStatusPlugin implements WorkflowPlugin {
3
+ name: string;
4
+ onWorkflowStart(wf: WorkflowContext): Promise<void>;
5
+ onWorkflowFinish(wf: WorkflowContext, status: 'success' | 'failed'): Promise<void>;
6
+ private updateStatus;
7
+ }
@@ -0,0 +1,7 @@
1
+ import { WorkflowPlugin, WorkflowContext } from '../types.js';
2
+ export declare class PluginManager {
3
+ private plugins;
4
+ register(plugin: WorkflowPlugin): void;
5
+ triggerWorkflowStart(wf: WorkflowContext): Promise<void>;
6
+ triggerWorkflowFinish(wf: WorkflowContext, status: 'success' | 'failed'): Promise<void>;
7
+ }
@@ -0,0 +1,38 @@
1
+ import { WorkflowExecutionReport, JobPayload, JobRecord, JobStatus } from './types.js';
2
+ export declare class QueueManager {
3
+ private workerId;
4
+ constructor(workerId: string);
5
+ init(): Promise<void>;
6
+ /**
7
+ * Enqueues a new job into the database.
8
+ * Includes simple GitHub-style concurrency cancellation.
9
+ */
10
+ enqueue(workflowId: string, payload: JobPayload, concurrencyKey?: string): Promise<any>;
11
+ /**
12
+ * ATOMICALY claims the oldest pending job.
13
+ * Requires SQLite >= 3.35 for the RETURNING clause.
14
+ */
15
+ claimNextJob(): Promise<JobRecord | null>;
16
+ /**
17
+ * Marks a job as completed or failed
18
+ */
19
+ finishJob(jobId: string | number, status: JobStatus): Promise<void>;
20
+ /**
21
+ * Checks if the current job has been marked for cancellation by another event
22
+ */
23
+ isCancelled(jobId: number): Promise<boolean>;
24
+ clearStaleJobs(): Promise<any>;
25
+ createTables(): Promise<any>;
26
+ /**
27
+ * Save complete execution report JSON to DB
28
+ */
29
+ saveReport(jobId: string | number, report: WorkflowExecutionReport): Promise<void>;
30
+ /**
31
+ * Fetch job details + report by ID
32
+ */
33
+ getJob(jobId: string | number): Promise<any>;
34
+ /**
35
+ * List recent jobs for dashboard status monitoring
36
+ */
37
+ listJobs(limit?: number): Promise<any[]>;
38
+ }
@@ -0,0 +1,14 @@
1
+ import { Reporter, WorkflowExecutionReport } from '../types.js';
2
+ export declare class HtmlReporter implements Reporter {
3
+ name: string;
4
+ private outputDir;
5
+ private ansiUp;
6
+ constructor(options: {
7
+ outputDir: string;
8
+ });
9
+ report(execReport: WorkflowExecutionReport): Promise<void>;
10
+ /**
11
+ * Render standalone HTML template using Tailwind CSS via CDN
12
+ */
13
+ generateHtml(execReport: WorkflowExecutionReport): string;
14
+ }
@@ -0,0 +1,9 @@
1
+ import { Reporter, WorkflowExecutionReport } from '../types.js';
2
+ export declare class JsonFileReporter implements Reporter {
3
+ name: string;
4
+ private outputDir;
5
+ constructor(options: {
6
+ outputDir: string;
7
+ });
8
+ report(execReport: WorkflowExecutionReport): Promise<void>;
9
+ }
@@ -0,0 +1,15 @@
1
+ import { Reporter, WorkflowExecutionReport } from '../types.js';
2
+ export declare class SlackReporter implements Reporter {
3
+ readonly name = "slack";
4
+ private webhookUrl;
5
+ private token;
6
+ private channel;
7
+ private notifyOn;
8
+ constructor(options: {
9
+ webhookUrl: string;
10
+ token: string;
11
+ channel: string;
12
+ notifyOn?: ('success' | 'failed')[];
13
+ });
14
+ report(execReport: WorkflowExecutionReport): Promise<void>;
15
+ }
@@ -0,0 +1,28 @@
1
+ export declare const BUILTIN_HELPERS: Record<string, any>;
2
+ export declare class SafeExpressionEvaluator {
3
+ /**
4
+ * Deterministic Value Evaluator (Used for `env:`, `name:`, `image:`, `concurrency:`)
5
+ * - Native non-string types (booleans, numbers, objects) pass through untouched.
6
+ * - Strings WITHOUT `${` are returned as 100% literal strings (zero JS AST overhead).
7
+ * - Strings WITH `${` are evaluated strictly as ES Template Literals.
8
+ */
9
+ static evaluateValue(val: any, context?: Record<string, any>): Promise<any>;
10
+ /**
11
+ * Deterministic Condition Evaluator (Used for `if:`)
12
+ * Strictly parses code as a JavaScript expression and coerces result to boolean.
13
+ * Throws an explicit AST Parse Error on invalid syntax (fails fast and loud).
14
+ */
15
+ static evaluateCondition(code: string, context?: Record<string, any>): Promise<boolean>;
16
+ /**
17
+ * Evaluates direct JS code (Used for `eval:` steps or internal expression resolution).
18
+ */
19
+ static evaluateExpression(code: string, context?: Record<string, any>): Promise<any>;
20
+ /**
21
+ * Alias wrapper for backward compatibility with step runners
22
+ */
23
+ static evaluateAsync(code: string, context?: Record<string, any>): Promise<any>;
24
+ /**
25
+ * Asynchronous AST Node Walker with Security Guards
26
+ */
27
+ private static evalNodeAsync;
28
+ }
@@ -0,0 +1,15 @@
1
+ export declare class SecretStore {
2
+ private envFilePath?;
3
+ private secrets;
4
+ /**
5
+ * Initialize secrets from host environment or a specified .env file
6
+ */
7
+ constructor(envFilePath?: string | undefined);
8
+ reload(): void;
9
+ get(key: string): string | undefined;
10
+ getAll(): Record<string, string>;
11
+ /**
12
+ * Returns a list of secret values to be redacted from logs
13
+ */
14
+ getSecretValuesForRedaction(): string[];
15
+ }
@@ -0,0 +1,5 @@
1
+ import type { PreprocessedWebhook, WebhookPreprocessor } from '../../types.js';
2
+ export declare class GitHubPreprocessor implements WebhookPreprocessor {
3
+ name: string;
4
+ parse(headers: Record<string, string>, rawBodyBuffer: Buffer, secret?: string): PreprocessedWebhook;
5
+ }
@@ -0,0 +1,32 @@
1
+ import { WebhookPreprocessor, WebhookServerOptions } from '../types.js';
2
+ export declare class WebhookServer {
3
+ private server;
4
+ private preprocessors;
5
+ private workflows;
6
+ private queue;
7
+ private secrets;
8
+ private adminToken;
9
+ static withPort(options: WebhookServerOptions & {
10
+ port: number;
11
+ }): Promise<void>;
12
+ constructor(options: WebhookServerOptions);
13
+ registerPreprocessor(preprocessor: WebhookPreprocessor): void;
14
+ private handleRequest;
15
+ /**
16
+ * Processes incoming HTTP webhooks
17
+ */
18
+ private handleWebhook;
19
+ /**
20
+ * Handles Zero-Downtime Secret Reload
21
+ */
22
+ private handleSecretReload;
23
+ /**
24
+ * Serves the Server Health & Jobs Dashboard
25
+ */
26
+ private renderDashboard;
27
+ /**
28
+ * Serves single job HTML report
29
+ */
30
+ private renderRunDetails;
31
+ listen(port: number): Promise<void>;
32
+ }
@@ -0,0 +1,181 @@
1
+ import { QueueManager } from './queue.js';
2
+ import { SecretStore } from './secrets.js';
3
+ export interface StepContext {
4
+ jobId: string;
5
+ stepId: string;
6
+ workspacePath: string;
7
+ command: string;
8
+ env?: Record<string, string>;
9
+ image?: string;
10
+ timeoutMs?: number;
11
+ }
12
+ export interface StepResult {
13
+ exitCode: number;
14
+ durationMs: number;
15
+ error?: Error;
16
+ }
17
+ export interface StepExecutionHandle {
18
+ /**
19
+ * Promise that resolves when step completes or fails
20
+ */
21
+ done: Promise<StepResult>;
22
+ /**
23
+ * Safely kills the step process tree
24
+ */
25
+ cancel(): Promise<void>;
26
+ /**
27
+ * Path to where logs are being written on disk
28
+ */
29
+ logFilePath: string;
30
+ }
31
+ export interface ExecutionDriver {
32
+ name: string;
33
+ /**
34
+ * Validates if host can run this driver (e.g., systemd is present)
35
+ */
36
+ isSupported(): Promise<boolean>;
37
+ /**
38
+ * Executes a step context
39
+ */
40
+ execute(ctx: StepContext): Promise<StepExecutionHandle>;
41
+ }
42
+ export interface PreprocessedWebhook {
43
+ isValid: boolean;
44
+ inputs: Record<string, any>;
45
+ }
46
+ export interface WebhookPreprocessor {
47
+ name: string;
48
+ parse(headers: Record<string, string>, rawBodyBuffer: Buffer, secret?: string): PreprocessedWebhook;
49
+ }
50
+ export interface WorkflowStep {
51
+ id?: string;
52
+ name?: string;
53
+ run?: string;
54
+ eval?: string;
55
+ dispatch?: string;
56
+ image?: string;
57
+ timeoutMs?: number;
58
+ env?: Record<string, string>;
59
+ }
60
+ export interface WorkflowDefinition {
61
+ id: string;
62
+ name: string;
63
+ on: {
64
+ provider: string;
65
+ if?: string;
66
+ };
67
+ concurrency?: {
68
+ group: string;
69
+ cancelInProgress?: boolean;
70
+ };
71
+ steps: WorkflowStep[];
72
+ }
73
+ export interface WebhookServerOptions {
74
+ queue: QueueManager;
75
+ secrets: SecretStore;
76
+ adminToken: string;
77
+ workflows: WorkflowDefinition[];
78
+ }
79
+ export type JobStatus = 'pending' | 'running' | 'success' | 'failed' | 'cancelling' | 'cancelled';
80
+ export interface JobRecord {
81
+ id: number;
82
+ workflow_id: string;
83
+ concurrency_key: string | null;
84
+ status: JobStatus;
85
+ worker_id: string | null;
86
+ payload: string;
87
+ created_at: string;
88
+ }
89
+ export type WorkflowInputs = Record<string, any>;
90
+ export interface JobPayload {
91
+ workflowId: string;
92
+ steps: WorkflowStep[];
93
+ inputs: WorkflowInputs;
94
+ }
95
+ export interface MatrixStrategy {
96
+ matrix?: Record<string, (string | number | boolean)[]>;
97
+ 'max-parallel'?: number;
98
+ }
99
+ export interface ParsedWorkflow {
100
+ id?: string;
101
+ name: string;
102
+ strategy?: MatrixStrategy;
103
+ env?: Record<string, string>;
104
+ steps: WorkflowStep[];
105
+ [key: string]: any;
106
+ }
107
+ export interface IngressContext {
108
+ provider: string;
109
+ headers: Record<string, string>;
110
+ body: any;
111
+ rawBuffer: Buffer;
112
+ }
113
+ export interface WorkflowContext {
114
+ jobId: string;
115
+ workflowName: string;
116
+ inputs: WorkflowInputs;
117
+ env: Record<string, string>;
118
+ }
119
+ export interface WorkflowPlugin {
120
+ name: string;
121
+ /** Verify HMAC or signatures */
122
+ onAuthenticate?: (ctx: IngressContext) => Promise<boolean> | boolean;
123
+ /** Convert raw body/headers into normalized inputs */
124
+ onTransform?: (ctx: IngressContext) => Promise<Record<string, any>> | Record<string, any>;
125
+ /** Final gatekeeper check (return false to drop job before enqueueing) */
126
+ onFilter?: (inputs: Record<string, any>, ctx: IngressContext) => Promise<boolean> | boolean;
127
+ /** Runs before any steps execute (e.g. notify Slack, update GitHub status to "Pending") */
128
+ onWorkflowStart?: (wf: WorkflowContext) => Promise<void>;
129
+ /** Runs right before a step executes (e.g. inject dynamic secrets, prepare workspace) */
130
+ onStepBefore?: (step: StepContext, wf: WorkflowContext) => Promise<void>;
131
+ /** Runs after a step finishes (e.g. parse outputs, stream step metrics) */
132
+ onStepAfter?: (step: StepContext, result: StepResult, wf: WorkflowContext) => Promise<void>;
133
+ /** Runs when workflow succeeds or fails (e.g. update GitHub status to "Success/Failure", wipe layers) */
134
+ onWorkflowFinish?: (wf: WorkflowContext, status: 'success' | 'failed', error?: Error) => Promise<void>;
135
+ }
136
+ export interface StepReport {
137
+ id: string;
138
+ name: string;
139
+ status: 'success' | 'failed' | 'skipped' | 'cancelled';
140
+ durationMs: number;
141
+ exitCode: number;
142
+ error?: string;
143
+ outputs: Record<string, any>;
144
+ logFilePath: string;
145
+ }
146
+ export interface WorkflowExecutionReport {
147
+ jobId: string;
148
+ workflowName: string;
149
+ status: 'success' | 'failed' | 'cancelled';
150
+ durationMs: number;
151
+ startedAt: string;
152
+ finishedAt: string;
153
+ inputs: Record<string, any>;
154
+ environment: Record<string, string>;
155
+ steps: StepReport[];
156
+ artifacts: string[];
157
+ rerunToken: string;
158
+ }
159
+ export interface Reporter {
160
+ name: string;
161
+ report(execReport: WorkflowExecutionReport): Promise<void>;
162
+ }
163
+ export interface RunnerConfig {
164
+ /** Ingress HTTP Gateway Port */
165
+ port: number;
166
+ /** Admin Secret for API / webhook operations */
167
+ adminToken: string;
168
+ /** SQLite Database connection URL / path */
169
+ database: string;
170
+ /** Directory where workflow YAML files live */
171
+ workflows: string;
172
+ /** Number of concurrent worker loops to spawn */
173
+ workers: number;
174
+ /** Storage path for job workspaces and step logs */
175
+ storagePath: string;
176
+ /** Global environment variables injected into all step runs */
177
+ env: Record<string, string>;
178
+ /** Registered reporter plugins (JSON, Slack, HTML, etc.) */
179
+ reporters: Reporter[];
180
+ }
181
+ export type UserRunnerConfig = Partial<RunnerConfig>;
@@ -0,0 +1,8 @@
1
+ import { QueueManager } from './queue.js';
2
+ import { SecretStore } from './secrets.js';
3
+ import type { RunnerConfig } from './types.js';
4
+ export declare function startWorkers(count: number, queue: QueueManager, secrets: SecretStore, config: RunnerConfig): Promise<void>[];
5
+ /**
6
+ * Main worker loop: continuously polls the SQLite queue for pending jobs.
7
+ */
8
+ export declare function startWorkerLoop(workerId: string, queue: QueueManager, secrets: SecretStore, config: RunnerConfig): Promise<void>;
package/package.json CHANGED
@@ -1,43 +1,47 @@
1
1
  {
2
2
  "name": "@cloud-cli/on",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "description": "CLI entry point for the on plugin ecosystem.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.30.3",
7
7
  "license": "MIT",
8
8
  "exports": {
9
- "./*": "./dist/*"
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/on.js"
12
+ }
10
13
  },
11
14
  "files": [
12
15
  "dist"
13
16
  ],
14
- "main": "dist/index.js",
17
+ "main": "dist/on.js",
15
18
  "bin": {
16
- "on": "dist/index.js"
19
+ "on": "dist/on.js"
17
20
  },
18
21
  "dependencies": {
19
- "acorn": "^8.17.0",
22
+ "acorn": "^8.18.0",
20
23
  "ansi_up": "^6.0.6",
21
24
  "dotenv": "^17.4.2",
22
- "yaml": "^2.8.2"
25
+ "yaml": "^2.9.0"
23
26
  },
24
27
  "scripts": {
25
- "nx": "nx",
26
- "build": "tsc",
28
+ "ci": "pnpm build && pnpm lint && pnpm test",
29
+ "build": "vite build && tsc --emitDeclarationOnly --declaration",
27
30
  "lint": "eslint .",
28
- "test": "echo true || npm wf start",
29
- "wf": "pnpm tsx src/index.ts -w tests -p 11235"
31
+ "test": "echo true",
32
+ "release": "npx --yes semantic-release@24 -b main --no-ci"
30
33
  },
31
34
  "devDependencies": {
32
- "@changesets/cli": "2.30.0",
33
- "@eslint/js": "10.0.1",
34
- "@types/node": "^26.0.0",
35
- "tsx": "^4.23.1",
36
- "typescript": "^7.0.0",
37
- "@typescript-eslint/eslint-plugin": "8.57.1",
38
- "@typescript-eslint/parser": "8.57.1",
39
- "typescript-eslint": "8.57.1",
40
- "vite": "^7.3.5",
41
- "vitest": "4.1.0"
35
+ "@changesets/cli": "^2.31.1",
36
+ "@eslint/js": "^10.0.1",
37
+ "@types/node": "^26.1.2",
38
+ "@typescript-eslint/eslint-plugin": "^8.66.0",
39
+ "@typescript-eslint/parser": "^8.66.0",
40
+ "eslint": "10.8.0",
41
+ "tsx": "^4.23.10",
42
+ "typescript": "^6.0.3",
43
+ "typescript-eslint": "^8.66.0",
44
+ "vite": "^8.2.1",
45
+ "vitest": "^4.1.10"
42
46
  }
43
47
  }
package/dist/config.js DELETED
@@ -1,15 +0,0 @@
1
- /**
2
- * Merges user config overrides with baseline defaults
3
- */
4
- export function resolveConfig(userConfig = {}) {
5
- return {
6
- port: userConfig.port ?? 3000,
7
- adminToken: userConfig.adminToken ?? process.env.RUNNER_ADMIN_SECRET ?? '',
8
- sqliteUrl: userConfig.sqliteUrl ?? process.env.DATABASE_URL ?? 'sqlite.db',
9
- workflowsDir: userConfig.workflowsDir ?? '.on/',
10
- workersCount: userConfig.workersCount ?? 5,
11
- storagePath: userConfig.storagePath ?? process.env.RUNNER_TMP ?? '/tmp/workspaces',
12
- env: userConfig.env ?? {},
13
- reporters: userConfig.reporters ?? [],
14
- };
15
- }
package/dist/db-client.js DELETED
@@ -1,38 +0,0 @@
1
- const baseURL = process.env.DATABASE_URL;
2
- let pragmas = [];
3
- async function query(method, statement, data, pragma = pragmas) {
4
- let req;
5
- let error;
6
- let retries = 1;
7
- let max = 3;
8
- while (retries < max) {
9
- try {
10
- req = await fetch(new URL('/query', baseURL), {
11
- method: 'POST',
12
- body: JSON.stringify({
13
- s: statement,
14
- d: data,
15
- m: method,
16
- p: pragma,
17
- }),
18
- });
19
- if (req.ok) {
20
- return await req.json();
21
- }
22
- await new Promise((r) => setTimeout(r, retries++ * 1000));
23
- }
24
- catch (e) {
25
- error = e;
26
- }
27
- }
28
- throw new Error(error || (await req.text()));
29
- }
30
- export const get = query.bind(null, 'get');
31
- export const run = query.bind(null, 'run');
32
- export const all = query.bind(null, 'all');
33
- export function pragma(p) {
34
- if (Array.isArray(p) && p.every((s) => typeof s === 'string')) {
35
- pragmas = p;
36
- }
37
- }
38
- export default { query, get, run, all, pragma };
@@ -1,11 +0,0 @@
1
- import { SystemdDriver } from './systemd.driver.js';
2
- import { StandardProcessDriver } from './standard-process.driver.js';
3
- export async function resolveDriver() {
4
- const systemd = new SystemdDriver();
5
- if (await systemd.isSupported()) {
6
- console.log('⚡ Selected Execution Driver: Systemd (cgroups enabled)');
7
- return systemd;
8
- }
9
- console.log('📦 Selected Execution Driver: Standard Process (Fallback)');
10
- return new StandardProcessDriver();
11
- }