@codfish/actions 1.1.0 → 2.0.1
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.
- package/.github/workflows/claude.yml +0 -1
- package/AGENT.md +21 -1
- package/README.md +22 -17
- package/SECURITY.md +2 -2
- package/bin/generate-docs.js +2 -2
- package/comment/README.md +5 -5
- package/npm-publish-pr/README.md +8 -8
- package/npm-publish-pr/action.yml +22 -15
- package/package.json +1 -1
- package/setup-node-and-install/README.md +23 -21
- package/setup-node-and-install/action.yml +98 -123
- package/tests/integration/comment/basic.bats +11 -11
- package/tests/integration/npm-pr-version/basic.bats +131 -46
- package/tests/integration/setup-node-and-install/basic.bats +471 -33
|
@@ -2,39 +2,35 @@ name: setup-node-and-install
|
|
|
2
2
|
|
|
3
3
|
description:
|
|
4
4
|
Sets up Node.js environment and installs dependencies with automatic package manager detection (npm/pnpm/yarn),
|
|
5
|
-
intelligent caching, and .
|
|
5
|
+
intelligent caching, and version detection via input, .node-version, .nvmrc, or package.json volta.node
|
|
6
6
|
|
|
7
7
|
inputs:
|
|
8
8
|
node-version:
|
|
9
|
-
description:
|
|
9
|
+
description:
|
|
10
|
+
'Node.js version to install (e.g. "24", "lts/*"). Precedence: node-version input > .node-version > .nvmrc >
|
|
11
|
+
package.json volta.node.'
|
|
10
12
|
required: false
|
|
11
|
-
cache-key-suffix:
|
|
12
|
-
description: Additional suffix for cache key to enable multiple caches per workflow
|
|
13
|
-
default: ''
|
|
14
13
|
install-options:
|
|
15
|
-
description: Extra command-line options to pass to npm/pnpm/yarn install
|
|
14
|
+
description: Extra command-line options to pass to npm/pnpm/yarn install.
|
|
16
15
|
default: ''
|
|
17
16
|
working-directory:
|
|
18
|
-
description: Directory containing package.json and lockfile
|
|
17
|
+
description: Directory containing package.json and lockfile.
|
|
19
18
|
default: .
|
|
20
19
|
|
|
21
|
-
outputs:
|
|
20
|
+
outputs:
|
|
21
|
+
cache-hit:
|
|
22
|
+
description: Whether the dependency cache was hit (true/false).
|
|
23
|
+
value: "${{ steps.setup-node.outputs.cache-hit == 'true' && 'true' || 'false' }}"
|
|
22
24
|
|
|
23
25
|
runs:
|
|
24
26
|
using: composite
|
|
25
27
|
|
|
26
28
|
steps:
|
|
27
|
-
- name: Validate
|
|
28
|
-
id:
|
|
29
|
+
- name: Validate environment and detect package manager
|
|
30
|
+
id: detect-package-manager
|
|
29
31
|
working-directory: ${{ inputs.working-directory }}
|
|
30
32
|
shell: bash
|
|
31
33
|
run: |
|
|
32
|
-
# Validate working directory exists
|
|
33
|
-
if [ ! -d "." ]; then
|
|
34
|
-
echo "❌ ERROR: Working directory '${{ inputs.working-directory }}' does not exist"
|
|
35
|
-
exit 1
|
|
36
|
-
fi
|
|
37
|
-
|
|
38
34
|
# Validate package.json exists
|
|
39
35
|
if [ ! -f "./package.json" ]; then
|
|
40
36
|
echo "❌ ERROR: package.json not found in '${{ inputs.working-directory }}'"
|
|
@@ -42,48 +38,6 @@ runs:
|
|
|
42
38
|
exit 1
|
|
43
39
|
fi
|
|
44
40
|
|
|
45
|
-
# Validate package.json is valid JSON
|
|
46
|
-
if ! jq empty package.json 2>/dev/null; then
|
|
47
|
-
echo "❌ ERROR: package.json is not valid JSON"
|
|
48
|
-
exit 1
|
|
49
|
-
fi
|
|
50
|
-
|
|
51
|
-
# Check Node version requirements (.nvmrc, .node-version, or input)
|
|
52
|
-
if [[ ! -f "./.nvmrc" && ! -f "./.node-version" && -z "$INPUT_NODE_VERSION" ]]; then
|
|
53
|
-
echo "node-version-missing=true" >> $GITHUB_OUTPUT
|
|
54
|
-
exit
|
|
55
|
-
fi
|
|
56
|
-
|
|
57
|
-
# Validate .nvmrc format if it exists
|
|
58
|
-
if [ -f "./.nvmrc" ]; then
|
|
59
|
-
nvmrc_version=$(cat .nvmrc | tr -d '\n\r' | xargs)
|
|
60
|
-
if [ -z "$nvmrc_version" ]; then
|
|
61
|
-
echo "❌ ERROR: .nvmrc file is empty"
|
|
62
|
-
exit 1
|
|
63
|
-
fi
|
|
64
|
-
echo "📋 Found .nvmrc with Node version: $nvmrc_version"
|
|
65
|
-
fi
|
|
66
|
-
|
|
67
|
-
# Validate .node-version format if it exists (and no .nvmrc)
|
|
68
|
-
if [ -f "./.node-version" ] && [ ! -f "./.nvmrc" ]; then
|
|
69
|
-
node_version_file=$(cat .node-version | tr -d '\n\r' | xargs)
|
|
70
|
-
if [ -z "$node_version_file" ]; then
|
|
71
|
-
echo "❌ ERROR: .node-version file is empty"
|
|
72
|
-
exit 1
|
|
73
|
-
fi
|
|
74
|
-
echo "📋 Found .node-version with Node version: $node_version_file"
|
|
75
|
-
fi
|
|
76
|
-
|
|
77
|
-
# Show priority info if both files exist
|
|
78
|
-
if [ -f "./.nvmrc" ] && [ -f "./.node-version" ]; then
|
|
79
|
-
echo "📋 Both .nvmrc and .node-version found, .nvmrc takes priority"
|
|
80
|
-
fi
|
|
81
|
-
|
|
82
|
-
# Validate node-version input format if provided
|
|
83
|
-
if [ -n "$INPUT_NODE_VERSION" ]; then
|
|
84
|
-
echo "📋 Using Node version from input: $INPUT_NODE_VERSION"
|
|
85
|
-
fi
|
|
86
|
-
|
|
87
41
|
# Detect package manager based on lockfiles (including yarn)
|
|
88
42
|
if [ -f "./pnpm-lock.yaml" ]; then
|
|
89
43
|
echo "package-manager=pnpm" >> $GITHUB_OUTPUT
|
|
@@ -106,115 +60,136 @@ runs:
|
|
|
106
60
|
echo "lockfile-path=" >> $GITHUB_OUTPUT
|
|
107
61
|
echo "📦 No lockfile found, defaulting to: npm"
|
|
108
62
|
fi
|
|
109
|
-
env:
|
|
110
|
-
INPUT_NODE_VERSION: ${{ inputs.node-version }}
|
|
111
|
-
|
|
112
|
-
- if: steps.setup.outputs.node-version-missing == 'true'
|
|
113
|
-
uses: actions/github-script@v7
|
|
114
|
-
with:
|
|
115
|
-
script: |
|
|
116
|
-
core.setFailed('You need to create an .nvmrc file, .node-version file, or pass a value in the `node-version` input.')
|
|
117
63
|
|
|
118
64
|
- name: Install pnpm
|
|
119
|
-
if: steps.
|
|
65
|
+
if: steps.detect-package-manager.outputs.package-manager == 'pnpm'
|
|
120
66
|
uses: pnpm/action-setup@v4
|
|
121
67
|
with:
|
|
122
68
|
run_install: false
|
|
123
69
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
id: setup-node
|
|
128
|
-
with:
|
|
129
|
-
# use detected package manager cache if a lockfile is present
|
|
130
|
-
cache: ${{ steps.setup.outputs.lockfile-exists == 'true' && steps.setup.outputs.package-manager || '' }}
|
|
131
|
-
# supplying a node-version input will override the .nvmrc file and give a warning, but that's to be expected.
|
|
132
|
-
cache-dependency-path: ${{ inputs.working-directory }}
|
|
133
|
-
node-version: ${{ inputs.node-version }}
|
|
134
|
-
node-version-file: '${{ inputs.working-directory }}/.nvmrc'
|
|
135
|
-
registry-url: 'https://registry.npmjs.org'
|
|
136
|
-
|
|
137
|
-
- name: Read .node-version file
|
|
138
|
-
if:
|
|
139
|
-
${{ inputs.node-version == '' && hashFiles(format('{0}/.nvmrc', inputs.working-directory)) == '' &&
|
|
140
|
-
hashFiles(format('{0}/.node-version', inputs.working-directory)) != '' }}
|
|
141
|
-
id: read-node-version
|
|
70
|
+
# Detect Node.js version to use (input > .node-version > .nvmrc > package.json volta.node)
|
|
71
|
+
- name: Detect node version
|
|
72
|
+
id: detect-node-version
|
|
142
73
|
working-directory: ${{ inputs.working-directory }}
|
|
143
74
|
shell: bash
|
|
144
75
|
run: |
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
76
|
+
resolved_version=""
|
|
77
|
+
|
|
78
|
+
if [ -n "${INPUT_NODE_VERSION}" ]; then
|
|
79
|
+
resolved_version="$INPUT_NODE_VERSION"
|
|
80
|
+
echo "📋 Using Node version from input: $resolved_version"
|
|
81
|
+
elif [ -f "./.node-version" ]; then
|
|
82
|
+
file_version=$(cat ./.node-version | tr -d '\n\r' | xargs)
|
|
83
|
+
if [ -n "$file_version" ]; then
|
|
84
|
+
resolved_version="$file_version"
|
|
85
|
+
echo "📋 Using Node version from .node-version: $resolved_version"
|
|
86
|
+
fi
|
|
87
|
+
fi
|
|
148
88
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
89
|
+
if [ -z "$resolved_version" ] && [ -f "./.nvmrc" ]; then
|
|
90
|
+
nvmrc_version=$(cat ./.nvmrc | tr -d '\n\r' | xargs)
|
|
91
|
+
if [ -n "$nvmrc_version" ]; then
|
|
92
|
+
resolved_version="$nvmrc_version"
|
|
93
|
+
echo "📋 Using Node version from .nvmrc: $resolved_version"
|
|
94
|
+
fi
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
if [ -z "$resolved_version" ]; then
|
|
98
|
+
volta_node=$(jq -r '.volta.node // empty' package.json 2>/dev/null || true)
|
|
99
|
+
if [ -n "$volta_node" ]; then
|
|
100
|
+
resolved_version="$volta_node"
|
|
101
|
+
echo "📋 Using Node version from package.json volta.node: $resolved_version"
|
|
102
|
+
fi
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
echo "version=$resolved_version" >> $GITHUB_OUTPUT
|
|
106
|
+
env:
|
|
107
|
+
INPUT_NODE_VERSION: ${{ inputs.node-version }}
|
|
108
|
+
|
|
109
|
+
- name: Setup Node.js
|
|
153
110
|
uses: actions/setup-node@v5
|
|
154
|
-
id: setup-node
|
|
111
|
+
id: setup-node
|
|
155
112
|
with:
|
|
156
|
-
# use detected package manager cache
|
|
157
|
-
cache: ${{ steps.
|
|
113
|
+
# use detected package manager cache
|
|
114
|
+
cache: ${{ steps.detect-package-manager.outputs.package-manager }}
|
|
158
115
|
cache-dependency-path: ${{ inputs.working-directory }}
|
|
159
|
-
node-version: ${{ steps.
|
|
116
|
+
node-version: ${{ steps.detect-node-version.outputs.version }}
|
|
160
117
|
registry-url: 'https://registry.npmjs.org'
|
|
161
118
|
|
|
162
|
-
#
|
|
119
|
+
# Apply `./node_modules` cache only if a lockfile is present and using `npm`
|
|
120
|
+
# Will remove the need to run install commands twice. Risk reduced by using a very specific cache key.
|
|
121
|
+
# Cache wont be used if the lockfile changes, package manager, node version, or OS changes.
|
|
122
|
+
- name: Setup node_modules dependency cache
|
|
123
|
+
if:
|
|
124
|
+
steps.detect-package-manager.outputs.lockfile-exists == 'true' &&
|
|
125
|
+
steps.detect-package-manager.outputs.package-manager == 'npm'
|
|
126
|
+
uses: actions/cache@v4
|
|
127
|
+
id: cache
|
|
128
|
+
with:
|
|
129
|
+
path: ${{ inputs.working-directory }}/node_modules
|
|
130
|
+
key:
|
|
131
|
+
${{ runner.os }}-node_modules-${{ steps.detect-package-manager.outputs.package-manager }}-${{
|
|
132
|
+
steps.setup-node.outputs.node-version }}-${{ hashFiles(format('{0}/{1}', inputs.working-directory,
|
|
133
|
+
steps.detect-package-manager.outputs.lockfile-path)) }}
|
|
134
|
+
|
|
163
135
|
- name: Install dependencies with pnpm
|
|
164
|
-
if: steps.
|
|
136
|
+
if: steps.detect-package-manager.outputs.package-manager == 'pnpm' && steps.cache.outputs.cache-hit != 'true'
|
|
165
137
|
working-directory: ${{ inputs.working-directory }}
|
|
166
138
|
shell: bash
|
|
167
139
|
run: |
|
|
168
140
|
echo "🔧 Installing dependencies with pnpm..."
|
|
169
|
-
if
|
|
141
|
+
if [ "$LOCKFILE_EXISTS" = "true" ]; then
|
|
142
|
+
INPUT_INSTALL_OPTIONS="--frozen-lockfile $INPUT_INSTALL_OPTIONS"
|
|
143
|
+
else
|
|
144
|
+
echo "⚠️ Warning: No lockfile found, not using --frozen-lockfile"
|
|
145
|
+
fi
|
|
146
|
+
if ! pnpm i $INPUT_INSTALL_OPTIONS; then
|
|
170
147
|
echo "❌ ERROR: pnpm install failed"
|
|
171
148
|
exit 1
|
|
172
149
|
fi
|
|
173
150
|
echo "✅ pnpm install completed successfully"
|
|
174
151
|
env:
|
|
175
152
|
INPUT_INSTALL_OPTIONS: ${{ inputs.install-options }}
|
|
153
|
+
LOCKFILE_EXISTS: ${{ steps.detect-package-manager.outputs.lockfile-exists }}
|
|
176
154
|
|
|
177
|
-
# Install dependencies with yarn
|
|
178
155
|
- name: Install dependencies with yarn
|
|
179
|
-
if: steps.
|
|
156
|
+
if: steps.detect-package-manager.outputs.package-manager == 'yarn' && steps.cache.outputs.cache-hit != 'true'
|
|
180
157
|
working-directory: ${{ inputs.working-directory }}
|
|
181
158
|
shell: bash
|
|
182
159
|
run: |
|
|
183
160
|
echo "🔧 Installing dependencies with yarn..."
|
|
184
|
-
if
|
|
161
|
+
if [ "$LOCKFILE_EXISTS" = "true" ]; then
|
|
162
|
+
INPUT_INSTALL_OPTIONS="--frozen-lockfile $INPUT_INSTALL_OPTIONS"
|
|
163
|
+
else
|
|
164
|
+
echo "⚠️ Warning: No lockfile found, results may vary"
|
|
165
|
+
fi
|
|
166
|
+
if ! yarn install $INPUT_INSTALL_OPTIONS; then
|
|
185
167
|
echo "❌ ERROR: yarn install failed"
|
|
186
168
|
exit 1
|
|
187
169
|
fi
|
|
188
170
|
echo "✅ yarn install completed successfully"
|
|
189
171
|
env:
|
|
190
172
|
INPUT_INSTALL_OPTIONS: ${{ inputs.install-options }}
|
|
173
|
+
LOCKFILE_EXISTS: ${{ steps.detect-package-manager.outputs.lockfile-exists }}
|
|
191
174
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if: steps.setup.outputs.lockfile-exists == 'true' && steps.setup.outputs.package-manager == 'npm'
|
|
175
|
+
- name: Install dependencies with npm
|
|
176
|
+
if: steps.detect-package-manager.outputs.package-manager == 'npm' && steps.cache.outputs.cache-hit != 'true'
|
|
195
177
|
working-directory: ${{ inputs.working-directory }}
|
|
196
178
|
shell: bash
|
|
197
179
|
run: |
|
|
198
|
-
|
|
199
|
-
if
|
|
200
|
-
|
|
201
|
-
|
|
180
|
+
NPM_CMD=""
|
|
181
|
+
if [ "$LOCKFILE_EXISTS" = "true" ]; then
|
|
182
|
+
NPM_CMD="ci"
|
|
183
|
+
else
|
|
184
|
+
echo "⚠️ Warning: No lockfile found, versions may vary"
|
|
185
|
+
NPM_CMD="install"
|
|
202
186
|
fi
|
|
203
|
-
echo "
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
- name: Install dependencies with npm (without lockfile)
|
|
208
|
-
if: steps.setup.outputs.lockfile-exists == 'false' && steps.setup.outputs.package-manager == 'npm'
|
|
209
|
-
working-directory: ${{ inputs.working-directory }}
|
|
210
|
-
shell: bash
|
|
211
|
-
run: |
|
|
212
|
-
echo "🔧 Installing dependencies with npm install..."
|
|
213
|
-
echo "⚠️ Warning: No lockfile found, versions may vary"
|
|
214
|
-
if ! npm install --no-save --prefer-offline --no-audit $INPUT_INSTALL_OPTIONS; then
|
|
215
|
-
echo "❌ ERROR: npm install failed"
|
|
187
|
+
echo "🔧 Installing dependencies with npm $NPM_CMD..."
|
|
188
|
+
if ! npm $NPM_CMD --no-save --prefer-offline --no-audit $INPUT_INSTALL_OPTIONS; then
|
|
189
|
+
echo "❌ ERROR: npm $NPM_CMD failed"
|
|
216
190
|
exit 1
|
|
217
191
|
fi
|
|
218
|
-
echo "✅ npm
|
|
192
|
+
echo "✅ npm $NPM_CMD completed successfully"
|
|
219
193
|
env:
|
|
220
194
|
INPUT_INSTALL_OPTIONS: ${{ inputs.install-options }}
|
|
195
|
+
LOCKFILE_EXISTS: ${{ steps.detect-package-manager.outputs.lockfile-exists }}
|
|
@@ -17,13 +17,13 @@ teardown() {
|
|
|
17
17
|
# Test tag generation logic from action
|
|
18
18
|
TAG_INPUT="test-tag"
|
|
19
19
|
MESSAGE_INPUT="Hello, World!"
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
bash -c "
|
|
22
22
|
tag=\"<!-- codfish/actions/comment $TAG_INPUT -->\"
|
|
23
23
|
echo \"Generated tag: \$tag\"
|
|
24
24
|
echo \"tag=\$tag\"
|
|
25
25
|
" > output.txt
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
assert_output_contains "tag=<!-- codfish/actions/comment test-tag -->" "$(cat output.txt)"
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -32,13 +32,13 @@ teardown() {
|
|
|
32
32
|
MESSAGE_INPUT="Line 1
|
|
33
33
|
Line 2
|
|
34
34
|
Line 3"
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
bash -c '
|
|
37
37
|
body=$(printf "$1")
|
|
38
38
|
echo "Processed message:"
|
|
39
39
|
echo "$body"
|
|
40
40
|
' -- "$MESSAGE_INPUT" > output.txt
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
assert_output_contains "Line 1" "$(cat output.txt)"
|
|
43
43
|
assert_output_contains "Line 2" "$(cat output.txt)"
|
|
44
44
|
assert_output_contains "Line 3" "$(cat output.txt)"
|
|
@@ -52,13 +52,13 @@ Line 3"
|
|
|
52
52
|
- Item 2
|
|
53
53
|
|
|
54
54
|
**Bold text** and *italic text*"
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
bash -c '
|
|
57
57
|
body=$(printf "$1")
|
|
58
58
|
echo "Markdown message:"
|
|
59
59
|
echo "$body"
|
|
60
60
|
' -- "$MESSAGE_INPUT" > output.txt
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
assert_output_contains "## Test Header" "$(cat output.txt)"
|
|
63
63
|
assert_output_contains "- Item 1" "$(cat output.txt)"
|
|
64
64
|
assert_output_contains "**Bold text**" "$(cat output.txt)"
|
|
@@ -68,7 +68,7 @@ Line 3"
|
|
|
68
68
|
# Test complete body generation
|
|
69
69
|
TAG_INPUT="build-status"
|
|
70
70
|
MESSAGE_INPUT="✅ Build successful!"
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
bash -c "
|
|
73
73
|
tag=\"<!-- codfish/actions/comment $TAG_INPUT -->\"
|
|
74
74
|
body=\$(printf '$MESSAGE_INPUT')
|
|
@@ -76,7 +76,7 @@ Line 3"
|
|
|
76
76
|
echo \"\$body\"
|
|
77
77
|
echo \"\$tag\"
|
|
78
78
|
" > output.txt
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
assert_output_contains "✅ Build successful!" "$(cat output.txt)"
|
|
81
81
|
assert_output_contains "<!-- codfish/actions/comment build-status -->" "$(cat output.txt)"
|
|
82
82
|
}
|
|
@@ -85,11 +85,11 @@ Line 3"
|
|
|
85
85
|
# Test with empty tag
|
|
86
86
|
TAG_INPUT=""
|
|
87
87
|
MESSAGE_INPUT="Message without tag"
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
bash -c "
|
|
90
90
|
tag=\"<!-- codfish/actions/comment $TAG_INPUT -->\"
|
|
91
91
|
echo \"Tag with empty input: \$tag\"
|
|
92
92
|
" > output.txt
|
|
93
|
-
|
|
93
|
+
|
|
94
94
|
assert_output_contains "<!-- codfish/actions/comment -->" "$(cat output.txt)"
|
|
95
|
-
}
|
|
95
|
+
}
|