@codfish/actions 1.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 (37) hide show
  1. package/.github/codeql-config.yml +21 -0
  2. package/.github/dependabot.yml +35 -0
  3. package/.github/workflows/claude-code-review.yml +43 -0
  4. package/.github/workflows/claude.yml +39 -0
  5. package/.github/workflows/release.yml +48 -0
  6. package/.github/workflows/security.yml +103 -0
  7. package/.github/workflows/update-docs.yml +38 -0
  8. package/.github/workflows/validate.yml +210 -0
  9. package/.husky/pre-commit +1 -0
  10. package/.nvmrc +1 -0
  11. package/AGENT.md +129 -0
  12. package/CLAUDE.md +3 -0
  13. package/CONTRIBUTING.md +316 -0
  14. package/README.md +207 -0
  15. package/SECURITY.md +208 -0
  16. package/bin/generate-docs.js +432 -0
  17. package/comment/README.md +82 -0
  18. package/comment/action.yml +102 -0
  19. package/eslint.config.js +8 -0
  20. package/npm-publish-pr/README.md +145 -0
  21. package/npm-publish-pr/action.yml +171 -0
  22. package/package.json +52 -0
  23. package/setup-node-and-install/README.md +139 -0
  24. package/setup-node-and-install/action.yml +220 -0
  25. package/tests/fixtures/.node-version +1 -0
  26. package/tests/fixtures/.nvmrc +1 -0
  27. package/tests/fixtures/lockfiles/package-lock.json +12 -0
  28. package/tests/fixtures/lockfiles/pnpm-lock.yaml +9 -0
  29. package/tests/fixtures/lockfiles/yarn.lock +7 -0
  30. package/tests/fixtures/package-json/minimal.json +4 -0
  31. package/tests/fixtures/package-json/scoped.json +6 -0
  32. package/tests/fixtures/package-json/valid.json +13 -0
  33. package/tests/integration/comment/basic.bats +95 -0
  34. package/tests/integration/npm-pr-version/basic.bats +353 -0
  35. package/tests/integration/setup-node-and-install/basic.bats +200 -0
  36. package/tests/scripts/test-helpers.sh +113 -0
  37. package/tests/scripts/test-runner.sh +115 -0
package/SECURITY.md ADDED
@@ -0,0 +1,208 @@
1
+ # Security Policy
2
+
3
+ <!-- prettier-ignore-start -->
4
+ <!-- START doctoc generated TOC please keep comment here to allow auto update -->
5
+ <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
6
+ ## Table of Contents
7
+
8
+ - [Supported Versions](#supported-versions)
9
+ - [Reporting a Vulnerability](#reporting-a-vulnerability)
10
+ - [šŸ”’ Private Disclosure](#-private-disclosure)
11
+ - [šŸ“‹ What to Include](#-what-to-include)
12
+ - [šŸ• Response Timeline](#-response-timeline)
13
+ - [Security Best Practices for Users](#security-best-practices-for-users)
14
+ - [šŸ” Secrets Management](#-secrets-management)
15
+ - [šŸ·ļø Action Versioning](#-action-versioning)
16
+ - [šŸ” Workflow Permissions](#-workflow-permissions)
17
+ - [šŸ›”ļø Input Validation](#-input-validation)
18
+ - [Security Features](#security-features)
19
+ - [šŸ”’ Automated Security Scanning](#-automated-security-scanning)
20
+ - [šŸ›”ļø Secure Development Practices](#-secure-development-practices)
21
+ - [šŸ” Supply Chain Security](#-supply-chain-security)
22
+ - [Known Security Considerations](#known-security-considerations)
23
+ - [GitHub Actions Environment](#github-actions-environment)
24
+ - [npm Publishing (npm-pr-version)](#npm-publishing-npm-pr-version)
25
+ - [Comment Actions](#comment-actions)
26
+ - [Incident Response](#incident-response)
27
+ - [Security Contact](#security-contact)
28
+ - [Acknowledgments](#acknowledgments)
29
+
30
+ <!-- END doctoc generated TOC please keep comment here to allow auto update -->
31
+ <!-- prettier-ignore-end -->
32
+
33
+ ## Supported Versions
34
+
35
+ This project follows a rolling release model. We provide security updates for:
36
+
37
+ | Version | Supported |
38
+ | ------------------- | ------------------- |
39
+ | main | āœ… Always supported |
40
+ | Latest release tags | āœ… Supported |
41
+ | Older releases | āŒ Not supported |
42
+
43
+ ## Reporting a Vulnerability
44
+
45
+ If you discover a security issue, please follow these steps:
46
+
47
+ ### šŸ”’ Private Disclosure
48
+
49
+ **Do NOT create a public issue for security vulnerabilities.**
50
+
51
+ Instead, please report security issues privately using one of these methods:
52
+
53
+ 1. **GitHub Security Advisories** (preferred)
54
+ - Go to the [Security tab](https://github.com/codfish/actions/security/advisories)
55
+ - Click "Report a vulnerability"
56
+ - Fill out the form with details
57
+
58
+ 2. **Email**
59
+ - Send details to: [chris@codfish.dev](mailto:chris@codfish.dev)
60
+ - Include "SECURITY" in the subject line
61
+
62
+ ### šŸ“‹ What to Include
63
+
64
+ When reporting a vulnerability, please include:
65
+
66
+ - **Description** of the vulnerability
67
+ - **Steps to reproduce** the issue
68
+ - **Potential impact** of the vulnerability
69
+ - **Suggested fix** (if you have one)
70
+ - **Your contact information** for follow-up
71
+
72
+ ### šŸ• Response Timeline
73
+
74
+ We aim to respond to security reports within:
75
+
76
+ - **Initial response**: 24-48 hours
77
+ - **Confirmation/triage**: 2-5 business days
78
+ - **Resolution**: Varies based on complexity
79
+
80
+ ## Security Best Practices for Users
81
+
82
+ When using these GitHub Actions in your workflows:
83
+
84
+ ### šŸ” Secrets Management
85
+
86
+ - **Never log secrets** in workflows that use these actions
87
+ - Use **GitHub Secrets** for sensitive information
88
+ - **Limit secret scope** to only necessary workflows
89
+ - **Rotate secrets** regularly
90
+
91
+ ```yaml
92
+ # āœ… Good - Using secrets properly
93
+ - uses: codfish/actions/npm-pr-version@v1
94
+ with:
95
+ npm-token: ${{ secrets.NPM_TOKEN }}
96
+ github-token: ${{ secrets.GITHUB_TOKEN }}
97
+
98
+ # āŒ Bad - Exposing secrets
99
+ - name: Debug
100
+ run: echo "Token: ${{ secrets.NPM_TOKEN }}"
101
+ ```
102
+
103
+ ### šŸ·ļø Action Versioning
104
+
105
+ - **Pin to specific versions or commit hashes** for production workflows
106
+ - **Avoid using `@main`** in production (use for testing only)
107
+
108
+ ```yaml
109
+ # āœ… Good - Pinned version
110
+ - uses: codfish/actions/setup-node-and-install@v1.2.3
111
+
112
+ # āš ļø Caution - Latest main (testing only)
113
+ - uses: codfish/actions/setup-node-and-install@main
114
+ ```
115
+
116
+ ### šŸ” Workflow Permissions
117
+
118
+ - **Use minimal permissions** required
119
+ - **Specify explicit permissions** when possible
120
+ - **Avoid using `write-all`** permissions
121
+
122
+ ```yaml
123
+ # āœ… Good - Minimal permissions
124
+ permissions:
125
+ contents: read
126
+ issues: write
127
+ pull-requests: write
128
+
129
+ # āŒ Bad - Excessive permissions
130
+ permissions: write-all
131
+ ```
132
+
133
+ ### šŸ›”ļø Input Validation
134
+
135
+ - **Validate user inputs** before using them in actions
136
+ - **Sanitize outputs** when displaying them
137
+ - **Be cautious with dynamic expressions**
138
+
139
+ ## Security Features
140
+
141
+ This project implements several security measures:
142
+
143
+ ### šŸ”’ Automated Security Scanning
144
+
145
+ - **Dependabot** for dependency updates
146
+ - **CodeQL** for static analysis
147
+ - **Dependency Review** for PR security checks
148
+ - **Secret scanning** with TruffleHog
149
+ - **npm audit** for vulnerability detection
150
+
151
+ ### šŸ›”ļø Secure Development Practices
152
+
153
+ - **Input validation** in all actions
154
+ - **Error handling** without information disclosure
155
+ - **No secret logging** in any action
156
+ - **Least privilege** principle in action permissions
157
+
158
+ ### šŸ” Supply Chain Security
159
+
160
+ - **Minimal dependencies** to reduce attack surface
161
+ - **Regular dependency updates** via Dependabot
162
+ - **Verified action references** in workflows
163
+
164
+ ## Known Security Considerations
165
+
166
+ ### GitHub Actions Environment
167
+
168
+ - **Actions run in GitHub's infrastructure** - we cannot control the runner environment
169
+ - **Secrets are available** to all steps in a job that has access
170
+ - **Workflow logs are visible** to users with read access to the repository
171
+
172
+ ### npm Publishing (npm-pr-version)
173
+
174
+ - **NPM tokens have broad permissions** - ensure tokens are scoped appropriately
175
+ - **Published packages are public** by default - review package contents
176
+ - **Version immutability** - published versions cannot be unpublished
177
+
178
+ ### Comment Actions
179
+
180
+ - **GitHub tokens can comment** on behalf of the workflow user
181
+ - **Comment content is public** - avoid including sensitive information
182
+ - **Rate limiting applies** - excessive commenting may be throttled
183
+
184
+ ## Incident Response
185
+
186
+ In case of a confirmed security vulnerability:
187
+
188
+ 1. **Assessment** - Evaluate severity and impact
189
+ 2. **Mitigation** - Develop and test fixes
190
+ 3. **Disclosure** - Coordinate with reporter on disclosure timeline
191
+ 4. **Release** - Deploy security fixes
192
+ 5. **Communication** - Notify users through appropriate channels
193
+
194
+ ## Security Contact
195
+
196
+ - **Primary**: [security@codfish.dev](mailto:security@codfish.dev)
197
+ - **GitHub**: [@codfish](https://github.com/codfish)
198
+
199
+ ## Acknowledgments
200
+
201
+ We appreciate security researchers and users who responsibly disclose vulnerabilities. Contributors who report valid
202
+ security issues will be acknowledged (with permission) in:
203
+
204
+ - Security advisories
205
+ - Release notes
206
+ - This security policy
207
+
208
+ Thank you for helping keep this project secure! šŸ”’
@@ -0,0 +1,432 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import yaml from 'js-yaml';
5
+ import path from 'path';
6
+
7
+ /**
8
+ * Generate documentation for all GitHub Actions in the repository
9
+ */
10
+ class DocumentationGenerator {
11
+ constructor() {
12
+ this.rootDir = process.cwd();
13
+ this.actions = [];
14
+ }
15
+
16
+ /**
17
+ * Find all action directories by looking for action.yml files
18
+ */
19
+ findActionDirectories() {
20
+ const entries = fs.readdirSync(this.rootDir, { withFileTypes: true });
21
+
22
+ return entries
23
+ .filter(entry => entry.isDirectory())
24
+ .filter(entry => !entry.name.startsWith('.') && entry.name !== 'node_modules')
25
+ .map(entry => entry.name)
26
+ .filter(dirName => {
27
+ const actionFile = path.join(this.rootDir, dirName, 'action.yml');
28
+ return fs.existsSync(actionFile);
29
+ });
30
+ }
31
+
32
+ /**
33
+ * Parse action.yml file to extract metadata
34
+ */
35
+ parseActionFile(dirName) {
36
+ const actionFile = path.join(this.rootDir, dirName, 'action.yml');
37
+
38
+ try {
39
+ const content = fs.readFileSync(actionFile, 'utf8');
40
+ const actionData = yaml.load(content);
41
+
42
+ return {
43
+ directory: dirName,
44
+ name: actionData.name || dirName,
45
+ description: actionData.description || 'No description available',
46
+ inputs: actionData.inputs || {},
47
+ outputs: actionData.outputs || {},
48
+ rawData: actionData,
49
+ };
50
+ } catch (error) {
51
+ console.error(`Error parsing ${actionFile}:`, error.message);
52
+ return null;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Extract usage example from README.md
58
+ */
59
+ extractUsageExample(dirName) {
60
+ const readmeFile = path.join(this.rootDir, dirName, 'README.md');
61
+
62
+ if (!fs.existsSync(readmeFile)) {
63
+ return null;
64
+ }
65
+
66
+ try {
67
+ const content = fs.readFileSync(readmeFile, 'utf8');
68
+
69
+ // Look for usage examples in various sections
70
+ const patterns = [
71
+ // Look for "## Usage" section with yaml code block
72
+ /## Usage[\s\S]*?```yaml\n([\s\S]*?)\n```/i,
73
+ // Look for any yaml code block with "uses: "
74
+ /```yaml\n([\s\S]*?uses:\s*[.\w/-]+[\s\S]*?)\n```/i,
75
+ // Look for specific action usage
76
+ new RegExp(`\`\`\`yaml\\n([\\s\\S]*?uses:\\s*[^\\n]*${dirName}[\\s\\S]*?)\\n\`\`\``, 'i'),
77
+ ];
78
+
79
+ for (const pattern of patterns) {
80
+ const match = content.match(pattern);
81
+ if (match && match[1]) {
82
+ // Clean up the example and ensure it's properly formatted
83
+ const example = match[1].trim();
84
+
85
+ // If it doesn't start with a step name, add one
86
+ if (!example.match(/^\s*-\s*name:/m) && !example.match(/^\s*-\s*uses:/m)) {
87
+ return `- uses: codfish/actions/${dirName}@v1\n${example.replace(/^/gm, ' ')}`;
88
+ }
89
+
90
+ return example;
91
+ }
92
+ }
93
+
94
+ // Fallback: create a basic example based on inputs
95
+ return this.generateBasicExample(dirName);
96
+ } catch (error) {
97
+ console.error(`Error reading README for ${dirName}:`, error.message);
98
+ return this.generateBasicExample(dirName);
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Generate a basic usage example based on action inputs
104
+ */
105
+ generateBasicExample(dirName, inputs = {}) {
106
+ let example = `- uses: codfish/actions/${dirName}@v1`;
107
+
108
+ const inputKeys = Object.keys(inputs);
109
+ if (inputKeys.length > 0) {
110
+ example += '\n with:';
111
+
112
+ // Add required inputs first
113
+ const requiredInputs = inputKeys.filter(key => inputs[key].required);
114
+ const optionalInputs = inputKeys.filter(key => !inputs[key].required);
115
+
116
+ [...requiredInputs, ...optionalInputs.slice(0, 2)].forEach(key => {
117
+ const input = inputs[key];
118
+ let value = 'value';
119
+
120
+ // Smart defaults based on input name
121
+ if (key.includes('token')) value = '${{ secrets.TOKEN_NAME }}';
122
+ else if (key.includes('version')) value = 'lts/*';
123
+ else if (key.includes('message')) value = 'Your message here';
124
+ else if (key.includes('tag')) value = 'tag-name';
125
+ else if (input.default) value = input.default;
126
+
127
+ example += `\n ${key}: ${value}`;
128
+ });
129
+ }
130
+
131
+ return example;
132
+ }
133
+
134
+ /**
135
+ * Generate markdown table for inputs or outputs
136
+ */
137
+ generateTable(items, type = 'inputs') {
138
+ if (!items || Object.keys(items).length === 0) {
139
+ return `*No ${type}*`;
140
+ }
141
+
142
+ const headers = type === 'inputs' ? '| Input | Description | Required | Default |' : '| Output | Description |';
143
+
144
+ const separator = type === 'inputs' ? '|-------|-------------|----------|---------|' : '|--------|-------------|';
145
+
146
+ let table = `${headers}\n${separator}`;
147
+
148
+ Object.entries(items).forEach(([key, config]) => {
149
+ const description = config.description || 'No description';
150
+
151
+ if (type === 'inputs') {
152
+ const required = config.required ? 'Yes' : 'No';
153
+ const defaultValue = config.default ? `\`${config.default}\` ` : '-';
154
+ table += `\n| \`${key}\` | ${description} | ${required} | ${defaultValue} |`;
155
+ } else {
156
+ table += `\n| \`${key}\` | ${description} |`;
157
+ }
158
+ });
159
+
160
+ return table;
161
+ }
162
+
163
+ /**
164
+ * Generate markdown section for a single action
165
+ */
166
+ generateActionSection(action) {
167
+ const { directory, name, description, inputs, outputs } = action;
168
+ const usageExample = this.extractUsageExample(directory);
169
+
170
+ let section = `### [${name}](./${directory}/)\n\n`;
171
+ section += `${description}\n\n`;
172
+
173
+ // Add inputs table
174
+ section += `**Inputs:**\n\n${this.generateTable(inputs, 'inputs')}\n\n`;
175
+
176
+ // Add outputs table if there are outputs
177
+ if (outputs && Object.keys(outputs).length > 0) {
178
+ section += `**Outputs:**\n\n${this.generateTable(outputs, 'outputs')}\n\n`;
179
+ }
180
+
181
+ // Add usage example
182
+ if (usageExample) {
183
+ section += `**Usage:**\n\n\`\`\`yaml\n${usageExample}\n\`\`\`\n\n`;
184
+ }
185
+
186
+ return section;
187
+ }
188
+
189
+ /**
190
+ * Generate just the content for actions (without the section header)
191
+ */
192
+ generateAvailableActionsContent() {
193
+ const actionDirs = this.findActionDirectories();
194
+
195
+ console.log(`Found ${actionDirs.length} action directories:`, actionDirs);
196
+
197
+ this.actions = actionDirs
198
+ .map(dir => this.parseActionFile(dir))
199
+ .filter(action => action !== null)
200
+ .sort((a, b) => a.name.localeCompare(b.name));
201
+
202
+ let content = '';
203
+
204
+ this.actions.forEach(action => {
205
+ content += this.generateActionSection(action);
206
+ });
207
+
208
+ return content.trim(); // Remove trailing newlines
209
+ }
210
+
211
+ /**
212
+ * Update the main README.md file using file descriptors for security
213
+ */
214
+ updateReadme() {
215
+ const readmePath = path.join(this.rootDir, 'README.md');
216
+
217
+ if (!fs.existsSync(readmePath)) {
218
+ console.error('README.md not found');
219
+ return false;
220
+ }
221
+
222
+ let fd;
223
+ try {
224
+ // Open file descriptor for reading and writing
225
+ fd = fs.openSync(readmePath, 'r+');
226
+
227
+ // Read content using file descriptor
228
+ const stats = fs.fstatSync(fd);
229
+ const buffer = Buffer.alloc(stats.size);
230
+ fs.readSync(fd, buffer, 0, stats.size, 0);
231
+ let content = buffer.toString('utf8');
232
+
233
+ // Find the action docs markers
234
+ const startMarker = '<!-- start action docs -->';
235
+ const endMarker = '<!-- end action docs -->';
236
+
237
+ const startIndex = content.indexOf(startMarker);
238
+ const endIndex = content.indexOf(endMarker);
239
+
240
+ if (startIndex === -1) {
241
+ console.error(`Could not find "${startMarker}" in README.md`);
242
+ console.error('Please add the marker where you want action documentation to be generated');
243
+ return false;
244
+ }
245
+
246
+ if (endIndex === -1) {
247
+ console.error(`Could not find "${endMarker}" in README.md`);
248
+ console.error('Please add the end marker after the start marker');
249
+ return false;
250
+ }
251
+
252
+ if (endIndex <= startIndex) {
253
+ console.error('End marker must come after start marker');
254
+ return false;
255
+ }
256
+
257
+ // Replace content between markers
258
+ const beforeMarker = content.substring(0, startIndex + startMarker.length);
259
+ const afterMarker = content.substring(endIndex);
260
+
261
+ const newContent = this.generateAvailableActionsContent();
262
+ const updatedContent = beforeMarker + '\n' + newContent + '\n' + afterMarker;
263
+
264
+ // Truncate and write back using file descriptor
265
+ fs.ftruncateSync(fd, 0);
266
+ fs.writeSync(fd, updatedContent, 0, 'utf8');
267
+
268
+ console.log('āœ… README.md updated successfully!');
269
+ console.log(`šŸ“ Generated documentation for ${this.actions.length} actions`);
270
+
271
+ return true;
272
+ } catch (error) {
273
+ console.error('Error updating README.md:', error.message);
274
+ return false;
275
+ } finally {
276
+ // Always close the file descriptor
277
+ if (fd !== undefined) {
278
+ try {
279
+ fs.closeSync(fd);
280
+ } catch (closeError) {
281
+ console.error('Error closing README.md file descriptor:', closeError.message);
282
+ }
283
+ }
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Update individual action README files with inputs/outputs using file descriptors for security
289
+ */
290
+ updateActionReadmes() {
291
+ const actionDirs = this.findActionDirectories();
292
+ let updatedCount = 0;
293
+
294
+ actionDirs.forEach(dirName => {
295
+ const readmePath = path.join(this.rootDir, dirName, 'README.md');
296
+
297
+ if (!fs.existsSync(readmePath)) {
298
+ console.log(`āš ļø No README.md found in ${dirName}, skipping`);
299
+ return;
300
+ }
301
+
302
+ const actionData = this.parseActionFile(dirName);
303
+ if (!actionData) {
304
+ console.log(`āš ļø Could not parse action.yml for ${dirName}, skipping`);
305
+ return;
306
+ }
307
+
308
+ let fd;
309
+ try {
310
+ // Open file descriptor for reading and writing
311
+ fd = fs.openSync(readmePath, 'r+');
312
+
313
+ // Read content using file descriptor
314
+ const stats = fs.fstatSync(fd);
315
+ const buffer = Buffer.alloc(stats.size);
316
+ fs.readSync(fd, buffer, 0, stats.size, 0);
317
+ let content = buffer.toString('utf8');
318
+ let modified = false;
319
+
320
+ // Update inputs section
321
+ const inputsStartMarker = '<!-- start inputs -->';
322
+ const inputsEndMarker = '<!-- end inputs -->';
323
+ const inputsStart = content.indexOf(inputsStartMarker);
324
+ const inputsEnd = content.indexOf(inputsEndMarker);
325
+
326
+ if (inputsStart !== -1 && inputsEnd !== -1 && inputsEnd > inputsStart) {
327
+ const inputsTable = this.generateTable(actionData.inputs, 'inputs');
328
+ const beforeInputs = content.substring(0, inputsStart + inputsStartMarker.length);
329
+ const afterInputs = content.substring(inputsEnd);
330
+ content = beforeInputs + '\n\n' + inputsTable + '\n\n' + afterInputs;
331
+ modified = true;
332
+ console.log(`āœ… Updated inputs section in ${dirName}/README.md`);
333
+ }
334
+
335
+ // Update outputs section
336
+ const outputsStartMarker = '<!-- start outputs -->';
337
+ const outputsEndMarker = '<!-- end outputs -->';
338
+ const outputsStart = content.indexOf(outputsStartMarker);
339
+ const outputsEnd = content.indexOf(outputsEndMarker);
340
+
341
+ if (outputsStart !== -1 && outputsEnd !== -1 && outputsEnd > outputsStart) {
342
+ const outputsTable = this.generateTable(actionData.outputs, 'outputs');
343
+ const beforeOutputs = content.substring(0, outputsStart + outputsStartMarker.length);
344
+ const afterOutputs = content.substring(outputsEnd);
345
+ content = beforeOutputs + '\n\n' + outputsTable + '\n\n' + afterOutputs;
346
+ modified = true;
347
+ console.log(`āœ… Updated outputs section in ${dirName}/README.md`);
348
+ }
349
+
350
+ if (modified) {
351
+ // Truncate and write back using file descriptor
352
+ fs.ftruncateSync(fd, 0);
353
+ fs.writeSync(fd, content, 0, 'utf8');
354
+ updatedCount++;
355
+ }
356
+ } catch (error) {
357
+ console.error(`Error updating ${dirName}/README.md:`, error.message);
358
+ } finally {
359
+ // Always close the file descriptor
360
+ if (fd !== undefined) {
361
+ try {
362
+ fs.closeSync(fd);
363
+ } catch (closeError) {
364
+ console.error(`Error closing ${dirName}/README.md file descriptor:`, closeError.message);
365
+ }
366
+ }
367
+ }
368
+ });
369
+
370
+ return updatedCount;
371
+ }
372
+
373
+ /**
374
+ * Run prettier formatting on all documentation files
375
+ */
376
+ async formatDocs() {
377
+ const { execSync } = await import('child_process');
378
+
379
+ try {
380
+ console.log('\nšŸŽØ Formatting documentation with prettier...');
381
+ execSync('pnpm format', {
382
+ stdio: 'inherit',
383
+ cwd: this.rootDir,
384
+ });
385
+ console.log('āœ… Documentation formatting complete!');
386
+ return true;
387
+ } catch (error) {
388
+ console.error('āŒ Prettier formatting failed:', error.message);
389
+ return false;
390
+ }
391
+ }
392
+
393
+ /**
394
+ * Run the documentation generation
395
+ */
396
+ async run() {
397
+ console.log('šŸ” Scanning for GitHub Actions...');
398
+
399
+ // Update main README
400
+ const mainSuccess = this.updateReadme();
401
+
402
+ // Update individual action READMEs
403
+ console.log('\nšŸ” Updating individual action README files...');
404
+ const updatedActionCount = this.updateActionReadmes();
405
+
406
+ if (mainSuccess) {
407
+ console.log(`\nšŸ“š Documentation generation complete!`);
408
+ console.log(`šŸ“ Updated main README.md with ${this.actions.length} actions`);
409
+ if (updatedActionCount > 0) {
410
+ console.log(`šŸ“ Updated ${updatedActionCount} action README files`);
411
+ }
412
+
413
+ // Format the documentation
414
+ const formatSuccess = await this.formatDocs();
415
+
416
+ if (formatSuccess) {
417
+ console.log('\nšŸŽ‰ All documentation updated and formatted successfully!');
418
+ console.log('Run `git diff` to see all changes.');
419
+ } else {
420
+ console.log('\nāš ļø Documentation updated but formatting failed.');
421
+ console.log('You may want to run `pnpm format` manually.');
422
+ }
423
+ } else {
424
+ console.error('\nāŒ Main README documentation generation failed!');
425
+ process.exit(1);
426
+ }
427
+ }
428
+ }
429
+
430
+ // Run the generator
431
+ const generator = new DocumentationGenerator();
432
+ generator.run();