@codfish/actions 0.0.0-PR-58--24ced07
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/README.md +283 -0
- package/bin/generate-docs.js +432 -0
- package/comment/README.md +82 -0
- package/comment/action.yml +102 -0
- package/npm-publish-pr/README.md +424 -0
- package/npm-publish-pr/action.yml +362 -0
- package/package.json +57 -0
- package/setup-node-and-install/README.md +184 -0
- package/setup-node-and-install/action.yml +228 -0
|
@@ -0,0 +1,362 @@
|
|
|
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) or
|
|
5
|
+
OIDC trusted publishing, and automatically comments on PR
|
|
6
|
+
|
|
7
|
+
inputs:
|
|
8
|
+
npm-token:
|
|
9
|
+
required: false
|
|
10
|
+
description:
|
|
11
|
+
Registry authentication token with publish permissions. If not provided, OIDC trusted publishing will be used.
|
|
12
|
+
tarball:
|
|
13
|
+
required: false
|
|
14
|
+
description:
|
|
15
|
+
Path to pre-built tarball to publish (e.g., '*.tgz'). When provided, publishes the tarball with --ignore-scripts
|
|
16
|
+
for security. Recommended for pull_request_target workflows to prevent execution of malicious lifecycle scripts.
|
|
17
|
+
comment:
|
|
18
|
+
required: false
|
|
19
|
+
default: 'true'
|
|
20
|
+
description: Whether to comment on the PR with the published version (true/false)
|
|
21
|
+
comment-tag:
|
|
22
|
+
required: false
|
|
23
|
+
default: npm-publish-pr
|
|
24
|
+
description: Tag to use for PR comments (for comment identification and updates)
|
|
25
|
+
dev:
|
|
26
|
+
required: false
|
|
27
|
+
default: 'false'
|
|
28
|
+
description: If true, use dev dependency install syntax in the PR comment (e.g. npm install -D, pnpm add -D).
|
|
29
|
+
|
|
30
|
+
outputs:
|
|
31
|
+
version:
|
|
32
|
+
description: Generated PR-specific version number (0.0.0-PR-{number}--{short-sha})
|
|
33
|
+
value: '${{ steps.publish.outputs.version }}'
|
|
34
|
+
package-name:
|
|
35
|
+
description: Package name from package.json
|
|
36
|
+
value: '${{ steps.publish.outputs.package-name }}'
|
|
37
|
+
error-message:
|
|
38
|
+
description: Error message if publish fails
|
|
39
|
+
value: '${{ steps.publish.outputs.error-message }}'
|
|
40
|
+
|
|
41
|
+
runs:
|
|
42
|
+
using: composite
|
|
43
|
+
|
|
44
|
+
steps:
|
|
45
|
+
- uses: codfish/actions/comment@v3
|
|
46
|
+
if: inputs.comment == 'true'
|
|
47
|
+
with:
|
|
48
|
+
message: ⏳ Publishing PR version...
|
|
49
|
+
upsert: true
|
|
50
|
+
tag: ${{ inputs.comment-tag }}
|
|
51
|
+
|
|
52
|
+
- name: Validate and publish to registry
|
|
53
|
+
id: publish
|
|
54
|
+
shell: bash
|
|
55
|
+
run: |
|
|
56
|
+
set +e # Don't exit on error so we can handle failures
|
|
57
|
+
|
|
58
|
+
# Initialize outputs for error handling
|
|
59
|
+
error_message=""
|
|
60
|
+
package_name=""
|
|
61
|
+
version=""
|
|
62
|
+
package_manager="npm"
|
|
63
|
+
tarball_mode=false
|
|
64
|
+
NPMRC_AUTH_FILE=""
|
|
65
|
+
REPACK_DIR=""
|
|
66
|
+
|
|
67
|
+
# Clean up on exit: remove temp auth file (never touch project .npmrc), temp files, repack dir
|
|
68
|
+
cleanup() {
|
|
69
|
+
local exit_code=$?
|
|
70
|
+
[ -n "$NPMRC_AUTH_FILE" ] && rm -f "$NPMRC_AUTH_FILE" 2>/dev/null || true
|
|
71
|
+
[ -n "$temp_pkg_json" ] && rm -f "$temp_pkg_json" 2>/dev/null || true
|
|
72
|
+
[ -n "$REPACK_DIR" ] && rm -rf "$REPACK_DIR" 2>/dev/null || true
|
|
73
|
+
return $exit_code
|
|
74
|
+
}
|
|
75
|
+
trap cleanup EXIT
|
|
76
|
+
|
|
77
|
+
# Detect if tarball mode is being used
|
|
78
|
+
if [ -n "$INPUT_TARBALL" ]; then
|
|
79
|
+
tarball_mode=true
|
|
80
|
+
echo "🔒 SECURE MODE: Using pre-built tarball (lifecycle scripts will NOT execute)"
|
|
81
|
+
|
|
82
|
+
# Expand glob patterns (e.g., *.tgz) to actual filename
|
|
83
|
+
shopt -s nullglob
|
|
84
|
+
tarball_files=($INPUT_TARBALL)
|
|
85
|
+
shopt -u nullglob
|
|
86
|
+
|
|
87
|
+
if [ ${#tarball_files[@]} -eq 0 ]; then
|
|
88
|
+
error_message="❌ ERROR: No tarball files found matching pattern: $INPUT_TARBALL"
|
|
89
|
+
echo "$error_message"
|
|
90
|
+
echo "error-message=$error_message" >> $GITHUB_OUTPUT
|
|
91
|
+
exit 1
|
|
92
|
+
elif [ ${#tarball_files[@]} -gt 1 ]; then
|
|
93
|
+
error_message="❌ ERROR: Multiple tarball files found matching pattern: $INPUT_TARBALL (found: ${tarball_files[*]}). Please specify a single tarball file."
|
|
94
|
+
echo "$error_message"
|
|
95
|
+
echo "error-message=$error_message" >> $GITHUB_OUTPUT
|
|
96
|
+
exit 1
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# Use the resolved tarball path
|
|
100
|
+
INPUT_TARBALL="${tarball_files[0]}"
|
|
101
|
+
echo "📦 Resolved tarball: $INPUT_TARBALL"
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
# Function to extract relevant error message from npm output
|
|
105
|
+
extract_error() {
|
|
106
|
+
local output="$1"
|
|
107
|
+
|
|
108
|
+
# Extract npm error lines (lines starting with "npm error")
|
|
109
|
+
local error_lines=$(echo "$output" | grep "^npm error" | head -5)
|
|
110
|
+
|
|
111
|
+
# If we found error lines, use those
|
|
112
|
+
if [ -n "$error_lines" ]; then
|
|
113
|
+
echo "$error_lines" | tr '\n' ' ' | tr -s ' '
|
|
114
|
+
else
|
|
115
|
+
# Otherwise, take the last few lines (likely contains the error)
|
|
116
|
+
echo "$output" | tail -10 | tr '\n' ' ' | tr -s ' ' | cut -c1-500
|
|
117
|
+
fi
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
# Standardized publish error handler
|
|
121
|
+
handle_publish_error() {
|
|
122
|
+
local manager_name="$1"
|
|
123
|
+
local publish_output="$2"
|
|
124
|
+
local extracted_error=""
|
|
125
|
+
|
|
126
|
+
extracted_error=$(extract_error "$publish_output")
|
|
127
|
+
error_message="❌ Failed to publish with ${manager_name}: ${extracted_error}"
|
|
128
|
+
echo "Full output: $publish_output"
|
|
129
|
+
echo "Error message: $error_message"
|
|
130
|
+
echo "error-message=$error_message" >> $GITHUB_OUTPUT
|
|
131
|
+
exit 1
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# In tarball mode: unpack, inject PR version, repack (so we publish a unique version)
|
|
135
|
+
# In normal mode, validate package.json
|
|
136
|
+
if [ "$tarball_mode" = true ]; then
|
|
137
|
+
# Validate tarball exists
|
|
138
|
+
if [ ! -f "$INPUT_TARBALL" ]; then
|
|
139
|
+
error_message="❌ ERROR: Tarball not found at path: $INPUT_TARBALL"
|
|
140
|
+
echo "$error_message"
|
|
141
|
+
echo "error-message=$error_message" >> $GITHUB_OUTPUT
|
|
142
|
+
exit 1
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
echo "📦 Unpacking tarball and injecting PR version: $INPUT_TARBALL"
|
|
146
|
+
|
|
147
|
+
# Unpack to temp dir (npm pack format: top-level "package/" with package.json inside)
|
|
148
|
+
repack_dir=$(mktemp -d)
|
|
149
|
+
REPACK_DIR="$repack_dir"
|
|
150
|
+
tar -xzf "$INPUT_TARBALL" -C "$repack_dir"
|
|
151
|
+
|
|
152
|
+
if [ ! -f "$repack_dir/package/package.json" ]; then
|
|
153
|
+
error_message="❌ ERROR: Could not extract package.json from tarball (expected package/package.json)"
|
|
154
|
+
echo "$error_message"
|
|
155
|
+
echo "error-message=$error_message" >> $GITHUB_OUTPUT
|
|
156
|
+
exit 1
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
package_name=$(jq -r '.name // empty' "$repack_dir/package/package.json")
|
|
160
|
+
if [ -z "$package_name" ] || [ "$package_name" = "null" ]; then
|
|
161
|
+
error_message="❌ ERROR: Tarball's package.json must have a 'name' field"
|
|
162
|
+
echo "$error_message"
|
|
163
|
+
echo "error-message=$error_message" >> $GITHUB_OUTPUT
|
|
164
|
+
exit 1
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
# Generate PR-specific version (same format as normal mode) so we don't overwrite published versions
|
|
168
|
+
version="0.0.0-PR-${PR}--$(echo ${SHA} | cut -c -7)"
|
|
169
|
+
jq --arg v "$version" '.version = $v' "$repack_dir/package/package.json" > "$repack_dir/package/package.json.tmp" && mv "$repack_dir/package/package.json.tmp" "$repack_dir/package/package.json"
|
|
170
|
+
|
|
171
|
+
# Repack for publish (still secure: --ignore-scripts used when publishing)
|
|
172
|
+
(cd "$repack_dir" && tar -czf repack.tgz package)
|
|
173
|
+
TARBALL_TO_PUBLISH="$repack_dir/repack.tgz"
|
|
174
|
+
|
|
175
|
+
echo "📦 Tarball package: $package_name@$version (PR version)"
|
|
176
|
+
echo "package-name=$package_name" >> $GITHUB_OUTPUT
|
|
177
|
+
echo "version=$version" >> $GITHUB_OUTPUT
|
|
178
|
+
else
|
|
179
|
+
# Normal mode: validate package.json exists in current directory
|
|
180
|
+
if [ ! -f "package.json" ]; then
|
|
181
|
+
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"
|
|
182
|
+
echo "$error_message"
|
|
183
|
+
echo "error-message=$(extract_error "$error_message")" >> $GITHUB_OUTPUT
|
|
184
|
+
exit 1
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
# Validate package.json is valid JSON
|
|
188
|
+
if ! jq empty package.json 2>/dev/null; then
|
|
189
|
+
error_message="❌ ERROR: package.json is not valid JSON"
|
|
190
|
+
echo "$error_message"
|
|
191
|
+
echo "error-message=$(extract_error "$error_message")" >> $GITHUB_OUTPUT
|
|
192
|
+
exit 1
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
# Check if package has a name
|
|
196
|
+
package_name=$(jq -r '.name // empty' package.json)
|
|
197
|
+
if [ -z "$package_name" ] || [ "$package_name" = "null" ]; then
|
|
198
|
+
error_message="❌ ERROR: package.json must have a 'name' field"
|
|
199
|
+
echo "$error_message"
|
|
200
|
+
echo "error-message=$(extract_error "$error_message")" >> $GITHUB_OUTPUT
|
|
201
|
+
exit 1
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
# Output package name for use in error handling
|
|
205
|
+
echo "package-name=$package_name" >> $GITHUB_OUTPUT
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
# Detect authentication mode and package manager
|
|
209
|
+
if [ -z "$INPUT_NPM_TOKEN" ]; then
|
|
210
|
+
echo "🔐 Using OIDC trusted publishing (no npm-token provided)"
|
|
211
|
+
echo "📦 Using npm for OIDC (--provenance requires npm)"
|
|
212
|
+
|
|
213
|
+
if [ -z "$ACTIONS_ID_TOKEN_REQUEST_URL" ]; then
|
|
214
|
+
error_message="❌ ERROR: OIDC token not available. Add 'permissions: { id-token: write }' to your workflow"
|
|
215
|
+
echo "$error_message"
|
|
216
|
+
echo "error-message=$error_message" >> $GITHUB_OUTPUT
|
|
217
|
+
exit 1
|
|
218
|
+
fi
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
if [ "$INPUT_NPM_TOKEN" ]; then
|
|
222
|
+
echo "🔐 Using token-based authentication"
|
|
223
|
+
|
|
224
|
+
# Token mode: use a temp userconfig file so we never overwrite or delete the project's .npmrc.
|
|
225
|
+
# npm merges NPM_CONFIG_USERCONFIG with project .npmrc, so custom registry/scoped config is preserved.
|
|
226
|
+
export NODE_AUTH_TOKEN="$INPUT_NPM_TOKEN"
|
|
227
|
+
NPMRC_AUTH_FILE=$(mktemp)
|
|
228
|
+
echo "//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}" > "$NPMRC_AUTH_FILE"
|
|
229
|
+
export NPM_CONFIG_USERCONFIG="$NPMRC_AUTH_FILE"
|
|
230
|
+
|
|
231
|
+
# Detect package manager for token-based publishing
|
|
232
|
+
if [ -f "./yarn.lock" ]; then
|
|
233
|
+
package_manager="yarn"
|
|
234
|
+
echo "📦 Detected package manager: yarn"
|
|
235
|
+
elif [ -f "./pnpm-lock.yaml" ]; then
|
|
236
|
+
package_manager="pnpm"
|
|
237
|
+
echo "📦 Detected package manager: pnpm"
|
|
238
|
+
else
|
|
239
|
+
package_manager="npm"
|
|
240
|
+
echo "📦 Detected package manager: npm"
|
|
241
|
+
fi
|
|
242
|
+
fi
|
|
243
|
+
|
|
244
|
+
# Generate version (skip in tarball mode - already extracted)
|
|
245
|
+
if [ "$tarball_mode" = false ]; then
|
|
246
|
+
version="0.0.0-PR-${PR}--$(echo ${SHA} | cut -c -7)"
|
|
247
|
+
echo "📦 Publishing $package_name@$version with $package_manager"
|
|
248
|
+
echo "version=$version" >> $GITHUB_OUTPUT
|
|
249
|
+
|
|
250
|
+
# Update package.json version (all package managers support npm version)
|
|
251
|
+
version_output=$(npm version $version --no-git-tag-version 2>&1)
|
|
252
|
+
version_exit_code=$?
|
|
253
|
+
if [ $version_exit_code -ne 0 ]; then
|
|
254
|
+
error_message="❌ ERROR: Failed to update package version. Check if the version format is valid. Error: $version_output"
|
|
255
|
+
echo "$error_message"
|
|
256
|
+
echo "error-message=$(extract_error "$error_message")" >> $GITHUB_OUTPUT
|
|
257
|
+
exit 1
|
|
258
|
+
fi
|
|
259
|
+
else
|
|
260
|
+
echo "📦 Publishing $package_name@$version from tarball"
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
# Publish package
|
|
264
|
+
if [ "$tarball_mode" = true ]; then
|
|
265
|
+
# SECURE TARBALL MODE: Publish repacked tarball (PR version injected) with --ignore-scripts
|
|
266
|
+
echo "🔒 Publishing tarball with --ignore-scripts (secure mode)"
|
|
267
|
+
|
|
268
|
+
if [ -z "$INPUT_NPM_TOKEN" ]; then
|
|
269
|
+
# OIDC mode with tarball
|
|
270
|
+
publish_output=$(npm publish "$TARBALL_TO_PUBLISH" --access public --tag pr --provenance --ignore-scripts 2>&1)
|
|
271
|
+
publish_exit_code=$?
|
|
272
|
+
|
|
273
|
+
if [ $publish_exit_code -ne 0 ]; then
|
|
274
|
+
handle_publish_error "OIDC (tarball)" "$publish_output"
|
|
275
|
+
fi
|
|
276
|
+
echo "✅ Successfully published $package_name@$version using OIDC (secure tarball mode)"
|
|
277
|
+
else
|
|
278
|
+
# Token mode with tarball - always use npm for tarball publishing
|
|
279
|
+
publish_output=$(npm publish "$TARBALL_TO_PUBLISH" --access public --tag pr --ignore-scripts 2>&1)
|
|
280
|
+
publish_exit_code=$?
|
|
281
|
+
|
|
282
|
+
if [ $publish_exit_code -ne 0 ]; then
|
|
283
|
+
handle_publish_error "npm (tarball)" "$publish_output"
|
|
284
|
+
fi
|
|
285
|
+
echo "✅ Successfully published $package_name@$version using npm (secure tarball mode)"
|
|
286
|
+
fi
|
|
287
|
+
else
|
|
288
|
+
# NORMAL MODE: Traditional publishing (INSECURE for pull_request_target)
|
|
289
|
+
if [ -z "$INPUT_NPM_TOKEN" ]; then
|
|
290
|
+
echo "📦 Publishing with OIDC trusted publishing..."
|
|
291
|
+
|
|
292
|
+
publish_output=$(npm publish --access public --tag pr --provenance 2>&1)
|
|
293
|
+
publish_exit_code=$?
|
|
294
|
+
|
|
295
|
+
if [ $publish_exit_code -ne 0 ]; then
|
|
296
|
+
handle_publish_error "OIDC" "$publish_output"
|
|
297
|
+
fi
|
|
298
|
+
echo "✅ Successfully published $package_name@$version using OIDC"
|
|
299
|
+
else
|
|
300
|
+
# Token mode: use detected package manager
|
|
301
|
+
case "$package_manager" in
|
|
302
|
+
"yarn")
|
|
303
|
+
publish_output=$(yarn publish --access public --tag pr --new-version $version --no-git-tag-version --skip-check-working-tree 2>&1)
|
|
304
|
+
publish_exit_code=$?
|
|
305
|
+
if [ $publish_exit_code -ne 0 ]; then
|
|
306
|
+
handle_publish_error "yarn" "$publish_output"
|
|
307
|
+
fi
|
|
308
|
+
;;
|
|
309
|
+
"pnpm")
|
|
310
|
+
publish_output=$(pnpm publish --no-git-checks --access public --tag pr 2>&1)
|
|
311
|
+
publish_exit_code=$?
|
|
312
|
+
if [ $publish_exit_code -ne 0 ]; then
|
|
313
|
+
handle_publish_error "pnpm" "$publish_output"
|
|
314
|
+
fi
|
|
315
|
+
;;
|
|
316
|
+
*)
|
|
317
|
+
publish_output=$(npm publish --access public --tag pr 2>&1)
|
|
318
|
+
publish_exit_code=$?
|
|
319
|
+
if [ $publish_exit_code -ne 0 ]; then
|
|
320
|
+
handle_publish_error "npm" "$publish_output"
|
|
321
|
+
fi
|
|
322
|
+
;;
|
|
323
|
+
esac
|
|
324
|
+
echo "✅ Successfully published $package_name@$version using $package_manager"
|
|
325
|
+
fi
|
|
326
|
+
fi
|
|
327
|
+
env:
|
|
328
|
+
# CRITICAL: Use INPUT_NPM_TOKEN instead of NPM_TOKEN here to avoid
|
|
329
|
+
# setting NPM_TOKEN in the environment when empty (which could break OIDC)
|
|
330
|
+
INPUT_NPM_TOKEN: ${{ inputs.npm-token }}
|
|
331
|
+
INPUT_TARBALL: ${{ inputs.tarball }}
|
|
332
|
+
PR: ${{ github.event.number }}
|
|
333
|
+
SHA: ${{ github.event.pull_request.head.sha }}
|
|
334
|
+
|
|
335
|
+
- uses: codfish/actions/comment@v3
|
|
336
|
+
if: failure() && inputs.comment == 'true'
|
|
337
|
+
with:
|
|
338
|
+
message: |
|
|
339
|
+
❌ **PR package publish failed!**
|
|
340
|
+
|
|
341
|
+
Error: ${{ steps.publish.outputs.error-message }}
|
|
342
|
+
|
|
343
|
+
📋 [View workflow logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for more details.
|
|
344
|
+
upsert: true
|
|
345
|
+
tag: ${{ inputs.comment-tag }}
|
|
346
|
+
|
|
347
|
+
- uses: codfish/actions/comment@v3
|
|
348
|
+
if: success() && inputs.comment == 'true'
|
|
349
|
+
with:
|
|
350
|
+
message: |
|
|
351
|
+
✅ **PR package published successfully!**
|
|
352
|
+
|
|
353
|
+
Install:
|
|
354
|
+
|
|
355
|
+
${{ inputs.dev == 'true' && 'pnpm add -D ' || 'pnpm add ' }}${{ steps.publish.outputs.package-name }}@${{ steps.publish.outputs.version }}
|
|
356
|
+
${{ inputs.dev == 'true' && 'npm install -D ' || 'npm install ' }}${{ steps.publish.outputs.package-name }}@${{ steps.publish.outputs.version }}
|
|
357
|
+
${{ inputs.dev == 'true' && 'yarn add -D ' || 'yarn add ' }}${{ steps.publish.outputs.package-name }}@${{ steps.publish.outputs.version }}
|
|
358
|
+
${{ inputs.dev == 'true' && 'bun add -d ' || 'bun add ' }}${{ steps.publish.outputs.package-name }}@${{ steps.publish.outputs.version }}
|
|
359
|
+
|
|
360
|
+
View on npm: https://www.npmjs.com/package/${{ steps.publish.outputs.package-name }}/v/${{ steps.publish.outputs.version }}
|
|
361
|
+
upsert: true
|
|
362
|
+
tag: ${{ inputs.comment-tag }}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
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": "0.0.0-PR-58--24ced07",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/codfish/actions.git"
|
|
11
|
+
},
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"comment",
|
|
17
|
+
"npm-publish-pr",
|
|
18
|
+
"setup-node-and-install",
|
|
19
|
+
"bin",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"lint": "eslint .",
|
|
24
|
+
"fix": "eslint . --fix",
|
|
25
|
+
"format": "prettier --write \"**/*.{json,css,md,yml}\" --config ./node_modules/@codfish/eslint-config/prettier.js",
|
|
26
|
+
"test": "bash tests/scripts/test-runner.sh",
|
|
27
|
+
"test:integration": "bash tests/scripts/test-runner.sh integration",
|
|
28
|
+
"test:unit": "bash tests/scripts/test-runner.sh unit",
|
|
29
|
+
"docs:generate": "node bin/generate-docs.js",
|
|
30
|
+
"prepare": "husky"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@codfish/eslint-config": "12.3.0",
|
|
34
|
+
"bats": "^1.13.0",
|
|
35
|
+
"doctoc": "^2.2.1",
|
|
36
|
+
"eslint": "^9.39.2",
|
|
37
|
+
"husky": "^9.1.7",
|
|
38
|
+
"js-yaml": "^4.1.0",
|
|
39
|
+
"lint-staged": "^16.2.7",
|
|
40
|
+
"prettier": "^3.8.1"
|
|
41
|
+
},
|
|
42
|
+
"packageManager": "pnpm@10.29.3",
|
|
43
|
+
"commitlint": {
|
|
44
|
+
"extends": [
|
|
45
|
+
"./node_modules/@codfish/eslint-config/commitlint.js"
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
"lint-staged": {
|
|
49
|
+
"*.{json,css}": [
|
|
50
|
+
"prettier --write --config ./node_modules/@codfish/eslint-config/prettier.js"
|
|
51
|
+
],
|
|
52
|
+
"*.md": [
|
|
53
|
+
"prettier --write --config ./node_modules/@codfish/eslint-config/prettier.js",
|
|
54
|
+
"doctoc --title '## Table of Contents'"
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# setup-node-and-install
|
|
2
|
+
|
|
3
|
+
Sets up Node.js environment and installs dependencies with automatic package manager detection, intelligent caching, and
|
|
4
|
+
dynamic Node version detection via the `node-version` input, `.node-version`, `.nvmrc`, or `package.json` `volta.node`.
|
|
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 (v6) with optimized caching
|
|
10
|
+
- **Upgrades npm to v11** (pinned to `^11.5.1` for OIDC trusted publishing support)
|
|
11
|
+
- Installs dependencies with appropriate commands based on detected package manager
|
|
12
|
+
- Supports `.node-version`, `.nvmrc`, and `package.json` `volta.node` for version specification
|
|
13
|
+
- Intelligent caching of node_modules when lockfiles are present
|
|
14
|
+
|
|
15
|
+
<!-- DOCTOC SKIP -->
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
See [action.yml](action.yml).
|
|
20
|
+
|
|
21
|
+
```yml
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v6
|
|
24
|
+
|
|
25
|
+
# Will setup node, inferring node version from your codebase & installing your dependencies
|
|
26
|
+
- uses: codfish/actions/setup-node-and-install@v3
|
|
27
|
+
|
|
28
|
+
# Or if you want to be explicit
|
|
29
|
+
- uses: codfish/actions/setup-node-and-install@v3
|
|
30
|
+
with:
|
|
31
|
+
node-version: 24.4
|
|
32
|
+
|
|
33
|
+
- run: npm test
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The `node-version` input is optional. If not supplied, this action will attempt to resolve a version using, in order:
|
|
37
|
+
|
|
38
|
+
1. `.node-version`, 2) `.nvmrc`, 3) `package.json` `volta.node`. If none are present, `actions/setup-node` runs without
|
|
39
|
+
an explicit version and will use its default behavior.
|
|
40
|
+
|
|
41
|
+
The `install-options` input is optional. If not supplied, the npm install commands will execute as defined without any
|
|
42
|
+
additional options.
|
|
43
|
+
|
|
44
|
+
**With `.nvmrc` file**
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
# .nvmrc
|
|
48
|
+
v18.14.1
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```yml
|
|
52
|
+
steps:
|
|
53
|
+
- uses: actions/checkout@v6
|
|
54
|
+
# will install Node v18.14.1
|
|
55
|
+
- uses: codfish/actions/setup-node-and-install@v3
|
|
56
|
+
- run: npm test
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**With `.node-version` file**
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
# .node-version
|
|
63
|
+
20.10.0
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```yml
|
|
67
|
+
steps:
|
|
68
|
+
- uses: actions/checkout@v6
|
|
69
|
+
# will install Node v20.10.0
|
|
70
|
+
- uses: codfish/actions/setup-node-and-install@v3
|
|
71
|
+
- run: npm test
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Node Version Resolution Priority
|
|
75
|
+
|
|
76
|
+
When multiple version specification methods are present, the action uses this priority order:
|
|
77
|
+
|
|
78
|
+
1. **Input parameter** (`node-version`) - highest priority
|
|
79
|
+
2. **`.node-version` file**
|
|
80
|
+
3. **`.nvmrc` file**
|
|
81
|
+
4. **`package.json` `volta.node` property**
|
|
82
|
+
5. **`actions/setup-node` default behavior** when no version is specified
|
|
83
|
+
|
|
84
|
+
## Inputs
|
|
85
|
+
|
|
86
|
+
<!-- start inputs -->
|
|
87
|
+
|
|
88
|
+
| Input | Description | Required | Default |
|
|
89
|
+
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- |
|
|
90
|
+
| `node-version` | Node.js version to install (e.g. "24", "lts/\*"). Precedence: node-version input > .node-version > .nvmrc > package.json volta.node. | No | - |
|
|
91
|
+
| `install-options` | Extra command-line options to pass to npm/pnpm/yarn install. | No | - |
|
|
92
|
+
| `working-directory` | Directory containing package.json and lockfile. | No | `.` |
|
|
93
|
+
| `registry-url` | Optional registry URL to configure for publishing (e.g. "https://registry.npmjs.org/"). Creates .npmrc with NODE_AUTH_TOKEN placeholder. NOT recommended if using semantic-release (it handles auth independently). Only needed for publishing with manual npm publish or other non-semantic-release workflows. | No | - |
|
|
94
|
+
| `upgrade-npm` | Whether to upgrade npm to v11.5.1. This is required for OIDC trusted publishing but can be disabled if you want to shave off some run time and you are still using token-based authentication. | No | `true` |
|
|
95
|
+
|
|
96
|
+
<!-- end inputs -->
|
|
97
|
+
|
|
98
|
+
## Package Manager Detection
|
|
99
|
+
|
|
100
|
+
The action automatically detects your package manager:
|
|
101
|
+
|
|
102
|
+
- **pnpm**: Detected when `pnpm-lock.yaml` exists
|
|
103
|
+
- **yarn**: Detected when `yarn.lock` exists
|
|
104
|
+
- **npm**: Detected when `package-lock.json` exists or as fallback
|
|
105
|
+
|
|
106
|
+
## npm Version Upgrade
|
|
107
|
+
|
|
108
|
+
This action automatically upgrades npm to **v11** after Node.js setup (pinned to `^11.5.1`). This ensures:
|
|
109
|
+
|
|
110
|
+
- npm 11.5.1+ is available for **OIDC trusted publishing** support (required as of January 2026)
|
|
111
|
+
- Stable, predictable npm behavior across workflows
|
|
112
|
+
- Security fixes and improvements within the v11 release line
|
|
113
|
+
- No unexpected breaking changes from major version updates
|
|
114
|
+
|
|
115
|
+
The upgrade happens transparently and is logged in the workflow output. The version is pinned to prevent unexpected
|
|
116
|
+
breaking changes while still receiving patch and minor updates within v11.
|
|
117
|
+
|
|
118
|
+
## Registry URL Configuration
|
|
119
|
+
|
|
120
|
+
The `registry-url` input configures npm authentication by creating a `.npmrc` file with a `NODE_AUTH_TOKEN` placeholder.
|
|
121
|
+
**In most cases, you should NOT set this parameter.**
|
|
122
|
+
|
|
123
|
+
### When NOT to use registry-url (recommended)
|
|
124
|
+
|
|
125
|
+
**Skip this parameter if:**
|
|
126
|
+
|
|
127
|
+
- You're **only installing dependencies** (the primary use case for this action) - authentication is not needed for
|
|
128
|
+
public packages
|
|
129
|
+
- You're using **semantic-release** for publishing - it handles npm authentication independently and `registry-url` can
|
|
130
|
+
cause conflicts
|
|
131
|
+
([semantic-release docs](https://semantic-release.gitbook.io/semantic-release/recipes/ci-configurations/github-actions#important-avoid-registry-url-in-setup-node))
|
|
132
|
+
- You're using **OIDC trusted publishing** with npm - the upgraded npm v11 handles this automatically
|
|
133
|
+
|
|
134
|
+
### When to use registry-url
|
|
135
|
+
|
|
136
|
+
**Only set this parameter if:**
|
|
137
|
+
|
|
138
|
+
- You're publishing to npm using **manual `npm publish`** (not semantic-release)
|
|
139
|
+
- You need to authenticate to a **private npm registry**
|
|
140
|
+
- You're using **legacy token-based publishing** and need the `.npmrc` file created
|
|
141
|
+
|
|
142
|
+
### Example with registry-url
|
|
143
|
+
|
|
144
|
+
```yml
|
|
145
|
+
- uses: codfish/actions/setup-node-and-install@v3
|
|
146
|
+
with:
|
|
147
|
+
registry-url: 'https://registry.npmjs.org/'
|
|
148
|
+
env:
|
|
149
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
150
|
+
|
|
151
|
+
- run: npm publish
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Examples
|
|
155
|
+
|
|
156
|
+
### With specific Node version
|
|
157
|
+
|
|
158
|
+
```yml
|
|
159
|
+
- uses: codfish/actions/setup-node-and-install@v3
|
|
160
|
+
with:
|
|
161
|
+
node-version: '18'
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### With pnpm in subdirectory
|
|
165
|
+
|
|
166
|
+
```yml
|
|
167
|
+
- uses: codfish/actions/setup-node-and-install@v3
|
|
168
|
+
with:
|
|
169
|
+
working-directory: './frontend'
|
|
170
|
+
install-options: '--frozen-lockfile'
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Migrating
|
|
174
|
+
|
|
175
|
+
Replace multiple setup steps with this single action:
|
|
176
|
+
|
|
177
|
+
```diff
|
|
178
|
+
- - uses: actions/setup-node@v4
|
|
179
|
+
- with:
|
|
180
|
+
- node-version-file: '.nvmrc'
|
|
181
|
+
- cache: 'npm'
|
|
182
|
+
- - run: npm ci --prefer-offline --no-audit
|
|
183
|
+
+ - uses: codfish/actions/setup-node-and-install@v3
|
|
184
|
+
```
|