@prisma/cli 8.0.0-rc.2-dev.49 → 8.0.0-rc.2-dev.50
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/cli.js +102 -30
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import process$1 from "node:process";
|
|
4
|
-
import { SERVICE_TOKEN_ENV_VAR, claimedExpiresAt, claimedIdentity, createCli, credentialWorkspaceId, credentialWorkspaceMismatchError, credentialsRequiredError, defineCommand, defineCommandFamily, defineSessionCommand, emptyServiceTokenError, flag, loadConfig, noSessionForWorkspaceError, positional, telemetryCommandGroup } from "@prisma/cli-engine";
|
|
4
|
+
import { SERVICE_TOKEN_ENV_VAR, claimedExpiresAt, claimedIdentity, createCli, credentialWorkspaceId, credentialWorkspaceMismatchError, credentialsRequiredError, defineCommand, defineCommandFamily, defineSessionCommand, emptyServiceTokenError, flag, loadConfig, noSessionForWorkspaceError, positional, readActiveAccessToken, telemetryCommandGroup } from "@prisma/cli-engine";
|
|
5
5
|
import { createComposerFamily } from "@prisma/composer/family";
|
|
6
6
|
import { ormCommandFamily } from "@prisma/orm-toolchain/cli";
|
|
7
7
|
import { CliStructuredError, notOk, ok } from "@prisma/cli-engine/protocol";
|
|
@@ -848,9 +848,9 @@ function sameWorkspaceId(left, right) {
|
|
|
848
848
|
}
|
|
849
849
|
//#endregion
|
|
850
850
|
//#region src/auth/token-storage.ts
|
|
851
|
-
const REFRESH_LOCK_RETRY_MS = 100;
|
|
852
|
-
const REFRESH_LOCK_STALE_MS = 3e4;
|
|
853
|
-
const REFRESH_LOCK_WAIT_TIMEOUT_MS = 25e3;
|
|
851
|
+
const REFRESH_LOCK_RETRY_MS$1 = 100;
|
|
852
|
+
const REFRESH_LOCK_STALE_MS$1 = 3e4;
|
|
853
|
+
const REFRESH_LOCK_WAIT_TIMEOUT_MS$1 = 25e3;
|
|
854
854
|
const EMPTY_AUTH_CONTEXT = {
|
|
855
855
|
activeWorkspaceId: null,
|
|
856
856
|
workspaces: {}
|
|
@@ -1097,8 +1097,8 @@ var FileTokenStorage = class {
|
|
|
1097
1097
|
async acquireRefreshLock() {
|
|
1098
1098
|
const lockId = randomUUID();
|
|
1099
1099
|
const startedAt = Date.now();
|
|
1100
|
-
const retryMs = this.options.lockRetryMs ?? REFRESH_LOCK_RETRY_MS;
|
|
1101
|
-
const waitTimeoutMs = this.options.lockWaitTimeoutMs ?? REFRESH_LOCK_WAIT_TIMEOUT_MS;
|
|
1100
|
+
const retryMs = this.options.lockRetryMs ?? REFRESH_LOCK_RETRY_MS$1;
|
|
1101
|
+
const waitTimeoutMs = this.options.lockWaitTimeoutMs ?? REFRESH_LOCK_WAIT_TIMEOUT_MS$1;
|
|
1102
1102
|
this.signal?.throwIfAborted();
|
|
1103
1103
|
await fs.mkdir(path.dirname(this.lockFilePath), { recursive: true });
|
|
1104
1104
|
while (true) {
|
|
@@ -1152,7 +1152,7 @@ var FileTokenStorage = class {
|
|
|
1152
1152
|
const stats = await fs.stat(this.lockFilePath).catch(() => null);
|
|
1153
1153
|
this.signal?.throwIfAborted();
|
|
1154
1154
|
if (!stats) return null;
|
|
1155
|
-
const staleMs = this.options.lockStaleMs ?? REFRESH_LOCK_STALE_MS;
|
|
1155
|
+
const staleMs = this.options.lockStaleMs ?? REFRESH_LOCK_STALE_MS$1;
|
|
1156
1156
|
return Date.now() - stats.mtimeMs > staleMs ? lockId : null;
|
|
1157
1157
|
}
|
|
1158
1158
|
async releaseRefreshLock(lockId) {
|
|
@@ -14315,6 +14315,19 @@ const FILE_MODE = 384;
|
|
|
14315
14315
|
const LOCK_STALE_MS = 5e3;
|
|
14316
14316
|
const LOCK_RETRY_MS = 10;
|
|
14317
14317
|
const LOCK_WAIT_TIMEOUT_MS = 1e4;
|
|
14318
|
+
const REFRESH_LOCK_STALE_MS = 3e4;
|
|
14319
|
+
const REFRESH_LOCK_RETRY_MS = 100;
|
|
14320
|
+
const REFRESH_LOCK_WAIT_TIMEOUT_MS = 3e4;
|
|
14321
|
+
const STATE_LOCK_TIMINGS = {
|
|
14322
|
+
staleMs: LOCK_STALE_MS,
|
|
14323
|
+
retryMs: LOCK_RETRY_MS,
|
|
14324
|
+
waitTimeoutMs: LOCK_WAIT_TIMEOUT_MS
|
|
14325
|
+
};
|
|
14326
|
+
const REFRESH_LOCK_TIMINGS = {
|
|
14327
|
+
staleMs: REFRESH_LOCK_STALE_MS,
|
|
14328
|
+
retryMs: REFRESH_LOCK_RETRY_MS,
|
|
14329
|
+
waitTimeoutMs: REFRESH_LOCK_WAIT_TIMEOUT_MS
|
|
14330
|
+
};
|
|
14318
14331
|
const EMPTY_STATE = {
|
|
14319
14332
|
version: 1,
|
|
14320
14333
|
sessions: [],
|
|
@@ -14417,9 +14430,9 @@ async function writeCredentialState(filePath, state) {
|
|
|
14417
14430
|
await fs.chmod(filePath, FILE_MODE).catch(() => {});
|
|
14418
14431
|
}
|
|
14419
14432
|
var StateLockTimeoutError = class extends CliStructuredError {
|
|
14420
|
-
constructor(lockPath) {
|
|
14433
|
+
constructor(lockPath, waitTimeoutMs) {
|
|
14421
14434
|
super("CLI.CREDENTIALS_LOCKED", "Another prisma process is still updating your credentials.", {
|
|
14422
|
-
why: `The credentials lock at ${lockPath} was held for longer than ${
|
|
14435
|
+
why: `The credentials lock at ${lockPath} was held for longer than ${waitTimeoutMs}ms.`,
|
|
14423
14436
|
nextActions: [{
|
|
14424
14437
|
kind: "user-choice",
|
|
14425
14438
|
label: "Wait for the other command to finish, then try again."
|
|
@@ -14435,8 +14448,20 @@ var StateLockTimeoutError = class extends CliStructuredError {
|
|
|
14435
14448
|
* small fixed staleness threshold.
|
|
14436
14449
|
*/
|
|
14437
14450
|
async function withStateLock(filePath, debug, run) {
|
|
14438
|
-
|
|
14439
|
-
|
|
14451
|
+
return withFileLock(`${filePath}.lock`, debug, STATE_LOCK_TIMINGS, run);
|
|
14452
|
+
}
|
|
14453
|
+
/**
|
|
14454
|
+
* The cross-process lock the delegated refresh holds for its whole
|
|
14455
|
+
* read → exchange → write sequence, so two processes never spend the
|
|
14456
|
+
* same refresh token. Distinct from the state lock: it IS held across
|
|
14457
|
+
* network I/O, so its staleness and wait budgets are larger, and it
|
|
14458
|
+
* uses its own lock path so short mutations are not queued behind it.
|
|
14459
|
+
*/
|
|
14460
|
+
async function withRefreshFileLock(filePath, debug, run) {
|
|
14461
|
+
return withFileLock(`${filePath}.refresh-lock`, debug, REFRESH_LOCK_TIMINGS, run);
|
|
14462
|
+
}
|
|
14463
|
+
async function withFileLock(lockPath, debug, timings, run) {
|
|
14464
|
+
const lockId = await acquireStateLock(lockPath, debug, timings);
|
|
14440
14465
|
debug(`lock acquired ${lockPath}`);
|
|
14441
14466
|
try {
|
|
14442
14467
|
return await run();
|
|
@@ -14445,15 +14470,15 @@ async function withStateLock(filePath, debug, run) {
|
|
|
14445
14470
|
debug(`lock released ${lockPath}`);
|
|
14446
14471
|
}
|
|
14447
14472
|
}
|
|
14448
|
-
async function acquireStateLock(lockPath, debug) {
|
|
14473
|
+
async function acquireStateLock(lockPath, debug, timings) {
|
|
14449
14474
|
const lockId = randomUUID();
|
|
14450
14475
|
const startedAt = Date.now();
|
|
14451
14476
|
await fs.mkdir(path.dirname(lockPath), { recursive: true });
|
|
14452
14477
|
while (true) {
|
|
14453
14478
|
if (await tryCreateStateLock(lockPath, lockId)) return lockId;
|
|
14454
|
-
const tookOver = await takeOverStaleStateLock(lockPath, debug);
|
|
14455
|
-
if (Date.now() - startedAt >=
|
|
14456
|
-
if (!tookOver) await new Promise((resolve) => setTimeout(resolve,
|
|
14479
|
+
const tookOver = await takeOverStaleStateLock(lockPath, debug, timings);
|
|
14480
|
+
if (Date.now() - startedAt >= timings.waitTimeoutMs) throw new StateLockTimeoutError(lockPath, timings.waitTimeoutMs);
|
|
14481
|
+
if (!tookOver) await new Promise((resolve) => setTimeout(resolve, timings.retryMs));
|
|
14457
14482
|
}
|
|
14458
14483
|
}
|
|
14459
14484
|
async function tryCreateStateLock(lockPath, lockId) {
|
|
@@ -14479,10 +14504,10 @@ async function tryCreateStateLock(lockPath, lockId) {
|
|
|
14479
14504
|
* then run their read-modify-write at once and one update is lost,
|
|
14480
14505
|
* which is the very thing the lock exists to prevent.
|
|
14481
14506
|
*/
|
|
14482
|
-
async function takeOverStaleStateLock(lockPath, debug) {
|
|
14507
|
+
async function takeOverStaleStateLock(lockPath, debug, timings) {
|
|
14483
14508
|
const stale = await fs.stat(lockPath).catch(() => null);
|
|
14484
14509
|
if (stale === null) return true;
|
|
14485
|
-
if (Date.now() - stale.mtimeMs <=
|
|
14510
|
+
if (Date.now() - stale.mtimeMs <= timings.staleMs) return false;
|
|
14486
14511
|
const takenPath = `${lockPath}.${randomUUID()}.stale`;
|
|
14487
14512
|
try {
|
|
14488
14513
|
await fs.rename(lockPath, takenPath);
|
|
@@ -14520,12 +14545,16 @@ function memoryBackedStorage(credential, withRefreshLock) {
|
|
|
14520
14545
|
let tokens = {
|
|
14521
14546
|
workspaceId: credentialWorkspaceId(credential.token) ?? NO_WORKSPACE_CLAIMED,
|
|
14522
14547
|
accessToken: credential.token,
|
|
14523
|
-
refreshToken: credential.refreshToken
|
|
14548
|
+
refreshToken: credential.refreshToken,
|
|
14549
|
+
expiresAt: claimedExpiresAt(credential.token) ?? credential.expiresAt
|
|
14524
14550
|
};
|
|
14525
14551
|
return {
|
|
14526
14552
|
getTokens: async () => tokens,
|
|
14527
|
-
setTokens: async (rotated) => {
|
|
14528
|
-
tokens =
|
|
14553
|
+
setTokens: async (rotated, expiresAt) => {
|
|
14554
|
+
tokens = {
|
|
14555
|
+
...rotated,
|
|
14556
|
+
expiresAt: claimedExpiresAt(rotated.accessToken) ?? expiresAt ?? tokens?.expiresAt
|
|
14557
|
+
};
|
|
14529
14558
|
},
|
|
14530
14559
|
clearTokens: async () => {
|
|
14531
14560
|
tokens = null;
|
|
@@ -14544,6 +14573,7 @@ var FileCredentialManager = class {
|
|
|
14544
14573
|
#filePath;
|
|
14545
14574
|
#debug;
|
|
14546
14575
|
#fetchWorkspaceName;
|
|
14576
|
+
#refreshCredential;
|
|
14547
14577
|
#pin = { kind: "unresolved" };
|
|
14548
14578
|
/** Built for one pinned credential. Every mutation that moves the
|
|
14549
14579
|
* pin discards it, so a command that mutates and then reaches for
|
|
@@ -14556,6 +14586,7 @@ var FileCredentialManager = class {
|
|
|
14556
14586
|
this.#filePath = resolveStateFilePath(options.env).filePath;
|
|
14557
14587
|
this.#debug = makeDebugLog(options.env, options.debugWrite);
|
|
14558
14588
|
this.#fetchWorkspaceName = options.fetchWorkspaceName;
|
|
14589
|
+
this.#refreshCredential = options.refreshCredential;
|
|
14559
14590
|
this.#debug(`state file ${this.#filePath}`);
|
|
14560
14591
|
}
|
|
14561
14592
|
get stateFilePath() {
|
|
@@ -14666,14 +14697,13 @@ var FileCredentialManager = class {
|
|
|
14666
14697
|
this.#activeStorage ??= this.#buildActiveStorage();
|
|
14667
14698
|
return this.#activeStorage;
|
|
14668
14699
|
}
|
|
14669
|
-
/** The
|
|
14700
|
+
/** The delegated path's read: the active credential's access token,
|
|
14670
14701
|
* fresh on every call, never the refresh token. Null when there is
|
|
14671
14702
|
* no active credential to read — storage exists only once
|
|
14672
14703
|
* activeCredential() has returned non-null. */
|
|
14673
|
-
async activeAccessToken() {
|
|
14704
|
+
async activeAccessToken(options) {
|
|
14674
14705
|
if (await this.activeCredential() === null) return null;
|
|
14675
|
-
|
|
14676
|
-
return tokens === null ? null : tokens.accessToken;
|
|
14706
|
+
return readActiveAccessToken(await this.activeCredentialStorage(), this.#refreshCredential, options);
|
|
14677
14707
|
}
|
|
14678
14708
|
/** §11.2: which storage is chosen once, when the pin resolves. Each
|
|
14679
14709
|
* has exactly one source of truth — the file, or process memory. */
|
|
@@ -14701,10 +14731,11 @@ var FileCredentialManager = class {
|
|
|
14701
14731
|
return {
|
|
14702
14732
|
workspaceId,
|
|
14703
14733
|
accessToken: record.token,
|
|
14704
|
-
...record.refreshToken === void 0 ? {} : { refreshToken: record.refreshToken }
|
|
14734
|
+
...record.refreshToken === void 0 ? {} : { refreshToken: record.refreshToken },
|
|
14735
|
+
...record.expiresAt === void 0 ? {} : { expiresAt: new Date(record.expiresAt) }
|
|
14705
14736
|
};
|
|
14706
14737
|
},
|
|
14707
|
-
setTokens: async (tokens) => {
|
|
14738
|
+
setTokens: async (tokens, expiresAt) => {
|
|
14708
14739
|
this.#debug(`rotation write for session ${workspaceId}`);
|
|
14709
14740
|
const claimed = credentialWorkspaceId(tokens.accessToken);
|
|
14710
14741
|
if (claimed !== void 0 && claimed !== workspaceId) throw credentialWorkspaceMismatchError(workspaceId);
|
|
@@ -14716,7 +14747,7 @@ var FileCredentialManager = class {
|
|
|
14716
14747
|
...record.name === void 0 ? {} : { name: record.name },
|
|
14717
14748
|
token: tokens.accessToken,
|
|
14718
14749
|
...tokens.refreshToken === void 0 ? {} : { refreshToken: tokens.refreshToken },
|
|
14719
|
-
...expiresAtSlice(tokens.accessToken, void 0)
|
|
14750
|
+
...expiresAtSlice(tokens.accessToken, expiresAt ?? (record.expiresAt === void 0 ? void 0 : new Date(record.expiresAt)))
|
|
14720
14751
|
};
|
|
14721
14752
|
return {
|
|
14722
14753
|
state: {
|
|
@@ -14745,7 +14776,7 @@ var FileCredentialManager = class {
|
|
|
14745
14776
|
};
|
|
14746
14777
|
});
|
|
14747
14778
|
},
|
|
14748
|
-
withRefreshLock: (fn) => this.#withRefreshLock(fn)
|
|
14779
|
+
withRefreshLock: (fn) => this.#withRefreshLock(() => withRefreshFileLock(this.#filePath, this.#debug, fn))
|
|
14749
14780
|
};
|
|
14750
14781
|
}
|
|
14751
14782
|
#withRefreshLock(fn) {
|
|
@@ -14881,6 +14912,45 @@ function environmentCredential(token) {
|
|
|
14881
14912
|
};
|
|
14882
14913
|
}
|
|
14883
14914
|
//#endregion
|
|
14915
|
+
//#region src/auth/refresh.ts
|
|
14916
|
+
const TRAILING_SLASH = /\/$/;
|
|
14917
|
+
const CREDENTIAL_REFRESH_TIMEOUT_MS = 1e4;
|
|
14918
|
+
/** The dumb HTTP adapter behind the engine's delegated-credential policy. */
|
|
14919
|
+
function makeCredentialRefresher(authBaseUrl) {
|
|
14920
|
+
const endpoint = `${authBaseUrl.replace(TRAILING_SLASH, "")}/token`;
|
|
14921
|
+
return async ({ refreshToken, signal }) => {
|
|
14922
|
+
signal.throwIfAborted();
|
|
14923
|
+
const refreshSignal = AbortSignal.any([signal, AbortSignal.timeout(CREDENTIAL_REFRESH_TIMEOUT_MS)]);
|
|
14924
|
+
const response = await fetch(endpoint, {
|
|
14925
|
+
method: "POST",
|
|
14926
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
14927
|
+
body: new URLSearchParams({
|
|
14928
|
+
grant_type: "refresh_token",
|
|
14929
|
+
refresh_token: refreshToken,
|
|
14930
|
+
client_id: CLIENT_ID
|
|
14931
|
+
}),
|
|
14932
|
+
signal: refreshSignal
|
|
14933
|
+
});
|
|
14934
|
+
const body = await readBody(response);
|
|
14935
|
+
if (response.status >= 400 && response.status < 500 && body?.error === "invalid_grant") return { kind: "invalid" };
|
|
14936
|
+
if (!response.ok || typeof body?.access_token !== "string" || typeof body.refresh_token !== "string" || typeof body.expires_in !== "number" || !Number.isFinite(body.expires_in) || body.expires_in < 0) throw new Error(`OAuth token refresh failed (status ${String(response.status)})`);
|
|
14937
|
+
return {
|
|
14938
|
+
kind: "success",
|
|
14939
|
+
accessToken: body.access_token,
|
|
14940
|
+
refreshToken: body.refresh_token,
|
|
14941
|
+
expiresAt: new Date(Date.now() + body.expires_in * 1e3)
|
|
14942
|
+
};
|
|
14943
|
+
};
|
|
14944
|
+
}
|
|
14945
|
+
async function readBody(response) {
|
|
14946
|
+
try {
|
|
14947
|
+
const body = await response.json();
|
|
14948
|
+
return typeof body === "object" && body !== null ? body : null;
|
|
14949
|
+
} catch {
|
|
14950
|
+
return null;
|
|
14951
|
+
}
|
|
14952
|
+
}
|
|
14953
|
+
//#endregion
|
|
14884
14954
|
//#region src/auth/workspace-name.ts
|
|
14885
14955
|
/** The manager's injected name lookup: a static-token client over the
|
|
14886
14956
|
* credential just minted. The manager constructs no API client and
|
|
@@ -15092,6 +15162,7 @@ async function assembleRuntime(proc) {
|
|
|
15092
15162
|
};
|
|
15093
15163
|
warnOnDeprecatedStateFileEnvVar(proc);
|
|
15094
15164
|
const apiBaseUrl = getApiBaseUrl(proc.env);
|
|
15165
|
+
const authBaseUrl = getAuthBaseUrl(proc.env);
|
|
15095
15166
|
return {
|
|
15096
15167
|
stdout: { write: (text) => {
|
|
15097
15168
|
proc.stdout.write(text);
|
|
@@ -15118,13 +15189,14 @@ async function assembleRuntime(proc) {
|
|
|
15118
15189
|
loadConfig: (configPath) => loadConfig(proc.cwd(), configPath),
|
|
15119
15190
|
credentialManager: new FileCredentialManager({
|
|
15120
15191
|
env: proc.env,
|
|
15121
|
-
fetchWorkspaceName: fetchWorkspaceName(apiBaseUrl)
|
|
15192
|
+
fetchWorkspaceName: fetchWorkspaceName(apiBaseUrl),
|
|
15193
|
+
refreshCredential: makeCredentialRefresher(authBaseUrl)
|
|
15122
15194
|
}),
|
|
15123
15195
|
managementApiClientConfig: {
|
|
15124
15196
|
clientId: CLIENT_ID,
|
|
15125
15197
|
redirectUri: DEFAULT_REDIRECT_URI,
|
|
15126
15198
|
apiBaseUrl,
|
|
15127
|
-
authBaseUrl
|
|
15199
|
+
authBaseUrl
|
|
15128
15200
|
},
|
|
15129
15201
|
spawn: spawnChild,
|
|
15130
15202
|
/** The engine has already decided and composed; the bin only forks
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma/cli",
|
|
3
|
-
"version": "8.0.0-rc.2-dev.
|
|
3
|
+
"version": "8.0.0-rc.2-dev.50",
|
|
4
4
|
"description": "Command-line interface for the Prisma Developer Platform.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
"open": "^11.0.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@repo/cli-conformance": "8.0.0-rc.2-dev.
|
|
55
|
-
"@repo/cli-telemetry": "8.0.0-rc.2-dev.
|
|
56
|
-
"@repo/tsconfig": "8.0.0-rc.2-dev.
|
|
54
|
+
"@repo/cli-conformance": "8.0.0-rc.2-dev.50",
|
|
55
|
+
"@repo/cli-telemetry": "8.0.0-rc.2-dev.50",
|
|
56
|
+
"@repo/tsconfig": "8.0.0-rc.2-dev.50",
|
|
57
57
|
"@types/node": "^22.19.19",
|
|
58
58
|
"tsdown": "^0.21.10",
|
|
59
59
|
"tsx": "^4.22.4",
|