Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,9 @@ jobs:
mkdir -p "$AUDIT_ROOT"
cd "$AUDIT_ROOT"
npm init --yes >/dev/null
npm install --no-audit --no-fund --ignore-scripts "@tangle-network/braid@$VERSION"
bash "$GITHUB_WORKSPACE/scripts/release/retry-registry-command.sh" \
npm install --prefer-online --no-audit --no-fund --ignore-scripts \
"@tangle-network/braid@$VERSION"
npm audit signatures --json --include-attestations > "$RUNNER_TEMP/npm-audit-signatures.json"

- name: Upload npm provenance
Expand Down
33 changes: 33 additions & 0 deletions scripts/release/retry-registry-command.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bash

set -euo pipefail

attempts="${BRAID_REGISTRY_ATTEMPTS:-30}"
delay_seconds="${BRAID_REGISTRY_DELAY_SECONDS:-5}"

if [[ ! "$attempts" =~ ^[1-9][0-9]*$ ]]; then
echo 'BRAID_REGISTRY_ATTEMPTS must be a positive integer' >&2
exit 64
fi
if [[ ! "$delay_seconds" =~ ^[0-9]+$ ]]; then
echo 'BRAID_REGISTRY_DELAY_SECONDS must be a non-negative integer' >&2
exit 64
fi
if (( $# == 0 )); then
echo 'A registry command is required' >&2
exit 64
fi

attempt=1
while (( attempt <= attempts )); do
if "$@"; then
exit 0
fi
if (( attempt == attempts )); then
echo "Registry command failed after $attempts attempts" >&2
exit 1
fi
echo "Waiting for registry propagation ($attempt/$attempts)" >&2
sleep "$delay_seconds"
((attempt += 1))
done
7 changes: 6 additions & 1 deletion scripts/release/smoke-package.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ async function sha256(path) {
const artifactRootValue = option('--artifact-root') ?? process.env.BRAID_RELEASE_ARTIFACT_ROOT
const registrySpec = option('--registry') ?? process.env.BRAID_RELEASE_REGISTRY_SPEC
const outputPathValue = option('--output') ?? process.env.BRAID_SMOKE_OUTPUT
const retryRegistryCommand = fileURLToPath(new URL('./retry-registry-command.sh', import.meta.url))
const expectedPlatform = option('--expect-platform') ?? process.env.BRAID_EXPECT_PLATFORM
const expectedArchitecture =
option('--expect-architecture') ?? process.env.BRAID_EXPECT_ARCHITECTURE
Expand All @@ -149,7 +150,11 @@ try {
const packRoot = join(smokeRoot, 'registry')
await mkdir(packRoot)
const npm = npmInvocation(['pack', registrySpec, '--pack-destination', packRoot])
await run(npm.file, npm.args, { cwd: smokeRoot })
await run('bash', [retryRegistryCommand, npm.file, ...npm.args], {
cwd: smokeRoot,
label: 'Registry package download',
timeoutMs: 5 * 60_000,
})
const archives = (await readdir(packRoot)).filter((name) => name.endsWith('.tgz'))
assert(archives.length === 1, 'Registry download did not produce exactly one tarball')
tarballPath = join(packRoot, archives[0])
Expand Down
40 changes: 39 additions & 1 deletion test/scripts.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import assert from 'node:assert/strict'
import { execFileSync } from 'node:child_process'
import { execFileSync, spawnSync } from 'node:child_process'
import { createHash } from 'node:crypto'
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
Expand Down Expand Up @@ -85,6 +85,44 @@ test('compiled tests receive the JavaScript helpers imported from scripts', asyn
assert.match(source, /entry\.name\.endsWith\('\.mjs'\)/u)
})

test('registry commands retry transient failures and stop at the configured limit', async () => {
const root = await mkdtemp(join(tmpdir(), 'braid-registry-retry-'))
const counter = join(root, 'attempts')
const retry = join(process.cwd(), 'scripts', 'release', 'retry-registry-command.sh')
const countAttempt = [
"const fs = require('node:fs')",
'const path = process.argv[1]',
"const count = Number(fs.existsSync(path) ? fs.readFileSync(path, 'utf8') : '0') + 1",
'fs.writeFileSync(path, String(count))',
"process.exit(count < Number(process.env.SUCCEED_ON ?? '999') ? 42 : 0)",
].join(';')
const environment = {
...process.env,
BRAID_REGISTRY_ATTEMPTS: '3',
BRAID_REGISTRY_DELAY_SECONDS: '0',
}

try {
const recovered = spawnSync('bash', [retry, process.execPath, '-e', countAttempt, counter], {
encoding: 'utf8',
env: { ...environment, SUCCEED_ON: '3' },
})
assert.equal(recovered.status, 0, recovered.stderr)
assert.equal(await readFile(counter, 'utf8'), '3')

await writeFile(counter, '0')
const exhausted = spawnSync('bash', [retry, process.execPath, '-e', countAttempt, counter], {
encoding: 'utf8',
env: environment,
})
assert.equal(exhausted.status, 1)
assert.match(exhausted.stderr, /Registry command failed after 3 attempts/u)
assert.equal(await readFile(counter, 'utf8'), '3')
} finally {
await rm(root, { recursive: true, force: true })
}
})

test('clean package installs cannot inherit disabled native dependency builds', () => {
const environment = nativeInstallEnvironment({
NPM_CONFIG_IGNORE_SCRIPTS: 'true',
Expand Down