@dashai/cli 0.9.0 → 0.10.0

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/bin.js CHANGED
@@ -543,8 +543,11 @@ function manifestJson(opts) {
543
543
  owns: {
544
544
  tables: [
545
545
  {
546
+ // NOTE: no `name` key here — the backend manifest validator
547
+ // (POST /api/installations/dev) is strict and rejects unrecognized
548
+ // table keys, and it derives the display name from the slug. A
549
+ // `name` here would fail provisioning's table materialization (#4).
546
550
  slug: "items",
547
- name: "Items",
548
551
  fields: [
549
552
  { slug: "title", type: "text" },
550
553
  { slug: "done", type: "boolean" }
@@ -719,10 +722,12 @@ function envLocal(apiUrl = SCAFFOLD_DEFAULT_API_URL) {
719
722
  "# There is no DASHWISE_INSTALLATION_ID \u2014 the API key resolves the",
720
723
  "# install server-side.",
721
724
  `DASHWISE_API_URL=${apiUrl}`,
722
- "# Scoped `dwk_` key. Left blank; `dashwise dev` injects the local-dev",
723
- "# key. For a standalone `next dev`, paste one from `dashwise api-keys",
724
- "# create <name>`.",
725
- "DASHWISE_API_KEY=",
725
+ "# Scoped `dwk_` key. Commented out on purpose: `dashwise dev` injects",
726
+ "# the local-dev key from ~/.config/dashwise/auth.json, and an empty",
727
+ "# `DASHWISE_API_KEY=` here would SHADOW that (and make a bare `next dev`",
728
+ "# fail with a confusing empty-key error). For a standalone `next dev`,",
729
+ "# uncomment + paste a key from `dashwise api-keys create <name>`.",
730
+ "# DASHWISE_API_KEY=",
726
731
  ""
727
732
  ].join("\n");
728
733
  }
@@ -1919,6 +1924,29 @@ cached in \`~/.config/dashwise/auth.json#modules[${opts.slug}]\` after
1919
1924
  as \`DASHWISE_API_KEY\` into the Next.js child env automatically. The
1920
1925
  key binds the install server-side \u2014 there is no \`DASHWISE_INSTALLATION_ID\`.
1921
1926
 
1927
+ ### Own tables vs. the shared Data Hub
1928
+
1929
+ The dev key \`dashwise init\` mints gives you **full access to your
1930
+ module's OWN tables** (\`owns.tables\` in \`module.json\`) through \`db\` /
1931
+ \`qb\` \u2014 no grant setup needed. That covers the common case.
1932
+
1933
+ The **shared workspace Data Hub** (other tables in the workspace, reached
1934
+ via \`datahub.table(<id>)\`) is separate: it is default-deny and returns
1935
+ **403** with the dev key until you mint a key that carries an explicit
1936
+ Data Hub grant. Do that with:
1937
+
1938
+ \`\`\`
1939
+ # read a specific Data Hub table:
1940
+ dashwise api-keys create datahub-read --grant datahub:table=<tableId>:read
1941
+ # or a whole database (application):
1942
+ dashwise api-keys create datahub-rw --grant datahub:app=<appId>:read,create,update
1943
+ \`\`\`
1944
+
1945
+ Paste the printed key as \`DASHWISE_API_KEY\` (or, for local dev, set it
1946
+ before \`dashwise dev\` so it wins over the auto-injected own-tables key).
1947
+ So: \`db\`/\`qb\` on your own tables works out of the box; \`datahub\` needs a
1948
+ granted key.
1949
+
1922
1950
  ## Auth in dev mode
1923
1951
 
1924
1952
  \`dashwise dev\` registers a dev-mode SSO session with the backend so
@@ -2303,18 +2331,40 @@ async function moduleInitCommand(slugArg, options) {
2303
2331
  let devInstall = null;
2304
2332
  const wantsProvision = options.provision !== false;
2305
2333
  if (wantsProvision) {
2334
+ const manifest = (() => {
2335
+ try {
2336
+ return readManifest(dir) ?? void 0;
2337
+ } catch {
2338
+ return void 0;
2339
+ }
2340
+ })();
2306
2341
  devInstall = await tryProvisionDevInstall({
2307
2342
  slug,
2308
2343
  name,
2309
2344
  description: options.description,
2310
2345
  workspaceFlag: options.workspace,
2311
- moduleKind: kind
2346
+ moduleKind: kind,
2347
+ ...manifest !== void 0 ? { manifest } : {}
2312
2348
  });
2313
2349
  } else {
2314
2350
  info(
2315
2351
  `Skipping provisioning (${code("--no-provision")}). ${dim("Run with provisioning later to enable `dashwise dev` data-plane.")}`
2316
2352
  );
2317
2353
  }
2354
+ const keyCached = !wantsProvision || readModuleEntry(slug)?.apiKey ? true : false;
2355
+ if (wantsProvision && !keyCached) {
2356
+ fail(
2357
+ `NO API KEY WAS CACHED for ${code(slug)} \u2014 the scaffold is written, but ${code("dashwise dev")} will run with NO token (data-plane + sign-in will fail).`
2358
+ );
2359
+ info(
2360
+ `Fix it by re-running provisioning once the cause above is resolved:
2361
+ ${code(`dashwise init ${slug} --force --workspace <your-workspace-slug>`)}
2362
+ Confirm success: the line ${code("\u2713 Provisioned dev install id=\u2026 API key cached")} must appear,
2363
+ and ${code("cat ~/.config/dashwise/auth.json")} must show a non-empty ${code(`modules.${slug}.apiKey`)} starting with ${code("dwk_")}.`
2364
+ );
2365
+ outro("Scaffold written, but provisioning failed \u2014 see above.");
2366
+ return 2;
2367
+ }
2318
2368
  info(`Next steps:`);
2319
2369
  process.stdout.write(`
2320
2370
  ${dim("1.")} ${code(`cd ${dir}`)}
@@ -2343,6 +2393,20 @@ async function tryProvisionDevInstall(args) {
2343
2393
  warn("CLI config disappeared mid-init. Skipping provisioning.");
2344
2394
  return null;
2345
2395
  }
2396
+ const targetApiUrl = resolveApiUrl();
2397
+ info(`Backend: ${code(targetApiUrl)}`);
2398
+ const hostOf = (u) => {
2399
+ try {
2400
+ return new URL(u).host;
2401
+ } catch {
2402
+ return u;
2403
+ }
2404
+ };
2405
+ if (hostOf(targetApiUrl) !== hostOf(cfg.apiUrl)) {
2406
+ warn(
2407
+ `Your saved login is for ${code(cfg.apiUrl)} but this run targets ${code(targetApiUrl)}. Provisioning uses the saved token, which the target backend will reject (401). Log in against the target first: ${code(`dashwise login --api-url ${targetApiUrl}`)}.`
2408
+ );
2409
+ }
2346
2410
  let workspace2;
2347
2411
  try {
2348
2412
  workspace2 = await selectWorkspace({
@@ -2358,25 +2422,42 @@ async function tryProvisionDevInstall(args) {
2358
2422
  info(
2359
2423
  `Provisioning dev install in workspace ${code(workspace2.slug)} (id=${workspace2.id})\u2026`
2360
2424
  );
2425
+ const baseBody = {
2426
+ slug: args.slug,
2427
+ name: args.name,
2428
+ ...args.description !== void 0 ? { description: args.description } : {},
2429
+ workspaceId: workspace2.id,
2430
+ ...args.moduleKind !== void 0 ? { moduleKind: args.moduleKind } : {}
2431
+ };
2432
+ const reqOpts = { baseUrl: resolveApiUrl(), auth: { token: cfg.token } };
2361
2433
  let response;
2362
2434
  try {
2363
2435
  response = await createDevInstallation(
2364
- {
2365
- slug: args.slug,
2366
- name: args.name,
2367
- ...args.description !== void 0 ? { description: args.description } : {},
2368
- workspaceId: workspace2.id,
2369
- ...args.moduleKind !== void 0 ? { moduleKind: args.moduleKind } : {}
2370
- },
2371
- { baseUrl: resolveApiUrl(), auth: { token: cfg.token } }
2436
+ { ...baseBody, ...args.manifest !== void 0 ? { manifest: args.manifest } : {} },
2437
+ reqOpts
2372
2438
  );
2373
2439
  } catch (err) {
2374
- if (err instanceof ApiError) {
2440
+ if (err instanceof ApiError && err.code === "MANIFEST_INVALID" && args.manifest !== void 0) {
2441
+ warn(
2442
+ `Couldn't pre-materialize the seeded table(s) (${err.message}). Provisioning without them; add tables later with ${code("dashwise table create")}.`
2443
+ );
2444
+ try {
2445
+ response = await createDevInstallation(baseBody, reqOpts);
2446
+ } catch (err2) {
2447
+ if (err2 instanceof ApiError) {
2448
+ surfaceProvisionApiError(err2, args.slug);
2449
+ return null;
2450
+ }
2451
+ warn(`Provisioning failed: ${err2.message}`);
2452
+ return null;
2453
+ }
2454
+ } else if (err instanceof ApiError) {
2375
2455
  surfaceProvisionApiError(err, args.slug);
2376
2456
  return null;
2457
+ } else {
2458
+ warn(`Provisioning failed: ${err.message}`);
2459
+ return null;
2377
2460
  }
2378
- warn(`Provisioning failed: ${err.message}`);
2379
- return null;
2380
2461
  }
2381
2462
  try {
2382
2463
  upsertModuleEntry(args.slug, {
@@ -2394,7 +2475,7 @@ async function tryProvisionDevInstall(args) {
2394
2475
  return response;
2395
2476
  }
2396
2477
  success(
2397
- `Provisioned dev install id=${response.installation.id}. API key cached in your CLI config.`
2478
+ `Provisioned dev install id=${response.installation.id}. API key cached in your CLI config.` + (response.tables_applied > 0 ? ` ${response.tables_applied} seeded table(s) materialized.` : "")
2398
2479
  );
2399
2480
  return response;
2400
2481
  }
@@ -2439,6 +2520,7 @@ var init_init = __esm({
2439
2520
  init_output();
2440
2521
  init_api_client();
2441
2522
  init_config();
2523
+ init_manifest_io();
2442
2524
  init_workspaces();
2443
2525
  }
2444
2526
  });
@@ -3353,8 +3435,13 @@ async function devCommand(options) {
3353
3435
  ` ${dim("api key")} ${maskedToken(apiKeyEnv)} ${dim(`(from ${source}${installId ? `, install id=${installId}` : ""})`)}`
3354
3436
  );
3355
3437
  } else if (manifestSlug !== void 0 && installationIdEnv === void 0) {
3356
- warn(
3357
- `No API key cached for module ${code(manifestSlug)}. Data-plane calls (db, qb) will fail with UnauthenticatedError. Run ${code(`dashwise init ${manifestSlug} --force`)} to provision a dev install + cache a key.`
3438
+ fail(
3439
+ `No ${code("dwk_")} API key for module ${code(manifestSlug)} \u2014 the module will run with NO token.`
3440
+ );
3441
+ info(
3442
+ ` Data-plane calls (db, qb) AND sign-in will fail until you provision a dev install:
3443
+ ${code(`dashwise init ${manifestSlug} --force --workspace <your-workspace-slug>`)}
3444
+ (Ensure you're logged into the right backend: ${code(`dashwise login --api-url ${apiUrl}`)}.)`
3358
3445
  );
3359
3446
  }
3360
3447
  info("");
@@ -3528,9 +3615,9 @@ async function resolveModuleApiKey(slug, entry, apiUrl, cliBearer) {
3528
3615
  if (process.env.DASHWISE_API_KEY) return null;
3529
3616
  if (!entry) return null;
3530
3617
  if (entry.apiKey) return null;
3531
- if (!entry.runtimeToken) return null;
3618
+ if (!entry.installationId) return null;
3532
3619
  info(
3533
- `Upgrading cached credentials for ${code(slug)} to a ${code("dwk_")} API key (the local runtime token format was retired)\u2026`
3620
+ `No usable ${code("dwk_")} API key cached for ${code(slug)} \u2014 minting a fresh one against dev install ${entry.installationId} (a stale CLI can cache an empty key)\u2026`
3534
3621
  );
3535
3622
  let minted;
3536
3623
  try {
@@ -8501,6 +8588,12 @@ program.name("dashwise").description("DashWise CLI \u2014 log in, author modules
8501
8588
  "--api-url <url>",
8502
8589
  "Override DashWise backend URL (default: $DASHWISE_API_URL or saved config or http://localhost:3000)"
8503
8590
  );
8591
+ program.hook("preAction", () => {
8592
+ const apiUrl = program.opts().apiUrl?.trim();
8593
+ if (apiUrl) {
8594
+ process.env.DASHWISE_API_URL = apiUrl.replace(/\/+$/, "");
8595
+ }
8596
+ });
8504
8597
  program.command("login").description("Sign in via the device-code flow.").option("--no-prompt", 'Skip the "open browser" prompt; just print the URL.').option("--name <name>", "Suggested name for the minted token (default: dashwise-cli).").action(async (cmdOpts) => {
8505
8598
  const apiUrl = program.opts().apiUrl;
8506
8599
  const exit = await loginCommand({
@@ -9138,7 +9231,7 @@ fieldCmd.command("set-type <table-slug> <field-slug> <new-type>").description(
9138
9231
  }
9139
9232
  })();
9140
9233
  function getVersion() {
9141
- return "0.9.0";
9234
+ return "0.10.0";
9142
9235
  }
9143
9236
  function parseIntOption(value) {
9144
9237
  const n = Number.parseInt(value, 10);