|
1 | 1 | # GitHub Copilot Instructions for Node.js Actions |
2 | 2 |
|
| 3 | +<!-- This file is managed by joshjohanning/sync-github-repo-settings. Do not edit directly — changes will be overwritten. --> |
| 4 | + |
3 | 5 | ## Project Overview |
4 | 6 |
|
5 | | -This is a Node.js GitHub Action template with ESLint, Prettier, Jest testing, and ncc bundling. Follow these guidelines when making changes. |
| 7 | +This is a Node.js GitHub Action with ESLint, Prettier, Jest testing, and ncc bundling. Follow these guidelines when making changes. |
6 | 8 |
|
7 | | -## Code Quality Standards |
| 9 | +## Critical Rules |
8 | 10 |
|
9 | | -### ESLint Configuration |
| 11 | +These rules prevent CI failures and broken releases. **Never skip them.** |
10 | 12 |
|
11 | | -- Follow the existing ESLint configuration in `eslint.config.js` |
12 | | -- Use ES modules (`import`/`export`) consistently |
13 | | -- Prefer `const` over `let` when variables don't change |
14 | | -- Use descriptive variable names and JSDoc comments for functions |
15 | | -- Handle errors gracefully with try/catch blocks |
| 13 | +- **MUST:** Run `npm run all` before committing. This runs format, lint, test, package, and badge generation. Fix any failures before proceeding. |
| 14 | +- **MUST:** Bump `package.json` version when the published artifact changes: action behavior, runtime requirements, production dependencies, inputs/outputs, or bundled code. Do **not** bump for docs-only, tests-only, CI-only, or devDependency-only changes. |
| 15 | +- **MUST:** When bumping versions or changing dependencies, run `npm install` first to sync `package-lock.json`, then run `npm run all`. A mismatched lockfile breaks CI. |
| 16 | +- **MUST:** Update `README.md` and `action.yml` when adding, removing, or changing inputs, outputs, or behavior. Keep usage examples in the README in sync with `action.yml`. |
| 17 | +- **MUST:** When updating Node.js support, update `runs.using` in `action.yml`, `engines.node` in `package.json`, and CI matrices together. |
| 18 | +- **MUST:** If the action calls GitHub APIs, support GitHub.com, GHES, and GHEC-DR using a `github-api-url` input with default `${{ github.api_url }}`. |
| 19 | +- **NEVER:** Log tokens, secrets, or authenticated URLs. Use `core.setSecret()` to mask sensitive values. |
| 20 | +- **NEVER:** Use `console.log` or `console.error`. Use `core.info()`, `core.warning()`, `core.error()`, and `core.debug()` instead. |
16 | 21 |
|
17 | | -### Prettier Formatting |
| 22 | +## Action Entry Point Pattern |
18 | 23 |
|
19 | | -- Code is automatically formatted with Prettier |
20 | | -- Run `npm run format:write` to format all files |
21 | | -- Use single quotes for strings unless they contain single quotes |
22 | | -- Line length limit is enforced by Prettier config |
| 24 | +- Main logic lives in `src/index.js` with an async `run()` function and top-level try/catch |
| 25 | +- Use `core.setFailed(error.message)` in the catch block — do not use `process.exit(1)` |
| 26 | +- Export helper functions for testability |
| 27 | +- If the action calls GitHub APIs, initialize Octokit with `baseUrl` for GHES/GHEC-DR support |
23 | 28 |
|
24 | | -### Import Organization |
| 29 | +## Input and Output Handling |
25 | 30 |
|
26 | | -```javascript |
27 | | -// Always follow this import order: |
28 | | -import * as core from '@actions/core'; |
29 | | -import * as github from '@actions/github'; |
30 | | -import { Octokit } from '@octokit/rest'; |
31 | | -// ... other imports |
32 | | -``` |
| 31 | +### Inputs |
33 | 32 |
|
34 | | -## Testing Guidelines |
| 33 | +- Use `core.getInput()` for string inputs and `core.getBooleanInput()` for boolean inputs |
| 34 | +- Validate inputs early in the function |
| 35 | +- Log input values for debugging (except sensitive data) |
| 36 | +- Every input in `action.yml` must be documented in the README |
35 | 37 |
|
36 | | -### Jest Test Structure |
| 38 | +### Outputs |
37 | 39 |
|
38 | | -- Use ES modules with `jest.unstable_mockModule()` for mocking |
39 | | -- Mock `@actions/core`, `@actions/github`, and external APIs |
40 | | -- Test both success and error scenarios |
41 | | -- Use descriptive test names that explain the behavior |
| 40 | +- Set outputs using `core.setOutput()` |
| 41 | +- Output names must match what's defined in `action.yml` |
42 | 42 |
|
43 | | -### Mock Patterns |
| 43 | +## Error Handling |
44 | 44 |
|
45 | | -```javascript |
46 | | -// Always mock modules before importing |
47 | | -jest.unstable_mockModule('@actions/core', () => mockCore); |
48 | | -const { functionToTest } = await import('../src/index.js'); |
49 | | -``` |
| 45 | +- Use `core.setFailed()` for fatal action failures |
| 46 | +- Use `core.warning()` for non-fatal issues |
| 47 | +- Check `error.status` for API errors — don't match on error message strings |
| 48 | +- Catch errors at call boundaries; let helper functions throw contextual errors rather than calling `core.setFailed()` directly |
50 | 49 |
|
51 | | -### Test Coverage |
| 50 | +## GitHub API Usage |
52 | 51 |
|
53 | | -- Write unit tests for all exported functions |
54 | | -- Test error handling paths |
55 | | -- Mock external dependencies (GitHub API, file system, etc.) |
56 | | -- Aim for meaningful assertions, not just code coverage |
| 52 | +### Octokit Initialization |
57 | 53 |
|
58 | | -## GitHub Actions Patterns |
| 54 | +- Initialize with `baseUrl` for GHES/GHEC-DR support: `new Octokit({ auth: token, baseUrl: apiUrl })` |
| 55 | +- GHES/GHE documentation doesn't typically need to be called out separately in the README unless there are specific differences to highlight |
59 | 56 |
|
60 | | -### Node Runtime |
| 57 | +### Pagination |
61 | 58 |
|
62 | | -- Only use Node.js runtimes officially supported by GitHub Actions (see [GitHub Actions documentation](https://docs.github.com/en/actions/sharing-automations/creating-actions/metadata-syntax-for-github-actions#runs-for-javascript-actions) for current supported versions) |
63 | | -- Use the same Node.js runtime version configured in this repo's `action.yml` for `runs.using` |
64 | | -- When updating Node.js support, update `runs.using` in `action.yml`, the `engines.node` range in `package.json`, and CI/test matrices together to stay consistent |
| 59 | +- Use `octokit.paginate()` for any endpoint that returns a list |
65 | 60 |
|
66 | | -### Input Handling |
| 61 | +### Rate Limiting |
67 | 62 |
|
68 | | -- Use our custom `getInput()` function for reliable local/CI compatibility |
69 | | -- Validate inputs early in the function |
70 | | -- Provide sensible defaults where appropriate |
71 | | -- Log input values for debugging (except sensitive data) |
| 63 | +- When iterating across many repos/resources, avoid unbounded parallel API calls — batch or serialize to reduce rate-limit pressure |
72 | 64 |
|
73 | | -### Output Setting |
| 65 | +## Logging and Job Summaries |
74 | 66 |
|
75 | | -- Always set outputs using `core.setOutput()` |
76 | | -- Use descriptive output names that match `action.yml` |
77 | | -- Include outputs in job summaries when available |
| 67 | +### Structured Logging |
78 | 68 |
|
79 | | -### Error Handling |
| 69 | +- Use `core.info()` for normal output, `core.debug()` for verbose details |
| 70 | +- Use `core.startGroup()` / `core.endGroup()` for collapsible log sections when processing multiple items |
80 | 71 |
|
81 | | -- Use `core.setFailed()` for action failures |
82 | | -- Use `core.warning()` for non-fatal issues |
83 | | -- Catch and handle API errors gracefully |
84 | | -- Provide helpful error messages |
| 72 | +### Job Summaries |
85 | 73 |
|
86 | | -### Local Development Support |
| 74 | +- Use `core.summary` to add rich output to the Actions UI when summarizing key data or findings |
87 | 75 |
|
88 | | -- Support running locally with environment variables |
89 | | -- Handle missing GitHub Actions environment gracefully |
90 | | -- Provide clear documentation for local testing |
| 76 | +## Security |
91 | 77 |
|
92 | | -## File Organization |
| 78 | +- If the action performs git operations with tokens, sanitize error messages to strip embedded credentials before logging |
| 79 | +- Follow principle of least privilege for token permissions |
| 80 | +- Document required permissions in the README |
| 81 | +- Document when using a GitHub App would be more appropriate than `github.token` or a PAT |
93 | 82 |
|
94 | | -### Source Structure |
| 83 | +## Testing |
95 | 84 |
|
96 | | -- Main logic in `src/index.js` |
97 | | -- Export functions for testing |
98 | | -- Keep functions focused and single-purpose |
99 | | -- Use JSDoc comments for all exported functions |
| 85 | +### Jest with ES Modules |
100 | 86 |
|
101 | | -### Build Process |
| 87 | +- Use `jest.unstable_mockModule()` for ESM mocking — mocks must be registered **before** dynamic imports: |
102 | 88 |
|
103 | | -- Use `npm run package` to bundle with ncc |
104 | | -- Don't commit the bundled `dist/` directory (during publishing this gets published to **tag-only**) |
105 | | -- Run `npm run all` before committing (format, lint, test, package, and badge updating) |
| 89 | + ```javascript |
| 90 | + jest.unstable_mockModule('@actions/core', () => ({ |
| 91 | + getInput: jest.fn(), |
| 92 | + setOutput: jest.fn(), |
| 93 | + setFailed: jest.fn(), |
| 94 | + info: jest.fn(), |
| 95 | + warning: jest.fn() |
| 96 | + })); |
106 | 97 |
|
107 | | -### Dependency and Version Changes |
| 98 | + const core = await import('@actions/core'); |
| 99 | + const { functionToTest } = await import('../src/index.js'); |
| 100 | + ``` |
108 | 101 |
|
109 | | -- When bumping versions or changing dependencies, run `npm install` first to sync the `package-lock.json`, then run `npm run all` |
110 | | -- Do not skip these steps -- a mismatched `package-lock.json` or failing checks will break CI |
| 102 | +### Test Environment Setup |
111 | 103 |
|
112 | | -## Documentation Standards |
| 104 | +- Set `process.env` variables **before** importing the module under test when the module validates config at load time |
| 105 | +- Reset mocks and environment between tests |
113 | 106 |
|
114 | | -### README and action.yml Updates |
| 107 | +### What to Test |
115 | 108 |
|
116 | | -- **Always update `README.md` and `action.yml` when adding, removing, or changing inputs, outputs, or behavior** -- do not forget this step |
117 | | -- Keep usage examples in the README in sync with `action.yml` |
118 | | -- Document all inputs and outputs in both `action.yml` (descriptions/defaults) and `README.md` (usage table/examples) |
119 | | -- Include local development instructions |
120 | | -- Update feature lists when adding functionality |
| 109 | +- Test both success and error paths |
| 110 | +- Assert `core.setFailed`, `core.warning`, and `core.setOutput` calls explicitly |
| 111 | +- Mock external dependencies (GitHub API, file system, etc.) |
| 112 | +- Export helper functions from `src/index.js` to make them individually testable |
121 | 113 |
|
122 | | -### Code Comments |
| 114 | +## Build and Release |
123 | 115 |
|
124 | | -- Use JSDoc for function documentation |
125 | | -- Include parameter types and return types |
126 | | -- Add inline comments for complex logic |
127 | | -- Document environment variable requirements |
| 116 | +### Build Process |
128 | 117 |
|
129 | | -## Dependencies |
| 118 | +- Use `npm run package` to bundle with ncc into `dist/` |
| 119 | +- The `dist/` directory is not committed to the main branch — it is built by CI and published for Git tags (for example, release tags like `v1.2.3` or major-version tags like `v1`) by the publish workflow |
| 120 | +- Users consume the action via version tags: `uses: owner/action@v1` |
130 | 121 |
|
131 | | -### Adding New Dependencies |
| 122 | +### Dependencies |
132 | 123 |
|
133 | 124 | - Prefer `@actions/*` packages for GitHub Actions functionality |
134 | | -- Keep dependencies minimal and well-maintained |
135 | | -- Update both `dependencies` and `devDependencies` appropriately |
136 | | -- Test that bundling still works after adding dependencies |
137 | | - |
138 | | -### Version Management |
139 | | - |
140 | | -- **Always increment the package.json version for each change** |
141 | | -- Use semantic versioning (major.minor.patch) |
142 | | -- Increment patch for bug fixes, minor for new features, major for breaking changes |
143 | | -- Update version before creating releases or publishing changes |
144 | | - |
145 | | -### GitHub API Usage |
146 | | - |
147 | | -- Use `@octokit/rest` for REST API calls |
148 | | -- Use `@actions/github` for context and helpers |
149 | | -- Handle rate limiting and authentication errors |
150 | | -- Cache API responses when appropriate |
151 | | -- Use pagination when necessary |
152 | | - |
153 | | -### GitHub Instance Support |
154 | | - |
155 | | -- **Always support GitHub.com, GHES, and GHEC-DR** using `github-api-url` input with default `${{ github.api_url }}` |
156 | | -- Initialize Octokit with a fallback `baseUrl`: `new Octokit({ auth: token, baseUrl: apiUrl || 'https://api.github.com' })` |
157 | | -- GHES/GHE documentation doesn't typically need to be called out separately in the README unless there are specific differences to highlight |
158 | | - |
159 | | -## Performance Considerations |
| 125 | +- Use `@octokit/rest` for REST API calls and `@actions/github` for context and helpers |
| 126 | +- Test that ncc bundling still works after adding dependencies |
| 127 | +- Use semantic versioning: patch for bug fixes, minor for new features, major for breaking changes |
160 | 128 |
|
161 | | -- Avoid unnecessary API calls (respect rate limits) |
162 | | -- Use efficient data structures for large datasets |
163 | | -- Handle large datasets with pagination and streaming when possible |
164 | | -- Cache API responses when appropriate to reduce redundant calls |
| 129 | +## Code Style |
165 | 130 |
|
166 | | -## Security Best Practices |
167 | | - |
168 | | -- Never log sensitive data (tokens, secrets) |
169 | | -- Use `core.setSecret()` to mask sensitive values |
170 | | -- Validate and sanitize user inputs |
171 | | -- Follow principle of least privilege for token permissions |
172 | | -- Document when using a GitHub App would be more appropriate than `github.token` or a PAT |
| 131 | +- Follow the existing ESLint configuration in `eslint.config.js` |
| 132 | +- Use ES modules (`import`/`export`) consistently |
| 133 | +- Code is automatically formatted with Prettier — run `npm run format:write` to format |
| 134 | +- Use single quotes for strings - when a string contains embedded single quotes (e.g., `Invalid 'days' input`), use a template literal instead of escaped quotes or double quotes (do not modify ESLint or Prettier config to resolve quote conflicts) |
| 135 | +- Use JSDoc comments with parameter types and return types for exported functions |
| 136 | +- Organize imports: `@actions/*` first, then `@octokit/*`, then other dependencies, then local imports |
0 commit comments