@tallpond/cli 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/cli.js +54 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,6 +49,27 @@ apply automatically; destructive ones are blocked pending an explicit migration.
|
|
|
49
49
|
The schema file is TypeScript: the CLI compiles it in-process (via esbuild) so it
|
|
50
50
|
runs on any supported Node — no bun or global TS loader required.
|
|
51
51
|
|
|
52
|
+
### Functions
|
|
53
|
+
|
|
54
|
+
If the project has a `functions/` directory, `tallpond deploy` also deploys it.
|
|
55
|
+
The convention: **each `.ts`/`.js` file directly under `functions/` is one
|
|
56
|
+
invokable function**, named after the file, and must `export default` its
|
|
57
|
+
handler:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
// functions/submitMove.ts — invoked as tallpond.functions.invoke('submitMove', args)
|
|
61
|
+
export default async (ctx, args) => {
|
|
62
|
+
await ctx.db.query({ table: 'moves', insert: args })
|
|
63
|
+
return { ok: true }
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
File names must be identifiers (letters/digits/underscores). Shared helpers and
|
|
68
|
+
types belong in a subdirectory (e.g. `functions/lib/`) — subdirectories aren't
|
|
69
|
+
scanned, and handlers can import from them freely (everything is bundled). A
|
|
70
|
+
top-level file without a default export is skipped with a warning rather than
|
|
71
|
+
deployed.
|
|
72
|
+
|
|
52
73
|
Full command reference: <https://tallpond.com> → docs → CLI.
|
|
53
74
|
|
|
54
75
|
## License
|
package/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import { parseArgs } from "node:util";
|
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { existsSync as existsSync2 } from "node:fs";
|
|
7
7
|
import { writeFile as writeFile3 } from "node:fs/promises";
|
|
8
|
-
import { resolve as resolve4 } from "node:path";
|
|
8
|
+
import { basename as basename2, resolve as resolve4 } from "node:path";
|
|
9
9
|
|
|
10
10
|
// src/deploy.ts
|
|
11
11
|
import { extname, join, resolve } from "node:path";
|
|
@@ -436,7 +436,33 @@ export function createHandler(functions) {
|
|
|
436
436
|
}
|
|
437
437
|
}
|
|
438
438
|
`;
|
|
439
|
-
async function
|
|
439
|
+
async function partitionByDefaultExport(entries) {
|
|
440
|
+
if (entries.length === 0) return { handlers: [], skipped: [] };
|
|
441
|
+
const { build } = await import("esbuild");
|
|
442
|
+
const result = await build({
|
|
443
|
+
entryPoints: entries.map((e) => e.file),
|
|
444
|
+
bundle: false,
|
|
445
|
+
format: "esm",
|
|
446
|
+
metafile: true,
|
|
447
|
+
write: false,
|
|
448
|
+
outdir: ".",
|
|
449
|
+
logLevel: "silent"
|
|
450
|
+
});
|
|
451
|
+
const withDefault = /* @__PURE__ */ new Set();
|
|
452
|
+
for (const out of Object.values(result.metafile.outputs)) {
|
|
453
|
+
if (out.entryPoint && out.exports.includes("default")) {
|
|
454
|
+
withDefault.add(resolve3(out.entryPoint));
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const handlers = entries.filter((e) => withDefault.has(e.file));
|
|
458
|
+
const skipped = entries.filter((e) => !withDefault.has(e.file));
|
|
459
|
+
return { handlers, skipped };
|
|
460
|
+
}
|
|
461
|
+
async function bundleFunctions(all) {
|
|
462
|
+
const { handlers: entries, skipped } = await partitionByDefaultExport(all);
|
|
463
|
+
if (entries.length === 0) {
|
|
464
|
+
return { script: "", manifest: [], skipped };
|
|
465
|
+
}
|
|
440
466
|
const dir = await mkdtemp2(join4(tmpdir2(), "tallpond-functions-"));
|
|
441
467
|
try {
|
|
442
468
|
await writeFile2(join4(dir, "runtime.mjs"), RUNTIME_SOURCE);
|
|
@@ -463,7 +489,8 @@ ${table}
|
|
|
463
489
|
});
|
|
464
490
|
return {
|
|
465
491
|
script: result.outputFiles[0].text,
|
|
466
|
-
manifest: entries.map((e) => ({ name: e.name }))
|
|
492
|
+
manifest: entries.map((e) => ({ name: e.name })),
|
|
493
|
+
skipped
|
|
467
494
|
};
|
|
468
495
|
} finally {
|
|
469
496
|
await rm2(dir, { recursive: true, force: true });
|
|
@@ -694,19 +721,33 @@ ${renderBlockedChanges(result.changes)}
|
|
|
694
721
|
}
|
|
695
722
|
const fnEntries = discoverFunctions(resolve4(process.cwd(), "functions"));
|
|
696
723
|
if (fnEntries.length > 0) {
|
|
697
|
-
const bundle = await bundleFunctions(fnEntries);
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
724
|
+
const { skipped, ...bundle } = await bundleFunctions(fnEntries);
|
|
725
|
+
for (const s of skipped) {
|
|
726
|
+
process.stderr.write(
|
|
727
|
+
`functions: skipping ${basename2(s.file)} \u2014 no default export (each file under functions/ is one handler; shared code belongs in a subdirectory)
|
|
728
|
+
`
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
if (bundle.manifest.length > 0) {
|
|
732
|
+
const version2 = `f${Date.now().toString(36)}`;
|
|
733
|
+
try {
|
|
734
|
+
const res = await deployFunctionsAsDev(gatewayUrl, token, project.appId, { version: version2, ...bundle });
|
|
735
|
+
process.stdout.write(`functions: ${res.functions.join(", ")} (version ${res.version})
|
|
702
736
|
`);
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
737
|
+
if (res.invocable === false) {
|
|
738
|
+
process.stderr.write(
|
|
739
|
+
`warning: functions deployed but NOT invocable \u2014 ${res.invocable_detail ?? "the gateway reports functions are disabled"}
|
|
740
|
+
`
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
} catch (e) {
|
|
744
|
+
if (e instanceof DevApiError) {
|
|
745
|
+
process.stderr.write(`functions deploy failed: ${e.message}
|
|
706
746
|
`);
|
|
707
|
-
|
|
747
|
+
return 1;
|
|
748
|
+
}
|
|
749
|
+
throw e;
|
|
708
750
|
}
|
|
709
|
-
throw e;
|
|
710
751
|
}
|
|
711
752
|
}
|
|
712
753
|
const dir = values.dir ?? (existsSync2(resolve4(process.cwd(), "dist")) ? "dist" : void 0);
|