Compare commits

...

55 Commits

Author SHA1 Message Date
eric sciple 667b1273e1 Validate expression block-scalar chomping 2025-11-22 05:43:40 +00:00
eric sciple 7f8bba4305 Merge pull request #214 from actions/release/0.3.20
Release version 0.3.20
2025-11-19 10:34:20 -06:00
GitHub Actions 43feb1a1f4 Release extension version 0.3.20 2025-11-19 16:32:52 +00:00
eric sciple d4aeaa3f3f Merge pull request #213 from indigok/patch-1
Add new artifact-metadata permission to schema
2025-11-19 10:19:40 -06:00
Indigo e4f8f24be3 Closing bracket
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 14:40:37 -08:00
Indigo 168cf44245 Add new artifact-metadata permission to schema 2025-11-13 13:54:34 -08:00
Francesco Renzi d4676627d8 Merge pull request #207 from actions/release/0.3.19
Release version 0.3.19
2025-09-30 12:39:48 +02:00
GitHub Actions d6b3b9d3e8 Release extension version 0.3.19 2025-09-30 10:37:47 +00:00
eric sciple 9ba7e48fbf Merge pull request #206 from lawrencegripper/lg/image-event
Add `on.image_version` support to language parser
2025-09-29 00:03:54 -05:00
Lawrence Gripper 6bd54f1b94 Merge branch 'lg/image-event' of github.com:lawrencegripper/languageservices into lg/image-event 2025-09-25 08:48:59 +00:00
Lawrence Gripper fcc72a8d97 Implement handling of new filters in typescript converter 2025-09-25 08:46:57 +00:00
Lawrence Gripper ce3b746742 Merge branch 'main' into lg/image-event 2025-09-24 11:42:22 +01:00
Lawrence Gripper 300c0dc569 Add support to language parser 2025-09-24 10:36:32 +00:00
eric sciple 6f63074d43 Merge pull request #204 from actions/release/0.3.18
Release version 0.3.18
2025-09-10 09:00:30 -05:00
GitHub Actions 7504f49ab6 Release extension version 0.3.18 2025-09-10 13:58:01 +00:00
eric sciple 629c9e23da Merge pull request #201 from lawrencegripper/lg/snapshot-keyword
Snapshot support
2025-09-09 12:40:55 -05:00
Lawrence Gripper 9838063a4e Fix up test for new limited context 2025-09-09 11:20:19 +00:00
Lawrence Gripper 01c3723641 fixup completion tests now we have new keywords 2025-09-09 11:09:05 +00:00
Lawrence Gripper 7cf82aa761 review: only add snapshot for factory job. remove context which isn't applicable 2025-09-09 10:31:20 +00:00
eric sciple 028715d071 Merge pull request #193 from actions/dependabot/npm_and_yarn/form-data-4.0.4
Bump form-data from 4.0.2 to 4.0.4
2025-09-04 14:12:17 -05:00
lawrencegripper cec59d9a4d More version bumping 🤦 2025-09-04 15:48:30 +00:00
lawrencegripper f316d205a9 chore: bump versions 2025-09-04 15:45:24 +00:00
Lawrence Gripper dd8308d7f9 Update workflow-parser/src/workflow-v1.0.json
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-04 16:37:04 +01:00
lawrencegripper 17f511bb6e chore(lint): Run prettier 2025-09-04 15:34:52 +00:00
lawrencegripper fca6e0aec1 Bump the version of relevant packages 2025-09-04 15:27:11 +00:00
lawrencegripper 4faa096820 Add support for new snapshot keyword and object into workflow parser 2025-09-04 15:25:36 +00:00
lawrencegripper ce274ee2ce 🐛 Add types to avoid npm run test failing with Cannot find module
Example error:

> src/templates/template-context.ts:1:28 - error TS2307: Cannot find module '@actions/expressions/funcs/info' or its corresponding type declarations.

related:

- https://github.com/actions/languageservices/issues/146
2025-09-04 15:02:25 +00:00
dependabot[bot] a13e5cd088 Bump form-data from 4.0.2 to 4.0.4
Bumps [form-data](https://github.com/form-data/form-data) from 4.0.2 to 4.0.4.
- [Release notes](https://github.com/form-data/form-data/releases)
- [Changelog](https://github.com/form-data/form-data/blob/master/CHANGELOG.md)
- [Commits](https://github.com/form-data/form-data/compare/v4.0.2...v4.0.4)

---
updated-dependencies:
- dependency-name: form-data
  dependency-version: 4.0.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-22 07:47:53 +00:00
Anthony Zavala 1f3436c3ca Merge pull request #192 from actions/anthonyzavala/bump-webpack-dev-server
Bump `webpack-dev-server: >=5.2.1`
2025-06-16 15:24:40 -07:00
Anthony Zavala 880d3e4109 Bump webpack-dev-server: >=5.2.1 2025-06-16 22:19:01 +00:00
Ben De St Paer-Gotch 09fd00ed88 Merge pull request #191 from actions/nebuk89-patch-1
Update README.md
2025-06-06 11:44:46 +01:00
Ben De St Paer-Gotch 435a10d9b6 Update README.md 2025-06-02 10:40:25 +01:00
Anthony Zavala 311a948ff0 Merge pull request #182 from actions/release/0.3.17
Release version 0.3.17
2025-05-07 14:06:00 -07:00
GitHub Actions b0fd29ab60 Release extension version 0.3.17 2025-05-07 21:04:23 +00:00
Anthony Zavala ccf95ef540 Merge pull request #181 from actions/anthonyzavala/bump-octokit-rest-and-lerna
Bump `@octokit/rest` from 19.0.7 to 21.1.1 & `lerna` from 8.2.1 to 8.2.2
2025-05-07 12:55:40 -07:00
Anthony Zavala e597a0c800 update env secret and vars parameters 2025-05-07 19:43:30 +00:00
Anthony Zavala 80c99e6e38 Bump @octokit/rest from 19.0.7 to 21.1.1 & lerna from 8.2.1 to 8.2.2 2025-05-07 18:11:36 +00:00
Anthony Zavala 655d268694 Merge pull request #180 from actions/alert-autofix-2
Potential fix for code scanning alert no. 2: Workflow does not contain permissions
2025-05-07 10:34:45 -07:00
Anthony Zavala 756ce20db2 Potential fix for code scanning alert no. 2: Workflow does not contain permissions
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-05-07 10:24:31 -07:00
Beth Brennan 04b9c0c333 Merge pull request #179 from actions/release/0.3.16
Release version 0.3.16
2025-05-07 09:40:37 -04:00
GitHub Actions ffef418dbc Release extension version 0.3.16 2025-05-07 13:36:26 +00:00
Beth Brennan e2ec264801 Merge pull request #175 from sgoedecke/patch-1
Update workflow-v1.0.json to include models permission
2025-04-15 12:51:34 -04:00
Sean Goedecke ea15cac4e0 Update workflow-v1.0.json to include models permission 2025-04-11 16:02:48 +10:00
Yang Cao 81db06000a Merge pull request #166 from actions/dependabot/npm_and_yarn/lerna-8.2.1
Bump lerna from 6.0.3 to 8.2.1
2025-03-11 10:54:49 -04:00
dependabot[bot] f0a24df8db Bump lerna from 6.0.3 to 8.2.1
Bumps [lerna](https://github.com/lerna/lerna/tree/HEAD/packages/lerna) from 6.0.3 to 8.2.1.
- [Release notes](https://github.com/lerna/lerna/releases)
- [Changelog](https://github.com/lerna/lerna/blob/main/packages/lerna/CHANGELOG.md)
- [Commits](https://github.com/lerna/lerna/commits/v8.2.1/packages/lerna)

---
updated-dependencies:
- dependency-name: lerna
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 01:44:06 +00:00
Josh Gross 7c0bffb677 Merge pull request #164 from actions/joshmgross/update-workflows
Update workflow actions and remove release notes file
2025-03-05 13:17:49 -05:00
Josh Gross 6fedfd7fa4 Update workflow actions and remove release notes file 2025-03-05 13:10:09 -05:00
Josh Gross 8725c3c1c6 Merge pull request #163 from actions/release/0.3.15
Release version 0.3.15
2025-03-05 13:06:15 -05:00
GitHub Actions 977d0ea9cd Release extension version 0.3.15 2025-03-05 18:02:03 +00:00
Josh Gross 48247b8730 Merge pull request #145 from gillisandrew/bug/import-assertions
Remove import assertions
2025-03-05 12:58:01 -05:00
eric sciple bdee101604 Merge pull request #152 from actions/release/0.3.14
Release version 0.3.14
2025-01-29 15:34:45 -06:00
GitHub Actions 7a41cd9e66 Release extension version 0.3.14 2025-01-29 21:32:29 +00:00
eric sciple 0d97e79d94 Merge pull request #150 from ericsciple/users/ericsciple/25-01-description
Add root-level description keyword
2025-01-29 15:15:08 -06:00
eric sciple 50b08a3a22 Add root-level description keyword 2025-01-28 19:24:42 +00:00
Andrew Gillis f02e9593c2 Remove import assertions 2024-11-29 09:31:04 -05:00
44 changed files with 7498 additions and 2987 deletions
+4 -2
View File
@@ -1,4 +1,6 @@
name: Build & Test
permissions:
contents: read
on:
push:
@@ -11,9 +13,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Use Node.js 16.15
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 16.15
cache: 'npm'
+2 -2
View File
@@ -25,9 +25,9 @@ jobs:
pull-requests: write
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: "16"
+5 -5
View File
@@ -24,10 +24,10 @@ jobs:
contents: read
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Check if version has changed
id: check-version
uses: actions/github-script@v6
uses: actions/github-script@v7
with:
script: |
const version = '${{ inputs.version }}' || require('./lerna.json').version;
@@ -65,9 +65,9 @@ jobs:
PKG_VERSION: "" # will be set in the workflow
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 16.x
cache: "npm"
@@ -80,7 +80,7 @@ jobs:
- run: npm ci
- name: Create release
uses: actions/github-script@v6
uses: actions/github-script@v7
with:
script: |
const fs = require("fs");
+16 -2
View File
@@ -8,6 +8,20 @@ This repository contains multiple npm packages for working with GitHub Actions w
- [languageserver](./languageserver) - Language Server for GitHub Actions, hosting the language service for LSP-compatible editors
- [browser-playground](./browser-playground) - Browser-based playground for the language service
## Contributing
### Note
See [CONTRIBUTING.md](./CONTRIBUTING.md)
Thank you for your interest in this GitHub repo, however, right now we are not taking contributions.
We continue to focus our resources on strategic areas that help our customers be successful while making developers' lives easier. While GitHub Actions remains a key part of this vision, we are allocating resources towards other areas of Actions and are not taking contributions to this repository at this time. The GitHub public roadmap is the best place to follow along for any updates on features were working on and what stage theyre in.
We are taking the following steps to better direct requests related to GitHub Actions, including:
1. We will be directing questions and support requests to our [Community Discussions area](https://github.com/orgs/community/discussions/categories/actions)
2. High Priority bugs can be reported through Community Discussions or you can report these to our support team https://support.github.com/contact/bug-report.
3. Security Issues should be handled as per our [security.md](security.md)
We will still provide security updates for this project and fix major breaking changes during this time.
You are welcome to still raise bugs in this repo.
+1 -1
View File
@@ -34,6 +34,6 @@
"typescript": "^4.9.4",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1"
"webpack-dev-server": ">=5.2.1"
}
}
+5 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/expressions",
"version": "0.3.13",
"version": "0.3.20",
"license": "MIT",
"type": "module",
"source": "./src/index.ts",
@@ -9,10 +9,12 @@
},
"exports": {
".": {
"import": "./dist/index.js"
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./*": {
"import": "./dist/*.js"
"import": "./dist/*.js",
"types": "./dist/*.d.ts"
}
},
"typesVersions": {
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/languageserver",
"version": "0.3.13",
"version": "0.3.20",
"description": "Language server for GitHub Actions",
"license": "MIT",
"type": "module",
@@ -43,9 +43,9 @@
"watch": "tsc --build tsconfig.build.json --watch"
},
"dependencies": {
"@actions/languageservice": "^0.3.13",
"@actions/workflow-parser": "^0.3.13",
"@octokit/rest": "^19.0.7",
"@actions/languageservice": "^0.3.20",
"@actions/workflow-parser": "^0.3.20",
"@octokit/rest": "^21.1.1",
"@octokit/types": "^9.0.0",
"vscode-languageserver": "^8.0.2",
"vscode-languageserver-textdocument": "^1.0.7",
@@ -125,7 +125,7 @@ async function getRemoteSecrets(
environmentSecrets:
(environmentName &&
(await cache.get(`${repo.owner}/${repo.name}/secrets/environment/${environmentName}`, undefined, () =>
fetchEnvironmentSecrets(octokit, repo.id, environmentName)
fetchEnvironmentSecrets(octokit, repo.owner, repo.name, environmentName)
))) ||
[],
orgSecrets: await cache.get(`${repo.owner}/secrets`, undefined, () => fetchOrganizationSecrets(octokit, repo))
@@ -151,14 +151,16 @@ async function fetchSecrets(octokit: Octokit, owner: string, name: string): Prom
async function fetchEnvironmentSecrets(
octokit: Octokit,
repositoryId: number,
owner: string,
name: string,
environmentName: string
): Promise<StringData[]> {
try {
return await octokit.paginate(
octokit.actions.listEnvironmentSecrets,
{
repository_id: repositoryId,
owner,
repo: name,
environment_name: environmentName,
per_page: 100
},
@@ -115,7 +115,7 @@ export async function getRemoteVariables(
environmentVariables:
(environmentName &&
(await cache.get(`${repo.owner}/${repo.name}/vars/environment/${environmentName}`, undefined, () =>
fetchEnvironmentVariables(octokit, repo.id, environmentName)
fetchEnvironmentVariables(octokit, repo.owner, repo.name, environmentName)
))) ||
[],
organizationVariables: await cache.get(`${repo.owner}/vars`, undefined, () =>
@@ -146,14 +146,16 @@ async function fetchVariables(octokit: Octokit, owner: string, name: string): Pr
async function fetchEnvironmentVariables(
octokit: Octokit,
repositoryId: number,
owner: string,
name: string,
environmentName: string
): Promise<Pair[]> {
try {
return await octokit.paginate(
octokit.actions.listEnvironmentVariables,
{
repository_id: repositoryId,
owner: owner,
repo: name,
environment_name: environmentName,
per_page: 100
},
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/languageservice",
"version": "0.3.13",
"version": "0.3.20",
"description": "Language service for GitHub Actions",
"license": "MIT",
"type": "module",
@@ -44,8 +44,8 @@
"watch": "tsc --build tsconfig.build.json --watch"
},
"dependencies": {
"@actions/expressions": "^0.3.13",
"@actions/workflow-parser": "^0.3.13",
"@actions/expressions": "^0.3.20",
"@actions/workflow-parser": "^0.3.20",
"vscode-languageserver-textdocument": "^1.0.7",
"vscode-languageserver-types": "^3.17.2",
"vscode-uri": "^3.0.8",
+1 -1
View File
@@ -1,7 +1,7 @@
import {promises as fs} from "fs";
import Webhook from "./webhook.js";
import schemaImport from "rest-api-description/descriptions/api.github.com/dereferenced/api.github.com.deref.json" assert {type: "json"};
import schemaImport from "rest-api-description/descriptions/api.github.com/dereferenced/api.github.com.deref.json";
import {deduplicateWebhooks} from "./deduplicate.js";
const schema = schemaImport as any;
+7 -7
View File
@@ -44,7 +44,7 @@ jobs:
const result = await complete(...getPositionFromCursor(input));
expect(result).not.toBeUndefined();
expect(result.length).toEqual(8);
expect(result.length).toEqual(9);
expect(result[0].label).toEqual("concurrency");
});
@@ -70,7 +70,7 @@ jobs:
|`;
const result = await complete(...getPositionFromCursor(input));
expect(result).not.toBeUndefined();
expect(result.length).toEqual(20);
expect(result.length).toEqual(21);
});
it("string definition completion in sequence", async () => {
@@ -243,7 +243,7 @@ jobs:
runs-|`;
const result = await complete(...getPositionFromCursor(input));
expect(result).not.toBeUndefined();
expect(result).toHaveLength(20);
expect(result).toHaveLength(21);
});
it("job key with comment afterwards", async () => {
@@ -254,7 +254,7 @@ jobs:
#`;
const result = await complete(...getPositionFromCursor(input));
expect(result).not.toBeUndefined();
expect(result).toHaveLength(20);
expect(result).toHaveLength(21);
});
it("job key with other values afterwards", async () => {
@@ -266,7 +266,7 @@ jobs:
concurrency: 'group-name'`;
const result = await complete(...getPositionFromCursor(input));
expect(result).not.toBeUndefined();
expect(result).toHaveLength(19);
expect(result).toHaveLength(20);
});
it("step key without space after colon", async () => {
@@ -335,7 +335,7 @@ jobs:
- uses: actions/checkout@v2
`;
const result = await complete(...getPositionFromCursor(input));
expect(result).toHaveLength(16);
expect(result).toHaveLength(17);
});
it("complete from behind a colon will replace it", async () => {
@@ -348,7 +348,7 @@ jobs:
- uses: actions/checkout@v2
`;
const result = await complete(...getPositionFromCursor(input));
expect(result).toHaveLength(16);
expect(result).toHaveLength(17);
const textEdit = result[0].textEdit as TextEdit;
expect(textEdit.range).toEqual({
start: {line: 5, character: 4},
@@ -1,4 +1,4 @@
import descriptions from "./descriptions.json" assert {type: "json"};
import descriptions from "./descriptions.json";
export const RootContext = "root";
const FunctionContext = "functions";
@@ -3,8 +3,8 @@ import {data, DescriptionDictionary} from "@actions/expressions";
import webhookObjects from "./objects.json";
import webhooks from "./webhooks.json";
import schedule from "./schedule.json" assert {type: "json"};
import workflow_call from "./workflow_call.json" assert {type: "json"};
import schedule from "./schedule.json";
import workflow_call from "./workflow_call.json";
const customEventPayloads: {[name: string]: unknown} = {
schedule,
+12 -2
View File
@@ -21,8 +21,18 @@ describe("end-to-end", () => {
const result = await complete(...getPositionFromCursor(input));
expect(result).not.toBeUndefined();
expect(result.length).toEqual(8);
expect(result.length).toEqual(9);
const labels = result.map(x => x.label);
expect(labels).toEqual(["concurrency", "defaults", "env", "jobs", "name", "on", "permissions", "run-name"]);
expect(labels).toEqual([
"concurrency",
"defaults",
"description",
"env",
"jobs",
"name",
"on",
"permissions",
"run-name"
]);
});
});
@@ -0,0 +1,58 @@
import {registerLogger} from "./log";
import {createDocument} from "./test-utils/document";
import {TestLogger} from "./test-utils/logger";
import {clearCache} from "./utils/workflow-cache";
import {validate} from "./validate";
registerLogger(new TestLogger());
beforeEach(() => {
clearCache();
});
describe("block scalar chomping - allowed cases", () => {
it("does NOT warn for step.run with clip chomping (exception)", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: |
echo \${{ github.event_name }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
it("does not warn for inline expression", async () => {
const input = `
on: push
jobs:
build:
if: \${{ github.event_name == 'push' }}
runs-on: ubuntu-latest
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
it("does not warn for quoted string", async () => {
const input = `
on: push
jobs:
build:
if: "\${{ github.event_name == 'push' }}"
runs-on: ubuntu-latest
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
});
@@ -0,0 +1,142 @@
import {DiagnosticSeverity} from "vscode-languageserver-types";
import {registerLogger} from "./log";
import {createDocument} from "./test-utils/document";
import {TestLogger} from "./test-utils/logger";
import {clearCache} from "./utils/workflow-cache";
import {validate} from "./validate";
registerLogger(new TestLogger());
beforeEach(() => {
clearCache();
});
describe("block scalar chomping - boolean fields", () => {
describe("job continue-on-error", () => {
it("errors with clip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
continue-on-error: |
\${{ matrix.experimental }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks boolean evaluation. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("errors with keep chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
continue-on-error: |+
\${{ matrix.experimental }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks boolean evaluation. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("does not error with strip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
continue-on-error: |-
\${{ matrix.experimental }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
});
describe("step continue-on-error", () => {
it("errors with clip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo hi
continue-on-error: |
\${{ matrix.experimental }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks boolean evaluation. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("errors with keep chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo hi
continue-on-error: |+
\${{ matrix.experimental }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks boolean evaluation. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("does not error with strip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo hi
continue-on-error: |-
\${{ matrix.experimental }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
});
});
@@ -0,0 +1,188 @@
import {DiagnosticSeverity} from "vscode-languageserver-types";
import {registerLogger} from "./log";
import {createDocument} from "./test-utils/document";
import {TestLogger} from "./test-utils/logger";
import {clearCache} from "./utils/workflow-cache";
import {validate} from "./validate";
registerLogger(new TestLogger());
beforeEach(() => {
clearCache();
});
describe("block scalar chomping - if fields", () => {
describe("job-if", () => {
it("errors with clip chomping", async () => {
const input = `
on: push
jobs:
build:
if: |
\${{ github.event_name == 'push' }}
runs-on: ubuntu-latest
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks boolean evaluation. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("errors with keep chomping", async () => {
const input = `
on: push
jobs:
build:
if: |+
\${{ github.event_name == 'push' }}
runs-on: ubuntu-latest
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks boolean evaluation. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("does not error with strip chomping", async () => {
const input = `
on: push
jobs:
build:
if: |-
\${{ github.event_name == 'push' }}
runs-on: ubuntu-latest
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
it("errors without ${{ }} (isExpression)", async () => {
const input = `
on: push
jobs:
build:
if: |
github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks boolean evaluation. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("uses > indicator in error message for folded scalars", async () => {
const input = `
on: push
jobs:
build:
if: >
\${{ github.event_name == 'push' }}
runs-on: ubuntu-latest
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks boolean evaluation. Use '>-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
});
describe("step-if", () => {
it("errors with clip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- if: |
\${{ github.event_name == 'push' }}
run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks boolean evaluation. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("errors with keep chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- if: |+
\${{ github.event_name == 'push' }}
run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks boolean evaluation. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("does not error with strip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- if: |-
\${{ github.event_name == 'push' }}
run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
});
});
@@ -0,0 +1,428 @@
import {DiagnosticSeverity} from "vscode-languageserver-types";
import {registerLogger} from "./log";
import {createDocument} from "./test-utils/document";
import {TestLogger} from "./test-utils/logger";
import {clearCache} from "./utils/workflow-cache";
import {validate} from "./validate";
registerLogger(new TestLogger());
beforeEach(() => {
clearCache();
});
describe("block scalar chomping - number fields", () => {
describe("job timeout-minutes", () => {
it("errors with clip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: |
\${{ matrix.timeout }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks number parsing. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("errors with keep chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: |+
\${{ matrix.timeout }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks number parsing. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("does not error with strip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: |-
\${{ matrix.timeout }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
});
describe("container.ports", () => {
it("errors with clip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
container:
image: node:16
ports: |
\${{ fromJSON('[80, 443]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks number parsing. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("errors with keep chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
container:
image: node:16
ports: |+
\${{ fromJSON('[8080, 9090]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks number parsing. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("does not error with strip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
container:
image: node:16
ports: |-
\${{ fromJSON('[80, 443]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
});
describe("container.volumes", () => {
it("errors with clip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
container:
image: node:16
volumes: |
\${{ fromJSON('["/data:/data"]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks number parsing. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("errors with keep chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
container:
image: node:16
volumes: |+
\${{ fromJSON('["/data:/data"]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks number parsing. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("does not error with strip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
container:
image: node:16
volumes: |-
\${{ fromJSON('["/data:/data"]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
});
describe("services.ports", () => {
it("errors with clip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
ports: |
\${{ fromJSON('[5432]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks number parsing. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("errors with keep chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
ports: |+
\${{ fromJSON('[5432]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks number parsing. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("does not error with strip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
ports: |-
\${{ fromJSON('[5432]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
});
describe("services.volumes", () => {
it("errors with clip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
volumes: |
\${{ fromJSON('["/var/lib/postgresql/data"]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks number parsing. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("errors with keep chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
volumes: |+
\${{ fromJSON('["/var/lib/postgresql/data"]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks number parsing. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("does not error with strip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
volumes: |-
\${{ fromJSON('["/var/lib/postgresql/data"]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
});
describe("step timeout-minutes", () => {
it("errors with clip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo hi
timeout-minutes: |
\${{ matrix.timeout }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks number parsing. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("errors with keep chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo hi
timeout-minutes: |+
\${{ matrix.timeout }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline which breaks number parsing. Use '|-' to strip trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Error
})
);
});
it("does not error with strip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo hi
timeout-minutes: |-
\${{ matrix.timeout }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
});
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,227 @@
import {DiagnosticSeverity} from "vscode-languageserver-types";
import {registerLogger} from "./log";
import {createDocument} from "./test-utils/document";
import {TestLogger} from "./test-utils/logger";
import {clearCache} from "./utils/workflow-cache";
import {validate} from "./validate";
registerLogger(new TestLogger());
beforeEach(() => {
clearCache();
});
describe("expression validation", () => {
describe("block scalar chomping - fields that warn only for clip", () => {
describe("env", () => {
it("warns with clip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: |
echo $VAR
env:
VAR: |
\${{ matrix.value }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline to expression result. Use '|-' to strip or '|+' to keep trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Warning
})
);
});
it("does not warn with keep chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: |
echo $VAR
env:
VAR: |+
\${{ matrix.value }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
it("does not warn with strip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: |
echo $VAR
env:
VAR: |-
\${{ matrix.value }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
it("uses > indicator in warning message for folded scalars", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: |
echo $VAR
env:
VAR: >
\${{ matrix.value }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline to expression result. Use '>-' to strip or '>+' to keep trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Warning
})
);
});
});
describe("action input", () => {
it("warns with clip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: |
\${{ github.ref }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline to expression result. Use '|-' to strip or '|+' to keep trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Warning
})
);
});
it("does not warn with keep chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: |+
\${{ github.ref }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
it("does not warn with strip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: |-
\${{ github.ref }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
});
describe("matrix value", () => {
it("warns with clip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
version: |
\${{ fromJSON('[1, 2, 3]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toContainEqual(
expect.objectContaining({
message:
"Block scalar adds trailing newline to expression result. Use '|-' to strip or '|+' to keep trailing newlines.",
code: "expression-block-scalar-chomping",
severity: DiagnosticSeverity.Warning
})
);
});
it("does not warn with keep chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
version: |+
\${{ fromJSON('[1, 2, 3]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
it("does not warn with strip chomping", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
version: |-
\${{ fromJSON('[1, 2, 3]') }}
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result.filter(d => d.code === "expression-block-scalar-chomping")).toEqual([]);
});
});
});
});
+99
View File
@@ -3,12 +3,14 @@ import {Expr} from "@actions/expressions/ast";
import {ParseWorkflowResult, WorkflowTemplate, isBasicExpression, isString} from "@actions/workflow-parser";
import {ErrorPolicy} from "@actions/workflow-parser/model/convert";
import {splitAllowedContext} from "@actions/workflow-parser/templates/allowed-context";
import {Definition} from "@actions/workflow-parser/templates/schema/definition";
import {BasicExpressionToken} from "@actions/workflow-parser/templates/tokens/basic-expression-token";
import {StringToken} from "@actions/workflow-parser/templates/tokens/string-token";
import {TemplateToken} from "@actions/workflow-parser/templates/tokens/template-token";
import {TokenRange} from "@actions/workflow-parser/templates/tokens/token-range";
import {File} from "@actions/workflow-parser/workflows/file";
import {FileProvider} from "@actions/workflow-parser/workflows/file-provider";
import {Scalar} from "yaml";
import {TextDocument} from "vscode-languageserver-textdocument";
import {Diagnostic, DiagnosticSeverity, URI} from "vscode-languageserver-types";
import {ActionMetadata, ActionReference} from "./action";
@@ -106,6 +108,8 @@ async function additionalValidations(
config?.contextProviderConfig,
getProviderContext(documentUri, template, root, token.range)
);
validateChomp(diagnostics, token, parent, key, validationDefinition);
}
if (token.definition?.key === "regular-step" && token.range) {
@@ -217,3 +221,98 @@ async function validateExpression(
);
}
}
function validateChomp(
diagnostics: Diagnostic[],
token: BasicExpressionToken,
parent: TemplateToken | undefined,
key: TemplateToken | undefined,
validationDefinition: Definition | undefined
): void {
// Not "clip" or "keep" chomp style?
if (token.chompStyle !== "clip" && token.chompStyle !== "keep") {
return;
}
// No definition? This can happen when the token is in an invalid position or the workflow has parse errors.
if (!validationDefinition) {
return;
}
// Step "run"?
if (parent?.definition?.key === "run-step" && key?.isScalar && key.toString() === "run") {
return;
}
// Block scalar indicator, i.e. | or >
const scalarIndicator = token.scalarType === Scalar.BLOCK_LITERAL ? "|" : ">";
const defKey = validationDefinition.key;
const parentDefKey = parent?.definition?.key;
// Error for boolean fields
if (
defKey === "job-if" ||
defKey === "step-if" ||
defKey === "step-continue-on-error" ||
(parentDefKey === "job-factory" && key?.isScalar && key.toString() === "continue-on-error")
) {
diagnostics.push({
message: `Block scalar adds trailing newline which breaks boolean evaluation. Use '${scalarIndicator}-' to strip trailing newlines.`,
range: mapRange(token.range),
severity: DiagnosticSeverity.Error,
code: "expression-block-scalar-chomping",
source: "github-actions"
});
}
// Error for number fields
else if (
defKey === "step-timeout-minutes" ||
(parentDefKey === "container-mapping" && key?.isScalar && ["ports", "volumes"].includes(key.toString())) ||
(parentDefKey === "job-factory" && key?.isScalar && key.toString() === "timeout-minutes")
) {
diagnostics.push({
message: `Block scalar adds trailing newline which breaks number parsing. Use '${scalarIndicator}-' to strip trailing newlines.`,
range: mapRange(token.range),
severity: DiagnosticSeverity.Error,
code: "expression-block-scalar-chomping",
source: "github-actions"
});
}
// Error for specific string fields
else if (
defKey === "run-name" ||
defKey === "step-name" ||
defKey === "container" ||
defKey === "services-container" ||
defKey === "job-environment" ||
defKey === "job-environment-name" ||
defKey === "runs-on" ||
defKey === "runs-on-labels" ||
(parentDefKey === "container-mapping" && key?.isScalar && ["image", "credentials"].includes(key.toString())) ||
(parentDefKey === "job-defaults-run" && key?.isScalar && ["shell", "working-directory"].includes(key.toString())) ||
(parentDefKey === "job-environment-mapping" && key?.isScalar && key.toString() === "url") ||
(parentDefKey === "job-factory" && key?.isScalar && key.toString() === "name") ||
(parentDefKey === "workflow-job" && key?.isScalar && key.toString() === "name") ||
(parentDefKey === "run-step" && key?.isScalar && key.toString() === "working-directory") ||
(parentDefKey === "runs-on-mapping" && key?.isScalar && key.toString() === "group")
) {
diagnostics.push({
message: `Block scalar adds trailing newline. Use '${scalarIndicator}-' to strip trailing newlines.`,
range: mapRange(token.range),
severity: DiagnosticSeverity.Error,
code: "expression-block-scalar-chomping",
source: "github-actions"
});
}
// Warning for everything else, but only on clip (default)
else if (token.chompStyle === "clip") {
diagnostics.push({
message: `Block scalar adds trailing newline to expression result. Use '${scalarIndicator}-' to strip or '${scalarIndicator}+' to keep trailing newlines.`,
range: mapRange(token.range),
severity: DiagnosticSeverity.Warning,
code: "expression-block-scalar-chomping",
source: "github-actions"
});
}
}
+1 -1
View File
@@ -6,5 +6,5 @@
"languageservice",
"languageserver"
],
"version": "0.3.13"
"version": "0.3.20"
}
+4022 -2921
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -8,6 +8,6 @@
"./languageserver"
],
"devDependencies": {
"lerna": "^6.0.3"
"lerna": "^8.2.2"
}
}
-1
View File
@@ -1 +0,0 @@
Release 0.3.5
+6 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/workflow-parser",
"version": "0.3.13",
"version": "0.3.20",
"license": "MIT",
"type": "module",
"source": "./src/index.ts",
@@ -9,10 +9,12 @@
},
"exports": {
".": {
"import": "./dist/index.js"
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./*": {
"import": "./dist/*.js"
"import": "./dist/*.js",
"types": "./dist/*.d.ts"
}
},
"typesVersions": {
@@ -43,7 +45,7 @@
"watch": "tsc --build tsconfig.build.json --watch"
},
"dependencies": {
"@actions/expressions": "^0.3.13",
"@actions/expressions": "^0.3.20",
"cronstrue": "^2.21.0",
"yaml": "^2.0.0-8"
},
+280
View File
@@ -200,4 +200,284 @@ jobs:
throw new Error("expected if to be a basic expression");
}
});
describe("Block scalar chomp style preservation", () => {
it("preserves clip chomping (|) for literal block scalar", () => {
const result = parseWorkflow(
{
name: "test.yaml",
content: `on: push
jobs:
build:
runs-on: ubuntu-latest
if: |
\${{ github.event_name == 'push' }}
steps:
- run: echo hi`
},
nullTrace
);
expect(result.context.errors.getErrors()).toHaveLength(0);
const workflowRoot = result.value!.assertMapping("root")!;
const jobs = workflowRoot.get(1).value.assertMapping("jobs");
const build = jobs.get(0).value.assertMapping("job");
const ifToken = build.get(1).value;
if (!isBasicExpression(ifToken)) {
throw new Error("expected if to be a basic expression");
}
expect(ifToken.scalarType).toBe("BLOCK_LITERAL");
expect(ifToken.chompStyle).toBe("clip");
});
it("preserves strip chomping (|-) for literal block scalar", () => {
const result = parseWorkflow(
{
name: "test.yaml",
content: `on: push
jobs:
build:
runs-on: ubuntu-latest
if: |-
\${{ github.event_name == 'push' }}
steps:
- run: echo hi`
},
nullTrace
);
expect(result.context.errors.getErrors()).toHaveLength(0);
const workflowRoot = result.value!.assertMapping("root")!;
const jobs = workflowRoot.get(1).value.assertMapping("jobs");
const build = jobs.get(0).value.assertMapping("job");
const ifToken = build.get(1).value;
if (!isBasicExpression(ifToken)) {
throw new Error("expected if to be a basic expression");
}
expect(ifToken.scalarType).toBe("BLOCK_LITERAL");
expect(ifToken.chompStyle).toBe("strip");
});
it("preserves keep chomping (|+) for literal block scalar", () => {
const result = parseWorkflow(
{
name: "test.yaml",
content: `on: push
jobs:
build:
runs-on: ubuntu-latest
if: |+
\${{ github.event_name == 'push' }}
steps:
- run: echo hi`
},
nullTrace
);
expect(result.context.errors.getErrors()).toHaveLength(0);
const workflowRoot = result.value!.assertMapping("root")!;
const jobs = workflowRoot.get(1).value.assertMapping("jobs");
const build = jobs.get(0).value.assertMapping("job");
const ifToken = build.get(1).value;
if (!isBasicExpression(ifToken)) {
throw new Error("expected if to be a basic expression");
}
expect(ifToken.scalarType).toBe("BLOCK_LITERAL");
expect(ifToken.chompStyle).toBe("keep");
});
it("preserves folded clip (>) chomping", () => {
const result = parseWorkflow(
{
name: "test.yaml",
content: `on: push
jobs:
build:
runs-on: ubuntu-latest
if: >
\${{ github.event_name == 'push' }}
steps:
- run: echo hi`
},
nullTrace
);
expect(result.context.errors.getErrors()).toHaveLength(0);
const workflowRoot = result.value!.assertMapping("root")!;
const jobs = workflowRoot.get(1).value.assertMapping("jobs");
const build = jobs.get(0).value.assertMapping("job");
const ifToken = build.get(1).value;
if (!isBasicExpression(ifToken)) {
throw new Error("expected if to be a basic expression");
}
expect(ifToken.scalarType).toBe("BLOCK_FOLDED");
expect(ifToken.chompStyle).toBe("clip");
});
it("preserves folded strip (>-) chomping", () => {
const result = parseWorkflow(
{
name: "test.yaml",
content: `on: push
jobs:
build:
runs-on: ubuntu-latest
if: >-
\${{ github.event_name == 'push' }}
steps:
- run: echo hi`
},
nullTrace
);
expect(result.context.errors.getErrors()).toHaveLength(0);
const workflowRoot = result.value!.assertMapping("root")!;
const jobs = workflowRoot.get(1).value.assertMapping("jobs");
const build = jobs.get(0).value.assertMapping("job");
const ifToken = build.get(1).value;
if (!isBasicExpression(ifToken)) {
throw new Error("expected if to be a basic expression");
}
expect(ifToken.scalarType).toBe("BLOCK_FOLDED");
expect(ifToken.chompStyle).toBe("strip");
});
it("preserves with explicit indent (|2)", () => {
const result = parseWorkflow(
{
name: "test.yaml",
content: `on: push
jobs:
build:
runs-on: ubuntu-latest
if: |2
\${{ github.event_name == 'push' }}
steps:
- run: echo hi`
},
nullTrace
);
expect(result.context.errors.getErrors()).toHaveLength(0);
const workflowRoot = result.value!.assertMapping("root")!;
const jobs = workflowRoot.get(1).value.assertMapping("jobs");
const build = jobs.get(0).value.assertMapping("job");
const ifToken = build.get(1).value;
if (!isBasicExpression(ifToken)) {
throw new Error("expected if to be a basic expression");
}
expect(ifToken.scalarType).toBe("BLOCK_LITERAL");
expect(ifToken.chompStyle).toBe("clip");
});
it("preserves with explicit indent and strip (|-2)", () => {
const result = parseWorkflow(
{
name: "test.yaml",
content: `on: push
jobs:
build:
runs-on: ubuntu-latest
if: |-2
\${{ github.event_name == 'push' }}
steps:
- run: echo hi`
},
nullTrace
);
expect(result.context.errors.getErrors()).toHaveLength(0);
const workflowRoot = result.value!.assertMapping("root")!;
const jobs = workflowRoot.get(1).value.assertMapping("jobs");
const build = jobs.get(0).value.assertMapping("job");
const ifToken = build.get(1).value;
if (!isBasicExpression(ifToken)) {
throw new Error("expected if to be a basic expression");
}
expect(ifToken.scalarType).toBe("BLOCK_LITERAL");
expect(ifToken.chompStyle).toBe("strip");
});
it("handles flow scalars (no chomp info for inline)", () => {
const result = parseWorkflow(
{
name: "test.yaml",
content: `on: push
jobs:
build:
runs-on: ubuntu-latest
if: \${{ github.event_name == 'push' }}
steps:
- run: echo hi`
},
nullTrace
);
expect(result.context.errors.getErrors()).toHaveLength(0);
const workflowRoot = result.value!.assertMapping("root")!;
const jobs = workflowRoot.get(1).value.assertMapping("jobs");
const build = jobs.get(0).value.assertMapping("job");
const ifToken = build.get(1).value;
if (!isBasicExpression(ifToken)) {
throw new Error("expected if to be a basic expression");
}
expect(ifToken.scalarType).toBeUndefined();
expect(ifToken.chompStyle).toBeUndefined();
});
it("handles job-if without ${{ }} (isExpression case)", () => {
const result = parseWorkflow(
{
name: "test.yaml",
content: `on: push
jobs:
build:
runs-on: ubuntu-latest
if: |
github.event_name == 'push'
steps:
- run: echo hi`
},
nullTrace
);
expect(result.context.errors.getErrors()).toHaveLength(0);
const workflowRoot = result.value!.assertMapping("root")!;
const jobs = workflowRoot.get(1).value.assertMapping("jobs");
const build = jobs.get(0).value.assertMapping("job");
const ifToken = build.get(1).value;
if (!isBasicExpression(ifToken)) {
throw new Error("expected if to be a basic expression");
}
expect(ifToken.scalarType).toBe("BLOCK_LITERAL");
expect(ifToken.chompStyle).toBe("clip");
});
});
});
@@ -7,10 +7,12 @@ import {TokenType} from "../../templates/tokens/types";
import {
BranchFilterConfig,
EventsConfig,
NamesFilterConfig,
PathFilterConfig,
ScheduleConfig,
TagFilterConfig,
TypesFilterConfig,
VersionsFilterConfig,
WorkflowFilterConfig
} from "../workflow-template";
import {isValidCron} from "./cron";
@@ -76,10 +78,11 @@ export function convertOn(context: TemplateContext, token: TemplateToken): Event
...convertPatternFilter("tags", eventToken),
...convertPatternFilter("paths", eventToken),
...convertFilter("types", eventToken),
...convertFilter("versions", eventToken),
...convertFilter("names", eventToken),
...convertFilter("workflows", eventToken)
};
}
return result;
}
@@ -121,8 +124,8 @@ function convertPatternFilter<T extends BranchFilterConfig & TagFilterConfig & P
return result;
}
function convertFilter<T extends TypesFilterConfig & WorkflowFilterConfig>(
name: "types" | "workflows",
function convertFilter<T extends TypesFilterConfig & WorkflowFilterConfig & VersionsFilterConfig & NamesFilterConfig>(
name: "types" | "workflows" | "versions" | "names",
token: MappingToken
): T {
const result = {} as T;
+16 -2
View File
@@ -16,7 +16,16 @@ export function convertJob(context: TemplateContext, jobKey: StringToken, token:
context.error(jobKey, error);
}
let concurrency, container, env, environment, name, outputs, runsOn, services, strategy: TemplateToken | undefined;
let concurrency,
container,
env,
environment,
name,
outputs,
runsOn,
services,
strategy,
snapshot: TemplateToken | undefined;
let needs: StringToken[] | undefined = undefined;
let steps: Step[] = [];
let workflowJobRef: StringToken | undefined;
@@ -86,6 +95,10 @@ export function convertJob(context: TemplateContext, jobKey: StringToken, token:
services = item.value;
break;
case "snapshot":
snapshot = item.value;
break;
case "steps":
steps = convertSteps(context, item.value);
break;
@@ -147,7 +160,8 @@ export function convertJob(context: TemplateContext, jobKey: StringToken, token:
container,
services,
outputs,
steps
steps,
snapshot
};
}
}
@@ -41,6 +41,7 @@ export type BaseJob = {
concurrency?: TemplateToken;
strategy?: TemplateToken;
outputs?: MappingToken;
snapshot?: TemplateToken;
};
// `job-factory` in the schema
@@ -129,6 +130,7 @@ export type EventsConfig = {
repository_dispatch?: TypesFilterConfig;
release?: TypesFilterConfig;
watch?: TypesFilterConfig;
image_versions?: TypesFilterConfig & VersionsFilterConfig & NamesFilterConfig;
// Index signature to allow easier lookup
[eventName: string]: unknown;
@@ -138,6 +140,14 @@ export type TypesFilterConfig = {
types?: string[];
};
export type VersionsFilterConfig = {
versions?: string[];
};
export type NamesFilterConfig = {
names?: string[];
};
export type BranchFilterConfig = {
branches?: string[];
"branches-ignore"?: string[];
@@ -1,6 +1,7 @@
// template-reader *just* does schema validation
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import {Scalar} from "yaml";
import {ObjectReader} from "./object-reader";
import {TemplateSchema} from "./schema";
import {DefinitionInfo} from "./schema/definition-info";
@@ -459,7 +460,20 @@ class TemplateReader {
// Doesn't contain "${{"
// Check if value should still be evaluated as an expression
if (definitionInfo.definition instanceof StringDefinition && definitionInfo.definition.isExpression) {
const expression = this.parseIntoExpressionToken(token.range!, raw, allowedContext, token, definitionInfo);
// For isExpression fields (e.g., 'if' conditions), parse token.value (not raw).
// Example YAML:
// if: |
// github.event_name == 'push'
// token.source/raw = "|\n github.event_name == 'push'\n" (includes block scalar header)
// token.value = "github.event_name == 'push'\n" (clean expression content)
// We need token.value because the '|' would interfere with expression parsing.
const expression = this.parseIntoExpressionToken(
token.range!,
token.value,
allowedContext,
token,
definitionInfo
);
if (expression) {
return expression;
}
@@ -606,13 +620,17 @@ class TemplateReader {
}
}
const blockScalarInfo = parseBlockScalarInfo(token);
return new BasicExpressionToken(
this._fileId,
token.range,
`format('${format.join("")}'${args.join("")})`,
definitionInfo,
expressionTokens,
raw
raw,
undefined,
blockScalarInfo.scalarType,
blockScalarInfo.chompStyle
);
}
@@ -686,6 +704,7 @@ class TemplateReader {
};
// Return the expression
const blockScalarInfo = parseBlockScalarInfo(token);
return <ParseExpressionResult>{
expression: new BasicExpressionToken(
this._fileId,
@@ -694,7 +713,9 @@ class TemplateReader {
definitionInfo,
undefined,
token.source,
expressionRange
expressionRange,
blockScalarInfo.scalarType,
blockScalarInfo.chompStyle
),
error: undefined
};
@@ -801,3 +822,53 @@ interface MatchDirectiveResult {
parameters: string[];
error: Error | undefined;
}
interface BlockScalarInfo {
scalarType: Scalar.Type | undefined;
chompStyle: "clip" | "strip" | "keep" | undefined;
}
/**
* Parse the block scalar info from the StringToken
* @param token The StringToken that may contain block scalar information
* @returns The scalar type and chomp style
*/
function parseBlockScalarInfo(token: StringToken): BlockScalarInfo {
const scalarType = token.scalarType;
// Only block scalars have chomp styles
if (scalarType !== Scalar.BLOCK_LITERAL && scalarType !== Scalar.BLOCK_FOLDED) {
return {scalarType: undefined, chompStyle: undefined};
}
// Parse chomp style from the block scalar header
// Look for block scalar indicators at the start: | or >
// Followed by optional chomp indicator (-, +) and/or explicit indent (digit)
// Examples: |, |-, |+, |2, |-2, |+2, >, >-, >+, >2, >-2, >+2
const header = token.blockScalarHeader;
if (!header) {
// If there's no header, assume clip (default)
return {scalarType, chompStyle: "clip"};
}
const blockScalarMatch = header.match(/^(\||>)([-+])?(\d)?/);
if (!blockScalarMatch) {
// Assume clip if we can't parse the indicator
return {scalarType, chompStyle: "clip"};
}
const chompIndicator = blockScalarMatch[2];
let chompStyle: "clip" | "strip" | "keep";
if (chompIndicator === "-") {
chompStyle = "strip";
} else if (chompIndicator === "+") {
chompStyle = "keep";
} else {
// No chomp indicator means clip (default)
chompStyle = "clip";
}
return {scalarType, chompStyle};
}
@@ -1,3 +1,4 @@
import {Scalar} from "yaml";
import {DefinitionInfo} from "../schema/definition-info";
import {CLOSE_EXPRESSION, OPEN_EXPRESSION} from "../template-constants";
@@ -23,6 +24,16 @@ export class BasicExpressionToken extends ExpressionToken {
*/
public readonly expressionRange: TokenRange | undefined;
/**
* The YAML scalar type (e.g., BLOCK_LITERAL, BLOCK_FOLDED, PLAIN, etc.) if the expression was parsed from a block scalar.
*/
public readonly scalarType: Scalar.Type | undefined;
/**
* The chomp style of the block scalar: 'clip' (default, keeps one newline), 'strip' (removes all), or 'keep' (keeps all).
*/
public readonly chompStyle: "clip" | "strip" | "keep" | undefined;
/**
* @param originalExpressions If the basic expression was transformed from individual expressions, these will be the original ones
*/
@@ -33,13 +44,17 @@ export class BasicExpressionToken extends ExpressionToken {
definitionInfo: DefinitionInfo | undefined,
originalExpressions: BasicExpressionToken[] | undefined,
source: string | undefined,
expressionRange?: TokenRange | undefined
expressionRange?: TokenRange | undefined,
scalarType?: Scalar.Type | undefined,
chompStyle?: "clip" | "strip" | "keep" | undefined
) {
super(TokenType.BasicExpression, file, range, undefined, definitionInfo);
this.expr = expression;
this.source = source;
this.originalExpressions = originalExpressions;
this.expressionRange = expressionRange;
this.scalarType = scalarType;
this.chompStyle = chompStyle;
}
public get expression(): string {
@@ -55,7 +70,9 @@ export class BasicExpressionToken extends ExpressionToken {
this.definitionInfo,
this.originalExpressions,
this.source,
this.expressionRange
this.expressionRange,
this.scalarType,
this.chompStyle
)
: new BasicExpressionToken(
this.file,
@@ -64,7 +81,9 @@ export class BasicExpressionToken extends ExpressionToken {
this.definitionInfo,
this.originalExpressions,
this.source,
this.expressionRange
this.expressionRange,
this.scalarType,
this.chompStyle
);
}
@@ -1,3 +1,4 @@
import {Scalar} from "yaml";
import {LiteralToken, TemplateToken} from ".";
import {DefinitionInfo} from "../schema/definition-info";
import {TokenRange} from "./token-range";
@@ -6,23 +7,45 @@ import {TokenType} from "./types";
export class StringToken extends LiteralToken {
public readonly value: string;
public readonly source: string | undefined;
public readonly scalarType: Scalar.Type | undefined;
public readonly blockScalarHeader: string | undefined;
public constructor(
file: number | undefined,
range: TokenRange | undefined,
value: string,
definitionInfo: DefinitionInfo | undefined,
source?: string
source?: string,
scalarType?: Scalar.Type,
blockScalarHeader?: string
) {
super(TokenType.String, file, range, definitionInfo);
this.value = value;
this.source = source;
this.scalarType = scalarType;
this.blockScalarHeader = blockScalarHeader;
}
public override clone(omitSource?: boolean): TemplateToken {
return omitSource
? new StringToken(undefined, undefined, this.value, this.definitionInfo, this.source)
: new StringToken(this.file, this.range, this.value, this.definitionInfo, this.source);
? new StringToken(
undefined,
undefined,
this.value,
this.definitionInfo,
this.source,
this.scalarType,
this.blockScalarHeader
)
: new StringToken(
this.file,
this.range,
this.value,
this.definitionInfo,
this.source,
this.scalarType,
this.blockScalarHeader
);
}
public override toString(): string {
+111 -2
View File
@@ -7,6 +7,7 @@
"properties": {
"on": "on",
"name": "workflow-name",
"description": "workflow-description",
"run-name": "run-name",
"defaults": "workflow-defaults",
"env": "workflow-env",
@@ -28,6 +29,7 @@
"required": true
},
"name": "workflow-name",
"description": "workflow-description",
"run-name": "run-name",
"defaults": "workflow-defaults",
"env": "workflow-env",
@@ -44,6 +46,10 @@
"description": "The name of the workflow that GitHub displays on your repository's 'Actions' tab.\n\n[Documentation](https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions#name)",
"string": {}
},
"workflow-description": {
"description": "A description for your workflow or reusable workflow",
"string": {}
},
"run-name": {
"context": [
"github",
@@ -93,6 +99,7 @@
"discussion_comment": "discussion-comment",
"fork": "fork",
"gollum": "gollum",
"image_version": "image-version",
"issue_comment": "issue-comment",
"issues": "issues",
"label": "label",
@@ -134,6 +141,7 @@
"discussion-comment-string",
"fork-string",
"gollum-string",
"image-version-string",
"issue-comment-string",
"issues-string",
"label-string",
@@ -430,6 +438,47 @@
"description": "Runs your workflow when someone creates or updates a Wiki page.",
"null": {}
},
"image-version-string": {
"description": "Runs your workflow when an image version is created or changes state.",
"string": {
"constant": "image_version"
}
},
"image-version": {
"description": "Runs your workflow when an image version is created or changes state.",
"one-of": [
"null",
"image-version-mapping"
]
},
"image-version-mapping": {
"mapping": {
"properties": {
"types": "image-version-activity",
"names": "event-names",
"versions": "event-versions"
}
}
},
"image-version-activity": {
"description": "The types of image version activity that trigger the workflow. Supported activity types: `created`, `ready`, `deleted`.",
"one-of": [
"image-version-activity-type",
"image-version-activity-types"
]
},
"image-version-activity-types": {
"sequence": {
"item-type": "image-version-activity-type"
}
},
"image-version-activity-type": {
"allowed-values": [
"created",
"ready",
"deleted"
]
},
"issue-comment-string": {
"description": "Runs your workflow when an issue or pull request comment is created, edited, or deleted.",
"string": {
@@ -1215,6 +1264,13 @@
"sequence-of-non-empty-string"
]
},
"event-names": {
"description": "Use the `names` filter when you want to include names via patterns or when you want to both include and exclude names using patterns. ",
"one-of": [
"non-empty-string",
"sequence-of-non-empty-string"
]
},
"event-tags": {
"description": "Use the `tags` filter when you want to include tag name patterns or when you want to both include and exclude tag names patterns. You cannot use both the `tags` and `tags-ignore` filters for the same event in a workflow.",
"one-of": [
@@ -1243,6 +1299,13 @@
"sequence-of-non-empty-string"
]
},
"event-versions": {
"description": "Use the `versions` filter when you want to include versions via patterns or when you want to both include and exclude versions using patterns. ",
"one-of": [
"non-empty-string",
"sequence-of-non-empty-string"
]
},
"repository-dispatch-string": {
"description": "You can use the GitHub API to trigger a webhook event called `repository_dispatch` when you want to trigger a workflow for activity that happens outside of GitHub.",
"string": {
@@ -1515,6 +1578,10 @@
"type": "permission-level-any",
"description": "Actions workflows, workflow runs, and artifacts."
},
"artifact-metadata": {
"type": "permission-level-any",
"description": "Storage and deployment records for build artifacts."
},
"attestations": {
"type": "permission-level-any",
"description": "Artifact attestations."
@@ -1543,6 +1610,10 @@
"type": "permission-level-any",
"description": "Issues and related comments, assignees, labels, and milestones."
},
"models": {
"type": "permission-level-read-or-no-access",
"description": "Call AI models with GitHub Models."
},
"packages": {
"type": "permission-level-any",
"description": "Packages published to the GitHub Package Platform."
@@ -1700,7 +1771,8 @@
"concurrency": "job-concurrency",
"outputs": "job-outputs",
"defaults": "job-defaults",
"steps": "steps"
"steps": "steps",
"snapshot": "snapshot"
}
}
},
@@ -1844,6 +1916,43 @@
"loose-value-type": "any"
}
},
"snapshot": {
"description": "Use `snapshot` to define a custom image you want to create or update after your job succeeds by taking a snapshot of your runner.",
"one-of": [
"non-empty-string",
"snapshot-mapping"
]
},
"snapshot-mapping": {
"mapping": {
"properties": {
"image-name": {
"description": "The desired name of the custom image you want to create or update.",
"type": "non-empty-string",
"required": true
},
"if": "snapshot-if",
"version": {
"description": "The desired major version updates upon a new custom image version creation.",
"type": "non-empty-string"
}
}
}
},
"snapshot-if": {
"context": [
"github",
"inputs",
"vars",
"needs",
"strategy",
"matrix"
],
"description": "Use the if conditional to prevent a snapshot from being taken unless a condition is met. Any supported context and expression can be used to create a conditional. Expressions in an `if` conditional do not require the bracketed expression syntax. When you use expressions in an `if` conditional, you may omit the expression syntax because GitHub automatically evaluates the `if` conditional as an expression.",
"string": {
"is-expression": true
}
},
"runs-on": {
"description": "Use `runs-on` to define the type of machine to run the job on.\n* The destination machine can be either a GitHub-hosted runner, larger runner, or a self-hosted runner.\n* You can target runners based on the labels assigned to them, or their group membership, or a combination of these.\n* You can provide `runs-on` as a single string or as an array of strings.\n* If you specify an array of strings, your workflow will execute on any runner that matches all of the specified `runs-on` values.\n* If you would like to run your workflow on multiple machines, use `jobs.<job_id>.strategy`.",
"context": [
@@ -2514,4 +2623,4 @@
}
}
}
}
}
@@ -1,6 +1,6 @@
import {JSONObjectReader} from "../templates/json-object-reader";
import {TemplateSchema} from "../templates/schema";
import WorkflowSchema from "../workflow-v1.0.json" assert {type: "json"};
import WorkflowSchema from "../workflow-v1.0.json";
let schema: TemplateSchema;
@@ -117,11 +117,28 @@ export class YamlObjectReader implements ObjectReader {
return new BooleanToken(fileId, range, value, undefined);
case "string": {
let source: string | undefined;
const scalarType = token.type;
let blockScalarHeader: string | undefined;
if (token.srcToken && "source" in token.srcToken) {
source = token.srcToken.source;
// Extract block scalar header. For example |-, |+, >-
//
// This relies on undocumented internal behavior (srcToken.props).
// Feature request for official support: https://github.com/eemeli/yaml/issues/643
if (token.srcToken.type === "block-scalar" && "props" in token.srcToken) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const props = token.srcToken.props as any[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
const headerProp = props.find((p: any) => p.type === "block-scalar-header");
if (headerProp && "source" in headerProp) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
blockScalarHeader = headerProp.source;
}
}
}
return new StringToken(fileId, range, value, undefined, source);
return new StringToken(fileId, range, value, undefined, source, scalarType, blockScalarHeader);
}
default:
throw new Error(`Unexpected value type '${typeof value}' when reading object`);
@@ -0,0 +1,58 @@
include-source: false # Drop file/line/col from output
---
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo hi
snapshot:
image-name: custom-image
version: 1.*
if: ${{ github.event_name == 'something' }}
---
{
"jobs": [
{
"type": "job",
"id": "build",
"name": "build",
"if": {
"type": 3,
"expr": "success()"
},
"runs-on": "ubuntu-latest",
"steps": [
{
"id": "__run",
"if": {
"type": 3,
"expr": "success()"
},
"run": "echo hi"
}
],
"snapshot": {
"type": 2,
"map": [
{
"Key": "image-name",
"Value": "custom-image"
},
{
"Key": "version",
"Value": "1.*"
},
{
"Key": "if",
"Value": {
"type": 3,
"expr": "github.event_name == 'something'"
}
}
]
}
}
]
}
+42
View File
@@ -0,0 +1,42 @@
include-source: false # Drop file/line/col from output
---
# on: push
# jobs:
# job1:
# runs-on: windows-2019
# snapshot: custom-image
# steps:
# - run: echo 1
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo hi
snapshot: custom-image
---
{
"jobs": [
{
"type": "job",
"id": "build",
"name": "build",
"if": {
"type": 3,
"expr": "success()"
},
"runs-on": "ubuntu-latest",
"steps": [
{
"id": "__run",
"if": {
"type": 3,
"expr": "success()"
},
"run": "echo hi"
}
],
"snapshot": "custom-image"
}
]
}
@@ -0,0 +1,49 @@
include-source: false
skip:
- C#
- Go
---
on:
image_version:
names: testing
versions: 1.*
jobs:
my-job:
runs-on: ubuntu-latest
steps:
- run: echo hi
---
{
"events": {
"image_version": {
"versions": [
"1.*"
],
"names": [
"testing"
]
}
},
"jobs": [
{
"type": "job",
"id": "my-job",
"name": "my-job",
"if": {
"type": 3,
"expr": "success()"
},
"runs-on": "ubuntu-latest",
"steps": [
{
"id": "__run",
"if": {
"type": 3,
"expr": "success()"
},
"run": "echo hi"
}
]
}
]
}
@@ -0,0 +1,53 @@
include-source: false
skip:
- C#
- Go
---
on:
image_version:
types:
- ready
names:
- one
- two
jobs:
my-job:
runs-on: ubuntu-latest
steps:
- run: echo hi
---
{
"events": {
"image_version": {
"types": [
"ready"
],
"names": [
"one",
"two"
]
}
},
"jobs": [
{
"type": "job",
"id": "my-job",
"name": "my-job",
"if": {
"type": 3,
"expr": "success()"
},
"runs-on": "ubuntu-latest",
"steps": [
{
"id": "__run",
"if": {
"type": 3,
"expr": "success()"
},
"run": "echo hi"
}
]
}
]
}
@@ -0,0 +1,53 @@
include-source: false
skip:
- C#
- Go
---
on:
image_version:
types:
- ready
versions:
- "1.0.0"
- "1.0.1"
jobs:
my-job:
runs-on: ubuntu-latest
steps:
- run: echo hi
---
{
"events": {
"image_version": {
"types": [
"ready"
],
"versions": [
"1.0.0",
"1.0.1"
]
}
},
"jobs": [
{
"type": "job",
"id": "my-job",
"name": "my-job",
"if": {
"type": 3,
"expr": "success()"
},
"runs-on": "ubuntu-latest",
"steps": [
{
"id": "__run",
"if": {
"type": 3,
"expr": "success()"
},
"run": "echo hi"
}
]
}
]
}
@@ -0,0 +1,39 @@
include-source: false
skip:
- C#
- Go
---
on: image_version
jobs:
my-job:
runs-on: ubuntu-latest
steps:
- run: echo hi
---
{
"events": {
"image_version": {}
},
"jobs": [
{
"type": "job",
"id": "my-job",
"name": "my-job",
"if": {
"type": 3,
"expr": "success()"
},
"runs-on": "ubuntu-latest",
"steps": [
{
"id": "__run",
"if": {
"type": 3,
"expr": "success()"
},
"run": "echo hi"
}
]
}
]
}
@@ -0,0 +1,34 @@
include-source: false # Drop file/line/col from output
---
description: My workflow description
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo hi
---
{
"jobs": [
{
"type": "job",
"id": "build",
"name": "build",
"if": {
"type": 3,
"expr": "success()"
},
"runs-on": "ubuntu-latest",
"steps": [
{
"id": "__run",
"if": {
"type": 3,
"expr": "success()"
},
"run": "echo hi"
}
]
}
]
}