@tallpond/cli 0.0.3 → 0.0.5
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 +18 -1
- package/cli.js +51 -4
- package/index.js +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,7 +72,8 @@ export default async (ctx: FunctionContext, args: { resourceId: string; move: un
|
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
The ctx surface is exactly `userId`, `resourceId`, `fn`, `invocationId`,
|
|
75
|
-
`db.query`/`db.batch` (raw `/v1/db` wire shape), and `gateway(path, body)`
|
|
75
|
+
`db.query`/`db.batch` (raw `/v1/db` wire shape), and `gateway(path, body?, opts?)`
|
|
76
|
+
(POST by default, `{ method: 'GET' }` for read routes) —
|
|
76
77
|
annotate with `FunctionContext` so the compiler enforces it. Full contract:
|
|
77
78
|
<https://tallpond.com/docs/functions/>.
|
|
78
79
|
|
|
@@ -82,6 +83,22 @@ scanned, and handlers can import from them freely (everything is bundled). A
|
|
|
82
83
|
top-level file without a default export is skipped with a warning rather than
|
|
83
84
|
deployed.
|
|
84
85
|
|
|
86
|
+
## `tallpond test-session [--user name]`
|
|
87
|
+
|
|
88
|
+
End-to-end testing without a browser: mints a bearer token for a synthetic
|
|
89
|
+
test user of your app. Test users have no wallet — their usage bills your own
|
|
90
|
+
balance directly (bounded by your per-app budget), and tokens expire after
|
|
91
|
+
24h. The token alone goes to stdout:
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
ALICE=$(tallpond test-session --user alice)
|
|
95
|
+
curl -H "Authorization: Bearer $ALICE" https://api.tallpond.com/v1/users/me
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
It authenticates on every user-facing route — tables, resources, functions,
|
|
99
|
+
files, AI — as that user, metering normally. Mint two names for multi-user
|
|
100
|
+
flows.
|
|
101
|
+
|
|
85
102
|
Full command reference: <https://tallpond.com> → docs → CLI.
|
|
86
103
|
|
|
87
104
|
## License
|
package/cli.js
CHANGED
|
@@ -306,6 +306,9 @@ function deployFunctionsAsDev(gatewayUrl, token, appId, bundle, fetcher = fetch)
|
|
|
306
306
|
bundle
|
|
307
307
|
);
|
|
308
308
|
}
|
|
309
|
+
function createTestSession(gatewayUrl, token, appId, name, fetcher = fetch) {
|
|
310
|
+
return request(fetcher, gatewayUrl, token, "POST", `dev/apps/${appId}/test-users`, { name });
|
|
311
|
+
}
|
|
309
312
|
function collectBundleFiles(dir) {
|
|
310
313
|
const out = [];
|
|
311
314
|
const walk = (d) => {
|
|
@@ -382,11 +385,15 @@ function decodeInvocation(token) {
|
|
|
382
385
|
function makeCtx(env, token) {
|
|
383
386
|
const fetchImpl = env.TALLPOND_FETCH ?? fetch
|
|
384
387
|
const base = String(env.TALLPOND_GATEWAY_URL ?? '').replace(/\\/$/, '')
|
|
385
|
-
async function call(path, body) {
|
|
388
|
+
async function call(path, body, opts) {
|
|
389
|
+
const method = (opts && opts.method) || 'POST'
|
|
390
|
+
const bodyless = method === 'GET' || method === 'HEAD'
|
|
391
|
+
const headers = { authorization: 'Bearer ' + token }
|
|
392
|
+
if (!bodyless) headers['content-type'] = 'application/json'
|
|
386
393
|
const res = await fetchImpl(base + path, {
|
|
387
|
-
method
|
|
388
|
-
headers
|
|
389
|
-
body: JSON.stringify(body),
|
|
394
|
+
method,
|
|
395
|
+
headers,
|
|
396
|
+
body: bodyless ? undefined : JSON.stringify(body),
|
|
390
397
|
})
|
|
391
398
|
const data = await res.json().catch(() => null)
|
|
392
399
|
if (!res.ok) {
|
|
@@ -508,6 +515,7 @@ Usage:
|
|
|
508
515
|
tallpond apps list List your apps
|
|
509
516
|
tallpond deploy [schemaFile] Deploy the app: schema + functions/ + built bundle
|
|
510
517
|
tallpond typegen [schemaFile] Generate row types (--out tallpond-env.d.ts)
|
|
518
|
+
tallpond test-session Mint a bearer token for a synthetic test user
|
|
511
519
|
tallpond help Show this help
|
|
512
520
|
|
|
513
521
|
Common flags:
|
|
@@ -521,6 +529,10 @@ deploy flags:
|
|
|
521
529
|
--no-bundle deploy the schema only
|
|
522
530
|
schemaFile defaults to ./.tallpond.schema.ts (skipped if absent)
|
|
523
531
|
|
|
532
|
+
test-session flags:
|
|
533
|
+
--user <name> which test user (default "default"; same name = same user)
|
|
534
|
+
--json print the full response as JSON instead of just the token
|
|
535
|
+
|
|
524
536
|
Platform/CI escape hatch (bypasses login; uses the shared deploy token):
|
|
525
537
|
--app-id <id> --deployment-id <id> --token <token>
|
|
526
538
|
[env TALLPOND_APP_ID, TALLPOND_DEPLOYMENT_ID, TALLPOND_DEPLOY_TOKEN]
|
|
@@ -539,6 +551,7 @@ async function main(argv) {
|
|
|
539
551
|
if (command === "create") return await runAppsCreate(argv.slice(1));
|
|
540
552
|
if (command === "deploy") return await runDeploy(argv.slice(1));
|
|
541
553
|
if (command === "typegen") return await runTypegen(argv.slice(1));
|
|
554
|
+
if (command === "test-session") return await runTestSession(argv.slice(1));
|
|
542
555
|
} catch (e) {
|
|
543
556
|
if (e instanceof DevApiError) {
|
|
544
557
|
if (e.status === 401) {
|
|
@@ -816,6 +829,40 @@ ${renderBlockedChanges(result.changes)}
|
|
|
816
829
|
}
|
|
817
830
|
return 1;
|
|
818
831
|
}
|
|
832
|
+
async function runTestSession(args) {
|
|
833
|
+
const { values } = parseArgs({
|
|
834
|
+
args,
|
|
835
|
+
allowPositionals: false,
|
|
836
|
+
options: {
|
|
837
|
+
"gateway-url": { type: "string" },
|
|
838
|
+
user: { type: "string" },
|
|
839
|
+
json: { type: "boolean" }
|
|
840
|
+
}
|
|
841
|
+
});
|
|
842
|
+
const project = readProjectConfig();
|
|
843
|
+
if (!project) {
|
|
844
|
+
process.stderr.write(`error: no ${PROJECT_CONFIG_FILE} here \u2014 run \`tallpond apps create <name>\` first
|
|
845
|
+
`);
|
|
846
|
+
return 1;
|
|
847
|
+
}
|
|
848
|
+
const gatewayUrl = resolveGatewayUrl(values["gateway-url"] ?? project.gatewayUrl);
|
|
849
|
+
const token = requireToken(gatewayUrl);
|
|
850
|
+
const session = await createTestSession(gatewayUrl, token, project.appId, values.user ?? "default");
|
|
851
|
+
if (values.json) {
|
|
852
|
+
process.stdout.write(`${JSON.stringify(session, null, 2)}
|
|
853
|
+
`);
|
|
854
|
+
return 0;
|
|
855
|
+
}
|
|
856
|
+
process.stderr.write(
|
|
857
|
+
`test user ${session.user_id} \u2014 usage bills your own balance (token expires ${session.expires_at})
|
|
858
|
+
use it as a bearer token, e.g.
|
|
859
|
+
curl -H "Authorization: Bearer <token>" ${gatewayUrl}/v1/users/me
|
|
860
|
+
`
|
|
861
|
+
);
|
|
862
|
+
process.stdout.write(`${session.token}
|
|
863
|
+
`);
|
|
864
|
+
return 0;
|
|
865
|
+
}
|
|
819
866
|
async function runTypegen(args) {
|
|
820
867
|
const { values, positionals } = parseArgs({
|
|
821
868
|
args,
|
package/index.js
CHANGED
|
@@ -241,6 +241,9 @@ function deployFunctionsAsDev(gatewayUrl, token, appId, bundle, fetcher = fetch)
|
|
|
241
241
|
bundle
|
|
242
242
|
);
|
|
243
243
|
}
|
|
244
|
+
function createTestSession(gatewayUrl, token, appId, name, fetcher = fetch) {
|
|
245
|
+
return request(fetcher, gatewayUrl, token, "POST", `dev/apps/${appId}/test-users`, { name });
|
|
246
|
+
}
|
|
244
247
|
function collectBundleFiles(dir) {
|
|
245
248
|
const out = [];
|
|
246
249
|
const walk = (d) => {
|
|
@@ -340,6 +343,7 @@ export {
|
|
|
340
343
|
PROJECT_CONFIG_FILE,
|
|
341
344
|
collectBundleFiles,
|
|
342
345
|
createApp,
|
|
346
|
+
createTestSession,
|
|
343
347
|
deploy,
|
|
344
348
|
deployFunctionsAsDev,
|
|
345
349
|
deploySchemaAsDev,
|