@castlekit/castle 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cli/onboarding.ts +48 -53
package/package.json
CHANGED
package/src/cli/onboarding.ts
CHANGED
|
@@ -332,6 +332,9 @@ export async function runOnboarding(): Promise<void> {
|
|
|
332
332
|
}
|
|
333
333
|
|
|
334
334
|
// Step 5: Create Castle config
|
|
335
|
+
const serverSpinner = p.spinner();
|
|
336
|
+
serverSpinner.start("Saving configuration...");
|
|
337
|
+
|
|
335
338
|
ensureCastleDir();
|
|
336
339
|
|
|
337
340
|
const config: CastleConfig = {
|
|
@@ -346,12 +349,9 @@ export async function runOnboarding(): Promise<void> {
|
|
|
346
349
|
};
|
|
347
350
|
|
|
348
351
|
writeConfig(config);
|
|
352
|
+
serverSpinner.message("Building Castle...");
|
|
349
353
|
|
|
350
|
-
|
|
351
|
-
const serverSpinner = p.spinner();
|
|
352
|
-
serverSpinner.start("Building Castle...");
|
|
353
|
-
|
|
354
|
-
const { spawn, execSync: execSyncChild } = await import("child_process");
|
|
354
|
+
const { spawn, exec, execSync: execSyncChild } = await import("child_process");
|
|
355
355
|
const { join } = await import("path");
|
|
356
356
|
const { writeFileSync: writeFile, mkdirSync: mkDir, readFileSync: readF } = await import("fs");
|
|
357
357
|
const { homedir: home } = await import("os");
|
|
@@ -360,14 +360,17 @@ export async function runOnboarding(): Promise<void> {
|
|
|
360
360
|
const logsDir = join(castleDir, "logs");
|
|
361
361
|
mkDir(logsDir, { recursive: true });
|
|
362
362
|
|
|
363
|
-
// Build for production
|
|
364
|
-
|
|
365
|
-
|
|
363
|
+
// Build for production (async so the spinner can animate)
|
|
364
|
+
const buildOk = await new Promise<boolean>((resolve) => {
|
|
365
|
+
const child = exec("npm run build", {
|
|
366
366
|
cwd: PROJECT_ROOT,
|
|
367
|
-
stdio: "ignore",
|
|
368
367
|
timeout: 120000,
|
|
369
368
|
});
|
|
370
|
-
|
|
369
|
+
child.on("close", (code) => resolve(code === 0));
|
|
370
|
+
child.on("error", () => resolve(false));
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
if (!buildOk) {
|
|
371
374
|
serverSpinner.stop(pc.red("Build failed"));
|
|
372
375
|
p.outro(pc.dim(`Try running ${BLUE("npm run build")} manually in the castle directory.`));
|
|
373
376
|
return;
|
|
@@ -377,17 +380,7 @@ export async function runOnboarding(): Promise<void> {
|
|
|
377
380
|
|
|
378
381
|
// Find node and next paths for the service
|
|
379
382
|
const nodePath = process.execPath;
|
|
380
|
-
|
|
381
|
-
// Locate next binary reliably (works for both local and global installs)
|
|
382
|
-
let nextBin: string;
|
|
383
|
-
try {
|
|
384
|
-
// npm bin gives the local node_modules/.bin directory
|
|
385
|
-
const binDir = execSyncChild("npm bin", { cwd: PROJECT_ROOT, encoding: "utf-8" }).trim();
|
|
386
|
-
nextBin = join(binDir, "next");
|
|
387
|
-
} catch {
|
|
388
|
-
// Fallback: try local node_modules directly
|
|
389
|
-
nextBin = join(PROJECT_ROOT, "node_modules", ".bin", "next");
|
|
390
|
-
}
|
|
383
|
+
const nextBin = join(PROJECT_ROOT, "node_modules", ".bin", "next");
|
|
391
384
|
|
|
392
385
|
// Castle port from config or default
|
|
393
386
|
const castlePort = String(config.server?.port || 3333);
|
|
@@ -395,18 +388,17 @@ export async function runOnboarding(): Promise<void> {
|
|
|
395
388
|
// Write PID file helper
|
|
396
389
|
const pidFile = join(castleDir, "server.pid");
|
|
397
390
|
|
|
398
|
-
// Kill any existing Castle server
|
|
391
|
+
// Kill any existing Castle server (by PID file)
|
|
399
392
|
try {
|
|
400
393
|
const existingPid = parseInt(readF(pidFile, "utf-8").trim(), 10);
|
|
401
394
|
if (Number.isInteger(existingPid) && existingPid > 0) {
|
|
402
395
|
process.kill(existingPid);
|
|
403
|
-
// Wait up to 3s for old process to die
|
|
404
396
|
for (let i = 0; i < 30; i++) {
|
|
405
397
|
try {
|
|
406
|
-
process.kill(existingPid, 0);
|
|
398
|
+
process.kill(existingPid, 0);
|
|
407
399
|
await new Promise((r) => setTimeout(r, 100));
|
|
408
400
|
} catch {
|
|
409
|
-
break;
|
|
401
|
+
break;
|
|
410
402
|
}
|
|
411
403
|
}
|
|
412
404
|
}
|
|
@@ -414,25 +406,19 @@ export async function runOnboarding(): Promise<void> {
|
|
|
414
406
|
// No existing server or already dead
|
|
415
407
|
}
|
|
416
408
|
|
|
417
|
-
//
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
writeFile(pidFile, String(server.pid));
|
|
409
|
+
// Kill anything else on the target port
|
|
410
|
+
try {
|
|
411
|
+
execSyncChild(`lsof -ti:${castlePort} | xargs kill -9 2>/dev/null`, {
|
|
412
|
+
stdio: "ignore",
|
|
413
|
+
timeout: 5000,
|
|
414
|
+
});
|
|
415
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
416
|
+
} catch {
|
|
417
|
+
// Nothing on port or lsof not available
|
|
427
418
|
}
|
|
428
|
-
server.unref();
|
|
429
419
|
|
|
430
420
|
// Install as a persistent service (auto-start on login)
|
|
431
421
|
if (process.platform === "darwin") {
|
|
432
|
-
// macOS: LaunchAgent
|
|
433
|
-
// We unload first to avoid conflicts, then load. KeepAlive is set to
|
|
434
|
-
// SuccessfulExit=false so launchd only restarts on crashes, not when
|
|
435
|
-
// we intentionally stop it.
|
|
436
422
|
const plistDir = join(home(), "Library", "LaunchAgents");
|
|
437
423
|
mkDir(plistDir, { recursive: true });
|
|
438
424
|
const plistPath = join(plistDir, "com.castlekit.castle.plist");
|
|
@@ -472,23 +458,16 @@ export async function runOnboarding(): Promise<void> {
|
|
|
472
458
|
</dict>
|
|
473
459
|
</dict>
|
|
474
460
|
</plist>`;
|
|
475
|
-
// Stop any running instance first, then kill our manually spawned one
|
|
476
|
-
// so launchd takes over as the sole process manager
|
|
477
461
|
try {
|
|
478
|
-
execSyncChild(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore" });
|
|
462
|
+
execSyncChild(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore", timeout: 10000 });
|
|
479
463
|
} catch { /* ignore */ }
|
|
480
464
|
writeFile(plistPath, plist);
|
|
481
465
|
try {
|
|
482
|
-
|
|
483
|
-
if (server.pid) {
|
|
484
|
-
try { process.kill(server.pid); } catch { /* already dead */ }
|
|
485
|
-
}
|
|
486
|
-
execSyncChild(`launchctl load "${plistPath}"`, { stdio: "ignore" });
|
|
466
|
+
execSyncChild(`launchctl load "${plistPath}"`, { stdio: "ignore", timeout: 10000 });
|
|
487
467
|
} catch {
|
|
488
|
-
// Non-fatal
|
|
468
|
+
// Non-fatal — fall back to spawning directly
|
|
489
469
|
}
|
|
490
470
|
} else if (process.platform === "linux") {
|
|
491
|
-
// Linux: systemd user service
|
|
492
471
|
const systemdDir = join(home(), ".config", "systemd", "user");
|
|
493
472
|
mkDir(systemdDir, { recursive: true });
|
|
494
473
|
const servicePath = join(systemdDir, "castle.service");
|
|
@@ -509,14 +488,30 @@ WantedBy=default.target
|
|
|
509
488
|
`;
|
|
510
489
|
writeFile(servicePath, service);
|
|
511
490
|
try {
|
|
512
|
-
execSyncChild("systemctl --user daemon-reload && systemctl --user enable --now castle.service", { stdio: "ignore" });
|
|
491
|
+
execSyncChild("systemctl --user daemon-reload && systemctl --user enable --now castle.service", { stdio: "ignore", timeout: 15000 });
|
|
513
492
|
} catch {
|
|
514
|
-
// Non-fatal
|
|
493
|
+
// Non-fatal
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// If no service manager started it, spawn directly
|
|
498
|
+
try {
|
|
499
|
+
await fetch(`http://localhost:${castlePort}`);
|
|
500
|
+
} catch {
|
|
501
|
+
// Server not up yet — spawn it directly as fallback
|
|
502
|
+
const server = spawn(nodePath, [nextBin, "start", "-p", castlePort], {
|
|
503
|
+
cwd: PROJECT_ROOT,
|
|
504
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
505
|
+
detached: true,
|
|
506
|
+
});
|
|
507
|
+
if (server.pid != null) {
|
|
508
|
+
writeFile(pidFile, String(server.pid));
|
|
515
509
|
}
|
|
510
|
+
server.unref();
|
|
516
511
|
}
|
|
517
512
|
|
|
518
513
|
// Wait for server to be ready
|
|
519
|
-
const maxWait =
|
|
514
|
+
const maxWait = 45000;
|
|
520
515
|
const startTime = Date.now();
|
|
521
516
|
let serverReady = false;
|
|
522
517
|
|