@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.
Files changed (223) hide show
  1. package/.checkov.yml +3 -0
  2. package/.env.example +379 -0
  3. package/.github/codeql/codeql-config.yml +7 -0
  4. package/.github/copilot-instructions.md +79 -0
  5. package/.github/dependabot.yml +32 -0
  6. package/.github/workflows/codeql-analysis.yml +53 -0
  7. package/.github/workflows/continuous-delivery.yml +93 -0
  8. package/.github/workflows/continuous-integration.yml +382 -0
  9. package/.github/workflows/linter.yml +47 -0
  10. package/.github/workflows/release.yaml +99 -0
  11. package/.github/workflows/version-check.yml +44 -0
  12. package/.grype.yml +151 -0
  13. package/.markdown-lint.yml +7 -0
  14. package/.mega-linter.yml +188 -0
  15. package/.node-version +1 -0
  16. package/.prettierignore +4 -0
  17. package/.prettierrc.yml +14 -0
  18. package/.vscode/launch.json +15 -0
  19. package/.vscode/mcp.json +9 -0
  20. package/.vscode/settings.json +15 -0
  21. package/.vscode/tasks.json +51 -0
  22. package/.yaml-lint.yml +10 -0
  23. package/CHANGELOG.md +75 -0
  24. package/CODEOWNERS +3 -0
  25. package/CODE_OF_CONDUCT.md +74 -0
  26. package/CONTRIBUTING.md +140 -0
  27. package/LICENSE +21 -0
  28. package/README.md +285 -2
  29. package/SECURITY.md +42 -0
  30. package/SUPPORT.md +19 -0
  31. package/__fixtures__/@actions/core.ts +14 -0
  32. package/__fixtures__/@actions/exec.ts +9 -0
  33. package/__fixtures__/@actions/github.ts +3 -0
  34. package/__fixtures__/@actions/http-client.ts +9 -0
  35. package/__fixtures__/@actions/io.ts +9 -0
  36. package/__fixtures__/@octokit/rest.ts +18 -0
  37. package/__fixtures__/crypto.ts +7 -0
  38. package/__fixtures__/fs.ts +27 -0
  39. package/__fixtures__/javascript/no-export/.env.fixture +2 -0
  40. package/__fixtures__/javascript/no-export/action.yml +16 -0
  41. package/__fixtures__/javascript/no-export/node_modules/.gitkeep +0 -0
  42. package/__fixtures__/javascript/no-export/package.json +12 -0
  43. package/__fixtures__/javascript/no-export/src/index.js +0 -0
  44. package/__fixtures__/javascript/no-export/src/main.js +0 -0
  45. package/__fixtures__/javascript/no-import/.env.fixture +2 -0
  46. package/__fixtures__/javascript/no-import/action.yml +16 -0
  47. package/__fixtures__/javascript/no-import/node_modules/.gitkeep +0 -0
  48. package/__fixtures__/javascript/no-import/package.json +12 -0
  49. package/__fixtures__/javascript/no-import/src/index.js +3 -0
  50. package/__fixtures__/javascript/no-import/src/main.js +7 -0
  51. package/__fixtures__/javascript/success/.env.fixture +2 -0
  52. package/__fixtures__/javascript/success/action.yml +16 -0
  53. package/__fixtures__/javascript/success/node_modules/.gitkeep +0 -0
  54. package/__fixtures__/javascript/success/package.json +12 -0
  55. package/__fixtures__/javascript/success/post/index.js +3 -0
  56. package/__fixtures__/javascript/success/post/main.js +13 -0
  57. package/__fixtures__/javascript/success/pre/index.js +3 -0
  58. package/__fixtures__/javascript/success/pre/main.js +13 -0
  59. package/__fixtures__/javascript/success/src/index.js +3 -0
  60. package/__fixtures__/javascript/success/src/main.js +13 -0
  61. package/__fixtures__/javascript-esm/no-import/.env.fixture +2 -0
  62. package/__fixtures__/javascript-esm/no-import/action.yml +16 -0
  63. package/__fixtures__/javascript-esm/no-import/node_modules/.gitkeep +0 -0
  64. package/__fixtures__/javascript-esm/no-import/package.json +13 -0
  65. package/__fixtures__/javascript-esm/no-import/src/index.js +3 -0
  66. package/__fixtures__/javascript-esm/no-import/src/main.js +7 -0
  67. package/__fixtures__/javascript-esm/success/.env.fixture +2 -0
  68. package/__fixtures__/javascript-esm/success/action.yml +16 -0
  69. package/__fixtures__/javascript-esm/success/node_modules/.gitkeep +0 -0
  70. package/__fixtures__/javascript-esm/success/package.json +13 -0
  71. package/__fixtures__/javascript-esm/success/post/index.js +3 -0
  72. package/__fixtures__/javascript-esm/success/post/main.js +13 -0
  73. package/__fixtures__/javascript-esm/success/pre/index.js +3 -0
  74. package/__fixtures__/javascript-esm/success/pre/main.js +13 -0
  75. package/__fixtures__/javascript-esm/success/src/index.js +3 -0
  76. package/__fixtures__/javascript-esm/success/src/main.js +13 -0
  77. package/__fixtures__/path.ts +7 -0
  78. package/__fixtures__/payloads/issue_opened.json +33 -0
  79. package/__fixtures__/stream/promises.ts +3 -0
  80. package/__fixtures__/tsconfig-paths.ts +4 -0
  81. package/__fixtures__/typescript/no-import/.env.fixture +2 -0
  82. package/__fixtures__/typescript/no-import/action.yml +16 -0
  83. package/__fixtures__/typescript/no-import/node_modules/.gitkeep +0 -0
  84. package/__fixtures__/typescript/no-import/package.json +15 -0
  85. package/__fixtures__/typescript/no-import/src/index.ts +3 -0
  86. package/__fixtures__/typescript/no-import/src/main.ts +3 -0
  87. package/__fixtures__/typescript/no-import/tsconfig.json +20 -0
  88. package/__fixtures__/typescript/success/.env.fixture +2 -0
  89. package/__fixtures__/typescript/success/action.yml +16 -0
  90. package/__fixtures__/typescript/success/node_modules/.gitkeep +0 -0
  91. package/__fixtures__/typescript/success/package.json +15 -0
  92. package/__fixtures__/typescript/success/post/index.ts +3 -0
  93. package/__fixtures__/typescript/success/post/main.ts +9 -0
  94. package/__fixtures__/typescript/success/pre/index.ts +3 -0
  95. package/__fixtures__/typescript/success/pre/main.ts +9 -0
  96. package/__fixtures__/typescript/success/src/index.ts +3 -0
  97. package/__fixtures__/typescript/success/src/main.ts +9 -0
  98. package/__fixtures__/typescript/success/tsconfig.json +20 -0
  99. package/__fixtures__/typescript/success-yaml/.env.fixture +2 -0
  100. package/__fixtures__/typescript/success-yaml/action.yaml +16 -0
  101. package/__fixtures__/typescript/success-yaml/node_modules/.gitkeep +0 -0
  102. package/__fixtures__/typescript/success-yaml/package.json +15 -0
  103. package/__fixtures__/typescript/success-yaml/post/index.ts +3 -0
  104. package/__fixtures__/typescript/success-yaml/post/main.ts +9 -0
  105. package/__fixtures__/typescript/success-yaml/pre/index.ts +3 -0
  106. package/__fixtures__/typescript/success-yaml/pre/main.ts +9 -0
  107. package/__fixtures__/typescript/success-yaml/src/index.ts +3 -0
  108. package/__fixtures__/typescript/success-yaml/src/main.ts +9 -0
  109. package/__fixtures__/typescript/success-yaml/tsconfig.json +20 -0
  110. package/__fixtures__/typescript-esm/no-export/.env.fixture +2 -0
  111. package/__fixtures__/typescript-esm/no-export/action.yml +17 -0
  112. package/__fixtures__/typescript-esm/no-export/node_modules/.gitkeep +0 -0
  113. package/__fixtures__/typescript-esm/no-export/package.json +17 -0
  114. package/__fixtures__/typescript-esm/no-export/src/index.ts +0 -0
  115. package/__fixtures__/typescript-esm/no-export/src/main.ts +0 -0
  116. package/__fixtures__/typescript-esm/no-export/tsconfig.json +25 -0
  117. package/__fixtures__/typescript-esm/no-import/.env.fixture +2 -0
  118. package/__fixtures__/typescript-esm/no-import/action.yml +17 -0
  119. package/__fixtures__/typescript-esm/no-import/node_modules/.gitkeep +0 -0
  120. package/__fixtures__/typescript-esm/no-import/package.json +17 -0
  121. package/__fixtures__/typescript-esm/no-import/src/index.ts +3 -0
  122. package/__fixtures__/typescript-esm/no-import/src/main.ts +3 -0
  123. package/__fixtures__/typescript-esm/no-import/tsconfig.json +25 -0
  124. package/__fixtures__/typescript-esm/success/.env.fixture +2 -0
  125. package/__fixtures__/typescript-esm/success/action.yml +17 -0
  126. package/__fixtures__/typescript-esm/success/node_modules/.gitkeep +0 -0
  127. package/__fixtures__/typescript-esm/success/package.json +17 -0
  128. package/__fixtures__/typescript-esm/success/post/index.ts +3 -0
  129. package/__fixtures__/typescript-esm/success/post/main.ts +9 -0
  130. package/__fixtures__/typescript-esm/success/pre/index.ts +3 -0
  131. package/__fixtures__/typescript-esm/success/pre/main.ts +9 -0
  132. package/__fixtures__/typescript-esm/success/src/index.ts +3 -0
  133. package/__fixtures__/typescript-esm/success/src/main.ts +9 -0
  134. package/__fixtures__/typescript-esm/success/tsconfig.json +25 -0
  135. package/__fixtures__/typescript-esm/tsconfig-comments/.env.fixture +2 -0
  136. package/__fixtures__/typescript-esm/tsconfig-comments/action.yml +17 -0
  137. package/__fixtures__/typescript-esm/tsconfig-comments/node_modules/.gitkeep +0 -0
  138. package/__fixtures__/typescript-esm/tsconfig-comments/package.json +17 -0
  139. package/__fixtures__/typescript-esm/tsconfig-comments/src/index.ts +3 -0
  140. package/__fixtures__/typescript-esm/tsconfig-comments/src/main.ts +9 -0
  141. package/__fixtures__/typescript-esm/tsconfig-comments/tsconfig.json +21 -0
  142. package/__tests__/command.test.ts +224 -0
  143. package/__tests__/stubs/artifact/internal/client.test.ts +340 -0
  144. package/__tests__/stubs/artifact/internal/delete/delete-artifact.test.ts +152 -0
  145. package/__tests__/stubs/artifact/internal/download/download-artifact.test.ts +119 -0
  146. package/__tests__/stubs/artifact/internal/find/get-artifact.test.ts +237 -0
  147. package/__tests__/stubs/artifact/internal/find/list-artifacts.test.ts +218 -0
  148. package/__tests__/stubs/artifact/internal/find/retry-options.test.ts +54 -0
  149. package/__tests__/stubs/artifact/internal/shared/config.test.ts +91 -0
  150. package/__tests__/stubs/artifact/internal/shared/errors.test.ts +38 -0
  151. package/__tests__/stubs/artifact/internal/shared/user-agent.test.ts +22 -0
  152. package/__tests__/stubs/artifact/internal/upload/path-and-artifact-name-validation.test.ts +53 -0
  153. package/__tests__/stubs/artifact/internal/upload/upload-artifact.test.ts +126 -0
  154. package/__tests__/stubs/artifact/internal/upload/upload-zip-specification.test.ts +49 -0
  155. package/__tests__/stubs/cache/cache.test.ts +357 -0
  156. package/__tests__/stubs/cache/errors.test.ts +55 -0
  157. package/__tests__/stubs/cache/internal/cacheUtils.test.ts +109 -0
  158. package/__tests__/stubs/cache/internal/constants.test.ts +81 -0
  159. package/__tests__/stubs/cache/internal/tar.test.ts +129 -0
  160. package/__tests__/stubs/core/core.test.ts +760 -0
  161. package/__tests__/stubs/core/path-utils.test.ts +45 -0
  162. package/__tests__/stubs/core/platform.test.ts +89 -0
  163. package/__tests__/stubs/core/summary.test.ts +455 -0
  164. package/__tests__/stubs/env.test.ts +72 -0
  165. package/__tests__/stubs/github/context.test.ts +73 -0
  166. package/__tests__/stubs/github/github.test.ts +15 -0
  167. package/__tests__/stubs/github/internal/utils.test.ts +98 -0
  168. package/__tests__/stubs/github/utils.test.ts +16 -0
  169. package/__tests__/utils/config.test.ts +78 -0
  170. package/__tests__/utils/output.test.ts +32 -0
  171. package/__tests__/utils/package.test.ts +33 -0
  172. package/badges/coverage.svg +1 -0
  173. package/bin/local-action.js +121 -0
  174. package/docs/create-a-job-summary.md +32 -0
  175. package/docs/debugging-in-vscode.md +27 -0
  176. package/docs/img/job-summary.png +0 -0
  177. package/docs/supported-functionality.md +126 -0
  178. package/eslint.config.mjs +74 -0
  179. package/jest.config.ts +41 -0
  180. package/package.json +93 -4
  181. package/src/command.ts +171 -0
  182. package/src/commands/run.ts +616 -0
  183. package/src/index.ts +21 -0
  184. package/src/stubs/artifact/artifact.ts +17 -0
  185. package/src/stubs/artifact/internal/client.ts +400 -0
  186. package/src/stubs/artifact/internal/delete/delete-artifact.ts +126 -0
  187. package/src/stubs/artifact/internal/download/download-artifact.ts +309 -0
  188. package/src/stubs/artifact/internal/find/get-artifact.ts +135 -0
  189. package/src/stubs/artifact/internal/find/list-artifacts.ts +188 -0
  190. package/src/stubs/artifact/internal/find/retry-options.ts +57 -0
  191. package/src/stubs/artifact/internal/shared/config.ts +64 -0
  192. package/src/stubs/artifact/internal/shared/errors.ts +53 -0
  193. package/src/stubs/artifact/internal/shared/interfaces.ts +195 -0
  194. package/src/stubs/artifact/internal/shared/user-agent.ts +13 -0
  195. package/src/stubs/artifact/internal/upload/path-and-artifact-name-validation.ts +92 -0
  196. package/src/stubs/artifact/internal/upload/upload-artifact.ts +127 -0
  197. package/src/stubs/artifact/internal/upload/upload-zip-specification.ts +108 -0
  198. package/src/stubs/artifact/internal/upload/zip.ts +122 -0
  199. package/src/stubs/cache/cache.ts +334 -0
  200. package/src/stubs/cache/errors.ts +31 -0
  201. package/src/stubs/cache/interfaces.ts +4 -0
  202. package/src/stubs/cache/internal/cacheUtils.ts +207 -0
  203. package/src/stubs/cache/internal/constants.ts +45 -0
  204. package/src/stubs/cache/internal/tar.ts +374 -0
  205. package/src/stubs/cache/options.ts +91 -0
  206. package/src/stubs/core/core.ts +743 -0
  207. package/src/stubs/core/path-utils.ts +39 -0
  208. package/src/stubs/core/platform.ts +132 -0
  209. package/src/stubs/core/summary.ts +399 -0
  210. package/src/stubs/env.ts +40 -0
  211. package/src/stubs/github/context.ts +94 -0
  212. package/src/stubs/github/github.ts +29 -0
  213. package/src/stubs/github/interfaces.ts +52 -0
  214. package/src/stubs/github/internal/utils.ts +83 -0
  215. package/src/stubs/github/utils.ts +52 -0
  216. package/src/types/quibble.d.ts +5 -0
  217. package/src/types.ts +116 -0
  218. package/src/utils/config.ts +47 -0
  219. package/src/utils/output.ts +20 -0
  220. package/src/utils/package.ts +38 -0
  221. package/tsconfig.base.json +29 -0
  222. package/tsconfig.eslint.json +22 -0
  223. 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
+ }