Skip to content

Commit 6c57146

Browse files
committed
chore: simplify npm.yml
1 parent cd679b5 commit 6c57146

File tree

4 files changed

+291
-37
lines changed

4 files changed

+291
-37
lines changed

.github/workflows/npm.yml

Lines changed: 7 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -184,38 +184,12 @@ jobs:
184184
run: |
185185
set -euo pipefail
186186
187-
TOOL='${{ matrix.tool }}'
188-
PLATFORM='${{ matrix.os }}'
189-
ARCH='${{ matrix.arch }}'
190-
191-
TMP_DIR="$ARTIFACT_DIR/tmp"
192-
rm -rf "$TMP_DIR"
193-
mkdir -p "$TMP_DIR"
194-
195-
FILE_PREFIX="$ARTIFACT_DIR/foundry_${RELEASE_VERSION}_${PLATFORM}_${ARCH}"
196-
if [[ -f "${FILE_PREFIX}.zip" ]]; then
197-
echo "Extracting ${FILE_PREFIX}.zip"
198-
if ! command -v unzip >/dev/null 2>&1; then
199-
sudo apt-get update -y && sudo apt-get install -y unzip
200-
fi
201-
unzip -o "${FILE_PREFIX}.zip" -d "$TMP_DIR"
202-
BIN="$TMP_DIR/${TOOL}.exe"
203-
else
204-
echo "Extracting ${FILE_PREFIX}.tar.gz"
205-
tar -xzf "${FILE_PREFIX}.tar.gz" -C "$TMP_DIR"
206-
BIN="$TMP_DIR/${TOOL}"
207-
fi
208-
209-
if [[ ! -f "$BIN" ]]; then
210-
echo "ERROR: Expected binary $BIN not found" >&2
211-
exit 1
212-
fi
213-
214-
PACKAGE_DIR="@foundry-rs/${TOOL}-${PLATFORM}-${ARCH}"
215-
echo "Staging binary $BIN into $PACKAGE_DIR"
216-
217-
TARGET_TOOL="$TOOL" PLATFORM_NAME="$PLATFORM" ARCH="$ARCH" \
218-
bun ./scripts/prepublish.mjs --tool "$TOOL" --binary "$BIN"
187+
bun ./scripts/stage-from-artifact.mjs \
188+
--tool '${{ matrix.tool }}' \
189+
--platform '${{ matrix.os }}' \
190+
--arch '${{ matrix.arch }}' \
191+
--release-version "$RELEASE_VERSION" \
192+
--artifact-dir "$ARTIFACT_DIR"
219193
220194
- name: Sanity Check Binary
221195
working-directory: ./npm
@@ -307,10 +281,7 @@ jobs:
307281
working-directory: ./npm
308282
run: |
309283
set -euo pipefail
310-
for tool in forge cast anvil chisel; do
311-
echo "Publishing meta package @foundry-rs/$tool"
312-
TARGET_TOOL="$tool" bun ./scripts/publish.mjs "./@foundry-rs/$tool"
313-
done
284+
bun ./scripts/publish-meta.mjs --release-version "$RELEASE_VERSION"
314285
env:
315286
PROVENANCE: true
316287
VERSION_NAME: ${{ env.RELEASE_VERSION }}

npm/scripts/publish-meta.mjs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#!/usr/bin/env bun
2+
3+
import * as NodePath from 'node:path'
4+
import * as NodeUtil from 'node:util'
5+
6+
import { colors, KNOWN_TOOLS } from '#const.mjs'
7+
8+
/**
9+
* @typedef {import('#const.mjs').Tool} Tool
10+
*/
11+
12+
main().catch(error => {
13+
console.error(colors.red, error)
14+
console.error(colors.reset)
15+
process.exit(1)
16+
})
17+
18+
/**
19+
* Publish each meta package (`@foundry-rs/<tool>`) to npm.
20+
* @returns {Promise<void>}
21+
*/
22+
async function main() {
23+
const { tools, releaseVersion } = resolveArgs()
24+
25+
for (const tool of tools) {
26+
const packageDir = NodePath.join('@foundry-rs', tool)
27+
console.info(colors.green, `Publishing meta package ${packageDir}`, colors.reset)
28+
29+
const result = await Bun
30+
.$`bun ./scripts/publish.mjs ${packageDir}`
31+
.cwd(NodePath.resolve(import.meta.dir, '..'))
32+
.env({
33+
...process.env,
34+
TARGET_TOOL: tool,
35+
TOOL: tool,
36+
RELEASE_VERSION: releaseVersion,
37+
VERSION_NAME: releaseVersion
38+
})
39+
.nothrow()
40+
41+
if (result.exitCode !== 0) {
42+
const stderr = typeof result.stderr === 'string' ? result.stderr : result.stderr?.toString('utf8')
43+
const stdout = typeof result.stdout === 'string' ? result.stdout : result.stdout?.toString('utf8')
44+
throw new Error(stderr || stdout || `Failed to publish ${packageDir}`)
45+
}
46+
}
47+
}
48+
49+
/**
50+
* Resolve CLI arguments and defaults.
51+
* @returns {{ tools: Tool[]; releaseVersion: string }}
52+
*/
53+
function resolveArgs() {
54+
const { values, positionals } = NodeUtil.parseArgs({
55+
args: Bun.argv.slice(2),
56+
allowPositionals: true,
57+
options: {
58+
tool: { type: 'string', multiple: true },
59+
tools: { type: 'string', multiple: true },
60+
'release-version': { type: 'string' },
61+
release: { type: 'string' }
62+
},
63+
strict: true
64+
})
65+
66+
const releaseCandidate = values['release-version']
67+
|| values.release
68+
|| process.env.RELEASE_VERSION
69+
|| process.env.VERSION_NAME
70+
|| ''
71+
72+
const releaseVersion = releaseCandidate.trim()
73+
if (!releaseVersion)
74+
throw new Error('Missing required RELEASE_VERSION')
75+
76+
const explicitTools = [...(values.tool ?? []), ...(values.tools ?? []), ...positionals]
77+
78+
const selected = explicitTools.length ? explicitTools : KNOWN_TOOLS
79+
const tools = /** @type {Tool[]} */ (selected.map(candidate => {
80+
const trimmed = candidate.trim()
81+
if (!trimmed) return undefined
82+
const maybeTool = /** @type {Tool} */ (trimmed)
83+
if (!KNOWN_TOOLS.includes(maybeTool))
84+
throw new Error(`Unsupported tool: ${candidate}`)
85+
return maybeTool
86+
}).filter(Boolean))
87+
88+
return { tools, releaseVersion }
89+
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
#!/usr/bin/env bun
2+
3+
import * as NodeFS from 'node:fs/promises'
4+
import * as NodeOS from 'node:os'
5+
import * as NodePath from 'node:path'
6+
import * as NodeUtil from 'node:util'
7+
8+
import { colors } from '#const.mjs'
9+
10+
/**
11+
* @typedef {import('#const.mjs').Tool} Tool
12+
* @typedef {import('#const.mjs').Platform} Platform
13+
* @typedef {import('#const.mjs').Arch} Arch
14+
*/
15+
16+
const RELEASE_ARTIFACT_PREFIX = 'foundry'
17+
18+
main().catch(error => {
19+
console.error(colors.red, error)
20+
console.error(colors.reset)
21+
process.exit(1)
22+
})
23+
24+
/**
25+
* Entry point: locate the platform-specific artifact, extract the binary,
26+
* and delegate to prepublish to stage it into the package directory.
27+
* @returns {Promise<void>}
28+
*/
29+
async function main() {
30+
const { tool, platform, arch, releaseVersion, artifactDir } = resolveArgs()
31+
32+
const artifactPrefix = NodePath.join(
33+
artifactDir,
34+
`${RELEASE_ARTIFACT_PREFIX}_${releaseVersion}_${platform}_${arch}`
35+
)
36+
37+
const archivePath = await chooseArchive(artifactPrefix)
38+
const extractionDir = await extractArchive(archivePath)
39+
40+
try {
41+
const binaryPath = await resolveExtractedBinary({ tool, platform, extractionDir })
42+
await stagePackage({ tool, platform, arch, binaryPath })
43+
} finally {
44+
await NodeFS.rm(extractionDir, { recursive: true, force: true })
45+
}
46+
}
47+
48+
/**
49+
* Parse CLI arguments/environment.
50+
* @returns {{ tool: Tool; platform: Platform; arch: Arch; releaseVersion: string; artifactDir: string }}
51+
*/
52+
function resolveArgs() {
53+
const { values } = NodeUtil.parseArgs({
54+
args: Bun.argv.slice(2),
55+
options: {
56+
tool: { type: 'string' },
57+
platform: { type: 'string' },
58+
arch: { type: 'string' },
59+
release: { type: 'string' },
60+
'release-version': { type: 'string' },
61+
artifacts: { type: 'string' },
62+
'artifact-dir': { type: 'string' }
63+
},
64+
strict: true
65+
})
66+
67+
const tool = requireValue(values.tool || process.env.TARGET_TOOL, 'tool')
68+
const platform = requireValue(values.platform || process.env.PLATFORM_NAME, 'platform')
69+
const arch = requireValue(values.arch || process.env.ARCH, 'arch')
70+
const releaseVersion = requireValue(
71+
values.release || values['release-version'] || process.env.RELEASE_VERSION,
72+
'release version'
73+
)
74+
const artifactDir = requireValue(
75+
values.artifacts || values['artifact-dir'] || process.env.ARTIFACT_DIR,
76+
'artifact directory'
77+
)
78+
79+
return /** @type {{ tool: Tool; platform: Platform; arch: Arch; releaseVersion: string; artifactDir: string }} */ ({
80+
tool: /** @type {Tool} */ (tool),
81+
platform: /** @type {Platform} */ (platform),
82+
arch: /** @type {Arch} */ (arch),
83+
releaseVersion,
84+
artifactDir: NodePath.resolve(artifactDir)
85+
})
86+
}
87+
88+
/**
89+
* @param {string | undefined} value
90+
* @param {string} name
91+
* @returns {string}
92+
*/
93+
function requireValue(value, name) {
94+
if (typeof value === 'string' && value.trim()) return value.trim()
95+
throw new Error(`Missing required ${name}`)
96+
}
97+
98+
/**
99+
* Determine which archive variant exists for the given artifact prefix.
100+
* @param {string} prefix
101+
* @returns {Promise<string>}
102+
*/
103+
async function chooseArchive(prefix) {
104+
const tarPath = `${prefix}.tar.gz`
105+
const zipPath = `${prefix}.zip`
106+
107+
if (await pathExists(tarPath)) return tarPath
108+
if (await pathExists(zipPath)) return zipPath
109+
110+
throw new Error(`No release artifact found for prefix ${prefix}`)
111+
}
112+
113+
/**
114+
* @param {string} filePath
115+
* @returns {Promise<boolean>}
116+
*/
117+
async function pathExists(filePath) {
118+
try {
119+
await NodeFS.access(filePath)
120+
return true
121+
} catch {
122+
return false
123+
}
124+
}
125+
126+
/**
127+
* Extract the archive into a temporary directory.
128+
* @param {string} archivePath
129+
* @returns {Promise<string>}
130+
*/
131+
async function extractArchive(archivePath) {
132+
const tempDir = await NodeFS.mkdtemp(NodePath.join(NodeOS.tmpdir(), 'foundry-npm-'))
133+
const command = archivePath.endsWith('.zip')
134+
? Bun.$`unzip -o ${archivePath} -d ${tempDir}`
135+
: Bun.$`tar -xzf ${archivePath} -C ${tempDir}`
136+
137+
const result = await command
138+
.env(process.env)
139+
.nothrow()
140+
141+
if (result.exitCode !== 0) {
142+
const stderr = typeof result.stderr === 'string'
143+
? result.stderr
144+
: result.stderr?.toString('utf8')
145+
const stdout = typeof result.stdout === 'string'
146+
? result.stdout
147+
: result.stdout?.toString('utf8')
148+
throw new Error(`Failed to extract ${archivePath}: ${stderr || stdout || 'unknown error'}`)
149+
}
150+
151+
return tempDir
152+
}
153+
154+
/**
155+
* Locate the expected binary within the extraction directory.
156+
* @param {{ tool: Tool; platform: Platform; extractionDir: string }} options
157+
* @returns {Promise<string>}
158+
*/
159+
async function resolveExtractedBinary({ tool, platform, extractionDir }) {
160+
const binaryName = platform === 'win32' ? `${tool}.exe` : tool
161+
const candidate = NodePath.join(extractionDir, binaryName)
162+
163+
if (await pathExists(candidate)) return candidate
164+
165+
throw new Error(`Binary ${binaryName} not found in ${extractionDir}`)
166+
}
167+
168+
/**
169+
* Delegate to prepublish to stage the extracted binary into the package dir.
170+
* @param {{ tool: Tool; platform: Platform; arch: Arch; binaryPath: string }} options
171+
* @returns {Promise<void>}
172+
*/
173+
async function stagePackage({ tool, platform, arch, binaryPath }) {
174+
console.info(colors.green, `Staging ${tool} (${platform}/${arch}) from ${binaryPath}`, colors.reset)
175+
176+
const subprocess = Bun
177+
.$`bun ./scripts/prepublish.mjs --tool ${tool} --binary ${binaryPath}`
178+
.cwd(NodePath.resolve(import.meta.dir, '..'))
179+
.env({
180+
...process.env,
181+
TARGET_TOOL: tool,
182+
TOOL: tool,
183+
PLATFORM_NAME: platform,
184+
ARCH: arch
185+
})
186+
187+
const result = await subprocess.nothrow()
188+
if (result.exitCode !== 0) {
189+
const stderr = typeof result.stderr === 'string'
190+
? result.stderr
191+
: result.stderr?.toString('utf8')
192+
throw new Error(stderr || `Failed to stage package for ${tool}`)
193+
}
194+
}

npm/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"types": [
3232
"bun",
3333
"node"
34-
],
34+
]
3535
},
3636
"exclude": [
3737
"dist",

0 commit comments

Comments
 (0)