Skip to content

Commit 570491a

Browse files
fix: propagate crate publish failures
1 parent 1f0befd commit 570491a

4 files changed

Lines changed: 199 additions & 10 deletions

File tree

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
if [[ $# -ne 1 ]]; then
6+
echo "Usage: $0 <crate-name>" >&2
7+
exit 2
8+
fi
9+
10+
crate_name="$1"
11+
metadata="$(cargo metadata --format-version 1 --no-deps)"
12+
version="$(
13+
jq --exit-status --raw-output --arg name "$crate_name" \
14+
'.packages[] | select(.name == $name) | .version' <<< "$metadata"
15+
)"
16+
17+
response_file="$(mktemp)"
18+
trap 'rm -f "$response_file"' EXIT
19+
20+
status="$(
21+
curl --silent --show-error \
22+
--output "$response_file" \
23+
--write-out '%{http_code}' \
24+
"https://crates.io/api/v1/crates/${crate_name}/${version}"
25+
)"
26+
27+
case "$status" in
28+
200)
29+
registry_version="$(jq --exit-status --raw-output '.version.num' "$response_file")"
30+
if [[ "$registry_version" != "$version" ]]; then
31+
echo "crates.io returned ${registry_version} for ${crate_name}@${version}" >&2
32+
exit 1
33+
fi
34+
35+
echo "${crate_name}@${version} is already published, skipping"
36+
exit 0
37+
;;
38+
404)
39+
;;
40+
*)
41+
echo "crates.io lookup for ${crate_name}@${version} failed with HTTP ${status}" >&2
42+
exit 1
43+
;;
44+
esac
45+
46+
cargo publish -p "$crate_name"
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import assert from "node:assert/strict";
2+
import { spawn } from "node:child_process";
3+
import { chmod, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
4+
import { tmpdir } from "node:os";
5+
import { join } from "node:path";
6+
import { describe, it } from "node:test";
7+
8+
const ROOT_URL = new URL("../../", import.meta.url);
9+
const SCRIPT_URL = new URL(".github/scripts/publish-crate-if-needed.sh", ROOT_URL);
10+
const WORKFLOW_URL = new URL(".github/workflows/release.yml", ROOT_URL);
11+
12+
const writeExecutable = async (path, contents) => {
13+
await writeFile(path, contents);
14+
await chmod(path, 0o755);
15+
};
16+
17+
const runPublishHelper = async ({ lookup = "existing", publishExit = 0 } = {}) => {
18+
const directory = await mkdtemp(join(tmpdir(), "srcmap-publish-test-"));
19+
const logPath = join(directory, "commands.log");
20+
21+
await writeFile(logPath, "");
22+
23+
await Promise.all([
24+
writeExecutable(
25+
join(directory, "cargo"),
26+
`#!/usr/bin/env bash
27+
set -eu
28+
printf 'cargo %s\\n' "$*" >> "$COMMAND_LOG"
29+
if [[ "$1" == "metadata" ]]; then
30+
printf '{"packages":[{"name":"srcmap-codec","version":"1.2.3"}]}'
31+
exit 0
32+
fi
33+
exit "${publishExit}"
34+
`,
35+
),
36+
writeExecutable(
37+
join(directory, "curl"),
38+
`#!/usr/bin/env bash
39+
set -eu
40+
printf 'curl %s\\n' "$*" >> "$COMMAND_LOG"
41+
if [[ "${lookup}" == "failure" ]]; then
42+
exit 7
43+
fi
44+
output=''
45+
while [[ $# -gt 0 ]]; do
46+
if [[ "$1" == "--output" || "$1" == "-o" ]]; then
47+
output="$2"
48+
shift 2
49+
continue
50+
fi
51+
shift
52+
done
53+
if [[ "${lookup}" == "existing" ]]; then
54+
printf '{"version":{"num":"1.2.3"}}' > "$output"
55+
printf '200'
56+
else
57+
printf '{"errors":[{"detail":"Not Found"}]}' > "$output"
58+
printf '404'
59+
fi
60+
`,
61+
),
62+
writeExecutable(
63+
join(directory, "jq"),
64+
`#!/usr/bin/env bash
65+
set -eu
66+
printf 'jq %s\\n' "$*" >> "$COMMAND_LOG"
67+
if [[ "$*" == *'.packages[]'* ]]; then
68+
printf '1.2.3\\n'
69+
exit 0
70+
fi
71+
if [[ "$*" == *'.version.num'* ]]; then
72+
printf '1.2.3\\n'
73+
exit 0
74+
fi
75+
exit 1
76+
`,
77+
),
78+
]);
79+
80+
const result = await new Promise((resolve, reject) => {
81+
const child = spawn("bash", [SCRIPT_URL.pathname, "srcmap-codec"], {
82+
cwd: new URL("../..", import.meta.url).pathname,
83+
env: {
84+
...process.env,
85+
COMMAND_LOG: logPath,
86+
PATH: `${directory}:${process.env.PATH}`,
87+
},
88+
});
89+
let stderr = "";
90+
let stdout = "";
91+
92+
child.stderr.setEncoding("utf8");
93+
child.stderr.on("data", (chunk) => {
94+
stderr += chunk;
95+
});
96+
child.stdout.setEncoding("utf8");
97+
child.stdout.on("data", (chunk) => {
98+
stdout += chunk;
99+
});
100+
child.on("error", reject);
101+
child.on("close", (exitCode) => resolve({ exitCode, stderr, stdout }));
102+
});
103+
104+
const commands = await readFile(logPath, "utf8");
105+
await rm(directory, { force: true, recursive: true });
106+
return { ...result, commands };
107+
};
108+
109+
describe("publish-crate-if-needed", () => {
110+
it("skips publishing when crates.io confirms the workspace version exists", async () => {
111+
const result = await runPublishHelper();
112+
113+
assert.equal(result.exitCode, 0);
114+
assert.match(result.stdout, /already published/i);
115+
assert.doesNotMatch(result.commands, /cargo publish/);
116+
});
117+
118+
it("publishes when crates.io reports that the workspace version is missing", async () => {
119+
const result = await runPublishHelper({ lookup: "missing" });
120+
121+
assert.equal(result.exitCode, 0);
122+
assert.match(result.commands, /cargo publish -p srcmap-codec/);
123+
});
124+
125+
it("stops when the crates.io lookup fails", async () => {
126+
const result = await runPublishHelper({ lookup: "failure" });
127+
128+
assert.notEqual(result.exitCode, 0);
129+
assert.doesNotMatch(result.commands, /cargo publish/);
130+
});
131+
132+
it("propagates cargo publish failures", async () => {
133+
const result = await runPublishHelper({ lookup: "missing", publishExit: 42 });
134+
135+
assert.equal(result.exitCode, 42);
136+
});
137+
138+
it("does not mask cargo publish failures in the release workflow", async () => {
139+
const workflow = await readFile(WORKFLOW_URL, "utf8");
140+
141+
assert.doesNotMatch(workflow, /cargo publish[^\n]*\|\|/);
142+
});
143+
});

.github/workflows/release.yml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,59 +29,59 @@ jobs:
2929
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
3030

3131
- name: Publish srcmap-codec
32-
run: cargo publish -p srcmap-codec || echo "Already published, skipping"
32+
run: .github/scripts/publish-crate-if-needed.sh srcmap-codec
3333
env:
3434
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
3535

3636
- name: Wait for crates.io index
3737
run: sleep 30
3838

3939
- name: Publish srcmap-scopes
40-
run: cargo publish -p srcmap-scopes || echo "Already published, skipping"
40+
run: .github/scripts/publish-crate-if-needed.sh srcmap-scopes
4141
env:
4242
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
4343

4444
- name: Wait for crates.io index
4545
run: sleep 30
4646

4747
- name: Publish srcmap-sourcemap
48-
run: cargo publish -p srcmap-sourcemap || echo "Already published, skipping"
48+
run: .github/scripts/publish-crate-if-needed.sh srcmap-sourcemap
4949
env:
5050
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
5151

5252
- name: Wait for crates.io index
5353
run: sleep 30
5454

5555
- name: Publish srcmap-symbolicate
56-
run: cargo publish -p srcmap-symbolicate || echo "Already published, skipping"
56+
run: .github/scripts/publish-crate-if-needed.sh srcmap-symbolicate
5757
env:
5858
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
5959

6060
- name: Publish srcmap-generator
61-
run: cargo publish -p srcmap-generator || echo "Already published, skipping"
61+
run: .github/scripts/publish-crate-if-needed.sh srcmap-generator
6262
env:
6363
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
6464

6565
- name: Wait for crates.io index
6666
run: sleep 30
6767

6868
- name: Publish srcmap-hermes
69-
run: cargo publish -p srcmap-hermes || echo "Already published, skipping"
69+
run: .github/scripts/publish-crate-if-needed.sh srcmap-hermes
7070
env:
7171
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
7272

7373
- name: Publish srcmap-ram-bundle
74-
run: cargo publish -p srcmap-ram-bundle || echo "Already published, skipping"
74+
run: .github/scripts/publish-crate-if-needed.sh srcmap-ram-bundle
7575
env:
7676
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
7777

7878
- name: Publish srcmap-remapping
79-
run: cargo publish -p srcmap-remapping || echo "Already published, skipping"
79+
run: .github/scripts/publish-crate-if-needed.sh srcmap-remapping
8080
env:
8181
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
8282

8383
- name: Publish srcmap-cli
84-
run: cargo publish -p srcmap-cli || echo "Already published, skipping"
84+
run: .github/scripts/publish-crate-if-needed.sh srcmap-cli
8585
env:
8686
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
8787

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"typos": "typos",
3030
"deny": "cargo deny check",
3131
"test": "pnpm run test:rust && pnpm run test:js",
32-
"test:js": "node --test .github/scripts/check-napi-declarations.test.mjs packages/codec/__tests__/codec.test.mjs packages/sourcemap/__tests__/sourcemap.test.mjs packages/sourcemap-wasm/__tests__/sourcemap-wasm.test.mjs packages/sourcemap-wasm/__tests__/coverage-utils.test.mjs packages/sourcemap-wasm/__tests__/browser.test.mjs packages/generator-wasm/__tests__/generator-wasm.test.mjs packages/remapping-wasm/__tests__/remapping-wasm.test.mjs packages/trace-mapping/__tests__/trace-mapping.test.mjs packages/trace-mapping/__tests__/compat.test.mjs packages/source-map/__tests__/source-map.test.mjs packages/gen-mapping/__tests__/gen-mapping.test.mjs packages/gen-mapping/__tests__/gen-mapping.cjs.test.cjs packages/remapping/__tests__/remapping.test.mjs packages/remapping/__tests__/remapping.cjs.test.cjs packages/remapping/__tests__/compat.test.mjs",
32+
"test:js": "node --test .github/scripts/check-napi-declarations.test.mjs .github/scripts/publish-crate-if-needed.test.mjs packages/codec/__tests__/codec.test.mjs packages/sourcemap/__tests__/sourcemap.test.mjs packages/sourcemap-wasm/__tests__/sourcemap-wasm.test.mjs packages/sourcemap-wasm/__tests__/coverage-utils.test.mjs packages/sourcemap-wasm/__tests__/browser.test.mjs packages/generator-wasm/__tests__/generator-wasm.test.mjs packages/remapping-wasm/__tests__/remapping-wasm.test.mjs packages/trace-mapping/__tests__/trace-mapping.test.mjs packages/trace-mapping/__tests__/compat.test.mjs packages/source-map/__tests__/source-map.test.mjs packages/gen-mapping/__tests__/gen-mapping.test.mjs packages/gen-mapping/__tests__/gen-mapping.cjs.test.cjs packages/remapping/__tests__/remapping.test.mjs packages/remapping/__tests__/remapping.cjs.test.cjs packages/remapping/__tests__/compat.test.mjs",
3333
"test:rust": "cargo test",
3434
"coverage": "pnpm run coverage:rust && pnpm run coverage:js",
3535
"coverage:js": "mkdir -p coverage && node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/js-lcov.info --test-reporter=spec --test-reporter-destination=stdout packages/codec/__tests__/codec.test.mjs packages/sourcemap/__tests__/sourcemap.test.mjs packages/sourcemap-wasm/__tests__/sourcemap-wasm.test.mjs packages/sourcemap-wasm/__tests__/coverage-utils.test.mjs packages/generator-wasm/__tests__/generator-wasm.test.mjs packages/remapping-wasm/__tests__/remapping-wasm.test.mjs packages/trace-mapping/__tests__/trace-mapping.test.mjs packages/trace-mapping/__tests__/compat.test.mjs packages/source-map/__tests__/source-map.test.mjs packages/gen-mapping/__tests__/gen-mapping.test.mjs packages/gen-mapping/__tests__/gen-mapping.cjs.test.cjs packages/remapping/__tests__/remapping.test.mjs packages/remapping/__tests__/remapping.cjs.test.cjs packages/remapping/__tests__/compat.test.mjs",

0 commit comments

Comments
 (0)