@intentius/chant-lexicon-github 0.0.18
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/dist/integrity.json +31 -0
- package/dist/manifest.json +15 -0
- package/dist/meta.json +135 -0
- package/dist/rules/deprecated-action-version.ts +49 -0
- package/dist/rules/detect-secrets.ts +53 -0
- package/dist/rules/extract-inline-structs.ts +62 -0
- package/dist/rules/file-job-limit.ts +49 -0
- package/dist/rules/gha006.ts +58 -0
- package/dist/rules/gha009.ts +42 -0
- package/dist/rules/gha011.ts +40 -0
- package/dist/rules/gha017.ts +32 -0
- package/dist/rules/gha018.ts +40 -0
- package/dist/rules/gha019.ts +72 -0
- package/dist/rules/job-timeout.ts +59 -0
- package/dist/rules/missing-recommended-inputs.ts +61 -0
- package/dist/rules/no-hardcoded-secrets.ts +46 -0
- package/dist/rules/no-raw-expressions.ts +51 -0
- package/dist/rules/suggest-cache.ts +71 -0
- package/dist/rules/use-condition-builders.ts +45 -0
- package/dist/rules/use-matrix-builder.ts +44 -0
- package/dist/rules/use-typed-actions.ts +47 -0
- package/dist/rules/validate-concurrency.ts +66 -0
- package/dist/rules/yaml-helpers.ts +129 -0
- package/dist/skills/chant-github.md +29 -0
- package/dist/skills/github-actions-patterns.md +93 -0
- package/dist/types/index.d.ts +358 -0
- package/package.json +33 -0
- package/src/codegen/docs-cli.ts +3 -0
- package/src/codegen/docs.ts +1138 -0
- package/src/codegen/generate-cli.ts +36 -0
- package/src/codegen/generate-lexicon.ts +58 -0
- package/src/codegen/generate-typescript.ts +149 -0
- package/src/codegen/generate.ts +141 -0
- package/src/codegen/naming.ts +57 -0
- package/src/codegen/package.ts +65 -0
- package/src/codegen/parse.ts +700 -0
- package/src/codegen/patches.ts +46 -0
- package/src/composites/cache.ts +25 -0
- package/src/composites/checkout.ts +31 -0
- package/src/composites/composites.test.ts +675 -0
- package/src/composites/deploy-environment.ts +77 -0
- package/src/composites/docker-build.ts +120 -0
- package/src/composites/download-artifact.ts +24 -0
- package/src/composites/go-ci.ts +91 -0
- package/src/composites/index.ts +26 -0
- package/src/composites/node-ci.ts +71 -0
- package/src/composites/node-pipeline.ts +151 -0
- package/src/composites/python-ci.ts +92 -0
- package/src/composites/setup-go.ts +24 -0
- package/src/composites/setup-node.ts +26 -0
- package/src/composites/setup-python.ts +24 -0
- package/src/composites/upload-artifact.ts +27 -0
- package/src/coverage.ts +49 -0
- package/src/expression.test.ts +147 -0
- package/src/expression.ts +214 -0
- package/src/generated/index.d.ts +358 -0
- package/src/generated/index.ts +29 -0
- package/src/generated/lexicon-github.json +135 -0
- package/src/generated/runtime.ts +4 -0
- package/src/import/generator.test.ts +110 -0
- package/src/import/generator.ts +119 -0
- package/src/import/parser.test.ts +98 -0
- package/src/import/parser.ts +73 -0
- package/src/index.ts +53 -0
- package/src/lint/post-synth/gha006.ts +58 -0
- package/src/lint/post-synth/gha009.ts +42 -0
- package/src/lint/post-synth/gha011.ts +40 -0
- package/src/lint/post-synth/gha017.ts +32 -0
- package/src/lint/post-synth/gha018.ts +40 -0
- package/src/lint/post-synth/gha019.ts +72 -0
- package/src/lint/post-synth/post-synth.test.ts +318 -0
- package/src/lint/post-synth/yaml-helpers.ts +129 -0
- package/src/lint/rules/data/deprecated-versions.ts +13 -0
- package/src/lint/rules/data/known-actions.ts +13 -0
- package/src/lint/rules/data/recommended-inputs.ts +10 -0
- package/src/lint/rules/data/secret-patterns.ts +31 -0
- package/src/lint/rules/deprecated-action-version.ts +49 -0
- package/src/lint/rules/detect-secrets.ts +53 -0
- package/src/lint/rules/extract-inline-structs.ts +62 -0
- package/src/lint/rules/file-job-limit.ts +49 -0
- package/src/lint/rules/index.ts +17 -0
- package/src/lint/rules/job-timeout.ts +59 -0
- package/src/lint/rules/missing-recommended-inputs.ts +61 -0
- package/src/lint/rules/no-hardcoded-secrets.ts +46 -0
- package/src/lint/rules/no-raw-expressions.ts +51 -0
- package/src/lint/rules/rules.test.ts +365 -0
- package/src/lint/rules/suggest-cache.ts +71 -0
- package/src/lint/rules/use-condition-builders.ts +45 -0
- package/src/lint/rules/use-matrix-builder.ts +44 -0
- package/src/lint/rules/use-typed-actions.ts +47 -0
- package/src/lint/rules/validate-concurrency.ts +66 -0
- package/src/lsp/completions.test.ts +9 -0
- package/src/lsp/completions.ts +20 -0
- package/src/lsp/hover.test.ts +9 -0
- package/src/lsp/hover.ts +38 -0
- package/src/package-cli.ts +42 -0
- package/src/plugin.test.ts +128 -0
- package/src/plugin.ts +408 -0
- package/src/serializer.test.ts +270 -0
- package/src/serializer.ts +383 -0
- package/src/skills/github-actions-patterns.md +93 -0
- package/src/spec/fetch.ts +55 -0
- package/src/validate-cli.ts +19 -0
- package/src/validate.test.ts +12 -0
- package/src/validate.ts +32 -0
- package/src/variables.ts +44 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Composite } from "@intentius/chant";
|
|
2
|
+
|
|
3
|
+
export interface SetupNodeProps {
|
|
4
|
+
nodeVersion?: string;
|
|
5
|
+
registryUrl?: string;
|
|
6
|
+
cache?: string;
|
|
7
|
+
cacheFilePath?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const SetupNode = Composite<SetupNodeProps>((props) => {
|
|
11
|
+
const withObj: Record<string, string> = {};
|
|
12
|
+
if (props.nodeVersion !== undefined) withObj["node-version"] = props.nodeVersion;
|
|
13
|
+
if (props.registryUrl !== undefined) withObj["registry-url"] = props.registryUrl;
|
|
14
|
+
if (props.cache !== undefined) withObj.cache = props.cache;
|
|
15
|
+
if (props.cacheFilePath !== undefined) withObj["cache-dependency-path"] = props.cacheFilePath;
|
|
16
|
+
|
|
17
|
+
const { createProperty } = require("@intentius/chant/runtime");
|
|
18
|
+
const StepClass = createProperty("GitHub::Actions::Step", "github");
|
|
19
|
+
const step = new StepClass({
|
|
20
|
+
name: "Setup Node.js",
|
|
21
|
+
uses: "actions/setup-node@v4",
|
|
22
|
+
...(Object.keys(withObj).length > 0 ? { with: withObj } : {}),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return { step };
|
|
26
|
+
}, "SetupNode");
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Composite } from "@intentius/chant";
|
|
2
|
+
|
|
3
|
+
export interface SetupPythonProps {
|
|
4
|
+
pythonVersion?: string;
|
|
5
|
+
cache?: string;
|
|
6
|
+
architecture?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const SetupPython = Composite<SetupPythonProps>((props) => {
|
|
10
|
+
const withObj: Record<string, string> = {};
|
|
11
|
+
if (props.pythonVersion !== undefined) withObj["python-version"] = props.pythonVersion;
|
|
12
|
+
if (props.cache !== undefined) withObj.cache = props.cache;
|
|
13
|
+
if (props.architecture !== undefined) withObj.architecture = props.architecture;
|
|
14
|
+
|
|
15
|
+
const { createProperty } = require("@intentius/chant/runtime");
|
|
16
|
+
const StepClass = createProperty("GitHub::Actions::Step", "github");
|
|
17
|
+
const step = new StepClass({
|
|
18
|
+
name: "Setup Python",
|
|
19
|
+
uses: "actions/setup-python@v5",
|
|
20
|
+
...(Object.keys(withObj).length > 0 ? { with: withObj } : {}),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return { step };
|
|
24
|
+
}, "SetupPython");
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Composite } from "@intentius/chant";
|
|
2
|
+
|
|
3
|
+
export interface UploadArtifactProps {
|
|
4
|
+
name: string;
|
|
5
|
+
path: string;
|
|
6
|
+
retentionDays?: number;
|
|
7
|
+
compressionLevel?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const UploadArtifact = Composite<UploadArtifactProps>((props) => {
|
|
11
|
+
const withObj: Record<string, string> = {
|
|
12
|
+
name: props.name,
|
|
13
|
+
path: props.path,
|
|
14
|
+
};
|
|
15
|
+
if (props.retentionDays !== undefined) withObj["retention-days"] = String(props.retentionDays);
|
|
16
|
+
if (props.compressionLevel !== undefined) withObj["compression-level"] = String(props.compressionLevel);
|
|
17
|
+
|
|
18
|
+
const { createProperty } = require("@intentius/chant/runtime");
|
|
19
|
+
const StepClass = createProperty("GitHub::Actions::Step", "github");
|
|
20
|
+
const step = new StepClass({
|
|
21
|
+
name: "Upload Artifact",
|
|
22
|
+
uses: "actions/upload-artifact@v4",
|
|
23
|
+
with: withObj,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return { step };
|
|
27
|
+
}, "UploadArtifact");
|
package/src/coverage.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coverage analysis for GitHub Actions lexicon.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync } from "fs";
|
|
6
|
+
import { join, dirname } from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import {
|
|
9
|
+
computeCoverage,
|
|
10
|
+
overallPct,
|
|
11
|
+
formatSummary,
|
|
12
|
+
formatVerbose,
|
|
13
|
+
checkThresholds,
|
|
14
|
+
type CoverageReport,
|
|
15
|
+
type CoverageThresholds,
|
|
16
|
+
} from "@intentius/chant/codegen/coverage";
|
|
17
|
+
|
|
18
|
+
export type { CoverageReport, CoverageThresholds };
|
|
19
|
+
export { computeCoverage, overallPct, formatSummary, formatVerbose, checkThresholds };
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Run coverage analysis for the GitHub Actions lexicon.
|
|
23
|
+
*/
|
|
24
|
+
export async function analyzeGitHubCoverage(opts?: {
|
|
25
|
+
basePath?: string;
|
|
26
|
+
verbose?: boolean;
|
|
27
|
+
minOverall?: number;
|
|
28
|
+
}): Promise<CoverageReport> {
|
|
29
|
+
const basePath = opts?.basePath ?? dirname(dirname(fileURLToPath(import.meta.url)));
|
|
30
|
+
const lexiconPath = join(basePath, "src", "generated", "lexicon-github.json");
|
|
31
|
+
const content = readFileSync(lexiconPath, "utf-8");
|
|
32
|
+
const report = computeCoverage(content);
|
|
33
|
+
|
|
34
|
+
if (opts?.verbose) {
|
|
35
|
+
console.error(formatVerbose(report));
|
|
36
|
+
} else {
|
|
37
|
+
console.error(formatSummary(report));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (typeof opts?.minOverall === "number") {
|
|
41
|
+
const result = checkThresholds(report, { minOverallPct: opts.minOverall });
|
|
42
|
+
if (!result.ok) {
|
|
43
|
+
for (const v of result.violations) console.error(` FAIL: ${v}`);
|
|
44
|
+
throw new Error("Coverage below threshold");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return report;
|
|
49
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
Expression,
|
|
4
|
+
github, runner, secrets, matrix, steps, needs, inputs, vars, env,
|
|
5
|
+
always, failure, success, cancelled,
|
|
6
|
+
contains, startsWith, toJSON, fromJSON, format,
|
|
7
|
+
branch, tag,
|
|
8
|
+
} from "./expression";
|
|
9
|
+
|
|
10
|
+
describe("Expression", () => {
|
|
11
|
+
test("wraps raw expression in ${{ }}", () => {
|
|
12
|
+
const expr = new Expression("github.ref");
|
|
13
|
+
expect(expr.toString()).toBe("${{ github.ref }}");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("raw() returns unwrapped expression", () => {
|
|
17
|
+
const expr = new Expression("github.sha");
|
|
18
|
+
expect(expr.raw()).toBe("github.sha");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("toJSON() returns toString()", () => {
|
|
22
|
+
const expr = new Expression("github.ref");
|
|
23
|
+
expect(expr.toJSON()).toBe("${{ github.ref }}");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("toYAML() returns toString()", () => {
|
|
27
|
+
const expr = new Expression("github.ref");
|
|
28
|
+
expect(expr.toYAML()).toBe("${{ github.ref }}");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("and() combines expressions", () => {
|
|
32
|
+
const a = new Expression("github.ref == 'refs/heads/main'");
|
|
33
|
+
const b = new Expression("github.event_name == 'push'");
|
|
34
|
+
expect(a.and(b).toString()).toBe("${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }}");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("or() combines expressions", () => {
|
|
38
|
+
const a = new Expression("github.ref == 'refs/heads/main'");
|
|
39
|
+
const b = new Expression("github.ref == 'refs/heads/develop'");
|
|
40
|
+
expect(a.or(b).toString()).toBe("${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' }}");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("not() negates expression", () => {
|
|
44
|
+
const expr = new Expression("github.event.pull_request.draft");
|
|
45
|
+
expect(expr.not().toString()).toBe("${{ !(github.event.pull_request.draft) }}");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("eq() creates equality comparison", () => {
|
|
49
|
+
expect(github.ref.eq("refs/heads/main").toString()).toBe("${{ github.ref == 'refs/heads/main' }}");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("ne() creates inequality comparison", () => {
|
|
53
|
+
expect(github.ref.ne("refs/heads/main").toString()).toBe("${{ github.ref != 'refs/heads/main' }}");
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("Context accessors", () => {
|
|
58
|
+
test("github context properties", () => {
|
|
59
|
+
expect(github.ref.toString()).toBe("${{ github.ref }}");
|
|
60
|
+
expect(github.sha.toString()).toBe("${{ github.sha }}");
|
|
61
|
+
expect(github.actor.toString()).toBe("${{ github.actor }}");
|
|
62
|
+
expect(github.repository.toString()).toBe("${{ github.repository }}");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("runner context properties", () => {
|
|
66
|
+
expect(runner.os.toString()).toBe("${{ runner.os }}");
|
|
67
|
+
expect(runner.arch.toString()).toBe("${{ runner.arch }}");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("secrets accessor", () => {
|
|
71
|
+
expect(secrets("DEPLOY_KEY").toString()).toBe("${{ secrets.DEPLOY_KEY }}");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("matrix accessor", () => {
|
|
75
|
+
expect(matrix("node-version").toString()).toBe("${{ matrix.node-version }}");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("steps accessor", () => {
|
|
79
|
+
expect(steps("build").outputs("result").toString()).toBe("${{ steps.build.outputs.result }}");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("needs accessor", () => {
|
|
83
|
+
expect(needs("build").outputs("artifact-path").toString()).toBe("${{ needs.build.outputs.artifact-path }}");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("inputs accessor", () => {
|
|
87
|
+
expect(inputs("environment").toString()).toBe("${{ inputs.environment }}");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("vars accessor", () => {
|
|
91
|
+
expect(vars("API_URL").toString()).toBe("${{ vars.API_URL }}");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("env accessor", () => {
|
|
95
|
+
expect(env("NODE_ENV").toString()).toBe("${{ env.NODE_ENV }}");
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("Condition helpers", () => {
|
|
100
|
+
test("always()", () => {
|
|
101
|
+
expect(always().toString()).toBe("${{ always() }}");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("failure()", () => {
|
|
105
|
+
expect(failure().toString()).toBe("${{ failure() }}");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("success()", () => {
|
|
109
|
+
expect(success().toString()).toBe("${{ success() }}");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("cancelled()", () => {
|
|
113
|
+
expect(cancelled().toString()).toBe("${{ cancelled() }}");
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("Function helpers", () => {
|
|
118
|
+
test("contains()", () => {
|
|
119
|
+
expect(contains(github.ref, "main").toString()).toBe("${{ contains(github.ref, 'main') }}");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("startsWith()", () => {
|
|
123
|
+
expect(startsWith(github.ref, "refs/tags/").toString()).toBe("${{ startsWith(github.ref, 'refs/tags/') }}");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("toJSON()", () => {
|
|
127
|
+
expect(toJSON(github.event).toString()).toBe("${{ toJSON(github.event) }}");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("fromJSON()", () => {
|
|
131
|
+
expect(fromJSON(steps("meta").outputs("matrix")).toString()).toBe("${{ fromJSON(steps.meta.outputs.matrix) }}");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("format()", () => {
|
|
135
|
+
expect(format("{0}-{1}", github.ref, github.sha).toString()).toBe("${{ format('{0}-{1}', github.ref, github.sha) }}");
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe("Convenience helpers", () => {
|
|
140
|
+
test("branch()", () => {
|
|
141
|
+
expect(branch("main").toString()).toBe("${{ github.ref == 'refs/heads/main' }}");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("tag()", () => {
|
|
145
|
+
expect(tag("v").toString()).toBe("${{ startsWith(github.ref, 'refs/tags/v') }}");
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Actions expression system.
|
|
3
|
+
*
|
|
4
|
+
* Provides a typed Expression class and context accessors for building
|
|
5
|
+
* ${{ }} expressions in GitHub Actions workflows.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { INTRINSIC_MARKER, type Intrinsic } from "@intentius/chant/intrinsic";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A GitHub Actions expression that serializes to `${{ raw }}`.
|
|
12
|
+
*/
|
|
13
|
+
export class Expression implements Intrinsic {
|
|
14
|
+
readonly [INTRINSIC_MARKER] = true as const;
|
|
15
|
+
private readonly _raw: string;
|
|
16
|
+
|
|
17
|
+
constructor(raw: string) {
|
|
18
|
+
this._raw = raw;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The raw expression string without ${{ }} wrapper. */
|
|
22
|
+
raw(): string {
|
|
23
|
+
return this._raw;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Returns the expression wrapped in ${{ }}. */
|
|
27
|
+
toString(): string {
|
|
28
|
+
return `\${{ ${this._raw} }}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
toJSON(): string {
|
|
32
|
+
return this.toString();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** YAML serialization — plain string with ${{ }}. */
|
|
36
|
+
toYAML(): string {
|
|
37
|
+
return this.toString();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Logical AND with another expression. */
|
|
41
|
+
and(other: Expression): Expression {
|
|
42
|
+
return new Expression(`${this._raw} && ${other._raw}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Logical OR with another expression. */
|
|
46
|
+
or(other: Expression): Expression {
|
|
47
|
+
return new Expression(`${this._raw} || ${other._raw}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Logical NOT of this expression. */
|
|
51
|
+
not(): Expression {
|
|
52
|
+
return new Expression(`!(${this._raw})`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Equality comparison. */
|
|
56
|
+
eq(value: string | Expression): Expression {
|
|
57
|
+
const rhs = value instanceof Expression ? value._raw : `'${value}'`;
|
|
58
|
+
return new Expression(`${this._raw} == ${rhs}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Inequality comparison. */
|
|
62
|
+
ne(value: string | Expression): Expression {
|
|
63
|
+
const rhs = value instanceof Expression ? value._raw : `'${value}'`;
|
|
64
|
+
return new Expression(`${this._raw} != ${rhs}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ── Context accessors ─────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
/** GitHub context — github.* properties. */
|
|
71
|
+
export const github = {
|
|
72
|
+
get ref() { return new Expression("github.ref"); },
|
|
73
|
+
get sha() { return new Expression("github.sha"); },
|
|
74
|
+
get actor() { return new Expression("github.actor"); },
|
|
75
|
+
get repository() { return new Expression("github.repository"); },
|
|
76
|
+
get repositoryOwner() { return new Expression("github.repository_owner"); },
|
|
77
|
+
get event() { return new Expression("github.event"); },
|
|
78
|
+
get eventName() { return new Expression("github.event_name"); },
|
|
79
|
+
get runId() { return new Expression("github.run_id"); },
|
|
80
|
+
get runNumber() { return new Expression("github.run_number"); },
|
|
81
|
+
get workflow() { return new Expression("github.workflow"); },
|
|
82
|
+
get workspace() { return new Expression("github.workspace"); },
|
|
83
|
+
get token() { return new Expression("github.token"); },
|
|
84
|
+
get job() { return new Expression("github.job"); },
|
|
85
|
+
get refName() { return new Expression("github.ref_name"); },
|
|
86
|
+
get refType() { return new Expression("github.ref_type"); },
|
|
87
|
+
get headRef() { return new Expression("github.head_ref"); },
|
|
88
|
+
get baseRef() { return new Expression("github.base_ref"); },
|
|
89
|
+
get serverUrl() { return new Expression("github.server_url"); },
|
|
90
|
+
get apiUrl() { return new Expression("github.api_url"); },
|
|
91
|
+
get graphqlUrl() { return new Expression("github.graphql_url"); },
|
|
92
|
+
get action() { return new Expression("github.action"); },
|
|
93
|
+
get actionPath() { return new Expression("github.action_path"); },
|
|
94
|
+
get triggeringActor() { return new Expression("github.triggering_actor"); },
|
|
95
|
+
} as const;
|
|
96
|
+
|
|
97
|
+
/** Runner context — runner.* properties. */
|
|
98
|
+
export const runner = {
|
|
99
|
+
get os() { return new Expression("runner.os"); },
|
|
100
|
+
get arch() { return new Expression("runner.arch"); },
|
|
101
|
+
get temp() { return new Expression("runner.temp"); },
|
|
102
|
+
get toolCache() { return new Expression("runner.tool_cache"); },
|
|
103
|
+
get name() { return new Expression("runner.name"); },
|
|
104
|
+
} as const;
|
|
105
|
+
|
|
106
|
+
/** Access a secret by name. */
|
|
107
|
+
export function secrets(name: string): Expression {
|
|
108
|
+
return new Expression(`secrets.${name}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Access a matrix value by key. */
|
|
112
|
+
export function matrix(key: string): Expression {
|
|
113
|
+
return new Expression(`matrix.${key}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Access a step output. */
|
|
117
|
+
export function steps(id: string): { outputs(name: string): Expression } {
|
|
118
|
+
return {
|
|
119
|
+
outputs(name: string): Expression {
|
|
120
|
+
return new Expression(`steps.${id}.outputs.${name}`);
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Access a job output from a needed job. */
|
|
126
|
+
export function needs(job: string): { outputs(name: string): Expression } {
|
|
127
|
+
return {
|
|
128
|
+
outputs(name: string): Expression {
|
|
129
|
+
return new Expression(`needs.${job}.outputs.${name}`);
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Access a workflow input. */
|
|
135
|
+
export function inputs(name: string): Expression {
|
|
136
|
+
return new Expression(`inputs.${name}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Access a configuration variable. */
|
|
140
|
+
export function vars(name: string): Expression {
|
|
141
|
+
return new Expression(`vars.${name}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Access an environment variable. */
|
|
145
|
+
export function env(name: string): Expression {
|
|
146
|
+
return new Expression(`env.${name}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ── Condition helpers ─────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
/** Always run, regardless of status. */
|
|
152
|
+
export function always(): Expression {
|
|
153
|
+
return new Expression("always()");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Run only if a previous step has failed. */
|
|
157
|
+
export function failure(): Expression {
|
|
158
|
+
return new Expression("failure()");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Run only if all previous steps succeeded (default). */
|
|
162
|
+
export function success(): Expression {
|
|
163
|
+
return new Expression("success()");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Run only if the workflow was cancelled. */
|
|
167
|
+
export function cancelled(): Expression {
|
|
168
|
+
return new Expression("cancelled()");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ── Function helpers ──────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
/** Check if a string contains a substring. */
|
|
174
|
+
export function contains(haystack: Expression | string, needle: Expression | string): Expression {
|
|
175
|
+
const h = haystack instanceof Expression ? haystack.raw() : `'${haystack}'`;
|
|
176
|
+
const n = needle instanceof Expression ? needle.raw() : `'${needle}'`;
|
|
177
|
+
return new Expression(`contains(${h}, ${n})`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Check if a string starts with a prefix. */
|
|
181
|
+
export function startsWith(value: Expression | string, prefix: Expression | string): Expression {
|
|
182
|
+
const v = value instanceof Expression ? value.raw() : `'${value}'`;
|
|
183
|
+
const p = prefix instanceof Expression ? prefix.raw() : `'${prefix}'`;
|
|
184
|
+
return new Expression(`startsWith(${v}, ${p})`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Convert a value to JSON. */
|
|
188
|
+
export function toJSON(value: Expression): Expression {
|
|
189
|
+
return new Expression(`toJSON(${value.raw()})`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Parse a JSON string. */
|
|
193
|
+
export function fromJSON(json: Expression | string): Expression {
|
|
194
|
+
const j = json instanceof Expression ? json.raw() : `'${json}'`;
|
|
195
|
+
return new Expression(`fromJSON(${j})`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Format a string with placeholders. */
|
|
199
|
+
export function format(template: string, ...args: Expression[]): Expression {
|
|
200
|
+
const argStrs = args.map((a) => a.raw()).join(", ");
|
|
201
|
+
return new Expression(`format('${template}', ${argStrs})`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ── Convenience helpers ───────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
/** Check if the ref matches a branch name. */
|
|
207
|
+
export function branch(name: string): Expression {
|
|
208
|
+
return github.ref.eq(`refs/heads/${name}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Check if the ref matches a tag prefix. */
|
|
212
|
+
export function tag(name: string): Expression {
|
|
213
|
+
return startsWith(github.ref, `refs/tags/${name}`);
|
|
214
|
+
}
|