@the-meridian/cli 1.2.0 → 1.3.0

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/dist/index.js CHANGED
@@ -6,8 +6,8 @@ import { fileURLToPath } from "url";
6
6
  import { Command } from "commander";
7
7
 
8
8
  // src/commands/db.ts
9
- import { readFileSync as readFileSync7 } from "fs";
10
- import { join as join6 } from "path";
9
+ import { readFileSync as readFileSync6 } from "fs";
10
+ import { join as join5 } from "path";
11
11
 
12
12
  // src/version.ts
13
13
  import { createRequire } from "module";
@@ -81,9 +81,9 @@ var devDatabaseSchema = z.object({
81
81
  /** Set by the API only while `status` is `error`; surfaced verbatim to the developer. */
82
82
  error_message: z.string().nullish(),
83
83
  /**
84
- * How the CLI reaches the database: `gateway` (the Meridian database
85
- * gateway with a per-developer certificate) or `proxy` (cloud-sql-proxy).
86
- * Absent from an API older than the gateway, which means `proxy`.
84
+ * How the CLI reaches the database. The API always reports `gateway` (the
85
+ * Meridian database gateway with a per-developer certificate) since
86
+ * MER-823; the CLI no longer reads it.
87
87
  */
88
88
  access: devDatabaseAccessSchema.nullish()
89
89
  }).loose();
@@ -111,11 +111,6 @@ var devCredentialsSchema = z.object({
111
111
  /** Echoed from the (nullable) dev-database column, so it can be null. */
112
112
  instance_connection_name: z.string().nullish()
113
113
  }).loose();
114
- var proxyTokenSchema = z.object({
115
- access_token: z.string(),
116
- expires_at: z.string().nullish(),
117
- instance_connection_name: z.string().nullish()
118
- }).loose();
119
114
  var migrationRunInputSchema = z.object({
120
115
  command: z.string(),
121
116
  status: z.enum(["success", "failed", "rolled_back"]),
@@ -350,13 +345,6 @@ var ApiClient = class _ApiClient {
350
345
  `/app/cli/apps/${encodePathSegment(appId)}/dev-database/credentials`
351
346
  );
352
347
  }
353
- async issueProxyToken(appId) {
354
- return this.request(
355
- proxyTokenSchema,
356
- "POST",
357
- `/app/cli/apps/${encodePathSegment(appId)}/dev-database/proxy-token`
358
- );
359
- }
360
348
  /**
361
349
  * Signs `publicKeyPem` (SPKI) as this developer's client certificate for the
362
350
  * database gateway. Each call replaces the previous certificate server-side.
@@ -592,9 +580,6 @@ function meridianHome() {
592
580
  function globalConfigPath() {
593
581
  return join(meridianHome(), "config.json");
594
582
  }
595
- function proxyBinDir() {
596
- return join(meridianHome(), "bin");
597
- }
598
583
  function projectStateDir(root) {
599
584
  return join(root, ".meridian");
600
585
  }
@@ -747,8 +732,8 @@ function connectionInfoFromCredentials(credentials, proxy) {
747
732
  };
748
733
  }
749
734
  var createSqlConnection = async (info, options) => {
750
- const { createConnection: createConnection2 } = await import("mysql2/promise");
751
- const connection = await createConnection2({
735
+ const { createConnection } = await import("mysql2/promise");
736
+ const connection = await createConnection({
752
737
  host: info.host,
753
738
  port: info.port,
754
739
  user: info.user,
@@ -832,7 +817,7 @@ function toDatabaseError(error2, info) {
832
817
  if (code === "ECONNREFUSED") {
833
818
  return build(
834
819
  `Could not reach ${target} (${code}).`,
835
- "Nothing is listening on that port. `meridian dev` and `meridian migrate` start the Cloud SQL Auth Proxy for you; start it first if you are connecting by hand."
820
+ "Nothing is listening on that port. `meridian dev` and `meridian migrate` start the database connection for you; start one of them first if you are connecting by hand."
836
821
  );
837
822
  }
838
823
  if (code === "ETIMEDOUT" || code === "PROTOCOL_SEQUENCE_TIMEOUT") {
@@ -2569,7 +2554,7 @@ async function findFreePort(options = {}) {
2569
2554
  }
2570
2555
  const ephemeral = await bindEphemeralPort(host);
2571
2556
  if (ephemeral === null) {
2572
- throw new CliError("Could not find a free local port for the Cloud SQL Auth Proxy.", {
2557
+ throw new CliError("Could not find a free local port for the database gateway forwarder.", {
2573
2558
  hint: "Close other applications using local ports and try again."
2574
2559
  });
2575
2560
  }
@@ -2782,513 +2767,6 @@ function diagnoseMigrationFailure(root) {
2782
2767
  return offender === null ? null : foreignDialectExplanation(offender);
2783
2768
  }
2784
2769
 
2785
- // src/lib/proxy/binary.ts
2786
- import { createHash as createHash2 } from "crypto";
2787
- import { chmodSync as chmodSync3, existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync6, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
2788
- import { join as join4 } from "path";
2789
- var PROXY_VERSION = "2.24.1";
2790
- var PROXY_CHECKSUMS = Object.freeze({
2791
- "cloud-sql-proxy.darwin.amd64": "67a38444d03fe77a8b11036f96fe5d2379640088bb6e338737fb48d97c58cf48",
2792
- "cloud-sql-proxy.darwin.arm64": "fd374545705f2aa687d8e525a2efcbb9d356cc8af60d4dfa1a330cb59a7f9450",
2793
- "cloud-sql-proxy.linux.386": "cf731f4becfb5a2de61f2998b2d660c5fb8f3067cbd3b86f5783ae9049c06f24",
2794
- "cloud-sql-proxy.linux.amd64": "fae2766aac9d614a2bdef2f2a7778f3d054f3acd5ff07a81a9e300bd471512eb",
2795
- "cloud-sql-proxy.linux.arm64": "da49fd73b0d33c0ab8e5dfc19dfb104cdd969ebd355eef8fd3b079c20a723c82",
2796
- "cloud-sql-proxy.x64.exe": "01ec54c69dbb8118c0b181c33ff1469a5cdb2dddb49eb86b0b173f6c55dea0bc",
2797
- "cloud-sql-proxy.x86.exe": "98c3b1897c80f2bc0b241b4452c18557a00cd86f89e4d7449c4ff1316e243c44"
2798
- });
2799
- var ASSET_NAME_BY_PLATFORM_ARCH = Object.freeze({
2800
- "darwin:arm64": "cloud-sql-proxy.darwin.arm64",
2801
- "darwin:x64": "cloud-sql-proxy.darwin.amd64",
2802
- "linux:x64": "cloud-sql-proxy.linux.amd64",
2803
- "linux:arm64": "cloud-sql-proxy.linux.arm64",
2804
- "linux:ia32": "cloud-sql-proxy.linux.386",
2805
- "win32:x64": "cloud-sql-proxy.x64.exe",
2806
- "win32:ia32": "cloud-sql-proxy.x86.exe"
2807
- });
2808
- function defaultVersion() {
2809
- const override = process.env.MERIDIAN_PROXY_VERSION;
2810
- if (override !== void 0 && override !== "") return override;
2811
- return PROXY_VERSION;
2812
- }
2813
- function resolveProxyAsset(options) {
2814
- const platform = options?.platform ?? process.platform;
2815
- const arch = options?.arch ?? process.arch;
2816
- const version = options?.version ?? defaultVersion();
2817
- const assetName = ASSET_NAME_BY_PLATFORM_ARCH[`${platform}:${arch}`];
2818
- if (assetName === void 0) {
2819
- throw new CliError(`The Cloud SQL Auth Proxy has no published build for ${platform}/${arch}.`, {
2820
- hint: "Set MERIDIAN_PROXY_PATH to the path of a proxy binary you install yourself."
2821
- });
2822
- }
2823
- const downloadUrl = `https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v${version}/${assetName}`;
2824
- const sha256Value = version === PROXY_VERSION ? PROXY_CHECKSUMS[assetName] : void 0;
2825
- const fileName = `cloud-sql-proxy-${version}-${platform}-${arch}${platform === "win32" ? ".exe" : ""}`;
2826
- return {
2827
- version,
2828
- assetName,
2829
- downloadUrl,
2830
- ...sha256Value !== void 0 ? { sha256: sha256Value } : {},
2831
- fileName
2832
- };
2833
- }
2834
- function sha256(data) {
2835
- return createHash2("sha256").update(data).digest("hex");
2836
- }
2837
- var CHECKSUM_ROW_PATTERN = /\[([^\]]+)]\([^)]*\)\s*\|\s*([0-9a-fA-F]{64})/g;
2838
- function parseChecksumTable(releaseBody) {
2839
- const table = {};
2840
- for (const match of releaseBody.matchAll(CHECKSUM_ROW_PATTERN)) {
2841
- const name = match[1];
2842
- const hash = match[2];
2843
- if (name !== void 0 && hash !== void 0) table[name] = hash.toLowerCase();
2844
- }
2845
- return table;
2846
- }
2847
- var RELEASE_API_URL = "https://api.github.com/repos/GoogleCloudPlatform/cloud-sql-proxy/releases/tags";
2848
- var checksumCache = /* @__PURE__ */ new Map();
2849
- async function resolveExpectedChecksum(asset, fetchImpl) {
2850
- if (asset.sha256 !== void 0) return asset.sha256;
2851
- const cacheKey = `${asset.version}:${asset.assetName}`;
2852
- const cached = checksumCache.get(cacheKey);
2853
- if (cached !== void 0) return cached;
2854
- const skipAllowed = process.env.MERIDIAN_PROXY_SKIP_CHECKSUM === "1";
2855
- const url = `${RELEASE_API_URL}/v${asset.version}`;
2856
- let response;
2857
- try {
2858
- response = await fetchImpl(url, { headers: { Accept: "application/vnd.github+json" } });
2859
- } catch (error2) {
2860
- if (skipAllowed) return void 0;
2861
- throw new CliError(`Could not reach ${url} to look up the checksum for cloud-sql-proxy v${asset.version}.`, {
2862
- hint: "Check your network connection, or set MERIDIAN_PROXY_SKIP_CHECKSUM=1 to install without verifying the checksum, at your own risk.",
2863
- cause: error2
2864
- });
2865
- }
2866
- if (!response.ok) {
2867
- if (skipAllowed) return void 0;
2868
- throw new CliError(
2869
- `Could not determine the sha256 checksum for cloud-sql-proxy v${asset.version} (GitHub release lookup for ${url} returned HTTP ${response.status}).`,
2870
- {
2871
- hint: "Set MERIDIAN_PROXY_SKIP_CHECKSUM=1 to install without verifying the checksum, at your own risk."
2872
- }
2873
- );
2874
- }
2875
- const rawBody = await response.text();
2876
- let releaseNotes = rawBody;
2877
- try {
2878
- const parsed = JSON.parse(rawBody);
2879
- if (typeof parsed === "object" && parsed !== null && "body" in parsed && typeof parsed.body === "string") {
2880
- releaseNotes = parsed.body;
2881
- }
2882
- } catch {
2883
- }
2884
- const checksum = parseChecksumTable(releaseNotes)[asset.assetName];
2885
- if (checksum !== void 0) {
2886
- checksumCache.set(cacheKey, checksum);
2887
- return checksum;
2888
- }
2889
- if (skipAllowed) return void 0;
2890
- throw new CliError(
2891
- `Could not find a published sha256 checksum for ${asset.assetName} in the cloud-sql-proxy v${asset.version} release notes.`,
2892
- { hint: "Set MERIDIAN_PROXY_SKIP_CHECKSUM=1 to install without verifying the checksum, at your own risk." }
2893
- );
2894
- }
2895
- function parseContentLength(header) {
2896
- if (header === null) return void 0;
2897
- const value = Number(header);
2898
- return Number.isFinite(value) ? value : void 0;
2899
- }
2900
- async function readResponseBody(response, onProgress) {
2901
- const total = parseContentLength(response.headers.get("content-length"));
2902
- if (response.body === null) {
2903
- const buffer = Buffer.from(await response.arrayBuffer());
2904
- onProgress?.(buffer.byteLength, total);
2905
- return buffer;
2906
- }
2907
- const reader = response.body.getReader();
2908
- const chunks = [];
2909
- let received = 0;
2910
- for (; ; ) {
2911
- const { done, value } = await reader.read();
2912
- if (done) break;
2913
- if (value !== void 0) {
2914
- chunks.push(value);
2915
- received += value.byteLength;
2916
- onProgress?.(received, total);
2917
- }
2918
- }
2919
- return Buffer.concat(chunks);
2920
- }
2921
- async function ensureProxyBinary(options = {}) {
2922
- const overridePath = process.env.MERIDIAN_PROXY_PATH;
2923
- if (overridePath !== void 0 && overridePath !== "") {
2924
- if (!existsSync4(overridePath)) {
2925
- throw new CliError(`MERIDIAN_PROXY_PATH is set to ${overridePath}, but that file does not exist.`, {
2926
- hint: "Fix or unset MERIDIAN_PROXY_PATH."
2927
- });
2928
- }
2929
- return overridePath;
2930
- }
2931
- const asset = resolveProxyAsset(options.version !== void 0 ? { version: options.version } : {});
2932
- const dir = options.dir ?? proxyBinDir();
2933
- const fetchImpl = options.fetchImpl ?? fetch;
2934
- const targetPath = join4(dir, asset.fileName);
2935
- mkdirSync4(dir, { recursive: true });
2936
- if (!options.force && existsSync4(targetPath)) {
2937
- const cached = readFileSync6(targetPath);
2938
- const expected2 = await resolveExpectedChecksum(asset, fetchImpl);
2939
- if (expected2 === void 0 || sha256(cached) === expected2) return targetPath;
2940
- rmSync2(targetPath, { force: true });
2941
- }
2942
- const response = await fetchImpl(asset.downloadUrl);
2943
- if (!response.ok) {
2944
- throw new CliError(
2945
- `Failed to download the Cloud SQL Auth Proxy v${asset.version} from ${asset.downloadUrl} (HTTP ${response.status}).`,
2946
- {
2947
- hint: "Check your network connection (and any corporate proxy settings), or set MERIDIAN_PROXY_PATH to a proxy binary you install yourself."
2948
- }
2949
- );
2950
- }
2951
- const buffer = await readResponseBody(response, options.onProgress);
2952
- const expected = await resolveExpectedChecksum(asset, fetchImpl);
2953
- const actual = sha256(buffer);
2954
- if (expected !== void 0 && actual !== expected) {
2955
- throw new CliError(
2956
- `Checksum mismatch for ${asset.assetName}: expected ${expected.slice(0, 12)}..., got ${actual.slice(0, 12)}....`,
2957
- {
2958
- hint: "This can happen after a corrupted download or an untrusted network. Try again; if it persists, report this."
2959
- }
2960
- );
2961
- }
2962
- const tmpPath = join4(dir, `.${asset.fileName}.${process.pid}.tmp`);
2963
- writeFileSync5(tmpPath, buffer);
2964
- chmodSync3(tmpPath, 493);
2965
- renameSync3(tmpPath, targetPath);
2966
- return targetPath;
2967
- }
2968
-
2969
- // src/lib/proxy/proxy.ts
2970
- import { spawn } from "child_process";
2971
- import { createConnection } from "net";
2972
- var PROBE_INTERVAL_MS = 150;
2973
- var KILL_GRACE_MS = 2e3;
2974
- var CRASH_BACKOFFS_MS = [200, 1e3, 3e3];
2975
- var MAX_CRASH_RESTARTS = CRASH_BACKOFFS_MS.length;
2976
- var REFRESH_RETRY_BACKOFFS_MS2 = [3e4, 6e4, 12e4];
2977
- var MIN_REFRESH_DELAY_MS2 = 6e4;
2978
- var LOG_RING_SIZE = 20;
2979
- var STDERR_TAIL_LINES = 10;
2980
- function buildProxyArgs(input2) {
2981
- return ["--address", input2.host, "--port", String(input2.port), input2.instanceConnectionName];
2982
- }
2983
- function buildProxyEnv(accessToken, base = process.env) {
2984
- return { ...base, CSQL_PROXY_TOKEN: accessToken };
2985
- }
2986
- function defaultProbe(host, port) {
2987
- return new Promise((resolve2) => {
2988
- const socket = createConnection({ host, port });
2989
- const finish = (ok) => {
2990
- socket.removeAllListeners();
2991
- socket.destroy();
2992
- resolve2(ok);
2993
- };
2994
- socket.once("connect", () => finish(true));
2995
- socket.once("error", () => finish(false));
2996
- socket.setTimeout(1e3, () => finish(false));
2997
- });
2998
- }
2999
- function computeRefreshDelayMs(token, refreshSkewMs, assumedLifetimeMs) {
3000
- let expiresAtMs;
3001
- if (token.expires_at !== void 0) {
3002
- const parsed = new Date(token.expires_at).getTime();
3003
- if (!Number.isNaN(parsed)) expiresAtMs = parsed;
3004
- }
3005
- const effectiveExpiryMs = expiresAtMs ?? Date.now() + assumedLifetimeMs;
3006
- return Math.max(effectiveExpiryMs - Date.now() - refreshSkewMs, MIN_REFRESH_DELAY_MS2);
3007
- }
3008
- function buildStartupFailureError(code, signal, lastLines) {
3009
- const tail = lastLines.slice(-STDERR_TAIL_LINES).join("\n");
3010
- const lower = tail.toLowerCase();
3011
- let hint2 = "Run `meridian dev` again; if this keeps happening, check the instance connection name and your network.";
3012
- if (lower.includes("iam") || lower.includes("permission") || lower.includes("forbidden")) {
3013
- hint2 = "The Meridian API's service account may be missing roles/cloudsql.client on the dev Cloud SQL instance. Contact Meridian support.";
3014
- } else if (lower.includes("invalid token") || lower.includes("401") || lower.includes("unauthorized")) {
3015
- hint2 = "The access token appears to have expired. Retry `meridian dev` to mint a fresh one.";
3016
- } else if (lower.includes("address already in use")) {
3017
- hint2 = "Another process is already listening on that port. Free it up, or retry with a different port.";
3018
- }
3019
- const summary = `The Cloud SQL Auth Proxy exited before it was ready (code ${code ?? "null"}, signal ${signal ?? "null"}).`;
3020
- return new CliError(tail === "" ? summary : `${summary}
3021
- ${tail}`, { hint: hint2 });
3022
- }
3023
- function waitForReady(child, probeImpl, host, port, timeoutMs, setTimeoutImpl, getLastLines) {
3024
- return new Promise((resolve2, reject) => {
3025
- let settled = false;
3026
- let pollTimer;
3027
- let timeoutTimer;
3028
- const cleanup = () => {
3029
- if (pollTimer !== void 0) clearTimeout(pollTimer);
3030
- if (timeoutTimer !== void 0) clearTimeout(timeoutTimer);
3031
- child.off("exit", onExit);
3032
- };
3033
- function onExit(code, signal) {
3034
- if (settled) return;
3035
- settled = true;
3036
- cleanup();
3037
- reject(buildStartupFailureError(code, signal, getLastLines()));
3038
- }
3039
- function poll() {
3040
- if (settled) return;
3041
- probeImpl(host, port).then((ok) => {
3042
- if (settled) return;
3043
- if (ok) {
3044
- settled = true;
3045
- cleanup();
3046
- resolve2();
3047
- } else {
3048
- pollTimer = setTimeoutImpl(poll, PROBE_INTERVAL_MS);
3049
- }
3050
- }).catch(() => {
3051
- if (!settled) pollTimer = setTimeoutImpl(poll, PROBE_INTERVAL_MS);
3052
- });
3053
- }
3054
- child.on("exit", onExit);
3055
- timeoutTimer = setTimeoutImpl(() => {
3056
- if (settled) return;
3057
- settled = true;
3058
- cleanup();
3059
- reject(
3060
- new CliError(`Timed out after ${timeoutMs}ms waiting for the Cloud SQL Auth Proxy to accept connections.`, {
3061
- hint: "The instance may be slow to reach, or readyTimeoutMs may need to be raised."
3062
- })
3063
- );
3064
- }, timeoutMs);
3065
- poll();
3066
- });
3067
- }
3068
- async function startProxy(options) {
3069
- const host = options.host ?? "127.0.0.1";
3070
- const spawnImpl = options.spawnImpl ?? spawn;
3071
- const probeImpl = options.probeImpl ?? defaultProbe;
3072
- const setTimeoutImpl = options.setTimeoutImpl ?? setTimeout;
3073
- const refreshSkewMs = options.refreshSkewMs ?? 5 * 6e4;
3074
- const assumedLifetimeMs = options.assumedLifetimeMs ?? 55 * 6e4;
3075
- const readyTimeoutMs = options.readyTimeoutMs ?? 3e4;
3076
- const emit = (event) => options.onEvent?.(event);
3077
- let stopped = false;
3078
- let running = false;
3079
- let expectingExit = false;
3080
- let currentToken = options.token;
3081
- let child;
3082
- let refreshTimer;
3083
- let crashTimer;
3084
- let refreshBackoffIndex = 0;
3085
- let crashRestartsRemaining = MAX_CRASH_RESTARTS;
3086
- let attemptCounter = 0;
3087
- let cleanupFns = [];
3088
- const knownTokens = /* @__PURE__ */ new Set();
3089
- const logRing = [];
3090
- function trackToken(token) {
3091
- if (token.access_token !== "") knownTokens.add(token.access_token);
3092
- }
3093
- function redact(text) {
3094
- let result = text;
3095
- for (const token of knownTokens) result = result.split(token).join("<redacted>");
3096
- return result;
3097
- }
3098
- function pushLog(line) {
3099
- const safeLine = redact(line);
3100
- logRing.push(safeLine);
3101
- if (logRing.length > LOG_RING_SIZE) logRing.shift();
3102
- emit({ type: "log", line: safeLine });
3103
- }
3104
- function wireOutput(c) {
3105
- for (const stream of [c.stdout, c.stderr]) {
3106
- if (stream === null) continue;
3107
- let buffer = "";
3108
- const onData = (chunk) => {
3109
- buffer += chunk.toString();
3110
- const lines = buffer.split("\n");
3111
- buffer = lines.pop() ?? "";
3112
- for (const line of lines) pushLog(line);
3113
- };
3114
- stream.on("data", onData);
3115
- cleanupFns.push(() => stream.off("data", onData));
3116
- }
3117
- }
3118
- function wireExit(c) {
3119
- c.once("exit", (code, signal) => {
3120
- if (child !== c) return;
3121
- const wasRunning = running;
3122
- const wasExpected = expectingExit;
3123
- running = false;
3124
- expectingExit = false;
3125
- emit({ type: "exit", code, signal });
3126
- if (stopped || wasExpected || !wasRunning) return;
3127
- void handleCrash();
3128
- });
3129
- }
3130
- async function spawnChild() {
3131
- attemptCounter += 1;
3132
- emit({ type: "starting", attempt: attemptCounter });
3133
- const args = buildProxyArgs({
3134
- instanceConnectionName: options.instanceConnectionName,
3135
- host,
3136
- port: options.port
3137
- });
3138
- const c = spawnImpl(options.binaryPath, args, {
3139
- stdio: ["ignore", "pipe", "pipe"],
3140
- env: buildProxyEnv(currentToken.access_token, options.env)
3141
- });
3142
- child = c;
3143
- wireOutput(c);
3144
- wireExit(c);
3145
- try {
3146
- await waitForReady(c, probeImpl, host, options.port, readyTimeoutMs, setTimeoutImpl, () => logRing.slice());
3147
- } catch (error2) {
3148
- expectingExit = true;
3149
- await killChild(c);
3150
- if (child === c) child = void 0;
3151
- throw error2;
3152
- }
3153
- emit({ type: "ready", port: options.port });
3154
- }
3155
- function killChild(c) {
3156
- if (c.exitCode !== null || c.signalCode !== null) return Promise.resolve();
3157
- return new Promise((resolve2) => {
3158
- let settled = false;
3159
- let killTimer;
3160
- const onExit = () => {
3161
- if (settled) return;
3162
- settled = true;
3163
- if (killTimer !== void 0) clearTimeout(killTimer);
3164
- resolve2();
3165
- };
3166
- c.once("exit", onExit);
3167
- c.kill("SIGTERM");
3168
- killTimer = setTimeoutImpl(() => {
3169
- if (!settled) c.kill("SIGKILL");
3170
- }, KILL_GRACE_MS);
3171
- });
3172
- }
3173
- function scheduleRefresh(token) {
3174
- if (stopped || options.refreshToken === void 0) return;
3175
- if (refreshTimer !== void 0) clearTimeout(refreshTimer);
3176
- const delayMs = computeRefreshDelayMs(token, refreshSkewMs, assumedLifetimeMs);
3177
- refreshTimer = setTimeoutImpl(() => {
3178
- refreshTimer = void 0;
3179
- void handleRefreshFire();
3180
- }, delayMs);
3181
- }
3182
- function scheduleRefreshRetry() {
3183
- if (stopped) return;
3184
- const delayMs = REFRESH_RETRY_BACKOFFS_MS2[Math.min(refreshBackoffIndex, REFRESH_RETRY_BACKOFFS_MS2.length - 1)] ?? 12e4;
3185
- refreshBackoffIndex += 1;
3186
- if (refreshTimer !== void 0) clearTimeout(refreshTimer);
3187
- refreshTimer = setTimeoutImpl(() => {
3188
- refreshTimer = void 0;
3189
- void handleRefreshFire();
3190
- }, delayMs);
3191
- }
3192
- async function handleRefreshFire() {
3193
- if (stopped || options.refreshToken === void 0) return;
3194
- let newToken;
3195
- try {
3196
- newToken = await options.refreshToken();
3197
- } catch (error2) {
3198
- emit({ type: "error", message: redact(`Token refresh failed: ${describeError(error2)}`) });
3199
- scheduleRefreshRetry();
3200
- return;
3201
- }
3202
- if (stopped) return;
3203
- trackToken(newToken);
3204
- currentToken = newToken;
3205
- expectingExit = true;
3206
- emit({ type: "restarting", reason: "token-refresh" });
3207
- try {
3208
- const previous = child;
3209
- if (previous !== void 0) await killChild(previous);
3210
- await spawnChild();
3211
- } catch (error2) {
3212
- emit({ type: "error", message: redact(`Restart after token refresh failed: ${describeError(error2)}`) });
3213
- scheduleRefreshRetry();
3214
- return;
3215
- }
3216
- running = true;
3217
- refreshBackoffIndex = 0;
3218
- emit({ type: "token-refreshed", ...newToken.expires_at !== void 0 ? { expiresAt: newToken.expires_at } : {} });
3219
- scheduleRefresh(newToken);
3220
- }
3221
- async function handleCrash() {
3222
- if (stopped) return;
3223
- if (crashRestartsRemaining <= 0) {
3224
- const tail = logRing.slice(-STDERR_TAIL_LINES).join("\n");
3225
- emit({
3226
- type: "error",
3227
- message: `The Cloud SQL Auth Proxy crashed repeatedly and could not be restarted.${tail === "" ? "" : `
3228
- ${tail}`}`
3229
- });
3230
- return;
3231
- }
3232
- const attemptIndex = MAX_CRASH_RESTARTS - crashRestartsRemaining;
3233
- const delayMs = CRASH_BACKOFFS_MS[attemptIndex] ?? (CRASH_BACKOFFS_MS[CRASH_BACKOFFS_MS.length - 1] ?? 3e3);
3234
- crashRestartsRemaining -= 1;
3235
- emit({ type: "restarting", reason: "crash" });
3236
- crashTimer = setTimeoutImpl(() => {
3237
- crashTimer = void 0;
3238
- if (stopped) return;
3239
- spawnChild().then(() => {
3240
- running = true;
3241
- }).catch((error2) => {
3242
- emit({ type: "error", message: redact(describeError(error2)) });
3243
- void handleCrash();
3244
- });
3245
- }, delayMs);
3246
- }
3247
- async function refresh() {
3248
- if (stopped) return;
3249
- if (options.refreshToken === void 0) {
3250
- throw new CliError("Cannot refresh the Cloud SQL Auth Proxy token: no refreshToken() was configured.");
3251
- }
3252
- if (refreshTimer !== void 0) {
3253
- clearTimeout(refreshTimer);
3254
- refreshTimer = void 0;
3255
- }
3256
- refreshBackoffIndex = 0;
3257
- await handleRefreshFire();
3258
- }
3259
- async function stop() {
3260
- if (stopped) return;
3261
- stopped = true;
3262
- if (refreshTimer !== void 0) clearTimeout(refreshTimer);
3263
- refreshTimer = void 0;
3264
- if (crashTimer !== void 0) clearTimeout(crashTimer);
3265
- crashTimer = void 0;
3266
- for (const cleanup of cleanupFns) cleanup();
3267
- cleanupFns = [];
3268
- const current2 = child;
3269
- if (current2 !== void 0) {
3270
- expectingExit = true;
3271
- await killChild(current2);
3272
- }
3273
- running = false;
3274
- child = void 0;
3275
- }
3276
- trackToken(currentToken);
3277
- await spawnChild();
3278
- running = true;
3279
- scheduleRefresh(currentToken);
3280
- return {
3281
- host,
3282
- port: options.port,
3283
- instanceConnectionName: options.instanceConnectionName,
3284
- get running() {
3285
- return running;
3286
- },
3287
- refresh,
3288
- stop
3289
- };
3290
- }
3291
-
3292
2770
  // src/lib/signals.ts
3293
2771
  var SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
3294
2772
  var SIGNAL_EXIT_CODES = {
@@ -3379,8 +2857,6 @@ function defaultSleep(ms) {
3379
2857
  }
3380
2858
  function resolveDeps(overrides) {
3381
2859
  return {
3382
- ensureProxyBinary,
3383
- startProxy,
3384
2860
  issueGatewayCertificate,
3385
2861
  startGatewayForwarder,
3386
2862
  findFreePort,
@@ -3453,15 +2929,6 @@ async function ensureDevDatabaseReady(client, appId, options = {}) {
3453
2929
  { success: (db) => `Developer database "${db.database}" is ready.` }
3454
2930
  );
3455
2931
  }
3456
- function toProxyToken(token) {
3457
- return {
3458
- access_token: token.access_token,
3459
- ...typeof token.expires_at === "string" ? { expires_at: token.expires_at } : {}
3460
- };
3461
- }
3462
- function formatMb(bytes) {
3463
- return (bytes / (1024 * 1024)).toFixed(1);
3464
- }
3465
2932
  function firstNonEmpty(...values) {
3466
2933
  for (const value of values) {
3467
2934
  if (typeof value === "string" && value.trim() !== "") return value;
@@ -3483,32 +2950,6 @@ function logEnvPatch(result, databaseUrl, wroteShadow) {
3483
2950
  }
3484
2951
  if (result.backedUp) detail(`Backed up the previous file to ${result.path}.backup`);
3485
2952
  }
3486
- function handleProxyEvent(event) {
3487
- switch (event.type) {
3488
- case "starting":
3489
- debug(`Starting the Cloud SQL Auth Proxy (attempt ${event.attempt}).`);
3490
- return;
3491
- case "ready":
3492
- debug(`Cloud SQL Auth Proxy listening on port ${event.port}.`);
3493
- return;
3494
- case "log":
3495
- debug(event.line);
3496
- return;
3497
- case "token-refreshed":
3498
- step("Refreshed the Cloud SQL Auth Proxy token.");
3499
- return;
3500
- case "restarting":
3501
- if (event.reason === "crash") warn("The Cloud SQL Auth Proxy crashed; restarting it.");
3502
- else debug("Restarting the Cloud SQL Auth Proxy with a refreshed token...");
3503
- return;
3504
- case "exit":
3505
- debug(`Cloud SQL Auth Proxy exited (code ${event.code ?? "null"}, signal ${event.signal ?? "null"}).`);
3506
- return;
3507
- case "error":
3508
- warn(`Cloud SQL Auth Proxy error: ${event.message} Restart \`meridian dev\` if the database becomes unreachable.`);
3509
- return;
3510
- }
3511
- }
3512
2953
  async function pickLocalPort(options, deps) {
3513
2954
  const findPortOptions = { start: DEFAULT_PORT_START };
3514
2955
  if (options.port !== void 0) findPortOptions.preferred = options.port;
@@ -3520,50 +2961,6 @@ async function pickLocalPort(options, deps) {
3520
2961
  }
3521
2962
  return port;
3522
2963
  }
3523
- async function startProxyEndpoint(client, appId, devDatabase, credentials, options, deps) {
3524
- const proxyToken = await withSpinner(
3525
- "Requesting a Cloud SQL Auth Proxy token...",
3526
- () => client.issueProxyToken(appId),
3527
- { success: "Issued a Cloud SQL Auth Proxy token." }
3528
- );
3529
- const binaryPath = await withSpinner(
3530
- "Preparing the Cloud SQL Auth Proxy...",
3531
- (spinner2) => {
3532
- const ensureOptions = {
3533
- onProgress: (received, total) => {
3534
- spinner2.update(
3535
- total !== void 0 ? `Downloading the Cloud SQL Auth Proxy... (${formatMb(received)}/${formatMb(total)} MB)` : `Downloading the Cloud SQL Auth Proxy... (${formatMb(received)} MB)`
3536
- );
3537
- }
3538
- };
3539
- return deps.ensureProxyBinary(ensureOptions);
3540
- },
3541
- { success: "Cloud SQL Auth Proxy is ready." }
3542
- );
3543
- const port = await pickLocalPort(options, deps);
3544
- const instanceConnectionName = firstNonEmpty(
3545
- credentials.instance_connection_name,
3546
- proxyToken.instance_connection_name,
3547
- devDatabase.instance_connection_name
3548
- );
3549
- if (instanceConnectionName === void 0) {
3550
- throw new CliError("The Meridian API did not return a Cloud SQL instance connection name.", {
3551
- hint: "Try again, or contact Meridian support if this persists."
3552
- });
3553
- }
3554
- return withSpinner(
3555
- `Starting the Cloud SQL Auth Proxy on 127.0.0.1:${port}...`,
3556
- () => deps.startProxy({
3557
- binaryPath,
3558
- instanceConnectionName,
3559
- token: toProxyToken(proxyToken),
3560
- port,
3561
- ...options.refreshToken !== false ? { refreshToken: async () => toProxyToken(await client.issueProxyToken(appId)) } : {},
3562
- onEvent: handleProxyEvent
3563
- }),
3564
- { success: `Cloud SQL Auth Proxy listening on 127.0.0.1:${port}.` }
3565
- );
3566
- }
3567
2964
  function handleForwarderEvent(event) {
3568
2965
  switch (event.type) {
3569
2966
  case "ready":
@@ -3616,8 +3013,8 @@ async function startDevSession(options = {}) {
3616
3013
  success: (creds) => `Fetched credentials for database "${creds.database}"${typeof creds.expires_at === "string" ? ` (expire ${creds.expires_at})` : ""}.`
3617
3014
  }
3618
3015
  );
3619
- const access = devDatabase.access === "gateway" ? "gateway" : "proxy";
3620
- proxy = access === "gateway" ? await startGatewayEndpoint(client, appId, credentials, options, deps) : await startProxyEndpoint(client, appId, devDatabase, credentials, options, deps);
3016
+ const access = "gateway";
3017
+ proxy = await startGatewayEndpoint(client, appId, credentials, options, deps);
3621
3018
  const connection = connectionInfoFromCredentials(credentials, { host: proxy.host, port: proxy.port });
3622
3019
  if (options.waitForDb !== false) {
3623
3020
  await withSpinner(
@@ -3918,7 +3315,7 @@ function registerDbCredentialCommand(db) {
3918
3315
 
3919
3316
  // src/commands/db-shell-repl.ts
3920
3317
  import { accessSync, constants as fsConstants } from "fs";
3921
- import { delimiter as delimiter2, join as join5 } from "path";
3318
+ import { delimiter as delimiter2, join as join4 } from "path";
3922
3319
  import { createInterface } from "readline";
3923
3320
  function candidateNames(name) {
3924
3321
  if (process.platform !== "win32") return [name];
@@ -3933,7 +3330,7 @@ function findExecutable(name) {
3933
3330
  const candidates = candidateNames(name);
3934
3331
  for (const dir of dirs) {
3935
3332
  for (const candidate of candidates) {
3936
- const fullPath = join5(dir, candidate);
3333
+ const fullPath = join4(dir, candidate);
3937
3334
  try {
3938
3335
  accessSync(fullPath, fsConstants.X_OK);
3939
3336
  return fullPath;
@@ -4068,7 +3465,7 @@ function defaultDbDeps() {
4068
3465
  function hasSeedConfigured(root) {
4069
3466
  let parsed;
4070
3467
  try {
4071
- parsed = JSON.parse(readFileSync7(join6(root, "package.json"), "utf8"));
3468
+ parsed = JSON.parse(readFileSync6(join5(root, "package.json"), "utf8"));
4072
3469
  } catch {
4073
3470
  return false;
4074
3471
  }
@@ -4247,7 +3644,7 @@ function registerDbCommand(program) {
4247
3644
  db.action(() => {
4248
3645
  db.outputHelp();
4249
3646
  });
4250
- db.command("reset").description("Drop and recreate every table in the developer database (keeps the MySQL user).").option("--migrate", "run `npx prisma migrate deploy` afterwards, without asking").option("--seed", "run `npx prisma db seed` afterwards, without asking").option("--port <port>", "local port for the Cloud SQL Auth Proxy", (value) => parsePortOption(value)).option("--env-file <file>", "project .env file to update (defaults to .env)").action(async (opts) => {
3647
+ db.command("reset").description("Drop and recreate every table in the developer database (keeps the MySQL user).").option("--migrate", "run `npx prisma migrate deploy` afterwards, without asking").option("--seed", "run `npx prisma db seed` afterwards, without asking").option("--port <port>", "local port for the database gateway forwarder", (value) => parsePortOption(value)).option("--env-file <file>", "project .env file to update (defaults to .env)").action(async (opts) => {
4251
3648
  await runAndSetExitCode2(
4252
3649
  () => runDbReset({
4253
3650
  ...opts.migrate === true ? { migrate: true } : {},
@@ -4257,7 +3654,7 @@ function registerDbCommand(program) {
4257
3654
  })
4258
3655
  );
4259
3656
  });
4260
- db.command("seed").description("Run `npx prisma db seed` with the Cloud SQL Auth Proxy up and DATABASE_URL set.").option("--port <port>", "local port for the Cloud SQL Auth Proxy", (value) => parsePortOption(value)).option("--env-file <file>", "project .env file to update (defaults to .env)").passThroughOptions().argument("[args...]", "extra arguments forwarded to `npx prisma db seed` (put them after `--`)").action(async (args, opts) => {
3657
+ db.command("seed").description("Run `npx prisma db seed` with the database connection up and DATABASE_URL set.").option("--port <port>", "local port for the database gateway forwarder", (value) => parsePortOption(value)).option("--env-file <file>", "project .env file to update (defaults to .env)").passThroughOptions().argument("[args...]", "extra arguments forwarded to `npx prisma db seed` (put them after `--`)").action(async (args, opts) => {
4261
3658
  await runAndSetExitCode2(
4262
3659
  () => runDbSeed({
4263
3660
  ...opts.port !== void 0 ? { port: opts.port } : {},
@@ -4266,7 +3663,7 @@ function registerDbCommand(program) {
4266
3663
  })
4267
3664
  );
4268
3665
  });
4269
- db.command("shell").description("Open a SQL shell against the developer database (the `mysql` client if available, otherwise a built-in REPL).").option("--port <port>", "local port for the Cloud SQL Auth Proxy", (value) => parsePortOption(value)).option("--env-file <file>", "project .env file to read/patch (defaults to .env)").option("--repl", "force the built-in SQL REPL even when `mysql` is on PATH").option("-e, --execute <sql>", "run one SQL statement and exit").option("--no-env", "do not patch DATABASE_URL into the project's .env file").action(async (opts) => {
3666
+ db.command("shell").description("Open a SQL shell against the developer database (the `mysql` client if available, otherwise a built-in REPL).").option("--port <port>", "local port for the database gateway forwarder", (value) => parsePortOption(value)).option("--env-file <file>", "project .env file to read/patch (defaults to .env)").option("--repl", "force the built-in SQL REPL even when `mysql` is on PATH").option("-e, --execute <sql>", "run one SQL statement and exit").option("--no-env", "do not patch DATABASE_URL into the project's .env file").action(async (opts) => {
4270
3667
  await runAndSetExitCode2(
4271
3668
  () => runDbShell({
4272
3669
  ...opts.port !== void 0 ? { port: opts.port } : {},
@@ -4405,7 +3802,7 @@ async function runDev(options = {}) {
4405
3802
  }
4406
3803
  }
4407
3804
  function registerDevCommand(program) {
4408
- program.command("dev").description("Run the managed developer database proxy and your app's dev server together.").option("--port <port>", "preferred local port for the Cloud SQL Auth Proxy").option("--env-file <file>", "the .env file to patch, relative to the project root (default .env)").option("--no-env", "don't touch any .env file; just print the DATABASE_URL for manual use").argument("[command...]", "run this instead of the app's dev script, e.g. `meridian dev -- next dev`").passThroughOptions().action(async (command, opts) => {
3805
+ program.command("dev").description("Run the managed developer database proxy and your app's dev server together.").option("--port <port>", "preferred local port for the database gateway forwarder").option("--env-file <file>", "the .env file to patch, relative to the project root (default .env)").option("--no-env", "don't touch any .env file; just print the DATABASE_URL for manual use").argument("[command...]", "run this instead of the app's dev script, e.g. `meridian dev -- next dev`").passThroughOptions().action(async (command, opts) => {
4409
3806
  const port = parsePortOption(opts.port);
4410
3807
  const code = await runDev({
4411
3808
  ...command.length > 0 ? { command } : {},
@@ -4418,8 +3815,8 @@ function registerDevCommand(program) {
4418
3815
  }
4419
3816
 
4420
3817
  // src/commands/link.ts
4421
- import { existsSync as existsSync5 } from "fs";
4422
- import { join as join7 } from "path";
3818
+ import { existsSync as existsSync4 } from "fs";
3819
+ import { join as join6 } from "path";
4423
3820
  function defaultDeps3() {
4424
3821
  return { createClient: createApiClient };
4425
3822
  }
@@ -4438,7 +3835,7 @@ async function runLink(options = {}) {
4438
3835
  const deps = { ...defaultDeps3(), ...options.deps };
4439
3836
  const json = isJsonOutput();
4440
3837
  const root = resolveProjectRoot(workingDirectory()) ?? workingDirectory();
4441
- if (!existsSync5(join7(root, "package.json"))) {
3838
+ if (!existsSync4(join6(root, "package.json"))) {
4442
3839
  log(`No package.json found in ${style.path(root)}.`);
4443
3840
  const proceed = await confirmAction({
4444
3841
  message: "Link this directory to a Meridian app anyway?",
@@ -4752,7 +4149,7 @@ function registerLogoutCommand(program) {
4752
4149
 
4753
4150
  // src/lib/db/snapshot.ts
4754
4151
  import { mkdir, readFile, readdir, rm, stat } from "fs/promises";
4755
- import { join as join8 } from "path";
4152
+ import { join as join7 } from "path";
4756
4153
  function snapshotFileName(date = /* @__PURE__ */ new Date()) {
4757
4154
  return `${date.toISOString().replace(/[:.]/g, "-")}.sql`;
4758
4155
  }
@@ -4771,7 +4168,7 @@ async function createSnapshot(info, options) {
4771
4168
  }
4772
4169
  const dump = options.dumpImpl ?? await loadMysqldump();
4773
4170
  await mkdir(options.dir, { recursive: true });
4774
- const path = join8(options.dir, snapshotFileName(createdAt));
4171
+ const path = join7(options.dir, snapshotFileName(createdAt));
4775
4172
  try {
4776
4173
  await dump({
4777
4174
  connection: {
@@ -4977,7 +4374,7 @@ async function listSnapshots(dir) {
4977
4374
  const names = entries.filter((name) => name.endsWith(".sql")).sort().reverse();
4978
4375
  const results = [];
4979
4376
  for (const name of names) {
4980
- const path = join8(dir, name);
4377
+ const path = join7(dir, name);
4981
4378
  const { size } = await stat(path);
4982
4379
  results.push({ path, name, bytes: size, createdAt: parseSnapshotDate(name) });
4983
4380
  }
@@ -5173,7 +4570,7 @@ function registerMigrateCommand(program) {
5173
4570
  ).option("--skip-snapshot", "run without a pre-migration snapshot (no rollback is possible)").option(
5174
4571
  "--allow-foreign-migrations",
5175
4572
  "run even when prisma/migrations was generated for SQLite or Postgres and cannot apply to MySQL"
5176
- ).option("--no-rollback", "keep a snapshot but do not automatically restore it after a failed migration").option("--keep-snapshots <n>", "number of recent snapshots to keep after a successful migration", "5").option("--port <port>", "preferred local port for the Cloud SQL Auth Proxy").option("--env-file <file>", "path (relative to the project root) of the .env file to patch with DATABASE_URL").argument("[prismaArgs...]", "extra arguments forwarded to `prisma migrate dev` verbatim, after --").passThroughOptions().action(async (prismaArgs, opts) => {
4573
+ ).option("--no-rollback", "keep a snapshot but do not automatically restore it after a failed migration").option("--keep-snapshots <n>", "number of recent snapshots to keep after a successful migration", "5").option("--port <port>", "preferred local port for the database gateway forwarder").option("--env-file <file>", "path (relative to the project root) of the .env file to patch with DATABASE_URL").argument("[prismaArgs...]", "extra arguments forwarded to `prisma migrate dev` verbatim, after --").passThroughOptions().action(async (prismaArgs, opts) => {
5177
4574
  const keepSnapshots = parseIntegerOption(opts.keepSnapshots, "--keep-snapshots", { min: 0 });
5178
4575
  const port = parsePortOption(opts.port);
5179
4576
  const exitCode = await runMigrate({