@devdogsuga/devtools 0.1.11 → 0.1.13

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.
Files changed (61) hide show
  1. package/bin/devtools-ci.mjs +15 -1
  2. package/bin/devtools.mjs +15 -1
  3. package/dist/catalog.d.ts +7 -5
  4. package/dist/catalog.js +1 -25
  5. package/dist/cf/build.d.ts +13 -0
  6. package/dist/cf/build.js +65 -0
  7. package/dist/cf/commands.js +6 -16
  8. package/dist/cf/local-env.d.ts +14 -0
  9. package/dist/cf/local-env.js +29 -0
  10. package/dist/ci.js +8 -1
  11. package/dist/cli.js +134 -138
  12. package/dist/commands.d.ts +10 -4
  13. package/dist/commands.js +40 -30
  14. package/dist/db/generated-env.d.ts +69 -0
  15. package/dist/db/generated-env.js +113 -0
  16. package/dist/db/seed-production.js +12 -6
  17. package/dist/docs/index-pages.js +2 -2
  18. package/dist/emails/commands.js +3 -3
  19. package/dist/env/discovery.js +19 -6
  20. package/dist/env-entry.d.ts +49 -0
  21. package/dist/env-entry.js +204 -0
  22. package/dist/environment-doctor.d.ts +9 -0
  23. package/dist/environment-doctor.js +18 -5
  24. package/dist/environment.d.ts +2 -2
  25. package/dist/environment.js +5 -28
  26. package/dist/images/commands.d.ts +3 -6
  27. package/dist/images/commands.js +26 -30
  28. package/dist/images/events.d.ts +27 -16
  29. package/dist/images/events.js +32 -68
  30. package/dist/images/graphics.js +2 -1
  31. package/dist/images/prompts.js +2 -2
  32. package/dist/instance.d.ts +29 -27
  33. package/dist/instance.js +49 -76
  34. package/dist/launch-ci.js +5 -5
  35. package/dist/launch.d.ts +2 -1
  36. package/dist/launch.js +74 -111
  37. package/dist/menu.d.ts +17 -5
  38. package/dist/menu.js +34 -11
  39. package/dist/moderation.d.ts +48 -40
  40. package/dist/moderation.js +95 -194
  41. package/dist/oauth/wizard.d.ts +2 -2
  42. package/dist/oauth/wizard.js +6 -26
  43. package/dist/persona.d.ts +18 -0
  44. package/dist/persona.js +221 -0
  45. package/dist/repo/peer-redirect-hooks.d.ts +16 -0
  46. package/dist/repo/peer-redirect-hooks.js +10 -0
  47. package/dist/repo/peer-redirect.d.ts +4 -0
  48. package/dist/repo/peer-redirect.js +26 -0
  49. package/dist/repo/peers.d.ts +15 -0
  50. package/dist/repo/peers.js +26 -1
  51. package/dist/repo/supabase-project.d.ts +60 -0
  52. package/dist/repo/supabase-project.js +124 -0
  53. package/dist/setup.js +2 -2
  54. package/dist/stack.js +142 -29
  55. package/dist/telemetry.d.ts +18 -5
  56. package/dist/telemetry.js +26 -5
  57. package/dist/ui.d.ts +15 -0
  58. package/dist/ui.js +20 -0
  59. package/dist/workflows/commands.d.ts +10 -0
  60. package/dist/workflows/commands.js +119 -25
  61. package/package.json +10 -5
@@ -14,4 +14,18 @@ const launchCiEntry = join(
14
14
  );
15
15
 
16
16
  const { launchCi } = await import(launchCiEntry);
17
- await launchCi(process.argv.slice(2));
17
+ try {
18
+ await launchCi(process.argv.slice(2));
19
+ } catch (err) {
20
+ // Whatever escapes before a command is dispatched (repo discovery, tier
21
+ // resolution, env entry) — the dispatch itself reports its own. See
22
+ // `bin/devtools-ci-bare.mjs` for the same shape.
23
+ process.stderr.write(
24
+ `devtools-ci: ${err instanceof Error ? err.message : String(err)}\n`,
25
+ );
26
+ const { captureDevtoolsError } = await import(
27
+ join(dirname(fileURLToPath(import.meta.url)), "..", "dist", "telemetry.js")
28
+ );
29
+ await captureDevtoolsError(err);
30
+ process.exitCode = 1;
31
+ }
package/bin/devtools.mjs CHANGED
@@ -24,4 +24,18 @@ const launchEntry = join(
24
24
  );
25
25
 
26
26
  const { launch } = await import(launchEntry);
27
- await launch(process.argv.slice(2));
27
+ try {
28
+ await launch(process.argv.slice(2));
29
+ } catch (err) {
30
+ // Whatever escapes before a command is dispatched (repo discovery, tier
31
+ // resolution, env entry) — the dispatch itself reports its own. See
32
+ // `bin/devtools-ci-bare.mjs` for the same shape.
33
+ process.stderr.write(
34
+ `devtools: ${err instanceof Error ? err.message : String(err)}\n`,
35
+ );
36
+ const { captureDevtoolsError } = await import(
37
+ join(dirname(fileURLToPath(import.meta.url)), "..", "dist", "telemetry.js")
38
+ );
39
+ await captureDevtoolsError(err);
40
+ process.exitCode = 1;
41
+ }
package/dist/catalog.d.ts CHANGED
@@ -14,13 +14,15 @@
14
14
  * a copy of the reason list for people who are not at a terminal, but that copy
15
15
  * is maintained by hand and says so. This is what settles a disagreement.
16
16
  *
17
- * Both calls run as the seeded moderator rather than through an admin client.
17
+ * Both calls run through a moderator client rather than an admin client.
18
18
  * `list_content_types` is `security definer` behind a `canModerate` check, so
19
19
  * an anonymous or ordinary client sees nothing, and `list_report_reasons` is
20
- * `security invoker` and subject to RLS. Using a persona means what prints here
21
- * is what a moderator would actually see, rather than what a superuser can.
20
+ * `security invoker` and subject to RLS. Using a moderator means what prints
21
+ * here is what a moderator would actually see, rather than what a superuser
22
+ * can. `moderation.ts`'s `withTemporaryModerator` is what supplies that
23
+ * client — this module reads with it, it does not sign in on its own.
22
24
  */
23
- import { type Instance } from "./instance.js";
25
+ import type { DevtoolsClient } from "./instance.js";
24
26
  export interface CatalogReason {
25
27
  reason: string;
26
28
  title: string;
@@ -43,7 +45,7 @@ export interface Catalog {
43
45
  reasons: CatalogReason[];
44
46
  contentTypes: CatalogContentType[];
45
47
  }
46
- export declare function readCatalog(instance: Instance): Promise<Catalog>;
48
+ export declare function readCatalog(client: DevtoolsClient): Promise<Catalog>;
47
49
  /**
48
50
  * Formats the catalog for `note()`.
49
51
  *
package/dist/catalog.js CHANGED
@@ -1,28 +1,4 @@
1
- /**
2
- * What can be reported on this instance, and for what reasons?
3
- *
4
- * Neither half of that question had an answer in the CLI before. `doctor` comes
5
- * closest, but it runs `conformance_check()` for a single app and reports
6
- * pass/fail per content type, a diagnostic rather than a listing, and it says
7
- * nothing about reasons at all.
8
- *
9
- * It matters more now than it used to, because both halves stopped being
10
- * editable at runtime. Content types are derived from each app's own schema and
11
- * report reasons are a platform-owned enum, so there is no configuration page
12
- * to open and look at. The database is the only source of truth, and this is
13
- * how a contributor reads it. `docs/platform/reporting-and-feedback.md` carries
14
- * a copy of the reason list for people who are not at a terminal, but that copy
15
- * is maintained by hand and says so. This is what settles a disagreement.
16
- *
17
- * Both calls run as the seeded moderator rather than through an admin client.
18
- * `list_content_types` is `security definer` behind a `canModerate` check, so
19
- * an anonymous or ordinary client sees nothing, and `list_report_reasons` is
20
- * `security invoker` and subject to RLS. Using a persona means what prints here
21
- * is what a moderator would actually see, rather than what a superuser can.
22
- */
23
- import { personaClient, PERSONAS } from "./instance.js";
24
- export async function readCatalog(instance) {
25
- const client = await personaClient(instance, PERSONAS.moderator);
1
+ export async function readCatalog(client) {
26
2
  const [reasons, contentTypes] = await Promise.all([
27
3
  client.rpc("list_report_reasons"),
28
4
  client.rpc("list_content_types"),
@@ -0,0 +1,13 @@
1
+ export declare function needsFrameworkBuild(app: string): boolean;
2
+ /** Build only `app`'s workspace dependencies — all `vinext dev` needs, since
3
+ * it compiles the app itself on demand. */
4
+ export declare function buildWorkspaceDeps(app: string): Promise<number>;
5
+ /** The pnpm invocations `buildWorkerApp` runs, in order. */
6
+ export declare function workerBuildCommands(app: string): string[][];
7
+ /**
8
+ * Build `app` for a local `wrangler dev`. `env` applies to the framework
9
+ * build only — it bakes tier-scoped values (NEXT_PUBLIC_*, CLOUDFLARE_ENV)
10
+ * into the bundle; the dependency builds are tier-independent `tsc` runs.
11
+ * Resolves to the first non-zero exit code, or 0.
12
+ */
13
+ export declare function buildWorkerApp(app: string, env?: NodeJS.ProcessEnv): Promise<number>;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The build a Worker app needs before `wrangler dev` can boot it locally.
3
+ *
4
+ * The vinext apps' `wrangler.jsonc` is the PRE-build shape: `main` is
5
+ * `cloudflare/worker.ts`, which imports vinext's app-router entry and its
6
+ * `virtual:vinext-*` modules, and `assets.directory` is `dist/client`. Plain
7
+ * `wrangler dev` against that file can never work — esbuild cannot resolve
8
+ * Vite's virtual modules, and on a fresh clone `dist/client` does not exist
9
+ * yet ("The directory specified by the "assets.directory" field ... does not
10
+ * exist"). `vinext build` writes `dist/` plus the `.wrangler/deploy/
11
+ * config.json` redirect that points a bare `wrangler dev` at the built
12
+ * `dist/server/wrangler.json` instead.
13
+ *
14
+ * Building every time, rather than only when `dist/` is missing, also keeps
15
+ * a checkout from running a stale bundle — or one a previous `cf preview
16
+ * --tier staging` baked for another tier.
17
+ *
18
+ * `workflows run`/`serve` use `vinext dev` instead and need none of this
19
+ * except the dependency build (`buildWorkspaceDeps`).
20
+ *
21
+ * The app's workspace dependencies build first, the way `devtools run build`
22
+ * orders them (see `run/pick.ts`): their `exports` resolve to gitignored
23
+ * `dist/` output, so on a fresh clone `vinext build` fails to load
24
+ * `next.config.ts` without them.
25
+ */
26
+ import { run } from "../db/run.js";
27
+ /** Apps deployed without a framework build: Wrangler bundles `main` itself. */
28
+ const NO_FRAMEWORK_BUILD = new Set(["sandbox"]);
29
+ export function needsFrameworkBuild(app) {
30
+ return !NO_FRAMEWORK_BUILD.has(app);
31
+ }
32
+ function workspaceDepsBuildCommand(app) {
33
+ return ["-r", "--if-present", "--filter", `${app}^...`, "run", "build"];
34
+ }
35
+ /** Build only `app`'s workspace dependencies — all `vinext dev` needs, since
36
+ * it compiles the app itself on demand. */
37
+ export function buildWorkspaceDeps(app) {
38
+ return run(workspaceDepsBuildCommand(app));
39
+ }
40
+ /** The pnpm invocations `buildWorkerApp` runs, in order. */
41
+ export function workerBuildCommands(app) {
42
+ const commands = [workspaceDepsBuildCommand(app)];
43
+ if (needsFrameworkBuild(app)) {
44
+ commands.push(["--filter", app, "exec", "vinext", "build"]);
45
+ }
46
+ return commands;
47
+ }
48
+ /**
49
+ * Build `app` for a local `wrangler dev`. `env` applies to the framework
50
+ * build only — it bakes tier-scoped values (NEXT_PUBLIC_*, CLOUDFLARE_ENV)
51
+ * into the bundle; the dependency builds are tier-independent `tsc` runs.
52
+ * Resolves to the first non-zero exit code, or 0.
53
+ */
54
+ export async function buildWorkerApp(app, env) {
55
+ const [, ...framework] = workerBuildCommands(app);
56
+ const depsCode = await buildWorkspaceDeps(app);
57
+ if (depsCode !== 0)
58
+ return depsCode;
59
+ for (const command of framework) {
60
+ const code = await run(command, env);
61
+ if (code !== 0)
62
+ return code;
63
+ }
64
+ return 0;
65
+ }
@@ -7,6 +7,7 @@
7
7
  import { confirm } from "@clack/prompts";
8
8
  import { loadEnvLoad } from "../repo/peers.js";
9
9
  import { run } from "../db/run.js";
10
+ import { buildWorkerApp } from "./build.js";
10
11
  import { resolveTier } from "../tier.js";
11
12
  import { unwrap } from "../ui.js";
12
13
  import { isWorkerApp, workerApps } from "../workers.js";
@@ -88,19 +89,6 @@ export async function runCf(argv) {
88
89
  throw err;
89
90
  }
90
91
  }
91
- if (app === "sandbox") {
92
- // No build step for the sandbox app: the tier only needs to reach
93
- // Wrangler's materialized `--env-file`, below.
94
- return withWranglerEnv(app, (envFile) => run([
95
- "--filter",
96
- app,
97
- "exec",
98
- "wrangler",
99
- "dev",
100
- "--env-file",
101
- envFile,
102
- ]), { env: loaded?.env });
103
- }
104
92
  // The build bakes tier-scoped values (NEXT_PUBLIC_* and anything else
105
93
  // read at build time) into the bundle, exactly like `cf:build:<tier>`'s
106
94
  // `DEPLOY_ENV=<tier> with-env` does — so it gets both the tier's loaded
@@ -115,8 +103,10 @@ export async function runCf(argv) {
115
103
  // `CLOUDFLARE_ENV=<tier>` (see each app's `cf:build:<tier>` script) —
116
104
  // vinext bakes the target environment in at BUILD time, and `wrangler
117
105
  // dev` cross-checks it against `-e`/the env it resolves, erroring loudly
118
- // on a mismatch rather than silently running the wrong tier.
119
- const build = await run(["--filter", app, "exec", "vinext", "build"], tier && loaded
106
+ // on a mismatch rather than silently running the wrong tier. The sandbox
107
+ // app has no framework build; `buildWorkerApp` only builds its workspace
108
+ // dependencies.
109
+ const build = await buildWorkerApp(app, tier && loaded
120
110
  ? { ...loaded.env, DEPLOY_ENV: tier, CLOUDFLARE_ENV: tier }
121
111
  : undefined);
122
112
  if (build !== 0)
@@ -125,7 +115,7 @@ export async function runCf(argv) {
125
115
  // credential-bearing file exists, this prevents build-tool temp cleanup
126
116
  // from invalidating the path before Wrangler opens it.
127
117
  return withWranglerEnv(app, (envFile) =>
128
- // `wrangler dev` auto-redirects to the just-built
118
+ // For a vinext app, `wrangler dev` auto-redirects to the just-built
129
119
  // `dist/server/wrangler.json` (a "config redirect" vinext writes to
130
120
  // `.wrangler/deploy/config.json`), so no `--config` flag is needed.
131
121
  // The bundle is already built above; invoke the project's Wrangler
@@ -1,5 +1,19 @@
1
1
  import type { EnvEntry } from "@devdogsuga/env";
2
2
  export declare function renderWranglerEnvFile(keys: readonly string[], environment: NodeJS.ProcessEnv): string;
3
+ /**
4
+ * The process env for a `vinext dev` child, scoped the way the temp
5
+ * `.dev.vars` is for `wrangler dev`.
6
+ *
7
+ * `@cloudflare/vite-plugin` has no `--env-file`: it reads the app dir's own
8
+ * `.dev.vars`/`.env`, or — with `CLOUDFLARE_INCLUDE_PROCESS_ENV` — its whole
9
+ * `process.env`, as Worker vars that override wrangler.jsonc's `vars`. So the
10
+ * scoping happens on the child's env instead: every key some OTHER app
11
+ * declares (and this app doesn't) is dropped, `NODE_ENV` is left to Vite, and
12
+ * `DEPLOY_ENV` is pinned to the tier rather than dropped, because the Vite
13
+ * process's own `next.config.ts` reads it too. Undeclared keys (PATH, HOME,
14
+ * pnpm's own) stay; the child needs them and they carry no app secrets.
15
+ */
16
+ export declare function scopedProcessEnv(app: string, environment: NodeJS.ProcessEnv, tier: string, entries?: readonly EnvEntry[]): Promise<NodeJS.ProcessEnv>;
3
17
  /**
4
18
  * Runs `fn` with the path to an app-scoped, mode-0600 `.dev.vars` file, and
5
19
  * guarantees the file (and its containing directory) is gone before this
@@ -44,6 +44,35 @@ async function scopedKeys(app, entries) {
44
44
  .map((entry) => entry.key)),
45
45
  ].sort();
46
46
  }
47
+ /**
48
+ * The process env for a `vinext dev` child, scoped the way the temp
49
+ * `.dev.vars` is for `wrangler dev`.
50
+ *
51
+ * `@cloudflare/vite-plugin` has no `--env-file`: it reads the app dir's own
52
+ * `.dev.vars`/`.env`, or — with `CLOUDFLARE_INCLUDE_PROCESS_ENV` — its whole
53
+ * `process.env`, as Worker vars that override wrangler.jsonc's `vars`. So the
54
+ * scoping happens on the child's env instead: every key some OTHER app
55
+ * declares (and this app doesn't) is dropped, `NODE_ENV` is left to Vite, and
56
+ * `DEPLOY_ENV` is pinned to the tier rather than dropped, because the Vite
57
+ * process's own `next.config.ts` reads it too. Undeclared keys (PATH, HOME,
58
+ * pnpm's own) stay; the child needs them and they carry no app secrets.
59
+ */
60
+ export async function scopedProcessEnv(app, environment, tier, entries) {
61
+ if (!entries) {
62
+ await loadRegistry();
63
+ entries = (await loadEnv()).declarations();
64
+ }
65
+ const own = new Set(await scopedKeys(app, entries));
66
+ const scoped = { ...environment };
67
+ for (const entry of entries) {
68
+ if (entry.source !== app && !own.has(entry.key))
69
+ delete scoped[entry.key];
70
+ }
71
+ delete scoped.NODE_ENV;
72
+ scoped.DEPLOY_ENV = tier;
73
+ scoped.CLOUDFLARE_INCLUDE_PROCESS_ENV = "true";
74
+ return scoped;
75
+ }
47
76
  /** Writes the mode-0600 `.dev.vars` into a fresh private temp directory. */
48
77
  function materializeEnvFile(keys, environment) {
49
78
  const directory = mkdtempSync(join(tmpdir(), "devtools-wrangler-env-"));
package/dist/ci.js CHANGED
@@ -47,7 +47,7 @@ import { loadRegistry } from "./env/discovery.js";
47
47
  import { positionals } from "./args.js";
48
48
  import { findCiCommand, subcommandCiNames } from "./commands.js";
49
49
  import { isWorkerApp, workerApps } from "./workers.js";
50
- import { captureDevtoolsError, initDevtoolsTelemetry } from "./telemetry.js";
50
+ import { captureDevtoolsError, initDevtoolsTelemetry, reportDevtoolsError, reportDevtoolsFailure, } from "./telemetry.js";
51
51
  function flagValue(rest, flag) {
52
52
  const index = rest.indexOf(flag);
53
53
  if (index === -1)
@@ -189,6 +189,10 @@ async function runAppDeploy(app, rest) {
189
189
  for (const step of steps) {
190
190
  const code = await step.fn();
191
191
  if (code !== 0) {
192
+ // A step is a subprocess (`pnpm … wrangler deploy`) whose output
193
+ // already explains itself in the job log; this is only the signal that
194
+ // a deploy broke, grouped per app and step.
195
+ reportDevtoolsFailure(`devtools-ci deploy ${app}: "${step.label}" failed`, { tier, exitCode: code });
192
196
  process.exitCode = code;
193
197
  return;
194
198
  }
@@ -271,6 +275,9 @@ async function runDeployCommand(rest) {
271
275
  : [
272
276
  `devtools-ci deploy ${sub}: ${err instanceof Error ? err.message : String(err)}`,
273
277
  ]);
278
+ // Caught here, so `launch-ci.ts`'s capture never sees it. Every deploy
279
+ // step runs in CI, where a failure is never the reader's typo to fix.
280
+ reportDevtoolsError(err);
274
281
  process.exitCode = 1;
275
282
  }
276
283
  }