actions-up 0.0.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/actions-up.js +5 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +67 -0
- package/dist/core/api/check-updates.d.ts +10 -0
- package/dist/core/api/check-updates.js +139 -0
- package/dist/core/api/client.d.ts +79 -0
- package/dist/core/api/client.js +187 -0
- package/dist/core/ast/guards/has-range.d.ts +10 -0
- package/dist/core/ast/guards/has-range.js +4 -0
- package/dist/core/ast/guards/is-node.d.ts +8 -0
- package/dist/core/ast/guards/is-node.js +4 -0
- package/dist/core/ast/guards/is-pair.d.ts +8 -0
- package/dist/core/ast/guards/is-pair.js +4 -0
- package/dist/core/ast/guards/is-scalar.d.ts +8 -0
- package/dist/core/ast/guards/is-scalar.js +4 -0
- package/dist/core/ast/guards/is-yaml-map.d.ts +8 -0
- package/dist/core/ast/guards/is-yaml-map.js +4 -0
- package/dist/core/ast/guards/is-yaml-sequence.d.ts +8 -0
- package/dist/core/ast/guards/is-yaml-sequence.js +4 -0
- package/dist/core/ast/scanners/scan-composite-action-ast.d.ts +14 -0
- package/dist/core/ast/scanners/scan-composite-action-ast.js +18 -0
- package/dist/core/ast/scanners/scan-workflow-ast.d.ts +14 -0
- package/dist/core/ast/scanners/scan-workflow-ast.js +23 -0
- package/dist/core/ast/update/apply-updates.d.ts +7 -0
- package/dist/core/ast/update/apply-updates.js +40 -0
- package/dist/core/ast/utils/extract-uses-from-steps.d.ts +13 -0
- package/dist/core/ast/utils/extract-uses-from-steps.js +24 -0
- package/dist/core/ast/utils/find-map-pair.d.ts +12 -0
- package/dist/core/ast/utils/find-map-pair.js +10 -0
- package/dist/core/ast/utils/get-line-number.d.ts +10 -0
- package/dist/core/ast/utils/get-line-number.js +9 -0
- package/dist/core/constants.d.ts +4 -0
- package/dist/core/constants.js +4 -0
- package/dist/core/fs/is-yaml-file.d.ts +7 -0
- package/dist/core/fs/is-yaml-file.js +4 -0
- package/dist/core/fs/read-yaml-document.d.ts +11 -0
- package/dist/core/fs/read-yaml-document.js +11 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +4 -0
- package/dist/core/interactive/format-version.d.ts +7 -0
- package/dist/core/interactive/format-version.js +5 -0
- package/dist/core/interactive/pad-string.d.ts +8 -0
- package/dist/core/interactive/pad-string.js +9 -0
- package/dist/core/interactive/prompt-update-selection.d.ts +2 -0
- package/dist/core/interactive/prompt-update-selection.js +203 -0
- package/dist/core/interactive/strip-ansi.d.ts +7 -0
- package/dist/core/interactive/strip-ansi.js +21 -0
- package/dist/core/parsing/parse-action-reference.d.ts +30 -0
- package/dist/core/parsing/parse-action-reference.js +34 -0
- package/dist/core/scan-action-file.d.ts +10 -0
- package/dist/core/scan-action-file.js +7 -0
- package/dist/core/scan-github-actions.d.ts +17 -0
- package/dist/core/scan-github-actions.js +116 -0
- package/dist/core/scan-workflow-file.d.ts +9 -0
- package/dist/core/scan-workflow-file.js +7 -0
- package/dist/core/schema/composite/is-composite-action-runs.d.ts +8 -0
- package/dist/core/schema/composite/is-composite-action-runs.js +6 -0
- package/dist/core/schema/composite/is-composite-action-step.d.ts +8 -0
- package/dist/core/schema/composite/is-composite-action-structure.d.ts +9 -0
- package/dist/core/schema/composite/is-composite-action-structure.js +6 -0
- package/dist/core/schema/workflow/is-workflow-job.d.ts +8 -0
- package/dist/core/schema/workflow/is-workflow-step.d.ts +8 -0
- package/dist/core/schema/workflow/is-workflow-structure.d.ts +8 -0
- package/dist/core/schema/workflow/is-workflow-structure.js +6 -0
- package/dist/package.js +2 -0
- package/dist/types/action-update.d.ts +21 -0
- package/dist/types/composite-action-runs.d.ts +12 -0
- package/dist/types/composite-action-step.d.ts +23 -0
- package/dist/types/composite-action-structure.d.ts +21 -0
- package/dist/types/github-action.d.ts +23 -0
- package/dist/types/scan-result.d.ts +12 -0
- package/dist/types/workflow-job.d.ts +18 -0
- package/dist/types/workflow-step.d.ts +20 -0
- package/dist/types/workflow-structure.d.ts +15 -0
- package/license.md +20 -0
- package/package.json +52 -1
- package/readme.md +175 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { ACTIONS_DIRECTORY, GITHUB_DIRECTORY, WORKFLOWS_DIRECTORY } from "./constants.js";
|
|
2
|
+
import { scanWorkflowFile } from "./scan-workflow-file.js";
|
|
3
|
+
import { scanActionFile } from "./scan-action-file.js";
|
|
4
|
+
import { isYamlFile } from "./fs/is-yaml-file.js";
|
|
5
|
+
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
6
|
+
import { readdir, stat } from "node:fs/promises";
|
|
7
|
+
async function scanGitHubActions(rootPath = process.cwd()) {
|
|
8
|
+
let result = {
|
|
9
|
+
compositeActions: /* @__PURE__ */ new Map(),
|
|
10
|
+
workflows: /* @__PURE__ */ new Map(),
|
|
11
|
+
actions: []
|
|
12
|
+
};
|
|
13
|
+
let normalizedRoot = resolve(rootPath);
|
|
14
|
+
function isWithin(root, candidate) {
|
|
15
|
+
let relativePath = relative(root, candidate);
|
|
16
|
+
return relativePath !== "" && !relativePath.startsWith("..") && !isAbsolute(relativePath);
|
|
17
|
+
}
|
|
18
|
+
let githubPath = join(normalizedRoot, GITHUB_DIRECTORY);
|
|
19
|
+
if (!isWithin(normalizedRoot, githubPath)) throw new Error("Invalid path: detected path traversal attempt");
|
|
20
|
+
function isValidName(name) {
|
|
21
|
+
if (name.includes("..") || name.includes("/") || name.includes("\\")) {
|
|
22
|
+
console.warn(`Skipping invalid name: ${name}`);
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
let workflowsPath = join(githubPath, WORKFLOWS_DIRECTORY);
|
|
28
|
+
if (!isWithin(normalizedRoot, workflowsPath)) return result;
|
|
29
|
+
try {
|
|
30
|
+
let workflowsStat = await stat(workflowsPath);
|
|
31
|
+
if (workflowsStat.isDirectory()) {
|
|
32
|
+
let files = await readdir(workflowsPath);
|
|
33
|
+
let workflowPromises = files.filter((file) => {
|
|
34
|
+
if (!isValidName(file)) return false;
|
|
35
|
+
return isYamlFile(file);
|
|
36
|
+
}).map(async (file) => {
|
|
37
|
+
let filePath = join(workflowsPath, file);
|
|
38
|
+
if (!isWithin(workflowsPath, filePath)) {
|
|
39
|
+
console.warn(`Skipping file outside workflows directory: ${file}`);
|
|
40
|
+
return {
|
|
41
|
+
success: false,
|
|
42
|
+
actions: [],
|
|
43
|
+
path: ""
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
let actions = await scanWorkflowFile(filePath);
|
|
48
|
+
return {
|
|
49
|
+
path: `${GITHUB_DIRECTORY}/${WORKFLOWS_DIRECTORY}/${file}`,
|
|
50
|
+
success: true,
|
|
51
|
+
actions
|
|
52
|
+
};
|
|
53
|
+
} catch {
|
|
54
|
+
return {
|
|
55
|
+
path: `${GITHUB_DIRECTORY}/${WORKFLOWS_DIRECTORY}/${file}`,
|
|
56
|
+
success: false,
|
|
57
|
+
actions: []
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
let workflowResults = await Promise.all(workflowPromises);
|
|
62
|
+
for (let workflow of workflowResults) if (workflow.success && workflow.path) if (workflow.actions.length > 0) {
|
|
63
|
+
result.workflows.set(workflow.path, workflow.actions);
|
|
64
|
+
result.actions.push(...workflow.actions);
|
|
65
|
+
} else result.workflows.set(workflow.path, []);
|
|
66
|
+
}
|
|
67
|
+
} catch {}
|
|
68
|
+
let actionsPath = join(githubPath, ACTIONS_DIRECTORY);
|
|
69
|
+
if (!isWithin(normalizedRoot, actionsPath)) return result;
|
|
70
|
+
try {
|
|
71
|
+
let actionsStat = await stat(actionsPath);
|
|
72
|
+
if (actionsStat.isDirectory()) {
|
|
73
|
+
let subdirectories = await readdir(actionsPath);
|
|
74
|
+
let actionPromises = subdirectories.map(async (subdir) => {
|
|
75
|
+
if (!isValidName(subdir)) return null;
|
|
76
|
+
let subdirPath = join(actionsPath, subdir);
|
|
77
|
+
if (!isWithin(actionsPath, subdirPath)) {
|
|
78
|
+
console.warn(`Skipping subdirectory outside actions path: ${subdir}`);
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
let subdirectoryStat = await stat(subdirPath);
|
|
83
|
+
if (!subdirectoryStat.isDirectory()) return null;
|
|
84
|
+
let actionFilePath = join(subdirPath, "action.yml");
|
|
85
|
+
if (!isWithin(subdirPath, actionFilePath)) return null;
|
|
86
|
+
let actions = [];
|
|
87
|
+
try {
|
|
88
|
+
actions = await scanActionFile(actionFilePath);
|
|
89
|
+
} catch {
|
|
90
|
+
try {
|
|
91
|
+
actionFilePath = join(subdirPath, "action.yaml");
|
|
92
|
+
if (!isWithin(subdirPath, actionFilePath)) return null;
|
|
93
|
+
actions = await scanActionFile(actionFilePath);
|
|
94
|
+
} catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
path: `${GITHUB_DIRECTORY}/${ACTIONS_DIRECTORY}/${subdir}`,
|
|
100
|
+
name: subdir,
|
|
101
|
+
actions
|
|
102
|
+
};
|
|
103
|
+
} catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
let actionResults = await Promise.all(actionPromises);
|
|
108
|
+
for (let actionResult of actionResults) if (actionResult) {
|
|
109
|
+
result.compositeActions.set(actionResult.name, actionResult.path);
|
|
110
|
+
result.actions.push(...actionResult.actions);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} catch {}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
export { scanGitHubActions };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { GitHubAction } from '../types/github-action';
|
|
2
|
+
/**
|
|
3
|
+
* Scans a GitHub workflow file for all action references.
|
|
4
|
+
*
|
|
5
|
+
* @param filePath - The path to the workflow YAML file to scan.
|
|
6
|
+
* @returns A promise that resolves to an array of GitHubAction objects found in
|
|
7
|
+
* the workflow.
|
|
8
|
+
*/
|
|
9
|
+
export declare function scanWorkflowFile(filePath: string): Promise<GitHubAction[]>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { scanWorkflowAst } from "./ast/scanners/scan-workflow-ast.js";
|
|
2
|
+
import { readYamlDocument } from "./fs/read-yaml-document.js";
|
|
3
|
+
async function scanWorkflowFile(filePath) {
|
|
4
|
+
let { document, content } = await readYamlDocument(filePath);
|
|
5
|
+
return scanWorkflowAst(document, content, filePath);
|
|
6
|
+
}
|
|
7
|
+
export { scanWorkflowFile };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { CompositeActionRuns } from '../../../types/composite-action-runs';
|
|
2
|
+
/**
|
|
3
|
+
* Type guard to check if a value conforms to the CompositeActionRuns interface.
|
|
4
|
+
*
|
|
5
|
+
* @param value - The value to check.
|
|
6
|
+
* @returns True if the value is a valid runs configuration.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isCompositeActionRuns(value: unknown): value is CompositeActionRuns;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { CompositeActionStep } from '../../../types/composite-action-step';
|
|
2
|
+
/**
|
|
3
|
+
* Type guard to check if a value conforms to the CompositeActionStep interface.
|
|
4
|
+
*
|
|
5
|
+
* @param value - The value to check.
|
|
6
|
+
* @returns True if the value is a valid composite action step.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isCompositeActionStep(value: unknown): value is CompositeActionStep;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CompositeActionStructure } from '../../../types/composite-action-structure';
|
|
2
|
+
/**
|
|
3
|
+
* Type guard to check if a value conforms to the CompositeActionStructure
|
|
4
|
+
* interface.
|
|
5
|
+
*
|
|
6
|
+
* @param value - The value to check.
|
|
7
|
+
* @returns True if the value is a valid composite action structure.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isCompositeActionStructure(value: unknown): value is CompositeActionStructure;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
function isCompositeActionStructure(value) {
|
|
2
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
3
|
+
let object = value;
|
|
4
|
+
return "name" in object || "description" in object || "runs" in object;
|
|
5
|
+
}
|
|
6
|
+
export { isCompositeActionStructure };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { WorkflowJob } from '../../../types/workflow-job';
|
|
2
|
+
/**
|
|
3
|
+
* Type guard to check if a value conforms to the WorkflowJob interface.
|
|
4
|
+
*
|
|
5
|
+
* @param value - The value to check.
|
|
6
|
+
* @returns True if the value is a valid workflow job.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isWorkflowJob(value: unknown): value is WorkflowJob;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { WorkflowStep } from '../../../types/workflow-step';
|
|
2
|
+
/**
|
|
3
|
+
* Type guard to check if a value conforms to the WorkflowStep interface.
|
|
4
|
+
*
|
|
5
|
+
* @param value - The value to check.
|
|
6
|
+
* @returns True if the value is a valid workflow step.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isWorkflowStep(value: unknown): value is WorkflowStep;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { WorkflowStructure } from '../../../types/workflow-structure';
|
|
2
|
+
/**
|
|
3
|
+
* Type guard to check if a value conforms to the WorkflowStructure interface.
|
|
4
|
+
*
|
|
5
|
+
* @param value - The value to check.
|
|
6
|
+
* @returns True if the value is a valid workflow structure.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isWorkflowStructure(value: unknown): value is WorkflowStructure;
|
package/dist/package.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { GitHubAction } from './github-action';
|
|
2
|
+
/** Update information for a GitHub Action. */
|
|
3
|
+
export interface ActionUpdate {
|
|
4
|
+
/** Current version string. */
|
|
5
|
+
currentVersion: string | null
|
|
6
|
+
|
|
7
|
+
/** Latest available version. */
|
|
8
|
+
latestVersion: string | null
|
|
9
|
+
|
|
10
|
+
/** SHA hash of the latest version. */
|
|
11
|
+
latestSha: string | null
|
|
12
|
+
|
|
13
|
+
/** The original action from scanning. */
|
|
14
|
+
action: GitHubAction
|
|
15
|
+
|
|
16
|
+
/** Whether this is a major version change. */
|
|
17
|
+
isBreaking: boolean
|
|
18
|
+
|
|
19
|
+
/** Whether an update is available. */
|
|
20
|
+
hasUpdate: boolean
|
|
21
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { CompositeActionStep } from './composite-action-step';
|
|
2
|
+
/** Represents the runs configuration for a composite action. */
|
|
3
|
+
export interface CompositeActionRuns {
|
|
4
|
+
/** Array of steps to execute. */
|
|
5
|
+
steps?: CompositeActionStep[]
|
|
6
|
+
|
|
7
|
+
/** Allow additional properties. */
|
|
8
|
+
[key: string]: unknown
|
|
9
|
+
|
|
10
|
+
/** Must be 'composite' for composite actions. */
|
|
11
|
+
using?: string
|
|
12
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Represents a step in a composite GitHub Action. */
|
|
2
|
+
export interface CompositeActionStep {
|
|
3
|
+
/** Environment variables for this step. */
|
|
4
|
+
env?: Record<string, unknown>
|
|
5
|
+
|
|
6
|
+
/** Working directory for the step. */
|
|
7
|
+
'working-directory'?: string
|
|
8
|
+
|
|
9
|
+
/** Allow additional properties. */
|
|
10
|
+
[key: string]: unknown
|
|
11
|
+
|
|
12
|
+
/** Shell to use for the run command. */
|
|
13
|
+
shell?: string
|
|
14
|
+
|
|
15
|
+
/** Action to use for this step. */
|
|
16
|
+
uses?: string
|
|
17
|
+
|
|
18
|
+
/** Display name for this step. */
|
|
19
|
+
name?: string
|
|
20
|
+
|
|
21
|
+
/** Shell command to run for this step. */
|
|
22
|
+
run?: string
|
|
23
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { CompositeActionRuns } from './composite-action-runs';
|
|
2
|
+
/** Represents the structure of a composite GitHub Action file. */
|
|
3
|
+
export interface CompositeActionStructure {
|
|
4
|
+
/** Output values from the action. */
|
|
5
|
+
outputs?: Record<string, unknown>
|
|
6
|
+
|
|
7
|
+
/** Input parameters for the action. */
|
|
8
|
+
inputs?: Record<string, unknown>
|
|
9
|
+
|
|
10
|
+
/** Runs configuration for composite actions. */
|
|
11
|
+
runs?: CompositeActionRuns
|
|
12
|
+
|
|
13
|
+
/** Allow additional properties. */
|
|
14
|
+
[key: string]: unknown
|
|
15
|
+
|
|
16
|
+
/** Description of what the action does. */
|
|
17
|
+
description?: string
|
|
18
|
+
|
|
19
|
+
/** Display name of the action. */
|
|
20
|
+
name?: string
|
|
21
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Represents a GitHub Action used in workflows or composite actions. */
|
|
2
|
+
export interface GitHubAction {
|
|
3
|
+
/** Type of the GitHub Action. */
|
|
4
|
+
type: 'composite' | 'external' | 'docker' | 'local'
|
|
5
|
+
|
|
6
|
+
/** Version or tag of the action (e.g., 'v1', 'main', commit SHA). */
|
|
7
|
+
version?: string | null
|
|
8
|
+
|
|
9
|
+
/** Line number where the action is used in the file. */
|
|
10
|
+
line?: number
|
|
11
|
+
|
|
12
|
+
/** Path to the file where this action is used. */
|
|
13
|
+
file?: string
|
|
14
|
+
|
|
15
|
+
/** Original `uses` string from workflow, if available. */
|
|
16
|
+
uses?: string
|
|
17
|
+
|
|
18
|
+
/** Full name of the action (e.g., 'actions/checkout'). */
|
|
19
|
+
name: string
|
|
20
|
+
|
|
21
|
+
/** Original `ref` string from workflow, if available. */
|
|
22
|
+
ref?: string
|
|
23
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { GitHubAction } from './github-action';
|
|
2
|
+
/** Result of scanning a repository for GitHub Actions usage. */
|
|
3
|
+
export interface ScanResult {
|
|
4
|
+
/** Map of workflow files to their used GitHub Actions. */
|
|
5
|
+
workflows: Map<string, GitHubAction[]>
|
|
6
|
+
|
|
7
|
+
/** Map of composite action names to their file paths. */
|
|
8
|
+
compositeActions: Map<string, string>
|
|
9
|
+
|
|
10
|
+
/** List of all unique GitHub Actions found in the repository. */
|
|
11
|
+
actions: GitHubAction[]
|
|
12
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { WorkflowStep } from './workflow-step';
|
|
2
|
+
/** Represents a job in a GitHub Actions workflow. */
|
|
3
|
+
export interface WorkflowJob {
|
|
4
|
+
/** Runner environment(s) to execute this job on (e.g., 'ubuntu-latest'). */
|
|
5
|
+
'runs-on'?: string[] | string
|
|
6
|
+
|
|
7
|
+
/** Job IDs that must complete successfully before this job runs. */
|
|
8
|
+
needs?: string[] | string
|
|
9
|
+
|
|
10
|
+
/** Array of steps to execute in this job. */
|
|
11
|
+
steps?: WorkflowStep[]
|
|
12
|
+
|
|
13
|
+
/** Allow additional properties for job configuration. */
|
|
14
|
+
[key: string]: unknown
|
|
15
|
+
|
|
16
|
+
/** Conditional expression to determine if the job should run. */
|
|
17
|
+
if?: string
|
|
18
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Represents a single step in a GitHub Actions workflow job. */
|
|
2
|
+
export interface WorkflowStep {
|
|
3
|
+
/** Input parameters to pass to the action. */
|
|
4
|
+
with?: Record<string, unknown>
|
|
5
|
+
|
|
6
|
+
/** Environment variables to set for this step. */
|
|
7
|
+
env?: Record<string, unknown>
|
|
8
|
+
|
|
9
|
+
/** Allow additional properties for step configuration. */
|
|
10
|
+
[key: string]: unknown
|
|
11
|
+
|
|
12
|
+
/** Action to use for this step (e.g., 'actions/checkout@v4'). */
|
|
13
|
+
uses?: string
|
|
14
|
+
|
|
15
|
+
/** Display name for this step. */
|
|
16
|
+
name?: string
|
|
17
|
+
|
|
18
|
+
/** Shell command to run for this step. */
|
|
19
|
+
run?: string
|
|
20
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { WorkflowJob } from './workflow-job';
|
|
2
|
+
/** Represents the root structure of a GitHub Actions workflow file. */
|
|
3
|
+
export interface WorkflowStructure {
|
|
4
|
+
/** Map of job IDs to job configurations. */
|
|
5
|
+
jobs?: Record<string, WorkflowJob>
|
|
6
|
+
|
|
7
|
+
/** Allow additional properties for workflow configuration. */
|
|
8
|
+
[key: string]: unknown
|
|
9
|
+
|
|
10
|
+
/** Display name for the workflow. */
|
|
11
|
+
name?: string
|
|
12
|
+
|
|
13
|
+
/** Events that trigger the workflow (push, pull_request, etc.). */
|
|
14
|
+
on?: unknown
|
|
15
|
+
}
|
package/license.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright 2025 Azat S. <to@azat.io>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
7
|
+
the Software without restriction, including without limitation the rights to
|
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
10
|
+
subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/package.json
CHANGED
|
@@ -1,4 +1,55 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "actions-up",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Interactive CLI tool to update GitHub Actions to latest versions with SHA pinning",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"github-actions",
|
|
7
|
+
"actions",
|
|
8
|
+
"updater",
|
|
9
|
+
"cli",
|
|
10
|
+
"workflow",
|
|
11
|
+
"ci-cd",
|
|
12
|
+
"security",
|
|
13
|
+
"sha",
|
|
14
|
+
"dependencies",
|
|
15
|
+
"update"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://github.com/azat-io/actions-up",
|
|
18
|
+
"repository": "azat-io/actions-up",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"author": "Azat S. <to@azat.io>",
|
|
21
|
+
"type": "module",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/core/index.d.ts",
|
|
25
|
+
"default": "./dist/core/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./package.json": "./package.json"
|
|
28
|
+
},
|
|
29
|
+
"main": "./dist/core/index.js",
|
|
30
|
+
"types": "./dist/core/index.d.ts",
|
|
31
|
+
"bin": {
|
|
32
|
+
"actions-up": "./bin/actions-up.js"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"./bin",
|
|
36
|
+
"./dist"
|
|
37
|
+
],
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@octokit/graphql": "^9.0.1",
|
|
40
|
+
"cac": "^6.7.14",
|
|
41
|
+
"enquirer": "^2.4.1",
|
|
42
|
+
"nanospinner": "^1.2.2",
|
|
43
|
+
"picocolors": "^1.1.1",
|
|
44
|
+
"semver": "^7.7.2",
|
|
45
|
+
"yaml": "^2.8.1"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": "^18.0.0 || >=20.0.0"
|
|
49
|
+
},
|
|
50
|
+
"pnpm": {
|
|
51
|
+
"overrides": {
|
|
52
|
+
"vite": "npm:rolldown-vite@latest"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
4
55
|
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Actions Up!
|
|
2
|
+
|
|
3
|
+
<img
|
|
4
|
+
src="https://raw.githubusercontent.com/azat-io/actions-up/main/assets/logo.svg"
|
|
5
|
+
alt="Actions Up! logo"
|
|
6
|
+
width="160"
|
|
7
|
+
height="160"
|
|
8
|
+
align="right"
|
|
9
|
+
/>
|
|
10
|
+
|
|
11
|
+
[](https://npmjs.com/package/actions-up)
|
|
12
|
+
[](https://codecov.io/gh/azat-io/actions-up)
|
|
13
|
+
[](https://github.com/azat-io/actions-up/blob/main/license.md)
|
|
14
|
+
|
|
15
|
+
Actions Up scans your workflows and composite actions to discover every referenced GitHub Action, then checks for newer releases.
|
|
16
|
+
|
|
17
|
+
Interactively upgrade and pin actions to exact commit SHAs for secure, reproducible CI and low‑friction maintenance.
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- **Auto-discovery**: Scans all workflows (`.github/workflows/*.yml`) and composite actions (`.github/actions/*/action.yml`)
|
|
22
|
+
- **SHA Pinning**: Updates actions to use commit SHA instead of tags for better security
|
|
23
|
+
- **Batch Updates**: Update multiple actions at once
|
|
24
|
+
- **Interactive Selection**: Choose which actions to update
|
|
25
|
+
- **Breaking Changes Detection**: Warns about major version updates
|
|
26
|
+
- **Fast & Efficient**: Parallel processing with optimized API calls
|
|
27
|
+
|
|
28
|
+
###
|
|
29
|
+
|
|
30
|
+
<br>
|
|
31
|
+
|
|
32
|
+
<picture>
|
|
33
|
+
<source
|
|
34
|
+
srcset="https://raw.githubusercontent.com/azat-io/actions-up/main/assets/example-light.webp"
|
|
35
|
+
media="(prefers-color-scheme: light)"
|
|
36
|
+
/>
|
|
37
|
+
<source
|
|
38
|
+
srcset="https://raw.githubusercontent.com/azat-io/actions-up/main/assets/example-dark.webp"
|
|
39
|
+
media="(prefers-color-scheme: dark)"
|
|
40
|
+
/>
|
|
41
|
+
<img
|
|
42
|
+
src="https://raw.githubusercontent.com/azat-io/actions-up/main/assets/example-light.webp"
|
|
43
|
+
alt="Actions Up interactive example"
|
|
44
|
+
width="820"
|
|
45
|
+
/>
|
|
46
|
+
</picture>
|
|
47
|
+
|
|
48
|
+
## Why
|
|
49
|
+
|
|
50
|
+
### The Problem
|
|
51
|
+
|
|
52
|
+
Keeping GitHub Actions updated is a critical but tedious task:
|
|
53
|
+
|
|
54
|
+
- **Security Risk**: Using outdated actions with known vulnerabilities
|
|
55
|
+
- **Manual Hell**: Checking dozens of actions across multiple workflows by hand
|
|
56
|
+
- **Version Tags Are Mutable**: v1 or v2 tags can change without notice, breaking reproducibility
|
|
57
|
+
- **Time Sink**: Hours spent on maintenance that could be used for actual development
|
|
58
|
+
|
|
59
|
+
### The Solution
|
|
60
|
+
|
|
61
|
+
Actions Up transforms a painful manual process into a delightful experience:
|
|
62
|
+
|
|
63
|
+
| Without Actions Up | With Actions Up |
|
|
64
|
+
| :----------------------------- | :------------------------------- |
|
|
65
|
+
| Check each action manually | Scan all workflows in seconds |
|
|
66
|
+
| Risk using vulnerable versions | SHA pinning for maximum security |
|
|
67
|
+
| 30+ minutes per repository | Under 1 minute total |
|
|
68
|
+
|
|
69
|
+
## GitHub Token Required
|
|
70
|
+
|
|
71
|
+
> **Important**: GitHub API has strict rate limits (60 requests/hour without token vs 5000 with token).
|
|
72
|
+
> A GitHub token is **practically required** for using Actions Up.
|
|
73
|
+
|
|
74
|
+
### Quick Token Setup
|
|
75
|
+
|
|
76
|
+
[Create a GitHub Personal Access Token](https://github.com/settings/tokens/new?scopes=public_repo&description=actions-up).
|
|
77
|
+
|
|
78
|
+
- For public repositories: Select `public_repo` scope
|
|
79
|
+
- For private repositories: Select `repo` scope
|
|
80
|
+
|
|
81
|
+
## Installation
|
|
82
|
+
|
|
83
|
+
Quick use (no installation)
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npx actions-up
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Global installation
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npm install -g actions-up
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Per-project
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npm install --save-dev actions-up
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Usage
|
|
102
|
+
|
|
103
|
+
### Interactive Mode (Default)
|
|
104
|
+
|
|
105
|
+
Run in your repository root with GitHub token:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
GITHUB_TOKEN=ghp_xxxx npx actions-up
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
This will:
|
|
112
|
+
|
|
113
|
+
1. Scan all `.github/workflows/*.yml` and `.github/actions/*/action.yml` files
|
|
114
|
+
2. Check for available updates
|
|
115
|
+
3. Show an interactive list to select updates
|
|
116
|
+
4. Apply selected updates with SHA pinning
|
|
117
|
+
|
|
118
|
+
### Auto-Update Mode
|
|
119
|
+
|
|
120
|
+
Skip all prompts and update everything:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
GITHUB_TOKEN=ghp_xxxx npx actions-up --yes
|
|
124
|
+
# or
|
|
125
|
+
GITHUB_TOKEN=ghp_xxxx npx actions-up -y
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Pro Tips
|
|
129
|
+
|
|
130
|
+
### Shell Aliases
|
|
131
|
+
|
|
132
|
+
Add to your `.zshrc`, `.bashrc` or `.config/fish/config.fish`:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# Basic alias with token from environment
|
|
136
|
+
export GITHUB_TOKEN=ghp_xxxx # Add this once to your shell config
|
|
137
|
+
alias actions-up='GITHUB_TOKEN=$GITHUB_TOKEN npx actions-up'
|
|
138
|
+
|
|
139
|
+
# With token from file
|
|
140
|
+
alias actions-up='GITHUB_TOKEN=$(cat ~/.github-token) npx actions-up'
|
|
141
|
+
|
|
142
|
+
# With 1Password CLI
|
|
143
|
+
alias actions-up='GITHUB_TOKEN=$(op read "op://Personal/GitHub/token") npx actions-up'
|
|
144
|
+
|
|
145
|
+
# With macOS Keychain
|
|
146
|
+
alias actions-up='GITHUB_TOKEN=$(security find-generic-password -w -s "github-token") npx actions-up'
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Example
|
|
150
|
+
|
|
151
|
+
```yaml
|
|
152
|
+
# Before
|
|
153
|
+
- uses: actions/checkout@v3
|
|
154
|
+
- uses: actions/setup-node@v3
|
|
155
|
+
|
|
156
|
+
# After running actions-up
|
|
157
|
+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
|
158
|
+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Security
|
|
162
|
+
|
|
163
|
+
Actions Up promotes security best practices:
|
|
164
|
+
|
|
165
|
+
- **SHA Pinning**: Uses commit SHA instead of mutable tags
|
|
166
|
+
- **Version Comments**: Adds version as comment for readability
|
|
167
|
+
- **No Auto-Updates**: Full control over what gets updated
|
|
168
|
+
|
|
169
|
+
## Contributing
|
|
170
|
+
|
|
171
|
+
See [Contributing Guide](https://github.com/azat-io/actions-up/blob/main/contributing.md).
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
MIT © [Azat S.](https://azat.io)
|