Merge branch 'main' into elbrenn/expression-complete

This commit is contained in:
Beth Brennan
2023-02-07 17:23:37 -05:00
committed by GitHub
121 changed files with 3265 additions and 1204 deletions
+5
View File
@@ -0,0 +1,5 @@
node_modules
dist
*.md
*.js
*.json
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@github/actions-workflow-parser",
"version": "0.1.113",
"version": "0.1.126",
"license": "MIT",
"type": "module",
"source": "./src/index.ts",
@@ -40,7 +40,7 @@
"watch": "tsc --build tsconfig.build.json --watch"
},
"dependencies": {
"@github/actions-expressions": "^0.1.113",
"@github/actions-expressions": "^0.1.126",
"cronstrue": "^2.21.0",
"yaml": "^2.0.0-8"
},
+31 -38
View File
@@ -6,19 +6,16 @@ import {parseWorkflow} from "./workflows/workflow-parser";
describe("Workflow Expression Parsing", () => {
it("preserves original expressions when building format", () => {
const result = parseWorkflow(
"test.yaml",
[
{
name: "test.yaml",
content: `on: push
{
name: "test.yaml",
content: `on: push
run-name: Test \${{ github.event_name }} \${{ github.ref }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo 'hello'`
}
],
},
nullTrace
);
@@ -44,20 +41,17 @@ jobs:
it("preserves original expressions when building format for multi-line strings", () => {
const result = parseWorkflow(
"test.yaml",
[
{
name: "test.yaml",
content: `on: push
{
name: "test.yaml",
content: `on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: |
echo \${{ github.event_name }}
echo 'hello' \${{ github.ref }}`
}
],
echo 'hello' \${{github.ref }}`
},
nullTrace
);
@@ -81,19 +75,27 @@ jobs:
}
expect(stepRun.originalExpressions).toHaveLength(2);
expect(stepRun.originalExpressions!.map(x => [x.toDisplayString(), x.range])).toEqual([
expect(stepRun.originalExpressions!.map(x => [x.toDisplayString(), x.range, x.expressionRange])).toEqual([
[
"${{ github.event_name }}",
{
start: {line: 7, column: 16},
end: {line: 7, column: 40}
},
{
start: {line: 7, column: 20},
end: {line: 7, column: 37}
}
],
[
"${{ github.ref }}",
{
start: {line: 8, column: 24},
end: {line: 8, column: 41}
end: {line: 8, column: 40}
},
{
start: {line: 8, column: 27},
end: {line: 8, column: 37}
}
]
]);
@@ -101,11 +103,9 @@ jobs:
it("return errors and string token with preserved expressions for (multiple) expression errors", () => {
const result = parseWorkflow(
"test.yaml",
[
{
name: "test.yaml",
content: `on: push
{
name: "test.yaml",
content: `on: push
jobs:
build:
runs-on: ubuntu-latest
@@ -113,8 +113,7 @@ jobs:
- run: |
echo \${{ abc }}
echo 'hello' \${{ gith }}`
}
],
},
nullTrace
);
@@ -139,11 +138,9 @@ jobs:
it("reports all errors for multi-line expressions at the correct locations", () => {
const result = parseWorkflow(
"test.yaml",
[
{
name: "test.yaml",
content: `on: push
{
name: "test.yaml",
content: `on: push
jobs:
build:
runs-on: ubuntu-latest
@@ -151,8 +148,7 @@ jobs:
- run: |
echo \${{ fromJSON2('test') }}
echo 'hello' \${{ toJSON2(inputs.test) }}`
}
],
},
nullTrace
);
@@ -178,19 +174,16 @@ jobs:
it("parses isExpression strings into expression tokens", () => {
const result = parseWorkflow(
"test.yaml",
[
{
name: "test.yaml",
content: `on: push
{
name: "test.yaml",
content: `on: push
jobs:
build:
runs-on: ubuntu-latest
if: github.event_name == 'push'
steps:
- run: echo 'hello'`
}
],
},
nullTrace
);
+17 -29
View File
@@ -5,13 +5,10 @@ import {parseWorkflow} from "./workflows/workflow-parser";
describe("parseWorkflow", () => {
it("parses valid workflow", () => {
const result = parseWorkflow(
"test.yaml",
[
{
name: "test.yaml",
content: "on: push\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - run: echo 'hello'"
}
],
{
name: "test.yaml",
content: "on: push\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - run: echo 'hello'"
},
nullTrace
);
@@ -20,13 +17,10 @@ describe("parseWorkflow", () => {
it("contains range for error", () => {
const result = parseWorkflow(
"test.yaml",
[
{
name: "test.yaml",
content: "on: push\njobs:\n build:\n steps:\n - run: echo 'hello'"
}
],
{
name: "test.yaml",
content: "on: push\njobs:\n build:\n steps:\n - run: echo 'hello'"
},
nullTrace
);
@@ -40,19 +34,16 @@ describe("parseWorkflow", () => {
it("error range for expression is constrained to scalar node", () => {
const result = parseWorkflow(
"test.yaml",
[
{
name: "test.yaml",
content: `on: push
{
name: "test.yaml",
content: `on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: \${{ github.event = 12 }}
run: echo 'hello'`
}
],
},
nullTrace
);
@@ -71,14 +62,11 @@ jobs:
it("tokens contain descriptions", () => {
const result = parseWorkflow(
"test.yaml",
[
{
name: "test.yaml",
content:
"on: push\nname: hello\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - run: echo 'hello'"
}
],
{
name: "test.yaml",
content:
"on: push\nname: hello\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - run: echo 'hello'"
},
nullTrace
);
@@ -7,22 +7,19 @@ function serializeTemplate(template: unknown): unknown {
}
describe("convertWorkflowTemplate", () => {
it("converts workflow with one job", () => {
it("converts workflow with one job", async () => {
const result = parseWorkflow(
"wf.yaml",
[
{
name: "wf.yaml",
content: `on: push
{
name: "wf.yaml",
content: `on: push
jobs:
build:
runs-on: ubuntu-latest`
}
],
},
nullTrace
);
const template = convertWorkflowTemplate(result.context, result.value!, ErrorPolicy.TryConversion);
const template = await convertWorkflowTemplate(result.context, result.value!, ErrorPolicy.TryConversion);
expect(serializeTemplate(template)).toEqual({
events: {
@@ -46,13 +43,11 @@ jobs:
});
});
it("converts workflow if expressions", () => {
it("converts workflow if expressions", async () => {
const result = parseWorkflow(
"wf.yaml",
[
{
name: "wf.yaml",
content: `on: push
{
name: "wf.yaml",
content: `on: push
jobs:
build:
if: \${{ true }}
@@ -60,12 +55,11 @@ jobs:
deploy:
if: true
runs-on: ubuntu-latest`
}
],
},
nullTrace
);
const template = convertWorkflowTemplate(result.context, result.value!, ErrorPolicy.TryConversion);
const template = await convertWorkflowTemplate(result.context, result.value!, ErrorPolicy.TryConversion);
expect(serializeTemplate(template)).toEqual({
events: {
@@ -98,23 +92,20 @@ jobs:
});
});
it("converts workflow with empty needs", () => {
it("converts workflow with empty needs", async () => {
const result = parseWorkflow(
"wf.yaml",
[
{
name: "wf.yaml",
content: `on: push
{
name: "wf.yaml",
content: `on: push
jobs:
build:
needs: # comment to preserve whitespace in test
runs-on: ubuntu-latest`
}
],
},
nullTrace
);
const template = convertWorkflowTemplate(result.context, result.value!, ErrorPolicy.TryConversion);
const template = await convertWorkflowTemplate(result.context, result.value!, ErrorPolicy.TryConversion);
expect(serializeTemplate(template)).toEqual({
errors: [
@@ -143,13 +134,11 @@ jobs:
});
});
it("converts workflow with needs errors", () => {
it("converts workflow with needs errors", async () => {
const result = parseWorkflow(
"wf.yaml",
[
{
name: "wf.yaml",
content: `on: push
{
name: "wf.yaml",
content: `on: push
jobs:
job1:
needs: [unknown-job, job3]
@@ -159,12 +148,11 @@ jobs:
job3:
needs: job1
runs-on: ubuntu-latest`
}
],
},
nullTrace
);
const template = convertWorkflowTemplate(result.context, result.value!, ErrorPolicy.TryConversion);
const template = await convertWorkflowTemplate(result.context, result.value!, ErrorPolicy.TryConversion);
expect(serializeTemplate(template)).toEqual({
errors: [
@@ -223,13 +211,11 @@ jobs:
});
});
it("converts workflow with invalid on", () => {
it("converts workflow with invalid on", async () => {
const result = parseWorkflow(
"wf.yaml",
[
{
name: "wf.yaml",
content: `on:
{
name: "wf.yaml",
content: `on:
workflow_dispatch:
inputs:
test:
@@ -239,12 +225,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- run: echo hello`
}
],
},
nullTrace
);
const template = convertWorkflowTemplate(result.context, result.value!, ErrorPolicy.TryConversion);
const template = await convertWorkflowTemplate(result.context, result.value!, ErrorPolicy.TryConversion);
expect(template.jobs).not.toBeUndefined();
expect(template.jobs).toHaveLength(1);
@@ -286,21 +271,18 @@ jobs:
});
});
it("converts workflow with invalid jobs", () => {
it("converts workflow with invalid jobs", async () => {
const result = parseWorkflow(
"wf.yaml",
[
{
name: "wf.yaml",
content: `on: push
{
name: "wf.yaml",
content: `on: push
jobs:
build:`
}
],
},
nullTrace
);
const template = convertWorkflowTemplate(result.context, result.value!, ErrorPolicy.TryConversion);
const template = await convertWorkflowTemplate(result.context, result.value!, ErrorPolicy.TryConversion);
expect(template.jobs).not.toBeUndefined();
expect(template.jobs).toHaveLength(0);
+76 -3
View File
@@ -1,9 +1,14 @@
import {TemplateContext} from "../templates/template-context";
import {TemplateToken, TemplateTokenError} from "../templates/tokens/template-token";
import {FileProvider} from "../workflows/file-provider";
import {parseFileReference} from "../workflows/file-reference";
import {parseWorkflow} from "../workflows/workflow-parser";
import {convertConcurrency} from "./converter/concurrency";
import {convertOn} from "./converter/events";
import {handleTemplateTokenErrors} from "./converter/handle-errors";
import {convertJobs} from "./converter/jobs";
import {convertReferencedWorkflow} from "./converter/referencedWorkflow";
import {isReusableWorkflowJob} from "./type-guards";
import {WorkflowTemplate} from "./workflow-template";
export enum ErrorPolicy {
@@ -11,11 +16,35 @@ export enum ErrorPolicy {
TryConversion
}
export function convertWorkflowTemplate(
export type WorkflowTemplateConverterOptions = {
/**
* The maximum depth of reusable workflows allowed in the workflow.
* If this depth is exceeded, an error will be reported.
* If {@link fetchReusableWorkflowDepth} is less than this value, the maximum depth
* won't be enforced.
* Default: 4
*/
maxReusableWorkflowDepth?: number;
/**
* The depth to fetch reusable workflows, up to {@link maxReusableWorkflowDepth}.
* Currently only a fetch depth of 0 or 1 is supported.
* Default: 0
*/
fetchReusableWorkflowDepth?: number;
};
const defaultOptions: Required<WorkflowTemplateConverterOptions> = {
maxReusableWorkflowDepth: 4,
fetchReusableWorkflowDepth: 0
};
export async function convertWorkflowTemplate(
context: TemplateContext,
root: TemplateToken,
errorPolicy: ErrorPolicy = ErrorPolicy.ReturnErrorsOnly
): WorkflowTemplate {
errorPolicy: ErrorPolicy = ErrorPolicy.ReturnErrorsOnly,
fileProvider?: FileProvider,
options: WorkflowTemplateConverterOptions = defaultOptions
): Promise<WorkflowTemplate> {
const result = {} as WorkflowTemplate;
if (context.errors.getErrors().length > 0 && errorPolicy === ErrorPolicy.ReturnErrorsOnly) {
@@ -25,6 +54,12 @@ export function convertWorkflowTemplate(
return result;
}
const opts = getOptionsWithDefaults(options);
if (fileProvider === undefined && opts.fetchReusableWorkflowDepth > 0) {
context.error(root, new Error("A file provider is required to fetch reusable workflows"));
}
try {
const rootMapping = root.assertMapping("root");
@@ -49,6 +84,31 @@ export function convertWorkflowTemplate(
break;
}
}
// Load referenced workflows
for (const job of result.jobs || []) {
if (isReusableWorkflowJob(job)) {
if (opts.maxReusableWorkflowDepth === 0) {
context.error(job.ref, new Error("Reusable workflows are not allowed"));
continue;
}
if (opts.fetchReusableWorkflowDepth === 0 || fileProvider === undefined) {
continue;
}
try {
const file = await fileProvider.getFileContent(parseFileReference(job.ref.value));
const workflow = parseWorkflow(file, context);
if (!workflow.value) {
continue;
}
convertReferencedWorkflow(context, workflow.value, job);
} catch {
context.error(job.ref, new Error("Unable to find reusable workflow"));
}
}
}
} catch (err) {
if (err instanceof TemplateTokenError) {
context.error(err.token, err);
@@ -66,3 +126,16 @@ export function convertWorkflowTemplate(
return result;
}
function getOptionsWithDefaults(options: WorkflowTemplateConverterOptions): Required<WorkflowTemplateConverterOptions> {
return {
maxReusableWorkflowDepth:
options.maxReusableWorkflowDepth !== undefined
? options.maxReusableWorkflowDepth
: defaultOptions.maxReusableWorkflowDepth,
fetchReusableWorkflowDepth:
options.fetchReusableWorkflowDepth !== undefined
? options.fetchReusableWorkflowDepth
: defaultOptions.fetchReusableWorkflowDepth
};
}
@@ -0,0 +1,145 @@
import {TemplateContext} from "../../templates/template-context";
import {StringToken, MappingToken, BasicExpressionToken, TemplateToken, ScalarToken} from "../../templates/tokens";
import {isSequence, isString} from "../../templates/tokens/type-guards";
import {WorkflowJob, Step} from "../workflow-template";
import {convertConcurrency} from "./concurrency";
import {convertToJobContainer, convertToJobServices} from "./container";
import {handleTemplateTokenErrors} from "./handle-errors";
import {IdBuilder} from "./id-builder";
import {convertToActionsEnvironmentRef} from "./job/environment";
import {convertRunsOn} from "./job/runs-on";
import {convertSteps} from "./steps";
export function convertJob(context: TemplateContext, jobKey: StringToken, token: MappingToken): WorkflowJob {
const error = new IdBuilder().tryAddKnownId(jobKey.value);
if (error) {
context.error(jobKey, error);
}
let concurrency, container, env, environment, name, outputs, runsOn, services, strategy: TemplateToken | undefined;
let needs: StringToken[] | undefined = undefined;
let steps: Step[] = [];
let workflowJobRef: StringToken | undefined;
let workflowJobInputs: MappingToken | undefined;
for (const item of token) {
const propertyName = item.key.assertString("job property name");
switch (propertyName.value) {
case "concurrency":
handleTemplateTokenErrors(item.value, context, undefined, () => convertConcurrency(context, item.value));
concurrency = item.value;
break;
case "container":
convertToJobContainer(context, item.value);
container = item.value;
break;
case "env":
env = item.value.assertMapping("job env");
break;
case "environment":
handleTemplateTokenErrors(item.value, context, undefined, () =>
convertToActionsEnvironmentRef(context, item.value)
);
environment = item.value;
break;
case "name":
name = item.value.assertScalar("job name");
break;
case "needs": {
needs = [];
if (isString(item.value)) {
const jobNeeds = item.value.assertString("job needs id");
needs.push(jobNeeds);
}
if (isSequence(item.value)) {
for (const seqItem of item.value) {
const jobNeeds = seqItem.assertString("job needs id");
needs.push(jobNeeds);
}
}
break;
}
case "outputs":
outputs = item.value.assertMapping("job outputs");
break;
case "runs-on":
handleTemplateTokenErrors(item.value, context, undefined, () => convertRunsOn(context, item.value));
runsOn = item.value;
break;
case "services":
convertToJobServices(context, item.value);
services = item.value;
break;
case "steps":
steps = convertSteps(context, item.value);
break;
case "strategy":
strategy = item.value;
break;
case "uses":
workflowJobRef = item.value.assertString("job uses value");
break;
case "with":
workflowJobInputs = item.value.assertMapping("uses-with value");
break;
}
}
if (workflowJobRef !== undefined) {
return {
type: "reusableWorkflowJob",
id: jobKey,
name: jobName(name, jobKey),
needs: needs ?? [],
if: new BasicExpressionToken(undefined, undefined, "success()", undefined, undefined, undefined),
ref: workflowJobRef,
"input-definitions": undefined,
"input-values": workflowJobInputs,
outputs: undefined,
concurrency,
strategy
};
} else {
return {
type: "job",
id: jobKey,
name: jobName(name, jobKey),
needs,
if: new BasicExpressionToken(undefined, undefined, "success()", undefined, undefined, undefined),
env,
concurrency,
environment,
strategy,
"runs-on": runsOn,
container,
services,
outputs,
steps
};
}
}
function jobName(name: ScalarToken | undefined, jobKey: StringToken): ScalarToken {
if (name === undefined) {
return jobKey;
}
if (isString(name) && name.value === "") {
return jobKey;
}
return name;
}
@@ -0,0 +1,54 @@
import {TemplateContext} from "../../../templates/template-context";
import {MappingToken, TemplateToken} from "../../../templates/tokens";
import {ReusableWorkflowJob} from "../../workflow-template";
type TokenMap = Map<string, [key: string, value: TemplateToken]>;
export function convertWorkflowJobInputs(context: TemplateContext, job: ReusableWorkflowJob) {
const inputDefinitions = createTokenMap(
job["input-definitions"]?.assertMapping("workflow job input definitions"),
"inputs"
);
const inputValues = createTokenMap(job["input-values"]?.assertMapping("workflow job input values"), "with");
if (inputDefinitions !== undefined) {
for (const [_, [name, value]] of inputDefinitions) {
const inputSpec = createTokenMap(value.assertMapping(`input ${name}`), `input ${name} key`)!;
const inputTypeToken = inputSpec.get("type")?.[1];
if (!inputTypeToken) {
// This should be validated by the template reader per the schema
continue;
}
const inputSet = inputValues !== undefined && inputValues.has(name.toLowerCase());
const required = inputSpec.get("required")?.[1].assertBoolean(`input ${name} required`).value;
if (required && !inputSet) {
context.error(job.ref, `Input ${name} is required, but not provided while calling.`);
}
}
}
if (inputValues !== undefined) {
for (const [_, [name, value]] of inputValues) {
if (!inputDefinitions?.has(name.toLowerCase())) {
context.error(value, `Invalid input, ${name} is not defined in the referenced workflow.`);
}
}
}
}
function createTokenMap(mapping: MappingToken | undefined, description: string): TokenMap | undefined {
if (!mapping) {
return undefined;
}
const result = new Map<string, [key: string, value: TemplateToken]>();
for (const item of mapping) {
const name = item.key.assertString(`${description} key`);
result.set(name.value.toLowerCase(), [name.value, item.value]);
}
return result;
}
@@ -0,0 +1,102 @@
import {TemplateContext} from "../../../templates/template-context";
import {TemplateToken} from "../../../templates/tokens";
import {isMapping, isString, isSequence} from "../../../templates/tokens/type-guards";
type RunsOn = {
labels: Set<string>;
group: string;
};
export function convertRunsOn(context: TemplateContext, token: TemplateToken): RunsOn {
const labels = convertRunsOnLabels(token);
if (!isMapping(token)) {
return {
labels,
group: ""
};
}
let group = "";
for (const item of token) {
const key = item.key.assertString("job runs-on property name");
switch (key.value) {
case "group": {
if (item.value.isExpression) {
continue;
}
const groupName = item.value.assertString("job runs-on group name").value;
const names = groupName.split("/");
switch (names.length) {
case 1: {
group = groupName;
break;
}
case 2: {
if (!["org", "organization", "ent", "enterprise"].includes(names[0])) {
context.error(
item.value,
`Invalid runs-on group name '${groupName}. Please use 'organization/' or 'enterprise/' prefix to target a single runner group.'`
);
continue;
}
if (!names[1]) {
context.error(item.value, `Invalid runs-on group name '${groupName}'.`);
continue;
}
group = groupName;
break;
}
default: {
context.error(
item.value,
`Invalid runs-on group name '${groupName}. Please use 'organization/' or 'enterprise/' prefix to target a single runner group.'`
);
break;
}
}
break;
}
case "labels": {
const mapLabels = convertRunsOnLabels(item.value);
for (const label of mapLabels) {
labels.add(label);
}
break;
}
}
}
return {
labels,
group
};
}
function convertRunsOnLabels(token: TemplateToken): Set<string> {
const labels = new Set<string>();
if (token.isExpression) {
return labels;
}
if (isString(token)) {
labels.add(token.value);
return labels;
}
if (isSequence(token)) {
for (const item of token) {
if (item.isExpression) {
continue;
}
const label = item.assertString("job runs-on label sequence item");
labels.add(label.value);
}
}
return labels;
}
@@ -1,23 +1,19 @@
import {TemplateContext} from "../../templates/template-context";
import {BasicExpressionToken, MappingToken, StringToken} from "../../templates/tokens";
import {StringToken} from "../../templates/tokens";
import {TemplateToken} from "../../templates/tokens/template-token";
import {isMapping, isSequence, isString} from "../../templates/tokens/type-guards";
import {Job} from "../workflow-template";
import {convertConcurrency} from "./concurrency";
import {convertToJobContainer, convertToJobServices} from "./container";
import {isMapping} from "../../templates/tokens/type-guards";
import {WorkflowJob} from "../workflow-template";
import {handleTemplateTokenErrors} from "./handle-errors";
import {IdBuilder} from "./id-builder";
import {convertToActionsEnvironmentRef} from "./job/environment";
import {convertSteps} from "./steps";
import {convertJob} from "./job";
type nodeInfo = {
name: string;
needs: StringToken[];
};
export function convertJobs(context: TemplateContext, token: TemplateToken): Job[] {
export function convertJobs(context: TemplateContext, token: TemplateToken): WorkflowJob[] {
if (isMapping(token)) {
const result: Job[] = [];
const result: WorkflowJob[] = [];
const jobsWithSatisfiedNeeds: nodeInfo[] = [];
const alljobsWithUnsatisfiedNeeds: nodeInfo[] = [];
@@ -53,7 +49,7 @@ export function convertJobs(context: TemplateContext, token: TemplateToken): Job
function validateNeeds(
token: TemplateToken,
context: TemplateContext,
result: Job[],
result: WorkflowJob[],
jobsWithSatisfiedNeeds: nodeInfo[],
alljobsWithUnsatisfiedNeeds: nodeInfo[]
) {
@@ -101,199 +97,3 @@ function validateNeeds(
}
}
function convertJob(context: TemplateContext, jobKey: StringToken, token: MappingToken): Job {
const error = new IdBuilder().tryAddKnownId(jobKey.value);
if (error) {
context.error(jobKey, error);
}
const result: Job = {
type: "job",
id: jobKey,
name: undefined,
needs: undefined,
if: new BasicExpressionToken(undefined, undefined, "success()", undefined, undefined, "success()"),
env: undefined,
concurrency: undefined,
environment: undefined,
strategy: undefined,
"runs-on": undefined,
container: undefined,
services: undefined,
outputs: undefined,
steps: []
};
for (const item of token) {
const propertyName = item.key.assertString("job property name");
switch (propertyName.value) {
case "concurrency":
handleTemplateTokenErrors(item.value, context, undefined, () => convertConcurrency(context, item.value));
result.concurrency = item.value;
break;
case "container":
// Do early validation, but don't convert
convertToJobContainer(context, item.value);
result.container = item.value;
break;
case "env":
result.env = item.value.assertMapping("job env");
break;
case "environment":
handleTemplateTokenErrors(item.value, context, undefined, () =>
convertToActionsEnvironmentRef(context, item.value)
);
result.environment = item.value;
break;
case "name":
result.name = item.value.assertScalar("job name");
break;
case "needs":
result.needs = [];
if (isString(item.value)) {
const jobNeeds = item.value.assertString("job needs id");
result.needs.push(jobNeeds);
}
if (isSequence(item.value)) {
for (const seqItem of item.value) {
const jobNeeds = seqItem.assertString("job needs id");
result.needs.push(jobNeeds);
}
}
break;
case "outputs":
result.outputs = item.value.assertMapping("job outputs");
break;
case "runs-on":
handleTemplateTokenErrors(item.value, context, undefined, () => convertRunsOn(context, item.value));
result["runs-on"] = item.value;
break;
case "services":
// Do early validation, but don't convert
convertToJobServices(context, item.value);
result.services = item.value;
break;
case "steps":
result.steps = convertSteps(context, item.value);
break;
case "strategy":
result.strategy = item.value;
break;
}
}
if (!result.name) {
result.name = result.id;
}
return result;
}
type RunsOn = {
labels: Set<string>;
group: string;
};
function convertRunsOn(context: TemplateContext, token: TemplateToken): RunsOn {
const labels = convertRunsOnLabels(token);
if (!isMapping(token)) {
return {
labels,
group: ""
};
}
let group = "";
for (const item of token) {
const key = item.key.assertString("job runs-on property name");
switch (key.value) {
case "group": {
if (item.value.isExpression) {
continue;
}
const groupName = item.value.assertString("job runs-on group name").value;
const names = groupName.split("/");
switch (names.length) {
case 1: {
group = groupName;
break;
}
case 2: {
if (!["org", "organization", "ent", "enterprise"].includes(names[0])) {
context.error(
item.value,
`Invalid runs-on group name '${groupName}. Please use 'organization/' or 'enterprise/' prefix to target a single runner group.'`
);
continue;
}
if (!names[1]) {
context.error(item.value, `Invalid runs-on group name '${groupName}'.`);
continue;
}
group = groupName;
break;
}
default: {
context.error(
item.value,
`Invalid runs-on group name '${groupName}. Please use 'organization/' or 'enterprise/' prefix to target a single runner group.'`
);
break;
}
}
break;
}
case "labels": {
const mapLabels = convertRunsOnLabels(item.value);
for (const label of mapLabels) {
labels.add(label);
}
break;
}
}
}
return {
labels,
group
};
}
function convertRunsOnLabels(token: TemplateToken): Set<string> {
const labels = new Set<string>();
if (token.isExpression) {
return labels;
}
if (isString(token)) {
labels.add(token.value);
return labels;
}
if (isSequence(token)) {
for (const item of token) {
if (item.isExpression) {
continue;
}
const label = item.assertString("job runs-on label sequence item");
labels.add(label.value);
}
}
return labels;
}
@@ -0,0 +1,97 @@
import {TemplateContext} from "../../templates/template-context";
import {TemplateToken} from "../../templates/tokens";
import {TokenType} from "../../templates/tokens/types";
import {ReusableWorkflowJob} from "../workflow-template";
import {handleTemplateTokenErrors} from "./handle-errors";
import {convertWorkflowJobInputs} from "./job/inputs";
import {convertJobs} from "./jobs";
export function convertReferencedWorkflow(
context: TemplateContext,
referencedWorkflow: TemplateToken,
job: ReusableWorkflowJob
) {
const mapping = referencedWorkflow.assertMapping("root");
// The language service doesn't currently handles on other documents,
// So use the ref in the original workflow as the error location
const tokenForErrors = job.ref;
for (const pair of mapping) {
const key = pair.key.assertString("root key");
switch (key.value) {
case "on": {
handleTemplateTokenErrors(tokenForErrors, context, undefined, () =>
convertReferencedWorkflowOn(context, pair.value, job)
);
break;
}
case "jobs": {
job.jobs = handleTemplateTokenErrors(tokenForErrors, context, [], () => convertJobs(context, pair.value));
break;
}
}
}
}
function convertReferencedWorkflowOn(context: TemplateContext, on: TemplateToken, job: ReusableWorkflowJob) {
const tokenForErrors = job.ref;
switch (on.templateTokenType) {
case TokenType.String: {
const event = on.assertString("Reference workflow on value").value;
if (event === "workflow_call") {
handleTemplateTokenErrors(tokenForErrors, context, undefined, () => convertWorkflowJobInputs(context, job));
return;
}
break;
}
case TokenType.Sequence: {
const events = on.assertSequence("Reference workflow on value");
for (const eventToken of events) {
const event = eventToken.assertString(`Reference workflow on value ${eventToken}`).value;
if (event === "workflow_call") {
handleTemplateTokenErrors(tokenForErrors, context, undefined, () => convertWorkflowJobInputs(context, job));
return;
}
}
break;
}
case TokenType.Mapping: {
const eventMapping = on.assertMapping("Reference workflow on value");
for (const pair of eventMapping) {
const event = pair.key.assertString(`Reference workflow on value ${pair.key}`).value;
if (event !== "workflow_call") {
continue;
}
if (pair.value.templateTokenType === TokenType.Null) {
handleTemplateTokenErrors(tokenForErrors, context, undefined, () => convertWorkflowJobInputs(context, job));
return;
}
const definitions = pair.value.assertMapping(`Reference workflow on value ${pair.key}`);
for (const definition of definitions) {
const definitionKey = definition.key.assertString(`on-workflow_call-${definition.key}`).value;
switch (definitionKey) {
case "inputs":
job["input-definitions"] = definition.value.assertMapping(`on-workflow_call-${definition.key}`);
break;
case "outputs":
job.outputs = definition.value.assertMapping(`on-workflow_call-${definition.key}`);
break;
}
}
handleTemplateTokenErrors(tokenForErrors, context, undefined, () => convertWorkflowJobInputs(context, job));
return;
}
break;
}
}
context.error(tokenForErrors, "workflow_call key is not defined in the referenced workflow.");
}
@@ -52,7 +52,7 @@ function convertStep(context: TemplateContext, idBuilder: IdBuilder, step: Templ
let uses: StringToken | undefined;
let continueOnError: boolean | undefined;
let env: MappingToken | undefined;
const ifCondition = new BasicExpressionToken(undefined, undefined, "success()", undefined, undefined, "success()");
const ifCondition = new BasicExpressionToken(undefined, undefined, "success()", undefined, undefined, undefined);
for (const item of mapping) {
const key = item.key.assertString("steps item key");
switch (key.value) {
@@ -1,4 +1,4 @@
import {ActionStep, RunStep, Step} from "./workflow-template";
import {ActionStep, Job, ReusableWorkflowJob, RunStep, Step, WorkflowJob} from "./workflow-template";
export function isRunStep(step: Step): step is RunStep {
return (step as RunStep).run !== undefined;
@@ -7,3 +7,11 @@ export function isRunStep(step: Step): step is RunStep {
export function isActionStep(step: Step): step is ActionStep {
return (step as ActionStep).uses !== undefined;
}
export function isJob(job: WorkflowJob): job is Job {
return job.type === "job";
}
export function isReusableWorkflowJob(job: WorkflowJob): job is ReusableWorkflowJob {
return job.type === "reusableWorkflowJob";
}
@@ -9,7 +9,7 @@ import {
export type WorkflowTemplate = {
events: EventsConfig;
jobs: Job[];
jobs: WorkflowJob[];
concurrency: TemplateToken;
env: TemplateToken;
@@ -28,23 +28,41 @@ export type ActionsEnvironmentReference = {
url?: TemplateToken;
};
export type Job = {
type: string;
export type WorkflowJob = Job | ReusableWorkflowJob;
export type JobType = "job" | "reusableWorkflowJob";
export type BaseJob = {
type: JobType;
id: StringToken;
name?: ScalarToken;
needs?: StringToken[];
if: BasicExpressionToken;
env?: MappingToken;
concurrency?: TemplateToken;
environment?: TemplateToken;
strategy?: TemplateToken;
outputs?: MappingToken;
};
// `job-factory` in the schema
export type Job = BaseJob & {
type: "job";
env?: MappingToken;
environment?: TemplateToken;
"runs-on"?: TemplateToken;
container?: TemplateToken;
services?: TemplateToken;
outputs?: MappingToken;
steps: Step[];
};
// `workflow-job` in the schema
export type ReusableWorkflowJob = BaseJob & {
type: "reusableWorkflowJob";
ref: StringToken;
"input-definitions"?: MappingToken;
"input-values"?: MappingToken;
jobs?: WorkflowJob[];
};
export type Container = {
image: StringToken;
credentials?: Credential;
@@ -616,10 +616,10 @@ class TemplateReader {
tr: TokenRange,
rawExpression: string,
allowedContext: string[],
token: TemplateToken,
token: StringToken,
definitionInfo: DefinitionInfo | undefined
): ExpressionToken | undefined {
const parseExpressionResult = this.parseExpression(tr, rawExpression, allowedContext, definitionInfo);
const parseExpressionResult = this.parseExpression(tr, token, rawExpression, allowedContext, definitionInfo);
// Check for error
if (parseExpressionResult.error) {
@@ -631,7 +631,8 @@ class TemplateReader {
}
private parseExpression(
range: TokenRange | undefined,
range: TokenRange,
token: StringToken,
value: string,
allowedContext: string[],
definitionInfo: DefinitionInfo | undefined
@@ -666,9 +667,31 @@ class TemplateReader {
};
}
const startTrim = value.length - value.trimStart().length;
const endTrim = value.length - value.trimEnd().length;
const expressionRange: TokenRange = {
start: {
...range.start,
column: range.start.column + OPEN_EXPRESSION.length + startTrim
},
end: {
...range.end,
column: range.end.column - CLOSE_EXPRESSION.length - endTrim
}
};
// Return the expression
return <ParseExpressionResult>{
expression: new BasicExpressionToken(this._fileId, range, trimmed, definitionInfo, undefined, value),
expression: new BasicExpressionToken(
this._fileId,
range,
trimmed,
definitionInfo,
undefined,
token.source,
expressionRange
),
error: undefined
};
}
@@ -11,10 +11,20 @@ import {TokenType} from "./types";
export class BasicExpressionToken extends ExpressionToken {
private readonly expr: string;
public readonly source: string | undefined;
public readonly originalExpressions: BasicExpressionToken[] | undefined;
public readonly source: string;
/**
* The range of the expression within the source string.
*
* `range` is the range of the entire expression, including the `${{` and `}}`. `expression` is only the expression
* without any ${{ }} markers. `expressionRange` is the range of just the expression within the document.
*/
public readonly expressionRange: TokenRange | undefined;
/**
* @param originalExpressions If the basic expression was transformed from individual expressions, these will be the original ones
*/
@@ -24,12 +34,14 @@ export class BasicExpressionToken extends ExpressionToken {
expression: string,
definitionInfo: DefinitionInfo | undefined,
originalExpressions: BasicExpressionToken[] | undefined,
source: string
source: string | undefined,
expressionRange?: TokenRange | undefined
) {
super(TokenType.BasicExpression, file, range, undefined, definitionInfo);
this.expr = expression;
this.originalExpressions = originalExpressions;
this.source = source;
this.originalExpressions = originalExpressions;
this.expressionRange = expressionRange;
}
public get expression(): string {
@@ -44,7 +56,8 @@ export class BasicExpressionToken extends ExpressionToken {
this.expr,
this.definitionInfo,
this.originalExpressions,
this.source
this.source,
this.expressionRange
)
: new BasicExpressionToken(
this.file,
@@ -52,7 +65,8 @@ export class BasicExpressionToken extends ExpressionToken {
this.expr,
this.definitionInfo,
this.originalExpressions,
this.source
this.source,
this.expressionRange
);
}
@@ -6,13 +6,10 @@ import {TemplateToken} from "./template-token";
describe("traverse", () => {
it("returns parent token and key", () => {
const workflow = parseWorkflow(
"wf.yaml",
[
{
name: "wf.yaml",
content: `on: push`
}
],
{
name: "wf.yaml",
content: `on: push`
},
nullTrace
);
@@ -2,6 +2,7 @@
export type Position = {
/** The one-based line value */
line: number;
/** The one-based column value */
column: number;
};
@@ -0,0 +1,6 @@
import {File} from "./file";
import {FileReference} from "./file-reference";
export interface FileProvider {
getFileContent(ref: FileReference): Promise<File>;
}
@@ -0,0 +1,55 @@
import {parseFileReference} from "./file-reference";
describe("parseFileReference", () => {
it("parses local file reference", () => {
const ref = parseFileReference("./workflow/path");
expect(ref).toEqual({
path: "workflow/path"
});
});
it("parses local file references with an empty path", () => {
const ref = parseFileReference("./");
expect(ref).toEqual({
path: ""
});
});
it("parses remote file reference", () => {
const ref = parseFileReference("owner/repo/path@version");
expect(ref).toEqual({
owner: "owner",
repository: "repo",
path: "path",
version: "version"
});
});
it("parses remote file reference with an empty path", () => {
const ref = parseFileReference("owner/repo@version");
expect(ref).toEqual({
owner: "owner",
repository: "repo",
path: "",
version: "version"
});
});
it("parses remote file reference with slashes in the version", () => {
const ref = parseFileReference("owner/repo@feature-branch/dev");
expect(ref).toEqual({
owner: "owner",
repository: "repo",
path: "",
version: "feature-branch/dev"
});
});
it("throws for malformed remote file references", () => {
expect(() => parseFileReference("owner/repo/path")).toThrowError("Invalid file reference: owner/repo/path");
expect(() => parseFileReference("owner/repo/path@")).toThrowError("Invalid file reference: owner/repo/path@");
expect(() => parseFileReference("owner@")).toThrowError("Invalid file reference: owner@");
});
});
@@ -0,0 +1,42 @@
export type FileReference = LocalFileReference | RemoteFileReference;
export type LocalFileReference = {
path: string;
};
export type RemoteFileReference = {
repository: string;
owner: string;
path: string;
version: string;
};
export function parseFileReference(ref: string): FileReference {
if (ref.startsWith("./")) {
return {
path: ref.substring(2)
};
}
const [remotePath, version] = ref.split("@");
const [owner, repository, ...pathSegments] = remotePath.split("/").filter(s => s.length > 0);
if (!owner || !repository || !version) {
throw new Error(`Invalid file reference: ${ref}`);
}
return {
repository,
owner,
path: pathSegments.join("/"),
version
};
}
export function fileIdentifier(ref: FileReference): string {
if (!("repository" in ref)) {
return "./" + ref.path;
}
return `${ref.owner}/${ref.repository}/${ref.path}@${ref.version}`;
}
@@ -11,7 +11,7 @@ jobs:
- name: 'Hello \${{ fromJSON('test') == inputs.name }}'
run: echo Hello, world!`;
const result = parseWorkflow("main.yaml", [{name: "main.yaml", content: content}], nullTrace);
const result = parseWorkflow({name: "main.yaml", content: content}, nullTrace);
expect(result.context.errors.count).toBe(1);
expect(result.value).toBeUndefined();
@@ -11,15 +11,16 @@ export interface ParseWorkflowResult {
value: TemplateToken | undefined;
}
export function parseWorkflow(entryFileName: string, files: File[], trace: TraceWriter): ParseWorkflowResult {
const context = new TemplateContext(new TemplateValidationErrors(), getWorkflowSchema(), trace);
export function parseWorkflow(entryFile: File, trace: TraceWriter): ParseWorkflowResult;
export function parseWorkflow(entryFile: File, context: TemplateContext): ParseWorkflowResult;
export function parseWorkflow(entryFile: File, contextOrTrace: TraceWriter | TemplateContext): ParseWorkflowResult {
const context =
contextOrTrace instanceof TemplateContext
? contextOrTrace
: new TemplateContext(new TemplateValidationErrors(), getWorkflowSchema(), contextOrTrace);
// Add file ids
files.forEach(x => context.getFileId(x.name));
const fileId = context.getFileId(entryFileName);
const fileContent = files[fileId - 1].content;
const reader = new YamlObjectReader(context, fileId, fileContent);
const fileId = context.getFileId(entryFile.name);
const reader = new YamlObjectReader(context, fileId, entryFile.content);
if (context.errors.count > 0) {
// The file is not valid YAML, template errors could be misleading
return {
@@ -81,13 +81,10 @@ it("YAML errors include range information", () => {
function parseAsWorkflow(content: string): TemplateToken | undefined {
const result = parseWorkflow(
"test.yaml",
[
{
name: "test.yaml",
content: content
}
],
{
name: "test.yaml",
content: content
},
nullTrace
);
+41 -16
View File
@@ -3,6 +3,9 @@ import * as path from "path";
import * as YAML from "yaml";
import {convertWorkflowTemplate} from "./model/convert";
import {TraceWriter} from "./templates/trace-writer";
import {File} from "./workflows/file";
import {FileProvider} from "./workflows/file-provider";
import {fileIdentifier, FileReference} from "./workflows/file-reference";
import {parseWorkflow} from "./workflows/workflow-parser";
interface TestOptions {
@@ -41,31 +44,53 @@ describe("x-lang tests", () => {
const testOptions: TestOptions = YAML.parse(testDocs[0]);
const unsupportedTest = contains(testOptions.skip, "TypeScript");
const test = () => {
let testFileName = ".github/workflows" + fileName.substring(fileName.lastIndexOf("/"));
let testInput = testDocs[1];
let expectedTemplate = testDocs[2].trim();
// TODO: when reusable workflows are implemented, implement correctly
const test = async () => {
const testFileName = ".github/workflows" + fileName.substring(fileName.lastIndexOf("/"));
const testInput = testDocs[1];
const expectedTemplate = testDocs[testDocs.length - 1].trim();
// For reusable workflow tests, additional workflows are passed in as pairs of
// file names and file contents
const reusableWorkflows: Record<string, File> = {};
if (fileName.indexOf("reusable") !== -1) {
testFileName = testDocs[1];
testInput = testDocs[2];
expectedTemplate = testDocs[3].trim();
for (let i = 2; i < testDocs.length - 1; i = i + 2) {
reusableWorkflows[testDocs[i]] = {
name: testDocs[i],
content: testDocs[i + 1]
};
}
}
const parseResult = parseWorkflow(
testFileName,
[
{
name: testFileName,
content: testInput
const testFileProvider: FileProvider = {
getFileContent: async (ref: FileReference) => {
const file = reusableWorkflows[fileIdentifier(ref)];
if (file) {
return file;
}
],
throw new Error("File not found: " + fileName);
}
};
const parseResult = parseWorkflow(
{
name: testFileName,
content: testInput
},
nullTrace
);
expect(parseResult.value).not.toBeUndefined();
const workflowTemplate = convertWorkflowTemplate(parseResult.context, parseResult.value!);
const workflowTemplate = await convertWorkflowTemplate(
parseResult.context,
parseResult.value!,
undefined,
testFileProvider,
{
fetchReusableWorkflowDepth: 1
}
);
// Unless this tests is only used by TypeScript, remove the events for now.
// TODO: Remove this once we parse events everywhere
+3 -3
View File
@@ -3,7 +3,7 @@ skip:
- TypeScript
---
on: push
concurrency:
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs:
@@ -13,7 +13,7 @@ jobs:
- uses: actions/checkout@v3
- run: echo hi
continue-on-error: true
concurrency:
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
build2:
@@ -24,7 +24,7 @@ jobs:
concurrency: staging
build4:
runs-on: macos-latest
concurrency:
concurrency:
group: ref
cancel-in-progress: ${{ github.ref }}
@@ -0,0 +1,23 @@
include-source: false # Drop file/line/col from output
---
on: push
jobs:
run:
runs-on: ubuntu-latest
env:
bad_insert1: "This is a bad ${{ insert }}"
bad_insert2: "${{ insert }} are bad at the beginning"
${{ insert }}: ${{ github.ref }}
steps:
- run: echo hi
---
{
"errors": [
{
"Message": ".github/workflows/errors-insert.yml (Line: 6, Col: 20): The directive 'insert' is not allowed in this context. Directives are not supported for expressions that are embedded within a string. Directives are only supported when the entire value is an expression."
},
{
"Message": ".github/workflows/errors-insert.yml (Line: 7, Col: 20): The directive 'insert' is not allowed in this context. Directives are not supported for expressions that are embedded within a string. Directives are only supported when the entire value is an expression."
}
]
}
@@ -0,0 +1,19 @@
include-source: false # Drop file/line/col from output
skip:
- TypeScript
---
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- null : ' '
run: echo {{ 😀 }}
---
{
"errors": [
{
"Message": ".github/workflows/errors-invalid-mapping-key.yml (Line: 6, Col: 9): Unexpected value ''"
}
]
}
@@ -15,7 +15,7 @@ jobs:
steps:
- run: echo hi
build3:
runs-on:
runs-on:
group: ent/
steps:
- run: echo hi
@@ -0,0 +1,19 @@
include-source: false # Drop file/line/col from output
---
on: push
jobs:
bad-strategy-key:
strategy:
bad-key:
os: [10]
runs-on: macos-latest
steps:
- run: echo hi
---
{
"errors": [
{
"Message": ".github/workflows/errors-matrix-bad-key.yml (Line: 5, Col: 7): Unexpected value 'bad-key'"
}
]
}
@@ -0,0 +1,22 @@
include-source: false # Drop file/line/col from output
skip:
- TypeScript
---
on: push
jobs:
empty-vector:
strategy:
matrix:
os: []
version: [10,12]
runs-on: macos-latest
steps:
- run: echo hi
---
{
"errors": [
{
"Message": ".github/workflows/errors-matrix-empty-vector.yml (Line: 6, Col: 13): Matrix vector 'os' does not contain any values"
}
]
}
@@ -1,7 +1,7 @@
include-source: false # Drop file/line/col from output
max-depth: 5
skip:
- TypeScript
max-depth: 5
---
on: push
jobs:
@@ -1,7 +1,7 @@
include-source: false # Drop file/line/col from output
max-file-size: 124
skip:
- TypeScript
max-file-size: 124
---
on: push
jobs:
@@ -1,7 +1,7 @@
include-source: false # Drop file/line/col from output
max-result-size: 768
skip:
- TypeScript
max-result-size: 768
---
on: push
jobs:
@@ -1,7 +1,6 @@
include-source: false # Drop file/line/col from output
skip:
- Go
- TypeScript
---
on: push
jobs:
@@ -1,7 +1,6 @@
include-source: false # Drop file/line/col from output
skip:
- Go
- TypeScript
---
on: push
jobs:
@@ -1,7 +1,6 @@
include-source: false # Drop file/line/col from output
skip:
- Go
- TypeScript
---
on: push
jobs:
@@ -1,8 +1,8 @@
include-source: false # Drop file/line/col from output
max-result-size: 2048
skip:
- TypeScript
- Go
- TypeScript
---
on: push
jobs:
@@ -12,7 +12,7 @@ jobs:
steps:
- run: echo Hello && World #string token
build2:
if: false
if: false
runs-on: ubuntu-latest
steps:
- run: echo 1
@@ -0,0 +1,49 @@
include-source: false # Drop file/line/col from output
skip:
- TypeScript
---
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Switch to using Python 3.10 by default
uses: actions/setup-python@v4
with:
python-version: >-
3.10
---
{
"jobs": [
{
"type": "job",
"id": "build",
"name": "build",
"if": {
"type": 3,
"expr": "success()"
},
"runs-on": "ubuntu-latest",
"steps": [
{
"id": "__actions_setup-python",
"name": "Switch to using Python 3.10 by default",
"if": {
"type": 3,
"expr": "success()"
},
"uses": "actions/setup-python@v4",
"with": {
"type": 2,
"map": [
{
"Key": "python-version",
"Value": "3.10"
}
]
}
}
]
}
]
}
+94
View File
@@ -0,0 +1,94 @@
include-source: false # Drop file/line/col from output
skip:
- TypeScript
---
on: push
jobs:
generate:
runs-on: ubuntu-latest
outputs:
matrix_map: ${{ steps.set-matrix.outputs.matrix }}
steps:
- run: echo hi
run:
runs-on: ubuntu-latest
needs: generate
env:
${{ insert }}: ${{ fromJson(needs.generate.outputs.matrix_map) }}
steps:
- run: echo hi
---
{
"jobs": [
{
"type": "job",
"id": "generate",
"name": "generate",
"if": {
"type": 3,
"expr": "success()"
},
"runs-on": "ubuntu-latest",
"outputs": {
"type": 2,
"map": [
{
"Key": "matrix_map",
"Value": {
"type": 3,
"expr": "steps.set-matrix.outputs.matrix"
}
}
]
},
"steps": [
{
"id": "__run",
"if": {
"type": 3,
"expr": "success()"
},
"run": "echo hi"
}
]
},
{
"type": "job",
"id": "run",
"name": "run",
"needs": [
"generate"
],
"if": {
"type": 3,
"expr": "success()"
},
"env": {
"type": 2,
"map": [
{
"Key": {
"type": 4,
"directive": "insert"
},
"Value": {
"type": 3,
"expr": "fromJson(needs.generate.outputs.matrix_map)"
}
}
]
},
"runs-on": "ubuntu-latest",
"steps": [
{
"id": "__run",
"if": {
"type": 3,
"expr": "success()"
},
"run": "echo hi"
}
]
}
]
}
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- run: echo hi
build4:
build4:
needs:
- build
- build2
+285
View File
@@ -0,0 +1,285 @@
include-source: false # Drop file/line/col from output
skip:
- TypeScript
---
on: push
jobs:
matrix-basic:
strategy:
matrix:
version: [10, 12, 14]
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- run: echo hi
matrix-nested-sequences:
strategy:
matrix:
version: [[[[10]],2],12,14]
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- run: echo hi
matrix-with-infinity:
strategy:
matrix:
version: [1, 999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999]
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- run: echo hi
nested-matrix:
strategy:
matrix: { vector1: [ {foo: {bar: baz} } ] }
runs-on: linux
steps:
- run: echo hi
---
{
"jobs": [
{
"type": "job",
"id": "matrix-basic",
"name": "matrix-basic",
"if": {
"type": 3,
"expr": "success()"
},
"strategy": {
"type": 2,
"map": [
{
"Key": "matrix",
"Value": {
"type": 2,
"map": [
{
"Key": "version",
"Value": {
"type": 1,
"seq": [
10,
12,
14
]
}
},
{
"Key": "os",
"Value": {
"type": 1,
"seq": [
"ubuntu-latest",
"windows-latest"
]
}
}
]
}
}
]
},
"runs-on": {
"type": 3,
"expr": "matrix.os"
},
"steps": [
{
"id": "__run",
"if": {
"type": 3,
"expr": "success()"
},
"run": "echo hi"
}
]
},
{
"type": "job",
"id": "matrix-nested-sequences",
"name": "matrix-nested-sequences",
"if": {
"type": 3,
"expr": "success()"
},
"strategy": {
"type": 2,
"map": [
{
"Key": "matrix",
"Value": {
"type": 2,
"map": [
{
"Key": "version",
"Value": {
"type": 1,
"seq": [
{
"type": 1,
"seq": [
{
"type": 1,
"seq": [
{
"type": 1,
"seq": [
10
]
}
]
},
2
]
},
12,
14
]
}
},
{
"Key": "os",
"Value": {
"type": 1,
"seq": [
"ubuntu-latest",
"windows-latest"
]
}
}
]
}
}
]
},
"runs-on": {
"type": 3,
"expr": "matrix.os"
},
"steps": [
{
"id": "__run",
"if": {
"type": 3,
"expr": "success()"
},
"run": "echo hi"
}
]
},
{
"type": "job",
"id": "matrix-with-infinity",
"name": "matrix-with-infinity",
"if": {
"type": 3,
"expr": "success()"
},
"strategy": {
"type": 2,
"map": [
{
"Key": "matrix",
"Value": {
"type": 2,
"map": [
{
"Key": "version",
"Value": {
"type": 1,
"seq": [
1,
"Infinity"
]
}
},
{
"Key": "os",
"Value": {
"type": 1,
"seq": [
"ubuntu-latest",
"windows-latest"
]
}
}
]
}
}
]
},
"runs-on": {
"type": 3,
"expr": "matrix.os"
},
"steps": [
{
"id": "__run",
"if": {
"type": 3,
"expr": "success()"
},
"run": "echo hi"
}
]
},
{
"type": "job",
"id": "nested-matrix",
"name": "nested-matrix",
"if": {
"type": 3,
"expr": "success()"
},
"strategy": {
"type": 2,
"map": [
{
"Key": "matrix",
"Value": {
"type": 2,
"map": [
{
"Key": "vector1",
"Value": {
"type": 1,
"seq": [
{
"type": 2,
"map": [
{
"Key": "foo",
"Value": {
"type": 2,
"map": [
{
"Key": "bar",
"Value": "baz"
}
]
}
}
]
}
]
}
}
]
}
}
]
},
"runs-on": "linux",
"steps": [
{
"id": "__run",
"if": {
"type": 3,
"expr": "success()"
},
"run": "echo hi"
}
]
}
]
}
+3 -3
View File
@@ -10,7 +10,7 @@ jobs:
name: ${{ github.actor }}
timeout-minutes: ${{ github.ref }}
cancel-timeout-minutes: 300
concurrency:
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
env:
@@ -28,10 +28,10 @@ jobs:
build2:
runs-on: [self-hosted, linux]
continue-on-error: true
name: Jobs Repro Hardcode
name: Jobs Repro Hardcode
timeout-minutes: 360
cancel-timeout-minutes: 300
concurrency:
concurrency:
group: groupA
cancel-in-progress: true
env:
@@ -1,6 +1,6 @@
include-source: false # Drop file/line/col from output
skip:
- Typescript
- TypeScript
---
on:
workflow_dispatch:
@@ -1,7 +1,6 @@
include-source: false # Drop file/line/col from output
skip:
- Go
- Typescript
- TypeScript
---
on:
workflow_dispatch:
@@ -22,6 +21,10 @@ on:
- debug
# Defaults to string
input4:
input5-Env:
description: 'Test environment'
type: environment
required: true
jobs:
my-job:
runs-on: ubuntu-latest
@@ -33,7 +36,8 @@ jobs:
"input1": "string",
"input2": "boolean",
"input3": "choice",
"input4": "string"
"input4": "string",
"input5-Env": "environment"
},
"jobs": [
{
@@ -1,6 +1,6 @@
include-source: false # Drop file/line/col from output
skip:
- Typescript
- TypeScript
---
on:
workflow_dispatch:
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
- uses: actions/checkout@v3
- run: echo hi
continue-on-error: true
deploy:
deploy:
runs-on: ubuntu-latest
permissions:
actions: write
@@ -1,6 +1,5 @@
include-source: true # Preserve file/line/col in serialized output
skip:
- Go
- TypeScript
---
on: push
@@ -33,46 +33,34 @@ jobs:
{
"jobs": [
{
"Type": "reusableWorkflowJob",
"Id": {
"type": "reusableWorkflowJob",
"id": {
"type": 0,
"file": 1,
"line": 3,
"col": 3,
"lit": "build"
},
"Name": {
"name": {
"type": 0,
"file": 1,
"line": 3,
"col": 3,
"lit": "build"
},
"Needs": [],
"If": {
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": {
"ref": {
"type": 0,
"file": 1,
"line": 4,
"col": 11,
"lit": "some-org-1/some-repo-1/.github/workflows/build.yml@v1"
},
"Permissions": null,
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"jobs": [
{
"type": "job",
"id": {
@@ -120,46 +108,34 @@ jobs:
]
},
{
"Type": "reusableWorkflowJob",
"Id": {
"type": "reusableWorkflowJob",
"id": {
"type": 0,
"file": 1,
"line": 5,
"col": 3,
"lit": "deploy"
},
"Name": {
"name": {
"type": 0,
"file": 1,
"line": 5,
"col": 3,
"lit": "deploy"
},
"Needs": [],
"If": {
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": {
"ref": {
"type": 0,
"file": 1,
"line": 6,
"col": 11,
"lit": "some-org-2/some-repo-2/.github/workflows/deploy.yml@v2"
},
"Permissions": null,
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"jobs": [
{
"type": "job",
"id": {
@@ -3,7 +3,7 @@ skip:
- TypeScript
---
# This is meant to cover all the different types of TemplateToken that are output
# with include-source: true. Currently it is missing type 4 (insert expression)
# with include-source: true. Currently it is missing type 4 (insert expression)
# and type 7 (null), once we have matrix strategy we should be able to get null or combine
# this test with preserves-source-info-basic.yml.
on: push
@@ -1,7 +1,6 @@
include-source: false # Drop file/line/col from output
skip:
- Go
- TypeScript
---
on: push
jobs:
@@ -53,17 +52,16 @@ jobs:
{
"jobs": [
{
"Type": "reusableWorkflowJob",
"Id": "build",
"Name": "build",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "build",
"name": "build",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/build.yml@v1",
"Permissions": null,
"InputDefinitions": {
"ref": "contoso/templates/.github/workflows/build.yml@v1",
"input-definitions": {
"type": 2,
"map": [
{
@@ -200,7 +198,7 @@ jobs:
}
]
},
"InputValues": {
"input-values": {
"type": 2,
"map": [
{
@@ -229,16 +227,7 @@ jobs:
}
]
},
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"jobs": [
{
"type": "job",
"id": "deploy",
@@ -66,19 +66,18 @@ jobs:
]
},
{
"Type": "reusableWorkflowJob",
"Id": "deploy-1",
"Name": "deploy-1",
"Needs": [
"type": "reusableWorkflowJob",
"id": "deploy-1",
"name": "deploy-1",
"needs": [
"build"
],
"If": {
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/deploy.yml@v1",
"Permissions": null,
"InputDefinitions": {
"ref": "contoso/templates/.github/workflows/deploy.yml@v1",
"input-definitions": {
"type": 2,
"map": [
{
@@ -99,7 +98,7 @@ jobs:
}
]
},
"InputValues": {
"input-values": {
"type": 2,
"map": [
{
@@ -108,7 +107,7 @@ jobs:
}
]
},
"SecretDefinitions": {
"secret-definitions": {
"type": 2,
"map": [
{
@@ -129,7 +128,7 @@ jobs:
}
]
},
"SecretValues": {
"secret-values": {
"type": 2,
"map": [
{
@@ -141,14 +140,7 @@ jobs:
}
]
},
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"jobs": [
{
"type": "job",
"id": "job1",
@@ -184,19 +176,18 @@ jobs:
]
},
{
"Type": "reusableWorkflowJob",
"Id": "deploy-2",
"Name": "deploy-2",
"Needs": [
"type": "reusableWorkflowJob",
"id": "deploy-2",
"name": "deploy-2",
"needs": [
"build"
],
"If": {
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/deploy.yml@v1",
"Permissions": null,
"InputDefinitions": {
"ref": "contoso/templates/.github/workflows/deploy.yml@v1",
"input-definitions": {
"type": 2,
"map": [
{
@@ -217,7 +208,7 @@ jobs:
}
]
},
"InputValues": {
"input-values": {
"type": 2,
"map": [
{
@@ -226,7 +217,7 @@ jobs:
}
]
},
"SecretDefinitions": {
"secret-definitions": {
"type": 2,
"map": [
{
@@ -247,15 +238,8 @@ jobs:
}
]
},
"SecretValues": null,
"InheritSecrets": true,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"inherit-secrets": true,
"jobs": [
{
"type": "job",
"id": "job1",
@@ -1,7 +1,6 @@
include-source: false # Drop file/line/col from output
skip:
- Go
- TypeScript
---
on: push
jobs:
@@ -35,17 +34,16 @@ jobs:
{
"jobs": [
{
"Type": "reusableWorkflowJob",
"Id": "deploy",
"Name": "deploy",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "deploy",
"name": "deploy",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/deploy.yml@v1",
"Permissions": null,
"InputDefinitions": {
"ref": "contoso/templates/.github/workflows/deploy.yml@v1",
"input-definitions": {
"type": 2,
"map": [
{
@@ -98,7 +96,7 @@ jobs:
}
]
},
"InputValues": {
"input-values": {
"type": 2,
"map": [
{
@@ -119,16 +117,7 @@ jobs:
}
]
},
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"jobs": [
{
"type": "job",
"id": "job1",
@@ -1,7 +1,6 @@
include-source: false # Drop file/line/col from output
skip:
- Go
- TypeScript
---
on: push
jobs:
@@ -26,28 +25,16 @@ jobs:
{
"jobs": [
{
"Type": "reusableWorkflowJob",
"Id": "build",
"Name": "build",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "build",
"name": "build",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/build.yml@v1",
"Permissions": null,
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"ref": "contoso/templates/.github/workflows/build.yml@v1",
"jobs": [
{
"type": "job",
"id": "build-it",
@@ -71,28 +58,16 @@ jobs:
]
},
{
"Type": "reusableWorkflowJob",
"Id": "build1",
"Name": "custom name",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "build1",
"name": "custom name",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/build.yml@v1",
"Permissions": null,
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"ref": "contoso/templates/.github/workflows/build.yml@v1",
"jobs": [
{
"type": "job",
"id": "build-it",
@@ -116,31 +91,19 @@ jobs:
]
},
{
"Type": "reusableWorkflowJob",
"Id": "build2",
"Name": {
"type": "reusableWorkflowJob",
"id": "build2",
"name": {
"type": 3,
"expr": "github.ref"
},
"Needs": [],
"If": {
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/build.yml@v1",
"Permissions": null,
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"ref": "contoso/templates/.github/workflows/build.yml@v1",
"jobs": [
{
"type": "job",
"id": "build-it",
@@ -28,51 +28,27 @@ jobs:
{
"jobs": [
{
"Type": "reusableWorkflowJob",
"Id": "deploy-level-0",
"Name": "deploy-level-0",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "deploy-level-0",
"name": "deploy-level-0",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/deploy-level-1.yml@v1",
"Permissions": null,
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"ref": "contoso/templates/.github/workflows/deploy-level-1.yml@v1",
"jobs": [
{
"Type": "reusableWorkflowJob",
"Id": "deploy-level-1",
"Name": "deploy-level-1",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "deploy-level-1",
"name": "deploy-level-1",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/deploy-level-2.yml@v1",
"Permissions": null,
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"ref": "contoso/templates/.github/workflows/deploy-level-2.yml@v1",
"jobs": [
{
"type": "job",
"id": "deploy-level-1",
@@ -0,0 +1,205 @@
include-source: false # Drop file/line/col from output
skip:
- Go
- TypeScript
max-nested-reusable-workflows-depth: 2
---
on: push
jobs:
a:
uses: contoso/templates/.github/workflows/deploy-level-1a.yml@v1
b:
uses: contoso/templates/.github/workflows/deploy-level-1b.yml@v1
c:
uses: contoso/templates/.github/workflows/deploy-level-1c.yml@v1
---
contoso/templates/.github/workflows/deploy-level-1a.yml@v1
---
on: workflow_call
jobs:
deploy-level-1a:
uses: contoso/templates/.github/workflows/deploy-level-2a.yml@v1
---
contoso/templates/.github/workflows/deploy-level-2a.yml@v1
---
on: workflow_call
jobs:
deploy-level-2a:
runs-on: ubuntu-latest
steps:
- run: echo hi
---
contoso/templates/.github/workflows/deploy-level-1b.yml@v1
---
on: workflow_call
jobs:
deploy-level-1b:
uses: contoso/templates/.github/workflows/deploy-level-2b.yml@v1
---
contoso/templates/.github/workflows/deploy-level-2b.yml@v1
---
on: workflow_call
jobs:
deploy-level-2b:
runs-on: ubuntu-latest
steps:
- run: echo hi
---
contoso/templates/.github/workflows/deploy-level-1c.yml@v1
---
on: workflow_call
jobs:
deploy-level-1c:
uses: contoso/templates/.github/workflows/deploy-level-2c.yml@v1
---
contoso/templates/.github/workflows/deploy-level-2c.yml@v1
---
on: workflow_call
jobs:
deploy-level-2c:
runs-on: ubuntu-latest
steps:
- run: echo hi
---
{
"jobs": [
{
"type": "reusableWorkflowJob",
"id": "a",
"name": "a",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"ref": "contoso/templates/.github/workflows/deploy-level-1a.yml@v1",
"jobs": [
{
"type": "reusableWorkflowJob",
"id": "deploy-level-1a",
"name": "deploy-level-1a",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"ref": "contoso/templates/.github/workflows/deploy-level-2a.yml@v1",
"jobs": [
{
"type": "job",
"id": "deploy-level-2a",
"name": "deploy-level-2a",
"if": {
"type": 3,
"expr": "success()"
},
"runs-on": "ubuntu-latest",
"steps": [
{
"id": "__run",
"if": {
"type": 3,
"expr": "success()"
},
"run": "echo hi"
}
]
}
]
}
]
},
{
"type": "reusableWorkflowJob",
"id": "b",
"name": "b",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"ref": "contoso/templates/.github/workflows/deploy-level-1b.yml@v1",
"jobs": [
{
"type": "reusableWorkflowJob",
"id": "deploy-level-1b",
"name": "deploy-level-1b",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"ref": "contoso/templates/.github/workflows/deploy-level-2b.yml@v1",
"jobs": [
{
"type": "job",
"id": "deploy-level-2b",
"name": "deploy-level-2b",
"if": {
"type": 3,
"expr": "success()"
},
"runs-on": "ubuntu-latest",
"steps": [
{
"id": "__run",
"if": {
"type": 3,
"expr": "success()"
},
"run": "echo hi"
}
]
}
]
}
]
},
{
"type": "reusableWorkflowJob",
"id": "c",
"name": "c",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"ref": "contoso/templates/.github/workflows/deploy-level-1c.yml@v1",
"jobs": [
{
"type": "reusableWorkflowJob",
"id": "deploy-level-1c",
"name": "deploy-level-1c",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"ref": "contoso/templates/.github/workflows/deploy-level-2c.yml@v1",
"jobs": [
{
"type": "job",
"id": "deploy-level-2c",
"name": "deploy-level-2c",
"if": {
"type": 3,
"expr": "success()"
},
"runs-on": "ubuntu-latest",
"steps": [
{
"id": "__run",
"if": {
"type": 3,
"expr": "success()"
},
"run": "echo hi"
}
]
}
]
}
]
}
]
}
@@ -42,80 +42,47 @@ jobs:
},
"jobs": [
{
"Type": "reusableWorkflowJob",
"Id": "deploy-level-0",
"Name": "deploy-level-0",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "deploy-level-0",
"name": "deploy-level-0",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/deploy-level-1.yml@v1",
"Permissions": {
"ref": "contoso/templates/.github/workflows/deploy-level-1.yml@v1",
"permissions": {
"actions": "write"
},
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"jobs": [
{
"Type": "reusableWorkflowJob",
"Id": "deploy-level-1",
"Name": "deploy-level-1",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "deploy-level-1",
"name": "deploy-level-1",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/deploy-level-2.yml@v1",
"Permissions": {
"ref": "contoso/templates/.github/workflows/deploy-level-2.yml@v1",
"permissions": {
"actions": "read"
},
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"jobs": [
{
"Type": "reusableWorkflowJob",
"Id": "deploy-level-3",
"Name": "deploy-level-3",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "deploy-level-3",
"name": "deploy-level-3",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/deploy-level-3.yml@v1",
"Permissions": {
"ref": "contoso/templates/.github/workflows/deploy-level-3.yml@v1",
"permissions": {
"actions": "read"
},
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"jobs": [
{
"type": "job",
"id": "deploy-level-3",
@@ -47,30 +47,19 @@ permissions:
},
"jobs": [
{
"Type": "reusableWorkflowJob",
"Id": "build",
"Name": "build",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "build",
"name": "build",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/build.yml@v1",
"Permissions": {
"ref": "contoso/templates/.github/workflows/build.yml@v1",
"permissions": {
"actions": "write"
},
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"jobs": [
{
"type": "job",
"id": "deploy",
@@ -47,30 +47,19 @@ jobs:
},
"jobs": [
{
"Type": "reusableWorkflowJob",
"Id": "build",
"Name": "build",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "build",
"name": "build",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/build.yml@v1",
"Permissions": {
"ref": "contoso/templates/.github/workflows/build.yml@v1",
"permissions": {
"actions": "read"
},
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"jobs": [
{
"type": "job",
"id": "deploy",
@@ -27,28 +27,16 @@ jobs:
{
"jobs": [
{
"Type": "reusableWorkflowJob",
"Id": "build",
"Name": "build",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "build",
"name": "build",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/build.yml@v1",
"Permissions": null,
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"ref": "contoso/templates/.github/workflows/build.yml@v1",
"jobs": [
{
"type": "job",
"id": "deploy",
@@ -72,30 +60,19 @@ jobs:
]
},
{
"Type": "reusableWorkflowJob",
"Id": "build2",
"Name": "build2",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "build2",
"name": "build2",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/build.yml@v1",
"Permissions": {
"ref": "contoso/templates/.github/workflows/build.yml@v1",
"permissions": {
"actions": "write"
},
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"jobs": [
{
"type": "job",
"id": "deploy",
@@ -27,28 +27,16 @@ jobs:
{
"jobs": [
{
"Type": "reusableWorkflowJob",
"Id": "build",
"Name": "build",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "build",
"name": "build",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/build.yml@v1",
"Permissions": null,
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"ref": "contoso/templates/.github/workflows/build.yml@v1",
"jobs": [
{
"type": "job",
"id": "deploy",
@@ -72,30 +60,19 @@ jobs:
]
},
{
"Type": "reusableWorkflowJob",
"Id": "build2",
"Name": "build2",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "build2",
"name": "build2",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/build.yml@v1",
"Permissions": {
"ref": "contoso/templates/.github/workflows/build.yml@v1",
"permissions": {
"actions": "write"
},
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"jobs": [
{
"type": "job",
"id": "deploy",
@@ -32,30 +32,19 @@ jobs:
},
"jobs": [
{
"Type": "reusableWorkflowJob",
"Id": "build",
"Name": "build",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "build",
"name": "build",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/build.yml@v1",
"Permissions": {
"ref": "contoso/templates/.github/workflows/build.yml@v1",
"permissions": {
"actions": "read"
},
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"jobs": [
{
"type": "job",
"id": "deploy",
@@ -82,30 +71,19 @@ jobs:
]
},
{
"Type": "reusableWorkflowJob",
"Id": "build2",
"Name": "build2",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "build2",
"name": "build2",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/build.yml@v1",
"Permissions": {
"ref": "contoso/templates/.github/workflows/build.yml@v1",
"permissions": {
"actions": "write"
},
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"jobs": [
{
"type": "job",
"id": "deploy",
@@ -1,7 +1,6 @@
include-source: false # Drop file/line/col from output
skip:
- Go
- TypeScript
---
on: push
jobs:
@@ -31,17 +30,16 @@ jobs:
{
"jobs": [
{
"Type": "reusableWorkflowJob",
"Id": "build",
"Name": "build",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "build",
"name": "build",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/build.yml@v1",
"Permissions": null,
"InputDefinitions": {
"ref": "contoso/templates/.github/workflows/build.yml@v1",
"input-definitions": {
"type": 2,
"map": [
{
@@ -58,7 +56,7 @@ jobs:
}
]
},
"InputValues": {
"input-values": {
"type": 2,
"map": [
{
@@ -70,15 +68,7 @@ jobs:
}
]
},
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": {
"strategy": {
"type": 2,
"map": [
{
@@ -101,7 +91,7 @@ jobs:
}
]
},
"Jobs": [
"jobs": [
{
"type": "job",
"id": "build-it",
@@ -1,7 +1,6 @@
include-source: false # Drop file/line/col from output
skip:
- Go
- TypeScript
---
on: push
jobs:
@@ -33,28 +32,16 @@ jobs:
{
"jobs": [
{
"Type": "reusableWorkflowJob",
"Id": "build",
"Name": "build",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "build",
"name": "build",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/build.yml@v1",
"Permissions": null,
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"ref": "contoso/templates/.github/workflows/build.yml@v1",
"jobs": [
{
"type": "job",
"id": "deploy",
@@ -78,28 +65,16 @@ jobs:
]
},
{
"Type": "reusableWorkflowJob",
"Id": "build2",
"Name": "build2",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "build2",
"name": "build2",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso2/templates2/.github/workflows/build2.yml@v1",
"Permissions": null,
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"ref": "contoso2/templates2/.github/workflows/build2.yml@v1",
"jobs": [
{
"type": "job",
"id": "deploy2",
@@ -1,7 +1,6 @@
include-source: false # Drop file/line/col from output
skip:
- Go
- TypeScript
---
on: push
jobs:
@@ -21,28 +20,16 @@ jobs:
{
"jobs": [
{
"Type": "reusableWorkflowJob",
"Id": "build",
"Name": "build",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "build",
"name": "build",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/build.yml@v1",
"Permissions": null,
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"ref": "contoso/templates/.github/workflows/build.yml@v1",
"jobs": [
{
"type": "job",
"id": "deploy",
@@ -1,7 +1,6 @@
include-source: false # Drop file/line/col from output
skip:
- Go
- TypeScript
---
on: push
jobs:
@@ -27,22 +26,16 @@ jobs:
{
"jobs": [
{
"Type": "reusableWorkflowJob",
"Id": "build",
"Name": "build",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "build",
"name": "build",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/build.yml@v1",
"Permissions": null,
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": null,
"SecretValues": null,
"InheritSecrets": false,
"Outputs": {
"ref": "contoso/templates/.github/workflows/build.yml@v1",
"outputs": {
"type": 2,
"map": [
{
@@ -66,12 +59,7 @@ jobs:
}
]
},
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"jobs": [
{
"type": "job",
"id": "job1",
@@ -25,19 +25,16 @@ jobs:
{
"jobs": [
{
"Type": "reusableWorkflowJob",
"Id": "build",
"Name": "build",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "build",
"name": "build",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/build.yml@v1",
"Permissions": null,
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": {
"ref": "contoso/templates/.github/workflows/build.yml@v1",
"secret-definitions": {
"type": 2,
"map": [
{
@@ -46,7 +43,7 @@ jobs:
}
]
},
"SecretValues": {
"secret-values": {
"type": 2,
"map": [
{
@@ -58,14 +55,7 @@ jobs:
}
]
},
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"jobs": [
{
"type": "job",
"id": "deploy",
@@ -32,19 +32,16 @@ jobs:
{
"jobs": [
{
"Type": "reusableWorkflowJob",
"Id": "build",
"Name": "build",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "build",
"name": "build",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/build.yml@v1",
"Permissions": null,
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": {
"ref": "contoso/templates/.github/workflows/build.yml@v1",
"secret-definitions": {
"type": 2,
"map": [
{
@@ -61,7 +58,7 @@ jobs:
}
]
},
"SecretValues": {
"secret-values": {
"type": 2,
"map": [
{
@@ -73,14 +70,7 @@ jobs:
}
]
},
"InheritSecrets": false,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"jobs": [
{
"type": "job",
"id": "deploy",
@@ -104,19 +94,16 @@ jobs:
]
},
{
"Type": "reusableWorkflowJob",
"Id": "build2",
"Name": "build2",
"Needs": [],
"If": {
"type": "reusableWorkflowJob",
"id": "build2",
"name": "build2",
"needs": [],
"if": {
"type": 3,
"expr": "success()"
},
"Ref": "contoso/templates/.github/workflows/build.yml@v1",
"Permissions": null,
"InputDefinitions": null,
"InputValues": null,
"SecretDefinitions": {
"ref": "contoso/templates/.github/workflows/build.yml@v1",
"secret-definitions": {
"type": 2,
"map": [
{
@@ -133,15 +120,8 @@ jobs:
}
]
},
"SecretValues": null,
"InheritSecrets": true,
"Outputs": null,
"Defaults": null,
"Env": null,
"Concurrency": null,
"EmbeddedConcurrency": null,
"Strategy": null,
"Jobs": [
"inherit-secrets": true,
"jobs": [
{
"type": "job",
"id": "deploy",
@@ -6,10 +6,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: Actions/Capitalized@v2
- uses: actions/setup-node@v2
- uses: docker://alpine:latest
- run: echo hello
- uses: actions/checkout@v2
- uses: Actions/Capitalized@v2
- uses: actions/setup-node@v3
- uses: docker://alpine:latest
- run: echo hello
@@ -34,6 +36,14 @@ jobs:
},
"uses": "actions/checkout@v2"
},
{
"id": "__Actions_Capitalized",
"if": {
"type": 3,
"expr": "success()"
},
"uses": "Actions/Capitalized@v2"
},
{
"id": "__actions_setup-node",
"if": {
@@ -66,6 +76,14 @@ jobs:
},
"uses": "actions/checkout@v2"
},
{
"id": "__Actions_Capitalized_2",
"if": {
"type": 3,
"expr": "success()"
},
"uses": "Actions/Capitalized@v2"
},
{
"id": "__actions_setup-node_2",
"if": {
@@ -1,6 +1,4 @@
include-source: false # Drop file/line/col from output
skip:
- Go
---
on: push
jobs:
@@ -0,0 +1,77 @@
include-source: false # Drop file/line/col from output
---
on: push
env:
UNDERSCORED_INT_AS_STRING: 12_345
UNDERSCORED_FLOAT_AS_STRING: 23_456.789_321
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
vector-1:
- 12_345
- 23_456.789_321
steps:
- run: echo hi
---
{
"env": {
"type": 2,
"map": [
{
"Key": "UNDERSCORED_INT_AS_STRING",
"Value": "12_345"
},
{
"Key": "UNDERSCORED_FLOAT_AS_STRING",
"Value": "23_456.789_321"
}
]
},
"jobs": [
{
"type": "job",
"id": "build",
"name": "build",
"if": {
"type": 3,
"expr": "success()"
},
"strategy": {
"type": 2,
"map": [
{
"Key": "matrix",
"Value": {
"type": 2,
"map": [
{
"Key": "vector-1",
"Value": {
"type": 1,
"seq": [
"12_345",
"23_456.789_321"
]
}
}
]
}
}
]
},
"runs-on": "ubuntu-latest",
"steps": [
{
"id": "__run",
"if": {
"type": 3,
"expr": "success()"
},
"run": "echo hi"
}
]
}
]
}
@@ -1,6 +1,5 @@
include-source: false # Drop file/line/col from output
skip:
- Go
- TypeScript
---
on: push
@@ -1,6 +1,4 @@
include-source: false # Drop file/line/col from output
skip:
- Go
---
on: push
jobs:
@@ -1,6 +1,5 @@
include-source: false # Drop file/line/col from output
skip:
- Go
- TypeScript
---
on: push
@@ -1,6 +1,5 @@
include-source: false # Drop file/line/col from output
skip:
- Go
- TypeScript
---
on: push
@@ -1,6 +1,4 @@
include-source: false # Drop file/line/col from output
skip:
- Go
---
on: push
jobs:
@@ -1,6 +1,5 @@
include-source: false # Drop file/line/col from output
skip:
- Go
- TypeScript
---
on: push
@@ -35,7 +34,7 @@ jobs:
# Customer-breaking change we discovered when upgrading from YamlDotNet.Signed 6.0.0 to YamlDotNet 12.0.2.
# YamlDotNet 12.0.2 produces the error "While scanning a multi-line double-quoted scalar, found wrong indentation."
- "hello
- "hello
world"
steps:
@@ -1,6 +1,6 @@
include-source: false # Drop file/line/col from output
skip:
- TypeScript
- TypeScript
---
on: push
jobs:
@@ -12,6 +12,14 @@ jobs:
dot: .
single-quoted-number: '8'
double-quoted-number: "9"
hex-number: 0xCB2431
hex-string: "0xCB2431"
# binary-number: 0b10100111001 // Binary numbers are not supported by C# at the moment
# binary-string: "0b1111"
octal-number: 0o1234567
octal-string: "0o1234567"
python-version: >-
3.10
---
{
"jobs": [
@@ -46,6 +54,26 @@ jobs:
{
"Key": "double-quoted-number",
"Value": "9"
},
{
"Key": "hex-number",
"Value": "13313073"
},
{
"Key": "hex-string",
"Value": "0xCB2431"
},
{
"Key": "octal-number",
"Value": "342391"
},
{
"Key": "octal-string",
"Value": "0o1234567"
},
{
"Key": "python-version",
"Value": "3.10"
}
]
}
@@ -1,6 +1,6 @@
include-source: false # Drop file/line/col from output
skip:
- TypeScript
- TypeScript
---
on: push
jobs: