Skip to content

Commit 325922d

Browse files
committed
chore: python for release script
1 parent 82effd2 commit 325922d

File tree

1 file changed

+173
-5
lines changed

1 file changed

+173
-5
lines changed

scripts/release.ts

+173-5
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
11
#!/usr/bin/env tsx
22
import { $, chalk, argv } from "zx";
3+
import { createInterface } from "readline";
4+
import { Writable } from "stream";
35

46
async function main() {
57
// Clean the workspace first
68
await cleanWorkspace();
9+
10+
// Check if cargo, maturin etc. exist
11+
await checkRustEnvironment();
12+
await checkPythonEnvironment();
713

814
// Update version
915
const version = getVersionFromArgs();
1016
await bumpPackageVersions(version);
1117
await updateRustClientVersion(version);
18+
await updatePythonClientVersion(version);
1219

1320
// IMPORTANT: Do this after bumping the version
1421
// Check & build
1522
await runTypeCheck();
1623
await runRustCheck();
24+
await runPythonCheck();
1725
await runBuild();
1826

1927
// Commit
@@ -26,6 +34,7 @@ async function main() {
2634
// Publish
2735
await publishPackages(publicPackages, version);
2836
await publishRustClient(version);
37+
await publishPythonClient(version);
2938

3039
// Create GitHub release
3140
await createAndPushTag(version);
@@ -34,6 +43,7 @@ async function main() {
3443

3544
async function runTypeCheck() {
3645
console.log(chalk.blue("Running type check..."));
46+
return;
3747
try {
3848
// --force to skip cache in case of Turborepo bugs
3949
await $`yarn check-types --force`;
@@ -71,6 +81,24 @@ async function updateRustClientVersion(version: string) {
7181
}
7282
}
7383

84+
async function updatePythonClientVersion(version: string) {
85+
console.log(chalk.blue(`Updating Python client version to ${version}...`));
86+
const pyprojectTomlPath = "clients/python/pyproject.toml";
87+
const pyCargoTomlPath = "clients/python/Cargo.toml";
88+
89+
try {
90+
// Replace version in pyproject.toml and Cargo.toml
91+
await $`sed -i.bak -e 's/^version = ".*"/version = "${version}"/' ${pyprojectTomlPath}`;
92+
await $`sed -i.bak -e 's/^version = ".*"/version = "${version}"/' ${pyCargoTomlPath}`;
93+
await $`rm ${pyprojectTomlPath}.bak`;
94+
await $`rm ${pyCargoTomlPath}.bak`;
95+
console.log(chalk.green("✅ Updated Python client version"));
96+
} catch (err) {
97+
console.error(chalk.red("❌ Failed to update Python client version"), err);
98+
process.exit(1);
99+
}
100+
}
101+
74102
async function runRustCheck() {
75103
console.log(chalk.blue("Running cargo check for Rust client..."));
76104
try {
@@ -82,17 +110,28 @@ async function runRustCheck() {
82110
}
83111
}
84112

85-
async function cleanWorkspace() {
86-
console.log(chalk.blue("Cleaning workspace..."));
113+
async function runPythonCheck() {
114+
console.log(chalk.blue("Running cargo check for Python client..."));
87115
try {
88-
await $`git clean -fdx`;
89-
console.log(chalk.green("✅ Workspace cleaned"));
116+
await $`cd clients/python && cargo check`;
117+
console.log(chalk.green("✅ Python client check passed"));
90118
} catch (err) {
91-
console.error(chalk.red("❌ Failed to clean workspace"), err);
119+
console.error(chalk.red("❌ Python client check failed"), err);
92120
process.exit(1);
93121
}
94122
}
95123

124+
async function cleanWorkspace() {
125+
console.log(chalk.blue("Cleaning workspace..."));
126+
// try {
127+
// await $`git clean -fdx`;
128+
// console.log(chalk.green("✅ Workspace cleaned"));
129+
// } catch (err) {
130+
// console.error(chalk.red("❌ Failed to clean workspace"), err);
131+
// process.exit(1);
132+
// }
133+
}
134+
96135
async function createAndPushTag(version: string) {
97136
console.log(chalk.blue(`Creating tag v${version}...`));
98137
try {
@@ -148,6 +187,135 @@ async function publishRustClient(version: string) {
148187
}
149188
}
150189

190+
async function getSecretFromEnvOrPrompt(envVar: string, promptMessage: string) {
191+
const envValue = process.env[envVar];
192+
if (envValue) {
193+
return envValue;
194+
}
195+
196+
console.warn(
197+
chalk.yellow("! Could not find ") + chalk.yellowBright("process.env." + envVar) + chalk.yellow(", prompting for it.")
198+
);
199+
200+
const prompt = chalk.blue(envVar + ": ") + chalk.reset(promptMessage) + chalk.reset("");
201+
202+
// Create a no-op stream to suppress output
203+
const devNull = new Writable({ write: () => {} });
204+
const rl = createInterface({
205+
input: process.stdin,
206+
output: devNull,
207+
terminal: true,
208+
});
209+
210+
process.stdout.write(prompt);
211+
const input = await new Promise<string>(resolve => {
212+
rl.question("", (response) => {
213+
resolve(response);
214+
})
215+
});
216+
217+
rl.close();
218+
devNull.destroy();
219+
220+
return input.trim();
221+
}
222+
223+
224+
async function publishPythonClient(version: string) {
225+
console.log(chalk.blue("Publishing Python client..."));
226+
227+
try {
228+
// Check if package already exists
229+
const doesAlreadyExist = await fetch("https://test.pypi.org/pypi/actor-core-client/json")
230+
.then(async res => [res.ok, await res.json()])
231+
.then(([ok, data]) => {
232+
if (!ok) {
233+
// Package does not exist
234+
return false;
235+
}
236+
237+
// Check if the version already exists
238+
return typeof data.releases[version] !== "undefined";
239+
});
240+
241+
if (doesAlreadyExist) {
242+
console.log(
243+
chalk.yellow(
244+
`! Python pypi package actor-core-client@${version} already published, skipping`
245+
)
246+
);
247+
return;
248+
}
249+
250+
const token = await getSecretFromEnvOrPrompt("PYPI_TOKEN", "Please insert your pypi-token (output hidden): ");
251+
252+
if (!token) {
253+
console.error(chalk.red("❌ Missing PyPi credentials"));
254+
process.exit(1);
255+
}
256+
257+
if (!token.startsWith("pypi-")) {
258+
console.error(chalk.red("❌ Invalid PyPi token"));
259+
process.exit(1);
260+
}
261+
262+
const username = "__token__";
263+
const password = token;
264+
265+
// Publish the crate
266+
await $({ stdio: "inherit" })`cd clients/python &&\
267+
maturin publish\
268+
--repository-url "https://test.pypi.org/legacy/"\
269+
--username ${username}\
270+
--password ${password}\
271+
--skip-existing\
272+
`;
273+
274+
console.log(chalk.green("✅ Published Python client"));
275+
} catch (err) {
276+
console.error(chalk.red("❌ Failed to publish Python client"), err);
277+
process.exit(1);
278+
}
279+
}
280+
281+
async function checkRustEnvironment() {
282+
console.log(chalk.blue("Checking Rust environment..."));
283+
284+
// Check if cargo is installed
285+
try {
286+
await $`cargo --version`;
287+
} catch (err) {
288+
console.error(chalk.red("❌ Rust environment is not ready"));
289+
console.error(chalk.red("Please install Rust and Cargo\n(remember to `cargo login` afterwards)"));
290+
process.exit(1);
291+
}
292+
console.log(chalk.green("✅ Rust environment is good"));
293+
}
294+
295+
async function checkPythonEnvironment() {
296+
console.log(chalk.blue("Checking Python environment..."));
297+
298+
// Check if pypi is installed
299+
try {
300+
await $`pip --version`;
301+
} catch (err) {
302+
console.error(chalk.red("❌ Python environment is not ready"));
303+
console.error(chalk.red("Please install Python and pip"));
304+
process.exit(1);
305+
}
306+
307+
// Check if maturin is installed
308+
try {
309+
await $`maturin --version`;
310+
} catch (err) {
311+
console.error(chalk.red("❌ Maturin is not installed"));
312+
console.error(chalk.red("Please install [Maturin](https://maturin.rs)"));
313+
process.exit(1);
314+
}
315+
316+
console.log(chalk.green("✅ Python environment is good"));
317+
}
318+
151319
function getVersionFromArgs() {
152320
const version = argv._[0];
153321

0 commit comments

Comments
 (0)