lakebed 0.0.10 → 0.0.12
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/README.md +2 -2
- package/package.json +1 -1
- package/src/cli.js +120 -24
- package/src/runtime.js +16 -0
- package/src/version.js +1 -1
package/README.md
CHANGED
|
@@ -120,8 +120,8 @@ queries: {
|
|
|
120
120
|
```sh
|
|
121
121
|
lakebed new [name] [--template todo] [--no-git]
|
|
122
122
|
lakebed create [name] [--template todo] [--no-git]
|
|
123
|
-
lakebed dev
|
|
124
|
-
lakebed build
|
|
123
|
+
lakebed dev [capsule-dir] [--port 3000]
|
|
124
|
+
lakebed build [capsule-dir] --target anonymous [--out .lakebed/artifacts/app.json] [--json]
|
|
125
125
|
lakebed deploy [capsule-dir] [--ttl 7d] [--api <url>] [--json]
|
|
126
126
|
lakebed claim [capsule-dir] [--api <url>] [--json]
|
|
127
127
|
lakebed anonymous-server [--port 8787] [--public-root-url <url>] [--app-base-domain <domain>]
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
3
|
import { createServer } from "node:http";
|
|
4
4
|
import { existsSync, realpathSync } from "node:fs";
|
|
5
|
-
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
6
|
-
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
5
|
+
import { mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
6
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
7
7
|
import { createInterface } from "node:readline/promises";
|
|
8
8
|
import { promisify } from "node:util";
|
|
9
9
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -38,13 +38,13 @@ function usage() {
|
|
|
38
38
|
Usage:
|
|
39
39
|
lakebed new [name] [--template todo] [--no-git]
|
|
40
40
|
lakebed create [name] [--template todo] [--no-git]
|
|
41
|
-
lakebed dev
|
|
42
|
-
lakebed build
|
|
41
|
+
lakebed dev [capsule-dir] [--port 3000]
|
|
42
|
+
lakebed build [capsule-dir] --target anonymous [--out .lakebed/artifacts/app.json] [--json]
|
|
43
43
|
lakebed deploy [capsule-dir] [--ttl 7d] [--api <url>] [--json]
|
|
44
44
|
lakebed claim [capsule-dir] [--api <url>] [--json]
|
|
45
45
|
lakebed anonymous-server [--port 8787] [--public-root-url <url>] [--app-base-domain <domain>]
|
|
46
46
|
lakebed inspect <deploy-id-or-url> [--api <url>] [--json]
|
|
47
|
-
lakebed run-many
|
|
47
|
+
lakebed run-many [capsule-dir] [--count 20] [--base-port 4000]
|
|
48
48
|
lakebed auth as <name>
|
|
49
49
|
lakebed auth reset
|
|
50
50
|
lakebed db list [deploy-id-or-url] [--port 3000]
|
|
@@ -62,12 +62,27 @@ function readArg(args, name, fallback) {
|
|
|
62
62
|
return args[index + 1] ?? fallback;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
const optionsWithValues = new Set([
|
|
66
|
+
"--api",
|
|
67
|
+
"--app-base-domain",
|
|
68
|
+
"--base-port",
|
|
69
|
+
"--count",
|
|
70
|
+
"--out",
|
|
71
|
+
"--port",
|
|
72
|
+
"--public-root-url",
|
|
73
|
+
"--target",
|
|
74
|
+
"--template",
|
|
75
|
+
"--ttl"
|
|
76
|
+
]);
|
|
77
|
+
|
|
65
78
|
function positionals(args) {
|
|
66
79
|
const values = [];
|
|
67
80
|
for (let index = 0; index < args.length; index += 1) {
|
|
68
81
|
const value = args[index];
|
|
69
82
|
if (value.startsWith("--")) {
|
|
70
|
-
|
|
83
|
+
if (optionsWithValues.has(value)) {
|
|
84
|
+
index += 1;
|
|
85
|
+
}
|
|
71
86
|
continue;
|
|
72
87
|
}
|
|
73
88
|
|
|
@@ -92,7 +107,7 @@ function hasFlag(args, name) {
|
|
|
92
107
|
|
|
93
108
|
function resolveCapsuleDir(value) {
|
|
94
109
|
if (!value) {
|
|
95
|
-
return
|
|
110
|
+
return root;
|
|
96
111
|
}
|
|
97
112
|
|
|
98
113
|
return isAbsolute(value) ? value : resolve(root, value);
|
|
@@ -356,6 +371,32 @@ function sendJson(ws, message) {
|
|
|
356
371
|
ws.send(JSON.stringify(message));
|
|
357
372
|
}
|
|
358
373
|
|
|
374
|
+
async function capsuleFileFingerprint(rootDir, dir = rootDir, entries = []) {
|
|
375
|
+
const dirEntries = await readdir(dir, { withFileTypes: true });
|
|
376
|
+
|
|
377
|
+
for (const entry of dirEntries) {
|
|
378
|
+
if (entry.name === "node_modules" || entry.name === ".lakebed" || entry.name === ".DS_Store") {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const absolutePath = join(dir, entry.name);
|
|
383
|
+
if (entry.isDirectory()) {
|
|
384
|
+
await capsuleFileFingerprint(rootDir, absolutePath, entries);
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (!entry.isFile()) {
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const info = await stat(absolutePath);
|
|
393
|
+
const path = relative(rootDir, absolutePath).split(sep).join("/");
|
|
394
|
+
entries.push(`${path}:${info.size}:${info.mtimeMs}`);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return entries.sort().join("\n");
|
|
398
|
+
}
|
|
399
|
+
|
|
359
400
|
function createContext({ stateCell, auth, logs, env }) {
|
|
360
401
|
return {
|
|
361
402
|
auth,
|
|
@@ -402,25 +443,53 @@ export async function startDevServer({
|
|
|
402
443
|
shooBaseUrl = shooBaseUrlFromEnv()
|
|
403
444
|
} = {}) {
|
|
404
445
|
const resolvedCapsuleDir = resolveCapsuleDir(capsuleDir);
|
|
405
|
-
|
|
446
|
+
let currentBuild = await buildCapsule({ capsuleDir: resolvedCapsuleDir, sourceStore, capsuleId });
|
|
406
447
|
const defaultAuth = await readAuth();
|
|
407
|
-
const stateCell = new StateCell(
|
|
448
|
+
const stateCell = new StateCell(currentBuild.app.schema);
|
|
408
449
|
const logs = new LogBuffer();
|
|
409
450
|
const subscriptions = new Map();
|
|
451
|
+
let fileFingerprint = sourceStore ? "" : await capsuleFileFingerprint(resolvedCapsuleDir);
|
|
452
|
+
let rebuildTimer = null;
|
|
453
|
+
let rebuildPromise = Promise.resolve();
|
|
454
|
+
|
|
455
|
+
async function rebuild() {
|
|
456
|
+
try {
|
|
457
|
+
const nextBuild = await buildCapsule({ capsuleDir: resolvedCapsuleDir, sourceStore, capsuleId });
|
|
458
|
+
currentBuild = nextBuild;
|
|
459
|
+
stateCell.updateSchema(nextBuild.app.schema);
|
|
460
|
+
logs.append("info", "dev server rebuilt capsule", { capsuleDir: resolvedCapsuleDir });
|
|
461
|
+
for (const client of wss.clients) {
|
|
462
|
+
sendJson(client, { op: "refresh" });
|
|
463
|
+
}
|
|
464
|
+
} catch (error) {
|
|
465
|
+
logs.append("error", "dev server rebuild failed", { error: error instanceof Error ? error.message : String(error) });
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function scheduleRebuild() {
|
|
470
|
+
if (rebuildTimer) {
|
|
471
|
+
clearTimeout(rebuildTimer);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
rebuildTimer = setTimeout(() => {
|
|
475
|
+
rebuildTimer = null;
|
|
476
|
+
rebuildPromise = rebuildPromise.then(rebuild, rebuild);
|
|
477
|
+
}, 100);
|
|
478
|
+
}
|
|
410
479
|
|
|
411
480
|
const server = createServer(async (req, res) => {
|
|
412
481
|
try {
|
|
413
482
|
const requestUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
414
483
|
|
|
415
484
|
if (requestUrl.pathname === "/" || requestUrl.pathname === "/index.html" || requestUrl.pathname === "/auth/callback") {
|
|
416
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
417
|
-
res.end(html(
|
|
485
|
+
res.writeHead(200, { "Cache-Control": "no-store", "Content-Type": "text/html; charset=utf-8" });
|
|
486
|
+
res.end(html(currentBuild.app.name ?? "Lakebed Capsule", { shooBaseUrl }));
|
|
418
487
|
return;
|
|
419
488
|
}
|
|
420
489
|
|
|
421
490
|
if (requestUrl.pathname === "/client.js") {
|
|
422
|
-
res.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8" });
|
|
423
|
-
res.end(await readFile(
|
|
491
|
+
res.writeHead(200, { "Cache-Control": "no-store", "Content-Type": "application/javascript; charset=utf-8" });
|
|
492
|
+
res.end(await readFile(currentBuild.clientOut, "utf8"));
|
|
424
493
|
return;
|
|
425
494
|
}
|
|
426
495
|
|
|
@@ -457,11 +526,11 @@ export async function startDevServer({
|
|
|
457
526
|
for (const name of subscription.queries) {
|
|
458
527
|
try {
|
|
459
528
|
const data = await runQuery({
|
|
460
|
-
app:
|
|
529
|
+
app: currentBuild.app,
|
|
461
530
|
stateCell,
|
|
462
531
|
auth: subscription.auth,
|
|
463
532
|
logs,
|
|
464
|
-
env:
|
|
533
|
+
env: currentBuild.env,
|
|
465
534
|
name
|
|
466
535
|
});
|
|
467
536
|
sendJson(ws, { op: "query.result", name, data });
|
|
@@ -493,11 +562,11 @@ export async function startDevServer({
|
|
|
493
562
|
if (message.op === "query.subscribe") {
|
|
494
563
|
subscription.queries.add(message.name);
|
|
495
564
|
const data = await runQuery({
|
|
496
|
-
app:
|
|
565
|
+
app: currentBuild.app,
|
|
497
566
|
stateCell,
|
|
498
567
|
auth: subscription.auth,
|
|
499
568
|
logs,
|
|
500
|
-
env:
|
|
569
|
+
env: currentBuild.env,
|
|
501
570
|
name: message.name
|
|
502
571
|
});
|
|
503
572
|
sendJson(ws, { id: message.id, op: "query.result", ok: true, name: message.name, data });
|
|
@@ -506,11 +575,11 @@ export async function startDevServer({
|
|
|
506
575
|
|
|
507
576
|
if (message.op === "mutation.run") {
|
|
508
577
|
const { result } = await runMutation({
|
|
509
|
-
app:
|
|
578
|
+
app: currentBuild.app,
|
|
510
579
|
stateCell,
|
|
511
580
|
auth: subscription.auth,
|
|
512
581
|
logs,
|
|
513
|
-
env:
|
|
582
|
+
env: currentBuild.env,
|
|
514
583
|
name: message.name,
|
|
515
584
|
args: message.args ?? []
|
|
516
585
|
});
|
|
@@ -570,15 +639,42 @@ export async function startDevServer({
|
|
|
570
639
|
console.log(`Auth: ${defaultAuth.userId}`);
|
|
571
640
|
}
|
|
572
641
|
|
|
642
|
+
const watchInterval = sourceStore
|
|
643
|
+
? null
|
|
644
|
+
: setInterval(async () => {
|
|
645
|
+
try {
|
|
646
|
+
const nextFingerprint = await capsuleFileFingerprint(resolvedCapsuleDir);
|
|
647
|
+
if (nextFingerprint === fileFingerprint) {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
fileFingerprint = nextFingerprint;
|
|
652
|
+
scheduleRebuild();
|
|
653
|
+
} catch (error) {
|
|
654
|
+
logs.append("error", "dev server file watch failed", { error: error instanceof Error ? error.message : String(error) });
|
|
655
|
+
}
|
|
656
|
+
}, 300);
|
|
657
|
+
|
|
573
658
|
return {
|
|
574
|
-
app
|
|
575
|
-
|
|
659
|
+
get app() {
|
|
660
|
+
return currentBuild.app;
|
|
661
|
+
},
|
|
662
|
+
get buildDir() {
|
|
663
|
+
return currentBuild.buildDir;
|
|
664
|
+
},
|
|
576
665
|
capsuleDir: resolvedCapsuleDir,
|
|
577
666
|
logs,
|
|
578
667
|
port,
|
|
579
668
|
stateCell,
|
|
580
669
|
url: `http://localhost:${port}`,
|
|
581
670
|
async close() {
|
|
671
|
+
if (watchInterval) {
|
|
672
|
+
clearInterval(watchInterval);
|
|
673
|
+
}
|
|
674
|
+
if (rebuildTimer) {
|
|
675
|
+
clearTimeout(rebuildTimer);
|
|
676
|
+
}
|
|
677
|
+
await rebuildPromise.catch(() => {});
|
|
582
678
|
for (const client of wss.clients) {
|
|
583
679
|
client.close();
|
|
584
680
|
}
|
|
@@ -831,7 +927,7 @@ async function readResponseJson(response) {
|
|
|
831
927
|
|
|
832
928
|
async function deployCommand(args) {
|
|
833
929
|
const [capsuleArg] = positionals(args);
|
|
834
|
-
const capsuleDir =
|
|
930
|
+
const capsuleDir = resolveCapsuleDir(capsuleArg);
|
|
835
931
|
const sourceStore = await createMemorySourceStoreFromDirectory(capsuleDir);
|
|
836
932
|
const serverEnvFileExists = sourceStore.hasFile(SERVER_ENV_FILE);
|
|
837
933
|
const serverEnv = await readCapsuleServerEnv(sourceStore);
|
|
@@ -973,7 +1069,7 @@ async function deployCommand(args) {
|
|
|
973
1069
|
|
|
974
1070
|
async function claimCommand(args) {
|
|
975
1071
|
const [capsuleArg] = positionals(args);
|
|
976
|
-
const capsuleDir =
|
|
1072
|
+
const capsuleDir = resolveCapsuleDir(capsuleArg);
|
|
977
1073
|
const api = deployApiUrl(args);
|
|
978
1074
|
const metadata = await readDeployMetadata(capsuleDir);
|
|
979
1075
|
|
|
@@ -1153,7 +1249,7 @@ This is a Lakebed capsule. Build the app inside this directory using the Lakebed
|
|
|
1153
1249
|
Run locally:
|
|
1154
1250
|
|
|
1155
1251
|
\`\`\`sh
|
|
1156
|
-
lakebed dev
|
|
1252
|
+
lakebed dev
|
|
1157
1253
|
\`\`\`
|
|
1158
1254
|
|
|
1159
1255
|
Deploy:
|
package/src/runtime.js
CHANGED
|
@@ -187,6 +187,22 @@ export class StateCell {
|
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
updateSchema(schema) {
|
|
191
|
+
this.schema = schema;
|
|
192
|
+
|
|
193
|
+
for (const tableName of Object.keys(schema ?? {})) {
|
|
194
|
+
if (!this.tables.has(tableName)) {
|
|
195
|
+
this.tables.set(tableName, new Map());
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
for (const tableName of this.tables.keys()) {
|
|
200
|
+
if (!schema?.[tableName]) {
|
|
201
|
+
this.tables.delete(tableName);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
190
206
|
createDb() {
|
|
191
207
|
const db = {};
|
|
192
208
|
for (const tableName of this.tables.keys()) {
|
package/src/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const LAKEBED_VERSION = "0.0.
|
|
1
|
+
export const LAKEBED_VERSION = "0.0.12";
|