@ronaldphilipsen/local-action 0.0.0-stage → 7.1.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/.checkov.yml +3 -0
- package/.env.example +379 -0
- package/.github/codeql/codeql-config.yml +7 -0
- package/.github/copilot-instructions.md +79 -0
- package/.github/dependabot.yml +32 -0
- package/.github/workflows/codeql-analysis.yml +53 -0
- package/.github/workflows/continuous-delivery.yml +93 -0
- package/.github/workflows/continuous-integration.yml +382 -0
- package/.github/workflows/linter.yml +47 -0
- package/.github/workflows/release.yaml +99 -0
- package/.github/workflows/version-check.yml +44 -0
- package/.grype.yml +151 -0
- package/.markdown-lint.yml +7 -0
- package/.mega-linter.yml +188 -0
- package/.node-version +1 -0
- package/.prettierignore +4 -0
- package/.prettierrc.yml +14 -0
- package/.vscode/launch.json +15 -0
- package/.vscode/mcp.json +9 -0
- package/.vscode/settings.json +15 -0
- package/.vscode/tasks.json +51 -0
- package/.yaml-lint.yml +10 -0
- package/CHANGELOG.md +75 -0
- package/CODEOWNERS +3 -0
- package/CODE_OF_CONDUCT.md +74 -0
- package/CONTRIBUTING.md +140 -0
- package/LICENSE +21 -0
- package/README.md +285 -2
- package/SECURITY.md +42 -0
- package/SUPPORT.md +19 -0
- package/__fixtures__/@actions/core.ts +14 -0
- package/__fixtures__/@actions/exec.ts +9 -0
- package/__fixtures__/@actions/github.ts +3 -0
- package/__fixtures__/@actions/http-client.ts +9 -0
- package/__fixtures__/@actions/io.ts +9 -0
- package/__fixtures__/@octokit/rest.ts +18 -0
- package/__fixtures__/crypto.ts +7 -0
- package/__fixtures__/fs.ts +27 -0
- package/__fixtures__/javascript/no-export/.env.fixture +2 -0
- package/__fixtures__/javascript/no-export/action.yml +16 -0
- package/__fixtures__/javascript/no-export/node_modules/.gitkeep +0 -0
- package/__fixtures__/javascript/no-export/package.json +12 -0
- package/__fixtures__/javascript/no-export/src/index.js +0 -0
- package/__fixtures__/javascript/no-export/src/main.js +0 -0
- package/__fixtures__/javascript/no-import/.env.fixture +2 -0
- package/__fixtures__/javascript/no-import/action.yml +16 -0
- package/__fixtures__/javascript/no-import/node_modules/.gitkeep +0 -0
- package/__fixtures__/javascript/no-import/package.json +12 -0
- package/__fixtures__/javascript/no-import/src/index.js +3 -0
- package/__fixtures__/javascript/no-import/src/main.js +7 -0
- package/__fixtures__/javascript/success/.env.fixture +2 -0
- package/__fixtures__/javascript/success/action.yml +16 -0
- package/__fixtures__/javascript/success/node_modules/.gitkeep +0 -0
- package/__fixtures__/javascript/success/package.json +12 -0
- package/__fixtures__/javascript/success/post/index.js +3 -0
- package/__fixtures__/javascript/success/post/main.js +13 -0
- package/__fixtures__/javascript/success/pre/index.js +3 -0
- package/__fixtures__/javascript/success/pre/main.js +13 -0
- package/__fixtures__/javascript/success/src/index.js +3 -0
- package/__fixtures__/javascript/success/src/main.js +13 -0
- package/__fixtures__/javascript-esm/no-import/.env.fixture +2 -0
- package/__fixtures__/javascript-esm/no-import/action.yml +16 -0
- package/__fixtures__/javascript-esm/no-import/node_modules/.gitkeep +0 -0
- package/__fixtures__/javascript-esm/no-import/package.json +13 -0
- package/__fixtures__/javascript-esm/no-import/src/index.js +3 -0
- package/__fixtures__/javascript-esm/no-import/src/main.js +7 -0
- package/__fixtures__/javascript-esm/success/.env.fixture +2 -0
- package/__fixtures__/javascript-esm/success/action.yml +16 -0
- package/__fixtures__/javascript-esm/success/node_modules/.gitkeep +0 -0
- package/__fixtures__/javascript-esm/success/package.json +13 -0
- package/__fixtures__/javascript-esm/success/post/index.js +3 -0
- package/__fixtures__/javascript-esm/success/post/main.js +13 -0
- package/__fixtures__/javascript-esm/success/pre/index.js +3 -0
- package/__fixtures__/javascript-esm/success/pre/main.js +13 -0
- package/__fixtures__/javascript-esm/success/src/index.js +3 -0
- package/__fixtures__/javascript-esm/success/src/main.js +13 -0
- package/__fixtures__/path.ts +7 -0
- package/__fixtures__/payloads/issue_opened.json +33 -0
- package/__fixtures__/stream/promises.ts +3 -0
- package/__fixtures__/tsconfig-paths.ts +4 -0
- package/__fixtures__/typescript/no-import/.env.fixture +2 -0
- package/__fixtures__/typescript/no-import/action.yml +16 -0
- package/__fixtures__/typescript/no-import/node_modules/.gitkeep +0 -0
- package/__fixtures__/typescript/no-import/package.json +15 -0
- package/__fixtures__/typescript/no-import/src/index.ts +3 -0
- package/__fixtures__/typescript/no-import/src/main.ts +3 -0
- package/__fixtures__/typescript/no-import/tsconfig.json +20 -0
- package/__fixtures__/typescript/success/.env.fixture +2 -0
- package/__fixtures__/typescript/success/action.yml +16 -0
- package/__fixtures__/typescript/success/node_modules/.gitkeep +0 -0
- package/__fixtures__/typescript/success/package.json +15 -0
- package/__fixtures__/typescript/success/post/index.ts +3 -0
- package/__fixtures__/typescript/success/post/main.ts +9 -0
- package/__fixtures__/typescript/success/pre/index.ts +3 -0
- package/__fixtures__/typescript/success/pre/main.ts +9 -0
- package/__fixtures__/typescript/success/src/index.ts +3 -0
- package/__fixtures__/typescript/success/src/main.ts +9 -0
- package/__fixtures__/typescript/success/tsconfig.json +20 -0
- package/__fixtures__/typescript/success-yaml/.env.fixture +2 -0
- package/__fixtures__/typescript/success-yaml/action.yaml +16 -0
- package/__fixtures__/typescript/success-yaml/node_modules/.gitkeep +0 -0
- package/__fixtures__/typescript/success-yaml/package.json +15 -0
- package/__fixtures__/typescript/success-yaml/post/index.ts +3 -0
- package/__fixtures__/typescript/success-yaml/post/main.ts +9 -0
- package/__fixtures__/typescript/success-yaml/pre/index.ts +3 -0
- package/__fixtures__/typescript/success-yaml/pre/main.ts +9 -0
- package/__fixtures__/typescript/success-yaml/src/index.ts +3 -0
- package/__fixtures__/typescript/success-yaml/src/main.ts +9 -0
- package/__fixtures__/typescript/success-yaml/tsconfig.json +20 -0
- package/__fixtures__/typescript-esm/no-export/.env.fixture +2 -0
- package/__fixtures__/typescript-esm/no-export/action.yml +17 -0
- package/__fixtures__/typescript-esm/no-export/node_modules/.gitkeep +0 -0
- package/__fixtures__/typescript-esm/no-export/package.json +17 -0
- package/__fixtures__/typescript-esm/no-export/src/index.ts +0 -0
- package/__fixtures__/typescript-esm/no-export/src/main.ts +0 -0
- package/__fixtures__/typescript-esm/no-export/tsconfig.json +25 -0
- package/__fixtures__/typescript-esm/no-import/.env.fixture +2 -0
- package/__fixtures__/typescript-esm/no-import/action.yml +17 -0
- package/__fixtures__/typescript-esm/no-import/node_modules/.gitkeep +0 -0
- package/__fixtures__/typescript-esm/no-import/package.json +17 -0
- package/__fixtures__/typescript-esm/no-import/src/index.ts +3 -0
- package/__fixtures__/typescript-esm/no-import/src/main.ts +3 -0
- package/__fixtures__/typescript-esm/no-import/tsconfig.json +25 -0
- package/__fixtures__/typescript-esm/success/.env.fixture +2 -0
- package/__fixtures__/typescript-esm/success/action.yml +17 -0
- package/__fixtures__/typescript-esm/success/node_modules/.gitkeep +0 -0
- package/__fixtures__/typescript-esm/success/package.json +17 -0
- package/__fixtures__/typescript-esm/success/post/index.ts +3 -0
- package/__fixtures__/typescript-esm/success/post/main.ts +9 -0
- package/__fixtures__/typescript-esm/success/pre/index.ts +3 -0
- package/__fixtures__/typescript-esm/success/pre/main.ts +9 -0
- package/__fixtures__/typescript-esm/success/src/index.ts +3 -0
- package/__fixtures__/typescript-esm/success/src/main.ts +9 -0
- package/__fixtures__/typescript-esm/success/tsconfig.json +25 -0
- package/__fixtures__/typescript-esm/tsconfig-comments/.env.fixture +2 -0
- package/__fixtures__/typescript-esm/tsconfig-comments/action.yml +17 -0
- package/__fixtures__/typescript-esm/tsconfig-comments/node_modules/.gitkeep +0 -0
- package/__fixtures__/typescript-esm/tsconfig-comments/package.json +17 -0
- package/__fixtures__/typescript-esm/tsconfig-comments/src/index.ts +3 -0
- package/__fixtures__/typescript-esm/tsconfig-comments/src/main.ts +9 -0
- package/__fixtures__/typescript-esm/tsconfig-comments/tsconfig.json +21 -0
- package/__tests__/command.test.ts +224 -0
- package/__tests__/stubs/artifact/internal/client.test.ts +340 -0
- package/__tests__/stubs/artifact/internal/delete/delete-artifact.test.ts +152 -0
- package/__tests__/stubs/artifact/internal/download/download-artifact.test.ts +119 -0
- package/__tests__/stubs/artifact/internal/find/get-artifact.test.ts +237 -0
- package/__tests__/stubs/artifact/internal/find/list-artifacts.test.ts +218 -0
- package/__tests__/stubs/artifact/internal/find/retry-options.test.ts +54 -0
- package/__tests__/stubs/artifact/internal/shared/config.test.ts +91 -0
- package/__tests__/stubs/artifact/internal/shared/errors.test.ts +38 -0
- package/__tests__/stubs/artifact/internal/shared/user-agent.test.ts +22 -0
- package/__tests__/stubs/artifact/internal/upload/path-and-artifact-name-validation.test.ts +53 -0
- package/__tests__/stubs/artifact/internal/upload/upload-artifact.test.ts +126 -0
- package/__tests__/stubs/artifact/internal/upload/upload-zip-specification.test.ts +49 -0
- package/__tests__/stubs/cache/cache.test.ts +357 -0
- package/__tests__/stubs/cache/errors.test.ts +55 -0
- package/__tests__/stubs/cache/internal/cacheUtils.test.ts +109 -0
- package/__tests__/stubs/cache/internal/constants.test.ts +81 -0
- package/__tests__/stubs/cache/internal/tar.test.ts +129 -0
- package/__tests__/stubs/core/core.test.ts +760 -0
- package/__tests__/stubs/core/path-utils.test.ts +45 -0
- package/__tests__/stubs/core/platform.test.ts +89 -0
- package/__tests__/stubs/core/summary.test.ts +455 -0
- package/__tests__/stubs/env.test.ts +72 -0
- package/__tests__/stubs/github/context.test.ts +73 -0
- package/__tests__/stubs/github/github.test.ts +15 -0
- package/__tests__/stubs/github/internal/utils.test.ts +98 -0
- package/__tests__/stubs/github/utils.test.ts +16 -0
- package/__tests__/utils/config.test.ts +78 -0
- package/__tests__/utils/output.test.ts +32 -0
- package/__tests__/utils/package.test.ts +33 -0
- package/badges/coverage.svg +1 -0
- package/bin/local-action.js +121 -0
- package/docs/create-a-job-summary.md +32 -0
- package/docs/debugging-in-vscode.md +27 -0
- package/docs/img/job-summary.png +0 -0
- package/docs/supported-functionality.md +126 -0
- package/eslint.config.mjs +74 -0
- package/jest.config.ts +41 -0
- package/package.json +93 -4
- package/src/command.ts +171 -0
- package/src/commands/run.ts +616 -0
- package/src/index.ts +21 -0
- package/src/stubs/artifact/artifact.ts +17 -0
- package/src/stubs/artifact/internal/client.ts +400 -0
- package/src/stubs/artifact/internal/delete/delete-artifact.ts +126 -0
- package/src/stubs/artifact/internal/download/download-artifact.ts +309 -0
- package/src/stubs/artifact/internal/find/get-artifact.ts +135 -0
- package/src/stubs/artifact/internal/find/list-artifacts.ts +188 -0
- package/src/stubs/artifact/internal/find/retry-options.ts +57 -0
- package/src/stubs/artifact/internal/shared/config.ts +64 -0
- package/src/stubs/artifact/internal/shared/errors.ts +53 -0
- package/src/stubs/artifact/internal/shared/interfaces.ts +195 -0
- package/src/stubs/artifact/internal/shared/user-agent.ts +13 -0
- package/src/stubs/artifact/internal/upload/path-and-artifact-name-validation.ts +92 -0
- package/src/stubs/artifact/internal/upload/upload-artifact.ts +127 -0
- package/src/stubs/artifact/internal/upload/upload-zip-specification.ts +108 -0
- package/src/stubs/artifact/internal/upload/zip.ts +122 -0
- package/src/stubs/cache/cache.ts +334 -0
- package/src/stubs/cache/errors.ts +31 -0
- package/src/stubs/cache/interfaces.ts +4 -0
- package/src/stubs/cache/internal/cacheUtils.ts +207 -0
- package/src/stubs/cache/internal/constants.ts +45 -0
- package/src/stubs/cache/internal/tar.ts +374 -0
- package/src/stubs/cache/options.ts +91 -0
- package/src/stubs/core/core.ts +743 -0
- package/src/stubs/core/path-utils.ts +39 -0
- package/src/stubs/core/platform.ts +132 -0
- package/src/stubs/core/summary.ts +399 -0
- package/src/stubs/env.ts +40 -0
- package/src/stubs/github/context.ts +94 -0
- package/src/stubs/github/github.ts +29 -0
- package/src/stubs/github/interfaces.ts +52 -0
- package/src/stubs/github/internal/utils.ts +83 -0
- package/src/stubs/github/utils.ts +52 -0
- package/src/types/quibble.d.ts +5 -0
- package/src/types.ts +116 -0
- package/src/utils/config.ts +47 -0
- package/src/utils/output.ts +20 -0
- package/src/utils/package.ts +38 -0
- package/tsconfig.base.json +29 -0
- package/tsconfig.eslint.json +22 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Last Reviewed Commit: 02afeb157764304bb3bfe1a6cfd37258ec3fcf7c
|
|
3
|
+
* Last Reviewed Date: 2026-01-16
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
*
|
|
7
|
+
* - Removed getConcurrency
|
|
8
|
+
* - Removed getUploadChunkTimeout
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Used for controlling the highWaterMark value of the zip that is being
|
|
13
|
+
* streamed. The same value is used as the chunk size that is use during upload
|
|
14
|
+
* to blob storage
|
|
15
|
+
*/
|
|
16
|
+
export function getUploadChunkSize(): number {
|
|
17
|
+
return 8 * 1024 * 1024 // 8 MB Chunks
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Checks if the current environment is a GitHub Enterprise Server instance
|
|
22
|
+
*
|
|
23
|
+
* @returns True if running on GHES
|
|
24
|
+
*/
|
|
25
|
+
export function isGhes(): boolean {
|
|
26
|
+
const ghUrl = new URL(process.env.GITHUB_SERVER_URL || 'https://github.com')
|
|
27
|
+
|
|
28
|
+
const hostname = ghUrl.hostname.trimEnd().toUpperCase()
|
|
29
|
+
const isGitHubHost = hostname === 'GITHUB.COM'
|
|
30
|
+
const isGheHost = hostname.endsWith('.GHE.COM')
|
|
31
|
+
const isLocalHost = hostname.endsWith('.LOCALHOST')
|
|
32
|
+
|
|
33
|
+
return !isGitHubHost && !isGheHost && !isLocalHost
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Gets the GitHub workspace directory
|
|
38
|
+
*
|
|
39
|
+
* @remarks
|
|
40
|
+
*
|
|
41
|
+
* - Modified to use process.cwd() as a fallback
|
|
42
|
+
*
|
|
43
|
+
* @returns GitHub workspace directory
|
|
44
|
+
*/
|
|
45
|
+
export function getGitHubWorkspaceDir(): string {
|
|
46
|
+
return process.env.GITHUB_WORKSPACE || process.cwd()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* This value can be changed with ACTIONS_ARTIFACT_MAX_ARTIFACT_COUNT variable.
|
|
51
|
+
* Defaults to 1000 as a safeguard for rate limiting.
|
|
52
|
+
*/
|
|
53
|
+
export function getMaxArtifactListCount(): number {
|
|
54
|
+
const maxCountVar = process.env.ACTIONS_ARTIFACT_MAX_ARTIFACT_COUNT || '1000'
|
|
55
|
+
|
|
56
|
+
const maxCount = parseInt(maxCountVar)
|
|
57
|
+
if (isNaN(maxCount) || maxCount < 1) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
'Invalid value set for ACTIONS_ARTIFACT_MAX_ARTIFACT_COUNT env variable'
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return maxCount
|
|
64
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Last Reviewed Commit: 16b786a545a0b3a90e4dc4330af7225cf06f7e93
|
|
3
|
+
* Last Reviewed Date: 2026-01-16
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Files Not Found Error
|
|
8
|
+
*/
|
|
9
|
+
export class FilesNotFoundError extends Error {
|
|
10
|
+
files: string[]
|
|
11
|
+
|
|
12
|
+
constructor(files: string[] = []) {
|
|
13
|
+
let message = 'No files were found to upload'
|
|
14
|
+
|
|
15
|
+
if (files.length > 0) message += `: ${files.join(', ')}`
|
|
16
|
+
|
|
17
|
+
super(message)
|
|
18
|
+
this.files = files
|
|
19
|
+
this.name = 'FilesNotFoundError'
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Invalid Response Error
|
|
25
|
+
*/
|
|
26
|
+
export class InvalidResponseError extends Error {
|
|
27
|
+
constructor(message: string) {
|
|
28
|
+
super(message)
|
|
29
|
+
this.name = 'InvalidResponseError'
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Artifact Not Found Error
|
|
35
|
+
*/
|
|
36
|
+
export class ArtifactNotFoundError extends Error {
|
|
37
|
+
constructor(message = 'Artifact not found') {
|
|
38
|
+
super(message)
|
|
39
|
+
this.name = 'ArtifactNotFoundError'
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* GHES Not Supported Error
|
|
45
|
+
*/
|
|
46
|
+
export class GHESNotSupportedError extends Error {
|
|
47
|
+
constructor(
|
|
48
|
+
message = '@actions/artifact v2.0.0+, upload-artifact@v4+ and download-artifact@v4+ are not currently supported on GHES.'
|
|
49
|
+
) {
|
|
50
|
+
super(message)
|
|
51
|
+
this.name = 'GHESNotSupportedError'
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Last Reviewed Commit: d7ddca4309f83d679cb59391635b94e2dfbc40fb
|
|
3
|
+
* Last Reviewed Date: 2026-01-16
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Response from the server when an artifact is uploaded
|
|
8
|
+
*/
|
|
9
|
+
export interface UploadArtifactResponse {
|
|
10
|
+
/**
|
|
11
|
+
* Total size of the artifact in bytes. Not provided if no artifact was
|
|
12
|
+
* uploaded
|
|
13
|
+
*/
|
|
14
|
+
size?: number
|
|
15
|
+
/**
|
|
16
|
+
* The id of the artifact that was created. Not provided if no artifact was
|
|
17
|
+
* uploaded. This ID can be used as input to other APIs to download, delete or
|
|
18
|
+
* get more information about an artifact:
|
|
19
|
+
* https://docs.github.com/en/rest/actions/artifacts
|
|
20
|
+
*/
|
|
21
|
+
id?: number
|
|
22
|
+
/**
|
|
23
|
+
* The SHA256 digest of the artifact that was created. Not provided if no
|
|
24
|
+
* artifact was uploaded
|
|
25
|
+
*/
|
|
26
|
+
digest?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Options for uploading an artifact
|
|
31
|
+
*/
|
|
32
|
+
export interface UploadArtifactOptions {
|
|
33
|
+
/**
|
|
34
|
+
* Duration after which artifact will expire in days.
|
|
35
|
+
*
|
|
36
|
+
* By default artifact expires after 90 days:
|
|
37
|
+
* https://docs.github.com/en/actions/configuring-and-managing-workflows/persisting-workflow-data-using-artifacts#downloading-and-deleting-artifacts-after-a-workflow-run-is-complete
|
|
38
|
+
*
|
|
39
|
+
* Use this option to override the default expiry.
|
|
40
|
+
*
|
|
41
|
+
* Min value: 1
|
|
42
|
+
* Max value: 90 unless changed by repository setting
|
|
43
|
+
*
|
|
44
|
+
* If this is set to a greater value than the retention settings allowed, the
|
|
45
|
+
* retention on artifacts will be reduced to match the max value allowed on
|
|
46
|
+
* server, and the upload process will continue. An input of 0 assumes default
|
|
47
|
+
* retention setting.
|
|
48
|
+
*/
|
|
49
|
+
retentionDays?: number
|
|
50
|
+
/**
|
|
51
|
+
* The level of compression for Zlib to be applied to the artifact archive.
|
|
52
|
+
* The value can range from 0 to 9:
|
|
53
|
+
* - 0: No compression
|
|
54
|
+
* - 1: Best speed
|
|
55
|
+
* - 6: Default compression (same as GNU Gzip)
|
|
56
|
+
* - 9: Best compression
|
|
57
|
+
* Higher levels will result in better compression, but will take longer to
|
|
58
|
+
* complete. For large files that are not easily compressed, a value of 0 is
|
|
59
|
+
* recommended for significantly faster uploads.
|
|
60
|
+
*/
|
|
61
|
+
compressionLevel?: number
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Response from the server when getting an artifact
|
|
66
|
+
*/
|
|
67
|
+
export interface GetArtifactResponse {
|
|
68
|
+
/**
|
|
69
|
+
* Metadata about the artifact that was found
|
|
70
|
+
*/
|
|
71
|
+
artifact: Artifact
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Options for listing artifacts
|
|
76
|
+
*/
|
|
77
|
+
export interface ListArtifactsOptions {
|
|
78
|
+
/**
|
|
79
|
+
* Filter the workflow run's artifacts to the latest by name. In the case of
|
|
80
|
+
* reruns, this can be useful to avoid duplicates.
|
|
81
|
+
*/
|
|
82
|
+
latest?: boolean
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Response from the server when listing artifacts
|
|
87
|
+
*/
|
|
88
|
+
export interface ListArtifactsResponse {
|
|
89
|
+
/**
|
|
90
|
+
* A list of artifacts that were found
|
|
91
|
+
*/
|
|
92
|
+
artifacts: Artifact[]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Response from the server when downloading an artifact
|
|
97
|
+
*/
|
|
98
|
+
export interface DownloadArtifactResponse {
|
|
99
|
+
/**
|
|
100
|
+
* The path where the artifact was downloaded to
|
|
101
|
+
*/
|
|
102
|
+
downloadPath?: string
|
|
103
|
+
/**
|
|
104
|
+
* Returns true if the digest of the downloaded artifact does not match the
|
|
105
|
+
* expected hash
|
|
106
|
+
*/
|
|
107
|
+
digestMismatch?: boolean
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Options for downloading an artifact
|
|
112
|
+
*/
|
|
113
|
+
export interface DownloadArtifactOptions {
|
|
114
|
+
/**
|
|
115
|
+
* Denotes where the artifact will be downloaded to. If not specified then the
|
|
116
|
+
* artifact is download to GITHUB_WORKSPACE
|
|
117
|
+
*/
|
|
118
|
+
path?: string
|
|
119
|
+
/**
|
|
120
|
+
* The hash that was computed for the artifact during upload. If provided, the
|
|
121
|
+
* outcome of the download will provide a digestMismatch property indicating
|
|
122
|
+
* whether the hash of the downloaded artifact matches the expected hash.
|
|
123
|
+
*/
|
|
124
|
+
expectedHash?: string
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface StreamExtractResponse {
|
|
128
|
+
/**
|
|
129
|
+
* The SHA256 hash of the downloaded file
|
|
130
|
+
*/
|
|
131
|
+
sha256Digest?: string
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* An Actions Artifact
|
|
136
|
+
*/
|
|
137
|
+
export interface Artifact {
|
|
138
|
+
/**
|
|
139
|
+
* The name of the artifact
|
|
140
|
+
*/
|
|
141
|
+
name: string
|
|
142
|
+
/**
|
|
143
|
+
* The ID of the artifact
|
|
144
|
+
*/
|
|
145
|
+
id: number
|
|
146
|
+
/**
|
|
147
|
+
* The size of the artifact in bytes
|
|
148
|
+
*/
|
|
149
|
+
size: number
|
|
150
|
+
/**
|
|
151
|
+
* The time when the artifact was created
|
|
152
|
+
*/
|
|
153
|
+
createdAt?: Date
|
|
154
|
+
/**
|
|
155
|
+
* The digest of the artifact, computed at time of upload.
|
|
156
|
+
*/
|
|
157
|
+
digest?: string
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* FindOptions are for fetching Artifact(s) out of the scope of the current run.
|
|
162
|
+
*/
|
|
163
|
+
export interface FindOptions {
|
|
164
|
+
/**
|
|
165
|
+
* The criteria for finding Artifact(s) out of the scope of the current run.
|
|
166
|
+
*/
|
|
167
|
+
findBy?: {
|
|
168
|
+
/**
|
|
169
|
+
* Token with actions:read permissions
|
|
170
|
+
*/
|
|
171
|
+
token: string
|
|
172
|
+
/**
|
|
173
|
+
* WorkflowRun of the artifact(s) to lookup
|
|
174
|
+
*/
|
|
175
|
+
workflowRunId: number
|
|
176
|
+
/**
|
|
177
|
+
* Repository owner (eg. 'actions')
|
|
178
|
+
*/
|
|
179
|
+
repositoryOwner: string
|
|
180
|
+
/**
|
|
181
|
+
* Repository owner (eg. 'toolkit')
|
|
182
|
+
*/
|
|
183
|
+
repositoryName: string
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Response from the server when deleting an artifact
|
|
189
|
+
*/
|
|
190
|
+
export interface DeleteArtifactResponse {
|
|
191
|
+
/**
|
|
192
|
+
* The id of the artifact that was deleted
|
|
193
|
+
*/
|
|
194
|
+
id: number
|
|
195
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Last Reviewed Commit: 4b6a4d80e13a01ecbf41e39098883d69cb54c0d3
|
|
3
|
+
* Last Reviewed Date: 2026-01-16
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates a user agent string for the GitHub client
|
|
8
|
+
*
|
|
9
|
+
* @returns User Agent
|
|
10
|
+
*/
|
|
11
|
+
export function getUserAgentString(): string {
|
|
12
|
+
return `@ronaldphilipsen/local-action-${process.env.npm_package_version}`
|
|
13
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Last Reviewed Commit: 7da3ac6edad34a891be2c261a6eb4d74a9843fbc
|
|
3
|
+
* Last Reviewed Date: 2026-01-16
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { info } from '../../../core/core.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Invalid characters that cannot be in the artifact name or an uploaded file.
|
|
10
|
+
* Will be rejected from the server if attempted to be sent over. These
|
|
11
|
+
* characters are not allowed due to limitations with certain file systems such
|
|
12
|
+
* as NTFS. To maintain platform-agnostic behavior, all characters that are not
|
|
13
|
+
* supported by an individual filesystem/platform will not be supported on all
|
|
14
|
+
* fileSystems/platforms
|
|
15
|
+
*
|
|
16
|
+
* FilePaths can include characters such as \ and / which are not permitted in
|
|
17
|
+
* the artifact name alone
|
|
18
|
+
*/
|
|
19
|
+
/* istanbul ignore next */
|
|
20
|
+
const invalidArtifactFilePathCharacters = new Map<string, string>([
|
|
21
|
+
['"', ' Double quote "'],
|
|
22
|
+
[':', ' Colon :'],
|
|
23
|
+
['<', ' Less than <'],
|
|
24
|
+
['>', ' Greater than >'],
|
|
25
|
+
['|', ' Vertical bar |'],
|
|
26
|
+
['*', ' Asterisk *'],
|
|
27
|
+
['?', ' Question mark ?'],
|
|
28
|
+
['\r', ' Carriage return \\r'],
|
|
29
|
+
['\n', ' Line feed \\n']
|
|
30
|
+
])
|
|
31
|
+
|
|
32
|
+
/* istanbul ignore next */
|
|
33
|
+
const invalidArtifactNameCharacters = new Map<string, string>([
|
|
34
|
+
...invalidArtifactFilePathCharacters,
|
|
35
|
+
['\\', ' Backslash \\'],
|
|
36
|
+
['/', ' Forward slash /']
|
|
37
|
+
])
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Validates the name of the artifact
|
|
41
|
+
*
|
|
42
|
+
* @param name Artifact Name
|
|
43
|
+
*/
|
|
44
|
+
export function validateArtifactName(name: string): void {
|
|
45
|
+
if (!name)
|
|
46
|
+
throw new Error(`Provided artifact name input during validation is empty`)
|
|
47
|
+
|
|
48
|
+
for (const [
|
|
49
|
+
invalidCharacterKey,
|
|
50
|
+
errorMessageForCharacter
|
|
51
|
+
] of invalidArtifactNameCharacters) {
|
|
52
|
+
if (name.includes(invalidCharacterKey))
|
|
53
|
+
throw new Error(
|
|
54
|
+
`The artifact name is not valid: ${name}. Contains the following character: ${errorMessageForCharacter}
|
|
55
|
+
|
|
56
|
+
Invalid characters include: ${Array.from(
|
|
57
|
+
invalidArtifactNameCharacters.values()
|
|
58
|
+
).toString()}
|
|
59
|
+
|
|
60
|
+
These characters are not allowed in the artifact name due to limitations with certain file systems such as NTFS. To maintain file system agnostic behavior, these characters are intentionally not allowed to prevent potential problems with downloads on different file systems.`
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
info(`Artifact name is valid!`)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Validates file paths
|
|
69
|
+
*
|
|
70
|
+
* @param path File Path
|
|
71
|
+
*/
|
|
72
|
+
export function validateFilePath(path: string): void {
|
|
73
|
+
if (!path)
|
|
74
|
+
throw new Error(`Provided file path input during validation is empty`)
|
|
75
|
+
|
|
76
|
+
for (const [
|
|
77
|
+
invalidCharacterKey,
|
|
78
|
+
errorMessageForCharacter
|
|
79
|
+
] of invalidArtifactFilePathCharacters) {
|
|
80
|
+
if (path.includes(invalidCharacterKey))
|
|
81
|
+
throw new Error(
|
|
82
|
+
`The path for one of the files in artifact is not valid: ${path}. Contains the following character: ${errorMessageForCharacter}
|
|
83
|
+
|
|
84
|
+
Invalid characters include: ${Array.from(
|
|
85
|
+
invalidArtifactFilePathCharacters.values()
|
|
86
|
+
).toString()}
|
|
87
|
+
|
|
88
|
+
The following characters are not allowed in files that are uploaded due to limitations with certain file systems such as NTFS. To maintain file system agnostic behavior, these characters are intentionally not allowed to prevent potential problems with downloads on different file systems.
|
|
89
|
+
`
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Last Reviewed Commit: 1e0c16f0dc67246ccb8005965c289aba4114bd56
|
|
3
|
+
* Last Reviewed Date: 2026-01-16
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import crypto from 'crypto'
|
|
7
|
+
import fs from 'fs'
|
|
8
|
+
import path from 'path'
|
|
9
|
+
import { finished } from 'stream/promises'
|
|
10
|
+
import { EnvMeta } from '../../../../stubs/env.js'
|
|
11
|
+
import * as core from '../../../core/core.js'
|
|
12
|
+
import { FilesNotFoundError, InvalidResponseError } from '../shared/errors.js'
|
|
13
|
+
import type {
|
|
14
|
+
Artifact,
|
|
15
|
+
UploadArtifactOptions,
|
|
16
|
+
UploadArtifactResponse
|
|
17
|
+
} from '../shared/interfaces.js'
|
|
18
|
+
import { validateArtifactName } from './path-and-artifact-name-validation.js'
|
|
19
|
+
import {
|
|
20
|
+
type UploadZipSpecification,
|
|
21
|
+
getUploadZipSpecification,
|
|
22
|
+
validateRootDirectory
|
|
23
|
+
} from './upload-zip-specification.js'
|
|
24
|
+
import { createZipUploadStream } from './zip.js'
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Uploads an artifact.
|
|
28
|
+
*
|
|
29
|
+
* @remarks
|
|
30
|
+
*
|
|
31
|
+
* - Does not upload any artifacts. All artifacts are compressed and saved to
|
|
32
|
+
* the local filesystem.
|
|
33
|
+
*
|
|
34
|
+
* @param name Name
|
|
35
|
+
* @param files Files
|
|
36
|
+
* @param rootDirectory Root Directory
|
|
37
|
+
* @param options Upload Artifact Options
|
|
38
|
+
* @returns Upload Artifact Response
|
|
39
|
+
*/
|
|
40
|
+
export async function uploadArtifact(
|
|
41
|
+
name: string,
|
|
42
|
+
files: string[],
|
|
43
|
+
rootDirectory: string,
|
|
44
|
+
options?: UploadArtifactOptions | undefined
|
|
45
|
+
): Promise<UploadArtifactResponse> {
|
|
46
|
+
validateArtifactName(name)
|
|
47
|
+
validateRootDirectory(rootDirectory)
|
|
48
|
+
|
|
49
|
+
const zipSpecification: UploadZipSpecification[] = getUploadZipSpecification(
|
|
50
|
+
files,
|
|
51
|
+
rootDirectory
|
|
52
|
+
)
|
|
53
|
+
/* istanbul ignore next */
|
|
54
|
+
if (zipSpecification.length === 0)
|
|
55
|
+
throw new FilesNotFoundError(
|
|
56
|
+
zipSpecification.flatMap(s => (s.sourcePath ? [s.sourcePath] : []))
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
// Multiple artifacts cannot have the same name.
|
|
60
|
+
if (EnvMeta.artifacts.some(a => a.name === name))
|
|
61
|
+
throw new Error(`An artifact with the name ${name} already exists`)
|
|
62
|
+
|
|
63
|
+
// Only 10 artifacts can be created in a single job.
|
|
64
|
+
if (EnvMeta.artifacts.length >= 10)
|
|
65
|
+
throw new Error('Maximum number of artifacts (10) created')
|
|
66
|
+
|
|
67
|
+
// Create the artifact metadata
|
|
68
|
+
const artifact: Artifact = {
|
|
69
|
+
name,
|
|
70
|
+
id: EnvMeta.artifacts.length + 1,
|
|
71
|
+
size: 0,
|
|
72
|
+
createdAt: new Date()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const response: UploadArtifactResponse = {
|
|
76
|
+
size: 0,
|
|
77
|
+
id: artifact.id,
|
|
78
|
+
digest: ''
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const zipUploadStream = await createZipUploadStream(
|
|
82
|
+
zipSpecification,
|
|
83
|
+
options?.compressionLevel
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
const writeStream = fs.createWriteStream(
|
|
87
|
+
path.join(process.env.LOCAL_ACTION_ARTIFACT_PATH!, `${artifact.name}.zip`)
|
|
88
|
+
)
|
|
89
|
+
const hashStream = crypto.createHash('sha256')
|
|
90
|
+
|
|
91
|
+
/* istanbul ignore next */
|
|
92
|
+
writeStream
|
|
93
|
+
.on('error', error => {
|
|
94
|
+
throw error
|
|
95
|
+
})
|
|
96
|
+
.on('finish', () => {
|
|
97
|
+
try {
|
|
98
|
+
core.info(`Finalizing artifact upload`)
|
|
99
|
+
EnvMeta.artifacts.push(artifact)
|
|
100
|
+
core.info(
|
|
101
|
+
`Artifact ${artifact.name}.zip successfully finalized. Artifact ID ${artifact.id}`
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
response.size = artifact.size
|
|
105
|
+
} catch (error: any) {
|
|
106
|
+
core.debug(`Artifact creation failed: ${error}`)
|
|
107
|
+
throw new InvalidResponseError(
|
|
108
|
+
'CreateArtifact: response from backend was not ok'
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
/* istanbul ignore next */
|
|
114
|
+
zipUploadStream
|
|
115
|
+
.on('data', chunk => {
|
|
116
|
+
artifact.size += chunk.length
|
|
117
|
+
})
|
|
118
|
+
.pipe(writeStream)
|
|
119
|
+
zipUploadStream.pipe(hashStream).setEncoding('hex')
|
|
120
|
+
|
|
121
|
+
await finished(writeStream)
|
|
122
|
+
|
|
123
|
+
hashStream.end()
|
|
124
|
+
response.digest = hashStream.read()
|
|
125
|
+
|
|
126
|
+
return response
|
|
127
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Last Reviewed Commit: 7f19a7886a8b3b16c971a89cb787b850cb6a7980
|
|
3
|
+
* Last Reviewed Date: 2026-01-16
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from 'fs'
|
|
7
|
+
import { normalize, resolve } from 'path'
|
|
8
|
+
import * as core from '../../../core/core.js'
|
|
9
|
+
import { validateFilePath } from './path-and-artifact-name-validation.js'
|
|
10
|
+
|
|
11
|
+
export interface UploadZipSpecification {
|
|
12
|
+
/**
|
|
13
|
+
* An absolute source path that points to a file that will be added to a zip.
|
|
14
|
+
* Null if creating a new directory
|
|
15
|
+
*/
|
|
16
|
+
sourcePath: string | null
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The destination path in a zip for a file
|
|
20
|
+
*/
|
|
21
|
+
destinationPath: string
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Information about the file
|
|
25
|
+
*
|
|
26
|
+
* https://nodejs.org/api/fs.html#class-fsstats
|
|
27
|
+
*/
|
|
28
|
+
stats: fs.Stats
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Checks if a root directory exists and is valid.
|
|
33
|
+
*
|
|
34
|
+
* @param rootDirectory Root Directory
|
|
35
|
+
*/
|
|
36
|
+
export function validateRootDirectory(rootDirectory: string): void {
|
|
37
|
+
if (!fs.existsSync(rootDirectory))
|
|
38
|
+
throw new Error(
|
|
39
|
+
`The provided rootDirectory ${rootDirectory} does not exist`
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
if (!fs.statSync(rootDirectory).isDirectory())
|
|
43
|
+
throw new Error(
|
|
44
|
+
`The provided rootDirectory ${rootDirectory} is not a valid directory`
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
core.info(`Root directory input is valid!`)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Creates a specification that describes how a zip file will be created for a
|
|
52
|
+
* set of input files.
|
|
53
|
+
*
|
|
54
|
+
* @param filesToZip Files to Zip
|
|
55
|
+
* @param rootDirectory Root Directory
|
|
56
|
+
* @returns Upload Zip Specification
|
|
57
|
+
*/
|
|
58
|
+
/* istanbul ignore next */
|
|
59
|
+
export function getUploadZipSpecification(
|
|
60
|
+
filesToZip: string[],
|
|
61
|
+
rootDirectory: string
|
|
62
|
+
): UploadZipSpecification[] {
|
|
63
|
+
const specification: UploadZipSpecification[] = []
|
|
64
|
+
|
|
65
|
+
// Normalize and resolve, this allows for either absolute or relative paths to
|
|
66
|
+
// be used
|
|
67
|
+
rootDirectory = normalize(rootDirectory)
|
|
68
|
+
rootDirectory = resolve(rootDirectory)
|
|
69
|
+
|
|
70
|
+
for (let file of filesToZip) {
|
|
71
|
+
const stats = fs.lstatSync(file, { throwIfNoEntry: false })
|
|
72
|
+
if (!stats) throw new Error(`File ${file} does not exist`)
|
|
73
|
+
|
|
74
|
+
if (!stats.isDirectory()) {
|
|
75
|
+
// Normalize and resolve, this allows for either absolute or relative
|
|
76
|
+
// paths to be used
|
|
77
|
+
file = normalize(file)
|
|
78
|
+
file = resolve(file)
|
|
79
|
+
|
|
80
|
+
if (!file.startsWith(rootDirectory))
|
|
81
|
+
throw new Error(
|
|
82
|
+
`The rootDirectory: ${rootDirectory} is not a parent directory of the file: ${file}`
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
// Check for forbidden characters in file paths that may cause ambiguous
|
|
86
|
+
// behavior if downloaded on different file systems
|
|
87
|
+
const uploadPath = file.replace(rootDirectory, '')
|
|
88
|
+
validateFilePath(uploadPath)
|
|
89
|
+
|
|
90
|
+
specification.push({
|
|
91
|
+
sourcePath: file,
|
|
92
|
+
destinationPath: uploadPath,
|
|
93
|
+
stats
|
|
94
|
+
})
|
|
95
|
+
} else {
|
|
96
|
+
// Empty directory
|
|
97
|
+
const directoryPath = file.replace(rootDirectory, '')
|
|
98
|
+
validateFilePath(directoryPath)
|
|
99
|
+
|
|
100
|
+
specification.push({
|
|
101
|
+
sourcePath: null,
|
|
102
|
+
destinationPath: directoryPath,
|
|
103
|
+
stats
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return specification
|
|
108
|
+
}
|