├── .eslintignore
├── .gitattributes
├── .gitignore
├── SECURITY.md
├── .github
├── workflows
│ ├── build-and-publish.yml
│ └── graphs
│ │ └── build-and-publish.yml
├── PULL_REQUEST_TEMPLATE.md
└── ISSUE_TEMPLATE.md
├── src
├── bundled_package.json
└── main.ts
├── LICENSE.md
├── CONTRIBUTING.md
├── .eslintrc.json
├── package.json
├── action.yml
├── README.md
├── CODE_OF_CONDUCT.md
└── tsconfig.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist/
2 | lib/
3 | node_modules/
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | hello-world-go-action filter=lfs diff=lfs merge=lfs -text
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | __test__/_temp
2 | _temp/
3 | lib/
4 | node_modules/
5 | .DS_Store
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Reporting a Vulnerability
4 |
5 | If you discover a vulnerability that is not suitable for public discussion, please send an email to [security@actionforge.dev](mailto:security@actionforge.dev). The email will be processed within 24 hours. If you prefer, you can also report security issues via GitHub Issues.
--------------------------------------------------------------------------------
/.github/workflows/build-and-publish.yml:
--------------------------------------------------------------------------------
1 | on: [push]
2 |
3 | jobs:
4 | build-and-publish:
5 | runs-on: ubuntu-latest
6 | name: Execute action graph
7 | steps:
8 | - name: Execute action graph
9 | uses: actionforge/action@1cb7c397d2c7caa8d8071041976b6f2e4e2ead20 # v0.9.58
10 | with:
11 | graph_file: build-and-publish.yml
--------------------------------------------------------------------------------
/src/bundled_package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "action",
3 | "binaries": {
4 | "linux-x64": "6bf6dc96a334b2f7a975ad6a8f06a67e49401d476088f618b7d9dafb07a390e1",
5 | "darwin-x64": "e60013321145db2365fe1bddbe6ba7f712de49d8af50936ba4877964617cb8c2",
6 | "darwin-arm64": "7bcf44be4979c154fd7f06905b6b6fe0e0f39272d1c44d981cbff2a71eedfce1",
7 | "win32-x64": "87db763ce2252fbb6e63f24ddadc2b4183f0bdfa39adbf1f1686d20b931c5247"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Contributing to Actionforge
2 |
3 | ❤️ First off, thanks for taking the time to contribute! ❤️
4 |
5 | Before you create a pull request, please make sure you check the following:
6 |
7 | - [ ] A description of the problem you're trying to solve.
8 | - [ ] An overview of the suggested solution.
9 | - [ ] Your code should contain tests relevant for the problem you are solving.
10 | - [ ] If the feature changes current behavior, reasons why your solution is better.
11 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # Actionforge Community License (ACL) Version 1.0.0
2 |
3 | This SOFTWARE is licensed under the Actionforge Community License that you can find [here](https://github.com/actionforge/legal/blob/main/LICENSE.md).
4 |
5 | Licenses for commercial use will soon be available on the GitHub Marketplace.
6 |
7 | For further information [Get in touch](mailto:hello@actionforge.dev).
8 |
9 | ## Definitions
10 |
11 | - **SOFTWARE**: Refers to both the source files and binaries included in the project.
12 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ❤️ Every contribution is welcome! Whether you're fixing a bug, adding new features, sharing an idea, or improving documentation, your help makes the project so much better.
4 |
5 | Please read the [Code of Conduct](CODE_OF_CONDUCT.md) before contributing.
6 |
7 | Other ways to contribute:
8 |
9 | - ⭐ Star [actionforge/action](https://github.com/actionforge/action)
10 | - 🐦 Follow Actionforge on X [@actionforge](https://twitter.com/actionforge)
11 | - 📝 Share what you think about Actionforge.
12 |
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ❤️ First off, thanks for taking the time to contribute! ❤️
2 |
3 | ## Describe the bug or feature
4 |
5 | A clear and concise description of what the bug is.
6 |
7 | ## To reproduce a bug
8 |
9 | Steps to reproduce the behavior:
10 |
11 | 1. Go to '...'
12 | 2. Click on '....'
13 | 3. Scroll down to '....'
14 | 4. See error
15 |
16 | ## Expected behavior
17 |
18 | A clear and concise description of what you expected to happen.
19 |
20 | ## Screenshots
21 |
22 | If applicable, add screenshots to help explain your problem.
23 |
24 | ## GitHub Repo Link
25 |
26 | If applicable, add a link to the GitHub Actions workflow.
27 |
28 | ## Additional context
29 |
30 | Add any other context about the problem here.
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "@typescript-eslint/parser",
4 | "parserOptions": {
5 | "project": [
6 | "tsconfig.json"
7 | ]
8 | },
9 | "overrides": [
10 | {
11 | "files": [
12 | "*.ts"
13 | ],
14 | "extends": [
15 | "eslint:recommended",
16 | "plugin:@typescript-eslint/recommended"
17 | ],
18 | "rules": {
19 | "@typescript-eslint/no-unused-vars": [
20 | "error",
21 | {
22 | "argsIgnorePattern": "^_",
23 | "varsIgnorePattern": "^_",
24 | "caughtErrorsIgnorePattern": "^_"
25 | }
26 | ],
27 | "@typescript-eslint/no-floating-promises": "error",
28 | "@typescript-eslint/explicit-function-return-type": [
29 | "error",
30 | {
31 | "allowedNames": [
32 | "ignoredFunctionName",
33 | "ignoredMethodName"
34 | ]
35 | }
36 | ],
37 | "@typescript-eslint/no-shadow": "error"
38 | }
39 | },
40 | {
41 | "files": [
42 | "*.html"
43 | ],
44 | "rules": {}
45 | }
46 | ]
47 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "action",
3 | "version": "0.9.58",
4 | "description": "",
5 | "main": "dist/index.js",
6 | "scripts": {
7 | "build": "tsc && ncc build src/main.ts -o dist",
8 | "test": "echo \"Error: no test specified\" && exit 1",
9 | "lint": "eslint src"
10 | },
11 | "keywords": [
12 | "github",
13 | "actions",
14 | "workflows",
15 | "node",
16 | "graph"
17 | ],
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/actionforge/action.git"
21 | },
22 | "author": "Actionforge",
23 | "license": "Actionforge Community License",
24 | "bugs": {
25 | "url": "https://github.com/actionforge/action/issues"
26 | },
27 | "homepage": "https://github.com/actionforge/action#readme",
28 | "dependencies": {
29 | "@actions/core": "^1.10.1",
30 | "@actions/github": "^6.0.0",
31 | "@actions/tool-cache": "^2.0.1",
32 | "@octokit/action": "^7.0.0",
33 | "got": "^14.3.0"
34 | },
35 | "devDependencies": {
36 | "@types/got": "^9.6.12",
37 | "@types/node": "^20.12.12",
38 | "@typescript-eslint/eslint-plugin": "^7.10.0",
39 | "@typescript-eslint/parser": "^7.10.0",
40 | "@vercel/ncc": "^0.38.1",
41 | "eslint": "^8.56.0",
42 | "typescript": "^5.4.5"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/action.yml:
--------------------------------------------------------------------------------
1 | name: Actionforge
2 | description: GitHub Action for executing an Actionforge Action Graph.
3 | inputs:
4 | graph_file:
5 | description: >
6 | The name of the graph file located in the `.github/workflows/graphs` directory.
7 | This file defines the Actionforge Action Graph to be executed.
8 | required: true
9 | token:
10 | description: >
11 | A Personal Access Token (PAT) used for authentication.
12 | If not provided, the default GitHub token for the repository will be used.
13 |
14 | [Learn more about creating and using
15 | encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
16 | default: ${{ github.token }}
17 | inputs:
18 | description: >
19 | A JSON string containing the inputs to be passed to the Actionforge action graph.
20 | required: false
21 | default: '{}'
22 | secrets:
23 | description: >
24 | A JSON string containing the secrets to be passed to the Actionforge action graph.
25 | required: false
26 | default: '{}'
27 | matrix:
28 | description: >
29 | A JSON string containing the matrix to be passed to the Actionforge action graph.
30 | required: false
31 | default: '{}'
32 | runner_base_url:
33 | description: >
34 | Custom runner URL from which to obtain the graph runner binary.
35 | required: false
36 | default: ''
37 | runner_path:
38 | description: >
39 | Custom path to the graph runner binary to execute the action graph.
40 | required: false
41 | default: ''
42 | branding:
43 | icon: 'check'
44 | color: 'green'
45 | runs:
46 | using: 'node20'
47 | main: 'dist/index.js'
48 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | 
3 |
4 | # Actionforge Action
5 |
6 |
7 |
8 | [](https://www.actionforge.dev/github/actionforge/action/main/.github/workflows/graphs/build-and-publish.yml)
9 | [](https://www.typescriptlang.org/)
10 | [](https://www.github.com/actionforge/legal/blob/main/LICENSE.md)
11 |
12 |
13 |
14 | Welcome to the Actionforge Action source code!
15 |
16 | Actionforge introduces Action Graphs, a faster and easier way to create and execute GitHub workflows. This new approach replaces tedious YAML file editing by hand with a user-friendly graph editor. Action Graphs are compatible with GitHub Actions. The core product consists out of 4 components:
17 |
18 | - ♾️ [VS Code Extension](https://www.github.com/actionforge/vscode-ext) - Extension to modify Action Graphs within VS Code.
19 | - 🟢 [GitHub Action](https://www.github.com/actionforge/action) - GitHub Action that reads the Action Graph and starts the Graph Runner with it.
20 | - 🏃♀️ [Graph Runner](https://www.github.com/actionforge/graph-runner) - The cli program that executes an Action Graph.
21 | - 🕸️ [Graph Editor](https://www.github.com/actionforge/graph-editor) - Visual graph editor to create and build Action Graphs. These graphs will be committed to your repository.
22 |
23 | For a full introduction check out the [Actionforge Documentation](https://www.actionforge.dev/docs).
24 |
25 | ## Usage
26 |
27 |
28 | ```yaml
29 | - uses: actionforge/action@1cb7c397d2c7caa8d8071041976b6f2e4e2ead20 # v0.9.58
30 | with:
31 | # The name of the graph file located in the `.github/workflows/graphs` directory.
32 | # This file defines the Actionforge Action Graph to be executed.
33 | graph_file: ''
34 |
35 | # A Personal Access Token (PAT) used for authentication.
36 | # If not provided, the default GitHub token for the repository will be used.
37 | # Default: ${{ github.token }}
38 | #
39 | # [Learn more about creating and using encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
40 | token: ''
41 | ```
42 |
43 | For examples check out the [Actionforge Examples](https://www.github.com/actionforge/examples) 🔗 repository.
44 |
45 | ## License
46 |
47 | This SOFTWARE is licensed under the Actionforge Community License that you can find [here](https://github.com/actionforge/legal/blob/main/LICENSE.md).
48 |
49 | Licenses for commercial use will soon be available on the GitHub Marketplace.
50 |
51 | For further information [Get in touch](mailto:hello@actionforge.dev).
52 |
--------------------------------------------------------------------------------
/.github/workflows/graphs/build-and-publish.yml:
--------------------------------------------------------------------------------
1 | entry: gh-start
2 | executions:
3 | - src:
4 | node: github-com-actions-setup-node-cherry-grape-banana
5 | port: exec
6 | dst:
7 | node: run-v1-strawberry-cat-blueberry
8 | port: exec
9 | - src:
10 | node: gh-start
11 | port: exec-on-push
12 | dst:
13 | node: github-com-actions-checkout-coconut-coconut-banana
14 | port: exec
15 | - src:
16 | node: github-com-actions-checkout-coconut-coconut-banana
17 | port: exec
18 | dst:
19 | node: branch-v1-orange-giraffe-lion
20 | port: exec
21 | - src:
22 | node: branch-v1-orange-giraffe-lion
23 | port: exec-then
24 | dst:
25 | node: run-v1-brown-brown-panda
26 | port: exec
27 | - src:
28 | node: run-v1-brown-brown-panda
29 | port: exec-success
30 | dst:
31 | node: github-com-actions-setup-node-cherry-grape-banana
32 | port: exec
33 | - src:
34 | node: branch-v1-orange-giraffe-lion
35 | port: exec-otherwise
36 | dst:
37 | node: github-com-actions-setup-node-cherry-grape-banana
38 | port: exec
39 | connections:
40 | - src:
41 | node: env-get-v1-grape-tiger-cherry
42 | port: env
43 | dst:
44 | node: string-match-v1-panda-orange-gold
45 | port: str1
46 | - src:
47 | node: string-match-v1-panda-orange-gold
48 | port: result
49 | dst:
50 | node: branch-v1-orange-giraffe-lion
51 | port: condition
52 | nodes:
53 | - id: gh-start
54 | type: gh-start@v1
55 | position:
56 | x: -380
57 | y: 150
58 | settings:
59 | folded: false
60 | - id: github-com-actions-checkout-coconut-coconut-banana
61 | type: github.com/actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29
62 | position:
63 | x: 130
64 | y: 50
65 | settings:
66 | folded: false
67 | - id: github-com-actions-setup-node-cherry-grape-banana
68 | type: github.com/actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8
69 | position:
70 | x: 1360
71 | y: 110
72 | inputs:
73 | node-version: v20.10.0
74 | settings:
75 | folded: false
76 | - id: run-v1-strawberry-cat-blueberry
77 | type: run@v1
78 | position:
79 | x: 1920
80 | y: 90
81 | inputs:
82 | script: |-
83 | npm install
84 | npm run lint
85 | settings:
86 | folded: false
87 | - id: run-v1-brown-brown-panda
88 | type: run@v1
89 | position:
90 | x: 950
91 | y: -130
92 | inputs:
93 | script: |-
94 | # Ensure the version in package.json matches the current git tag
95 | PACKAGE_VERSION=$(jq -r '.version' package.json)
96 | TAG_VERSION=${GITHUB_REF_NAME#v}
97 |
98 | if [ "$PACKAGE_VERSION" != "$TAG_VERSION" ]; then
99 | echo "Version mismatch: package.json version is $PACKAGE_VERSION, but tag is v$TAG_VERSION"
100 | exit 1
101 | fi
102 | settings:
103 | folded: false
104 | - id: env-get-v1-grape-tiger-cherry
105 | type: env-get@v1
106 | position:
107 | x: -70
108 | y: 1090
109 | inputs:
110 | env: GITHUB_REF
111 | settings:
112 | folded: false
113 | - id: string-match-v1-panda-orange-gold
114 | type: string-match@v1
115 | position:
116 | x: 270
117 | y: 930
118 | inputs:
119 | op: startswith
120 | str2: refs/tags/
121 | settings:
122 | folded: false
123 | - id: branch-v1-orange-giraffe-lion
124 | type: branch@v1
125 | position:
126 | x: 680
127 | y: 220
128 | settings:
129 | folded: false
130 | registries: []
131 | description: ''
132 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | hello@actionforge.dev.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Basic Options */
4 | // "incremental": true, /* Enable incremental compilation */
5 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
7 | // "allowJs": true, /* Allow javascript files to be compiled. */
8 | // "checkJs": true, /* Report errors in .js files. */
9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */
11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
12 | // "sourceMap": true, /* Generates corresponding '.map' file. */
13 | // "outFile": "./", /* Concatenate and emit output to single file. */
14 | "outDir": "./lib", /* Redirect output structure to the directory. */
15 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
16 | // "composite": true, /* Enable project compilation */
17 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
18 | // "removeComments": true, /* Do not emit comments to output. */
19 | // "noEmit": true, /* Do not emit outputs. */
20 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
21 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
22 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
23 |
24 | /* Strict Type-Checking Options */
25 | "strict": true, /* Enable all strict type-checking options. */
26 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
27 | // "strictNullChecks": true, /* Enable strict null checks. */
28 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
29 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
30 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
31 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
32 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
33 |
34 | /* Additional Checks */
35 | // "noUnusedLocals": true, /* Report errors on unused locals. */
36 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
37 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
38 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
39 |
40 | /* Module Resolution Options */
41 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
42 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
43 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
44 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
45 | // "typeRoots": [], /* List of folders to include type definitions from. */
46 | // "types": [], /* Type declaration files to be included in compilation. */
47 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
48 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
49 | "resolveJsonModule": true, /* Allows importing modules with a '.json' extension, which is a common practice in node projects. */
50 | "sourceMap": true,
51 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
52 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
53 |
54 | /* Source Map Options */
55 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
56 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
57 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
58 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
59 |
60 | /* Experimental Options */
61 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
62 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
63 | },
64 | "exclude": ["__test__", "lib", "node_modules"]
65 | }
66 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import * as core from "@actions/core";
2 | import * as github from "@actions/github";
3 | import * as stream from "stream";
4 | import * as util from "util";
5 | import * as crypto from "crypto";
6 | import * as tc from "@actions/tool-cache";
7 | import * as pjdata from './bundled_package.json';
8 | import * as pjson from './../package.json';
9 |
10 | import os from "os";
11 | import path from "path";
12 | import fs from "fs";
13 | import got from "got";
14 | import cp from "child_process";
15 |
16 | const packageJson: Record = pjson as unknown as Record;
17 | const pj: BundledPackage = pjdata as unknown as BundledPackage;
18 |
19 | type BundledPackage = {
20 | name: string;
21 | binaries: Record;
22 | };
23 |
24 | const debugOutput = process.env.ACTIONS_STEP_DEBUG === 'true';
25 |
26 | /**
27 | * Represents information about a runner version,
28 | * including its download URL, resolved version, and file name.
29 | */
30 | export interface IRunnerVersionInfo {
31 | downloadUrl: string;
32 | filename: string;
33 | }
34 |
35 | /**
36 | * Extracts the contents of an archive file to a directory.
37 | * @param archivePath The path to the archive file.
38 | * @returns A Promise that resolves to the path of the extracted directory.
39 | */
40 | export async function extractArchive(archivePath: string): Promise {
41 | if (archivePath.endsWith(".tar.gz")) {
42 | return tc.extractTar(archivePath);
43 | } else if (archivePath.endsWith(".zip")) {
44 | return tc.extractZip(archivePath);
45 | } else {
46 | throw new Error(`Unsupported archive format: ${archivePath}`);
47 | }
48 | }
49 |
50 | /**
51 | * Calculates the SHA256 hash of a file.
52 | * @param filePath The path to the file.
53 | * @returns A Promise that resolves to the hash.
54 | */
55 | export async function calculateFileHash(filePath: string): Promise {
56 | return new Promise((resolve, reject) => {
57 | const hash = crypto.createHash('sha256');
58 | const s = fs.createReadStream(filePath);
59 |
60 | s.on('data', (data) => hash.update(data));
61 | s.on('end', () => resolve(hash.digest('hex')));
62 | s.on('error', (err) => reject(err));
63 | });
64 | }
65 |
66 | /**
67 | * Downloads the specified runner version and returns the path to the executable.
68 | * @param info - The runner version information.
69 | * @returns The path to the executable.
70 | */
71 | async function downloadRunner(info: IRunnerVersionInfo, token: string | null, hashCheck: boolean): Promise {
72 | const tempDir = process.env.RUNNER_TEMP || '.';
73 | const zipPath = path.join(tempDir, info.filename + '.zip');
74 | if (debugOutput) {
75 | console.log(`📥 Downloaded zip to ${zipPath} from ${info.downloadUrl} 🚀`);
76 | }
77 |
78 | const pipeline = util.promisify(stream.pipeline);
79 | await pipeline(
80 | got.stream(info.downloadUrl, {
81 | method: "GET",
82 | headers: {
83 | "Accept": "application/octet-stream",
84 | "User-Agent": "GitHub Actions",
85 | ...(token ? {
86 | "Authorization": `token ${token}` } : {}
87 | ),
88 | },
89 | }),
90 | fs.createWriteStream(zipPath)
91 | );
92 |
93 | const zipDstPath = await extractArchive(zipPath);
94 | if (debugOutput) {
95 | console.log(`📦 Extracted runner to ${zipDstPath}`);
96 | }
97 |
98 | let binPath = path.join(zipDstPath, info.filename);
99 | if (os.platform() === "linux" || os.platform() === "darwin") {
100 | fs.chmodSync(binPath, 0o755);
101 | } else {
102 | binPath = binPath + ".exe";
103 | }
104 |
105 | if (hashCheck) {
106 | const hash = await calculateFileHash(binPath);
107 | if (hash.length !== 64 || hash !== pj.binaries[`${os.platform()}-${os.arch()}`]) {
108 | throw new Error(`Hash mismatch for ${binPath}`);
109 | }
110 | }
111 |
112 | return binPath;
113 | }
114 |
115 | /**
116 | * Executes a runner with the specified runnerPath and sets up the required environment variables.
117 | * @param runnerPath The path to the runner executable.
118 | * @param graphFile The path to the graph file.
119 | * @returns A Promise that resolves when the runner has finished executing.
120 | */
121 | async function executeRunner(
122 | runnerPath: string,
123 | graphFile: string,
124 | inputs: string,
125 | matrix: string,
126 | secrets: string,
127 | ): Promise {
128 | const token = core.getInput("token");
129 |
130 | const octokit = github.getOctokit(token);
131 |
132 | const { data } = await octokit.rest.repos.getContent({
133 | owner: github.context.repo.owner,
134 | repo: github.context.repo.repo,
135 | ref: github.context.sha,
136 | path: graphFile,
137 | });
138 |
139 | fs.mkdirSync(path.dirname(graphFile), { recursive: true });
140 | const buf = Buffer.from((data as { content: string }).content, "base64");
141 | fs.writeFileSync(graphFile, buf.toString("utf-8"));
142 |
143 | const customEnv = {
144 | ...process.env,
145 | GRAPH_FILE: graphFile,
146 | INPUT_MATRIX: matrix,
147 | INPUT_INPUTS: inputs,
148 | INPUT_SECRETS: secrets,
149 | };
150 | console.log(`🟢 Running graph-runner`, graphFile);
151 | cp.execSync(runnerPath, { stdio: "inherit", env: customEnv });
152 | }
153 |
154 | /**
155 | * Asserts that the given object is an empty or non-empty string.
156 | */
157 | function assertValidString(o?: string | null): string {
158 | if (o === null || o === undefined || o === "" || o === "null" || o === "{}") {
159 | return "";
160 | } else {
161 | return o;
162 | }
163 | }
164 |
165 | /**
166 | * The main function that downloads and installs the runner.
167 | */
168 | async function run(): Promise {
169 | let runnerPath: string = core.getInput("runner_path", { trimWhitespace: true });
170 |
171 | const runnerBaseUrl: string = core.getInput("runner_base_url", { trimWhitespace: true });
172 | const inputs: string = assertValidString(core.getInput("inputs"));
173 | const matrix: string = assertValidString(core.getInput("matrix"));
174 | const secrets: string = assertValidString(core.getInput("secrets"));
175 |
176 | const token = core.getInput("token");
177 | if (!token) {
178 | throw new Error(`No GitHub token found`)
179 | }
180 |
181 | const GRAPH_FILE_DIR = ".github/workflows/graphs";
182 | let graphFile = path.join(
183 | GRAPH_FILE_DIR,
184 | core.getInput("graph_file", { required: true })
185 | );
186 | if (os.platform() === "win32") {
187 | graphFile = graphFile.replace(/\\/g, "/");
188 | }
189 |
190 | // Log output
191 | const sha = process.env.GITHUB_SHA;
192 | const repo = process.env.GITHUB_REPOSITORY;
193 | const runId = process.env.GITHUB_RUN_ID;
194 | const output = `🟢 View Action Graph: https://www.actionforge.dev/github/${repo}/${sha}/${graphFile}?run_id=${runId}`;
195 | const delimiter = '-'.repeat(32);
196 | console.log(`${delimiter}`);
197 | console.log(output);
198 | console.log(`${delimiter}`);
199 |
200 | if (runnerPath) {
201 | console.log("\u27a1 Custom runner path set:", runnerPath);
202 | } else {
203 | const baseUrl = `https://github.com/actionforge/${pj.name}/releases/download/v${packageJson.version}`;
204 | const downloadUrl = `${runnerBaseUrl.replace(/\/$/, "") || baseUrl}/graph-runner-${os.platform()}-${os.arch()}.zip`;
205 | if (runnerBaseUrl) {
206 | console.log("\u27a1 Custom runner URL set:", downloadUrl);
207 | }
208 |
209 | const downloadInfo = {
210 | downloadUrl: downloadUrl,
211 | filename: `graph-runner-${os.platform()}-${os.arch()}`,
212 | };
213 |
214 | runnerPath = await downloadRunner(downloadInfo, runnerBaseUrl ? null : token, runnerBaseUrl ? false : true);
215 | }
216 |
217 | return executeRunner(runnerPath, graphFile, inputs, matrix, secrets);
218 | }
219 |
220 | async function main(): Promise {
221 | try {
222 | await run();
223 | } catch (error) {
224 | if (error instanceof Error) core.setFailed(error.message);
225 | return process.exit(1);
226 | }
227 | }
228 |
229 | void main();
230 |
--------------------------------------------------------------------------------