@devdogsuga/devtools 0.1.26 → 0.1.28

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 (271) hide show
  1. package/README.md +72 -11
  2. package/bin/devtools.mjs +4 -4
  3. package/dist/cli-CZKkNHK5.js +6619 -0
  4. package/dist/failure-log-BBPGejQv.js +256 -0
  5. package/dist/launch.js +267 -281
  6. package/dist/local-env-D8HC2JK_.js +351 -0
  7. package/dist/local-env-DrBCXqD8.js +2 -0
  8. package/dist/menu-CQBsQRgH.js +1684 -0
  9. package/dist/peer-redirect-hooks.js +15 -0
  10. package/dist/peers-RBplwovA.js +229 -0
  11. package/dist/root-C2OmZDwD.js +94 -0
  12. package/dist/stack-Bx-55oi_.js +2 -0
  13. package/dist/stack-BynCgRVS.js +181 -0
  14. package/dist/supabase-project-0knM0mqd.js +426 -0
  15. package/dist/telemetry-C4foD8MD.js +249 -0
  16. package/dist/telemetry.js +2 -148
  17. package/package.json +10 -29
  18. package/bin/devtools-ci-bare.mjs +0 -48
  19. package/bin/devtools-ci.mjs +0 -31
  20. package/dist/args.d.ts +0 -26
  21. package/dist/args.js +0 -78
  22. package/dist/bws/bw.d.ts +0 -18
  23. package/dist/bws/bw.js +0 -70
  24. package/dist/bws/client.d.ts +0 -43
  25. package/dist/bws/client.js +0 -363
  26. package/dist/bws/environments.d.ts +0 -96
  27. package/dist/bws/environments.js +0 -111
  28. package/dist/bws/pace.d.ts +0 -25
  29. package/dist/bws/pace.js +0 -43
  30. package/dist/bws/retry.d.ts +0 -12
  31. package/dist/bws/retry.js +0 -39
  32. package/dist/bws/store.d.ts +0 -1
  33. package/dist/bws/store.js +0 -32
  34. package/dist/bws/token.d.ts +0 -31
  35. package/dist/bws/token.js +0 -88
  36. package/dist/bws/vault.d.ts +0 -29
  37. package/dist/bws/vault.js +0 -200
  38. package/dist/catalog.d.ts +0 -57
  39. package/dist/catalog.js +0 -57
  40. package/dist/cf/build.d.ts +0 -13
  41. package/dist/cf/build.js +0 -65
  42. package/dist/cf/commands.d.ts +0 -1
  43. package/dist/cf/commands.js +0 -172
  44. package/dist/cf/local-env.d.ts +0 -58
  45. package/dist/cf/local-env.js +0 -177
  46. package/dist/ci.d.ts +0 -1
  47. package/dist/ci.js +0 -328
  48. package/dist/cli.d.ts +0 -11
  49. package/dist/cli.js +0 -1094
  50. package/dist/commands.d.ts +0 -261
  51. package/dist/commands.js +0 -1318
  52. package/dist/completions.d.ts +0 -4
  53. package/dist/completions.js +0 -110
  54. package/dist/cron/commands.d.ts +0 -47
  55. package/dist/cron/commands.js +0 -372
  56. package/dist/cron/discovery.d.ts +0 -55
  57. package/dist/cron/discovery.js +0 -85
  58. package/dist/cron/schema.d.ts +0 -45
  59. package/dist/cron/schema.js +0 -35
  60. package/dist/db/config-push.d.ts +0 -8
  61. package/dist/db/config-push.js +0 -11
  62. package/dist/db/connection.d.ts +0 -79
  63. package/dist/db/connection.js +0 -114
  64. package/dist/db/exec.d.ts +0 -1
  65. package/dist/db/exec.js +0 -11
  66. package/dist/db/generate-types.d.ts +0 -1
  67. package/dist/db/generate-types.js +0 -4
  68. package/dist/db/generated-env.d.ts +0 -69
  69. package/dist/db/generated-env.js +0 -113
  70. package/dist/db/introspect.d.ts +0 -1
  71. package/dist/db/introspect.js +0 -122
  72. package/dist/db/local-env.d.ts +0 -65
  73. package/dist/db/local-env.js +0 -23
  74. package/dist/db/new-migration.d.ts +0 -8
  75. package/dist/db/new-migration.js +0 -69
  76. package/dist/db/pooler.d.ts +0 -23
  77. package/dist/db/pooler.js +0 -55
  78. package/dist/db/run.d.ts +0 -68
  79. package/dist/db/run.js +0 -156
  80. package/dist/db/seed-buckets.d.ts +0 -2
  81. package/dist/db/seed-buckets.js +0 -4
  82. package/dist/db/seed-production.d.ts +0 -8
  83. package/dist/db/seed-production.js +0 -79
  84. package/dist/db/seed-roles.d.ts +0 -7
  85. package/dist/db/seed-roles.js +0 -33
  86. package/dist/db/session-refresh.d.ts +0 -43
  87. package/dist/db/session-refresh.js +0 -40
  88. package/dist/deploy/migrations.d.ts +0 -20
  89. package/dist/deploy/migrations.js +0 -68
  90. package/dist/deploy/orphans.d.ts +0 -20
  91. package/dist/deploy/orphans.js +0 -177
  92. package/dist/deploy/preflight.d.ts +0 -53
  93. package/dist/deploy/preflight.js +0 -189
  94. package/dist/deploy/report.d.ts +0 -26
  95. package/dist/deploy/report.js +0 -70
  96. package/dist/deploy/require-planner.d.ts +0 -2
  97. package/dist/deploy/require-planner.js +0 -60
  98. package/dist/deploy/require-token.d.ts +0 -15
  99. package/dist/deploy/require-token.js +0 -78
  100. package/dist/deploy/secrets-file.d.ts +0 -17
  101. package/dist/deploy/secrets-file.js +0 -166
  102. package/dist/deploy/write-env.d.ts +0 -40
  103. package/dist/deploy/write-env.js +0 -395
  104. package/dist/docs/index-pages.d.ts +0 -38
  105. package/dist/docs/index-pages.js +0 -186
  106. package/dist/emails/commands.d.ts +0 -15
  107. package/dist/emails/commands.js +0 -130
  108. package/dist/emails/email-types.d.ts +0 -13
  109. package/dist/emails/email-types.js +0 -1
  110. package/dist/emails/fixtures.d.ts +0 -20
  111. package/dist/emails/fixtures.js +0 -20
  112. package/dist/env/audit.d.ts +0 -206
  113. package/dist/env/audit.js +0 -436
  114. package/dist/env/cloudflare.d.ts +0 -21
  115. package/dist/env/cloudflare.js +0 -61
  116. package/dist/env/commands.d.ts +0 -99
  117. package/dist/env/commands.js +0 -613
  118. package/dist/env/discovery.d.ts +0 -15
  119. package/dist/env/discovery.js +0 -239
  120. package/dist/env/document.d.ts +0 -119
  121. package/dist/env/document.js +0 -323
  122. package/dist/env/example.d.ts +0 -68
  123. package/dist/env/example.js +0 -605
  124. package/dist/env/selection.d.ts +0 -105
  125. package/dist/env/selection.js +0 -228
  126. package/dist/env-entry.d.ts +0 -49
  127. package/dist/env-entry.js +0 -204
  128. package/dist/environment-doctor.d.ts +0 -45
  129. package/dist/environment-doctor.js +0 -429
  130. package/dist/environment.d.ts +0 -92
  131. package/dist/environment.js +0 -185
  132. package/dist/fingerprint.d.ts +0 -10
  133. package/dist/fingerprint.js +0 -18
  134. package/dist/gen/campus-map.d.ts +0 -1
  135. package/dist/gen/campus-map.js +0 -473
  136. package/dist/gen/commands.d.ts +0 -1
  137. package/dist/gen/commands.js +0 -25
  138. package/dist/gen/hypno.d.ts +0 -1
  139. package/dist/gen/hypno.js +0 -134
  140. package/dist/gh/client.d.ts +0 -80
  141. package/dist/gh/client.js +0 -259
  142. package/dist/gh/environments.d.ts +0 -83
  143. package/dist/gh/environments.js +0 -178
  144. package/dist/gh/rulesets/actors.d.ts +0 -14
  145. package/dist/gh/rulesets/actors.js +0 -69
  146. package/dist/gh/rulesets/api.d.ts +0 -15
  147. package/dist/gh/rulesets/api.js +0 -104
  148. package/dist/gh/rulesets/commands.d.ts +0 -3
  149. package/dist/gh/rulesets/commands.js +0 -203
  150. package/dist/gh/rulesets/desired.d.ts +0 -112
  151. package/dist/gh/rulesets/desired.js +0 -271
  152. package/dist/gh/rulesets/diff.d.ts +0 -65
  153. package/dist/gh/rulesets/diff.js +0 -170
  154. package/dist/gh/rulesets/types.d.ts +0 -76
  155. package/dist/gh/rulesets/types.js +0 -13
  156. package/dist/gh/settings/api.d.ts +0 -40
  157. package/dist/gh/settings/api.js +0 -180
  158. package/dist/gh/settings/commands.d.ts +0 -1
  159. package/dist/gh/settings/commands.js +0 -254
  160. package/dist/gh/settings/desired.d.ts +0 -53
  161. package/dist/gh/settings/desired.js +0 -43
  162. package/dist/gh/settings/diff.d.ts +0 -57
  163. package/dist/gh/settings/diff.js +0 -161
  164. package/dist/gh/settings/types.d.ts +0 -92
  165. package/dist/gh/settings/types.js +0 -11
  166. package/dist/gh/settings/workflows.d.ts +0 -41
  167. package/dist/gh/settings/workflows.js +0 -128
  168. package/dist/grantRoot.d.ts +0 -42
  169. package/dist/grantRoot.js +0 -89
  170. package/dist/help.d.ts +0 -18
  171. package/dist/help.js +0 -169
  172. package/dist/images/commands.d.ts +0 -32
  173. package/dist/images/commands.js +0 -231
  174. package/dist/images/events.d.ts +0 -47
  175. package/dist/images/events.js +0 -116
  176. package/dist/images/graphics.d.ts +0 -70
  177. package/dist/images/graphics.js +0 -234
  178. package/dist/images/open-graph-types.d.ts +0 -75
  179. package/dist/images/open-graph-types.js +0 -1
  180. package/dist/images/prompts.d.ts +0 -34
  181. package/dist/images/prompts.js +0 -100
  182. package/dist/images/render.d.ts +0 -21
  183. package/dist/images/render.js +0 -61
  184. package/dist/images/select.d.ts +0 -64
  185. package/dist/images/select.js +0 -96
  186. package/dist/instance.d.ts +0 -69
  187. package/dist/instance.js +0 -102
  188. package/dist/invocation.d.ts +0 -49
  189. package/dist/invocation.js +0 -78
  190. package/dist/launch-ci.d.ts +0 -1
  191. package/dist/launch-ci.js +0 -90
  192. package/dist/launch.d.ts +0 -18
  193. package/dist/menu.d.ts +0 -43
  194. package/dist/menu.js +0 -300
  195. package/dist/moderation.d.ts +0 -70
  196. package/dist/moderation.js +0 -128
  197. package/dist/oauth/browser.d.ts +0 -1
  198. package/dist/oauth/browser.js +0 -13
  199. package/dist/oauth/config.d.ts +0 -24
  200. package/dist/oauth/config.js +0 -24
  201. package/dist/oauth/connect-url.d.ts +0 -17
  202. package/dist/oauth/connect-url.js +0 -23
  203. package/dist/oauth/db.d.ts +0 -37
  204. package/dist/oauth/db.js +0 -108
  205. package/dist/oauth/device.d.ts +0 -93
  206. package/dist/oauth/device.js +0 -198
  207. package/dist/oauth/discovery.d.ts +0 -28
  208. package/dist/oauth/discovery.js +0 -54
  209. package/dist/oauth/env-file.d.ts +0 -6
  210. package/dist/oauth/env-file.js +0 -35
  211. package/dist/oauth/exchange.d.ts +0 -43
  212. package/dist/oauth/exchange.js +0 -93
  213. package/dist/oauth/label.d.ts +0 -1
  214. package/dist/oauth/label.js +0 -25
  215. package/dist/oauth/loopback.d.ts +0 -52
  216. package/dist/oauth/loopback.js +0 -168
  217. package/dist/oauth/pkce.d.ts +0 -6
  218. package/dist/oauth/pkce.js +0 -34
  219. package/dist/oauth/target.d.ts +0 -54
  220. package/dist/oauth/target.js +0 -102
  221. package/dist/oauth/transport.d.ts +0 -41
  222. package/dist/oauth/transport.js +0 -32
  223. package/dist/oauth/wizard.d.ts +0 -40
  224. package/dist/oauth/wizard.js +0 -578
  225. package/dist/persona.d.ts +0 -18
  226. package/dist/persona.js +0 -221
  227. package/dist/pick.d.ts +0 -6
  228. package/dist/pick.js +0 -64
  229. package/dist/pipes.d.ts +0 -1
  230. package/dist/pipes.js +0 -25
  231. package/dist/planner/checks.d.ts +0 -26
  232. package/dist/planner/checks.js +0 -104
  233. package/dist/planner/commands.d.ts +0 -10
  234. package/dist/planner/commands.js +0 -367
  235. package/dist/planner/db.d.ts +0 -15
  236. package/dist/planner/db.js +0 -33
  237. package/dist/planner/role.d.ts +0 -33
  238. package/dist/planner/role.js +0 -68
  239. package/dist/repo/peer-redirect-hooks.d.ts +0 -16
  240. package/dist/repo/peer-redirect-hooks.js +0 -10
  241. package/dist/repo/peer-redirect.d.ts +0 -4
  242. package/dist/repo/peer-redirect.js +0 -26
  243. package/dist/repo/peers.d.ts +0 -39
  244. package/dist/repo/peers.js +0 -139
  245. package/dist/repo/resolve.d.ts +0 -41
  246. package/dist/repo/resolve.js +0 -152
  247. package/dist/repo/root.d.ts +0 -23
  248. package/dist/repo/root.js +0 -91
  249. package/dist/repo/source.d.ts +0 -14
  250. package/dist/repo/source.js +0 -54
  251. package/dist/repo/supabase-project.d.ts +0 -60
  252. package/dist/repo/supabase-project.js +0 -124
  253. package/dist/repo/tsx-loader.d.ts +0 -6
  254. package/dist/repo/tsx-loader.js +0 -41
  255. package/dist/run/pick.d.ts +0 -90
  256. package/dist/run/pick.js +0 -664
  257. package/dist/setup.d.ts +0 -1
  258. package/dist/setup.js +0 -280
  259. package/dist/stack.d.ts +0 -48
  260. package/dist/stack.js +0 -403
  261. package/dist/telemetry.d.ts +0 -59
  262. package/dist/tier.d.ts +0 -40
  263. package/dist/tier.js +0 -96
  264. package/dist/ui.d.ts +0 -40
  265. package/dist/ui.js +0 -71
  266. package/dist/version.d.ts +0 -7
  267. package/dist/version.js +0 -17
  268. package/dist/workers.d.ts +0 -5
  269. package/dist/workers.js +0 -33
  270. package/dist/workflows/commands.d.ts +0 -54
  271. package/dist/workflows/commands.js +0 -763
package/dist/run/pick.js DELETED
@@ -1,664 +0,0 @@
1
- /**
2
- * `pnpm devtools run <task>`: which apps a root pnpm task runs against.
3
- *
4
- * `pnpm dev` used to start every app in the workspace at once: two Next dev
5
- * servers on two ports and a Flutter run, when almost nobody is working on
6
- * more than one. pnpm's own `--filter` has always made scoping possible, so
7
- * the capability was there. The DEFAULT was wrong, and a default you have to
8
- * know a flag to escape is one most contributors never escape.
9
- *
10
- * So every root package script routes through here. With a TTY and no
11
- * explicit filter it asks; without either it is a passthrough that costs one
12
- * or two process spawns (one to build dependencies first, for tasks that need
13
- * that — see `NEEDS_DEPS_BUILT` below — then one to run the task itself).
14
- *
15
- * ## Why this lives in devtools now
16
- *
17
- * It began here, moved out to `scripts/pick.mjs` as plain Node, and has come
18
- * back. The move out rested on two grounds, and re-checking them on 2026-08-27
19
- * found one of them false:
20
- *
21
- * * "CI pays for it." It does not. No workflow invokes the root task
22
- * aliases: `ci.yaml` and `deploy.yaml` both call `pnpm -r …`/`pnpm --filter
23
- * …` directly, which never reaches this file. Only a person at a terminal
24
- * pays it, and there it buys the question this exists to ask. The `CI`
25
- * guard in `shouldAsk` stays as insurance against a runner that one day
26
- * does type `pnpm build`.
27
- * * "It must not need a build." This one holds, and is why the module stays
28
- * careful. `pnpm build` routes through here, so anything on this path that
29
- * had to be compiled first would be a cycle. It is safe because devtools
30
- * runs from source under `--conditions=devdogs-source`, and because the
31
- * one workspace dependency with no source condition (`@devdogsuga/docs`,
32
- * which is `dist`-only) is reached through a lazy `await import` in
33
- * `docs/index-pages.ts` and never loads on this path. ⚠️ A top-level
34
- * `import` of `@devdogsuga/docs` anywhere in the eager graph would
35
- * deadlock `pnpm build` on itself.
36
- *
37
- * ## Turbo's `^build` ordering, in pnpm terms
38
- *
39
- * Turbo's task graph guaranteed that a package's workspace dependencies were
40
- * built before the task ran against it — `dependsOn: ["^build"]` in the old
41
- * `turbo.json`, for every task in `NEEDS_DEPS_BUILT` below. pnpm has no single
42
- * flag that means the same thing, but it has the two pieces that compose into
43
- * it: `<pkg>^...` selects only the direct and indirect DEPENDENCIES of a
44
- * package (excluding the package itself), and plain recursive `pnpm -r` runs
45
- * scripts in dependency order (a package's own dependencies always run before
46
- * it). So building the deps-of set with `-r --filter '<pkg>^...' run build`
47
- * and then running the real task against the package itself reproduces
48
- * turbo's ordering with two spawns instead of turbo's one.
49
- *
50
- * `@devdogsuga/email` is the one package that needs its OWN build before its
51
- * OWN typecheck/test, not just its dependencies' — its build step generates
52
- * gitignored `src/generated/templates.ts`. That case never runs through this
53
- * picker (it lists `apps/*`, not `packages/*`; see `appsWith` below), but
54
- * matters for anyone reasoning about the workspace-wide passthrough path
55
- * (see `passthroughApps` and `allAppNames`), which builds the union of every
56
- * app's dependencies — email included, since some app depends on it —
57
- * before the target task, deliberately stopping short of a bare `pnpm -r run
58
- * build`: that would also run every APP's own build script, which a
59
- * typecheck/lint/test/dev run has no business doing (see `passthroughApps`'s
60
- * doc comment for why that is more than just wasted work).
61
- */
62
- import { spawn, spawnSync } from "node:child_process";
63
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, } from "node:fs";
64
- import { dirname, join } from "node:path";
65
- import { cancel, confirm, isCancel, multiselect } from "@clack/prompts";
66
- import { findRepoRoot } from "../repo/root.js";
67
- import { loadEnvLoad } from "../repo/peers.js";
68
- /**
69
- * Where the last answer per task is kept.
70
- *
71
- * Under `node_modules/.cache` rather than a dotfile at the root: it is a
72
- * convenience, not configuration, and losing it to a reinstall costs one extra
73
- * keystroke. Putting it there also means no new `.gitignore` entry and no new
74
- * file in the listing every contributor sees.
75
- */
76
- function memoryPath() {
77
- return join(findRepoRoot(), "node_modules", ".cache", "devdogs", "tasks.json");
78
- }
79
- /**
80
- * pnpm's own ways of naming packages, plus `--scope` for backward
81
- * compatibility with anyone who still types the turbo-era flag out of habit.
82
- * Any of them means "already decided" for `shouldAsk`; `--scope`'s value is
83
- * translated into a `--filter` when a command actually gets built, since pnpm
84
- * itself has no `--scope` flag.
85
- */
86
- const FILTERS = ["--filter", "-F", "--scope"];
87
- /**
88
- * Tasks whose turbo definition depended on `^build` — the packages a target
89
- * depends on must be built first. Everything else forwarded through `run`
90
- * (`generate-types`, and any package script not in this list) needs no
91
- * dependency build: turbo's `generate-types` declared no `dependsOn` at all,
92
- * and nothing else is routed through the root aliases this picker serves.
93
- */
94
- const NEEDS_DEPS_BUILT = new Set([
95
- "build",
96
- "dev",
97
- "typecheck",
98
- "lint",
99
- "lint:fix",
100
- "test",
101
- "test:coverage",
102
- ]);
103
- // ── Passthrough ──────────────────────────────────────────────────────────────
104
- /**
105
- * Runs one `pnpm` invocation to completion, reporting its own exit the way
106
- * `passthroughApps` needs to: a truthy return means "keep going", `never` on
107
- * anything that ends the process (a signal, or a non-zero status treated as
108
- * final).
109
- *
110
- * `pnpm` resolves from the workspace root's `node_modules/.bin`, which is
111
- * already on PATH. Signals and exit codes pass straight through, so a Ctrl-C
112
- * in a dev server behaves exactly as it did before this existed: when the
113
- * child dies to a signal rather than exiting with a code, this process
114
- * re-raises that same signal against itself instead of collapsing it to a
115
- * generic exit code, which is what lets a shell watching this process see a
116
- * conventional signal death rather than a failure.
117
- *
118
- * ⚠️ `cwd` is explicit, and must be. Reached through
119
- * `pnpm --filter @devdogsuga/devtools run cli`, this process starts in
120
- * `packages/devtools`, and a `pnpm -r`/`--filter` invoked there would scope
121
- * itself relative to that one package rather than the workspace root.
122
- */
123
- function runOne(args, extraEnv, { final }) {
124
- const result = spawnSync("pnpm", args, {
125
- cwd: findRepoRoot(),
126
- stdio: "inherit",
127
- // Guards the one recursion that would matter: a bare, filter-less `pnpm
128
- // -r` never re-enters the root package's own scripts (pnpm excludes the
129
- // workspace root from an unfiltered recursive run by default), but an
130
- // explicit `--filter` naming the root package — or a `!`-exclusion or
131
- // `[since]` selector, both of which DO match the root even though a bare
132
- // `-r` does not — would. `DEVDOGS_PICK=0` is the same recursion guard
133
- // turbo's spawn used: it means "already decided", so a root script that
134
- // re-entered `pnpm devtools run …` would pass straight through instead of
135
- // asking again.
136
- env: { ...process.env, ...extraEnv, DEVDOGS_PICK: "0" },
137
- });
138
- if (result.signal) {
139
- // Restore the default disposition first: any SIGINT/SIGTERM listener
140
- // this process itself registered (`@clack/prompts` installs one while a
141
- // prompt is open) would otherwise run instead of the OS just ending the
142
- // process, which is what re-raising is supposed to produce.
143
- process.removeAllListeners(result.signal);
144
- process.kill(process.pid, result.signal);
145
- // Default disposition means the OS ends this process as part of that
146
- // call, so nothing below ever runs long enough to matter.
147
- for (;;) {
148
- /* unreachable */
149
- }
150
- }
151
- const status = result.status ?? 1;
152
- if (status !== 0 || final) {
153
- process.exit(status);
154
- }
155
- return true;
156
- }
157
- /**
158
- * Runs a sequence of `pnpm` invocations — typically "build the dependencies",
159
- * then "run the actual task" — stopping (and exiting non-zero) at the first
160
- * one that fails, and exiting with the LAST one's status otherwise. This is
161
- * what stands in for turbo's single-process task graph: turbo built
162
- * dependencies and ran the target task inside one invocation, so a dependency
163
- * build failure and a task failure looked identical from the outside (a
164
- * failed `turbo run`); this reproduces that from the outside by never
165
- * reaching the second spawn if the first one failed.
166
- *
167
- * Never returns: every branch inside `runOne` either exits or re-raises a
168
- * signal.
169
- */
170
- function passthrough(commands, extraEnv) {
171
- commands.forEach((args, index) => {
172
- runOne(args, extraEnv, { final: index === commands.length - 1 });
173
- });
174
- // Unreachable: `runOne` above always exits on the final command (`final:
175
- // true` forces it even on success), and exits early on any earlier
176
- // failure. This satisfies the `never` return type without a bare `throw`.
177
- process.exit(1);
178
- }
179
- /**
180
- * Whether to ask at all.
181
- *
182
- * Every one of these is a case where a prompt is either impossible or wrong:
183
- *
184
- * * `CI`, set by GitHub Actions and every other runner. No workflow reaches
185
- * this today (see the header), but a workflow that blocked on a
186
- * multiselect would hang until its timeout with no output saying why, and
187
- * that failure is bad enough to keep guarding against.
188
- * * No TTY: a pipe, a `pnpm` invoked by a script, an editor task runner.
189
- * Nobody is there to answer. Every other picker in this repo checks the
190
- * same thing.
191
- * * An explicit filter. The caller has already said which packages, and
192
- * asking again would be asking them to repeat themselves.
193
- * * `DEVDOGS_PICK=0`, the escape hatch and the recursion guard above.
194
- */
195
- export function shouldAsk(args) {
196
- if (process.env.CI)
197
- return false;
198
- if (process.env.DEVDOGS_PICK === "0")
199
- return false;
200
- if (!process.stdin.isTTY)
201
- return false;
202
- return !args.some((arg) => FILTERS.includes(arg) || FILTERS.some((f) => arg.startsWith(`${f}=`)));
203
- }
204
- /**
205
- * Pulls every `--filter`/`-F`/`--scope` value out of `args`, returning the
206
- * package patterns named and the remaining args with those flags removed.
207
- *
208
- * `--scope` is folded into the same bucket as `--filter` here: pnpm has no
209
- * `--scope` flag of its own, so a caller who still types the turbo-era name
210
- * gets it translated into a real `--filter` value rather than passed through
211
- * to a `pnpm` invocation that would reject it outright.
212
- *
213
- * Exported for the same reason `parseTierArg` is: pure, no `process.exit`, so
214
- * it is unit-testable without going through `passthrough`.
215
- */
216
- export function extractFilters(args) {
217
- const filters = [];
218
- const rest = [];
219
- for (let i = 0; i < args.length; i++) {
220
- const arg = args[i];
221
- const eqFlag = FILTERS.find((f) => arg.startsWith(`${f}=`));
222
- if (eqFlag) {
223
- filters.push(arg.slice(eqFlag.length + 1));
224
- continue;
225
- }
226
- if (FILTERS.includes(arg)) {
227
- const value = args[i + 1];
228
- if (value !== undefined) {
229
- filters.push(value);
230
- i++;
231
- continue;
232
- }
233
- }
234
- rest.push(arg);
235
- }
236
- return { filters, rest };
237
- }
238
- /**
239
- * Every `apps/*` package name, regardless of whether it defines the task in
240
- * question — unlike `appsWith`, which is a picker concern (only offer apps
241
- * that actually have this script). This exists for exactly one thing: naming
242
- * "every dependency any app has" without hand-listing packages, since apps
243
- * are the only packages in this workspace nothing else depends on (every
244
- * `packages/*`/`docs` package is reachable from at least one app's
245
- * dependency graph, and no app depends on another app).
246
- */
247
- function allAppNames() {
248
- const dir = join(findRepoRoot(), "apps");
249
- if (!existsSync(dir))
250
- return [];
251
- const names = [];
252
- for (const entry of readdirSync(dir)) {
253
- const manifest = join(dir, entry, "package.json");
254
- if (!existsSync(manifest))
255
- continue;
256
- try {
257
- const pkg = JSON.parse(readFileSync(manifest, "utf8"));
258
- if (pkg.name)
259
- names.push(pkg.name);
260
- }
261
- catch {
262
- // Same as `appsWith`: an unparsable manifest is one we cannot use.
263
- }
264
- }
265
- return names;
266
- }
267
- /**
268
- * Builds the `pnpm run <task>` invocation(s) for a set of package filters (or
269
- * none, for the whole workspace) and runs them via `passthrough`.
270
- *
271
- * Two spawns for a task in `NEEDS_DEPS_BUILT`: one that builds only the
272
- * dependencies of the target packages, then one that runs the real task
273
- * against the target packages themselves. A task outside `NEEDS_DEPS_BUILT`
274
- * skips straight to the second spawn.
275
- *
276
- * The first spawn's filter set matters more than it looks: it must build
277
- * every dependency the target packages need, and NOTHING ELSE — never an
278
- * app's own `build` script, since turbo's `^build` never ran one either
279
- * (typechecking `platform` built platform's DEPENDENCIES, then ran
280
- * `platform`'s `typecheck` script; it never ran `platform`'s own `build`).
281
- * Getting this wrong is not just wasted work: an app's `build` script can
282
- * need things a `typecheck`/`lint`/`test` run has no reason to have (a
283
- * database, a chosen deploy tier, a Flutter SDK on PATH), so running one
284
- * unasked can fail outright.
285
- *
286
- * * Filtered (`filters` non-empty): `<pkg>^...` per filter — pnpm's
287
- * deps-of selector, excluding the package itself.
288
- * * Whole workspace: the union of `<app>^...` over every app in the
289
- * workspace (`allAppNames()`), NOT a bare `pnpm -r run build`. A bare
290
- * recursive build would build every package with a `build` script,
291
- * apps included, for exactly the reason above. Every non-app package in
292
- * this workspace is reachable from at least one app's dependency graph
293
- * (verified against every current `apps/*` package, not assumed), so
294
- * this covers the same ground a bare `-r` would minus the apps
295
- * themselves.
296
- *
297
- * `dev` is the one persistent task and runs with `--parallel`: pnpm's
298
- * ordinary recursive mode runs one package at a time in dependency order,
299
- * which is wrong for two dev servers that are each supposed to keep running
300
- * — `--parallel` "completely disregard[s] concurrency and topological
301
- * sorting, running a given script immediately in all matching packages with
302
- * prefixed streaming output" (pnpm's own words for the flag), which is
303
- * exactly the live, interleaved multi-app output turbo's `dev` gave. Every
304
- * other task keeps pnpm's default topological/serial recursion, which is a
305
- * real difference from turbo: turbo ran independent tasks (lint, typecheck,
306
- * test across several apps) concurrently up to its own concurrency limit,
307
- * while this runs them one package at a time. Slower on a multi-app
308
- * selection, never wrong.
309
- *
310
- * `build` with no filter (the whole workspace) is the one case that skips
311
- * the separate dependency spawn even though it is in `NEEDS_DEPS_BUILT`: a
312
- * bare `pnpm -r run build` already builds every package — apps included, and
313
- * rightly so, since the task itself IS `build` — in dependency order in one
314
- * pass, so a preceding dependency-only spawn would just redo part of it. A
315
- * FILTERED build still needs the pre-step — naming a package by itself,
316
- * with no `^...`, selects only that package, not what it depends on.
317
- */
318
- function passthroughApps(task, filters, rest, extraEnv) {
319
- const commands = [];
320
- const needsDepsPreStep = NEEDS_DEPS_BUILT.has(task) && (task !== "build" || filters.length > 0);
321
- if (needsDepsPreStep) {
322
- const depsOfTargets = filters.length > 0 ? filters : allAppNames();
323
- const depsFilters = depsOfTargets.flatMap((f) => ["--filter", `${f}^...`]);
324
- commands.push(["-r", "--if-present", ...depsFilters, "run", "build"]);
325
- }
326
- const ownFilters = filters.flatMap((f) => ["--filter", f]);
327
- const parallel = task === "dev" ? ["--parallel"] : [];
328
- commands.push([
329
- "-r",
330
- "--if-present",
331
- ...parallel,
332
- ...ownFilters,
333
- "run",
334
- task,
335
- ...rest,
336
- ]);
337
- passthrough(commands, extraEnv);
338
- }
339
- // ── dev on vinext ────────────────────────────────────────────────────────────
340
- /** A `dev` script that starts `vinext dev` (platform's `with-env vinext dev`). */
341
- const VINEXT_DEV = /(^|[\s;&|])vinext\s+dev\b/;
342
- /**
343
- * Splits a `dev` run between apps whose dev server is `vinext dev` and the
344
- * rest.
345
- *
346
- * A vinext app's server code runs in workerd, which only sees what
347
- * `@cloudflare/vite-plugin` hands it: the app dir's own `.dev.vars`/`.env`, or
348
- * with `CLOUDFLARE_INCLUDE_PROCESS_ENV` the whole `process.env`. The repo's
349
- * `.env` is at the root, so through the app's own `with-env vinext dev` script
350
- * the Worker gets nothing and env validation fails. So a vinext app skips its
351
- * script and runs `pnpm --filter <app> exec vinext dev` with the env
352
- * `scopedProcessEnv` builds, the same way `workflows run` starts one: the
353
- * flag set, and every other app's variables left out. Its script's own
354
- * `with-env` would load the whole `.env` back in, so it is not used.
355
- *
356
- * Everything else still runs its own `dev` script under one `pnpm --parallel`.
357
- * `apps` is every app with a `dev` script; `filters` the selection (empty for
358
- * all of them). Only apps are named here: no other package has a `dev`
359
- * script.
360
- */
361
- export function planDev(apps, filters, rest) {
362
- const selected = filters.length > 0
363
- ? apps.filter((app) => filters.includes(app.name))
364
- : [...apps];
365
- const vinext = selected
366
- .filter((app) => VINEXT_DEV.test(app.script))
367
- .map((app) => app.name);
368
- const otherFilters = filters.length > 0
369
- ? filters.filter((f) => !vinext.includes(f))
370
- : apps.map((app) => app.name).filter((name) => !vinext.includes(name));
371
- const others = otherFilters.length > 0
372
- ? [
373
- "-r",
374
- "--if-present",
375
- "--parallel",
376
- ...otherFilters.flatMap((f) => ["--filter", f]),
377
- "run",
378
- "dev",
379
- ...rest,
380
- ]
381
- : null;
382
- return { vinext, others };
383
- }
384
- /**
385
- * `dev` when at least one selected app runs `vinext dev`: builds every
386
- * selected app's workspace dependencies, then starts the vinext apps (each
387
- * with its own scoped env, see `planDev`) and the rest side by side, and
388
- * exits when the first of them does, stopping the others.
389
- *
390
- * The env is the session's: `launch.ts` has already entered the tier into
391
- * `process.env` (or `--tier` loaded one into `tierEnv`).
392
- */
393
- async function runDev(plan, filters, rest, tierEnv) {
394
- const depsOfTargets = filters.length > 0 ? filters : allAppNames();
395
- runOne([
396
- "-r",
397
- "--if-present",
398
- ...depsOfTargets.flatMap((f) => ["--filter", `${f}^...`]),
399
- "run",
400
- "build",
401
- ], tierEnv, { final: false });
402
- // Dynamic, like `loadEnvLoad`: the env registry is no business of a plain
403
- // `pnpm build`.
404
- const { scopedProcessEnv } = await import("../cf/local-env.js");
405
- const environment = { ...process.env, ...tierEnv };
406
- const tier = environment.DEPLOY_ENV ?? "development";
407
- const children = [
408
- ...(await Promise.all(plan.vinext.map(async (app) => ({
409
- args: ["--filter", app, "exec", "vinext", "dev", ...rest],
410
- env: await scopedProcessEnv(app, environment, tier),
411
- })))),
412
- ...(plan.others ? [{ args: plan.others, env: environment }] : []),
413
- ].map(({ args, env }) => spawn("pnpm", args, {
414
- cwd: findRepoRoot(),
415
- stdio: "inherit",
416
- env: { ...env, DEVDOGS_PICK: "0" },
417
- }));
418
- // Ctrl-C reaches every child through the terminal's process group; this
419
- // process waits for them to finish rather than dying first.
420
- const ignore = () => undefined;
421
- process.on("SIGINT", ignore);
422
- process.on("SIGTERM", ignore);
423
- const first = await Promise.race(children.map((child) => new Promise((resolve) => {
424
- child.once("error", () => resolve(1));
425
- child.once("exit", (code, signal) => resolve(code ?? (signal ? 130 : 1)));
426
- })));
427
- for (const child of children) {
428
- if (child.exitCode === null && child.signalCode === null) {
429
- child.kill("SIGTERM");
430
- }
431
- }
432
- process.exit(first);
433
- }
434
- /** `passthroughApps`, except that a `dev` selecting a vinext app goes to
435
- * `runDev`. */
436
- async function dispatch(task, filters, rest, tierEnv) {
437
- if (task === "dev") {
438
- const plan = planDev(appsWith("dev"), filters, rest);
439
- if (plan.vinext.length > 0)
440
- return runDev(plan, filters, rest, tierEnv);
441
- }
442
- passthroughApps(task, filters, rest, tierEnv);
443
- }
444
- // ── Tier ─────────────────────────────────────────────────────────────────────
445
- /** The tiers a loaded env can select. `preflight` is not a deploy environment
446
- * (see `@devdogsuga/env`'s target table) and is refused here for the same
447
- * reason `loadEnvironment` itself would refuse it. */
448
- const RUN_TIERS = new Set(["development", "staging", "production"]);
449
- /**
450
- * Parses and strips `--tier <value>` from pnpm-bound args, mirroring the
451
- * `--all` strip above.
452
- *
453
- * Pure on purpose — no `process.exit` — so this is unit-testable without
454
- * going through `passthrough`, which every other path in this file ends at
455
- * and which a test cannot observe return from. `runTask` is the one caller,
456
- * and it is the one that turns `error` into an exit.
457
- */
458
- export function parseTierArg(args) {
459
- const idx = args.indexOf("--tier");
460
- if (idx === -1)
461
- return { rest: [...args] };
462
- const value = args[idx + 1];
463
- if (!value || value.startsWith("-")) {
464
- return { rest: [...args], error: "devtools run: --tier requires a value." };
465
- }
466
- if (!RUN_TIERS.has(value)) {
467
- return {
468
- rest: [...args],
469
- error: `devtools run: unknown tier "${value}". ` +
470
- "Expected: development, staging, production.",
471
- };
472
- }
473
- return {
474
- tier: value,
475
- rest: [...args.slice(0, idx), ...args.slice(idx + 2)],
476
- };
477
- }
478
- // ── The apps ─────────────────────────────────────────────────────────────────
479
- /**
480
- * The apps that actually define this task, by package name.
481
- *
482
- * `apps/*` only. Packages are libraries an app pulls in, and a filtered
483
- * `pnpm -r --filter '<app>^...' run build` already builds those first (see
484
- * `passthroughApps` above), so listing all twelve would ask about things the
485
- * answer does not change. Reading each `package.json` rather than shelling
486
- * out to a dry-run equivalent, which costs about a second before the first
487
- * question.
488
- */
489
- function appsWith(task) {
490
- const dir = join(findRepoRoot(), "apps");
491
- if (!existsSync(dir))
492
- return [];
493
- const found = [];
494
- for (const entry of readdirSync(dir)) {
495
- const manifest = join(dir, entry, "package.json");
496
- if (!existsSync(manifest))
497
- continue;
498
- try {
499
- const pkg = JSON.parse(readFileSync(manifest, "utf8"));
500
- const script = pkg.scripts?.[task];
501
- if (pkg.name && script)
502
- found.push({ name: pkg.name, script });
503
- }
504
- catch {
505
- // A manifest we cannot parse is one we cannot offer. pnpm will report
506
- // it far better than a picker could.
507
- }
508
- }
509
- return found;
510
- }
511
- // ── Memory ───────────────────────────────────────────────────────────────────
512
- function remembered(task) {
513
- try {
514
- const all = JSON.parse(readFileSync(memoryPath(), "utf8"));
515
- return all[task] ?? [];
516
- }
517
- catch {
518
- return [];
519
- }
520
- }
521
- function remember(task, apps) {
522
- try {
523
- let all = {};
524
- try {
525
- all = JSON.parse(readFileSync(memoryPath(), "utf8"));
526
- }
527
- catch {
528
- // First run, or a file from an older shape. Either way, start clean.
529
- }
530
- all[task] = apps;
531
- mkdirSync(dirname(memoryPath()), { recursive: true });
532
- writeFileSync(memoryPath(), `${JSON.stringify(all, null, 2)}\n`);
533
- }
534
- catch {
535
- // Remembering is a convenience. Failing to write it must never fail the
536
- // command the contributor actually asked for.
537
- }
538
- }
539
- // ── Entry ────────────────────────────────────────────────────────────────────
540
- /**
541
- * Runs one root task, asking which apps first where that makes sense.
542
- *
543
- * Never returns: every path ends in `passthrough`, which exits with the
544
- * underlying `pnpm` invocation's own status. That is why `cli.ts` dispatches
545
- * this before `intro()`. There is no `outro()` to reach, and a banner would
546
- * land on the stream a dev server is about to take over.
547
- */
548
- export async function runTask(argv) {
549
- const [task, ...args] = argv;
550
- if (!task) {
551
- console.error("usage: pnpm devtools run <task> [pnpm args…]");
552
- process.exit(1);
553
- }
554
- // `--all` is this command's own flag, not pnpm's: it means "no question,
555
- // every package", which is what the root scripts did before this existed.
556
- // Removed from the argv so pnpm never sees a flag it does not know.
557
- const all = args.includes("--all");
558
- const withoutAll = args.filter((arg) => arg !== "--all");
559
- const tierArg = parseTierArg(withoutAll);
560
- if (tierArg.error) {
561
- console.error(tierArg.error);
562
- process.exit(1);
563
- }
564
- const { tier, rest } = tierArg;
565
- // No `--yes` here, deliberately: `run` is a bare pnpm passthrough with no
566
- // confirmation flag of its own to spare, unlike `cron run`/`workflows run`.
567
- // A non-interactive caller that wants production has no way to say so, and
568
- // that is the point — the same exposure those two gate behind `--yes` gets
569
- // gated behind a terminal existing at all.
570
- if (tier === "production") {
571
- if (!process.stdin.isTTY) {
572
- console.error("devtools run: --tier production needs a terminal to confirm — " +
573
- "nothing ran.");
574
- process.exit(1);
575
- }
576
- const approved = await confirm({
577
- message: `Run \`${task}\` against production? This loads .env.production's ` +
578
- "credentials into the child process.",
579
- initialValue: false,
580
- });
581
- if (isCancel(approved) || !approved) {
582
- cancel("Nothing ran.");
583
- process.exit(0);
584
- }
585
- }
586
- let tierEnv;
587
- if (tier) {
588
- // Dynamic import, not a top-level one: this module is on the `pnpm build`
589
- // hot path (see the header), and a bare `pnpm build` with no `--tier`
590
- // must never load `@devdogsuga/env/load` or drag dotenvx into the graph.
591
- const { loadEnvironment, MissingEnvFileError } = await loadEnvLoad();
592
- try {
593
- // override: true because THIS process runs under `with-env`
594
- // (development), so process.env already holds development's values.
595
- // Passing the full loaded env — not just DEPLOY_ENV — to the child
596
- // matters because a grandchild `with-env` (started by the app's own
597
- // script) resolves variables first-file-wins from ITS OWN process.env:
598
- // without the full map, that env would still carry devtools' inherited
599
- // development values, which would beat `.env.<tier>` for anything
600
- // `.env.<tier>` does not also redeclare.
601
- const loaded = await loadEnvironment(tier, { override: true });
602
- tierEnv = { ...loaded.env, DEPLOY_ENV: tier };
603
- }
604
- catch (err) {
605
- if (err instanceof MissingEnvFileError) {
606
- console.error(`devtools run: ${err.message}`);
607
- process.exit(1);
608
- }
609
- throw err;
610
- }
611
- }
612
- // `shouldAsk` is false whenever `rest` already carries an explicit filter,
613
- // so extracting filters here (rather than unconditionally) would be
614
- // redundant with it in every case that matters; `all` is the one case
615
- // `shouldAsk` cannot see for itself. Either way, `rest` may still carry an
616
- // explicit `--filter`/`-F`/`--scope` that needs pulling out before it is
617
- // forwarded — as a trailing arg after `run <task>`, pnpm hands it straight
618
- // to the underlying script rather than treating it as its own flag.
619
- if (all || !shouldAsk(rest)) {
620
- const { filters, rest: cleanRest } = extractFilters(rest);
621
- return dispatch(task, filters, cleanRest, tierEnv);
622
- }
623
- const apps = appsWith(task);
624
- // Nothing to choose between: one app, or none that define this task (pnpm
625
- // will say so better than a picker with a single option would).
626
- if (apps.length < 2)
627
- return dispatch(task, [], rest, tierEnv);
628
- const previous = remembered(task);
629
- const chosen = await multiselect({
630
- // The `--all` escape is named in the message rather than offered as an
631
- // option. An "everything" entry used to sit alongside the apps; clack's
632
- // toggle-all `a` replaces most of what it was for, but not all of it. `a`
633
- // selects every app in THIS list, which is `apps/*`; `--all` passes no
634
- // filter at all, which is every package in the workspace.
635
- //
636
- // For `build` those nearly coincide, since filtering to an app pulls its
637
- // dependencies in through the deps-of build step. For `test`, `lint` and
638
- // `typecheck` they do not: those tasks only run against the apps
639
- // themselves, not their dependencies, so filtering to the four apps runs
640
- // four test tasks while an unfiltered run covers every package. Selecting
641
- // every app and expecting a workspace-wide test run would quietly skip
642
- // every suite in `packages/*`.
643
- message: `\`${task}\` — which apps? (a selects all; --all runs every package)`,
644
- options: apps.map((app) => ({
645
- value: app.name,
646
- label: app.name,
647
- // The actual command, so the choice is made against what runs, not a name.
648
- hint: app.script.length > 58 ? `${app.script.slice(0, 57)}…` : app.script,
649
- })),
650
- // Only what was picked last time, and only names still on offer, which
651
- // also drops the retired "everything" entry from an older cache rather
652
- // than preselecting a value nothing would match. Nothing is preselected on
653
- // a first run, so with `required` the first answer is a real choice rather
654
- // than an Enter through a default.
655
- initialValues: previous.filter((name) => apps.some((app) => app.name === name)),
656
- required: true,
657
- });
658
- if (isCancel(chosen)) {
659
- cancel("Nothing ran.");
660
- process.exit(0);
661
- }
662
- remember(task, chosen);
663
- return dispatch(task, chosen, rest, tierEnv);
664
- }
package/dist/setup.d.ts DELETED
@@ -1 +0,0 @@
1
- export declare function runSetup(): Promise<void>;