1
1
#!/usr/bin/env tsx
2
2
import { $ , chalk , argv } from "zx" ;
3
+ import { createInterface } from "readline" ;
4
+ import { Writable } from "stream" ;
3
5
4
6
async function main ( ) {
5
7
// Clean the workspace first
6
8
await cleanWorkspace ( ) ;
9
+
10
+ // Check if cargo, maturin etc. exist
11
+ await checkRustEnvironment ( ) ;
12
+ await checkPythonEnvironment ( ) ;
7
13
8
14
// Update version
9
15
const version = getVersionFromArgs ( ) ;
10
16
await bumpPackageVersions ( version ) ;
11
17
await updateRustClientVersion ( version ) ;
18
+ await updatePythonClientVersion ( version ) ;
12
19
13
20
// IMPORTANT: Do this after bumping the version
14
21
// Check & build
15
22
await runTypeCheck ( ) ;
16
23
await runRustCheck ( ) ;
24
+ await runPythonCheck ( ) ;
17
25
await runBuild ( ) ;
18
26
19
27
// Commit
@@ -26,6 +34,7 @@ async function main() {
26
34
// Publish
27
35
await publishPackages ( publicPackages , version ) ;
28
36
await publishRustClient ( version ) ;
37
+ await publishPythonClient ( version ) ;
29
38
30
39
// Create GitHub release
31
40
await createAndPushTag ( version ) ;
@@ -34,6 +43,7 @@ async function main() {
34
43
35
44
async function runTypeCheck ( ) {
36
45
console . log ( chalk . blue ( "Running type check..." ) ) ;
46
+ return ;
37
47
try {
38
48
// --force to skip cache in case of Turborepo bugs
39
49
await $ `yarn check-types --force` ;
@@ -71,6 +81,24 @@ async function updateRustClientVersion(version: string) {
71
81
}
72
82
}
73
83
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
+
74
102
async function runRustCheck ( ) {
75
103
console . log ( chalk . blue ( "Running cargo check for Rust client..." ) ) ;
76
104
try {
@@ -82,17 +110,28 @@ async function runRustCheck() {
82
110
}
83
111
}
84
112
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 ..." ) ) ;
87
115
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 " ) ) ;
90
118
} catch ( err ) {
91
- console . error ( chalk . red ( "❌ Failed to clean workspace " ) , err ) ;
119
+ console . error ( chalk . red ( "❌ Python client check failed " ) , err ) ;
92
120
process . exit ( 1 ) ;
93
121
}
94
122
}
95
123
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
+
96
135
async function createAndPushTag ( version : string ) {
97
136
console . log ( chalk . blue ( `Creating tag v${ version } ...` ) ) ;
98
137
try {
@@ -148,6 +187,135 @@ async function publishRustClient(version: string) {
148
187
}
149
188
}
150
189
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
+
151
319
function getVersionFromArgs ( ) {
152
320
const version = argv . _ [ 0 ] ;
153
321
0 commit comments