playcademy 0.13.2 → 0.13.4
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/dist/constants.d.ts +0 -2
- package/dist/constants.js +0 -2
- package/dist/db.js +0 -2
- package/dist/index.js +189 -107
- package/dist/templates/playcademy-gitignore.template +0 -1
- package/dist/utils.js +106 -62
- package/package.json +2 -2
package/dist/constants.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
@@ -7564,58 +7562,10 @@ function displayRegisteredRoutes(integrations, customRoutes = []) {
|
|
|
7564
7562
|
}
|
|
7565
7563
|
}
|
|
7566
7564
|
|
|
7567
|
-
// src/lib/dev/pid.ts
|
|
7568
|
-
init_constants2();
|
|
7569
|
-
init_core();
|
|
7570
|
-
import { existsSync as existsSync15, readFileSync as readFileSync4, unlinkSync as unlinkSync2 } from "fs";
|
|
7571
|
-
import { mkdir as mkdir4, unlink, writeFile as writeFile4 } from "fs/promises";
|
|
7572
|
-
import { join as join15 } from "path";
|
|
7573
|
-
function getDevServerPidPath() {
|
|
7574
|
-
return join15(getWorkspace(), CLI_DIRECTORIES.WORKSPACE, CLI_FILES.DEV_SERVER_PID);
|
|
7575
|
-
}
|
|
7576
|
-
async function createDevServerPidFile() {
|
|
7577
|
-
const pidPath = getDevServerPidFile();
|
|
7578
|
-
const pidDir = join15(getWorkspace(), CLI_DIRECTORIES.WORKSPACE);
|
|
7579
|
-
await mkdir4(pidDir, { recursive: true });
|
|
7580
|
-
await writeFile4(pidPath, process.pid.toString());
|
|
7581
|
-
}
|
|
7582
|
-
async function removeDevServerPidFile() {
|
|
7583
|
-
const pidPath = getDevServerPidFile();
|
|
7584
|
-
if (existsSync15(pidPath)) {
|
|
7585
|
-
await unlink(pidPath);
|
|
7586
|
-
}
|
|
7587
|
-
}
|
|
7588
|
-
function isDevServerRunning() {
|
|
7589
|
-
const pidPath = getDevServerPidFile();
|
|
7590
|
-
if (!existsSync15(pidPath)) {
|
|
7591
|
-
return false;
|
|
7592
|
-
}
|
|
7593
|
-
try {
|
|
7594
|
-
const pid = parseInt(readFileSync4(pidPath, "utf8").trim(), 10);
|
|
7595
|
-
if (process.platform === "win32") {
|
|
7596
|
-
return true;
|
|
7597
|
-
} else {
|
|
7598
|
-
try {
|
|
7599
|
-
process.kill(pid, 0);
|
|
7600
|
-
return true;
|
|
7601
|
-
} catch {
|
|
7602
|
-
unlinkSync2(pidPath);
|
|
7603
|
-
return false;
|
|
7604
|
-
}
|
|
7605
|
-
}
|
|
7606
|
-
} catch {
|
|
7607
|
-
unlinkSync2(pidPath);
|
|
7608
|
-
return false;
|
|
7609
|
-
}
|
|
7610
|
-
}
|
|
7611
|
-
function getDevServerPidFile() {
|
|
7612
|
-
return getDevServerPidPath();
|
|
7613
|
-
}
|
|
7614
|
-
|
|
7615
7565
|
// src/lib/dev/reload.ts
|
|
7616
7566
|
init_constants2();
|
|
7617
7567
|
init_core();
|
|
7618
|
-
import { join as
|
|
7568
|
+
import { join as join15, relative as relative3 } from "path";
|
|
7619
7569
|
import chokidar from "chokidar";
|
|
7620
7570
|
import { bold as bold4, cyan as cyan3, dim as dim6, green as green3 } from "colorette";
|
|
7621
7571
|
function formatTime() {
|
|
@@ -7632,9 +7582,9 @@ function startHotReload(onReload, options = {}) {
|
|
|
7632
7582
|
const customRoutesConfig = options.config?.integrations?.customRoutes;
|
|
7633
7583
|
const customRoutesDir = typeof customRoutesConfig === "object" && customRoutesConfig.directory || DEFAULT_API_ROUTES_DIRECTORY;
|
|
7634
7584
|
const watchPaths = [
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
|
|
7585
|
+
join15(workspace, customRoutesDir),
|
|
7586
|
+
join15(workspace, "playcademy.config.js"),
|
|
7587
|
+
join15(workspace, "playcademy.config.json")
|
|
7638
7588
|
];
|
|
7639
7589
|
const watcher = chokidar.watch(watchPaths, {
|
|
7640
7590
|
persistent: true,
|
|
@@ -7675,18 +7625,134 @@ function startHotReload(onReload, options = {}) {
|
|
|
7675
7625
|
|
|
7676
7626
|
// src/lib/dev/server.ts
|
|
7677
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 ipv4AllAvailable = await isPortAvailableOnHost(startPort, "0.0.0.0");
|
|
7652
|
+
const ipv6AllAvailable = await isPortAvailableOnHost(startPort, "::");
|
|
7653
|
+
if (ipv4AllAvailable && ipv6AllAvailable) {
|
|
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
|
|
7678
7746
|
init_constants2();
|
|
7679
7747
|
init_loader();
|
|
7680
7748
|
init_core();
|
|
7681
|
-
import { mkdir as mkdir5 } from "fs/promises";
|
|
7682
|
-
import { join as join17 } from "path";
|
|
7683
|
-
import { Miniflare } from "miniflare";
|
|
7684
7749
|
async function startDevServer(options) {
|
|
7685
7750
|
const {
|
|
7686
|
-
port,
|
|
7751
|
+
port: preferredPort,
|
|
7687
7752
|
config: providedConfig,
|
|
7688
7753
|
platformUrl = process.env.PLAYCADEMY_BASE_URL || "http://localhost:5174"
|
|
7689
7754
|
} = options;
|
|
7755
|
+
const port = await findAvailablePort(preferredPort);
|
|
7690
7756
|
const config = providedConfig ?? await loadConfig();
|
|
7691
7757
|
const hasSandboxTimebackCreds = !!process.env.TIMEBACK_API_CLIENT_ID;
|
|
7692
7758
|
const devConfig = config.integrations?.timeback && !hasSandboxTimebackCreds ? { ...config, integrations: { ...config.integrations, timeback: void 0 } } : config;
|
|
@@ -7721,12 +7787,13 @@ async function startDevServer(options) {
|
|
|
7721
7787
|
if (hasDatabase) {
|
|
7722
7788
|
await initializeDatabase(mf);
|
|
7723
7789
|
}
|
|
7790
|
+
await writeBackendServerInfo(port);
|
|
7724
7791
|
return mf;
|
|
7725
7792
|
}
|
|
7726
7793
|
async function ensureDatabaseDirectory() {
|
|
7727
7794
|
const dbDir = join17(getWorkspace(), CLI_DIRECTORIES.DATABASE);
|
|
7728
7795
|
try {
|
|
7729
|
-
await
|
|
7796
|
+
await mkdir4(dbDir, { recursive: true });
|
|
7730
7797
|
} catch (error) {
|
|
7731
7798
|
throw new Error(`Failed to create database directory: ${getErrorMessage(error)}`);
|
|
7732
7799
|
}
|
|
@@ -7735,7 +7802,7 @@ async function ensureDatabaseDirectory() {
|
|
|
7735
7802
|
async function ensureKvDirectory() {
|
|
7736
7803
|
const kvDir = join17(getWorkspace(), CLI_DIRECTORIES.KV);
|
|
7737
7804
|
try {
|
|
7738
|
-
await
|
|
7805
|
+
await mkdir4(kvDir, { recursive: true });
|
|
7739
7806
|
} catch (error) {
|
|
7740
7807
|
throw new Error(`Failed to create KV directory: ${getErrorMessage(error)}`);
|
|
7741
7808
|
}
|
|
@@ -7744,7 +7811,15 @@ async function ensureKvDirectory() {
|
|
|
7744
7811
|
async function initializeDatabase(mf) {
|
|
7745
7812
|
const d1 = await mf.getD1Database("DB");
|
|
7746
7813
|
await d1.exec("SELECT 1");
|
|
7747
|
-
|
|
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
|
+
});
|
|
7748
7823
|
}
|
|
7749
7824
|
|
|
7750
7825
|
// src/lib/timeback/cleanup.ts
|
|
@@ -7810,7 +7885,7 @@ function displayResourcesStatus(resources, logger2) {
|
|
|
7810
7885
|
}
|
|
7811
7886
|
|
|
7812
7887
|
// src/commands/init/config.ts
|
|
7813
|
-
import { writeFileSync as
|
|
7888
|
+
import { writeFileSync as writeFileSync5 } from "fs";
|
|
7814
7889
|
import { resolve as resolve9 } from "path";
|
|
7815
7890
|
init_file_loader();
|
|
7816
7891
|
init_constants2();
|
|
@@ -7862,7 +7937,7 @@ var configCommand = new Command("config").description("Create playcademy.config
|
|
|
7862
7937
|
emoji: gameInfo.emoji,
|
|
7863
7938
|
timeback: timebackConfig ?? void 0
|
|
7864
7939
|
});
|
|
7865
|
-
|
|
7940
|
+
writeFileSync5(resolve9(getWorkspace(), configFileName), configContent, "utf-8");
|
|
7866
7941
|
displayConfigSuccess(!!timebackConfig);
|
|
7867
7942
|
} catch (error) {
|
|
7868
7943
|
logger.newLine();
|
|
@@ -7975,7 +8050,7 @@ var initCommand = new Command2("init").description("Initialize a playcademy.conf
|
|
|
7975
8050
|
kv: kv ?? void 0,
|
|
7976
8051
|
timeback: timebackConfig ?? void 0
|
|
7977
8052
|
});
|
|
7978
|
-
|
|
8053
|
+
writeFileSync6(resolve10(getWorkspace(), configFileName), configContent, "utf-8");
|
|
7979
8054
|
displaySuccessMessage({
|
|
7980
8055
|
configFileName,
|
|
7981
8056
|
apiDirectory: customRoutes?.directory ?? null,
|
|
@@ -7997,7 +8072,7 @@ async function addPlaycademySdk() {
|
|
|
7997
8072
|
}
|
|
7998
8073
|
if (!pkg.dependencies) pkg.dependencies = {};
|
|
7999
8074
|
pkg.dependencies["@playcademy/sdk"] = "latest";
|
|
8000
|
-
|
|
8075
|
+
writeFileSync6(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
8001
8076
|
return true;
|
|
8002
8077
|
}
|
|
8003
8078
|
initCommand.addCommand(configCommand);
|
|
@@ -8587,7 +8662,43 @@ var getStatusCommand = new Command11("status").description("Check your developer
|
|
|
8587
8662
|
|
|
8588
8663
|
// src/commands/dev/server.ts
|
|
8589
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
|
+
}
|
|
8590
8698
|
async function runDevServer(options) {
|
|
8699
|
+
const workspace = getWorkspace();
|
|
8700
|
+
const serverRef = { current: null };
|
|
8701
|
+
setupCleanupHandlers(workspace, () => serverRef.current);
|
|
8591
8702
|
try {
|
|
8592
8703
|
logger.newLine();
|
|
8593
8704
|
const config = await loadConfig();
|
|
@@ -8597,7 +8708,7 @@ async function runDevServer(options) {
|
|
|
8597
8708
|
logger.warn("No backend integrations or custom routes configured");
|
|
8598
8709
|
logger.newLine();
|
|
8599
8710
|
logger.admonition("tip", "How to Add Backend", [
|
|
8600
|
-
`Add integrations to ${blueBright4("playcademy.config.js")} or run \`playcademy init
|
|
8711
|
+
`Add integrations to ${blueBright4("playcademy.config.js")} or run \`playcademy api init\``
|
|
8601
8712
|
]);
|
|
8602
8713
|
logger.newLine();
|
|
8603
8714
|
logger.remark("Nothing to do");
|
|
@@ -8605,52 +8716,27 @@ async function runDevServer(options) {
|
|
|
8605
8716
|
return;
|
|
8606
8717
|
}
|
|
8607
8718
|
const port = parseInt(options.port, 10);
|
|
8608
|
-
|
|
8719
|
+
const server = await startDevServer({
|
|
8609
8720
|
port,
|
|
8610
8721
|
config,
|
|
8611
8722
|
logger: options.logger !== false
|
|
8612
8723
|
});
|
|
8613
|
-
|
|
8724
|
+
serverRef.current = server;
|
|
8614
8725
|
logger.success(`Game API started: ${blueBright4(underline2(`http://localhost:${port}/api`))}`);
|
|
8615
8726
|
logger.newLine();
|
|
8616
|
-
const workspace = getWorkspace();
|
|
8617
8727
|
const customRoutesDir = getCustomRoutesDirectory(workspace, config);
|
|
8618
|
-
|
|
8728
|
+
const customRoutes = await discoverRoutes(customRoutesDir);
|
|
8619
8729
|
displayRegisteredRoutes(config.integrations, customRoutes);
|
|
8620
8730
|
logger.newLine();
|
|
8621
|
-
if (
|
|
8622
|
-
|
|
8623
|
-
async () => {
|
|
8624
|
-
await server.dispose();
|
|
8625
|
-
clearModuleCache();
|
|
8626
|
-
const newConfig = await loadConfig();
|
|
8627
|
-
server = await startDevServer({
|
|
8628
|
-
port,
|
|
8629
|
-
config: newConfig,
|
|
8630
|
-
logger: options.logger !== false
|
|
8631
|
-
});
|
|
8632
|
-
const newCustomRoutesDir = getCustomRoutesDirectory(workspace, newConfig);
|
|
8633
|
-
customRoutes = await discoverRoutes(newCustomRoutesDir);
|
|
8634
|
-
},
|
|
8635
|
-
{ config }
|
|
8636
|
-
);
|
|
8731
|
+
if (options.reload !== false) {
|
|
8732
|
+
await setupServerHotReload(serverRef, port, workspace, config, options.logger !== false);
|
|
8637
8733
|
}
|
|
8638
8734
|
logger.remark(`Press ${underline2("ctrl+c")} to stop`);
|
|
8639
8735
|
logger.newLine();
|
|
8640
|
-
process.on("SIGINT", async () => {
|
|
8641
|
-
await removeDevServerPidFile();
|
|
8642
|
-
process.exit(0);
|
|
8643
|
-
});
|
|
8644
|
-
process.on("SIGTERM", async () => {
|
|
8645
|
-
await removeDevServerPidFile();
|
|
8646
|
-
process.exit(0);
|
|
8647
|
-
});
|
|
8648
8736
|
} catch (error) {
|
|
8649
8737
|
logAndExit(error, logger, { prefix: "Failed to start dev server" });
|
|
8650
8738
|
}
|
|
8651
8739
|
}
|
|
8652
|
-
function clearModuleCache() {
|
|
8653
|
-
}
|
|
8654
8740
|
|
|
8655
8741
|
// src/commands/dev/index.ts
|
|
8656
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);
|
|
@@ -8762,7 +8848,7 @@ async function runDbDiff() {
|
|
|
8762
8848
|
// src/commands/db/init.ts
|
|
8763
8849
|
init_file_loader();
|
|
8764
8850
|
init_constants2();
|
|
8765
|
-
import { readFileSync as readFileSync6, writeFileSync as
|
|
8851
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
8766
8852
|
import { input as input6 } from "@inquirer/prompts";
|
|
8767
8853
|
async function runDbInit() {
|
|
8768
8854
|
try {
|
|
@@ -8819,7 +8905,7 @@ async function runDbInit() {
|
|
|
8819
8905
|
);
|
|
8820
8906
|
}
|
|
8821
8907
|
}
|
|
8822
|
-
|
|
8908
|
+
writeFileSync7(configFile.path, updatedContent, "utf-8");
|
|
8823
8909
|
logger.success(`Updated ${configFile.path.split("/").pop()}`);
|
|
8824
8910
|
}
|
|
8825
8911
|
logger.newLine();
|
|
@@ -8841,12 +8927,12 @@ async function runDbInit() {
|
|
|
8841
8927
|
// src/commands/db/reset.ts
|
|
8842
8928
|
init_src2();
|
|
8843
8929
|
init_src();
|
|
8844
|
-
init_constants2();
|
|
8845
8930
|
import { spawn } from "child_process";
|
|
8846
8931
|
import { existsSync as existsSync16, rmSync as rmSync2 } from "fs";
|
|
8847
8932
|
import { join as join18 } from "path";
|
|
8848
8933
|
import { confirm as confirm6 } from "@inquirer/prompts";
|
|
8849
8934
|
import { Miniflare as Miniflare2 } from "miniflare";
|
|
8935
|
+
init_constants2();
|
|
8850
8936
|
async function runDbReset() {
|
|
8851
8937
|
try {
|
|
8852
8938
|
const workspace = getWorkspace();
|
|
@@ -8859,7 +8945,7 @@ async function runDbReset() {
|
|
|
8859
8945
|
return;
|
|
8860
8946
|
}
|
|
8861
8947
|
logger.newLine();
|
|
8862
|
-
if (
|
|
8948
|
+
if (isServerRunning("backend", workspace)) {
|
|
8863
8949
|
logger.admonition("warning", "Stop Dev Server First", [
|
|
8864
8950
|
"The development server must be stopped before resetting the database.",
|
|
8865
8951
|
"Stop the server (Ctrl+C) and run this command again to ensure a clean reset."
|
|
@@ -10581,7 +10667,7 @@ import { Command as Command27 } from "commander";
|
|
|
10581
10667
|
// src/commands/debug/bundle.ts
|
|
10582
10668
|
init_src();
|
|
10583
10669
|
init_constants2();
|
|
10584
|
-
import { writeFileSync as
|
|
10670
|
+
import { writeFileSync as writeFileSync8 } from "fs";
|
|
10585
10671
|
import { join as join28 } from "path";
|
|
10586
10672
|
import { Command as Command26 } from "commander";
|
|
10587
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) => {
|
|
@@ -10613,7 +10699,7 @@ var bundleCommand = new Command26("bundle").description("Bundle and inspect the
|
|
|
10613
10699
|
(result) => `Bundled ${formatSize(result.code.length)}`
|
|
10614
10700
|
);
|
|
10615
10701
|
const outputPath = join28(workspace, options.output);
|
|
10616
|
-
|
|
10702
|
+
writeFileSync8(outputPath, bundle.code, "utf-8");
|
|
10617
10703
|
logger.success(`Bundle saved to ${options.output}`);
|
|
10618
10704
|
logger.newLine();
|
|
10619
10705
|
logger.highlight("Bundle Analysis");
|
|
@@ -10691,7 +10777,6 @@ export {
|
|
|
10691
10777
|
compareIntegrationKeys,
|
|
10692
10778
|
confirmDeploymentPlan,
|
|
10693
10779
|
createClient,
|
|
10694
|
-
createDevServerPidFile,
|
|
10695
10780
|
deployBackendIfNeeded,
|
|
10696
10781
|
deployExternalGame,
|
|
10697
10782
|
deployGameBackend,
|
|
@@ -10730,7 +10815,6 @@ export {
|
|
|
10730
10815
|
getCustomRoutesSize,
|
|
10731
10816
|
getDeployedGame,
|
|
10732
10817
|
getDeploymentId,
|
|
10733
|
-
getDevServerPidPath,
|
|
10734
10818
|
getDrizzleKitApiExports,
|
|
10735
10819
|
getEnvironment,
|
|
10736
10820
|
getErrorMessage,
|
|
@@ -10764,7 +10848,6 @@ export {
|
|
|
10764
10848
|
importTypescriptDefault,
|
|
10765
10849
|
importTypescriptFile,
|
|
10766
10850
|
integrationChangeDetectors,
|
|
10767
|
-
isDevServerRunning,
|
|
10768
10851
|
listProfiles,
|
|
10769
10852
|
loadAuthStore,
|
|
10770
10853
|
loadConfig,
|
|
@@ -10782,7 +10865,6 @@ export {
|
|
|
10782
10865
|
promptForTimeBackIntegration,
|
|
10783
10866
|
registerCustomRoutes,
|
|
10784
10867
|
removeDeployedGame,
|
|
10785
|
-
removeDevServerPidFile,
|
|
10786
10868
|
removeProfile,
|
|
10787
10869
|
reportCancellation,
|
|
10788
10870
|
reportDeploymentSuccess,
|
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
|
|
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 ipv4AllAvailable = await isPortAvailableOnHost(startPort, "0.0.0.0");
|
|
584
|
+
const ipv6AllAvailable = await isPortAvailableOnHost(startPort, "::");
|
|
585
|
+
if (ipv4AllAvailable && ipv6AllAvailable) {
|
|
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
|
|
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 (
|
|
833
|
-
return
|
|
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
|
|
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(
|
|
847
|
-
const outFile =
|
|
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
|
|
876
|
-
import { join as
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
1238
|
-
const isBuiltPackage =
|
|
1287
|
+
const embeddedEdgeSrc = join5(distDir, "edge-play", "src");
|
|
1288
|
+
const isBuiltPackage = existsSync4(embeddedEdgeSrc);
|
|
1239
1289
|
const monorepoRoot = getMonorepoRoot();
|
|
1240
|
-
const monorepoEdgeSrc =
|
|
1290
|
+
const monorepoEdgeSrc = join5(monorepoRoot, "packages/edge-play/src");
|
|
1241
1291
|
const edgePlaySrc = isBuiltPackage ? embeddedEdgeSrc : monorepoEdgeSrc;
|
|
1242
|
-
const cliPackageRoot = isBuiltPackage ?
|
|
1243
|
-
const cliNodeModules = isBuiltPackage ?
|
|
1244
|
-
const workspaceNodeModules =
|
|
1245
|
-
const constantsEntry = isBuiltPackage ?
|
|
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":
|
|
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:
|
|
1311
|
-
"fs/promises":
|
|
1312
|
-
path:
|
|
1313
|
-
os:
|
|
1314
|
-
process:
|
|
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
|
|
1382
|
-
import { join as
|
|
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 =
|
|
1394
|
-
const drizzleConfigJsPath =
|
|
1395
|
-
return
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.13.4",
|
|
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.
|
|
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",
|