@devdogsuga/devtools 0.1.50 → 0.1.51-next.527
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -7
- package/bin/with-env.mjs +17 -0
- package/dist/app-registry-BoAnenqC.js +38 -0
- package/dist/{cli-t3gWyy3w.js → cli-D4LUUxUR.js} +2286 -718
- package/dist/launch.js +23 -11
- package/dist/load-Cp8AMERM.js +640 -0
- package/dist/load-Czhayrsk.js +2 -0
- package/dist/{menu-DYQ-Dd-O.js → menu-DjE4EZKO.js} +475 -178
- package/dist/{failure-log-DOt6E3hS.js → root-CsocfxnK.js} +104 -5
- package/dist/session-CmDxHEMn.js +2 -0
- package/dist/session-rbONwFvY.js +431 -0
- package/dist/{stack-C_50FafG.js → stack-7Lbym6Wq.js} +2 -3
- package/dist/stack-7qA2UP9g.js +2 -0
- package/dist/supabase-project-BoJKMCxL.js +766 -0
- package/dist/{telemetry-C07-ioHm.js → telemetry-BKFDtzXI.js} +8 -9
- package/dist/telemetry.js +1 -1
- package/dist/with-env.js +370 -0
- package/dist/worker-env-CAFmquhW.js +36 -0
- package/dist/wrangler-env-BrlNe4pm.js +34 -0
- package/env.ts +1 -1
- package/package.json +11 -20
- package/dist/local-env-BuHU5Ckd.js +0 -357
- package/dist/local-env-MqH18s-N.js +0 -2
- package/dist/peers-DvUH5VxX.js +0 -354
- package/dist/root-CjPWNkBu.js +0 -99
- package/dist/stack-o1Kml3V0.js +0 -2
- package/dist/supabase-project-C93xRFkN.js +0 -436
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { a as
|
|
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 ../
|
|
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 ../
|
|
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 ../
|
|
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-
|
|
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 };
|
package/dist/with-env.js
ADDED
|
@@ -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.
|
|
3
|
+
"version": "0.1.51-next.527",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "Contributor CLI for
|
|
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/
|
|
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/
|
|
56
|
-
"@devdogsuga/
|
|
57
|
-
"@devdogsuga/config": "0.1.3",
|
|
58
|
-
"@devdogsuga/telemetry": "0.1.5"
|
|
48
|
+
"@devdogsuga/sdk": "0.1.0",
|
|
49
|
+
"@devdogsuga/config": "0.1.4-next.526"
|
|
59
50
|
},
|
|
60
51
|
"scripts": {
|
|
61
52
|
"build": "tsdown && node scripts/write-build-info.mjs && node ../../scripts/check-bundle-imports.mjs",
|