@@ -6,7 +6,18 @@ import chalk from "chalk";
66import degit from "degit" ;
77import { getTemplate } from "./templates/index.js" ;
88import { PackageManager , getInstallCommand , getRunCommand } from "./utils/packageManager.js" ;
9- import { SPACETIME_VERSIONS , SERVER_CONFIG } from "./config.js" ;
9+
10+ const SPACETIME_VERSIONS = {
11+ SDK : "^1.3.1" ,
12+ RUNTIME : "1.3.*" ,
13+ CLI : "1.3" ,
14+ } as const ;
15+
16+ const SERVER_CONFIG = {
17+ LOCAL_PORT : 3000 ,
18+ MAINCLOUD_URI : "wss://maincloud.spacetimedb.com" ,
19+ LOCAL_URI : "ws://localhost:3000" ,
20+ } as const ;
1021
1122const SPACETIME_SDK_PACKAGE = "@clockworklabs/spacetimedb-sdk" ;
1223
@@ -46,14 +57,9 @@ export async function createProject(options: CreateProjectOptions): Promise<bool
4657 console . error ( chalk . red ( "Failed to create project:" ) ) ;
4758 console . error ( chalk . red ( error instanceof Error ? error . message : String ( error ) ) ) ;
4859
49- try {
50- await fs . remove ( root ) ;
51- console . log ( chalk . gray ( "Cleaned up incomplete project files" ) ) ;
52- } catch ( cleanupError ) {
53- console . warn ( chalk . yellow ( "Warning: Failed to clean up project files" ) ) ;
54- console . warn ( chalk . gray ( `You may need to manually remove: ${ root } ` ) ) ;
55- console . error ( "Cleanup error details:" , cleanupError ) ;
56- }
60+ await fs
61+ . remove ( root )
62+ . catch ( ( ) => console . warn ( chalk . yellow ( `Warning: Manual cleanup needed: ${ root } ` ) ) ) ;
5763
5864 return false ;
5965 }
@@ -139,105 +145,77 @@ async function configureServer(root: string, name: string, serverLanguage: strin
139145 }
140146}
141147
142- async function updateConfigFile (
143- filePath : string ,
144- updates : Record < string , string > ,
145- warnMessage : string ,
146- ) : Promise < void > {
148+ // existing rust and C# example servers configs need to be updated to work with the project setup
149+ async function configureRustServer ( serverDir : string , name : string ) {
150+ const safeName = name . replace ( / [ ^ a - z A - Z 0 - 9 _ ] / g , "_" ) ;
151+ const cargoPath = path . join ( serverDir , "Cargo.toml" ) ;
152+
147153 try {
148- if ( ! ( await fs . pathExists ( filePath ) ) ) {
149- console . warn (
150- chalk . gray ( `Warning: ${ path . basename ( filePath ) } not found, skipping configuration` ) ,
154+ if ( await fs . pathExists ( cargoPath ) ) {
155+ let content = await fs . readFile ( cargoPath , "utf-8" ) ;
156+ content = content . replace ( / ^ n a m e = .* $ / m, `name = "${ safeName } "` ) ;
157+ content = content . replace ( / e d i t i o n \. w o r k s p a c e = t r u e / g, 'edition = "2021"' ) ;
158+ content = content . replace ( / l o g \. w o r k s p a c e = t r u e / g, 'log = "0.4"' ) ;
159+ content = content . replace (
160+ / s p a c e t i m e d b = \{ p a t h = " .* " \} / g,
161+ `spacetimedb = "${ SPACETIME_VERSIONS . CLI } "` ,
151162 ) ;
152- return ;
153- }
154-
155- let content = await fs . readFile ( filePath , "utf-8" ) ;
156-
157- for ( const [ pattern , replacement ] of Object . entries ( updates ) ) {
158- const regex = new RegExp ( pattern , "g" ) ;
159- content = content . replace ( regex , replacement ) ;
163+ content = content . replace (
164+ / s p a c e t i m e d b - l i b = \{ p a t h = " .* " \} / g,
165+ `spacetimedb-lib = "${ SPACETIME_VERSIONS . CLI } "` ,
166+ ) ;
167+ await fs . writeFile ( cargoPath , content ) ;
160168 }
161-
162- await fs . writeFile ( filePath , content ) ;
163169 } catch ( error ) {
164- console . warn ( chalk . gray ( warnMessage ) , error ) ;
170+ console . warn ( chalk . gray ( "Warning: Could not update Cargo.toml" ) , error ) ;
165171 }
166172}
167173
168- // existing rust and C# example servers configs need to be updated to work with the project setup
169- async function configureRustServer ( serverDir : string , name : string ) {
170- const safeName = name . replace ( / [ ^ a - z A - Z 0 - 9 _ ] / g, "_" ) ;
171-
172- await updateConfigFile (
173- path . join ( serverDir , "Cargo.toml" ) ,
174- {
175- "^name = .*$" : `name = "${ safeName } "` ,
176- "edition\\.workspace = true" : 'edition = "2021"' ,
177- "log\\.workspace = true" : 'log = "0.4"' ,
178- 'spacetimedb = \\{ path = ".*" \\}' : `spacetimedb = "${ SPACETIME_VERSIONS . CLI } "` ,
179- 'spacetimedb-lib = \\{ path = ".*" \\}' : `spacetimedb-lib = "${ SPACETIME_VERSIONS . CLI } "` ,
180- } ,
181- "Warning: Could not update Cargo.toml" ,
182- ) ;
183- }
184-
185174async function configureCsharpServer ( serverDir : string ) {
186- await updateConfigFile (
187- path . join ( serverDir , "StdbModule.csproj" ) ,
188- {
189- '<PackageReference Include="SpacetimeDB\\.Runtime" Version="[^"]*" \\/>' : `<PackageReference Include="SpacetimeDB.Runtime" Version="${ SPACETIME_VERSIONS . RUNTIME } " />` ,
190- } ,
191- "Warning: Could not update .csproj file" ,
192- ) ;
175+ const csprojPath = path . join ( serverDir , "StdbModule.csproj" ) ;
176+
177+ try {
178+ if ( await fs . pathExists ( csprojPath ) ) {
179+ let content = await fs . readFile ( csprojPath , "utf-8" ) ;
180+ content = content . replace (
181+ / < P a c k a g e R e f e r e n c e I n c l u d e = " S p a c e t i m e D B \. R u n t i m e " V e r s i o n = " [ ^ " ] * " \/ > / g,
182+ `<PackageReference Include="SpacetimeDB.Runtime" Version="${ SPACETIME_VERSIONS . RUNTIME } " />` ,
183+ ) ;
184+ await fs . writeFile ( csprojPath , content ) ;
185+ }
186+ } catch ( error ) {
187+ console . warn ( chalk . gray ( "Warning: Could not update .csproj file" ) , error ) ;
188+ }
193189}
194190
195191async function updateClientConfig ( root : string , name : string , useLocal : boolean ) {
196192 const targetUri = useLocal ? SERVER_CONFIG . LOCAL_URI : SERVER_CONFIG . MAINCLOUD_URI ;
193+ const appPath = path . join ( root , "client/src/App.tsx" ) ;
197194
198- await updateConfigFile (
199- path . join ( root , "client/src/App.tsx" ) ,
200- {
201- "\\.withModuleName\\(['\"`][^'\"`]*['\"`]\\)" : `.withModuleName('${ name } ')` ,
202- "\\.withUri\\(['\"`]ws:\\/\\/localhost:3000['\"`]\\)" : `.withUri('${ targetUri } ')` ,
203- } ,
204- "Warning: Could not update client config" ,
205- ) ;
195+ try {
196+ if ( await fs . pathExists ( appPath ) ) {
197+ let content = await fs . readFile ( appPath , "utf-8" ) ;
198+ content = content . replace (
199+ / \. w i t h M o d u l e N a m e \( [ ' " ` ] [ ^ ' " ` ] * [ ' " ` ] \) / g,
200+ `.withModuleName('${ name } ')` ,
201+ ) ;
202+ content = content . replace (
203+ / \. w i t h U r i \( [ ' " ` ] w s : \/ \/ l o c a l h o s t : 3 0 0 0 [ ' " ` ] \) / g,
204+ `.withUri('${ targetUri } ')` ,
205+ ) ;
206+ await fs . writeFile ( appPath , content ) ;
207+ }
208+ } catch ( error ) {
209+ console . warn ( chalk . gray ( "Warning: Could not update client config" ) , error ) ;
210+ }
206211}
207212
208213async function installDependencies ( root : string , packageManager : PackageManager ) : Promise < void > {
209- const args = getInstallCommand ( packageManager ) ;
210214 const clientDir = path . join ( root , "client" ) ;
211-
212215 if ( ! ( await fs . pathExists ( clientDir ) ) ) {
213216 throw new Error ( `Client directory not found: ${ clientDir } ` ) ;
214217 }
215-
216- return new Promise ( ( resolve , reject ) => {
217- const child = spawn ( packageManager , args , {
218- cwd : clientDir ,
219- stdio : "inherit" ,
220- } ) ;
221-
222- const timeout = setTimeout ( ( ) => {
223- child . kill ( "SIGTERM" ) ;
224- reject ( new Error ( `${ packageManager } install timeout` ) ) ;
225- } , 600000 ) ; // 10 min
226-
227- child . on ( "close" , ( code ) => {
228- clearTimeout ( timeout ) ;
229- if ( code !== 0 ) {
230- reject ( new Error ( `${ packageManager } install failed with exit code ${ code } ` ) ) ;
231- return ;
232- }
233- resolve ( ) ;
234- } ) ;
235-
236- child . on ( "error" , ( error ) => {
237- clearTimeout ( timeout ) ;
238- reject ( new Error ( `Failed to spawn ${ packageManager } : ${ error . message } ` ) ) ;
239- } ) ;
240- } ) ;
218+ await runCommand ( packageManager , getInstallCommand ( packageManager ) , clientDir , 600000 ) ; // 10 min
241219}
242220
243221async function setupSpacetimeDB (
@@ -263,19 +241,22 @@ async function setupSpacetimeDB(
263241 spinner . start ( `Publishing to ${ target } ...` ) ;
264242 spinner . stop ( ) ;
265243 console . log ( ) ;
266- await runCommand (
267- "spacetime" ,
268- [
269- "publish" ,
270- "--project-path" ,
271- "server" ,
272- "--server" ,
273- useLocal ? "local" : "maincloud" ,
274- "--yes" ,
275- name ,
276- ] ,
277- root ,
278- ) ;
244+ const publishArgs = [
245+ "publish" ,
246+ "--project-path" ,
247+ "server" ,
248+ "--server" ,
249+ useLocal ? "local" : "maincloud" ,
250+ ] ;
251+
252+ if ( useLocal ) {
253+ publishArgs . push ( "--anonymous" ) ;
254+ } else {
255+ publishArgs . push ( "--yes" ) ;
256+ }
257+ publishArgs . push ( name ) ;
258+
259+ await runCommand ( "spacetime" , publishArgs , root ) ;
279260 spinner . start ( "Generating module bindings..." ) ;
280261 spinner . stop ( ) ;
281262 console . log ( ) ;
@@ -317,7 +298,12 @@ async function ensureLocalServer(spinner: any) {
317298
318299 if ( ! isRunning ) {
319300 spinner . text = "Starting local SpacetimeDB server..." ;
320- spawn ( "spacetime" , [ "start" ] , { detached : true , stdio : "ignore" } ) ;
301+ const child = spawn ( "spacetime" , [ "start" ] , {
302+ detached : true ,
303+ stdio : "ignore" ,
304+ } ) ;
305+
306+ child . unref ( ) ;
321307
322308 await new Promise ( ( resolve ) => setTimeout ( resolve , 3000 ) ) ;
323309
@@ -376,7 +362,12 @@ function printInstructions(
376362 console . log ( message ) ;
377363}
378364
379- function runCommand ( command : string , args : string [ ] , cwd : string ) : Promise < void > {
365+ function runCommand (
366+ command : string ,
367+ args : string [ ] ,
368+ cwd : string ,
369+ timeoutMs : number = 300000 , // 5 min
370+ ) : Promise < void > {
380371 const SAFE_COMMANDS = [ "spacetime" , "npm" , "yarn" , "pnpm" , "bun" ] ;
381372 if ( ! SAFE_COMMANDS . includes ( command ) ) {
382373 throw new Error ( `Unsafe command: ${ command } ` ) ;
@@ -388,7 +379,7 @@ function runCommand(command: string, args: string[], cwd: string): Promise<void>
388379 const timeout = setTimeout ( ( ) => {
389380 childProcess . kill ( "SIGTERM" ) ;
390381 reject ( new Error ( `Command timeout: ${ command } ` ) ) ;
391- } , 300000 ) ; // 5 min
382+ } , timeoutMs ) ;
392383
393384 childProcess . on ( "close" , ( code ) => {
394385 clearTimeout ( timeout ) ;
0 commit comments