@devdogsuga/devtools 0.1.12 → 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.
- package/bin/devtools-ci.mjs +15 -1
- package/bin/devtools.mjs +15 -1
- package/dist/catalog.d.ts +7 -5
- package/dist/catalog.js +1 -25
- package/dist/cf/build.d.ts +13 -0
- package/dist/cf/build.js +65 -0
- package/dist/cf/commands.js +6 -16
- package/dist/cf/local-env.d.ts +14 -0
- package/dist/cf/local-env.js +29 -0
- package/dist/ci.js +8 -1
- package/dist/cli.js +134 -138
- package/dist/commands.d.ts +10 -4
- package/dist/commands.js +40 -30
- package/dist/db/generated-env.d.ts +69 -0
- package/dist/db/generated-env.js +113 -0
- package/dist/db/seed-production.js +12 -6
- package/dist/docs/index-pages.js +2 -2
- package/dist/emails/commands.js +3 -3
- package/dist/env-entry.d.ts +49 -0
- package/dist/env-entry.js +204 -0
- package/dist/environment-doctor.d.ts +9 -0
- package/dist/environment-doctor.js +18 -5
- package/dist/environment.d.ts +2 -2
- package/dist/environment.js +5 -28
- package/dist/images/commands.d.ts +3 -6
- package/dist/images/commands.js +26 -30
- package/dist/images/events.d.ts +27 -16
- package/dist/images/events.js +32 -68
- package/dist/images/graphics.js +2 -1
- package/dist/images/prompts.js +2 -2
- package/dist/instance.d.ts +29 -27
- package/dist/instance.js +49 -76
- package/dist/launch-ci.js +5 -5
- package/dist/launch.d.ts +2 -1
- package/dist/launch.js +74 -111
- package/dist/menu.d.ts +17 -5
- package/dist/menu.js +34 -11
- package/dist/moderation.d.ts +48 -40
- package/dist/moderation.js +95 -194
- package/dist/oauth/wizard.d.ts +2 -2
- package/dist/oauth/wizard.js +6 -26
- package/dist/persona.d.ts +18 -0
- package/dist/persona.js +221 -0
- package/dist/repo/peers.d.ts +2 -0
- package/dist/repo/peers.js +3 -0
- package/dist/repo/supabase-project.d.ts +60 -0
- package/dist/repo/supabase-project.js +124 -0
- package/dist/setup.js +2 -2
- package/dist/stack.js +142 -29
- package/dist/telemetry.d.ts +18 -5
- package/dist/telemetry.js +26 -5
- package/dist/ui.d.ts +15 -0
- package/dist/ui.js +20 -0
- package/dist/workflows/commands.d.ts +10 -0
- package/dist/workflows/commands.js +119 -25
- package/package.json +9 -4
package/bin/devtools-ci.mjs
CHANGED
|
@@ -14,4 +14,18 @@ const launchCiEntry = join(
|
|
|
14
14
|
);
|
|
15
15
|
|
|
16
16
|
const { launchCi } = await import(launchCiEntry);
|
|
17
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
21
|
-
* is what a moderator would actually see, rather than what a superuser
|
|
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 {
|
|
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(
|
|
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>;
|
package/dist/cf/build.js
ADDED
|
@@ -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
|
+
}
|
package/dist/cf/commands.js
CHANGED
|
@@ -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
|
-
|
|
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
|
package/dist/cf/local-env.d.ts
CHANGED
|
@@ -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
|
package/dist/cf/local-env.js
CHANGED
|
@@ -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
|
}
|