@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,39 @@
1
+ /**
2
+ * Last Reviewed Commit: 00282d61458d99f725588a9463b9d25dfda51c22
3
+ * Last Reviewed Date: 2026-01-16
4
+ */
5
+
6
+ import * as path from 'path'
7
+
8
+ /**
9
+ * Converts the given path to the posix form. On Windows, `\\` will be replaced
10
+ * with `/`.
11
+ *
12
+ * @param pth Path to transform
13
+ * @return Posix path
14
+ */
15
+ export function toPosixPath(pth: string): string {
16
+ return pth.replace(/[\\]/g, '/')
17
+ }
18
+
19
+ /**
20
+ * Converts the given path to the win32 form. On Linux, `/` will be replaced
21
+ * with `\\`.
22
+ *
23
+ * @param pth Path to transform
24
+ * @return Win32 path
25
+ */
26
+ export function toWin32Path(pth: string): string {
27
+ return pth.replace(/[/]/g, '\\')
28
+ }
29
+
30
+ /**
31
+ * Converts the given path to a platform-specific path. It does this by
32
+ * replacing instances of `/` and `\` with the platform-specific path separator.
33
+ *
34
+ * @param pth The path to platformize
35
+ * @return The platform-specific path
36
+ */
37
+ export function toPlatformPath(pth: string): string {
38
+ return pth.replace(/[/\\]/g, path.sep)
39
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Last Reviewed Commit: d402248c459082c3727b5d592f9c44b81d7d9709
3
+ * Last Reviewed Date: 2026-01-16
4
+ */
5
+
6
+ import * as exec from '@actions/exec'
7
+ import os from 'os'
8
+
9
+ /**
10
+ * Gets the Windows version and name.
11
+ *
12
+ * @remarks
13
+ *
14
+ * - Exported for testing purposes.
15
+ *
16
+ * @returns Promise with the Windows version and name
17
+ */
18
+ export const getWindowsInfo = async (): Promise<{
19
+ name: string
20
+ version: string
21
+ }> => {
22
+ const { stdout: version } = await exec.getExecOutput(
23
+ 'powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Version"',
24
+ undefined,
25
+ {
26
+ silent: true
27
+ }
28
+ )
29
+
30
+ const { stdout: name } = await exec.getExecOutput(
31
+ 'powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Caption"',
32
+ undefined,
33
+ {
34
+ silent: true
35
+ }
36
+ )
37
+
38
+ return {
39
+ name: name.trim(),
40
+ version: version.trim()
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Gets the macOS version and name.
46
+ *
47
+ * @remarks
48
+ *
49
+ * - Exported for testing purposes.
50
+ *
51
+ * @returns Promise with the macOS version and name
52
+ */
53
+ export const getMacOsInfo = async (): Promise<{
54
+ name: string
55
+ version: string
56
+ }> => {
57
+ const { stdout } = await exec.getExecOutput('sw_vers', undefined, {
58
+ silent: true
59
+ })
60
+
61
+ const version = stdout.match(/ProductVersion:\s*(.+)/)?.[1] ?? ''
62
+ const name = stdout.match(/ProductName:\s*(.+)/)?.[1] ?? ''
63
+
64
+ return {
65
+ name,
66
+ version
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Gets the Linux version and name.
72
+ *
73
+ * @remarks
74
+ *
75
+ * - Exported for testing purposes.
76
+ *
77
+ * @returns Promise with the Linux version and name
78
+ */
79
+ export const getLinuxInfo = async (): Promise<{
80
+ name: string
81
+ version: string
82
+ }> => {
83
+ const { stdout } = await exec.getExecOutput(
84
+ 'lsb_release',
85
+ ['-i', '-r', '-s'],
86
+ {
87
+ silent: true
88
+ }
89
+ )
90
+
91
+ const [name, version] = stdout.trim().split('\n')
92
+
93
+ return {
94
+ name,
95
+ version
96
+ }
97
+ }
98
+
99
+ export const platform = os.platform()
100
+ export const arch = os.arch()
101
+ export const isWindows = platform === 'win32'
102
+ export const isMacOS = platform === 'darwin'
103
+ export const isLinux = platform === 'linux'
104
+
105
+ /**
106
+ * Gets the platform details.
107
+ *
108
+ * @returns Promise with the platform details
109
+ */
110
+ /* istanbul ignore next */
111
+ export async function getDetails(): Promise<{
112
+ name: string
113
+ platform: string
114
+ arch: string
115
+ version: string
116
+ isWindows: boolean
117
+ isMacOS: boolean
118
+ isLinux: boolean
119
+ }> {
120
+ return {
121
+ ...(await (isWindows
122
+ ? getWindowsInfo()
123
+ : isMacOS
124
+ ? getMacOsInfo()
125
+ : getLinuxInfo())),
126
+ platform,
127
+ arch,
128
+ isWindows,
129
+ isMacOS,
130
+ isLinux
131
+ }
132
+ }
@@ -0,0 +1,399 @@
1
+ /**
2
+ * Last Reviewed Commit: 07242b37a49ef3c3704f429570b3c9e377b72ecb
3
+ * Last Reviewed Date: 2026-01-16
4
+ */
5
+
6
+ import fs from 'fs'
7
+ import { EOL } from 'os'
8
+ import path from 'path'
9
+ import { CoreMeta } from './core.js'
10
+
11
+ /**
12
+ * A row for a summary table.
13
+ */
14
+ export type SummaryTableRow = (SummaryTableCell | string)[]
15
+
16
+ /**
17
+ * A cell for a summary table row.
18
+ */
19
+ export interface SummaryTableCell {
20
+ /**
21
+ * Cell content
22
+ */
23
+ data: string
24
+ /**
25
+ * Render cell as header
26
+ * (optional) default: false
27
+ */
28
+ header?: boolean
29
+ /**
30
+ * Number of columns the cell extends
31
+ * (optional) default: '1'
32
+ */
33
+ colspan?: string
34
+ /**
35
+ * Number of rows the cell extends
36
+ * (optional) default: '1'
37
+ */
38
+ rowspan?: string
39
+ }
40
+
41
+ /**
42
+ * Summary image options.
43
+ */
44
+ export interface SummaryImageOptions {
45
+ /**
46
+ * The width of the image in pixels. Must be an integer without a unit.
47
+ * (optional)
48
+ */
49
+ width?: string
50
+ /**
51
+ * The height of the image in pixels. Must be an integer without a unit.
52
+ * (optional)
53
+ */
54
+ height?: string
55
+ }
56
+
57
+ /**
58
+ * Summary write options.
59
+ */
60
+ export interface SummaryWriteOptions {
61
+ /**
62
+ * Replace all existing content in summary file with buffer contents
63
+ * (optional) default: false
64
+ */
65
+ overwrite?: boolean
66
+ }
67
+
68
+ /**
69
+ * A class for creating and writing job step summaries.
70
+ */
71
+ export class Summary {
72
+ /** Output buffer to write to the summary file. */
73
+ private _buffer: string
74
+
75
+ /** The path to the summary file. */
76
+ private _filePath?: string
77
+
78
+ /**
79
+ * Initialize with an empty buffer.
80
+ */
81
+ constructor() {
82
+ this._buffer = ''
83
+ }
84
+
85
+ /**
86
+ * Finds the summary file path from the environment. Rejects if the
87
+ * environment variable is not set/empty or the file does not exist.
88
+ *
89
+ * @remarks
90
+ *
91
+ * - Uses core metadata to track step summary path.
92
+ *
93
+ * @returns Step summary file path.
94
+ */
95
+ async filePath(): Promise<string> {
96
+ // Return the current value, if available.
97
+ if (this._filePath) return this._filePath
98
+
99
+ // Throw if the path is not set/empty.
100
+ if (!CoreMeta.stepSummaryPath)
101
+ throw new Error(
102
+ 'Unable to find environment variable for $GITHUB_STEP_SUMMARY. Check if your runtime environment supports job summaries.'
103
+ )
104
+
105
+ try {
106
+ // Resolve the full path to the file.
107
+ CoreMeta.stepSummaryPath = path.resolve(
108
+ process.cwd(),
109
+ CoreMeta.stepSummaryPath
110
+ )
111
+
112
+ // If the file does not exist, create it. GitHub Actions runners normally
113
+ // create this file automatically. When testing with local-action, we do
114
+ // not know if the file already exists.
115
+ if (!fs.existsSync(CoreMeta.stepSummaryPath))
116
+ fs.writeFileSync(CoreMeta.stepSummaryPath, '', { encoding: 'utf8' })
117
+
118
+ // Test access to the file (read or write).
119
+ fs.accessSync(
120
+ CoreMeta.stepSummaryPath,
121
+ fs.constants.R_OK | fs.constants.W_OK
122
+ )
123
+ } catch {
124
+ throw new Error(
125
+ `Unable to access summary file: '${CoreMeta.stepSummaryPath}'. Check if the file has correct read/write permissions.`
126
+ )
127
+ }
128
+
129
+ this._filePath = CoreMeta.stepSummaryPath
130
+ return Promise.resolve(this._filePath)
131
+ }
132
+
133
+ /**
134
+ * Wraps content in the provided HTML tag and adds any specified attributes.
135
+ *
136
+ * @param tag HTML tag to wrap. Example: 'html', 'body', 'div', etc.
137
+ * @param content The content to wrap within the tag.
138
+ * @param attrs A key-value list of HTML attributes to add.
139
+ * @returns Content wrapped in an HTML element.
140
+ */
141
+ wrap(
142
+ tag: string,
143
+ content: string | null,
144
+ attrs: { [attribute: string]: string } = {}
145
+ ): string {
146
+ const htmlAttrs: string = Object.entries(attrs)
147
+ .map(([key, value]) => ` ${key}="${value}"`)
148
+ .join('')
149
+
150
+ return !content
151
+ ? `<${tag}${htmlAttrs}>`
152
+ : `<${tag}${htmlAttrs}>${content}</${tag}>`
153
+ }
154
+
155
+ /**
156
+ * Writes the buffer to the summary file and empties the buffer. This can
157
+ * append (default) or overwrite the file.
158
+ *
159
+ * @param options Options for the write operation.
160
+ * @returns A promise that resolves to the Summary instance for chaining.
161
+ */
162
+ async write(
163
+ options: SummaryWriteOptions = { overwrite: false }
164
+ ): Promise<Summary> {
165
+ // Set the function to call based on the overwrite setting.
166
+ const writeFunc = options.overwrite ? fs.writeFileSync : fs.appendFileSync
167
+
168
+ // If the file does not exist, create it. GitHub Actions runners normally
169
+ // create this file automatically. When testing with local-action, we do not
170
+ // know if the file already exists.
171
+ const filePath: string = await this.filePath()
172
+
173
+ // Call the write function.
174
+ writeFunc(filePath, this._buffer, { encoding: 'utf8' })
175
+
176
+ // Empty the buffer.
177
+ return this.emptyBuffer()
178
+ }
179
+
180
+ /**
181
+ * Clears the buffer and summary file.
182
+ *
183
+ * @returns A promise that resolve to the Summary instance for chaining.
184
+ */
185
+ async clear(): Promise<Summary> {
186
+ return this.emptyBuffer().write({ overwrite: true })
187
+ }
188
+
189
+ /**
190
+ * Returns the current buffer as a string.
191
+ *
192
+ * @returns Current buffer contents.
193
+ */
194
+ stringify(): string {
195
+ return this._buffer
196
+ }
197
+
198
+ /**
199
+ * Returns `true` the buffer is empty, `false` otherwise.
200
+ *
201
+ * @returns Whether the buffer is empty.
202
+ */
203
+ isEmptyBuffer(): boolean {
204
+ return this._buffer.length === 0
205
+ }
206
+
207
+ /**
208
+ * Resets the buffer without writing to the summary file.
209
+ *
210
+ * @returns The Summary instance for chaining.
211
+ */
212
+ emptyBuffer(): Summary {
213
+ this._buffer = ''
214
+ return this
215
+ }
216
+
217
+ /**
218
+ * Adds raw text to the buffer.
219
+ *
220
+ * @param text The content to add.
221
+ * @param addEOL Whether to append `EOL` to the raw text (default: `false`).
222
+ *
223
+ * @returns The Summary instance for chaining.
224
+ */
225
+ addRaw(text: string, addEOL: boolean = false): Summary {
226
+ this._buffer += text
227
+ return addEOL ? this.addEOL() : this
228
+ }
229
+
230
+ /**
231
+ * Adds the operating system-specific `EOL` marker to the buffer.
232
+ *
233
+ * @returns The Summary instance for chaining.
234
+ */
235
+ addEOL(): Summary {
236
+ return this.addRaw(EOL)
237
+ }
238
+
239
+ /**
240
+ * Adds a code block (\<code\>) to the buffer.
241
+ *
242
+ * @param code Content to render within the code block.
243
+ * @param lang Language to use for syntax highlighting.
244
+ * @returns Summary instance for chaining.
245
+ */
246
+ addCodeBlock(code: string, lang?: string): Summary {
247
+ return this.addRaw(
248
+ this.wrap('pre', this.wrap('code', code), lang ? { lang } : {})
249
+ ).addEOL()
250
+ }
251
+
252
+ /**
253
+ * Adds a list (\<li\>) element to the buffer.
254
+ *
255
+ * @param items List of items to render.
256
+ * @param ordered Whether the list should be ordered.
257
+ * @returns Summary instance for chaining.
258
+ */
259
+ addList(items: string[], ordered: boolean = false): Summary {
260
+ return this.addRaw(
261
+ this.wrap(
262
+ ordered ? 'ol' : 'ul',
263
+ items.map(item => this.wrap('li', item)).join('')
264
+ )
265
+ ).addEOL()
266
+ }
267
+
268
+ /**
269
+ * Adds a table (\<table\>) element to the buffer.
270
+ *
271
+ * @param rows Table rows to render.
272
+ * @returns Summary instance for chaining.
273
+ */
274
+ addTable(rows: SummaryTableRow[]): Summary {
275
+ return this.addRaw(
276
+ this.wrap(
277
+ 'table',
278
+ // The table body consists of a list of rows, each with a list of cells.
279
+ rows
280
+ .map(row => {
281
+ const cells: string = row
282
+ .map(cell => {
283
+ // Cell is a string, return as-is.
284
+ if (typeof cell === 'string') return this.wrap('td', cell)
285
+
286
+ // Cell is a SummaryTableCell, extract the data and attributes.
287
+ return this.wrap(cell.header ? 'th' : 'td', cell.data, {
288
+ ...(cell.colspan ? { colspan: cell.colspan } : {}),
289
+ ...(cell.rowspan ? { rowspan: cell.rowspan } : {})
290
+ })
291
+ })
292
+ .join('')
293
+
294
+ return this.wrap('tr', cells)
295
+ })
296
+ .join('')
297
+ )
298
+ ).addEOL()
299
+ }
300
+
301
+ /**
302
+ * Adds a details (\<details\>) element to the buffer.
303
+ *
304
+ * @param label Text for the \<summary\> element.
305
+ * @param content Text for the \<details\> container.
306
+ * @returns Summary instance for chaining.
307
+ */
308
+ addDetails(label: string, content: string): Summary {
309
+ return this.addRaw(
310
+ this.wrap('details', this.wrap('summary', label) + content)
311
+ ).addEOL()
312
+ }
313
+
314
+ /**
315
+ * Adds an image (\<img\>) element to the buffer.
316
+ *
317
+ * @param src Path to the image to embed.
318
+ * @param alt Text description of the image.
319
+ * @param options Additional image attributes.
320
+ * @returns Summary instance for chaining.
321
+ */
322
+ addImage(
323
+ src: string,
324
+ alt: string,
325
+ options: SummaryImageOptions = {}
326
+ ): Summary {
327
+ return this.addRaw(
328
+ this.wrap('img', null, {
329
+ src,
330
+ alt,
331
+ ...(options.width ? { width: options.width } : {}),
332
+ ...(options.height ? { height: options.height } : {})
333
+ })
334
+ ).addEOL()
335
+ }
336
+
337
+ /**
338
+ * Adds a heading (\<hX\>) element to the buffer.
339
+ *
340
+ * @param text Heading text to render.
341
+ * @param level Heading level. Defaults to `1`.
342
+ * @returns Summary instance for chaining.
343
+ */
344
+ addHeading(text: string, level: number | string = 1): Summary {
345
+ // If level is a string, attempt to parse it as a number.
346
+ const levelAsNum = typeof level === 'string' ? parseInt(level) : level
347
+
348
+ // If level is less than 1 or greater than 6, default to `h1`.
349
+ const tag =
350
+ Number.isNaN(levelAsNum) || levelAsNum < 1 || levelAsNum > 6
351
+ ? 'h1'
352
+ : `h${level}`
353
+
354
+ const element = this.wrap(tag, text)
355
+ return this.addRaw(element).addEOL()
356
+ }
357
+
358
+ /**
359
+ * Adds a horizontal rule (\<hr\>) element to the buffer.
360
+ *
361
+ * @returns Summary instance for chaining.
362
+ */
363
+ addSeparator(): Summary {
364
+ return this.addRaw(this.wrap('hr', null)).addEOL()
365
+ }
366
+
367
+ /**
368
+ * Adds a line break (\<br\>) to the buffer.
369
+ *
370
+ * @returns Summary instance for chaining.
371
+ */
372
+ addBreak(): Summary {
373
+ return this.addRaw(this.wrap('br', null)).addEOL()
374
+ }
375
+
376
+ /**
377
+ * Adds a block quote \<blockquote\> element to the buffer.
378
+ *
379
+ * @param text Quote text to render.
380
+ * @param cite (Optional) Citation URL.
381
+ * @returns Summary instance for chaining.
382
+ */
383
+ addQuote(text: string, cite?: string): Summary {
384
+ return this.addRaw(
385
+ this.wrap('blockquote', text, cite ? { cite } : {})
386
+ ).addEOL()
387
+ }
388
+
389
+ /**
390
+ * Adds an anchor (\<a\>) element to the buffer.
391
+ *
392
+ * @param text Text content to render.
393
+ * @param href Hyperlink to the target.
394
+ * @returns Summary instance for chaining.
395
+ */
396
+ addLink(text: string, href: string): Summary {
397
+ return this.addRaw(this.wrap('a', text, { href })).addEOL()
398
+ }
399
+ }
@@ -0,0 +1,40 @@
1
+ import type { EnvMetadata } from '../types.js'
2
+
3
+ /**
4
+ * Environment Metdata
5
+ */
6
+ export const EnvMeta: EnvMetadata = {
7
+ actionFile: '',
8
+ actionPath: '',
9
+ artifacts: [],
10
+ caches: {},
11
+ dotenvFile: '',
12
+ entrypoint: '',
13
+ env: {},
14
+ inputs: {},
15
+ outputs: {},
16
+ path: '',
17
+ postEntrypoint: undefined,
18
+ preEntrypoint: undefined
19
+ }
20
+
21
+ /**
22
+ * Reset the Environment Metadata
23
+ *
24
+ * Simple convenience function to reset the environment metadata back to initial
25
+ * state.
26
+ */
27
+ export function ResetEnvMetadata(): void {
28
+ EnvMeta.actionFile = ''
29
+ EnvMeta.actionPath = ''
30
+ EnvMeta.artifacts = []
31
+ EnvMeta.caches = {}
32
+ EnvMeta.dotenvFile = ''
33
+ EnvMeta.entrypoint = ''
34
+ EnvMeta.env = {}
35
+ EnvMeta.inputs = {}
36
+ EnvMeta.outputs = {}
37
+ EnvMeta.path = ''
38
+ EnvMeta.postEntrypoint = undefined
39
+ EnvMeta.preEntrypoint = undefined
40
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Last Reviewed Commit: faa425440f86f9c16587a19dfb59491253a2c92a
3
+ * Last Reviewed Date: 2026-01-16
4
+ */
5
+
6
+ import { existsSync, readFileSync } from 'fs'
7
+ import { EOL } from 'os'
8
+ import type { WebhookPayload } from './interfaces.js'
9
+
10
+ export class Context {
11
+ /**
12
+ * Webhook payload object that triggered the workflow
13
+ */
14
+ payload: WebhookPayload
15
+
16
+ eventName: string
17
+ sha: string
18
+ ref: string
19
+ workflow: string
20
+ action: string
21
+ actor: string
22
+ job: string
23
+ runAttempt: number
24
+ runNumber: number
25
+ runId: number
26
+ apiUrl: string
27
+ serverUrl: string
28
+ graphqlUrl: string
29
+
30
+ /**
31
+ * Hydrate the context from the environment
32
+ */
33
+ constructor() {
34
+ this.payload = {}
35
+
36
+ /* istanbul ignore next */
37
+ if (process.env.GITHUB_EVENT_PATH) {
38
+ if (existsSync(process.env.GITHUB_EVENT_PATH)) {
39
+ this.payload = JSON.parse(
40
+ readFileSync(process.env.GITHUB_EVENT_PATH, { encoding: 'utf8' })
41
+ )
42
+ } else {
43
+ const path = process.env.GITHUB_EVENT_PATH
44
+ process.stdout.write(`GITHUB_EVENT_PATH ${path} does not exist${EOL}`)
45
+ }
46
+ }
47
+
48
+ this.eventName = process.env.GITHUB_EVENT_NAME as string
49
+ this.sha = process.env.GITHUB_SHA as string
50
+ this.ref = process.env.GITHUB_REF as string
51
+ this.workflow = process.env.GITHUB_WORKFLOW as string
52
+ this.action = process.env.GITHUB_ACTION as string
53
+ this.actor = process.env.GITHUB_ACTOR as string
54
+ this.job = process.env.GITHUB_JOB as string
55
+ this.runAttempt = parseInt(process.env.GITHUB_RUN_ATTEMPT as string, 10)
56
+ this.runNumber = parseInt(process.env.GITHUB_RUN_NUMBER as string, 10)
57
+ this.runId = parseInt(process.env.GITHUB_RUN_ID as string, 10)
58
+ /* istanbul ignore next */
59
+ this.apiUrl = process.env.GITHUB_API_URL ?? 'https://api.github.com'
60
+ /* istanbul ignore next */
61
+ this.serverUrl = process.env.GITHUB_SERVER_URL ?? 'https://github.com'
62
+ /* istanbul ignore next */
63
+ this.graphqlUrl =
64
+ process.env.GITHUB_GRAPHQL_URL ?? 'https://api.github.com/graphql'
65
+ }
66
+
67
+ /* istanbul ignore next */
68
+ get issue(): { owner: string; repo: string; number: number } {
69
+ const payload = this.payload
70
+
71
+ return {
72
+ ...this.repo,
73
+ number: (payload.issue || payload.pull_request || payload).number
74
+ }
75
+ }
76
+
77
+ /* istanbul ignore next */
78
+ get repo(): { owner: string; repo: string } {
79
+ if (process.env.GITHUB_REPOSITORY) {
80
+ const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/')
81
+ return { owner, repo }
82
+ }
83
+
84
+ if (this.payload.repository)
85
+ return {
86
+ owner: this.payload.repository.owner.login,
87
+ repo: this.payload.repository.name
88
+ }
89
+
90
+ throw new Error(
91
+ "context.repo requires a GITHUB_REPOSITORY environment variable like 'owner/repo'"
92
+ )
93
+ }
94
+ }