@devdogsuga/devtools 0.1.25 → 0.1.27

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
@@ -1,239 +0,0 @@
1
- /**
2
- * Fills the `@devdogsuga/env` registry by importing every manifest.
3
- *
4
- * The registry populates as a SIDE EFFECT of importing each manifest module.
5
- * `declare()` runs at import time, so a process that never imports them sees an
6
- * empty registry and every derived selector returns nothing. That is the
7
- * fail-open shape this module closes: anything that routes secrets calls
8
- * `loadRegistry()` first, and `assertRegistryLoaded()` is the guard the
9
- * selectors' consumers use to refuse an empty answer.
10
- *
11
- * Discovery is by filename, per the model doc: `src/env.ts`, then `env.ts`, per
12
- * workspace package. Both present is a hard error, because two manifests in one
13
- * package means two places for the next declaration to land and no rule saying
14
- * which. No `package.json` pointer: every other tool in this repo is found by
15
- * filename (`tsconfig.json`, `wrangler.jsonc`, `supadart.yaml`), and a pointer
16
- * would be the only one of its kind.
17
- *
18
- * One special case: `supabase/env.ts` sits at the repo root, OUTSIDE the
19
- * workspace globs. Its variables belong to `config.toml` and the Supabase
20
- * CLI, not to any package, so it lives next to the config that reads them.
21
- *
22
- * `@devdogsuga/env` itself (the package that exports `define()`/`declare()`)
23
- * no longer needs a special-case exclusion here: since the Backstage
24
- * cutover it is an installed npm dependency, not a workspace package under
25
- * `packages/`, so this scan never sees it at all.
26
- *
27
- * devtools' OWN operator manifest (this package's root `env.ts` — the keys
28
- * no app reads at all: `BWS_ACCESS_TOKEN`, `CLOUDFLARE_API_TOKEN`, and
29
- * friends) has the same problem in reverse: since the Backstage cutover
30
- * devtools is no longer one of the TARGET repo's workspace packages either,
31
- * so the scan below would never see it. It is loaded unconditionally,
32
- * always, regardless of which repo devtools is running against — see
33
- * `ownManifestPath()`'s own comment for how it stays resolvable both from a
34
- * workspace checkout and from an installed `pnpm dlx`/`node_modules` copy,
35
- * and for why loading it through the same `importRepoTs()` tsx path as
36
- * every other manifest (rather than a plain static `import`) is what keeps
37
- * its `declare()`/`define()` calls landing in the SAME `@devdogsuga/env`
38
- * registry instance the target repo's manifests populate (module identity —
39
- * see `repo/peers.ts`'s header and the devtools-dlx prototype's
40
- * FINDINGS.md experiment 3). Its bare `@devdogsuga/env` import is
41
- * redirected to the copy `loadEnv()` resolved through the repo, because
42
- * from a `pnpm dlx` copy the specifier does not resolve at all: devtools
43
- * ships no copy of its own (`@devdogsuga/env` is only ever an optional
44
- * peer), and nothing installs one next to it. See `repo/peer-redirect.ts`.
45
- *
46
- * Most workspace packages declare nothing, so "no env.ts found" means
47
- * not-a-manifest rather than an error.
48
- *
49
- * ⚠️ `apps/sandbox` was once listed here as having no manifest ON PURPOSE, on
50
- * the grounds that a Worker reads bindings that arrive as a function argument
51
- * rather than `process.env`. That premise is still true and the conclusion
52
- * drawn from it was still wrong: a manifest is also the only thing that routes
53
- * a credential to a deployed environment and the only thing that tells
54
- * `env audit` a Worker secret is supposed to be there. Without one,
55
- * `SANDBOX_PROXY_TOKEN`, minted at deploy time and therefore in no Bitwarden
56
- * project by design, was reported as an orphan, i.e. as safe for the §3.6 prune
57
- * path to delete. `apps/sandbox/env.ts` exists now, and states that reasoning
58
- * at length.
59
- */
60
- import { existsSync, readdirSync } from "node:fs";
61
- import { dirname, join } from "node:path";
62
- import { fileURLToPath, pathToFileURL } from "node:url";
63
- import { redirectPeer } from "../repo/peer-redirect.js";
64
- import { getEnvSync, loadEnv, repoPeerUrl } from "../repo/peers.js";
65
- import { findRepoRoot } from "../repo/root.js";
66
- import { importRepoTs } from "../repo/tsx-loader.js";
67
- /**
68
- * The single in-flight (or settled) load.
69
- *
70
- * Node's module cache already makes a second `import()` of the same manifest a
71
- * no-op, but memoizing the whole pass is asserted here rather than inherited: a
72
- * future bundler or test runner that re-evaluates modules would duplicate every
73
- * declaration, and duplicated declarations are what the completeness test
74
- * treats as a bug. A failed load stays failed. Every later call gets the same
75
- * rejection, because retrying into a half-populated registry would hide the
76
- * manifest that broke.
77
- */
78
- let loaded;
79
- /**
80
- * Imports every manifest so the registry populates. Idempotent; concurrent
81
- * callers share one pass. Await this before touching any derived selector.
82
- */
83
- export function loadRegistry() {
84
- loaded ??= importManifests();
85
- return loaded;
86
- }
87
- /**
88
- * Refuses to proceed on an empty registry.
89
- *
90
- * For SYNC consumers of the derived selectors (`selectForPush`, `routeTo`),
91
- * which cannot await the load themselves. An empty registry makes every
92
- * selector return `[]`, and `[]` fails open in the worst direction: nothing is
93
- * never-store, so `BWS_ACCESS_TOKEN` would upload. Better a crash naming the
94
- * missing call than a push that quietly stores the unstorable.
95
- */
96
- export function assertRegistryLoaded() {
97
- if (getEnvSync().variables().size === 0) {
98
- throw new Error("The env registry is empty — no manifest has been imported, so the " +
99
- "derived key sets would all be []. Await loadRegistry() (from " +
100
- "devtools' env/discovery.ts) before routing or selecting secrets.");
101
- }
102
- }
103
- async function importManifests() {
104
- // Warms `repo/peers.ts`'s sync cache before any manifest imports: each
105
- // manifest's own `declare()`/`define()` calls run against the SAME
106
- // `@devdogsuga/env` module instance this line resolves (module identity —
107
- // see `repo/peers.ts`'s header), and `assertRegistryLoaded()` /
108
- // `gh/environments.ts`'s getters read it back synchronously afterward.
109
- await loadEnv();
110
- // devtools' own manifest imports `@devdogsuga/env` by bare specifier, which
111
- // does not resolve at all from a `pnpm dlx` copy. Point it at the copy
112
- // `loadEnv()` just resolved through the repo. See `repo/peer-redirect.ts`.
113
- const envUrl = repoPeerUrl("@devdogsuga/env");
114
- if (envUrl) {
115
- redirectPeer({
116
- parentURL: pathToFileURL(ownManifestPath()).href,
117
- specifier: "@devdogsuga/env",
118
- url: envUrl,
119
- });
120
- }
121
- // The Next apps' manifests run `createEnv` at import time. Without this flag
122
- // they would validate the AMBIENT environment, a devtools process rather than
123
- // an app build, and throw on whatever is missing. The manifests already
124
- // short-circuit their `resolveEnvironment()` call under the flag, so setting
125
- // it here is the supported "read the declarations, skip the values" mode.
126
- // Restored afterward so a long-lived process (tests) does not keep validation
127
- // off for whatever runs next.
128
- const previous = process.env.SKIP_ENV_VALIDATION;
129
- process.env.SKIP_ENV_VALIDATION = "1";
130
- try {
131
- for (const path of manifestPaths()) {
132
- try {
133
- await importRepoTs(path);
134
- }
135
- catch (cause) {
136
- throw new Error(`The env manifest at ${path} failed to import.`, {
137
- cause,
138
- });
139
- }
140
- }
141
- }
142
- finally {
143
- if (previous === undefined)
144
- delete process.env.SKIP_ENV_VALIDATION;
145
- else
146
- process.env.SKIP_ENV_VALIDATION = previous;
147
- }
148
- }
149
- /**
150
- * Every manifest file, in a stable order.
151
- *
152
- * The workspace members are enumerated by scanning `apps/*` and `packages/*`
153
- * plus the literal `docs`, a deliberately boring mirror of the three globs in
154
- * `pnpm-workspace.yaml` rather than a YAML parse: the globs have not changed
155
- * since the workspace existed, devtools carries no YAML dependency, and a new
156
- * top-level glob would already mean editing more interesting files than this
157
- * one.
158
- */
159
- function manifestPaths() {
160
- const paths = [];
161
- for (const dir of workspaceDirs()) {
162
- const manifest = manifestIn(dir);
163
- if (manifest)
164
- paths.push(manifest);
165
- }
166
- // The repo-root special case: not a workspace package, see the header.
167
- const supabase = manifestIn(join(findRepoRoot(), "supabase"));
168
- if (supabase)
169
- paths.push(supabase);
170
- // devtools' own operator manifest — always included, regardless of the
171
- // target repo. Last, matching where it sat back when devtools itself was
172
- // `packages/devtools` (alphabetically last of the packages this repo
173
- // ever had). `SECTION_ORDER` in `env/example.ts` fixes the RENDERED
174
- // section order independently of this insertion order, so this position
175
- // is cosmetic for `.env.example`; it still matters for which source
176
- // "wins" a shared key's declaration-order tie-break.
177
- paths.push(ownManifestPath());
178
- return paths;
179
- }
180
- /**
181
- * The absolute path to devtools' own `env.ts`, resolved relative to THIS
182
- * module's own file rather than the target repo — it ships alongside
183
- * `dist/` (see `package.json`'s `files`), not inside the target repo's
184
- * workspace, so `findRepoRoot()`/`workspaceDirs()` can never find it.
185
- *
186
- * `import.meta.url` here is `dist/env/discovery.js` (this file's build
187
- * output — `tsconfig.json`'s `rootDir: "src"` / `outDir: "dist"` mirrors
188
- * `src/env/discovery.ts`'s own location one level down), so two `..` reach
189
- * the package root: `dist/env/discovery.js` -> `dist/env` -> `dist` ->
190
- * package root, where `env.ts` sits (deliberately outside `src/`, per that
191
- * file's own header — `tsconfig.typecheck.json` includes it as a sibling
192
- * of `src` for exactly that reason). Same file, same relative shape, in
193
- * both a workspace checkout (`packages/devtools/env.ts`) and an installed
194
- * copy (`node_modules/@devdogsuga/devtools/env.ts`).
195
- */
196
- function ownManifestPath() {
197
- const here = dirname(fileURLToPath(import.meta.url));
198
- return join(here, "..", "..", "env.ts");
199
- }
200
- function workspaceDirs() {
201
- const dirs = [];
202
- const repoRoot = findRepoRoot();
203
- for (const parent of ["apps", "packages"]) {
204
- const names = [];
205
- for (const entry of readdirSync(join(repoRoot, parent), {
206
- withFileTypes: true,
207
- })) {
208
- // node_modules and dotfiles are not packages.
209
- if (!entry.isDirectory())
210
- continue;
211
- if (entry.name === "node_modules" || entry.name.startsWith("."))
212
- continue;
213
- names.push(entry.name);
214
- }
215
- // Sorted, because readdir order is whatever the filesystem feels like and
216
- // registry insertion order is now OBSERVABLE: `env example` renders
217
- // keys in declaration order and CI byte-compares the result, so two
218
- // machines walking the same tree must import the same manifests in the
219
- // same sequence.
220
- names.sort();
221
- for (const name of names)
222
- dirs.push(join(repoRoot, parent, name));
223
- }
224
- dirs.push(join(repoRoot, "docs"));
225
- return dirs;
226
- }
227
- /** `src/env.ts`, then `env.ts`; both present in one package is a hard error. */
228
- function manifestIn(dir) {
229
- const inSrc = join(dir, "src", "env.ts");
230
- const atRoot = join(dir, "env.ts");
231
- const hasSrc = existsSync(inSrc);
232
- const hasRoot = existsSync(atRoot);
233
- if (hasSrc && hasRoot) {
234
- throw new Error(`${dir} has BOTH src/env.ts and env.ts. One package gets one manifest — ` +
235
- "two of them means two places for the next declaration to land, and " +
236
- "no rule saying which. Merge them.");
237
- }
238
- return hasSrc ? inSrc : hasRoot ? atRoot : null;
239
- }
@@ -1,119 +0,0 @@
1
- /**
2
- * An editable `.env` that survives being edited.
3
- *
4
- * The root `.env` is not a data file. It is 150 lines of hard-won commentary
5
- * about which value breaks the Supabase CLI when empty, which one must stay
6
- * commented out, and why. A writer that parses to a Map and serializes back
7
- * destroys all of it on the first save.
8
- *
9
- * So this keeps the file as **lines** and edits in place. An untouched line is
10
- * returned byte-for-byte, including its spacing, quoting style and trailing
11
- * comment. Only the lines actually being changed are rewritten.
12
- *
13
- * Nothing is ever deleted. Removing a key comments it out, so the value stays
14
- * recoverable from the file itself, and re-adding it later uncomments that line
15
- * rather than appending a duplicate.
16
- */
17
- export interface EnvLine {
18
- raw: string;
19
- key?: string;
20
- /** Parsed value, when this line is an assignment. */
21
- value?: string;
22
- commented?: boolean;
23
- }
24
- /**
25
- * Reads the value half of an assignment.
26
- *
27
- * Deliberately NOT a general dotenv parser: it does not expand `$VAR`, because
28
- * a stored value that means one thing in the file and another in the process
29
- * cannot be rotated with confidence.
30
- */
31
- export declare function parseValue(rest: string): string;
32
- /**
33
- * Splits everything after `=` into its value and its trailing comment.
34
- *
35
- * Quote-aware, because the naive "cut at the first `#`" truncates a generated
36
- * password into something that still looks like one. A `#` inside quotes is
37
- * part of the value; outside them it starts a comment only when whitespace
38
- * precedes it.
39
- */
40
- export declare function splitComment(rest: string): {
41
- comment: string;
42
- };
43
- /**
44
- * Where a value came from and when, written on the same line as the value.
45
- *
46
- * It answers "is this still what production has?" without running anything. A
47
- * `.env` that has been sitting for three weeks says so rather than looking
48
- * exactly like one pulled this morning.
49
- */
50
- export interface Stamp {
51
- environment: string;
52
- action: "pushed" | "pulled";
53
- /** ISO date. Passed in rather than read from the clock, so this is testable. */
54
- date: string;
55
- }
56
- export declare function isStamp(comment: string): boolean;
57
- /** Always double-quoted, so a multi-line value round-trips through one line. */
58
- export declare function quote(value: string): string;
59
- export declare class EnvDocument {
60
- private lines;
61
- /** Whether this session has already appended, so it separates only once. */
62
- private appended;
63
- private constructor();
64
- static parse(text: string): EnvDocument;
65
- static empty(): EnvDocument;
66
- private find;
67
- /** The active value, or undefined when absent or commented out. */
68
- get(key: string): string | undefined;
69
- /** Present and active. */
70
- has(key: string): boolean;
71
- /** Present, but commented out. */
72
- isCommented(key: string): boolean;
73
- /** Every active assignment, in file order. */
74
- entries(): [string, string][];
75
- keys(): string[];
76
- /**
77
- * Sets a value, preferring to revive a commented line over appending.
78
- *
79
- * Appending when a commented form already exists is how a file ends up with
80
- * the same key twice: one stale, both plausible, and which one looks
81
- * authoritative depends on where the reader scrolled to.
82
- */
83
- set(key: string, value: string, stamp?: Stamp): void;
84
- /** Index of the first line mentioning a key, active or commented. */
85
- private first;
86
- /**
87
- * Moves every line for a key next to that key's first appearance.
88
- *
89
- * Files drift: a key gets commented out near the bottom, re-added at the top
90
- * six weeks later, and now two lines claim the same name a hundred lines
91
- * apart. Whichever one a reader scrolls to first looks authoritative.
92
- *
93
- * The FIRST occurrence keeps its position, so the standalone comment block
94
- * documenting a key stays attached to it. Everything else moves up to join
95
- * it, in the order it already had.
96
- */
97
- group(): boolean;
98
- /**
99
- * Blanks every active value without losing one.
100
- *
101
- * Each becomes a commented line holding what it was, plus an empty active
102
- * line under it. The file still declares every key it needs, which is what
103
- * makes it a usable checklist, while holding nothing.
104
- *
105
- * Already-empty keys are skipped. Commenting out `FOO=""` to write `FOO=""`
106
- * underneath is churn that makes the next diff harder to read.
107
- */
108
- reset(): string[];
109
- /**
110
- * Comments a key out rather than deleting it.
111
- *
112
- * A no-op when it is already commented or absent, so running a sync twice
113
- * cannot produce `## KEY=`.
114
- */
115
- comment(key: string): boolean;
116
- /** Restores a commented assignment. Returns its value, or undefined. */
117
- uncomment(key: string): string | undefined;
118
- toString(): string;
119
- }
@@ -1,323 +0,0 @@
1
- /**
2
- * An editable `.env` that survives being edited.
3
- *
4
- * The root `.env` is not a data file. It is 150 lines of hard-won commentary
5
- * about which value breaks the Supabase CLI when empty, which one must stay
6
- * commented out, and why. A writer that parses to a Map and serializes back
7
- * destroys all of it on the first save.
8
- *
9
- * So this keeps the file as **lines** and edits in place. An untouched line is
10
- * returned byte-for-byte, including its spacing, quoting style and trailing
11
- * comment. Only the lines actually being changed are rewritten.
12
- *
13
- * Nothing is ever deleted. Removing a key comments it out, so the value stays
14
- * recoverable from the file itself, and re-adding it later uncomments that line
15
- * rather than appending a duplicate.
16
- */
17
- /**
18
- * `KEY=value`, active. Groups: indent, `export ` prefix, key, value.
19
- *
20
- * The prefix is CAPTURED rather than skipped so an update can put it back.
21
- * Dropping it turns `export FOO=` into `FOO=`, which still parses here and
22
- * stops being exported to child processes. That shows up as a missing variable
23
- * three tools downstream.
24
- */
25
- const ACTIVE = /^(\s*)((?:export\s+)?)([A-Za-z_][A-Za-z0-9_]*)\s*=(.*)$/;
26
- /** `# KEY=value`, the commented form this tool writes and reads back. */
27
- const COMMENTED = /^(\s*)#\s?((?:export\s+)?)([A-Za-z_][A-Za-z0-9_]*)\s*=(.*)$/;
28
- /**
29
- * Reads the value half of an assignment.
30
- *
31
- * Deliberately NOT a general dotenv parser: it does not expand `$VAR`, because
32
- * a stored value that means one thing in the file and another in the process
33
- * cannot be rotated with confidence.
34
- */
35
- export function parseValue(rest) {
36
- const text = rest.trim();
37
- if (text.startsWith('"')) {
38
- const end = findClosing(text, '"');
39
- if (end === -1)
40
- return unescape(text.slice(1));
41
- return unescape(text.slice(1, end));
42
- }
43
- if (text.startsWith("'")) {
44
- const end = findClosing(text, "'");
45
- if (end === -1)
46
- return text.slice(1);
47
- // Single quotes are literal, the only way to store a backslash sequence
48
- // verbatim.
49
- return text.slice(1, end);
50
- }
51
- // Unquoted. A `#` only starts a comment when whitespace precedes it: `#` is
52
- // an ordinary character in a generated password, and stripping from the first
53
- // one truncates the credential into something that still looks like one.
54
- const hash = text.search(/\s#/);
55
- return (hash === -1 ? text : text.slice(0, hash)).trim();
56
- }
57
- function findClosing(text, quote) {
58
- for (let i = 1; i < text.length; i += 1) {
59
- if (text[i] === "\\") {
60
- i += 1;
61
- continue;
62
- }
63
- if (text[i] === quote)
64
- return i;
65
- }
66
- return -1;
67
- }
68
- function unescape(text) {
69
- return text.replace(/\\([nrt\\"'])/g, (_, c) => c === "n" ? "\n" : c === "r" ? "\r" : c === "t" ? "\t" : c);
70
- }
71
- /**
72
- * Splits everything after `=` into its value and its trailing comment.
73
- *
74
- * Quote-aware, because the naive "cut at the first `#`" truncates a generated
75
- * password into something that still looks like one. A `#` inside quotes is
76
- * part of the value; outside them it starts a comment only when whitespace
77
- * precedes it.
78
- */
79
- export function splitComment(rest) {
80
- const text = rest.trimStart();
81
- let after;
82
- if (text.startsWith('"') || text.startsWith("'")) {
83
- const end = findClosing(text, text[0]);
84
- after = end === -1 ? text.length : end + 1;
85
- }
86
- else {
87
- const hash = text.search(/\s#/);
88
- after = hash === -1 ? text.length : hash;
89
- }
90
- const tail = text.slice(after);
91
- const hash = tail.indexOf("#");
92
- return { comment: hash === -1 ? "" : tail.slice(hash).trimEnd() };
93
- }
94
- /**
95
- * Recognises a stamp this tool wrote, so it is replaced rather than duplicated.
96
- *
97
- * Deliberately narrow: it matches the exact bracketed shape below and nothing
98
- * else, because the cost of a false positive is deleting somebody's own note.
99
- */
100
- const STAMP = /\s*#\s*\[[a-z][a-z0-9-]* (?:pushed|pulled) \d{4}-\d{2}-\d{2}\]\s*$/;
101
- function stampText(stamp) {
102
- return `# [${stamp.environment} ${stamp.action} ${stamp.date}]`;
103
- }
104
- export function isStamp(comment) {
105
- return STAMP.test(comment);
106
- }
107
- /** Always double-quoted, so a multi-line value round-trips through one line. */
108
- export function quote(value) {
109
- return `"${value
110
- .replace(/\\/g, "\\\\")
111
- .replace(/"/g, '\\"')
112
- .replace(/\n/g, "\\n")
113
- .replace(/\r/g, "\\r")
114
- .replace(/\t/g, "\\t")}"`;
115
- }
116
- export class EnvDocument {
117
- lines;
118
- /** Whether this session has already appended, so it separates only once. */
119
- appended = false;
120
- constructor(lines) {
121
- this.lines = lines;
122
- }
123
- static parse(text) {
124
- const lines = text.split("\n").map((raw) => {
125
- const active = ACTIVE.exec(raw);
126
- if (active) {
127
- return { raw, key: active[3], value: parseValue(active[4]) };
128
- }
129
- const commented = COMMENTED.exec(raw);
130
- if (commented) {
131
- return {
132
- raw,
133
- key: commented[3],
134
- value: parseValue(commented[4]),
135
- commented: true,
136
- };
137
- }
138
- return { raw };
139
- });
140
- return new EnvDocument(lines);
141
- }
142
- static empty() {
143
- return new EnvDocument([]);
144
- }
145
- find(key, commented) {
146
- return this.lines.findIndex((l) => l.key === key && Boolean(l.commented) === commented);
147
- }
148
- /** The active value, or undefined when absent or commented out. */
149
- get(key) {
150
- const i = this.find(key, false);
151
- return i === -1 ? undefined : this.lines[i].value;
152
- }
153
- /** Present and active. */
154
- has(key) {
155
- return this.find(key, false) !== -1;
156
- }
157
- /** Present, but commented out. */
158
- isCommented(key) {
159
- return this.find(key, false) === -1 && this.find(key, true) !== -1;
160
- }
161
- /** Every active assignment, in file order. */
162
- entries() {
163
- return this.lines
164
- .filter((l) => l.key !== undefined && !l.commented)
165
- .map((l) => [l.key, l.value]);
166
- }
167
- keys() {
168
- return this.entries().map(([k]) => k);
169
- }
170
- /**
171
- * Sets a value, preferring to revive a commented line over appending.
172
- *
173
- * Appending when a commented form already exists is how a file ends up with
174
- * the same key twice: one stale, both plausible, and which one looks
175
- * authoritative depends on where the reader scrolled to.
176
- */
177
- set(key, value, stamp) {
178
- const active = this.find(key, false);
179
- if (active !== -1) {
180
- const line = this.lines[active];
181
- const match = ACTIVE.exec(line.raw);
182
- const existing = splitComment(match[4]).comment;
183
- // Somebody's own note gets moved out of the way rather than overwritten.
184
- // It was written about this key, so it belongs above the whole group:
185
- // the commented-out history sits between the note and the key.
186
- if (stamp && existing !== "" && !isStamp(existing)) {
187
- this.lines.splice(this.first(key), 0, { raw: existing });
188
- }
189
- const trailing = stamp
190
- ? ` ${stampText(stamp)}`
191
- : existing === ""
192
- ? ""
193
- : ` ${existing}`;
194
- // Re-find: the splice above may have shifted this line down.
195
- const at = this.find(key, false);
196
- const target = this.lines[at];
197
- target.raw = `${match[1]}${match[2]}${key}=${quote(value)}${trailing}`;
198
- target.value = value;
199
- return;
200
- }
201
- const commented = this.find(key, true);
202
- if (commented !== -1) {
203
- const line = this.lines[commented];
204
- line.raw = `${key}=${quote(value)}${stamp ? ` ${stampText(stamp)}` : ""}`;
205
- line.value = value;
206
- line.commented = false;
207
- return;
208
- }
209
- // One blank line between the file's own contents and anything appended,
210
- // and only one: a separator per appended key would grow a gap every time a
211
- // sync adds a batch.
212
- if (!this.appended &&
213
- this.lines.length > 0 &&
214
- this.lines.at(-1).raw !== "") {
215
- this.lines.push({ raw: "" });
216
- }
217
- this.appended = true;
218
- this.lines.push({
219
- raw: `${key}=${quote(value)}${stamp ? ` ${stampText(stamp)}` : ""}`,
220
- key,
221
- value,
222
- });
223
- }
224
- /** Index of the first line mentioning a key, active or commented. */
225
- first(key) {
226
- return this.lines.findIndex((l) => l.key === key);
227
- }
228
- /**
229
- * Moves every line for a key next to that key's first appearance.
230
- *
231
- * Files drift: a key gets commented out near the bottom, re-added at the top
232
- * six weeks later, and now two lines claim the same name a hundred lines
233
- * apart. Whichever one a reader scrolls to first looks authoritative.
234
- *
235
- * The FIRST occurrence keeps its position, so the standalone comment block
236
- * documenting a key stays attached to it. Everything else moves up to join
237
- * it, in the order it already had.
238
- */
239
- group() {
240
- const out = [];
241
- const taken = new Set();
242
- let moved = false;
243
- for (let i = 0; i < this.lines.length; i += 1) {
244
- if (taken.has(i))
245
- continue;
246
- const line = this.lines[i];
247
- out.push(line);
248
- taken.add(i);
249
- if (line.key === undefined)
250
- continue;
251
- for (let j = i + 1; j < this.lines.length; j += 1) {
252
- if (taken.has(j) || this.lines[j].key !== line.key)
253
- continue;
254
- if (j !== i + 1 || taken.has(i + 1))
255
- moved = true;
256
- out.push(this.lines[j]);
257
- taken.add(j);
258
- }
259
- }
260
- this.lines = out;
261
- return moved;
262
- }
263
- /**
264
- * Blanks every active value without losing one.
265
- *
266
- * Each becomes a commented line holding what it was, plus an empty active
267
- * line under it. The file still declares every key it needs, which is what
268
- * makes it a usable checklist, while holding nothing.
269
- *
270
- * Already-empty keys are skipped. Commenting out `FOO=""` to write `FOO=""`
271
- * underneath is churn that makes the next diff harder to read.
272
- */
273
- reset() {
274
- const out = [];
275
- const cleared = [];
276
- for (const line of this.lines) {
277
- if (line.key === undefined || line.commented || line.value === "") {
278
- out.push(line);
279
- continue;
280
- }
281
- const exported = ACTIVE.exec(line.raw)?.[2] ?? "";
282
- line.raw = `# ${line.raw.trimStart()}`;
283
- line.commented = true;
284
- out.push(line);
285
- out.push({
286
- raw: `${exported}${line.key}=""`,
287
- key: line.key,
288
- value: "",
289
- });
290
- cleared.push(line.key);
291
- }
292
- this.lines = out;
293
- return cleared;
294
- }
295
- /**
296
- * Comments a key out rather than deleting it.
297
- *
298
- * A no-op when it is already commented or absent, so running a sync twice
299
- * cannot produce `## KEY=`.
300
- */
301
- comment(key) {
302
- const i = this.find(key, false);
303
- if (i === -1)
304
- return false;
305
- const line = this.lines[i];
306
- line.raw = `# ${line.raw.trimStart()}`;
307
- line.commented = true;
308
- return true;
309
- }
310
- /** Restores a commented assignment. Returns its value, or undefined. */
311
- uncomment(key) {
312
- const i = this.find(key, true);
313
- if (i === -1)
314
- return undefined;
315
- const line = this.lines[i];
316
- line.raw = line.raw.replace(/^(\s*)#\s?/, "$1");
317
- line.commented = false;
318
- return line.value;
319
- }
320
- toString() {
321
- return this.lines.map((l) => l.raw).join("\n");
322
- }
323
- }