playcademy 0.13.1 → 0.13.3

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.
@@ -72,8 +72,6 @@ declare const CLI_FILES: {
72
72
  readonly AUTH_STORE: "auth.json";
73
73
  /** Games deployment info store */
74
74
  readonly GAMES_STORE: "games.json";
75
- /** Dev server PID file */
76
- readonly DEV_SERVER_PID: "dev-server.pid";
77
75
  /** Initial database file (before miniflare) */
78
76
  readonly INITIAL_DATABASE: "initial.sqlite";
79
77
  };
package/dist/constants.js CHANGED
@@ -35,8 +35,6 @@ var CLI_FILES = {
35
35
  AUTH_STORE: "auth.json",
36
36
  /** Games deployment info store */
37
37
  GAMES_STORE: "games.json",
38
- /** Dev server PID file */
39
- DEV_SERVER_PID: "dev-server.pid",
40
38
  /** Initial database file (before miniflare) */
41
39
  INITIAL_DATABASE: "initial.sqlite"
42
40
  };
package/dist/db.js CHANGED
@@ -39,8 +39,6 @@ var CLI_FILES = {
39
39
  AUTH_STORE: "auth.json",
40
40
  /** Games deployment info store */
41
41
  GAMES_STORE: "games.json",
42
- /** Dev server PID file */
43
- DEV_SERVER_PID: "dev-server.pid",
44
42
  /** Initial database file (before miniflare) */
45
43
  INITIAL_DATABASE: "initial.sqlite"
46
44
  };
package/dist/index.js CHANGED
@@ -2145,8 +2145,6 @@ var init_paths = __esm({
2145
2145
  AUTH_STORE: "auth.json",
2146
2146
  /** Games deployment info store */
2147
2147
  GAMES_STORE: "games.json",
2148
- /** Dev server PID file */
2149
- DEV_SERVER_PID: "dev-server.pid",
2150
2148
  /** Initial database file (before miniflare) */
2151
2149
  INITIAL_DATABASE: "initial.sqlite"
2152
2150
  };
@@ -3726,7 +3724,7 @@ import { program } from "commander";
3726
3724
 
3727
3725
  // src/commands/init/index.ts
3728
3726
  import { execSync as execSync3 } from "child_process";
3729
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
3727
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "fs";
3730
3728
  import { resolve as resolve10 } from "path";
3731
3729
 
3732
3730
  // ../../node_modules/@inquirer/core/dist/esm/lib/errors.js
@@ -5441,7 +5439,6 @@ import { checkbox, confirm, input, select } from "@inquirer/prompts";
5441
5439
  import { bold as bold3, cyan as cyan2 } from "colorette";
5442
5440
 
5443
5441
  // src/lib/init/scaffold.ts
5444
- init_src();
5445
5442
  init_constants2();
5446
5443
  init_core();
5447
5444
  init_loader2();
@@ -5454,21 +5451,15 @@ var playcademyGitignoreTemplate = loadTemplateString("playcademy-gitignore");
5454
5451
  async function scaffoldApiDirectory(apiDirectory, sampleRoutes) {
5455
5452
  const apiPath = resolve6(getWorkspace(), apiDirectory);
5456
5453
  const samplePath = join9(apiPath, "sample");
5457
- await runStep(
5458
- "Scaffolding API directory",
5459
- async () => {
5460
- if (!existsSync8(apiPath)) {
5461
- mkdirSync3(apiPath, { recursive: true });
5462
- }
5463
- if (!existsSync8(samplePath)) {
5464
- mkdirSync3(samplePath, { recursive: true });
5465
- }
5466
- for (const route of sampleRoutes) {
5467
- writeFileSync3(join9(samplePath, route.filename), route.template, "utf-8");
5468
- }
5469
- },
5470
- "API directory scaffolded"
5471
- );
5454
+ if (!existsSync8(apiPath)) {
5455
+ mkdirSync3(apiPath, { recursive: true });
5456
+ }
5457
+ if (!existsSync8(samplePath)) {
5458
+ mkdirSync3(samplePath, { recursive: true });
5459
+ }
5460
+ for (const route of sampleRoutes) {
5461
+ writeFileSync3(join9(samplePath, route.filename), route.template, "utf-8");
5462
+ }
5472
5463
  }
5473
5464
  function validateApiDirectoryDoesNotExist(value) {
5474
5465
  const dirPath = resolve6(getWorkspace(), value.trim());
@@ -5486,7 +5477,8 @@ function ensurePlaycademyGitignore() {
5486
5477
  const gitignorePath = join9(playcademyDir, ".gitignore");
5487
5478
  writeFileSync3(gitignorePath, playcademyGitignoreTemplate);
5488
5479
  }
5489
- async function scaffoldIntegrations(customRoutes, database, kv, gameName) {
5480
+ async function scaffoldIntegrations(gameName, options) {
5481
+ const { customRoutes, database, kv } = options;
5490
5482
  ensurePlaycademyGitignore();
5491
5483
  if (customRoutes) {
5492
5484
  const sampleRoutes = [
@@ -7570,58 +7562,10 @@ function displayRegisteredRoutes(integrations, customRoutes = []) {
7570
7562
  }
7571
7563
  }
7572
7564
 
7573
- // src/lib/dev/pid.ts
7574
- init_constants2();
7575
- init_core();
7576
- import { existsSync as existsSync15, readFileSync as readFileSync4, unlinkSync as unlinkSync2 } from "fs";
7577
- import { mkdir as mkdir4, unlink, writeFile as writeFile4 } from "fs/promises";
7578
- import { join as join15 } from "path";
7579
- function getDevServerPidPath() {
7580
- return join15(getWorkspace(), CLI_DIRECTORIES.WORKSPACE, CLI_FILES.DEV_SERVER_PID);
7581
- }
7582
- async function createDevServerPidFile() {
7583
- const pidPath = getDevServerPidFile();
7584
- const pidDir = join15(getWorkspace(), CLI_DIRECTORIES.WORKSPACE);
7585
- await mkdir4(pidDir, { recursive: true });
7586
- await writeFile4(pidPath, process.pid.toString());
7587
- }
7588
- async function removeDevServerPidFile() {
7589
- const pidPath = getDevServerPidFile();
7590
- if (existsSync15(pidPath)) {
7591
- await unlink(pidPath);
7592
- }
7593
- }
7594
- function isDevServerRunning() {
7595
- const pidPath = getDevServerPidFile();
7596
- if (!existsSync15(pidPath)) {
7597
- return false;
7598
- }
7599
- try {
7600
- const pid = parseInt(readFileSync4(pidPath, "utf8").trim(), 10);
7601
- if (process.platform === "win32") {
7602
- return true;
7603
- } else {
7604
- try {
7605
- process.kill(pid, 0);
7606
- return true;
7607
- } catch {
7608
- unlinkSync2(pidPath);
7609
- return false;
7610
- }
7611
- }
7612
- } catch {
7613
- unlinkSync2(pidPath);
7614
- return false;
7615
- }
7616
- }
7617
- function getDevServerPidFile() {
7618
- return getDevServerPidPath();
7619
- }
7620
-
7621
7565
  // src/lib/dev/reload.ts
7622
7566
  init_constants2();
7623
7567
  init_core();
7624
- import { join as join16, relative as relative3 } from "path";
7568
+ import { join as join15, relative as relative3 } from "path";
7625
7569
  import chokidar from "chokidar";
7626
7570
  import { bold as bold4, cyan as cyan3, dim as dim6, green as green3 } from "colorette";
7627
7571
  function formatTime() {
@@ -7638,9 +7582,9 @@ function startHotReload(onReload, options = {}) {
7638
7582
  const customRoutesConfig = options.config?.integrations?.customRoutes;
7639
7583
  const customRoutesDir = typeof customRoutesConfig === "object" && customRoutesConfig.directory || DEFAULT_API_ROUTES_DIRECTORY;
7640
7584
  const watchPaths = [
7641
- join16(workspace, customRoutesDir),
7642
- join16(workspace, "playcademy.config.js"),
7643
- join16(workspace, "playcademy.config.json")
7585
+ join15(workspace, customRoutesDir),
7586
+ join15(workspace, "playcademy.config.js"),
7587
+ join15(workspace, "playcademy.config.json")
7644
7588
  ];
7645
7589
  const watcher = chokidar.watch(watchPaths, {
7646
7590
  persistent: true,
@@ -7681,18 +7625,134 @@ function startHotReload(onReload, options = {}) {
7681
7625
 
7682
7626
  // src/lib/dev/server.ts
7683
7627
  init_src2();
7628
+ import { mkdir as mkdir4 } from "fs/promises";
7629
+ import { join as join17 } from "path";
7630
+ import { Miniflare } from "miniflare";
7631
+
7632
+ // ../utils/src/port.ts
7633
+ import { existsSync as existsSync15, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
7634
+ import { createServer as createServer2 } from "node:net";
7635
+ import { homedir as homedir3 } from "node:os";
7636
+ import { join as join16 } from "node:path";
7637
+ async function isPortAvailableOnHost(port, host) {
7638
+ return new Promise((resolve11) => {
7639
+ const server = createServer2();
7640
+ server.once("error", () => {
7641
+ resolve11(false);
7642
+ });
7643
+ server.once("listening", () => {
7644
+ server.close();
7645
+ resolve11(true);
7646
+ });
7647
+ server.listen(port, host);
7648
+ });
7649
+ }
7650
+ async function findAvailablePort(startPort = 4321) {
7651
+ const allInterfacesAvailable = await isPortAvailableOnHost(startPort, "0.0.0.0");
7652
+ const ipv6Available = await isPortAvailableOnHost(startPort, "::1");
7653
+ if (allInterfacesAvailable && ipv6Available) {
7654
+ return startPort;
7655
+ }
7656
+ return findAvailablePort(startPort + 1);
7657
+ }
7658
+ function getRegistryPath() {
7659
+ const home = homedir3();
7660
+ const dir = join16(home, ".playcademy");
7661
+ if (!existsSync15(dir)) {
7662
+ mkdirSync4(dir, { recursive: true });
7663
+ }
7664
+ return join16(dir, ".proc");
7665
+ }
7666
+ function readRegistry() {
7667
+ const registryPath = getRegistryPath();
7668
+ if (!existsSync15(registryPath)) {
7669
+ return {};
7670
+ }
7671
+ try {
7672
+ const content = readFileSync4(registryPath, "utf-8");
7673
+ return JSON.parse(content);
7674
+ } catch {
7675
+ return {};
7676
+ }
7677
+ }
7678
+ function writeRegistry(registry) {
7679
+ const registryPath = getRegistryPath();
7680
+ writeFileSync4(registryPath, JSON.stringify(registry, null, 2), "utf-8");
7681
+ }
7682
+ function getServerKey(type, port) {
7683
+ return `${type}-${port}`;
7684
+ }
7685
+ function writeServerInfo(type, info) {
7686
+ const registry = readRegistry();
7687
+ const key = getServerKey(type, info.port);
7688
+ registry[key] = info;
7689
+ writeRegistry(registry);
7690
+ }
7691
+ function readServerInfo(type, projectRoot) {
7692
+ const registry = readRegistry();
7693
+ const servers = Object.entries(registry).filter(([key]) => key.startsWith(`${type}-`)).map(([, info]) => info);
7694
+ if (servers.length === 0) {
7695
+ return null;
7696
+ }
7697
+ if (projectRoot) {
7698
+ const match = servers.find((s) => s.projectRoot === projectRoot);
7699
+ return match || null;
7700
+ }
7701
+ return servers[0] || null;
7702
+ }
7703
+ function isServerRunning(type, projectRoot) {
7704
+ const info = readServerInfo(type, projectRoot);
7705
+ if (!info) {
7706
+ return false;
7707
+ }
7708
+ try {
7709
+ if (process.platform === "win32") {
7710
+ return true;
7711
+ } else {
7712
+ process.kill(info.pid, 0);
7713
+ return true;
7714
+ }
7715
+ } catch {
7716
+ cleanupServerInfo(type, projectRoot);
7717
+ return false;
7718
+ }
7719
+ }
7720
+ function cleanupServerInfo(type, projectRoot, pid) {
7721
+ const registry = readRegistry();
7722
+ const keysToRemove = [];
7723
+ for (const [key, info] of Object.entries(registry)) {
7724
+ if (key.startsWith(`${type}-`)) {
7725
+ let matches = true;
7726
+ if (projectRoot && info.projectRoot !== projectRoot) {
7727
+ matches = false;
7728
+ }
7729
+ if (pid !== void 0 && info.pid !== pid) {
7730
+ matches = false;
7731
+ }
7732
+ if (matches) {
7733
+ keysToRemove.push(key);
7734
+ }
7735
+ }
7736
+ }
7737
+ for (const key of keysToRemove) {
7738
+ delete registry[key];
7739
+ }
7740
+ if (keysToRemove.length > 0) {
7741
+ writeRegistry(registry);
7742
+ }
7743
+ }
7744
+
7745
+ // src/lib/dev/server.ts
7684
7746
  init_constants2();
7685
7747
  init_loader();
7686
7748
  init_core();
7687
- import { mkdir as mkdir5 } from "fs/promises";
7688
- import { join as join17 } from "path";
7689
- import { Miniflare } from "miniflare";
7690
7749
  async function startDevServer(options) {
7691
7750
  const {
7692
- port,
7751
+ port: preferredPort,
7693
7752
  config: providedConfig,
7694
7753
  platformUrl = process.env.PLAYCADEMY_BASE_URL || "http://localhost:5174"
7695
7754
  } = options;
7755
+ const port = await findAvailablePort(preferredPort);
7696
7756
  const config = providedConfig ?? await loadConfig();
7697
7757
  const hasSandboxTimebackCreds = !!process.env.TIMEBACK_API_CLIENT_ID;
7698
7758
  const devConfig = config.integrations?.timeback && !hasSandboxTimebackCreds ? { ...config, integrations: { ...config.integrations, timeback: void 0 } } : config;
@@ -7727,12 +7787,13 @@ async function startDevServer(options) {
7727
7787
  if (hasDatabase) {
7728
7788
  await initializeDatabase(mf);
7729
7789
  }
7790
+ await writeBackendServerInfo(port);
7730
7791
  return mf;
7731
7792
  }
7732
7793
  async function ensureDatabaseDirectory() {
7733
7794
  const dbDir = join17(getWorkspace(), CLI_DIRECTORIES.DATABASE);
7734
7795
  try {
7735
- await mkdir5(dbDir, { recursive: true });
7796
+ await mkdir4(dbDir, { recursive: true });
7736
7797
  } catch (error) {
7737
7798
  throw new Error(`Failed to create database directory: ${getErrorMessage(error)}`);
7738
7799
  }
@@ -7741,7 +7802,7 @@ async function ensureDatabaseDirectory() {
7741
7802
  async function ensureKvDirectory() {
7742
7803
  const kvDir = join17(getWorkspace(), CLI_DIRECTORIES.KV);
7743
7804
  try {
7744
- await mkdir5(kvDir, { recursive: true });
7805
+ await mkdir4(kvDir, { recursive: true });
7745
7806
  } catch (error) {
7746
7807
  throw new Error(`Failed to create KV directory: ${getErrorMessage(error)}`);
7747
7808
  }
@@ -7750,7 +7811,15 @@ async function ensureKvDirectory() {
7750
7811
  async function initializeDatabase(mf) {
7751
7812
  const d1 = await mf.getD1Database("DB");
7752
7813
  await d1.exec("SELECT 1");
7753
- await createDevServerPidFile();
7814
+ }
7815
+ async function writeBackendServerInfo(port) {
7816
+ writeServerInfo("backend", {
7817
+ pid: process.pid,
7818
+ port,
7819
+ url: `http://localhost:${port}/api`,
7820
+ startedAt: Date.now(),
7821
+ projectRoot: getWorkspace()
7822
+ });
7754
7823
  }
7755
7824
 
7756
7825
  // src/lib/timeback/cleanup.ts
@@ -7816,7 +7885,7 @@ function displayResourcesStatus(resources, logger2) {
7816
7885
  }
7817
7886
 
7818
7887
  // src/commands/init/config.ts
7819
- import { writeFileSync as writeFileSync4 } from "fs";
7888
+ import { writeFileSync as writeFileSync5 } from "fs";
7820
7889
  import { resolve as resolve9 } from "path";
7821
7890
  init_file_loader();
7822
7891
  init_constants2();
@@ -7868,7 +7937,7 @@ var configCommand = new Command("config").description("Create playcademy.config
7868
7937
  emoji: gameInfo.emoji,
7869
7938
  timeback: timebackConfig ?? void 0
7870
7939
  });
7871
- writeFileSync4(resolve9(getWorkspace(), configFileName), configContent, "utf-8");
7940
+ writeFileSync5(resolve9(getWorkspace(), configFileName), configContent, "utf-8");
7872
7941
  displayConfigSuccess(!!timebackConfig);
7873
7942
  } catch (error) {
7874
7943
  logger.newLine();
@@ -7934,27 +8003,35 @@ var initCommand = new Command2("init").description("Initialize a playcademy.conf
7934
8003
  kv,
7935
8004
  customRoutes
7936
8005
  } = await promptForIntegrations();
7937
- let depsAdded = false;
7938
- const hasPackageJsonFile = hasPackageJson();
7939
- if (hasPackageJsonFile) {
7940
- logger.newLine();
7941
- const sdkAdded = await addPlaycademySdk();
7942
- if (sdkAdded) depsAdded = true;
7943
- }
7944
- if (customRoutes || database) {
7945
- logger.newLine();
7946
- const scaffoldDepsAdded = await scaffoldIntegrations(
7947
- customRoutes,
7948
- database,
7949
- kv,
7950
- gameInfo.name
7951
- );
7952
- if (scaffoldDepsAdded) depsAdded = true;
7953
- }
7954
- if (depsAdded) {
7955
- logger.newLine();
7956
- await installDependencies();
7957
- }
8006
+ logger.newLine();
8007
+ await runStep(
8008
+ "Setting up project",
8009
+ async () => {
8010
+ let depsAdded = false;
8011
+ const hasPackageJsonFile = hasPackageJson();
8012
+ if (hasPackageJsonFile) {
8013
+ const sdkAdded = await addPlaycademySdk();
8014
+ if (sdkAdded) depsAdded = true;
8015
+ }
8016
+ if (customRoutes || database) {
8017
+ const scaffoldOptions = { customRoutes, database, kv };
8018
+ const scaffoldDepsAdded = await scaffoldIntegrations(
8019
+ gameInfo.name,
8020
+ scaffoldOptions
8021
+ );
8022
+ if (scaffoldDepsAdded) depsAdded = true;
8023
+ }
8024
+ if (depsAdded) {
8025
+ const pm = detectPackageManager(getWorkspace());
8026
+ const installCmd = getInstallCommand(pm);
8027
+ execSync3(installCmd, {
8028
+ cwd: getWorkspace(),
8029
+ stdio: ["ignore", "ignore", "ignore"]
8030
+ });
8031
+ }
8032
+ },
8033
+ "Project configured"
8034
+ );
7958
8035
  logger.newLine();
7959
8036
  const configContent = configFormat === "js" ? generateJsConfig({
7960
8037
  name: gameInfo.name,
@@ -7973,7 +8050,7 @@ var initCommand = new Command2("init").description("Initialize a playcademy.conf
7973
8050
  kv: kv ?? void 0,
7974
8051
  timeback: timebackConfig ?? void 0
7975
8052
  });
7976
- writeFileSync5(resolve10(getWorkspace(), configFileName), configContent, "utf-8");
8053
+ writeFileSync6(resolve10(getWorkspace(), configFileName), configContent, "utf-8");
7977
8054
  displaySuccessMessage({
7978
8055
  configFileName,
7979
8056
  apiDirectory: customRoutes?.directory ?? null,
@@ -7995,20 +8072,9 @@ async function addPlaycademySdk() {
7995
8072
  }
7996
8073
  if (!pkg.dependencies) pkg.dependencies = {};
7997
8074
  pkg.dependencies["@playcademy/sdk"] = "latest";
7998
- writeFileSync5(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
8075
+ writeFileSync6(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
7999
8076
  return true;
8000
8077
  }
8001
- async function installDependencies() {
8002
- const pm = detectPackageManager(getWorkspace());
8003
- const installCmd = getInstallCommand(pm);
8004
- await runStep(
8005
- "Installing dependencies...",
8006
- async () => {
8007
- execSync3(installCmd, { cwd: getWorkspace(), stdio: ["ignore", "ignore", "ignore"] });
8008
- },
8009
- "Dependencies installed"
8010
- );
8011
- }
8012
8078
  initCommand.addCommand(configCommand);
8013
8079
 
8014
8080
  // src/commands/login.ts
@@ -8596,7 +8662,43 @@ var getStatusCommand = new Command11("status").description("Check your developer
8596
8662
 
8597
8663
  // src/commands/dev/server.ts
8598
8664
  import { blueBright as blueBright4, underline as underline2 } from "colorette";
8665
+ function setupCleanupHandlers(workspace, getServer) {
8666
+ let isShuttingDown = false;
8667
+ const cleanup = () => {
8668
+ if (isShuttingDown) return;
8669
+ isShuttingDown = true;
8670
+ cleanupServerInfo("backend", workspace, process.pid);
8671
+ const server = getServer();
8672
+ if (server) {
8673
+ server.dispose().then(() => process.exit(0)).catch(() => process.exit(1));
8674
+ } else {
8675
+ process.exit(0);
8676
+ }
8677
+ };
8678
+ process.on("SIGINT", cleanup);
8679
+ process.on("SIGTERM", cleanup);
8680
+ }
8681
+ async function setupServerHotReload(serverRef, port, workspace, config, loggerEnabled) {
8682
+ return startHotReload(
8683
+ async () => {
8684
+ if (serverRef.current) {
8685
+ await serverRef.current.dispose();
8686
+ }
8687
+ const newConfig = await loadConfig();
8688
+ serverRef.current = await startDevServer({
8689
+ port,
8690
+ config: newConfig,
8691
+ logger: loggerEnabled
8692
+ });
8693
+ await discoverRoutes(getCustomRoutesDirectory(workspace, newConfig));
8694
+ },
8695
+ { config }
8696
+ );
8697
+ }
8599
8698
  async function runDevServer(options) {
8699
+ const workspace = getWorkspace();
8700
+ const serverRef = { current: null };
8701
+ setupCleanupHandlers(workspace, () => serverRef.current);
8600
8702
  try {
8601
8703
  logger.newLine();
8602
8704
  const config = await loadConfig();
@@ -8614,52 +8716,27 @@ async function runDevServer(options) {
8614
8716
  return;
8615
8717
  }
8616
8718
  const port = parseInt(options.port, 10);
8617
- let server = await startDevServer({
8719
+ const server = await startDevServer({
8618
8720
  port,
8619
8721
  config,
8620
8722
  logger: options.logger !== false
8621
8723
  });
8622
- const hotReload = options.reload !== false;
8724
+ serverRef.current = server;
8623
8725
  logger.success(`Game API started: ${blueBright4(underline2(`http://localhost:${port}/api`))}`);
8624
8726
  logger.newLine();
8625
- const workspace = getWorkspace();
8626
8727
  const customRoutesDir = getCustomRoutesDirectory(workspace, config);
8627
- let customRoutes = await discoverRoutes(customRoutesDir);
8728
+ const customRoutes = await discoverRoutes(customRoutesDir);
8628
8729
  displayRegisteredRoutes(config.integrations, customRoutes);
8629
8730
  logger.newLine();
8630
- if (hotReload) {
8631
- startHotReload(
8632
- async () => {
8633
- await server.dispose();
8634
- clearModuleCache();
8635
- const newConfig = await loadConfig();
8636
- server = await startDevServer({
8637
- port,
8638
- config: newConfig,
8639
- logger: options.logger !== false
8640
- });
8641
- const newCustomRoutesDir = getCustomRoutesDirectory(workspace, newConfig);
8642
- customRoutes = await discoverRoutes(newCustomRoutesDir);
8643
- },
8644
- { config }
8645
- );
8731
+ if (options.reload !== false) {
8732
+ await setupServerHotReload(serverRef, port, workspace, config, options.logger !== false);
8646
8733
  }
8647
8734
  logger.remark(`Press ${underline2("ctrl+c")} to stop`);
8648
8735
  logger.newLine();
8649
- process.on("SIGINT", async () => {
8650
- await removeDevServerPidFile();
8651
- process.exit(0);
8652
- });
8653
- process.on("SIGTERM", async () => {
8654
- await removeDevServerPidFile();
8655
- process.exit(0);
8656
- });
8657
8736
  } catch (error) {
8658
8737
  logAndExit(error, logger, { prefix: "Failed to start dev server" });
8659
8738
  }
8660
8739
  }
8661
- function clearModuleCache() {
8662
- }
8663
8740
 
8664
8741
  // src/commands/dev/index.ts
8665
8742
  var devCommand = new Command12("dev").description("Start local backend development server").option("-p, --port <port>", "Backend server port", String(DEFAULT_PORTS.BACKEND)).option("--no-reload", "Disable hot reload").option("--no-logger", "Disable HTTP request logging").action(runDevServer);
@@ -8771,7 +8848,7 @@ async function runDbDiff() {
8771
8848
  // src/commands/db/init.ts
8772
8849
  init_file_loader();
8773
8850
  init_constants2();
8774
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
8851
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync7 } from "fs";
8775
8852
  import { input as input6 } from "@inquirer/prompts";
8776
8853
  async function runDbInit() {
8777
8854
  try {
@@ -8828,7 +8905,7 @@ async function runDbInit() {
8828
8905
  );
8829
8906
  }
8830
8907
  }
8831
- writeFileSync6(configFile.path, updatedContent, "utf-8");
8908
+ writeFileSync7(configFile.path, updatedContent, "utf-8");
8832
8909
  logger.success(`Updated ${configFile.path.split("/").pop()}`);
8833
8910
  }
8834
8911
  logger.newLine();
@@ -8850,12 +8927,12 @@ async function runDbInit() {
8850
8927
  // src/commands/db/reset.ts
8851
8928
  init_src2();
8852
8929
  init_src();
8853
- init_constants2();
8854
8930
  import { spawn } from "child_process";
8855
8931
  import { existsSync as existsSync16, rmSync as rmSync2 } from "fs";
8856
8932
  import { join as join18 } from "path";
8857
8933
  import { confirm as confirm6 } from "@inquirer/prompts";
8858
8934
  import { Miniflare as Miniflare2 } from "miniflare";
8935
+ init_constants2();
8859
8936
  async function runDbReset() {
8860
8937
  try {
8861
8938
  const workspace = getWorkspace();
@@ -8868,7 +8945,7 @@ async function runDbReset() {
8868
8945
  return;
8869
8946
  }
8870
8947
  logger.newLine();
8871
- if (isDevServerRunning()) {
8948
+ if (isServerRunning("backend", workspace)) {
8872
8949
  logger.admonition("warning", "Stop Dev Server First", [
8873
8950
  "The development server must be stopped before resetting the database.",
8874
8951
  "Stop the server (Ctrl+C) and run this command again to ensure a clean reset."
@@ -10590,7 +10667,7 @@ import { Command as Command27 } from "commander";
10590
10667
  // src/commands/debug/bundle.ts
10591
10668
  init_src();
10592
10669
  init_constants2();
10593
- import { writeFileSync as writeFileSync7 } from "fs";
10670
+ import { writeFileSync as writeFileSync8 } from "fs";
10594
10671
  import { join as join28 } from "path";
10595
10672
  import { Command as Command26 } from "commander";
10596
10673
  var bundleCommand = new Command26("bundle").description("Bundle and inspect the game backend worker code (for debugging)").option("-o, --output <path>", "Output file path", CLI_DEFAULT_OUTPUTS.WORKER_BUNDLE).option("--minify", "Minify the output").option("--sourcemap", "Include source maps").action(async (options) => {
@@ -10622,7 +10699,7 @@ var bundleCommand = new Command26("bundle").description("Bundle and inspect the
10622
10699
  (result) => `Bundled ${formatSize(result.code.length)}`
10623
10700
  );
10624
10701
  const outputPath = join28(workspace, options.output);
10625
- writeFileSync7(outputPath, bundle.code, "utf-8");
10702
+ writeFileSync8(outputPath, bundle.code, "utf-8");
10626
10703
  logger.success(`Bundle saved to ${options.output}`);
10627
10704
  logger.newLine();
10628
10705
  logger.highlight("Bundle Analysis");
@@ -10700,7 +10777,6 @@ export {
10700
10777
  compareIntegrationKeys,
10701
10778
  confirmDeploymentPlan,
10702
10779
  createClient,
10703
- createDevServerPidFile,
10704
10780
  deployBackendIfNeeded,
10705
10781
  deployExternalGame,
10706
10782
  deployGameBackend,
@@ -10739,7 +10815,6 @@ export {
10739
10815
  getCustomRoutesSize,
10740
10816
  getDeployedGame,
10741
10817
  getDeploymentId,
10742
- getDevServerPidPath,
10743
10818
  getDrizzleKitApiExports,
10744
10819
  getEnvironment,
10745
10820
  getErrorMessage,
@@ -10773,7 +10848,6 @@ export {
10773
10848
  importTypescriptDefault,
10774
10849
  importTypescriptFile,
10775
10850
  integrationChangeDetectors,
10776
- isDevServerRunning,
10777
10851
  listProfiles,
10778
10852
  loadAuthStore,
10779
10853
  loadConfig,
@@ -10791,7 +10865,6 @@ export {
10791
10865
  promptForTimeBackIntegration,
10792
10866
  registerCustomRoutes,
10793
10867
  removeDeployedGame,
10794
- removeDevServerPidFile,
10795
10868
  removeProfile,
10796
10869
  reportCancellation,
10797
10870
  reportDeploymentSuccess,
@@ -1,4 +1,3 @@
1
1
  *.zip
2
- *.pid
3
2
  db
4
3
  kv
package/dist/utils.js CHANGED
@@ -303,16 +303,6 @@ var CLI_DIRECTORIES = {
303
303
  /** KV storage directory within workspace */
304
304
  KV: join(WORKSPACE_NAME, "kv")
305
305
  };
306
- var CLI_FILES = {
307
- /** Auth store file in user config directory */
308
- AUTH_STORE: "auth.json",
309
- /** Games deployment info store */
310
- GAMES_STORE: "games.json",
311
- /** Dev server PID file */
312
- DEV_SERVER_PID: "dev-server.pid",
313
- /** Initial database file (before miniflare) */
314
- INITIAL_DATABASE: "initial.sqlite"
315
- };
316
306
 
317
307
  // src/constants/timeback.ts
318
308
  var CONFIG_FILE_NAMES = [
@@ -567,10 +557,70 @@ function processConfigVariables(config) {
567
557
  }
568
558
 
569
559
  // src/lib/dev/server.ts
570
- import { mkdir as mkdir3 } from "fs/promises";
560
+ import { mkdir as mkdir2 } from "fs/promises";
571
561
  import { join as join7 } from "path";
572
562
  import { Miniflare } from "miniflare";
573
563
 
564
+ // ../utils/src/port.ts
565
+ import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "node:fs";
566
+ import { createServer } from "node:net";
567
+ import { homedir } from "node:os";
568
+ import { join as join2 } from "node:path";
569
+ async function isPortAvailableOnHost(port, host) {
570
+ return new Promise((resolve4) => {
571
+ const server = createServer();
572
+ server.once("error", () => {
573
+ resolve4(false);
574
+ });
575
+ server.once("listening", () => {
576
+ server.close();
577
+ resolve4(true);
578
+ });
579
+ server.listen(port, host);
580
+ });
581
+ }
582
+ async function findAvailablePort(startPort = 4321) {
583
+ const allInterfacesAvailable = await isPortAvailableOnHost(startPort, "0.0.0.0");
584
+ const ipv6Available = await isPortAvailableOnHost(startPort, "::1");
585
+ if (allInterfacesAvailable && ipv6Available) {
586
+ return startPort;
587
+ }
588
+ return findAvailablePort(startPort + 1);
589
+ }
590
+ function getRegistryPath() {
591
+ const home = homedir();
592
+ const dir = join2(home, ".playcademy");
593
+ if (!existsSync2(dir)) {
594
+ mkdirSync(dir, { recursive: true });
595
+ }
596
+ return join2(dir, ".proc");
597
+ }
598
+ function readRegistry() {
599
+ const registryPath = getRegistryPath();
600
+ if (!existsSync2(registryPath)) {
601
+ return {};
602
+ }
603
+ try {
604
+ const content = readFileSync(registryPath, "utf-8");
605
+ return JSON.parse(content);
606
+ } catch {
607
+ return {};
608
+ }
609
+ }
610
+ function writeRegistry(registry) {
611
+ const registryPath = getRegistryPath();
612
+ writeFileSync(registryPath, JSON.stringify(registry, null, 2), "utf-8");
613
+ }
614
+ function getServerKey(type, port) {
615
+ return `${type}-${port}`;
616
+ }
617
+ function writeServerInfo(type, info) {
618
+ const registry = readRegistry();
619
+ const key = getServerKey(type, info.port);
620
+ registry[key] = info;
621
+ writeRegistry(registry);
622
+ }
623
+
574
624
  // src/lib/core/client.ts
575
625
  import { PlaycademyClient } from "@playcademy/sdk";
576
626
 
@@ -816,7 +866,7 @@ var CROSS_MARK = String.fromCodePoint(10006);
816
866
  init_package_json();
817
867
 
818
868
  // src/lib/templates/loader.ts
819
- import { existsSync as existsSync2, readFileSync } from "fs";
869
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
820
870
  import { dirname as dirname3, resolve as resolve3 } from "path";
821
871
  import { fileURLToPath } from "url";
822
872
  var currentDir = dirname3(fileURLToPath(import.meta.url));
@@ -829,8 +879,8 @@ function loadTemplateString(filename) {
829
879
  resolve3(currentDir, "templates", name)
830
880
  ]);
831
881
  for (const candidate of candidatePaths) {
832
- if (existsSync2(candidate)) {
833
- return readFileSync(candidate, "utf-8");
882
+ if (existsSync3(candidate)) {
883
+ return readFileSync2(candidate, "utf-8");
834
884
  }
835
885
  }
836
886
  throw new Error(`Template not found: ${filename}. Searched: ${candidatePaths.join(", ")}`);
@@ -839,12 +889,12 @@ function loadTemplateString(filename) {
839
889
  // src/lib/core/import.ts
840
890
  import { mkdtempSync, rmSync } from "fs";
841
891
  import { tmpdir } from "os";
842
- import { join as join2 } from "path";
892
+ import { join as join3 } from "path";
843
893
  import { pathToFileURL } from "url";
844
894
  import * as esbuild from "esbuild";
845
895
  async function importTypescriptFile(filePath, bundleOptions) {
846
- const tempDir = mkdtempSync(join2(tmpdir(), "playcademy-import-"));
847
- const outFile = join2(tempDir, "bundle.mjs");
896
+ const tempDir = mkdtempSync(join3(tmpdir(), "playcademy-import-"));
897
+ const outFile = join3(tempDir, "bundle.mjs");
848
898
  try {
849
899
  await esbuild.build({
850
900
  entryPoints: [filePath],
@@ -872,8 +922,8 @@ async function importTypescriptDefault(filePath, bundleOptions) {
872
922
  }
873
923
 
874
924
  // src/lib/deploy/bundle.ts
875
- import { existsSync as existsSync3 } from "fs";
876
- import { join as join4 } from "path";
925
+ import { existsSync as existsSync4 } from "fs";
926
+ import { join as join5 } from "path";
877
927
 
878
928
  // ../edge-play/src/entry.ts
879
929
  var entry_default = "/**\n * Game Backend Entry Point\n *\n * This file is the main entry point for deployed game backends.\n * It creates a Hono app and registers all enabled integration routes.\n *\n * Bundled with esbuild and deployed to Cloudflare Workers (or AWS Lambda).\n * Config is injected at build time via esbuild's `define` option.\n */\n\nimport { Hono } from 'hono'\nimport { cors } from 'hono/cors'\n\nimport { PlaycademyClient } from '@playcademy/sdk/server'\n\nimport { ENV_VARS } from './constants'\nimport { registerBuiltinRoutes } from './register-routes'\n\nimport type { PlaycademyConfig } from '@playcademy/sdk/server'\nimport type { HonoEnv } from './types'\n\n/**\n * Config injected at build time by esbuild\n *\n * The `declare const` tells TypeScript \"this exists at runtime, trust me.\"\n * During bundling, esbuild's `define` option does literal text replacement:\n *\n * Example bundling:\n * Source: if (PLAYCADEMY_CONFIG.integrations.timeback) { ... }\n * Define: { 'PLAYCADEMY_CONFIG': JSON.stringify({ integrations: { timeback: {...} } }) }\n * Output: if ({\"integrations\":{\"timeback\":{...}}}.integrations.timeback) { ... }\n *\n * This enables tree-shaking: if timeback is not configured, those code paths are removed.\n * The bundled Worker only includes the routes that are actually enabled.\n */\ndeclare const PLAYCADEMY_CONFIG: PlaycademyConfig & {\n customRoutes?: Array<{ path: string; file: string }>\n}\n\n// XXX: Polyfill process global for SDK compatibility\n// SDK code may reference process.env without importing it\n// @ts-expect-error - Adding global for Worker environment\nglobalThis.process = {\n env: {}, // Populated per-request from Worker env bindings\n cwd: () => '/',\n}\n\nconst app = new Hono<HonoEnv>()\n\n// TODO: Harden CORS in production - restrict to trusted origins:\n// - Game's assetBundleBase (for hosted games)\n// - Game's externalUrl (for external games)\n// - Platform frontend domains (hub.playcademy.com, hub.dev.playcademy.net)\n// This would require passing game metadata through env bindings during deployment\napp.use(\n '*',\n cors({\n origin: '*', // Permissive for now\n allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],\n allowHeaders: ['Content-Type', 'Authorization'],\n }),\n)\n\nlet sdkPromise: Promise<PlaycademyClient> | null = null\n\napp.use('*', async (c, next) => {\n // Populate process.env from Worker bindings for SDK compatibility\n globalThis.process.env = {\n [ENV_VARS.PLAYCADEMY_API_KEY]: c.env.PLAYCADEMY_API_KEY,\n [ENV_VARS.GAME_ID]: c.env.GAME_ID,\n [ENV_VARS.PLAYCADEMY_BASE_URL]: c.env.PLAYCADEMY_BASE_URL,\n }\n\n // Set config for all routes\n c.set('config', PLAYCADEMY_CONFIG)\n c.set('customRoutes', PLAYCADEMY_CONFIG.customRoutes || [])\n\n await next()\n})\n\n// Initialize SDK lazily on first request\napp.use('*', async (c, next) => {\n if (!sdkPromise) {\n sdkPromise = PlaycademyClient.init({\n apiKey: c.env[ENV_VARS.PLAYCADEMY_API_KEY],\n gameId: c.env[ENV_VARS.GAME_ID],\n baseUrl: c.env[ENV_VARS.PLAYCADEMY_BASE_URL],\n config: PLAYCADEMY_CONFIG,\n })\n }\n\n c.set('sdk', await sdkPromise)\n await next()\n})\n\n/**\n * Register built-in integration routes based on enabled integrations\n *\n * This function conditionally imports and registers routes like:\n * - POST /api/integrations/timeback/end-activity (if timeback enabled)\n * - GET /api/health (always included)\n *\n * Uses dynamic imports for tree-shaking: if an integration is not enabled,\n * its route code is completely removed from the bundle.\n */\nawait registerBuiltinRoutes(app, PLAYCADEMY_CONFIG.integrations)\n\nexport default app\n";
@@ -1129,7 +1179,7 @@ function textLoaderPlugin() {
1129
1179
  init_file_loader();
1130
1180
  import { mkdir, writeFile } from "fs/promises";
1131
1181
  import { tmpdir as tmpdir2 } from "os";
1132
- import { join as join3, relative } from "path";
1182
+ import { join as join4, relative } from "path";
1133
1183
 
1134
1184
  // src/lib/deploy/hash.ts
1135
1185
  import { createHash } from "crypto";
@@ -1148,7 +1198,7 @@ async function discoverRoutes(apiDir) {
1148
1198
  const routes = await Promise.all(
1149
1199
  files.map(async (file) => {
1150
1200
  const routePath = filePathToRoutePath(file);
1151
- const absolutePath = join3(apiDir, file);
1201
+ const absolutePath = join4(apiDir, file);
1152
1202
  const relativePath = relative(getWorkspace(), absolutePath);
1153
1203
  const methods = await detectExportedMethods(absolutePath);
1154
1204
  return {
@@ -1208,10 +1258,10 @@ async function transpileRoute(filePath) {
1208
1258
  if (!result.outputFiles?.[0]) {
1209
1259
  throw new Error("Transpilation failed: no output");
1210
1260
  }
1211
- const tempDir = join3(tmpdir2(), "playcademy-dev");
1261
+ const tempDir = join4(tmpdir2(), "playcademy-dev");
1212
1262
  await mkdir(tempDir, { recursive: true });
1213
1263
  const hash = hashContent(filePath).slice(0, 12);
1214
- const jsPath = join3(tempDir, `${hash}.mjs`);
1264
+ const jsPath = join4(tempDir, `${hash}.mjs`);
1215
1265
  await writeFile(jsPath, result.outputFiles[0].text);
1216
1266
  return jsPath;
1217
1267
  }
@@ -1222,7 +1272,7 @@ async function discoverCustomRoutes(config) {
1222
1272
  const workspace = getWorkspace();
1223
1273
  const customRoutesConfig = config.integrations?.customRoutes;
1224
1274
  const customRoutesDir = typeof customRoutesConfig === "object" && customRoutesConfig.directory || DEFAULT_API_ROUTES_DIRECTORY;
1225
- const customRoutes = await discoverRoutes(join4(workspace, customRoutesDir));
1275
+ const customRoutes = await discoverRoutes(join5(workspace, customRoutesDir));
1226
1276
  const customRouteData = customRoutes.map((r) => ({
1227
1277
  path: r.path,
1228
1278
  file: r.file,
@@ -1234,15 +1284,15 @@ async function discoverCustomRoutes(config) {
1234
1284
  function resolveEmbeddedSourcePaths() {
1235
1285
  const workspace = getWorkspace();
1236
1286
  const distDir = new URL(".", import.meta.url).pathname;
1237
- const embeddedEdgeSrc = join4(distDir, "edge-play", "src");
1238
- const isBuiltPackage = existsSync3(embeddedEdgeSrc);
1287
+ const embeddedEdgeSrc = join5(distDir, "edge-play", "src");
1288
+ const isBuiltPackage = existsSync4(embeddedEdgeSrc);
1239
1289
  const monorepoRoot = getMonorepoRoot();
1240
- const monorepoEdgeSrc = join4(monorepoRoot, "packages/edge-play/src");
1290
+ const monorepoEdgeSrc = join5(monorepoRoot, "packages/edge-play/src");
1241
1291
  const edgePlaySrc = isBuiltPackage ? embeddedEdgeSrc : monorepoEdgeSrc;
1242
- const cliPackageRoot = isBuiltPackage ? join4(distDir, "../../..") : join4(monorepoRoot, "packages/cli");
1243
- const cliNodeModules = isBuiltPackage ? join4(cliPackageRoot, "node_modules") : monorepoRoot;
1244
- const workspaceNodeModules = join4(workspace, "node_modules");
1245
- const constantsEntry = isBuiltPackage ? join4(embeddedEdgeSrc, "..", "..", "constants", "src", "index.ts") : join4(monorepoRoot, "packages", "constants", "src", "index.ts");
1292
+ const cliPackageRoot = isBuiltPackage ? join5(distDir, "../../..") : join5(monorepoRoot, "packages/cli");
1293
+ const cliNodeModules = isBuiltPackage ? join5(cliPackageRoot, "node_modules") : monorepoRoot;
1294
+ const workspaceNodeModules = join5(workspace, "node_modules");
1295
+ const constantsEntry = isBuiltPackage ? join5(embeddedEdgeSrc, "..", "..", "constants", "src", "index.ts") : join5(monorepoRoot, "packages", "constants", "src", "index.ts");
1246
1296
  return {
1247
1297
  isBuiltPackage,
1248
1298
  edgePlaySrc,
@@ -1302,16 +1352,16 @@ function createEsbuildConfig(entryCode, paths, bundleConfig, customRoutesDir, op
1302
1352
  // │ Example: import * as route from '@game-api/hello.ts' │
1303
1353
  // │ Resolves to: /user-project/server/api/hello.ts │
1304
1354
  // └─────────────────────────────────────────────────────────────────┘
1305
- "@game-api": join4(workspace, customRoutesDir),
1355
+ "@game-api": join5(workspace, customRoutesDir),
1306
1356
  // ┌─ Node.js polyfills for Cloudflare Workers ──────────────────────┐
1307
1357
  // │ Workers don't have fs, path, os, etc. Redirect to polyfills │
1308
1358
  // │ that throw helpful errors if user code tries to use them. │
1309
1359
  // └─────────────────────────────────────────────────────────────────┘
1310
- fs: join4(edgePlaySrc, "polyfills.js"),
1311
- "fs/promises": join4(edgePlaySrc, "polyfills.js"),
1312
- path: join4(edgePlaySrc, "polyfills.js"),
1313
- os: join4(edgePlaySrc, "polyfills.js"),
1314
- process: join4(edgePlaySrc, "polyfills.js")
1360
+ fs: join5(edgePlaySrc, "polyfills.js"),
1361
+ "fs/promises": join5(edgePlaySrc, "polyfills.js"),
1362
+ path: join5(edgePlaySrc, "polyfills.js"),
1363
+ os: join5(edgePlaySrc, "polyfills.js"),
1364
+ process: join5(edgePlaySrc, "polyfills.js")
1315
1365
  },
1316
1366
  // ──── Build Plugins ────
1317
1367
  plugins: [textLoaderPlugin()],
@@ -1378,8 +1428,8 @@ import { checkbox, confirm, input, select } from "@inquirer/prompts";
1378
1428
  import { bold as bold3, cyan as cyan2 } from "colorette";
1379
1429
 
1380
1430
  // src/lib/init/database.ts
1381
- import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
1382
- import { join as join5 } from "path";
1431
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1432
+ import { join as join6 } from "path";
1383
1433
  var drizzleConfigTemplate = loadTemplateString("database/drizzle-config.ts");
1384
1434
  var dbSchemaUsersTemplate = loadTemplateString("database/db-schema-users.ts");
1385
1435
  var dbSchemaScoresTemplate = loadTemplateString("database/db-schema-scores.ts");
@@ -1390,9 +1440,9 @@ var packageTemplate = loadTemplateString("database/package.json");
1390
1440
  var rootGitignoreTemplate = loadTemplateString("gitignore");
1391
1441
  function hasDatabaseSetup() {
1392
1442
  const workspace = getWorkspace();
1393
- const drizzleConfigPath = join5(workspace, "drizzle.config.ts");
1394
- const drizzleConfigJsPath = join5(workspace, "drizzle.config.js");
1395
- return existsSync4(drizzleConfigPath) || existsSync4(drizzleConfigJsPath);
1443
+ const drizzleConfigPath = join6(workspace, "drizzle.config.ts");
1444
+ const drizzleConfigJsPath = join6(workspace, "drizzle.config.js");
1445
+ return existsSync5(drizzleConfigPath) || existsSync5(drizzleConfigJsPath);
1396
1446
  }
1397
1447
 
1398
1448
  // src/lib/init/scaffold.ts
@@ -1409,29 +1459,14 @@ function hasKVSetup(config) {
1409
1459
  return !!config.integrations?.kv;
1410
1460
  }
1411
1461
 
1412
- // src/lib/dev/pid.ts
1413
- import { mkdir as mkdir2, unlink, writeFile as writeFile2 } from "fs/promises";
1414
- import { join as join6 } from "path";
1415
- function getDevServerPidPath() {
1416
- return join6(getWorkspace(), CLI_DIRECTORIES.WORKSPACE, CLI_FILES.DEV_SERVER_PID);
1417
- }
1418
- async function createDevServerPidFile() {
1419
- const pidPath = getDevServerPidFile();
1420
- const pidDir = join6(getWorkspace(), CLI_DIRECTORIES.WORKSPACE);
1421
- await mkdir2(pidDir, { recursive: true });
1422
- await writeFile2(pidPath, process.pid.toString());
1423
- }
1424
- function getDevServerPidFile() {
1425
- return getDevServerPidPath();
1426
- }
1427
-
1428
1462
  // src/lib/dev/server.ts
1429
1463
  async function startDevServer(options) {
1430
1464
  const {
1431
- port,
1465
+ port: preferredPort,
1432
1466
  config: providedConfig,
1433
1467
  platformUrl = process.env.PLAYCADEMY_BASE_URL || "http://localhost:5174"
1434
1468
  } = options;
1469
+ const port = await findAvailablePort(preferredPort);
1435
1470
  const config = providedConfig ?? await loadConfig();
1436
1471
  const hasSandboxTimebackCreds = !!process.env.TIMEBACK_API_CLIENT_ID;
1437
1472
  const devConfig = config.integrations?.timeback && !hasSandboxTimebackCreds ? { ...config, integrations: { ...config.integrations, timeback: void 0 } } : config;
@@ -1466,12 +1501,13 @@ async function startDevServer(options) {
1466
1501
  if (hasDatabase) {
1467
1502
  await initializeDatabase(mf);
1468
1503
  }
1504
+ await writeBackendServerInfo(port);
1469
1505
  return mf;
1470
1506
  }
1471
1507
  async function ensureDatabaseDirectory() {
1472
1508
  const dbDir = join7(getWorkspace(), CLI_DIRECTORIES.DATABASE);
1473
1509
  try {
1474
- await mkdir3(dbDir, { recursive: true });
1510
+ await mkdir2(dbDir, { recursive: true });
1475
1511
  } catch (error) {
1476
1512
  throw new Error(`Failed to create database directory: ${getErrorMessage(error)}`);
1477
1513
  }
@@ -1480,7 +1516,7 @@ async function ensureDatabaseDirectory() {
1480
1516
  async function ensureKvDirectory() {
1481
1517
  const kvDir = join7(getWorkspace(), CLI_DIRECTORIES.KV);
1482
1518
  try {
1483
- await mkdir3(kvDir, { recursive: true });
1519
+ await mkdir2(kvDir, { recursive: true });
1484
1520
  } catch (error) {
1485
1521
  throw new Error(`Failed to create KV directory: ${getErrorMessage(error)}`);
1486
1522
  }
@@ -1489,7 +1525,15 @@ async function ensureKvDirectory() {
1489
1525
  async function initializeDatabase(mf) {
1490
1526
  const d1 = await mf.getD1Database("DB");
1491
1527
  await d1.exec("SELECT 1");
1492
- await createDevServerPidFile();
1528
+ }
1529
+ async function writeBackendServerInfo(port) {
1530
+ writeServerInfo("backend", {
1531
+ pid: process.pid,
1532
+ port,
1533
+ url: `http://localhost:${port}/api`,
1534
+ startedAt: Date.now(),
1535
+ projectRoot: getWorkspace()
1536
+ });
1493
1537
  }
1494
1538
 
1495
1539
  // src/lib/dev/reload.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playcademy",
3
- "version": "0.13.1",
3
+ "version": "0.13.3",
4
4
  "type": "module",
5
5
  "module": "./dist/index.js",
6
6
  "main": "./dist/index.js",
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@inquirer/prompts": "^7.8.6",
43
- "@playcademy/sdk": "0.1.5",
43
+ "@playcademy/sdk": "0.1.6",
44
44
  "better-sqlite3": "^12.4.1",
45
45
  "chokidar": "^4.0.3",
46
46
  "colorette": "^2.0.20",