Skip to content

Commit d09555b

Browse files
authored
Merge pull request #11 from pie-framework/develop
publish
2 parents cc804a0 + d61fe0f commit d09555b

4 files changed

Lines changed: 134 additions & 13 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
"@pie-qti/element-schemas": patch
3+
"@pie-qti/pie-to-qti2": patch
4+
"@pie-qti/qti-processing": patch
5+
"@pie-qti/qti2-assessment-player": patch
6+
"@pie-qti/qti2-default-components": patch
7+
"@pie-qti/qti2-item-player": patch
8+
"@pie-qti/qti2-player-elements": patch
9+
"@pie-qti/qti2-to-pie": patch
10+
"@pie-qti/qti2-typeset-katex": patch
11+
"@pie-qti/transform-cli": patch
12+
"@pie-qti/transform-core": patch
13+
"@pie-qti/transform-types": patch
14+
"@pie-qti/web-component-loaders": patch
15+
---
16+
17+
Publish the initial public release of all publishable PIE-QTI packages.
18+
19+

.changeset/qti-players-1.0.0.md

Lines changed: 0 additions & 12 deletions
This file was deleted.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
"check": "turbo check",
4444
"changeset": "changeset",
4545
"version": "changeset version",
46-
"release": "bun run build && changeset publish"
46+
"release": "node scripts/release.mjs"
4747
},
4848
"devDependencies": {
4949
"@biomejs/biome": "^2.3.11",

scripts/release.mjs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Local-friendly release script:
4+
* - runs build
5+
* - runs `changeset publish`
6+
* - if publish fails due to OTP/2FA, prompts for OTP (TTY only) and retries
7+
*
8+
* CI should use an npm automation token (no OTP). In non-interactive shells this
9+
* script will NOT prompt; it will fail with guidance.
10+
*/
11+
12+
import { spawn } from "node:child_process";
13+
import process from "node:process";
14+
import { createInterface } from "node:readline/promises";
15+
16+
function run(cmd, args, { env } = {}) {
17+
return new Promise((resolve) => {
18+
const child = spawn(cmd, args, {
19+
env: { ...process.env, ...(env ?? {}) },
20+
stdio: ["inherit", "pipe", "pipe"],
21+
});
22+
23+
let combined = "";
24+
25+
child.stdout.on("data", (buf) => {
26+
const s = buf.toString();
27+
combined += s;
28+
process.stdout.write(s);
29+
});
30+
31+
child.stderr.on("data", (buf) => {
32+
const s = buf.toString();
33+
combined += s;
34+
process.stderr.write(s);
35+
});
36+
37+
child.on("close", (code) => resolve({ code: code ?? 0, output: combined }));
38+
});
39+
}
40+
41+
function looksLikeOtpError(output) {
42+
// npm commonly uses EOTP; other tooling prints "one-time password" messages.
43+
return /EOTP\b|one[- ]time password|otp\b/i.test(output);
44+
}
45+
46+
function looksLikeAuthError(output) {
47+
return /ENEEDAUTH\b|npm ERR!\s+need auth|not authorized|auth token/i.test(output);
48+
}
49+
50+
async function promptOtp() {
51+
const rl = createInterface({ input: process.stdin, output: process.stdout });
52+
try {
53+
const otp = (await rl.question("NPM OTP required. Enter OTP: ")).trim();
54+
return otp;
55+
} finally {
56+
rl.close();
57+
}
58+
}
59+
60+
async function main() {
61+
// Allow passing flags through to `changeset publish` (e.g. --tag next, --dry-run).
62+
const publishArgs = process.argv.slice(2);
63+
64+
// Build first (same behavior as old `bun run build && changeset publish`).
65+
const build = await run("bun", ["run", "build"]);
66+
if (build.code !== 0) process.exit(build.code);
67+
68+
// First publish attempt.
69+
const first = await run("bunx", ["changeset", "publish", ...publishArgs]);
70+
if (first.code === 0) return;
71+
72+
// If it's an auth/token issue, fail fast with a helpful message.
73+
if (looksLikeAuthError(first.output)) {
74+
console.error(
75+
"\nPublish failed due to npm auth. Ensure you're logged in (npm login) or set NPM_TOKEN.\n"
76+
);
77+
process.exit(first.code);
78+
}
79+
80+
// OTP flow: only prompt in interactive terminals and if OTP isn't already set.
81+
const alreadyHasOtp = !!process.env.NPM_CONFIG_OTP;
82+
const canPrompt = Boolean(process.stdin.isTTY && process.stdout.isTTY);
83+
84+
if (!alreadyHasOtp && canPrompt && looksLikeOtpError(first.output)) {
85+
const otp = await promptOtp();
86+
if (!otp) {
87+
console.error("No OTP entered; aborting publish.");
88+
process.exit(first.code);
89+
}
90+
91+
const retry = await run(
92+
"bunx",
93+
["changeset", "publish", ...publishArgs],
94+
{ env: { NPM_CONFIG_OTP: otp } }
95+
);
96+
process.exit(retry.code);
97+
}
98+
99+
if (looksLikeOtpError(first.output) && !canPrompt) {
100+
console.error(
101+
"\nPublish failed because npm requires an OTP, but this is a non-interactive shell.\n" +
102+
'Re-run locally in a TTY, or set NPM_CONFIG_OTP=123456, or use an npm automation token in CI.\n'
103+
);
104+
}
105+
106+
process.exit(first.code);
107+
}
108+
109+
main().catch((err) => {
110+
console.error(err);
111+
process.exit(1);
112+
});
113+
114+

0 commit comments

Comments
 (0)