Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ To build a new version and release it on NPM, follow these steps:
1. Verify that the playground bundle for the new version is now present on the settings tab in https://rescript-lang.org/try.
1. Run `npm info rescript` to verify that the new version is now present with tag "ci".
1. Test the new version.
1. Tag the new version as appropriate (`latest` or `next`): `npm dist-tag add rescript@<version> <tag>`
1. Tag all packages for the new version as appropriate (`latest` or `next`): `./scripts/npmRelease.js --version <version> --tag <tag>`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the lore here that this is such a labor intensive process?
Just curious.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Labor intensive, well, you should have seen how these processes worked in earlier times. 🙈

The current procedure allows one to test the finished packages for a final time using npm install rescript@ci, and then decide manually which tag to set.

Ideas for further simplification/automation are of course welcome. If the final tag latest/next is to be set by CI automatically on a tag build (instead of just the ci tag), it needs to know 100% when to use what, and especially avoid publishing versions from maintenance branches as next/latest.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, I could be wrong, but that sounds like a bit of conditional logic in a script?
npm view rescript --json can show the current latest, ci, next tags.
If a maintenance branch needs to publish another 11, it can detect that a higher major exists. Stuff like that.

Again, I'm unfamiliar with what we are trying to achieve in the first place.

1. Create a release entry for the version tag on the [Github Releases page](https://github.com/rescript-lang/rescript-compiler/releases), copying the changes from `CHANGELOG.md`.
1. Create a PR with the following changes to prepare for development of the next version:
- Increment the `EXPECTED_VERSION` number in `yarn.config.cjs` for the next version.
Expand Down
105 changes: 105 additions & 0 deletions scripts/npmRelease.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#!/usr/bin/env node
/**
* Tag a published version of the main ReScript packages with a given dist-tag.
*
* Usage:
* node scripts/npmRelease.js --version 12.0.1 --tag next
* node scripts/npmRelease.js --version 12.0.1 --tag latest --otp 123456
*
* - Runs `npm dist-tag add` for: rescript, @rescript/runtime, and all platform
* optional packages, reusing the same OTP so you only get prompted once.
* - Pass `--dry-run` to see the commands without executing them.
*/
import { spawn } from "node:child_process";
import process from "node:process";
import readline from "node:readline/promises";
import { parseArgs } from "node:util";

const packages = [
"rescript",
"@rescript/runtime",
"@rescript/darwin-arm64",
"@rescript/darwin-x64",
"@rescript/linux-arm64",
"@rescript/linux-x64",
"@rescript/win32-x64",
];

async function promptForOtp(existingOtp) {
if (existingOtp) {
return existingOtp;
}
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const answer = await rl.question("npm one-time password: ");
rl.close();
return answer.trim();
}

async function runDistTag(pkg, version, tag, otp, dryRun) {
const spec = `${pkg}@${version}`;
const args = ["dist-tag", "add", spec, tag, "--otp", otp];
if (dryRun) {
console.log(`[dry-run] npm ${args.join(" ")}`);
return;
}
console.log(`Tagging ${spec} as ${tag}...`);
await new Promise((resolve, reject) => {
const child = spawn("npm", args, { stdio: "inherit" });
child.on("exit", code => {
if (code === 0) {
resolve();
} else {
reject(new Error(`npm dist-tag failed for ${spec} (exit ${code})`));
}
});
child.on("error", reject);
});
}

async function main() {
try {
const { values } = parseArgs({
args: process.argv.slice(2),
strict: true,
options: {
version: { type: "string", short: "v" },
tag: { type: "string", short: "t" },
otp: { type: "string" },
"dry-run": { type: "boolean" },
},
});
if (!values.version || !values.tag) {
console.error(
"Usage: node scripts/npmRelease.js --version <version> --tag <tag> [--otp <code>] [--dry-run]",
);
process.exitCode = 1;
return;
}
const otp = await promptForOtp(values.otp);
if (!otp) {
throw new Error("OTP is required to publish dist-tags.");
}
for (const pkg of packages) {
await runDistTag(
pkg,
values.version,
values.tag,
otp,
Boolean(values["dry-run"]),
);
}
if (values["dry-run"]) {
console.log("Dry run complete.");
} else {
console.log("All packages tagged successfully.");
}
} catch (error) {
console.error(error.message || error);
process.exitCode = 1;
}
}

await main();
Loading