@playcademy/sandbox 0.6.1-beta.5 → 0.6.1-beta.6
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/cli.js +70 -18
- package/dist/server.js +70 -18
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1084,7 +1084,7 @@ var package_default;
|
|
|
1084
1084
|
var init_package = __esm(() => {
|
|
1085
1085
|
package_default = {
|
|
1086
1086
|
name: "@playcademy/sandbox",
|
|
1087
|
-
version: "0.6.1-beta.
|
|
1087
|
+
version: "0.6.1-beta.6",
|
|
1088
1088
|
description: "Local development server for Playcademy game development",
|
|
1089
1089
|
type: "module",
|
|
1090
1090
|
exports: {
|
|
@@ -5689,6 +5689,14 @@ var init_schema = __esm(() => {
|
|
|
5689
5689
|
platformServiceJwt: platformServiceJwtConfigSchema.optional(),
|
|
5690
5690
|
uploadBucket: exports_external.string().optional(),
|
|
5691
5691
|
queueIngressSecret: exports_external.string().optional()
|
|
5692
|
+
}).superRefine((config2, ctx) => {
|
|
5693
|
+
if (config2.isLocal && !config2.baseUrl) {
|
|
5694
|
+
ctx.addIssue({
|
|
5695
|
+
code: exports_external.ZodIssueCode.custom,
|
|
5696
|
+
path: ["baseUrl"],
|
|
5697
|
+
message: "baseUrl is required when isLocal is true"
|
|
5698
|
+
});
|
|
5699
|
+
}
|
|
5692
5700
|
});
|
|
5693
5701
|
});
|
|
5694
5702
|
|
|
@@ -27520,26 +27528,70 @@ var init_playcademy = __esm(() => {
|
|
|
27520
27528
|
});
|
|
27521
27529
|
|
|
27522
27530
|
// ../utils/src/tunnel.ts
|
|
27523
|
-
|
|
27524
|
-
|
|
27531
|
+
function servicePort(service) {
|
|
27532
|
+
if (!service) {
|
|
27533
|
+
return null;
|
|
27534
|
+
}
|
|
27525
27535
|
try {
|
|
27526
|
-
|
|
27536
|
+
const url = new URL(service);
|
|
27537
|
+
return url.port || (url.protocol === "https:" ? "443" : "80");
|
|
27527
27538
|
} catch {
|
|
27528
|
-
|
|
27539
|
+
return null;
|
|
27529
27540
|
}
|
|
27530
|
-
|
|
27531
|
-
|
|
27541
|
+
}
|
|
27542
|
+
async function readTunnelIngress(port) {
|
|
27543
|
+
try {
|
|
27544
|
+
const response = await fetch(`http://127.0.0.1:${port}/config`, {
|
|
27545
|
+
signal: AbortSignal.timeout(1000)
|
|
27546
|
+
});
|
|
27547
|
+
if (!response.ok) {
|
|
27548
|
+
return null;
|
|
27549
|
+
}
|
|
27550
|
+
const data = await response.json();
|
|
27551
|
+
return data.config?.ingress ?? [];
|
|
27552
|
+
} catch {
|
|
27553
|
+
return null;
|
|
27554
|
+
}
|
|
27555
|
+
}
|
|
27556
|
+
function matchingHostname(rules, expectedPort) {
|
|
27557
|
+
for (const rule of rules ?? []) {
|
|
27558
|
+
if (rule.hostname && servicePort(rule.service) === expectedPort) {
|
|
27559
|
+
return rule.hostname;
|
|
27560
|
+
}
|
|
27561
|
+
}
|
|
27562
|
+
return null;
|
|
27563
|
+
}
|
|
27564
|
+
async function getTunnelUrl(platformBaseUrl) {
|
|
27565
|
+
const expectedPort = servicePort(platformBaseUrl);
|
|
27566
|
+
if (!expectedPort) {
|
|
27567
|
+
throw new Error(`Cannot resolve a tunnel for unparseable platform URL '${platformBaseUrl}'`);
|
|
27568
|
+
}
|
|
27569
|
+
const cachedPort = matchedPorts.get(expectedPort);
|
|
27570
|
+
if (cachedPort !== undefined) {
|
|
27571
|
+
const hostname = matchingHostname(await readTunnelIngress(cachedPort), expectedPort);
|
|
27572
|
+
if (hostname) {
|
|
27573
|
+
return `https://${hostname}`;
|
|
27574
|
+
}
|
|
27575
|
+
matchedPorts.delete(expectedPort);
|
|
27532
27576
|
}
|
|
27533
|
-
const
|
|
27534
|
-
const
|
|
27535
|
-
|
|
27536
|
-
|
|
27577
|
+
const ports = Array.from({ length: TUNNEL_METRICS_PORT_RANGE }, (_, offset) => TUNNEL_METRICS_PORT + offset);
|
|
27578
|
+
const results = await Promise.all(ports.map(async (port) => ({ port, rules: await readTunnelIngress(port) })));
|
|
27579
|
+
for (const { port, rules } of results) {
|
|
27580
|
+
const hostname = matchingHostname(rules, expectedPort);
|
|
27581
|
+
if (hostname) {
|
|
27582
|
+
matchedPorts.set(expectedPort, port);
|
|
27583
|
+
return `https://${hostname}`;
|
|
27584
|
+
}
|
|
27537
27585
|
}
|
|
27538
|
-
|
|
27586
|
+
const foreign = results.flatMap(({ rules }) => (rules ?? []).filter((rule) => rule.hostname).map((rule) => `${rule.hostname} -> ${rule.service}`));
|
|
27587
|
+
if (foreign.length > 0) {
|
|
27588
|
+
throw new Error(`A local tunnel is running, but none route to the platform at ${platformBaseUrl} (found: ${foreign.join(", ")}). ${TUNNEL_START_HINT}`);
|
|
27589
|
+
}
|
|
27590
|
+
throw new Error(`Local tunnel is not running. ${TUNNEL_START_HINT}`);
|
|
27539
27591
|
}
|
|
27540
|
-
var TUNNEL_METRICS_PORT = 20241,
|
|
27592
|
+
var TUNNEL_METRICS_PORT = 20241, TUNNEL_METRICS_PORT_RANGE = 10, TUNNEL_START_HINT = "Start this platform's tunnel with `bun dev` or `bun run tunnel`.", matchedPorts;
|
|
27541
27593
|
var init_tunnel = __esm(() => {
|
|
27542
|
-
|
|
27594
|
+
matchedPorts = new Map;
|
|
27543
27595
|
});
|
|
27544
27596
|
|
|
27545
27597
|
// ../utils/src/stages.ts
|
|
@@ -28183,13 +28235,13 @@ class DeployService {
|
|
|
28183
28235
|
return this.deps.config.baseUrl;
|
|
28184
28236
|
}
|
|
28185
28237
|
try {
|
|
28186
|
-
return await getTunnelUrl();
|
|
28187
|
-
} catch {
|
|
28238
|
+
return await getTunnelUrl(this.deps.config.baseUrl);
|
|
28239
|
+
} catch (error) {
|
|
28188
28240
|
setAttributes({
|
|
28189
28241
|
"app.deploy.tunnel_available": false,
|
|
28190
28242
|
"app.deploy.failure_kind": "local_tunnel_unavailable"
|
|
28191
28243
|
});
|
|
28192
|
-
throw new ValidationError(
|
|
28244
|
+
throw new ValidationError(errorMessage(error));
|
|
28193
28245
|
}
|
|
28194
28246
|
}
|
|
28195
28247
|
async deployToCloudflare(args2) {
|
|
@@ -140714,7 +140766,7 @@ var init_session_controller = __esm(() => {
|
|
|
140714
140766
|
let baseUrl;
|
|
140715
140767
|
if (ctx.config.isLocal) {
|
|
140716
140768
|
try {
|
|
140717
|
-
baseUrl = await getTunnelUrl();
|
|
140769
|
+
baseUrl = await getTunnelUrl(ctx.config.baseUrl);
|
|
140718
140770
|
} catch {}
|
|
140719
140771
|
}
|
|
140720
140772
|
return { token, exp, baseUrl };
|
package/dist/server.js
CHANGED
|
@@ -1083,7 +1083,7 @@ var package_default;
|
|
|
1083
1083
|
var init_package = __esm(() => {
|
|
1084
1084
|
package_default = {
|
|
1085
1085
|
name: "@playcademy/sandbox",
|
|
1086
|
-
version: "0.6.1-beta.
|
|
1086
|
+
version: "0.6.1-beta.6",
|
|
1087
1087
|
description: "Local development server for Playcademy game development",
|
|
1088
1088
|
type: "module",
|
|
1089
1089
|
exports: {
|
|
@@ -5688,6 +5688,14 @@ var init_schema = __esm(() => {
|
|
|
5688
5688
|
platformServiceJwt: platformServiceJwtConfigSchema.optional(),
|
|
5689
5689
|
uploadBucket: exports_external.string().optional(),
|
|
5690
5690
|
queueIngressSecret: exports_external.string().optional()
|
|
5691
|
+
}).superRefine((config2, ctx) => {
|
|
5692
|
+
if (config2.isLocal && !config2.baseUrl) {
|
|
5693
|
+
ctx.addIssue({
|
|
5694
|
+
code: exports_external.ZodIssueCode.custom,
|
|
5695
|
+
path: ["baseUrl"],
|
|
5696
|
+
message: "baseUrl is required when isLocal is true"
|
|
5697
|
+
});
|
|
5698
|
+
}
|
|
5691
5699
|
});
|
|
5692
5700
|
});
|
|
5693
5701
|
|
|
@@ -27519,26 +27527,70 @@ var init_playcademy = __esm(() => {
|
|
|
27519
27527
|
});
|
|
27520
27528
|
|
|
27521
27529
|
// ../utils/src/tunnel.ts
|
|
27522
|
-
|
|
27523
|
-
|
|
27530
|
+
function servicePort(service) {
|
|
27531
|
+
if (!service) {
|
|
27532
|
+
return null;
|
|
27533
|
+
}
|
|
27524
27534
|
try {
|
|
27525
|
-
|
|
27535
|
+
const url = new URL(service);
|
|
27536
|
+
return url.port || (url.protocol === "https:" ? "443" : "80");
|
|
27526
27537
|
} catch {
|
|
27527
|
-
|
|
27538
|
+
return null;
|
|
27528
27539
|
}
|
|
27529
|
-
|
|
27530
|
-
|
|
27540
|
+
}
|
|
27541
|
+
async function readTunnelIngress(port) {
|
|
27542
|
+
try {
|
|
27543
|
+
const response = await fetch(`http://127.0.0.1:${port}/config`, {
|
|
27544
|
+
signal: AbortSignal.timeout(1000)
|
|
27545
|
+
});
|
|
27546
|
+
if (!response.ok) {
|
|
27547
|
+
return null;
|
|
27548
|
+
}
|
|
27549
|
+
const data = await response.json();
|
|
27550
|
+
return data.config?.ingress ?? [];
|
|
27551
|
+
} catch {
|
|
27552
|
+
return null;
|
|
27553
|
+
}
|
|
27554
|
+
}
|
|
27555
|
+
function matchingHostname(rules, expectedPort) {
|
|
27556
|
+
for (const rule of rules ?? []) {
|
|
27557
|
+
if (rule.hostname && servicePort(rule.service) === expectedPort) {
|
|
27558
|
+
return rule.hostname;
|
|
27559
|
+
}
|
|
27560
|
+
}
|
|
27561
|
+
return null;
|
|
27562
|
+
}
|
|
27563
|
+
async function getTunnelUrl(platformBaseUrl) {
|
|
27564
|
+
const expectedPort = servicePort(platformBaseUrl);
|
|
27565
|
+
if (!expectedPort) {
|
|
27566
|
+
throw new Error(`Cannot resolve a tunnel for unparseable platform URL '${platformBaseUrl}'`);
|
|
27567
|
+
}
|
|
27568
|
+
const cachedPort = matchedPorts.get(expectedPort);
|
|
27569
|
+
if (cachedPort !== undefined) {
|
|
27570
|
+
const hostname = matchingHostname(await readTunnelIngress(cachedPort), expectedPort);
|
|
27571
|
+
if (hostname) {
|
|
27572
|
+
return `https://${hostname}`;
|
|
27573
|
+
}
|
|
27574
|
+
matchedPorts.delete(expectedPort);
|
|
27531
27575
|
}
|
|
27532
|
-
const
|
|
27533
|
-
const
|
|
27534
|
-
|
|
27535
|
-
|
|
27576
|
+
const ports = Array.from({ length: TUNNEL_METRICS_PORT_RANGE }, (_, offset) => TUNNEL_METRICS_PORT + offset);
|
|
27577
|
+
const results = await Promise.all(ports.map(async (port) => ({ port, rules: await readTunnelIngress(port) })));
|
|
27578
|
+
for (const { port, rules } of results) {
|
|
27579
|
+
const hostname = matchingHostname(rules, expectedPort);
|
|
27580
|
+
if (hostname) {
|
|
27581
|
+
matchedPorts.set(expectedPort, port);
|
|
27582
|
+
return `https://${hostname}`;
|
|
27583
|
+
}
|
|
27536
27584
|
}
|
|
27537
|
-
|
|
27585
|
+
const foreign = results.flatMap(({ rules }) => (rules ?? []).filter((rule) => rule.hostname).map((rule) => `${rule.hostname} -> ${rule.service}`));
|
|
27586
|
+
if (foreign.length > 0) {
|
|
27587
|
+
throw new Error(`A local tunnel is running, but none route to the platform at ${platformBaseUrl} (found: ${foreign.join(", ")}). ${TUNNEL_START_HINT}`);
|
|
27588
|
+
}
|
|
27589
|
+
throw new Error(`Local tunnel is not running. ${TUNNEL_START_HINT}`);
|
|
27538
27590
|
}
|
|
27539
|
-
var TUNNEL_METRICS_PORT = 20241,
|
|
27591
|
+
var TUNNEL_METRICS_PORT = 20241, TUNNEL_METRICS_PORT_RANGE = 10, TUNNEL_START_HINT = "Start this platform's tunnel with `bun dev` or `bun run tunnel`.", matchedPorts;
|
|
27540
27592
|
var init_tunnel = __esm(() => {
|
|
27541
|
-
|
|
27593
|
+
matchedPorts = new Map;
|
|
27542
27594
|
});
|
|
27543
27595
|
|
|
27544
27596
|
// ../utils/src/stages.ts
|
|
@@ -28182,13 +28234,13 @@ class DeployService {
|
|
|
28182
28234
|
return this.deps.config.baseUrl;
|
|
28183
28235
|
}
|
|
28184
28236
|
try {
|
|
28185
|
-
return await getTunnelUrl();
|
|
28186
|
-
} catch {
|
|
28237
|
+
return await getTunnelUrl(this.deps.config.baseUrl);
|
|
28238
|
+
} catch (error) {
|
|
28187
28239
|
setAttributes({
|
|
28188
28240
|
"app.deploy.tunnel_available": false,
|
|
28189
28241
|
"app.deploy.failure_kind": "local_tunnel_unavailable"
|
|
28190
28242
|
});
|
|
28191
|
-
throw new ValidationError(
|
|
28243
|
+
throw new ValidationError(errorMessage(error));
|
|
28192
28244
|
}
|
|
28193
28245
|
}
|
|
28194
28246
|
async deployToCloudflare(args2) {
|
|
@@ -140713,7 +140765,7 @@ var init_session_controller = __esm(() => {
|
|
|
140713
140765
|
let baseUrl;
|
|
140714
140766
|
if (ctx.config.isLocal) {
|
|
140715
140767
|
try {
|
|
140716
|
-
baseUrl = await getTunnelUrl();
|
|
140768
|
+
baseUrl = await getTunnelUrl(ctx.config.baseUrl);
|
|
140717
140769
|
} catch {}
|
|
140718
140770
|
}
|
|
140719
140771
|
return { token, exp, baseUrl };
|