@valbuild/server 0.113.0 → 0.115.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/declarations/src/ValOps.d.ts +0 -7
- package/dist/declarations/src/ValOpsFS.d.ts +0 -7
- package/dist/declarations/src/ValOpsHttp.d.ts +0 -13
- package/dist/declarations/src/ValRouter.d.ts +26 -0
- package/dist/declarations/src/getValidationErrorFileRef.d.ts +1 -1
- package/dist/declarations/src/index.d.ts +2 -2
- package/dist/declarations/src/login.d.ts +43 -16
- package/dist/declarations/src/patchStore.d.ts +6 -6
- package/dist/valbuild-server.cjs.dev.js +184 -187
- package/dist/valbuild-server.cjs.prod.js +184 -187
- package/dist/valbuild-server.esm.js +183 -186
- package/package.json +4 -4
|
@@ -5318,13 +5318,6 @@ class ValOpsFS extends ValOps {
|
|
|
5318
5318
|
};
|
|
5319
5319
|
}
|
|
5320
5320
|
}
|
|
5321
|
-
async getCommitSummary() {
|
|
5322
|
-
return {
|
|
5323
|
-
error: {
|
|
5324
|
-
message: "Val is in development / local mode. Cannot generate summary"
|
|
5325
|
-
}
|
|
5326
|
-
};
|
|
5327
|
-
}
|
|
5328
5321
|
async getStat(params) {
|
|
5329
5322
|
// In ValOpsFS, we don't have a websocket server to listen to file changes so we use long-polling.
|
|
5330
5323
|
// If a file that Val depends on changes, we break the connection and tell the client to request again to get the latest values.
|
|
@@ -6502,9 +6495,6 @@ const MetadataRes = z.object({
|
|
|
6502
6495
|
metadata: Metadata,
|
|
6503
6496
|
type: z.union([z.literal("file"), z.literal("image")]).nullable()
|
|
6504
6497
|
});
|
|
6505
|
-
const SummaryResponse = z.object({
|
|
6506
|
-
commitSummary: z.string()
|
|
6507
|
-
});
|
|
6508
6498
|
const GetApplicablePatches = z.object({
|
|
6509
6499
|
patches: z.array(z.object({
|
|
6510
6500
|
path: z.string(),
|
|
@@ -6691,69 +6681,6 @@ class ValOpsHttp extends ValOps {
|
|
|
6691
6681
|
};
|
|
6692
6682
|
}
|
|
6693
6683
|
}
|
|
6694
|
-
async getCommitSummary(preparedCommit) {
|
|
6695
|
-
try {
|
|
6696
|
-
var _res$headers$get2;
|
|
6697
|
-
const res = await fetch(`${this.contentUrl}/v1/${this.project}/commit-summary`, {
|
|
6698
|
-
method: "POST",
|
|
6699
|
-
headers: {
|
|
6700
|
-
...this.authHeaders,
|
|
6701
|
-
"Content-Type": "application/json"
|
|
6702
|
-
},
|
|
6703
|
-
body: JSON.stringify({
|
|
6704
|
-
patchedSourceFiles: preparedCommit.patchedSourceFiles,
|
|
6705
|
-
previousSourceFiles: preparedCommit.previousSourceFiles
|
|
6706
|
-
})
|
|
6707
|
-
});
|
|
6708
|
-
if (res.ok) {
|
|
6709
|
-
const json = await res.json();
|
|
6710
|
-
const parsed = SummaryResponse.safeParse(json);
|
|
6711
|
-
if (parsed.success) {
|
|
6712
|
-
return {
|
|
6713
|
-
commitSummary: parsed.data.commitSummary
|
|
6714
|
-
};
|
|
6715
|
-
}
|
|
6716
|
-
console.error(`Could not parse summary response. Error: ${fromError(parsed.error).toString()}`);
|
|
6717
|
-
return {
|
|
6718
|
-
error: {
|
|
6719
|
-
message: `Cannot get the summary of your changes. An error has been logged. Possible cause: the current version of Val might be too old. Please try again later or contact the developers on your team.`
|
|
6720
|
-
}
|
|
6721
|
-
};
|
|
6722
|
-
}
|
|
6723
|
-
if (res.status === 401) {
|
|
6724
|
-
console.error("Unauthorized to get summary");
|
|
6725
|
-
return {
|
|
6726
|
-
error: {
|
|
6727
|
-
message: "Could not get summary. Although your user is authorized, the application has authorization issues. Contact the developers on your team and ask them to verify the api keys."
|
|
6728
|
-
}
|
|
6729
|
-
};
|
|
6730
|
-
}
|
|
6731
|
-
const unknownErrorMessage = `Could not get summary. HTTP error: ${res.status} ${res.statusText}`;
|
|
6732
|
-
if ((_res$headers$get2 = res.headers.get("Content-Type")) !== null && _res$headers$get2 !== void 0 && _res$headers$get2.includes("application/json")) {
|
|
6733
|
-
const json = await res.json();
|
|
6734
|
-
const message = getErrorMessageFromUnknownJson(json, unknownErrorMessage);
|
|
6735
|
-
console.error("Summary error:", message);
|
|
6736
|
-
return {
|
|
6737
|
-
error: {
|
|
6738
|
-
message
|
|
6739
|
-
}
|
|
6740
|
-
};
|
|
6741
|
-
}
|
|
6742
|
-
console.error(unknownErrorMessage);
|
|
6743
|
-
return {
|
|
6744
|
-
error: {
|
|
6745
|
-
message: unknownErrorMessage
|
|
6746
|
-
}
|
|
6747
|
-
};
|
|
6748
|
-
} catch (e) {
|
|
6749
|
-
console.error("Could not get summary (connection error?):", e);
|
|
6750
|
-
return {
|
|
6751
|
-
error: {
|
|
6752
|
-
message: `Could not get summary. Error: ${e instanceof Error ? e.message : JSON.stringify(e)}`
|
|
6753
|
-
}
|
|
6754
|
-
};
|
|
6755
|
-
}
|
|
6756
|
-
}
|
|
6757
6684
|
async getStat(params) {
|
|
6758
6685
|
if (!(params !== null && params !== void 0 && params.profileId)) {
|
|
6759
6686
|
return {
|
|
@@ -7081,7 +7008,7 @@ class ValOpsHttp extends ValOps {
|
|
|
7081
7008
|
coreVersion: Internal.VERSION.core
|
|
7082
7009
|
})
|
|
7083
7010
|
}).then(async res => {
|
|
7084
|
-
var _res$headers$
|
|
7011
|
+
var _res$headers$get2;
|
|
7085
7012
|
if (res.ok) {
|
|
7086
7013
|
const parsed = SavePatchResponse.safeParse(await res.json());
|
|
7087
7014
|
if (parsed.success) {
|
|
@@ -7100,7 +7027,7 @@ class ValOpsHttp extends ValOps {
|
|
|
7100
7027
|
message: "Conflict: " + (await res.text())
|
|
7101
7028
|
});
|
|
7102
7029
|
}
|
|
7103
|
-
if ((_res$headers$
|
|
7030
|
+
if ((_res$headers$get2 = res.headers.get("Content-Type")) !== null && _res$headers$get2 !== void 0 && _res$headers$get2.includes("application/json")) {
|
|
7104
7031
|
const json = await res.json();
|
|
7105
7032
|
const message = getErrorMessageFromUnknownJson(json, "Unknown error");
|
|
7106
7033
|
return result.err({
|
|
@@ -7383,51 +7310,9 @@ class ValOpsHttp extends ValOps {
|
|
|
7383
7310
|
};
|
|
7384
7311
|
});
|
|
7385
7312
|
}
|
|
7386
|
-
async getCommitMessage(preparedCommit) {
|
|
7387
|
-
var _res$headers$get4;
|
|
7388
|
-
const res = await fetch(`${this.contentUrl}/v1/${this.project}/commit-summary`, {
|
|
7389
|
-
method: "POST",
|
|
7390
|
-
headers: {
|
|
7391
|
-
...this.authHeaders,
|
|
7392
|
-
"Content-Type": "application/json"
|
|
7393
|
-
},
|
|
7394
|
-
body: JSON.stringify({
|
|
7395
|
-
patchedSourceFiles: preparedCommit.patchedSourceFiles,
|
|
7396
|
-
previousSourceFiles: preparedCommit.previousSourceFiles
|
|
7397
|
-
})
|
|
7398
|
-
});
|
|
7399
|
-
if (res.ok) {
|
|
7400
|
-
const json = await res.json();
|
|
7401
|
-
const parsed = SummaryResponse.safeParse(json);
|
|
7402
|
-
if (parsed.success) {
|
|
7403
|
-
return {
|
|
7404
|
-
commitSummary: parsed.data.commitSummary
|
|
7405
|
-
};
|
|
7406
|
-
}
|
|
7407
|
-
return {
|
|
7408
|
-
error: {
|
|
7409
|
-
message: `Could not parse commit summary response. Error: ${fromError(parsed.error)}`
|
|
7410
|
-
}
|
|
7411
|
-
};
|
|
7412
|
-
}
|
|
7413
|
-
if ((_res$headers$get4 = res.headers.get("Content-Type")) !== null && _res$headers$get4 !== void 0 && _res$headers$get4.includes("application/json")) {
|
|
7414
|
-
const json = await res.json();
|
|
7415
|
-
const message = getErrorMessageFromUnknownJson(json, "Unknown error");
|
|
7416
|
-
return {
|
|
7417
|
-
error: {
|
|
7418
|
-
message
|
|
7419
|
-
}
|
|
7420
|
-
};
|
|
7421
|
-
}
|
|
7422
|
-
return {
|
|
7423
|
-
error: {
|
|
7424
|
-
message: "Could not get commit message. HTTP error: " + res.status + " " + res.statusText
|
|
7425
|
-
}
|
|
7426
|
-
};
|
|
7427
|
-
}
|
|
7428
7313
|
async commit(prepared, message, committer, filesDirectory, newBranch) {
|
|
7429
7314
|
try {
|
|
7430
|
-
var _res$headers$
|
|
7315
|
+
var _res$headers$get3;
|
|
7431
7316
|
const existingBranch = this.branch;
|
|
7432
7317
|
const res = await fetch(`${this.contentUrl}/v1/${this.project}/commit`, {
|
|
7433
7318
|
method: "POST",
|
|
@@ -7464,7 +7349,7 @@ class ValOpsHttp extends ValOps {
|
|
|
7464
7349
|
}
|
|
7465
7350
|
};
|
|
7466
7351
|
}
|
|
7467
|
-
if ((_res$headers$
|
|
7352
|
+
if ((_res$headers$get3 = res.headers.get("Content-Type")) !== null && _res$headers$get3 !== void 0 && _res$headers$get3.includes("application/json")) {
|
|
7468
7353
|
const json = z.object({
|
|
7469
7354
|
isNotFastForward: z.boolean().optional()
|
|
7470
7355
|
}).safeParse(await res.json());
|
|
@@ -9117,49 +9002,6 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
9117
9002
|
});
|
|
9118
9003
|
}
|
|
9119
9004
|
},
|
|
9120
|
-
"/commit-summary": {
|
|
9121
|
-
GET: async req => {
|
|
9122
|
-
const cookies = req.cookies;
|
|
9123
|
-
const auth = getAuth(cookies);
|
|
9124
|
-
if (auth.error) {
|
|
9125
|
-
return {
|
|
9126
|
-
status: 401,
|
|
9127
|
-
json: {
|
|
9128
|
-
message: auth.error
|
|
9129
|
-
}
|
|
9130
|
-
};
|
|
9131
|
-
}
|
|
9132
|
-
const query = req.query;
|
|
9133
|
-
const patchIds = query.patch_id;
|
|
9134
|
-
const patches = await serverOps.fetchPatches({
|
|
9135
|
-
patchIds,
|
|
9136
|
-
excludePatchOps: false
|
|
9137
|
-
});
|
|
9138
|
-
const analysis = serverOps.analyzePatches(patches.patches, patches.commits, commit);
|
|
9139
|
-
const preparedCommit = await serverOps.prepare({
|
|
9140
|
-
...analysis,
|
|
9141
|
-
...patches
|
|
9142
|
-
});
|
|
9143
|
-
const res = await serverOps.getCommitSummary(preparedCommit);
|
|
9144
|
-
if (res.error) {
|
|
9145
|
-
console.error("Failed to summarize", res.error);
|
|
9146
|
-
return {
|
|
9147
|
-
status: 400,
|
|
9148
|
-
json: {
|
|
9149
|
-
message: res.error.message
|
|
9150
|
-
}
|
|
9151
|
-
};
|
|
9152
|
-
}
|
|
9153
|
-
return {
|
|
9154
|
-
status: 200,
|
|
9155
|
-
json: {
|
|
9156
|
-
baseSha: await serverOps.getBaseSha(),
|
|
9157
|
-
patchIds,
|
|
9158
|
-
commitSummary: res.commitSummary
|
|
9159
|
-
}
|
|
9160
|
-
};
|
|
9161
|
-
}
|
|
9162
|
-
},
|
|
9163
9005
|
"/save": {
|
|
9164
9006
|
POST: async req => {
|
|
9165
9007
|
const cookies = req.cookies;
|
|
@@ -9442,11 +9284,18 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
9442
9284
|
}
|
|
9443
9285
|
const json = await upstreamRes.json();
|
|
9444
9286
|
const wsUrl = options.valContentUrl.replace(/^https:/, "wss:").replace(/^http:/, "ws:") + `/v1/${options.project}/ai/connect`;
|
|
9287
|
+
// Forwarded as-is, minus anything that is not a string: which
|
|
9288
|
+
// providers are reachable is the content server's to decide, and
|
|
9289
|
+
// this only has to carry the answer without editorialising.
|
|
9290
|
+
const providers = Array.isArray(json.providers) ? json.providers.filter(provider => typeof provider === "string") : undefined;
|
|
9445
9291
|
return {
|
|
9446
9292
|
status: 200,
|
|
9447
9293
|
json: {
|
|
9448
9294
|
nonce: json.nonce,
|
|
9449
|
-
wsUrl
|
|
9295
|
+
wsUrl,
|
|
9296
|
+
...(providers ? {
|
|
9297
|
+
providers
|
|
9298
|
+
} : {})
|
|
9450
9299
|
}
|
|
9451
9300
|
};
|
|
9452
9301
|
} catch (err) {
|
|
@@ -10464,6 +10313,10 @@ async function initHandlerOptions(route, opts, config) {
|
|
|
10464
10313
|
const maybeValProject = opts.project || process.env.VAL_PROJECT;
|
|
10465
10314
|
const valBuildUrl = opts.valBuildUrl || process.env.VAL_BUILD_URL || "https://admin.val.build";
|
|
10466
10315
|
const valContentUrl = opts.valContentUrl || process.env.VAL_CONTENT_URL || DEFAULT_CONTENT_HOST;
|
|
10316
|
+
warnIfInsecureUrls({
|
|
10317
|
+
valBuildUrl,
|
|
10318
|
+
valContentUrl
|
|
10319
|
+
});
|
|
10467
10320
|
if (isProxyMode) {
|
|
10468
10321
|
var _opts$versions, _opts$versions2;
|
|
10469
10322
|
if (!maybeApiKey || !maybeValSecret) {
|
|
@@ -10505,7 +10358,6 @@ async function initHandlerOptions(route, opts, config) {
|
|
|
10505
10358
|
};
|
|
10506
10359
|
} else {
|
|
10507
10360
|
const cwd = process.cwd();
|
|
10508
|
-
const valBuildUrl = opts.valBuildUrl || process.env.VAL_BUILD_URL || "https://admin.val.build";
|
|
10509
10361
|
return {
|
|
10510
10362
|
mode: "fs",
|
|
10511
10363
|
cwd,
|
|
@@ -10522,6 +10374,84 @@ async function initHandlerOptions(route, opts, config) {
|
|
|
10522
10374
|
}
|
|
10523
10375
|
}
|
|
10524
10376
|
|
|
10377
|
+
/**
|
|
10378
|
+
* Hosts we send credentials to, and what each one puts at risk. They differ:
|
|
10379
|
+
* only `valBuildUrl` hands back the app token that becomes the session cookie,
|
|
10380
|
+
* so a single shared sentence would overstate one and understate the other.
|
|
10381
|
+
*/
|
|
10382
|
+
|
|
10383
|
+
const CREDENTIAL_BEARING_URLS = ["valBuildUrl", "valContentUrl"];
|
|
10384
|
+
const WHAT_IS_AT_RISK = {
|
|
10385
|
+
valBuildUrl: "Val's api key is sent to this host, and the token it returns is what this server signs into the session cookie, " + "so both can be read - and the token replaced - by anyone on the network path.",
|
|
10386
|
+
valContentUrl: "Val's api key, or the caller's personal access token, is sent to this host, " + "so it can be read by anyone on the network path."
|
|
10387
|
+
};
|
|
10388
|
+
|
|
10389
|
+
// NOTE: `URL.hostname` keeps the brackets on an IPv6 literal, so this is
|
|
10390
|
+
// "[::1]" and not "::1" - and `http://[0:0:0:0:0:0:0:1]` normalises to the
|
|
10391
|
+
// same short form before it gets here. Dropping the brackets looks like a
|
|
10392
|
+
// tidy-up and silently stops matching IPv6 loopback.
|
|
10393
|
+
const LOOPBACK_HOSTNAMES = ["localhost", "127.0.0.1", "[::1]"];
|
|
10394
|
+
|
|
10395
|
+
/**
|
|
10396
|
+
* The URL as it is safe to print. `http://user:pass@host` is a legal override,
|
|
10397
|
+
* and a warning about credential exposure that puts the password in the log
|
|
10398
|
+
* would be the very thing it is warning about.
|
|
10399
|
+
*/
|
|
10400
|
+
function forLog(parsed) {
|
|
10401
|
+
if (!parsed.username && !parsed.password) {
|
|
10402
|
+
return parsed.href;
|
|
10403
|
+
}
|
|
10404
|
+
const redacted = new URL(parsed.href);
|
|
10405
|
+
redacted.username = "";
|
|
10406
|
+
redacted.password = "";
|
|
10407
|
+
return `${redacted.href} (credentials redacted)`;
|
|
10408
|
+
}
|
|
10409
|
+
|
|
10410
|
+
/**
|
|
10411
|
+
* Returns a warning if `url` would send credentials somewhere they can be read
|
|
10412
|
+
* off the wire, or null if it is fine.
|
|
10413
|
+
*
|
|
10414
|
+
* Both URLs default to https, but each is overridable - `opts.valBuildUrl` /
|
|
10415
|
+
* `VAL_BUILD_URL`, `opts.valContentUrl` / `VAL_CONTENT_URL` - and neither
|
|
10416
|
+
* override has ever been scheme-checked. Point one at a plain http host and the
|
|
10417
|
+
* api key goes out in clear text, and whatever comes back is whatever the
|
|
10418
|
+
* network says: for `valBuildUrl` that includes the app token this server
|
|
10419
|
+
* re-signs into the session cookie.
|
|
10420
|
+
*
|
|
10421
|
+
* Loopback over http is exempt: that is a val.build running on the developer's
|
|
10422
|
+
* own machine, and there is no network to be on the wrong side of.
|
|
10423
|
+
*
|
|
10424
|
+
* This warns rather than throws. Both overrides are set by the operator, not by
|
|
10425
|
+
* an attacker, so this is a misconfiguration to surface - not untrusted input to
|
|
10426
|
+
* reject - and refusing to boot would break anyone deliberately pointing at an
|
|
10427
|
+
* internal http host today.
|
|
10428
|
+
*/
|
|
10429
|
+
function insecureUrlWarning(name, url) {
|
|
10430
|
+
let parsed;
|
|
10431
|
+
try {
|
|
10432
|
+
parsed = new URL(url);
|
|
10433
|
+
} catch {
|
|
10434
|
+
// NOTE: the URL is not echoed here. It did not parse, so there is nothing
|
|
10435
|
+
// to redact with, and an unparseable string can still hold a password.
|
|
10436
|
+
return `Val: ${name} is not a valid URL.`;
|
|
10437
|
+
}
|
|
10438
|
+
if (parsed.protocol === "https:") {
|
|
10439
|
+
return null;
|
|
10440
|
+
}
|
|
10441
|
+
if (parsed.protocol === "http:" && (LOOPBACK_HOSTNAMES.includes(parsed.hostname) || parsed.hostname.endsWith(".localhost"))) {
|
|
10442
|
+
return null;
|
|
10443
|
+
}
|
|
10444
|
+
return `Val: ${name} is set to ${forLog(parsed)}, which is not https. ` + `${WHAT_IS_AT_RISK[name]} ` + `Use https, or a loopback address for local development.`;
|
|
10445
|
+
}
|
|
10446
|
+
function warnIfInsecureUrls(urls) {
|
|
10447
|
+
for (const name of CREDENTIAL_BEARING_URLS) {
|
|
10448
|
+
const warning = insecureUrlWarning(name, urls[name]);
|
|
10449
|
+
if (warning) {
|
|
10450
|
+
console.warn(warning);
|
|
10451
|
+
}
|
|
10452
|
+
}
|
|
10453
|
+
}
|
|
10454
|
+
|
|
10525
10455
|
// TODO: remove
|
|
10526
10456
|
async function safeReadGit(cwd) {
|
|
10527
10457
|
async function findGitHead(currentDir, depth) {
|
|
@@ -12525,7 +12455,17 @@ async function findAndEvalValConfigFile(projectRoot) {
|
|
|
12525
12455
|
}
|
|
12526
12456
|
|
|
12527
12457
|
/**
|
|
12528
|
-
* The Val login
|
|
12458
|
+
* The Val login flow, as reusable primitives.
|
|
12459
|
+
*
|
|
12460
|
+
* This is an RFC 8628 device authorization grant. The shape that matters:
|
|
12461
|
+
* {@link ValDeviceAuthorization.deviceCode} is a secret this process holds and
|
|
12462
|
+
* polls with, while {@link ValDeviceAuthorization.userCode} is the short string
|
|
12463
|
+
* the human reads out of the terminal and types into a browser. Only the device
|
|
12464
|
+
* code can collect a token.
|
|
12465
|
+
*
|
|
12466
|
+
* Keep them apart. Show the user code; never print, log or put the device code
|
|
12467
|
+
* in a URL. An earlier version of this flow used one value for both jobs, which
|
|
12468
|
+
* meant anyone who saw the verification link could collect the token it led to.
|
|
12529
12469
|
*
|
|
12530
12470
|
* The CLI wraps these with terminal output, and `@valbuild/language-server`
|
|
12531
12471
|
* wraps them with LSP `window/showDocument` and progress reporting. Neither the
|
|
@@ -12539,6 +12479,21 @@ const DEFAULT_LOGIN_HOST = "https://admin.val.build";
|
|
|
12539
12479
|
function defaultHost() {
|
|
12540
12480
|
return process.env.VAL_BUILD_URL || DEFAULT_LOGIN_HOST;
|
|
12541
12481
|
}
|
|
12482
|
+
|
|
12483
|
+
/**
|
|
12484
|
+
* What gets shown on the approval screen so the person can tell which terminal
|
|
12485
|
+
* is asking. Self-reported and therefore a hint, not proof — the server treats
|
|
12486
|
+
* it as untrusted display text.
|
|
12487
|
+
*/
|
|
12488
|
+
function defaultDeviceName() {
|
|
12489
|
+
try {
|
|
12490
|
+
return `${os.hostname()} (${os.platform()})`;
|
|
12491
|
+
} catch {
|
|
12492
|
+
// hostname() can throw on locked-down containers. A missing device name is
|
|
12493
|
+
// not worth failing a login over; the server renders "Unknown".
|
|
12494
|
+
return "";
|
|
12495
|
+
}
|
|
12496
|
+
}
|
|
12542
12497
|
class ValLoginError extends Error {
|
|
12543
12498
|
constructor(code, message, details) {
|
|
12544
12499
|
super(message);
|
|
@@ -12548,20 +12503,24 @@ class ValLoginError extends Error {
|
|
|
12548
12503
|
}
|
|
12549
12504
|
}
|
|
12550
12505
|
|
|
12551
|
-
/** A login attempt that is waiting for the user to
|
|
12506
|
+
/** A login attempt that is waiting for the user to approve it in a browser. */
|
|
12552
12507
|
|
|
12553
12508
|
/**
|
|
12554
|
-
* Begin a login attempt. The caller is responsible for getting
|
|
12555
|
-
*
|
|
12509
|
+
* Begin a login attempt. The caller is responsible for getting the user code
|
|
12510
|
+
* and verification URL in front of the user.
|
|
12556
12511
|
*/
|
|
12557
12512
|
async function startValLogin(options = {}) {
|
|
12558
12513
|
var _response$headers$get;
|
|
12559
12514
|
const host = options.host ?? defaultHost();
|
|
12515
|
+
const deviceName = options.deviceName ?? defaultDeviceName();
|
|
12560
12516
|
const response = await fetch(`${host}/api/login`, {
|
|
12561
12517
|
method: "POST",
|
|
12562
12518
|
headers: {
|
|
12563
12519
|
"Content-Type": "application/json"
|
|
12564
|
-
}
|
|
12520
|
+
},
|
|
12521
|
+
body: JSON.stringify(deviceName ? {
|
|
12522
|
+
device_name: deviceName
|
|
12523
|
+
} : {})
|
|
12565
12524
|
});
|
|
12566
12525
|
if (response.status >= 500) {
|
|
12567
12526
|
const text = await response.text().catch(() => "");
|
|
@@ -12572,42 +12531,63 @@ async function startValLogin(options = {}) {
|
|
|
12572
12531
|
throw new ValLoginError("unexpected-content-type", "Unexpected failure while trying to login (content type was not JSON).", text ? `Server response: ${text} (status: ${response.status})` : `Status: ${response.status}`);
|
|
12573
12532
|
}
|
|
12574
12533
|
const json = await response.json();
|
|
12575
|
-
const
|
|
12576
|
-
const
|
|
12577
|
-
|
|
12578
|
-
|
|
12534
|
+
const deviceCode = json === null || json === void 0 ? void 0 : json.device_code;
|
|
12535
|
+
const userCode = json === null || json === void 0 ? void 0 : json.user_code;
|
|
12536
|
+
const verificationUri = json === null || json === void 0 ? void 0 : json.verification_uri;
|
|
12537
|
+
if (typeof deviceCode !== "string" || typeof userCode !== "string" || typeof verificationUri !== "string") {
|
|
12538
|
+
throw new ValLoginError("unexpected-response", "Unexpected response from the server. This version of Val may be too old for the login flow on this host — try updating @valbuild/cli.", JSON.stringify(json));
|
|
12579
12539
|
}
|
|
12580
12540
|
return {
|
|
12581
|
-
|
|
12582
|
-
|
|
12541
|
+
deviceCode,
|
|
12542
|
+
userCode,
|
|
12543
|
+
verificationUri,
|
|
12544
|
+
verificationUriComplete: typeof (json === null || json === void 0 ? void 0 : json.verification_uri_complete) === "string" ? json.verification_uri_complete : verificationUri,
|
|
12545
|
+
expiresInSeconds: typeof (json === null || json === void 0 ? void 0 : json.expires_in) === "number" ? json.expires_in : DEFAULT_LOGIN_EXPIRES_IN_SECONDS,
|
|
12546
|
+
intervalSeconds: typeof (json === null || json === void 0 ? void 0 : json.interval) === "number" ? json.interval : DEFAULT_LOGIN_POLL_INTERVAL_SECONDS
|
|
12583
12547
|
};
|
|
12584
12548
|
}
|
|
12585
|
-
|
|
12586
|
-
|
|
12549
|
+
|
|
12550
|
+
/** Fallbacks for a server that omits the optional RFC 8628 timing fields. */
|
|
12551
|
+
const DEFAULT_LOGIN_EXPIRES_IN_SECONDS = 600;
|
|
12552
|
+
const DEFAULT_LOGIN_POLL_INTERVAL_SECONDS = 5;
|
|
12587
12553
|
|
|
12588
12554
|
/**
|
|
12589
|
-
*
|
|
12555
|
+
* How much to add to the poll interval when the server answers `slow_down`.
|
|
12556
|
+
* RFC 8628 section 3.5 specifies 5 seconds.
|
|
12557
|
+
*/
|
|
12558
|
+
const SLOW_DOWN_INCREMENT_SECONDS = 5;
|
|
12559
|
+
|
|
12560
|
+
/**
|
|
12561
|
+
* Poll until the user approves the login in their browser.
|
|
12590
12562
|
*
|
|
12591
12563
|
* Accepts an `AbortSignal` so an editor can cancel the flow when the user
|
|
12592
|
-
* dismisses the prompt, instead of leaving a poll loop running
|
|
12564
|
+
* dismisses the prompt, instead of leaving a poll loop running to expiry.
|
|
12593
12565
|
*/
|
|
12594
|
-
async function awaitValLoginConfirmation(
|
|
12566
|
+
async function awaitValLoginConfirmation(authorization, options = {}) {
|
|
12595
12567
|
const host = options.host ?? defaultHost();
|
|
12596
|
-
const maxDuration = options.maxDurationMs ??
|
|
12597
|
-
const pollInterval = options.pollIntervalMs ?? DEFAULT_LOGIN_POLL_INTERVAL;
|
|
12568
|
+
const maxDuration = options.maxDurationMs ?? authorization.expiresInSeconds * 1000;
|
|
12598
12569
|
const now = options.now ?? (() => Date.now());
|
|
12570
|
+
// Mutable: `slow_down` widens it as we go, and never narrows it again.
|
|
12571
|
+
let intervalMs = authorization.intervalSeconds * 1000;
|
|
12599
12572
|
const start = now();
|
|
12600
12573
|
while (now() - start < maxDuration) {
|
|
12601
12574
|
var _options$signal, _options$signal2;
|
|
12602
12575
|
if ((_options$signal = options.signal) !== null && _options$signal !== void 0 && _options$signal.aborted) {
|
|
12603
12576
|
throw new ValLoginError("aborted", "Login was cancelled.");
|
|
12604
12577
|
}
|
|
12605
|
-
|
|
12578
|
+
// Wait first: the user has not had time to approve anything yet.
|
|
12579
|
+
await new Promise(resolve => setTimeout(resolve, intervalMs));
|
|
12606
12580
|
if ((_options$signal2 = options.signal) !== null && _options$signal2 !== void 0 && _options$signal2.aborted) {
|
|
12607
12581
|
throw new ValLoginError("aborted", "Login was cancelled.");
|
|
12608
12582
|
}
|
|
12609
|
-
const response = await fetch(`${host}/api/login
|
|
12610
|
-
method: "POST"
|
|
12583
|
+
const response = await fetch(`${host}/api/login`, {
|
|
12584
|
+
method: "POST",
|
|
12585
|
+
headers: {
|
|
12586
|
+
"Content-Type": "application/json"
|
|
12587
|
+
},
|
|
12588
|
+
body: JSON.stringify({
|
|
12589
|
+
device_code: authorization.deviceCode
|
|
12590
|
+
})
|
|
12611
12591
|
});
|
|
12612
12592
|
if (response.status >= 500) {
|
|
12613
12593
|
throw new ValLoginError("server-error", "An error occurred on the server.", `Status: ${response.status}`);
|
|
@@ -12625,6 +12605,23 @@ async function awaitValLoginConfirmation(nonce, options = {}) {
|
|
|
12625
12605
|
}
|
|
12626
12606
|
throw new ValLoginError("unexpected-response", "Unexpected response from the server.", JSON.stringify(json));
|
|
12627
12607
|
}
|
|
12608
|
+
const json = await response.json().catch(() => null);
|
|
12609
|
+
const error = typeof (json === null || json === void 0 ? void 0 : json.error) === "string" ? json.error : null;
|
|
12610
|
+
const description = typeof (json === null || json === void 0 ? void 0 : json.error_description) === "string" ? json.error_description : undefined;
|
|
12611
|
+
if (error === "authorization_pending") {
|
|
12612
|
+
continue;
|
|
12613
|
+
}
|
|
12614
|
+
if (error === "slow_down" || response.status === 429) {
|
|
12615
|
+
intervalMs += SLOW_DOWN_INCREMENT_SECONDS * 1000;
|
|
12616
|
+
continue;
|
|
12617
|
+
}
|
|
12618
|
+
if (error === "access_denied") {
|
|
12619
|
+
throw new ValLoginError("access-denied", "The login was declined in the browser.", description);
|
|
12620
|
+
}
|
|
12621
|
+
if (error === "expired_token") {
|
|
12622
|
+
throw new ValLoginError("expired", "The login code expired before it was approved.", description);
|
|
12623
|
+
}
|
|
12624
|
+
throw new ValLoginError("unexpected-response", "Unexpected response from the server.", JSON.stringify(json));
|
|
12628
12625
|
}
|
|
12629
12626
|
throw new ValLoginError("timeout", "Login confirmation timed out.");
|
|
12630
12627
|
}
|
|
@@ -12926,4 +12923,4 @@ function readCapturedReport(snapshotDir) {
|
|
|
12926
12923
|
return JSON.parse(fs.readFileSync(reportPath, "utf-8"));
|
|
12927
12924
|
}
|
|
12928
12925
|
|
|
12929
|
-
export {
|
|
12926
|
+
export { DEFAULT_LOGIN_EXPIRES_IN_SECONDS, DEFAULT_LOGIN_HOST, DEFAULT_LOGIN_POLL_INTERVAL_SECONDS, Service, ValFSHost, ValLoginError, ValModuleLoader, ValOpsFS, ValOpsHttp, ValSourceFileHandler, analyzeValModule, awaitValLoginConfirmation, checkRemoteRef, classifyJsonValuesOp, compareWithCapturedReport, createDefaultValFSHost, createFixPatch, createJsonEntryPathMap, createModulePathMap, createService, createValApiRouter, createValModuleFileInspector, createValServer, currentFixHandlers, decodeJwtWithoutVerifying, describePatchStoreProblems, downloadFileFromRemote, encodeJwt, evalValConfigFile, extractFileMetadata, extractImageMetadata, extractJsonValuesEntry, findAndEvalValConfigFile, findJsonEntryFilePath, fixHandlers, formatPatchSourceError, formatSyntaxErrorTree, getCachedRemoteFileDir, getCachedRemoteFilePath, getCompilerOptions, getExpire, getFileExt, getModulePathRange, getPersonalAccessTokenPath, getSettings, getValidationErrorFileRef, handleCheckAllFiles, handleFileMetadata, handleJsonValuesExtractEntry, handleRemoteFileCheck, handleRemoteFileDownload, handleRemoteFileUpload, handleRemoteGalleryFileUpload, handleUniqueFolderCheck, loadValModules, parsePersonalAccessTokenFile, patchSourceFile, persistPersonalAccessToken, readCapturedReport, readPatchStore, rebaseContentOp, replaySnapshot, safeReadGit, startValLogin, uploadRemoteFile, validateMetadata, verifyJwt };
|
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"./package.json": "./package.json"
|
|
17
17
|
},
|
|
18
18
|
"types": "dist/valbuild-server.cjs.d.ts",
|
|
19
|
-
"version": "0.
|
|
19
|
+
"version": "0.115.0",
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@prettier/sync": "^0.6.1",
|
|
22
22
|
"@types/jest": "^30.0.0"
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
"typescript": "^6.0.3",
|
|
30
30
|
"zod": "^4.4.3",
|
|
31
31
|
"zod-validation-error": "^5.0.0",
|
|
32
|
-
"@valbuild/shared": "0.
|
|
33
|
-
"@valbuild/
|
|
34
|
-
"@valbuild/
|
|
32
|
+
"@valbuild/shared": "0.114.0",
|
|
33
|
+
"@valbuild/core": "0.111.0",
|
|
34
|
+
"@valbuild/ui": "0.114.0"
|
|
35
35
|
},
|
|
36
36
|
"engines": {
|
|
37
37
|
"node": "^20.19.0 || >=22"
|