@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
@@ -0,0 +1,82 @@
1
+ # comment
2
+
3
+ Creates or updates pull request comments with intelligent upsert functionality using unique tags.
4
+
5
+ <!-- DOCTOC SKIP -->
6
+
7
+ ## Usage
8
+
9
+ See [action.yml](action.yml).
10
+
11
+ ```yaml
12
+ - name: Comment on PR
13
+ uses: codfish/actions/comment@v1
14
+ with:
15
+ message: '✅ Build successful!'
16
+ tag: 'build-status'
17
+ upsert: true
18
+ ```
19
+
20
+ ## Inputs
21
+
22
+ <!-- start inputs -->
23
+
24
+ | Input | Description | Required | Default |
25
+ | --------- | ------------------------------------------------------------------------------------- | -------- | ------- |
26
+ | `message` | The comment message content (supports markdown formatting) | Yes | - |
27
+ | `tag` | Unique identifier to find and update existing comments (required when upsert is true) | No | - |
28
+ | `upsert` | Update existing comment with matching tag instead of creating new comment | No | `false` |
29
+
30
+ <!-- end inputs -->
31
+
32
+ ## Examples
33
+
34
+ ### Basic comment
35
+
36
+ ```yaml
37
+ - uses: codfish/actions/comment@v1
38
+ with:
39
+ message: 'Hello from GitHub Actions! 👋'
40
+ ```
41
+
42
+ ### Updating comments with upsert
43
+
44
+ Use the `upsert` feature to update the same comment instead of creating multiple comments:
45
+
46
+ ```yaml
47
+ - name: Update build status
48
+ uses: codfish/actions/comment@v1
49
+ with:
50
+ message: |
51
+ ## Build Status
52
+ ⏳ Build in progress...
53
+ tag: 'build-status'
54
+ upsert: true
55
+
56
+ # Later in the workflow...
57
+ - name: Update build status
58
+ uses: codfish/actions/comment@v1
59
+ with:
60
+ message: |
61
+ ## Build Status
62
+ ✅ Build completed successfully!
63
+ tag: 'build-status'
64
+ upsert: true
65
+ ```
66
+
67
+ ### Multi-line markdown comment
68
+
69
+ ```yaml
70
+ - uses: codfish/actions/comment@v1
71
+ with:
72
+ message: |
73
+ ## 📊 Test Results
74
+
75
+ - ✅ Unit tests: 42 passed
76
+ - ✅ Integration tests: 12 passed
77
+ - 📦 Coverage: 98%
78
+
79
+ Great work! 🎉
80
+ tag: 'test-results'
81
+ upsert: true
82
+ ```
@@ -0,0 +1,102 @@
1
+ name: comment
2
+
3
+ description: Creates or updates a comment in a pull request with optional tagging for upsert functionality
4
+
5
+ inputs:
6
+ message:
7
+ description: The comment message content (supports markdown formatting)
8
+ required: true
9
+ tag:
10
+ description: Unique identifier to find and update existing comments (required when upsert is true)
11
+ required: false
12
+ upsert:
13
+ description: Update existing comment with matching tag instead of creating new comment
14
+ required: false
15
+ default: 'false'
16
+
17
+ runs:
18
+ using: composite
19
+
20
+ steps:
21
+ - name: Validate inputs and set globals
22
+ id: globals
23
+ shell: bash
24
+ run: |
25
+ # Validate required inputs
26
+ if [ -z "${{ inputs.message }}" ]; then
27
+ echo "❌ ERROR: 'message' input is required"
28
+ exit 1
29
+ fi
30
+
31
+ # Validate upsert logic
32
+ if [ "${{ inputs.upsert }}" = "true" ] && [ -z "${{ inputs.tag }}" ]; then
33
+ echo "❌ ERROR: 'tag' input is required when upsert is true"
34
+ exit 1
35
+ fi
36
+
37
+ # Create dynamic tag based on repository name
38
+ repo_namespace="${{ github.repository }}"
39
+ tag="<!-- ${repo_namespace}/comment ${{ inputs.tag }} -->"
40
+ body=$(printf '${{ inputs.message }}')
41
+
42
+ echo "💬 Comment tag: $tag"
43
+ echo "tag=$tag" >> $GITHUB_OUTPUT
44
+ echo "body<<EOF"$'\n'"$body"'\n'"$tag"$'\n'EOF >> "$GITHUB_OUTPUT"
45
+
46
+ - name: Check existing comments
47
+ id: check-comments
48
+ if: inputs.upsert == 'true'
49
+ uses: actions/github-script@v7
50
+ with:
51
+ script: |
52
+ try {
53
+ const comments = await github.rest.issues.listComments({
54
+ owner: context.repo.owner,
55
+ repo: context.repo.repo,
56
+ issue_number: context.issue.number,
57
+ });
58
+ const existingComment = comments.data.find(comment => comment.body.includes('${{ steps.globals.outputs.tag }}'));
59
+ core.setOutput('comment-id', existingComment ? existingComment.id : null);
60
+
61
+ if (existingComment) {
62
+ console.log(`Found existing comment with ID: ${existingComment.id}`);
63
+ } else {
64
+ console.log('No existing comment found, will create new one');
65
+ }
66
+ } catch (error) {
67
+ core.setFailed(`Failed to check existing comments: ${error.message}`);
68
+ }
69
+
70
+ - name: Update existing comment
71
+ if: steps.check-comments.outputs.comment-id != null
72
+ uses: actions/github-script@v7
73
+ with:
74
+ script: |
75
+ try {
76
+ await github.rest.issues.updateComment({
77
+ owner: context.repo.owner,
78
+ repo: context.repo.repo,
79
+ comment_id: ${{ steps.check-comments.outputs.comment-id }},
80
+ body: `${{ steps.globals.outputs.body }}`,
81
+ });
82
+ console.log('✅ Successfully updated existing comment');
83
+ } catch (error) {
84
+ core.setFailed(`Failed to update comment: ${error.message}`);
85
+ }
86
+
87
+ - name: Create new comment
88
+ if: steps.check-comments.outputs.comment-id == null
89
+ uses: actions/github-script@v7
90
+ with:
91
+ script: |
92
+ try {
93
+ const response = await github.rest.issues.createComment({
94
+ owner: context.repo.owner,
95
+ repo: context.repo.repo,
96
+ issue_number: context.issue.number,
97
+ body: `${{ steps.globals.outputs.body }}`,
98
+ });
99
+ console.log(`✅ Successfully created new comment with ID: ${response.data.id}`);
100
+ } catch (error) {
101
+ core.setFailed(`Failed to create comment: ${error.message}`);
102
+ }
@@ -0,0 +1,8 @@
1
+ import codfish from '@codfish/eslint-config';
2
+ import { defineConfig } from 'eslint/config';
3
+
4
+ export default defineConfig(codfish, {
5
+ rules: {
6
+ 'no-console': 'off',
7
+ },
8
+ });
@@ -0,0 +1,145 @@
1
+ # npm-pr-version
2
+
3
+ Publishes packages with PR-specific version numbers for testing in downstream applications before merging. Automatically
4
+ detects your package manager (npm, yarn, or pnpm) and uses the appropriate publish command. The action generates
5
+ versions in the format `0.0.0-PR-{number}--{short-sha}` and automatically comments on the pull request with the
6
+ published version.
7
+
8
+ **Key Features:**
9
+
10
+ - Automatic package manager detection (npm/yarn/pnpm)
11
+ - Automatic PR version generation
12
+ - Publishes to registry with `pr` tag
13
+ - Automatic PR commenting with version info
14
+ - No git history modification
15
+
16
+ <!-- DOCTOC SKIP -->
17
+
18
+ ## Usage
19
+
20
+ See [action.yml](action.yml).
21
+
22
+ ```yaml
23
+ steps:
24
+ - uses: actions/checkout@v5
25
+
26
+ - uses: codfish/actions/setup-node-and-install@v1
27
+ with:
28
+ node-version: lts/*
29
+
30
+ - run: npm run build
31
+
32
+ - uses: codfish/actions/npm-pr-version@v1
33
+ with:
34
+ npm-token: ${{ secrets.NPM_TOKEN }}
35
+ github-token: ${{ secrets.GITHUB_TOKEN }}
36
+ ```
37
+
38
+ ### Disable PR Comments
39
+
40
+ ```yaml
41
+ - uses: codfish/actions/npm-pr-version@v1
42
+ with:
43
+ npm-token: ${{ secrets.NPM_TOKEN }}
44
+ github-token: ${{ secrets.GITHUB_TOKEN }}
45
+ comment: false
46
+ ```
47
+
48
+ ### Custom Comment Tag
49
+
50
+ ```yaml
51
+ - uses: codfish/actions/npm-pr-version@v1
52
+ with:
53
+ npm-token: ${{ secrets.NPM_TOKEN }}
54
+ github-token: ${{ secrets.GITHUB_TOKEN }}
55
+ comment-tag: my-custom-tag
56
+ ```
57
+
58
+ ## Complete Workflow Example
59
+
60
+ ```yaml
61
+ name: PR Package Testing
62
+
63
+ on: pull_request_target
64
+
65
+ permissions:
66
+ contents: write
67
+ pull-requests: write
68
+
69
+ jobs:
70
+ publish-pr-package:
71
+ runs-on: ubuntu-latest
72
+ steps:
73
+ - uses: actions/checkout@v5
74
+
75
+ - uses: codfish/actions/setup-node-and-install@v1
76
+ with:
77
+ node-version: 'lts/*'
78
+
79
+ - name: Build package
80
+ run: npm run build
81
+
82
+ - name: Publish PR package
83
+ uses: codfish/actions/npm-pr-version@v1
84
+ with:
85
+ npm-token: ${{ secrets.NPM_TOKEN }}
86
+ github-token: ${{ secrets.GITHUB_TOKEN }}
87
+ ```
88
+
89
+ ## Testing Downstream
90
+
91
+ After the action runs, you can install the PR version in downstream projects:
92
+
93
+ ```bash
94
+ npm install my-package@0.0.0-PR-123--abc1234
95
+ ```
96
+
97
+ The package is published under the `pr` tag, so it won't interfere with your regular releases.
98
+
99
+ ## Inputs
100
+
101
+ <!-- start inputs -->
102
+
103
+ | Input | Description | Required | Default |
104
+ | -------------- | ----------------------------------------------------------------------------------- | -------- | ---------------- |
105
+ | `npm-token` | Registry authentication token with publish permissions (works with npm/yarn/pnpm) | No | - |
106
+ | `github-token` | GitHub token with pull request comment permissions (typically secrets.GITHUB_TOKEN) | Yes | - |
107
+ | `comment` | Whether to comment on the PR with the published version (true/false) | No | `true` |
108
+ | `comment-tag` | Tag to use for PR comments (for comment identification and updates) | No | `npm-publish-pr` |
109
+
110
+ <!-- end inputs -->
111
+
112
+ ## Package Manager Support
113
+
114
+ The action automatically detects your package manager and uses the appropriate publish command:
115
+
116
+ - **npm**: Uses `npm publish --access public --tag pr`
117
+ - **yarn**: Uses `yarn publish --access public --tag pr --new-version {version} --no-git-tag-version`
118
+ - **pnpm**: Uses `pnpm publish --access public --tag pr`
119
+
120
+ Detection is based on lockfile presence:
121
+
122
+ - `yarn.lock` → yarn
123
+ - `pnpm-lock.yaml` → pnpm
124
+ - `package-lock.json` or no lockfile → npm
125
+
126
+ ## Outputs
127
+
128
+ <!-- start outputs -->
129
+
130
+ | Output | Description |
131
+ | --------------- | --------------------------------------------------------------------- |
132
+ | `version` | Generated PR-specific version number (0.0.0-PR-{number}--{short-sha}) |
133
+ | `package-name` | Package name from package.json |
134
+ | `error-message` | Error message if publish fails |
135
+
136
+ <!-- end outputs -->
137
+
138
+ ## Version Format
139
+
140
+ Published versions follow the pattern: `0.0.0-PR-{pr-number}--{short-sha}`
141
+
142
+ Examples:
143
+
144
+ - `0.0.0-PR-123--abc1234` (PR #123, commit abc1234)
145
+ - `0.0.0-PR-456--def5678` (PR #456, commit def5678)
@@ -0,0 +1,171 @@
1
+ name: npm-pr-version
2
+
3
+ description:
4
+ Publishes package with PR-specific version (0.0.0-PR-123--abc1234) using detected package manager (npm/yarn/pnpm) and
5
+ automatically comments on PR
6
+
7
+ inputs:
8
+ npm-token:
9
+ required: false
10
+ description: Registry authentication token with publish permissions (works with npm/yarn/pnpm)
11
+ github-token:
12
+ required: true
13
+ description: GitHub token with pull request comment permissions (typically secrets.GITHUB_TOKEN)
14
+ comment:
15
+ required: false
16
+ default: 'true'
17
+ description: Whether to comment on the PR with the published version (true/false)
18
+ comment-tag:
19
+ required: false
20
+ default: npm-publish-pr
21
+ description: Tag to use for PR comments (for comment identification and updates)
22
+
23
+ outputs:
24
+ version:
25
+ description: Generated PR-specific version number (0.0.0-PR-{number}--{short-sha})
26
+ value: '${{ steps.publish.outputs.version }}'
27
+ package-name:
28
+ description: Package name from package.json
29
+ value: '${{ steps.publish.outputs.package-name }}'
30
+ error-message:
31
+ description: Error message if publish fails
32
+ value: '${{ steps.publish.outputs.error-message }}'
33
+
34
+ runs:
35
+ using: composite
36
+
37
+ steps:
38
+ - uses: codfish/actions/comment@v1
39
+ if: inputs.comment == 'true'
40
+ with:
41
+ message: ⏳ Publishing PR version...
42
+ upsert: true
43
+ tag: ${{ inputs.comment-tag }}
44
+
45
+ - name: Validate and publish to registry
46
+ id: publish
47
+ shell: bash
48
+ run: |
49
+ set +e # Don't exit on error so we can handle failures
50
+
51
+ # Initialize outputs for error handling
52
+ error_message=""
53
+ package_name=""
54
+ version=""
55
+
56
+ # Validate package.json exists
57
+ if [ ! -f "package.json" ]; then
58
+ error_message="❌ ERROR: package.json not found in current directory. Make sure you're running this action in a directory with a package.json file"
59
+ echo "$error_message"
60
+ echo "error-message=$error_message" >> $GITHUB_OUTPUT
61
+ exit 1
62
+ fi
63
+
64
+ # Validate package.json is valid JSON
65
+ if ! jq empty package.json 2>/dev/null; then
66
+ error_message="❌ ERROR: package.json is not valid JSON"
67
+ echo "$error_message"
68
+ echo "error-message=$error_message" >> $GITHUB_OUTPUT
69
+ exit 1
70
+ fi
71
+
72
+ # Check if package has a name
73
+ package_name=$(jq -r '.name // empty' package.json)
74
+ if [ -z "$package_name" ] || [ "$package_name" = "null" ]; then
75
+ error_message="❌ ERROR: package.json must have a 'name' field"
76
+ echo "$error_message"
77
+ echo "error-message=$error_message" >> $GITHUB_OUTPUT
78
+ exit 1
79
+ fi
80
+
81
+ # Output package name for use in error handling
82
+ echo "package-name=$package_name" >> $GITHUB_OUTPUT
83
+
84
+ # Detect package manager
85
+ if [ -f "./yarn.lock" ]; then
86
+ package_manager="yarn"
87
+ echo "📦 Detected package manager: yarn"
88
+ elif [ -f "./pnpm-lock.yaml" ]; then
89
+ package_manager="pnpm"
90
+ echo "📦 Detected package manager: pnpm"
91
+ else
92
+ package_manager="npm"
93
+ echo "📦 Detected package manager: npm"
94
+ fi
95
+
96
+ # Generate version
97
+ version="0.0.0-PR-${PR}--$(echo ${SHA} | cut -c -7)"
98
+ echo "📦 Publishing $package_name@$version with $package_manager"
99
+ echo "version=$version" >> $GITHUB_OUTPUT
100
+
101
+ # Update package.json version (all package managers support npm version)
102
+ version_output=$(npm version $version --no-git-tag-version 2>&1)
103
+ version_exit_code=$?
104
+ if [ $version_exit_code -ne 0 ]; then
105
+ error_message="❌ ERROR: Failed to update package version. Check if the version format is valid. Error: $version_output"
106
+ echo "$error_message"
107
+ echo "error-message=$error_message" >> $GITHUB_OUTPUT
108
+ exit 1
109
+ fi
110
+
111
+ # Publish package based on detected package manager
112
+ case "$package_manager" in
113
+ "yarn")
114
+ publish_output=$(yarn publish --access public --tag pr --new-version $version --no-git-tag-version 2>&1)
115
+ publish_exit_code=$?
116
+ if [ $publish_exit_code -ne 0 ]; then
117
+ error_message="❌ ERROR: Failed to publish package with yarn. Error: $publish_output"
118
+ echo "$error_message"
119
+ echo "error-message=$error_message" >> $GITHUB_OUTPUT
120
+ exit 1
121
+ fi
122
+ ;;
123
+ "pnpm")
124
+ publish_output=$(pnpm publish --access public --tag pr 2>&1)
125
+ publish_exit_code=$?
126
+ if [ $publish_exit_code -ne 0 ]; then
127
+ error_message="❌ ERROR: Failed to publish package with pnpm. Error: $publish_output"
128
+ echo "$error_message"
129
+ echo "error-message=$error_message" >> $GITHUB_OUTPUT
130
+ exit 1
131
+ fi
132
+ ;;
133
+ *)
134
+ publish_output=$(npm publish --access public --tag pr 2>&1)
135
+ publish_exit_code=$?
136
+ if [ $publish_exit_code -ne 0 ]; then
137
+ error_message="❌ ERROR: Failed to publish package with npm. Error: $publish_output"
138
+ echo "$error_message"
139
+ echo "error-message=$error_message" >> $GITHUB_OUTPUT
140
+ exit 1
141
+ fi
142
+ ;;
143
+ esac
144
+
145
+ echo "✅ Successfully published $package_name@$version using $package_manager"
146
+ env:
147
+ NODE_AUTH_TOKEN: ${{ inputs.npm-token }}
148
+ PR: ${{ github.event.number }}
149
+ SHA: ${{ github.event.pull_request.head.sha }}
150
+
151
+ - uses: codfish/actions/comment@v1
152
+ if: failure() && inputs.comment == 'true'
153
+ with:
154
+ message: |
155
+ ❌ **PR package publish failed!**
156
+
157
+ Error: ${{ steps.publish.outputs.error-message }}
158
+
159
+ Please check the workflow logs for more details.
160
+ upsert: true
161
+ tag: ${{ inputs.comment-tag }}
162
+
163
+ - uses: codfish/actions/comment@v1
164
+ if: success() && inputs.comment == 'true'
165
+ with:
166
+ message: |
167
+ ✅ **PR package published successfully!**
168
+
169
+ Install with: `npm install ${{ steps.publish.outputs.package-name }}@${{ steps.publish.outputs.version }}`
170
+ upsert: true
171
+ tag: ${{ inputs.comment-tag }}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@codfish/actions",
3
+ "type": "module",
4
+ "description": "Composite GitHub Actions for my projects.",
5
+ "author": "Chris O'Donnell <chris@codfish.dev>",
6
+ "license": "MIT",
7
+ "version": "1.1.0",
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "engines": {
12
+ "node": ">=20"
13
+ },
14
+ "volta": {
15
+ "node": "24.8.0"
16
+ },
17
+ "scripts": {
18
+ "lint": "eslint .",
19
+ "fix": "eslint . --fix",
20
+ "format": "prettier --write \"**/*.{json,css,md}\" --config ./node_modules/@codfish/eslint-config/prettier.js",
21
+ "test": "bash tests/scripts/test-runner.sh",
22
+ "test:integration": "bash tests/scripts/test-runner.sh integration",
23
+ "test:unit": "bash tests/scripts/test-runner.sh unit",
24
+ "docs:generate": "node bin/generate-docs.js",
25
+ "prepare": "husky"
26
+ },
27
+ "devDependencies": {
28
+ "@codfish/eslint-config": "^12.1.1",
29
+ "bats": "^1.10.0",
30
+ "doctoc": "^2.2.1",
31
+ "eslint": "^9.36.0",
32
+ "husky": "^9.1.7",
33
+ "js-yaml": "^4.1.0",
34
+ "lint-staged": "^16.2.0",
35
+ "prettier": "^3.6.2"
36
+ },
37
+ "packageManager": "pnpm@10.17.1",
38
+ "commitlint": {
39
+ "extends": [
40
+ "./node_modules/@codfish/eslint-config/commitlint.js"
41
+ ]
42
+ },
43
+ "lint-staged": {
44
+ "*.{json,css}": [
45
+ "prettier --write --config ./node_modules/@codfish/eslint-config/prettier.js"
46
+ ],
47
+ "*.md": [
48
+ "prettier --write --config ./node_modules/@codfish/eslint-config/prettier.js",
49
+ "doctoc --title '## Table of Contents'"
50
+ ]
51
+ }
52
+ }
@@ -0,0 +1,139 @@
1
+ # setup-node-and-install
2
+
3
+ Sets up Node.js environment and installs dependencies with automatic package manager detection, intelligent caching, and
4
+ .nvmrc/.node-version support.
5
+
6
+ This action provides the following functionality:
7
+
8
+ - Automatically detects package manager (npm, yarn, or pnpm) from lockfiles
9
+ - Uses GitHub's official `setup-node` action with optimized caching
10
+ - Installs dependencies with appropriate commands based on detected package manager
11
+ - Supports .nvmrc and .node-version files for version specification
12
+ - Intelligent caching of node_modules when lockfiles are present
13
+
14
+ <!-- DOCTOC SKIP -->
15
+
16
+ ## Usage
17
+
18
+ See [action.yml](action.yml).
19
+
20
+ ```yaml
21
+ steps:
22
+ - uses: actions/checkout@v5
23
+
24
+ # will install latest Node v18.x
25
+ - uses: codfish/actions/setup-node-and-install@v1
26
+ with:
27
+ node-version: 18
28
+ cache-key-suffix: '-${{ github.head_ref || github.event.release.tag_name }}'
29
+
30
+ - run: npm test
31
+ ```
32
+
33
+ The `node-version` input is optional. If not supplied, this action will attempt to use an `.nvmrc` file in your project.
34
+ If neither is supplied, it will fail your workflow.
35
+
36
+ The `cache-key-suffix` input is optional. If not supplied, no suffix will be applied to the cache key used to restore
37
+ cache in subsequent workflow runs.
38
+
39
+ The `install-options` input is optional. If not supplied, the npm install commands will execute as defined without any
40
+ additional options.
41
+
42
+ **With `.nvmrc` file**
43
+
44
+ ```sh
45
+ # .nvmrc
46
+ v18.14.1
47
+ ```
48
+
49
+ ```yaml
50
+ steps:
51
+ - uses: actions/checkout@v5
52
+ # will install Node v18.14.1
53
+ - uses: codfish/actions/setup-node-and-install@v1
54
+ - run: npm test
55
+ ```
56
+
57
+ **With `.node-version` file**
58
+
59
+ ```sh
60
+ # .node-version
61
+ 20.10.0
62
+ ```
63
+
64
+ ```yaml
65
+ steps:
66
+ - uses: actions/checkout@v5
67
+ # will install Node v20.10.0
68
+ - uses: codfish/actions/setup-node-and-install@v1
69
+ - run: npm test
70
+ ```
71
+
72
+ ## Node Version File Priority
73
+
74
+ When multiple version specification methods are present, the action uses this priority order:
75
+
76
+ 1. **Input parameter** (`node-version`) - highest priority
77
+ 2. **`.nvmrc` file** - takes precedence over .node-version
78
+ 3. **`.node-version` file** - used if no .nvmrc exists
79
+ 4. **Error** - if none of the above are present
80
+
81
+ ## Inputs
82
+
83
+ <!-- start inputs -->
84
+
85
+ | Input | Description | Required | Default |
86
+ | ------------------- | ----------------------------------------------------------------------------------------------------- | -------- | ------- |
87
+ | `node-version` | Node.js version to install (e.g. '24', 'lts/\*'). Defaults to .nvmrc or .node-version file if present | No | - |
88
+ | `cache-key-suffix` | Additional suffix for cache key to enable multiple caches per workflow | No | - |
89
+ | `install-options` | Extra command-line options to pass to npm/pnpm/yarn install | No | - |
90
+ | `working-directory` | Directory containing package.json and lockfile | No | `.` |
91
+
92
+ <!-- end inputs -->
93
+
94
+ ## Package Manager Detection
95
+
96
+ The action automatically detects your package manager:
97
+
98
+ - **pnpm**: Detected when `pnpm-lock.yaml` exists
99
+ - **npm**: Detected when `package-lock.json` exists or as fallback
100
+
101
+ ## Examples
102
+
103
+ ### With specific Node version
104
+
105
+ ```yaml
106
+ - uses: codfish/actions/setup-node-and-install@v1
107
+ with:
108
+ node-version: '18'
109
+ ```
110
+
111
+ ### With pnpm in subdirectory
112
+
113
+ ```yaml
114
+ - uses: codfish/actions/setup-node-and-install@v1
115
+ with:
116
+ working-directory: './frontend'
117
+ install-options: '--frozen-lockfile'
118
+ ```
119
+
120
+ ### With custom cache key
121
+
122
+ ```yaml
123
+ - uses: codfish/actions/setup-node-and-install@v1
124
+ with:
125
+ cache-key-suffix: '-${{ github.head_ref }}'
126
+ ```
127
+
128
+ ## Migrating
129
+
130
+ Replace multiple setup steps with this single action:
131
+
132
+ ```diff
133
+ - - uses: actions/setup-node@v4
134
+ - with:
135
+ - node-version-file: '.nvmrc'
136
+ - cache: 'npm'
137
+ - - run: npm ci --prefer-offline --no-audit
138
+ + - uses: codfish/actions/setup-node-and-install@v1
139
+ ```