Skip to content

Commit c1aef25

Browse files
Copilotneilime
andcommitted
fix(release): use fs instead of glob for tarball matching, fix artifact download path
Co-authored-by: neilime <314088+neilime@users.noreply.github.com> Signed-off-by: Emilien Escalle <emilien.escalle@escemi.com>
1 parent 87877f7 commit c1aef25

File tree

1 file changed

+41
-193
lines changed

1 file changed

+41
-193
lines changed

.github/workflows/release.yml

Lines changed: 41 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ on:
1515
type: string
1616
default: '["ubuntu-latest"]'
1717
required: false
18+
working-directory:
19+
description: "Working directory where the dependencies are installed."
20+
type: string
21+
required: false
22+
default: "."
1823
build-artifact-id:
1924
description: |
2025
Build artifact ID from a previous CI job to download before publishing.
@@ -23,45 +28,6 @@ on:
2328
type: string
2429
required: false
2530
default: ""
26-
package-tarball:
27-
description: |
28-
Path to a pre-built package tarball to publish (e.g., `my-package-1.0.0.tgz`).
29-
Use this when publishing a specific tarball file instead of running npm publish from source.
30-
Supports glob patterns to match tarball files.
31-
type: string
32-
required: false
33-
default: ""
34-
access:
35-
description: |
36-
Package access level for npm publish.
37-
- `public` — Publicly accessible package
38-
- `restricted` — Scoped package with restricted access
39-
Leave empty to use package.json default.
40-
type: string
41-
required: false
42-
default: ""
43-
docs:
44-
description: |
45-
Documentation generation parameters.
46-
Set to empty string or `false` to disable documentation generation.
47-
Set to `true` or a string to enable documentation generation with the default command `docs`.
48-
Accepts a JSON object for advanced options:
49-
50-
- `command`: Command to run for documentation generation (default: `docs`).
51-
- `output`: Output directory for documentation (default: `docs`).
52-
- `artifact`: Whether to upload documentation as an artifact (default: `false`).
53-
54-
Example:
55-
```json
56-
{
57-
"command": "build:docs",
58-
"output": "docs-dist",
59-
"artifact": true
60-
}
61-
```
62-
type: string
63-
required: false
64-
default: ""
6531
registry:
6632
description: |
6733
Registry configuration for package publishing.
@@ -85,19 +51,6 @@ on:
8551
type: string
8652
required: false
8753
default: "npm"
88-
publish-command:
89-
description: |
90-
Command to run for publishing the package.
91-
Defaults to `publish` which runs `npm publish` or equivalent for the detected package manager.
92-
Can be customized for monorepo setups or specific publish requirements.
93-
94-
Examples:
95-
- `publish` — Default npm/pnpm/yarn publish
96-
- `release` — Custom publish script in package.json
97-
- `publish --access public` — Publish with specific npm flags
98-
type: string
99-
required: false
100-
default: "publish"
10154
tag:
10255
description: |
10356
npm distribution tag for the published package.
@@ -110,43 +63,6 @@ on:
11063
type: string
11164
required: false
11265
default: "latest"
113-
dry-run:
114-
description: |
115-
Whether to perform a dry run (no actual publish).
116-
Useful for testing the release workflow without publishing.
117-
type: boolean
118-
required: false
119-
default: false
120-
provenance:
121-
description: |
122-
Whether to generate provenance attestation for the published package.
123-
Requires npm 9.5.0+ and appropriate permissions.
124-
See https://docs.npmjs.com/generating-provenance-statements.
125-
type: boolean
126-
required: false
127-
default: true
128-
working-directory:
129-
description: "Working directory where the package is located."
130-
type: string
131-
required: false
132-
default: "."
133-
secrets:
134-
registry-token:
135-
description: |
136-
Authentication token for the registry.
137-
For npm: Use an npm access token with publish permissions.
138-
For GitHub Packages: Use `GITHUB_TOKEN` or a PAT with `packages:write` permission.
139-
required: true
140-
outputs:
141-
version:
142-
description: "The version of the published package."
143-
value: ${{ jobs.release.outputs.version }}
144-
package-name:
145-
description: "The name of the published package."
146-
value: ${{ jobs.release.outputs.package-name }}
147-
docs-artifact-id:
148-
description: "ID of the documentation artifact (if uploaded)."
149-
value: ${{ jobs.release.outputs.docs-artifact-id }}
15066

15167
permissions: {}
15268

@@ -210,47 +126,6 @@ jobs:
210126
core.setOutput('registry-url', registryUrl);
211127
core.setOutput('registry-scope', registryScope);
212128
213-
- id: parse-docs
214-
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
215-
env:
216-
DOCS_INPUT: ${{ inputs.docs }}
217-
with:
218-
script: |
219-
const docsInput = process.env.DOCS_INPUT.trim();
220-
221-
if (!docsInput || docsInput === 'false') {
222-
core.info('Documentation generation disabled');
223-
return;
224-
}
225-
226-
let command = 'docs';
227-
let output = 'docs';
228-
let artifact = false;
229-
230-
if (docsInput === 'true') {
231-
// Use defaults
232-
} else if (docsInput.startsWith('{')) {
233-
try {
234-
const config = JSON.parse(docsInput);
235-
command = config.command || 'docs';
236-
output = config.output || 'docs';
237-
artifact = config.artifact === true;
238-
} catch (error) {
239-
return core.setFailed(`Failed to parse docs input as JSON: ${error.message}`);
240-
}
241-
} else {
242-
// Treat as custom command
243-
command = docsInput;
244-
}
245-
246-
core.info(`Docs command: ${command}`);
247-
core.info(`Docs output: ${output}`);
248-
core.info(`Docs artifact: ${artifact}`);
249-
250-
core.setOutput('command', command);
251-
core.setOutput('output', output);
252-
core.setOutput('artifact', artifact ? 'true' : '');
253-
254129
release:
255130
name: 🚀 Release
256131
runs-on: ${{ inputs.runs-on && fromJson(inputs.runs-on) || 'ubuntu-latest' }}
@@ -260,10 +135,6 @@ jobs:
260135
contents: read
261136
packages: write
262137
id-token: write # Required for provenance
263-
outputs:
264-
version: ${{ steps.package-info.outputs.version }}
265-
package-name: ${{ steps.package-info.outputs.name }}
266-
docs-artifact-id: ${{ steps.upload-docs.outputs.artifact-id }}
267138
steps:
268139
- uses: hoverkraft-tech/ci-github-common/actions/checkout@5ac504609f6ef35c5ac94bd8199063aa32104721 # 0.31.3
269140

@@ -282,8 +153,8 @@ jobs:
282153
if: inputs.build-artifact-id != ''
283154
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
284155
with:
285-
artifact-ids: ${{ inputs.build-artifact-id }}
286-
path: /
156+
name: ${{ inputs.build-artifact-id }}
157+
path: ${{ github.workspace }}
287158

288159
- id: package-info
289160
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
@@ -325,53 +196,6 @@ jobs:
325196
core.setOutput('name', name);
326197
core.setOutput('version', version);
327198
328-
- name: Generate documentation
329-
if: needs.prepare.outputs.docs-command != ''
330-
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
331-
env:
332-
DOCS_COMMAND: ${{ needs.prepare.outputs.docs-command }}
333-
RUN_SCRIPT_COMMAND: ${{ steps.setup-node.outputs.run-script-command }}
334-
WORKING_DIRECTORY: ${{ inputs.working-directory }}
335-
with:
336-
script: |
337-
const fs = require('node:fs');
338-
const path = require('node:path');
339-
340-
let workingDirectory = process.env.WORKING_DIRECTORY || '.';
341-
if (!path.isAbsolute(workingDirectory)) {
342-
workingDirectory = path.join(process.env.GITHUB_WORKSPACE, workingDirectory);
343-
}
344-
345-
if (!fs.existsSync(workingDirectory)) {
346-
return core.setFailed(`Working directory does not exist: ${workingDirectory}`);
347-
}
348-
349-
const docsCommand = process.env.DOCS_COMMAND;
350-
const runScriptCommand = process.env.RUN_SCRIPT_COMMAND;
351-
352-
core.info(`Running documentation command: ${docsCommand}`);
353-
354-
try {
355-
const exitCode = await exec.exec(runScriptCommand, [docsCommand], {
356-
cwd: workingDirectory,
357-
ignoreReturnCode: true
358-
});
359-
360-
if (exitCode !== 0) {
361-
return core.setFailed(`Documentation generation failed with exit code ${exitCode}`);
362-
}
363-
} catch (error) {
364-
return core.setFailed(`Documentation generation failed: ${error.message}`);
365-
}
366-
367-
- id: upload-docs
368-
if: needs.prepare.outputs.docs-artifact == 'true'
369-
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
370-
with:
371-
name: documentation-${{ github.run_id }}
372-
path: ${{ inputs.working-directory }}/${{ needs.prepare.outputs.docs-output }}
373-
if-no-files-found: error
374-
375199
- name: Publish package
376200
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
377201
env:
@@ -389,7 +213,6 @@ jobs:
389213
script: |
390214
const fs = require('node:fs');
391215
const path = require('node:path');
392-
const { glob } = require('glob');
393216
394217
let workingDirectory = process.env.WORKING_DIRECTORY || '.';
395218
if (!path.isAbsolute(workingDirectory)) {
@@ -409,25 +232,50 @@ jobs:
409232
const runScriptCommand = process.env.RUN_SCRIPT_COMMAND;
410233
const registryUrl = process.env.REGISTRY_URL;
411234
235+
// Simple glob matching function
236+
function matchGlob(pattern, filename) {
237+
const regexPattern = pattern
238+
.replace(/\./g, '\\.')
239+
.replace(/\*/g, '.*')
240+
.replace(/\?/g, '.');
241+
return new RegExp(`^${regexPattern}$`).test(filename);
242+
}
243+
412244
// Determine what to publish
413245
let publishTarget = null;
414246
415247
if (packageTarball) {
416248
// Publishing a specific tarball file
417-
const tarballPattern = path.isAbsolute(packageTarball)
418-
? packageTarball
419-
: path.join(workingDirectory, packageTarball);
420-
421-
const matches = await glob(tarballPattern, { nodir: true });
422-
249+
let searchDir = workingDirectory;
250+
let filePattern = packageTarball;
251+
252+
if (path.isAbsolute(packageTarball)) {
253+
searchDir = path.dirname(packageTarball);
254+
filePattern = path.basename(packageTarball);
255+
} else if (packageTarball.includes('/')) {
256+
searchDir = path.join(workingDirectory, path.dirname(packageTarball));
257+
filePattern = path.basename(packageTarball);
258+
}
259+
260+
if (!fs.existsSync(searchDir)) {
261+
return core.setFailed(`Search directory does not exist: ${searchDir}`);
262+
}
263+
264+
// Find matching files
265+
const allFiles = fs.readdirSync(searchDir);
266+
const matches = allFiles
267+
.filter(file => matchGlob(filePattern, file))
268+
.map(file => path.join(searchDir, file))
269+
.filter(file => fs.statSync(file).isFile());
270+
423271
if (matches.length === 0) {
424-
return core.setFailed(`No tarball found matching pattern: ${packageTarball}`);
272+
return core.setFailed(`No tarball found matching pattern: ${packageTarball} in ${searchDir}`);
425273
}
426-
274+
427275
if (matches.length > 1) {
428276
core.warning(`Multiple tarballs found: ${matches.join(', ')}. Using first match.`);
429277
}
430-
278+
431279
publishTarget = matches[0];
432280
core.info(`Publishing tarball: ${publishTarget}`);
433281
}

0 commit comments

Comments
 (0)