@devdogsuga/devtools 0.1.50 → 0.1.51-next.526

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.
@@ -1,8 +1,6 @@
1
- import { a as cliName, n as noteError } from "./failure-log-DOt6E3hS.js";
2
- import { a as ownVersion, n as discoverRepoRoot } from "./root-CjPWNkBu.js";
1
+ import { a as noteError, l as ownVersion, n as discoverRepoRoot, u as cliName } from "./root-CsocfxnK.js";
3
2
  import { readFileSync } from "node:fs";
4
3
  import * as Sentry from "@sentry/node";
5
- import "@sentry/core";
6
4
  //#region ../cli-core/src/mode.ts
7
5
  /**
8
6
  * Non-interactive mode: the one definition both published CLIs share.
@@ -75,7 +73,7 @@ function stripTierFlag(argv) {
75
73
  };
76
74
  }
77
75
  //#endregion
78
- //#region ../telemetry/dist/constants.js
76
+ //#region ../sdk/dist/telemetry/constants.js
79
77
  /**
80
78
  * `tracesSampleRate` by environment. Every environment not listed samples at
81
79
  * 0 — that is the "else 0" branch, not an omission, and covers `development`,
@@ -100,7 +98,7 @@ const TAG_KEYS = {
100
98
  ALERT: "alert"
101
99
  };
102
100
  //#endregion
103
- //#region ../telemetry/dist/scrub.js
101
+ //#region ../sdk/dist/telemetry/scrub.js
104
102
  /**
105
103
  * Pure scrubbing functions plus the `beforeSend` that composes them.
106
104
  *
@@ -124,6 +122,7 @@ const BEARER_TOKEN = /\b[Bb]earer\s+[A-Za-z0-9._-]+/g;
124
122
  function scrubSecrets(text) {
125
123
  return text.replace(ENV_ASSIGNMENT, "$1=[redacted]").replace(BEARER_TOKEN, "Bearer [redacted]");
126
124
  }
125
+ WINDOWS_PATH.source, WINDOWS_PATH.flags, POSIX_PATH.source, POSIX_PATH.flags, EMAIL.source, EMAIL.flags, ENV_ASSIGNMENT.source, ENV_ASSIGNMENT.flags, BEARER_TOKEN.source, BEARER_TOKEN.flags;
127
126
  /** Runs all three text scrubbers, in order. */
128
127
  function scrubText(text) {
129
128
  return scrubSecrets(scrubEmails(scrubPaths(text)));
@@ -165,7 +164,7 @@ function scrubEvent(event, _hint) {
165
164
  };
166
165
  }
167
166
  //#endregion
168
- //#region ../telemetry/dist/options.js
167
+ //#region ../sdk/dist/telemetry/options.js
169
168
  /**
170
169
  * Chains `beforeSend`-shaped functions left to right, short-circuiting the
171
170
  * moment one drops the event. Used to compose the shared scrubbers with a
@@ -214,7 +213,7 @@ function buildSentryOptions({ service, environment, dsn, release, extraBeforeSen
214
213
  //#region ../cli-core/src/telemetry.ts
215
214
  /**
216
215
  * devtools' own Sentry wiring — the CLI is a consumer of
217
- * `@devdogsuga/telemetry` like `apps/platform` and `apps/schedule-builder`,
216
+ * `@devdogsuga/sdk/telemetry` like `apps/platform` and `apps/schedule-builder`,
218
217
  * but on `@sentry/node` rather than a framework SDK: a CLI
219
218
  * process starts, runs one command, and exits, with none of a server's
220
219
  * request lifecycle for a framework integration to hook into.
@@ -222,7 +221,7 @@ function buildSentryOptions({ service, environment, dsn, release, extraBeforeSen
222
221
  * ## Reporting is on by default
223
222
  *
224
223
  * Every other `*_SENTRY_DSN` in this org is optional-and-empty until
225
- * configured (see `@devdogsuga/telemetry`'s no-op-without-DSN contract), and
224
+ * configured (see `@devdogsuga/sdk/telemetry`'s no-op-without-DSN contract), and
226
225
  * so is this one — empty means `buildSentryOptions` returns `undefined` and
227
226
  * `Sentry.init` never runs — but the DEFAULT here is reporting ON once a DSN
228
227
  * exists, unlike a feature a contributor opts into. `DEVTOOLS_TELEMETRY=0` is
@@ -273,7 +272,7 @@ function sent(id) {
273
272
  * `'ci'` whenever the run is non-interactive (no TTY, or `CI=true`, which the
274
273
  * runner sets itself before any workflow-authored env), `'local'` on a
275
274
  * contributor's terminal.
276
- * One of `@devdogsuga/telemetry`'s `ENVIRONMENTS`.
275
+ * One of `@devdogsuga/sdk/telemetry`'s `ENVIRONMENTS`.
277
276
  */
278
277
  function devtoolsEnvironment() {
279
278
  return isNonInteractive() ? "ci" : "local";
package/dist/telemetry.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as hasRealCaller, c as reportDevtoolsError, i as devtoolsTelemetryEnabled, l as reportDevtoolsFailure, n as captureDevtoolsError, o as initDevtoolsTelemetry, r as devtoolsEnvironment, s as lastSentryEventId, t as captureDevtoolsDeprecation, u as resolveDevtoolsDsn } from "./telemetry-C07-ioHm.js";
1
+ import { a as hasRealCaller, c as reportDevtoolsError, i as devtoolsTelemetryEnabled, l as reportDevtoolsFailure, n as captureDevtoolsError, o as initDevtoolsTelemetry, r as devtoolsEnvironment, s as lastSentryEventId, t as captureDevtoolsDeprecation, u as resolveDevtoolsDsn } from "./telemetry-BKFDtzXI.js";
2
2
  export { captureDevtoolsDeprecation, captureDevtoolsError, devtoolsEnvironment, devtoolsTelemetryEnabled, hasRealCaller, initDevtoolsTelemetry, lastSentryEventId, reportDevtoolsError, reportDevtoolsFailure, resolveDevtoolsDsn };
@@ -0,0 +1,370 @@
1
+ import { a as LocalStackOfflineError, c as applyDeployTierAliases, d as loadEnvironment, g as UnknownEnvironmentError, l as applyWranglerLocalDatabaseAlias, n as GENERATED_FILE, o as MissingEnvFileError, p as probeLocalStack } from "./load-Cp8AMERM.js";
2
+ import { n as availableTiers, s as askSession, t as SESSION_SELECTORS } from "./session-rbONwFvY.js";
3
+ import { createRequire } from "node:module";
4
+ import { existsSync, statSync, writeFileSync } from "node:fs";
5
+ import { dirname, join, resolve } from "node:path";
6
+ import { execFile, spawn } from "node:child_process";
7
+ import { fileURLToPath } from "node:url";
8
+ import { Command } from "commander";
9
+ //#region ../cli-core/src/env-runtime/confirm.ts
10
+ async function askOnTerminal(question) {
11
+ const { createInterface } = await import("node:readline/promises");
12
+ const rl = createInterface({
13
+ input: process.stdin,
14
+ output: process.stderr
15
+ });
16
+ try {
17
+ const answer = await rl.question(`${question} [y/N] `);
18
+ return /^y(es)?$/i.test(answer.trim());
19
+ } finally {
20
+ rl.close();
21
+ }
22
+ }
23
+ async function confirmProduction(options) {
24
+ if (options.tier !== "production" || options.yes) return { ok: true };
25
+ if (!options.isTTY) return {
26
+ ok: false,
27
+ reason: "--yes is required to preview production."
28
+ };
29
+ return await (options.ask ?? askOnTerminal)(`Run ${options.what} against live production data?`) ? { ok: true } : {
30
+ ok: false,
31
+ reason: "not confirmed."
32
+ };
33
+ }
34
+ //#endregion
35
+ //#region ../cli-core/src/env-runtime/generated.ts
36
+ /**
37
+ * Writes `.env.generated` — the local Supabase stack's connection block — when
38
+ * the stack is listening and the file is missing or stale, so
39
+ * `supabase start|stop` can stay plain passthroughs and nothing has to be
40
+ * remembered afterwards.
41
+ *
42
+ * Cost: `supabase status -o env` takes about 1.1s, paid once, not per run.
43
+ *
44
+ * Do the values survive a stack restart? Yes: the keys are signed from the
45
+ * `jwt_secret` in `supabase/config.toml` and the ports are pinned there, so a
46
+ * restart reproduces them byte for byte. What does invalidate the file is
47
+ * editing `config.toml` (a new secret or port), so the file counts as stale
48
+ * when `config.toml` is newer than it.
49
+ */
50
+ function supabaseStatus(root) {
51
+ return new Promise((resolve, reject) => {
52
+ execFile("supabase", [
53
+ "status",
54
+ "-o",
55
+ "env"
56
+ ], {
57
+ cwd: root,
58
+ shell: process.platform === "win32",
59
+ timeout: 3e4
60
+ }, (err, stdout) => err ? reject(new Error(err.message)) : resolve(stdout));
61
+ });
62
+ }
63
+ /**
64
+ * Where `supabase/config.toml` lives for the repo at `root`: `root` itself in
65
+ * DevDogsUGA, `<root>/devdogsuga` in a Backstage checkout (which has no
66
+ * `supabase/` of its own; see cli-core's `repo/layout.ts`). Falls back to
67
+ * `root` when neither has one.
68
+ */
69
+ function supabaseRootFor(root) {
70
+ if (existsSync(join(root, "supabase", "config.toml"))) return root;
71
+ const sibling = join(root, "devdogsuga");
72
+ return existsSync(join(sibling, "supabase", "config.toml")) ? sibling : root;
73
+ }
74
+ function isStale(root) {
75
+ const config = join(supabaseRootFor(root), "supabase", "config.toml");
76
+ if (!existsSync(config)) return false;
77
+ return statSync(config).mtimeMs > statSync(join(root, GENERATED_FILE)).mtimeMs;
78
+ }
79
+ async function ensureGeneratedEnv(options) {
80
+ const { root } = options;
81
+ const supabaseRoot = supabaseRootFor(root);
82
+ const targets = supabaseRoot === root ? [root] : [root, supabaseRoot];
83
+ const exists = targets.every((dir) => existsSync(join(dir, GENERATED_FILE)));
84
+ if (exists && !isStale(root)) return { action: "none" };
85
+ if (!await options.probe()) return { action: "none" };
86
+ try {
87
+ const output = await (options.status ?? supabaseStatus)(supabaseRoot);
88
+ if (!/^[A-Z_]+=/m.test(output)) return {
89
+ action: "failed",
90
+ reason: "unexpected output from supabase"
91
+ };
92
+ for (const dir of targets) writeFileSync(join(dir, GENERATED_FILE), output);
93
+ return {
94
+ action: exists ? "refreshed" : "written",
95
+ file: GENERATED_FILE
96
+ };
97
+ } catch (err) {
98
+ return {
99
+ action: "failed",
100
+ reason: err instanceof Error ? err.message : String(err)
101
+ };
102
+ }
103
+ }
104
+ //#endregion
105
+ //#region ../cli-core/src/env-runtime/cli.ts
106
+ /**
107
+ * Single env-loading helper for every workspace script:
108
+ *
109
+ * with-env [--tier <tier>] <command> [args...] [--tier <tier>]
110
+ * with-env -c '<shell command>'
111
+ * with-env --worker <app> [--yes] -- <command> [args...]
112
+ *
113
+ * `--tier` is also taken from the END of the wrapped command, because that is
114
+ * where `pnpm <script> --tier staging` puts it. On a terminal, the questions
115
+ * `askSession` (in `ask.ts`) shares with the devtools launcher are asked:
116
+ * which tier when several tier files are present and none is named, and which
117
+ * development database (remembered in `.env` if you say so) when `.env` names
118
+ * a remote one. With no terminal, an unnamed tier among several is refused.
119
+ *
120
+ * Loads the environment `DEPLOY_ENV` selects, where unset means development
121
+ * means the root `.env`. In development only, it also loads the
122
+ * `.env.generated` overlay when the local Supabase stack is actually listening
123
+ * (see `load.ts` for the probe table). Installed as a bin, so it inherits the
124
+ * calling package's directory and its node_modules/.bin, with no chdir or PATH
125
+ * fixup.
126
+ *
127
+ * The files it loaded are printed to stderr on every run. That is a design
128
+ * requirement, not chattiness: the probe silently decides between the hosted
129
+ * project and a running local container, and which database a command just
130
+ * touched must never be a guess.
131
+ *
132
+ * Use -c when the command needs a value *from* the env files. A $VAR in the
133
+ * script is expanded by pnpm's shell before this helper loads anything, so it
134
+ * would resolve against the ambient environment; quoting it and passing it to
135
+ * -c defers expansion until after the env is loaded. -c runs the string
136
+ * through @yarnpkg/shell, the same JS shell pnpm's shellEmulator uses, so it
137
+ * stays cross-platform.
138
+ *
139
+ * `--worker <app>` additionally writes the app's Worker env (see
140
+ * `worker-env.ts`) to a private mode-0600 file for the life of the command and
141
+ * substitutes its path for any argument spelled `{env-file}`, e.g.
142
+ * `with-env --worker platform -- wrangler dev --env-file {env-file}`.
143
+ * Previewing the production tier asks first (`--yes` when there is no
144
+ * terminal).
145
+ *
146
+ * ⚠️ The `--` there is load-bearing. Node scans a script's WHOLE argv for
147
+ * `--env-file`, not just its own options, and stops only at `--`: without it,
148
+ * the node running this file tries to load the literal `{env-file}` and dies
149
+ * with `node: {env-file}: not found` before any of this code runs.
150
+ *
151
+ * Neither mode spawns a platform shell, so this works on Windows and POSIX.
152
+ */
153
+ function findRoot(from) {
154
+ for (let dir = from;;) {
155
+ if (existsSync(join(dir, "pnpm-workspace.yaml"))) return dir;
156
+ const parent = dirname(dir);
157
+ if (parent === dir) {
158
+ console.error("with-env: could not locate the monorepo root.");
159
+ process.exit(1);
160
+ }
161
+ dir = parent;
162
+ }
163
+ }
164
+ const rootOverride = process.env.WITH_ENV_ROOT_FOR_TESTS;
165
+ if (rootOverride !== void 0 && rootOverride !== "") console.error(`with-env: root overridden by WITH_ENV_ROOT_FOR_TESTS=${rootOverride} (tests only)`);
166
+ const root = rootOverride !== void 0 && rootOverride !== "" ? rootOverride : findRoot(resolve(dirname(fileURLToPath(import.meta.url)), ".."));
167
+ const program = new Command("with-env").enablePositionalOptions().passThroughOptions().option("-c <script>", "run a shell script string with the env loaded").option("--tier <tier>", `environment to load (${SESSION_SELECTORS.join(", ")}, or bare development); overrides DEPLOY_ENV`).option("--worker <app>", "write <app>'s Worker env to a temp file; {env-file} in the command is replaced by its path").option("--yes", "confirm previewing the production tier without asking").argument("[command...]", "command to run with the env loaded").configureOutput({ writeErr: (str) => process.stderr.write(str.replace(/^error:/, "with-env:")) });
168
+ program.parse();
169
+ const opts = program.opts();
170
+ const args = program.args;
171
+ let trailingTier;
172
+ for (let i = 1; i < args.length; i++) {
173
+ const arg = args[i];
174
+ if (arg !== "--tier" && !arg.startsWith("--tier=")) continue;
175
+ const value = arg === "--tier" ? args[i + 1] : arg.slice(7);
176
+ if (value === void 0 || value === "" || value.startsWith("-")) {
177
+ console.error("with-env: --tier needs a value");
178
+ process.exit(1);
179
+ }
180
+ trailingTier = value;
181
+ args.splice(i, arg === "--tier" ? 2 : 1);
182
+ i--;
183
+ }
184
+ if (trailingTier !== void 0 && opts.tier !== void 0 && trailingTier !== opts.tier) {
185
+ console.error(`with-env: the script names --tier ${opts.tier} and its arguments name --tier ${trailingTier}`);
186
+ process.exit(1);
187
+ }
188
+ const explicitTier = opts.tier ?? trailingTier;
189
+ const usage = "with-env: usage: with-env <command> [args...]\n with-env -c '<shell command>'";
190
+ const shellMode = opts.c !== void 0;
191
+ if (shellMode && args.length > 0) {
192
+ console.error("with-env: -c takes a single quoted string\n" + usage);
193
+ process.exit(1);
194
+ }
195
+ if (shellMode && opts.worker !== void 0) {
196
+ console.error("with-env: --worker takes a command, not -c\n" + usage);
197
+ process.exit(1);
198
+ }
199
+ if (!shellMode && args.length === 0) {
200
+ console.error(usage);
201
+ process.exit(1);
202
+ }
203
+ const cwd = process.cwd();
204
+ const tierExists = (relPath) => existsSync(join(root, relPath));
205
+ const canAsk = process.stdin.isTTY === true && process.stdout.isTTY === true && process.env.CI !== "true" && process.env.CI !== "1";
206
+ const resolution = await askSession({
207
+ root,
208
+ explicit: explicitTier,
209
+ deployEnv: process.env.DEPLOY_ENV,
210
+ devDb: process.env.DEV_DB,
211
+ available: await availableTiers(root, tierExists),
212
+ canAsk,
213
+ unanswered: "probe",
214
+ name: "with-env"
215
+ });
216
+ if (!resolution.ok) {
217
+ console.error(`with-env: ${resolution.reason}`);
218
+ process.exit(1);
219
+ }
220
+ if (canAsk && resolution.tier === "development" && resolution.devDatabase === "local" && !await probeLocalStack()) {
221
+ const { cancel, confirm, isCancel } = await import("@clack/prompts");
222
+ const start = await confirm({ message: "The local Supabase stack is not running. Start it now?" });
223
+ if (isCancel(start)) {
224
+ cancel("Cancelled.");
225
+ process.exit(1);
226
+ }
227
+ if (start) {
228
+ if (await new Promise((done) => {
229
+ spawn("pnpm", [
230
+ "devtools",
231
+ "db",
232
+ "start"
233
+ ], {
234
+ cwd: root,
235
+ env: {
236
+ ...process.env,
237
+ DEPLOY_ENV: "development",
238
+ DEV_DB: "local"
239
+ },
240
+ stdio: "inherit",
241
+ shell: process.platform === "win32"
242
+ }).on("error", () => done(1)).on("exit", (exitCode) => done(exitCode ?? 1));
243
+ }) !== 0) {
244
+ console.error("with-env: `pnpm devtools db start` failed; see above.");
245
+ process.exit(1);
246
+ }
247
+ }
248
+ }
249
+ if (opts.worker !== void 0) {
250
+ const confirmed = await confirmProduction({
251
+ tier: resolution.tier,
252
+ what: opts.worker,
253
+ yes: opts.yes === true,
254
+ isTTY: Boolean(process.stdin.isTTY)
255
+ });
256
+ if (!confirmed.ok) {
257
+ console.error(`with-env: ${confirmed.reason}`);
258
+ process.exit(1);
259
+ }
260
+ }
261
+ if (resolution.tier === "development" && resolution.devDatabase !== "remote") {
262
+ const generated = await ensureGeneratedEnv({
263
+ root,
264
+ probe: () => probeLocalStack()
265
+ });
266
+ if (generated.action === "written" || generated.action === "refreshed") console.error(`with-env: ${generated.action} ${generated.file} from \`supabase status -o env\`.`);
267
+ else if (generated.action === "failed") console.error(`with-env: could not write .env.generated (${generated.reason}).`);
268
+ }
269
+ const sessionLabel = resolution.devDatabase === void 0 ? resolution.tier : `${resolution.tier}:${resolution.devDatabase}`;
270
+ let env;
271
+ let envFiles;
272
+ try {
273
+ const loaded = await loadEnvironment(resolution.tier, { devDatabase: resolution.devDatabase }, { root });
274
+ for (const warning of loaded.warnings) console.error(`with-env: ${warning}`);
275
+ console.error(`with-env: loaded ${loaded.files.join(" + ")} (${sessionLabel})`);
276
+ envFiles = loaded.files;
277
+ env = loaded.env;
278
+ } catch (err) {
279
+ if (err instanceof MissingEnvFileError) {
280
+ console.error(`with-env: ${err.message}`);
281
+ console.error("with-env: continuing with no env file loaded.");
282
+ envFiles = [];
283
+ env = {};
284
+ for (const [key, value] of Object.entries(process.env)) if (value !== void 0) env[key] = value;
285
+ applyWranglerLocalDatabaseAlias(env);
286
+ } else if (err instanceof LocalStackOfflineError) {
287
+ console.error(`with-env: ${err.message}`);
288
+ process.exit(1);
289
+ } else if (err instanceof UnknownEnvironmentError) {
290
+ console.error(`with-env: ${err.message}`);
291
+ process.exit(1);
292
+ } else throw err;
293
+ }
294
+ applyDeployTierAliases(env, resolution.tier);
295
+ env.DEPLOY_ENV = resolution.tier;
296
+ if (resolution.devDatabase !== void 0) env.DEV_DB = resolution.devDatabase;
297
+ /**
298
+ * dotenvx's CLI entry point, resolved through its package rather than a `.bin`
299
+ * shim: Node refuses to spawn `.cmd` without a shell, so the shim is not
300
+ * something we can exec directly.
301
+ */
302
+ function dotenvxCli() {
303
+ try {
304
+ const require = createRequire(import.meta.url);
305
+ const pkgPath = require.resolve("@dotenvx/dotenvx/package.json");
306
+ const { bin } = require("@dotenvx/dotenvx/package.json");
307
+ const entry = typeof bin === "string" ? bin : bin?.dotenvx;
308
+ if (!entry) throw new Error("no bin entry");
309
+ return join(dirname(pkgPath), entry);
310
+ } catch {
311
+ console.error("with-env: cannot find @dotenvx/dotenvx — run `pnpm install` at the repo root.");
312
+ process.exit(1);
313
+ }
314
+ }
315
+ if (shellMode && opts.c !== void 0) {
316
+ const [{ npath }, { execute }] = await Promise.all([import("@yarnpkg/fslib"), import("@yarnpkg/shell")]);
317
+ process.exit(await execute(opts.c, [], {
318
+ cwd: npath.toPortablePath(cwd),
319
+ env,
320
+ stdin: process.stdin,
321
+ stdout: process.stdout,
322
+ stderr: process.stderr
323
+ }));
324
+ }
325
+ const windows = process.platform === "win32";
326
+ let workerEnvFile;
327
+ if (opts.worker !== void 0) {
328
+ const [{ loadAppRegistry }, { buildWorkerEnv }, { writeWranglerEnvFile }] = await Promise.all([
329
+ import("./app-registry-BoAnenqC.js"),
330
+ import("./worker-env-CAFmquhW.js"),
331
+ import("./wrangler-env-BrlNe4pm.js")
332
+ ]);
333
+ try {
334
+ const registry = await loadAppRegistry(cwd);
335
+ workerEnvFile = await writeWranglerEnvFile(buildWorkerEnv(opts.worker, env, "dev", registry).env);
336
+ } catch (err) {
337
+ console.error(`with-env: --worker ${opts.worker}: ${err instanceof Error ? err.message : String(err)}`);
338
+ process.exit(1);
339
+ }
340
+ env.WORKER_ENV_FILE = workerEnvFile.path;
341
+ for (let i = 0; i < args.length; i++) if (args[i] === "{env-file}") args[i] = workerEnvFile.path;
342
+ }
343
+ const child = spawn(windows ? process.execPath : args[0], windows ? [
344
+ dotenvxCli(),
345
+ "run",
346
+ "--quiet",
347
+ ...envFiles.length === 0 ? ["--ignore=MISSING_ENV_FILE"] : [],
348
+ ...envFiles.flatMap((f) => ["-f", join(root, f)]),
349
+ "--",
350
+ ...args
351
+ ] : args.slice(1), {
352
+ cwd,
353
+ env,
354
+ stdio: "inherit"
355
+ });
356
+ child.on("error", (err) => {
357
+ workerEnvFile?.remove();
358
+ console.error(`with-env: ${err.message}`);
359
+ process.exit(1);
360
+ });
361
+ if (workerEnvFile !== void 0) for (const signal of ["SIGINT", "SIGTERM"]) process.on(signal, () => child.kill(signal));
362
+ child.on("exit", (code, signal) => {
363
+ workerEnvFile?.remove();
364
+ if (signal) {
365
+ process.removeAllListeners(signal);
366
+ process.kill(process.pid, signal);
367
+ } else process.exit(code ?? 1);
368
+ });
369
+ //#endregion
370
+ export {};
@@ -0,0 +1,36 @@
1
+ //#region ../cli-core/src/env-runtime/worker-env.ts
2
+ const WRANGLER_OWNED_KEYS = /* @__PURE__ */ new Set(["DEPLOY_ENV", "NODE_ENV"]);
3
+ function buildWorkerEnv(app, environment, purpose, registry) {
4
+ const env = {};
5
+ const absent = [];
6
+ const minted = [];
7
+ const storable = new Set(registry.storableKeys());
8
+ const entriesByKey = purpose === "dev" ? [...registry.variables()].sort(([a], [b]) => a < b ? -1 : 1) : [...registry.variables()];
9
+ for (const [key, entries] of entriesByKey) {
10
+ if (purpose === "dev") {
11
+ if (!entries.some((e) => e.source === app)) continue;
12
+ if (WRANGLER_OWNED_KEYS.has(key)) continue;
13
+ const value = environment[key];
14
+ if (value !== void 0) env[key] = value;
15
+ continue;
16
+ }
17
+ if (!entries.some((e) => e.source === app && !e.client)) continue;
18
+ const meta = entries[0].meta;
19
+ if (meta.secrecy === "secret") {
20
+ if (!storable.has(key)) {
21
+ minted.push(key);
22
+ continue;
23
+ }
24
+ } else if (meta.secrecy !== "public") continue;
25
+ const value = environment[key];
26
+ if (value === void 0 || value === "") absent.push(key);
27
+ else env[key] = value;
28
+ }
29
+ return {
30
+ env,
31
+ absent,
32
+ minted
33
+ };
34
+ }
35
+ //#endregion
36
+ export { buildWorkerEnv };
@@ -0,0 +1,34 @@
1
+ //#region ../cli-core/src/env-runtime/wrangler-env.ts
2
+ /**
3
+ * The mode-0600 env file `wrangler dev --env-file` reads, written to a fresh
4
+ * private temp directory so a credential-bearing file never lands in the repo.
5
+ */
6
+ /**
7
+ * dotenv preserves single-quoted values byte-for-byte, including multiline
8
+ * private keys and literal backslash sequences.
9
+ */
10
+ function renderWranglerEnvFile(env) {
11
+ return Object.entries(env).map(([key, value]) => `${key}='${value}'`).join("\n") + "\n";
12
+ }
13
+ async function writeWranglerEnvFile(env) {
14
+ const [{ mkdtempSync, rmSync, writeFileSync }, { tmpdir }, { join }] = await Promise.all([
15
+ import("node:fs"),
16
+ import("node:os"),
17
+ import("node:path")
18
+ ]);
19
+ const directory = mkdtempSync(join(tmpdir(), "with-env-wrangler-"));
20
+ const path = join(directory, ".dev.vars");
21
+ writeFileSync(path, renderWranglerEnvFile(env), {
22
+ encoding: "utf8",
23
+ mode: 384
24
+ });
25
+ return {
26
+ path,
27
+ remove: () => rmSync(directory, {
28
+ recursive: true,
29
+ force: true
30
+ })
31
+ };
32
+ }
33
+ //#endregion
34
+ export { renderWranglerEnvFile, writeWranglerEnvFile };
package/env.ts CHANGED
@@ -12,7 +12,7 @@
12
12
  * repository, refused storage because storing it defeats the thing it
13
13
  * protects. See the long-form reasoning in `src/bws/environments.ts`.
14
14
  */
15
- import { declare, define } from "@devdogsuga/env";
15
+ import { declare, define } from "@devdogsuga/sdk/env";
16
16
  import { z } from "zod";
17
17
 
18
18
  declare({
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@devdogsuga/devtools",
3
- "version": "0.1.50",
3
+ "version": "0.1.51-next.526",
4
4
  "private": false,
5
5
  "type": "module",
6
- "description": "Contributor CLI for a DevDogsUGA checkout: the real tools with the session's tier filled in, a menu over them, and the checks CI runs. Ships built JS; run via `pnpm dlx @devdogsuga/devtools` (or the `devtools` bin once installed) from inside a DevDogsUGA clone.",
6
+ "description": "Contributor CLI for DevDogs repositories (the platform monorepo, DogDays, DogPack): the real tools with the session's tier filled in, `setup`, `db sync`, `db types`, `dev-data`, a menu over them, the checks CI runs, and the `with-env` launcher. Ships built JS; run via `pnpm dlx @devdogsuga/devtools` or `npx @devdogsuga/devtools` (or the `devtools` bin once installed) from inside a clone.",
7
7
  "license": "BSD-3-Clause",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "https://github.com/DevDogsUGA/Backstage.git",
10
+ "url": "https://github.com/DevDogsUGA/DevDogsUGA.git",
11
11
  "directory": "packages/devtools"
12
12
  },
13
13
  "publishConfig": {
@@ -15,7 +15,8 @@
15
15
  "provenance": true
16
16
  },
17
17
  "bin": {
18
- "devtools": "./bin/devtools.mjs"
18
+ "devtools": "./bin/devtools.mjs",
19
+ "with-env": "./bin/with-env.mjs"
19
20
  },
20
21
  "files": [
21
22
  "bin",
@@ -24,27 +25,19 @@
24
25
  ],
25
26
  "dependencies": {
26
27
  "@clack/prompts": "1.7.0",
28
+ "@dotenvx/dotenvx": "^2.21.0",
27
29
  "@sentry/core": "10.75.1",
28
30
  "@sentry/node": "10.75.1",
29
31
  "@supabase/supabase-js": "^2.112.3",
32
+ "@yarnpkg/fslib": "^3.1.5",
33
+ "@yarnpkg/shell": "^4.1.3",
34
+ "commander": "^14.0.3",
30
35
  "dotenv": "^17.4.2",
31
36
  "postgres": "^3.4.9",
32
37
  "tsx": "^4.23.12",
33
38
  "typescript": "^6.0.3",
34
39
  "zod": "4.4.3"
35
40
  },
36
- "peerDependencies": {
37
- "@devdogsuga/db": "*",
38
- "@devdogsuga/env": "*"
39
- },
40
- "peerDependenciesMeta": {
41
- "@devdogsuga/db": {
42
- "optional": true
43
- },
44
- "@devdogsuga/env": {
45
- "optional": true
46
- }
47
- },
48
41
  "devDependencies": {
49
42
  "@types/node": "^26.2.0",
50
43
  "@vitest/coverage-v8": "^4.1.11",
@@ -52,10 +45,8 @@
52
45
  "tsdown": "^0.23.0",
53
46
  "vitest": "^4.1.11",
54
47
  "@devdogsuga/cli-core": "0.0.0",
55
- "@devdogsuga/db": "0.1.5",
56
- "@devdogsuga/env": "0.1.8",
57
- "@devdogsuga/config": "0.1.3",
58
- "@devdogsuga/telemetry": "0.1.5"
48
+ "@devdogsuga/config": "0.1.4-next.526",
49
+ "@devdogsuga/sdk": "0.1.0"
59
50
  },
60
51
  "scripts": {
61
52
  "build": "tsdown && node scripts/write-build-info.mjs && node ../../scripts/check-bundle-imports.mjs",