@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.
@@ -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 .nvmrc/.node-version support
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: Node.js version to install (e.g. '24', 'lts/*'). Defaults to .nvmrc or .node-version file if present
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 inputs and detect package manager
28
- id: setup
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.setup.outputs.package-manager == 'pnpm'
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
- - name: Setup node with .nvmrc
125
- if: ${{ inputs.node-version != '' || hashFiles(format('{0}/.nvmrc', inputs.working-directory)) != '' }}
126
- uses: actions/setup-node@v5
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
- node_version=$(cat .node-version | tr -d '\n\r' | xargs)
146
- echo "node-version=$node_version" >> $GITHUB_OUTPUT
147
- echo "📋 Read Node version from .node-version: $node_version"
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
- - name: Setup node with .node-version
150
- if:
151
- ${{ inputs.node-version == '' && hashFiles(format('{0}/.nvmrc', inputs.working-directory)) == '' &&
152
- hashFiles(format('{0}/.node-version', inputs.working-directory)) != '' }}
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-alt
111
+ id: setup-node
155
112
  with:
156
- # use detected package manager cache if a lockfile is present
157
- cache: ${{ steps.setup.outputs.lockfile-exists == 'true' && steps.setup.outputs.package-manager || '' }}
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.read-node-version.outputs.node-version }}
116
+ node-version: ${{ steps.detect-node-version.outputs.version }}
160
117
  registry-url: 'https://registry.npmjs.org'
161
118
 
162
- # Install dependencies with pnpm
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.setup.outputs.package-manager == 'pnpm'
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 ! pnpm install $INPUT_INSTALL_OPTIONS; then
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.setup.outputs.package-manager == 'yarn'
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 ! yarn install --frozen-lockfile $INPUT_INSTALL_OPTIONS; then
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
- # Install dependencies with npm (with lockfile)
193
- - name: Install dependencies with npm (with lockfile)
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
- echo "🔧 Installing dependencies with npm ci..."
199
- if ! npm ci --prefer-offline --no-audit $INPUT_INSTALL_OPTIONS; then
200
- echo "❌ ERROR: npm ci failed"
201
- exit 1
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 " npm ci completed successfully"
204
- env:
205
- INPUT_INSTALL_OPTIONS: ${{ inputs.install-options }}
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 install completed successfully"
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
+ }