@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/CHANGELOG.md +6 -0
- package/README.md +8 -17
- package/dist/index.js +25 -628
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
10
|
-
import { join as
|
|
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
|
|
85
|
-
* gateway with a per-developer certificate)
|
|
86
|
-
*
|
|
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
|
|
751
|
-
const connection = await
|
|
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
|
|
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
|
|
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 =
|
|
3620
|
-
proxy =
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
4422
|
-
import { join as
|
|
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 (!
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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({
|