@impeccable/reviews 0.1.19 → 0.1.20
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/host-questions.js +12 -0
- package/dist/playbook/cases/preview-protection.md +1 -1
- package/dist/tools.js +62 -23
- package/package.json +1 -1
package/dist/host-questions.js
CHANGED
|
@@ -228,6 +228,18 @@ export function sentTo(screen, url) {
|
|
|
228
228
|
const line = `This run sends it first to ${url}, to prove it opens before anything saves it.`;
|
|
229
229
|
return screen.kind === 'confirm' ? screen : { ...screen, prompt: `${screen.prompt} ${line}` };
|
|
230
230
|
}
|
|
231
|
+
/** On a second Netlify site behind a password, the offer to use the one the person typed for another site of this run (#384); on Skip, the paste screen. */
|
|
232
|
+
export function netlifySamePassword(site, other) {
|
|
233
|
+
return {
|
|
234
|
+
id: 'netlify-password-same',
|
|
235
|
+
kind: 'confirm',
|
|
236
|
+
title: 'Your Netlify previews are protected',
|
|
237
|
+
hint: `The previews of the Netlify site ${site} are behind its site password too. The run tries the password you typed for ${other} on ${site}'s preview first, and saves it for ${site} only if it opens.`,
|
|
238
|
+
prompt: `Use the same password for ${site}?`,
|
|
239
|
+
confirmLabel: 'Use it',
|
|
240
|
+
skipLabel: 'Type another',
|
|
241
|
+
};
|
|
242
|
+
}
|
|
231
243
|
/** The consent to a Vercel bypass secret, naming the project that holds it and, on a repository with several apps, the app whose previews it opens (#358). */
|
|
232
244
|
export function vercelBypassConsent(project, app) {
|
|
233
245
|
const screen = HOST_SCREENS['vercel-bypass'];
|
|
@@ -13,7 +13,7 @@ impeccable_prove_preview answered a `gate`: the host's own lock stands in front
|
|
|
13
13
|
- Any other `refused` on Netlify whose `reason` says to prove or try a page a visitor opens, or that the page asks for the app's own sign-in past the lock: the site password (Netlify's own, or the one typed) got past the lock to the app's own sign-in on the page you proved, or Netlify's form did not take it, or Netlify did not answer. Prove a page a visitor opens without signing in, as netlify-previews' step 1 says, and call the tool again; the consent is not asked a second time. Refused again, step 3.
|
|
14
14
|
- Any other `skipped` or `refused`: nothing was saved; `reason` says what stopped it. Step 3.
|
|
15
15
|
- `save-failed`: the setup API did not take it all; `reason` says how far it got. Call the tool once more; when it fails again, step 3, with "Save the Vercel bypass secret on the setup page", "Type the Netlify site password in the app's Settings, under "A way past your host's lock"" or "Save the Cloudflare service token on the setup page" in `left`.
|
|
16
|
-
2. **Check it from Impeccable's side**: impeccable_write to `connection-check` with `{"appKey": "main"}` (on a repository with several apps, the app's own key: the tool saved the key for that app), and report what it answered. Then go on with the case that sent you here: the proof stands, so save the method as vercel-previews', netlify-previews' or cloudflare-previews' step 2 says, Cloudflare's with the tool's `pattern`. Done when the method is saved.
|
|
16
|
+
2. **Check it from Impeccable's side**: impeccable_write to `connection-check` with `{"appKey": "main"}` (on a repository with several apps, the app's own key: the tool saved the key for that app), and report what it answered. A check that answers `access: "auth-wall"` met the lock all the same: say so under "What is not verified", with its `detail`, and never as verified; the last screen does not call the app set up while it stands. Then go on with the case that sent you here: the proof stands, so save the method as vercel-previews', netlify-previews' or cloudflare-previews' step 2 says, Cloudflare's with the tool's `pattern`. Done when the method is saved.
|
|
17
17
|
3. **When nothing was saved**, save no method and set up no other preview, whatever the checkup's `next` says, and open no test drive (pull-requests' "No preview, no test drive"). Say under "What is not verified" that the previews are behind the host's lock, with the tool's `reason`, and name the settings page where the person makes the key, or makes the Netlify site public (the report's bullet below); put the person's one step in `left`, the settings action alone and never running the setup again (for Netlify's site password: "Type the Netlify site password in the app's Settings, under "A way past your host's lock"", and, with the draft test drive open, "Then mark draft #<n> ready", never a bypass key, which Netlify has none of; for Netlify's team login, none: the person was asked in this run, so `left` stays empty and the report names the Visitor access page); end the report's "Not part of this setup" line with the lock the tool met: "Vercel's Deployment Protection in front of the previews", "Netlify's password in front of the previews", "Netlify's team login in front of the previews", or "Cloudflare Access in front of the previews". Done when the report says so.
|
|
18
18
|
|
|
19
19
|
### Reference
|
package/dist/tools.js
CHANGED
|
@@ -10,7 +10,7 @@ import { defineTool } from '@earendil-works/pi-coding-agent';
|
|
|
10
10
|
import { agreeToHostSetup, bindPreviewPullRequest, chooseHostTeam, chosenHostTeam, classifyPreviewCommand, endHostSetup, hostSetupAgreed, isSecretFile, linkedByRun, observePreviewAttempts, parseCommand, previewRetryState, settleVercelFirstDeployment, vercelProjectsAdded } from './fence.js';
|
|
11
11
|
import { requestJson } from './http.js';
|
|
12
12
|
import { appsLeftOut, focusCheckup, focusInstructions, focusState, severalApps } from './focus.js';
|
|
13
|
-
import { APPS_QUESTION, cloudflareAccessConsent, cloudflareAccessPaste, cloudflareAccessReusable, HOST_QUESTIONS, HOST_SCREENS, HOST_SIGNED_IN, namedAppQuestion, NETLIFY_PASSWORD_WHERE, sentTo, startedIn, vercelBypassConsent, vercelBypassPaste } from './host-questions.js';
|
|
13
|
+
import { APPS_QUESTION, cloudflareAccessConsent, cloudflareAccessPaste, cloudflareAccessReusable, HOST_QUESTIONS, HOST_SCREENS, HOST_SIGNED_IN, namedAppQuestion, NETLIFY_PASSWORD_WHERE, netlifySamePassword, sentTo, startedIn, vercelBypassConsent, vercelBypassPaste } from './host-questions.js';
|
|
14
14
|
import { enableNetlifyPreviews, folderOf } from './host-previews.js';
|
|
15
15
|
import { checkoutNames, liveShaped, netlifySiteOf, previewHostName, provePreview, readHostedPreviews, readmeNamesApp, vercelDeploymentOf } from './hosted.js';
|
|
16
16
|
import { cfRunner, cfStatus, CLOUDFLARE_APP_PAGE, connectCloudflareProject, enableCloudflarePreviews, pagesProject, pagesProjectName, readCloudflarePreviews } from './cloudflare.js';
|
|
@@ -142,7 +142,8 @@ function stageError(status, tasks) {
|
|
|
142
142
|
}
|
|
143
143
|
/** The line under the last screen's heading, about the pull request that screen ends on: the setup pull request when there is one, else the test drive; and, where a host builds the previews and so no setup pull request was needed, one line saying so. Without a test drive, what stopped it, from what the run saved and met (#321). */
|
|
144
144
|
export function resultLine(report) {
|
|
145
|
-
const lead =
|
|
145
|
+
const lead = report.walled ? `Impeccable's own check of the preview met a wall (${report.walled}), so the test drive's review can't see its pages yet. The report, printed here when this screen closes, says what the run found.`
|
|
146
|
+
: !report.testPr ? `${noTestDrive(report)} The report, printed here when this screen closes, says what the run found.` : report.setupPr ? 'The setup pull request adds the workflow that lets Impeccable run on your previews. Merge it when you are ready.' : SEE_THE_REVIEW;
|
|
146
147
|
return report.setupPr || !report.hostedBy ? lead : `${lead} ${nothingToMerge(report.hostedBy)}`;
|
|
147
148
|
}
|
|
148
149
|
/** Why a run opened no test drive, as far as what it saved and met tells. */
|
|
@@ -159,9 +160,12 @@ export function ending(report) {
|
|
|
159
160
|
// On a repository with several apps, each locked app's own line says it.
|
|
160
161
|
const step = !report.apps && !report.methodSaved && report.lock ? [LOCKS[report.lock].left] : [];
|
|
161
162
|
const todo = [...new Set([...step, ...apps.todo, ...report.left])];
|
|
162
|
-
if (report.testPr && !apps.notReady.length)
|
|
163
|
+
if (report.testPr && !apps.notReady.length && !report.walled)
|
|
163
164
|
return { outcome: 'done', title: "You're all set up", todo, code: 0 };
|
|
164
165
|
const outcome = report.methodSaved ? 'partial' : 'stopped';
|
|
166
|
+
// Impeccable's own check said it can't get in: whatever the run saved, reviews will meet the same wall (#383).
|
|
167
|
+
if (report.walled)
|
|
168
|
+
return { outcome, title: "Not set up: Impeccable's check can't get past the preview's wall", todo: [...new Set([`Open the app's Settings: Impeccable's check met ${report.walled}`, ...todo])], code: 1 };
|
|
165
169
|
if (apps.notReady.length)
|
|
166
170
|
return { outcome, title: `${apps.notReady.join(' and ')} ${apps.notReady.length === 1 ? 'is' : 'are'} not set up`, todo, code: 1 };
|
|
167
171
|
const title = report.methodSaved ? 'No test drive opened' : `Not set up: ${report.lock ? `the previews are behind ${LOCKS[report.lock].lock}` : 'no preview method was saved'}`;
|
|
@@ -1692,15 +1696,16 @@ export function createTools(ctx) {
|
|
|
1692
1696
|
const bypassOutcomes = new Map();
|
|
1693
1697
|
/** The stores whose consent was given before the test drive's push, each with the project it named (`<store>/<projectId>`): the call once the gate is met asks no second time for that project alone. */
|
|
1694
1698
|
const bypassConsented = new Set();
|
|
1695
|
-
/**
|
|
1696
|
-
|
|
1697
|
-
|
|
1699
|
+
/**
|
|
1700
|
+
* The Netlify site passwords saved in this run, by site, for the proof of that site's addresses to send; never for the
|
|
1701
|
+
* model. Each is one site's, so it goes only to that site's deploy previews, and a monorepo's sites keep theirs side by
|
|
1702
|
+
* side (#384): with what the tool answered, whether the person typed it, and the apps it is saved for.
|
|
1703
|
+
*/
|
|
1704
|
+
const sitePasswords = new Map();
|
|
1698
1705
|
/** What impeccable_netlify_password answered for a site, less the password, where the answer stands for the run: a skip is asked once, the team login kept after the retries stays, and a site made public stays public. Another site of the repository is read on its own. */
|
|
1699
1706
|
const passwordStands = new Map();
|
|
1700
1707
|
/** The sites whose password the person said yes to storing: a later call for the same site, after a page the app's own sign-in answered, asks no second time. */
|
|
1701
1708
|
const passwordConsented = new Set();
|
|
1702
|
-
/** The saved answer, what an address the password opens later gets. */
|
|
1703
|
-
let passwordSaved;
|
|
1704
1709
|
/** The sites whose last typed password did not open the preview: the paste screen, asked again, says so first (#375). */
|
|
1705
1710
|
const passwordRefused = new Set();
|
|
1706
1711
|
/** The address impeccable_prove_preview met Cloudflare Access's login at, of a Pages preview impeccable_cloudflare_previews listed, and the words it looked for there. */
|
|
@@ -1808,6 +1813,8 @@ export function createTools(ctx) {
|
|
|
1808
1813
|
const provenApps = new Set();
|
|
1809
1814
|
/** The apps whose preview check (connection-check or latest-pull-check) the setup API answered in this run ('' for the repository): a ready app missing here is named on the last screen (#338). */
|
|
1810
1815
|
const checkedApps = new Set();
|
|
1816
|
+
/** What Impeccable's own last check of each app's preview met where it did not get in ('' for the repository's), until a later check gets in (#383). */
|
|
1817
|
+
const checkWalls = new Map();
|
|
1811
1818
|
/** On a repository with several apps (#286): the method each app's preview write saved; the host's lock a proof of each app's preview met; and the apps whose lock was met with no key tool called for them since, which holds every other app's steps until it is. */
|
|
1812
1819
|
const appMethods = new Map();
|
|
1813
1820
|
const lockMet = new Map();
|
|
@@ -2230,6 +2237,9 @@ export function createTools(ctx) {
|
|
|
2230
2237
|
const proven = provenApps.has(key);
|
|
2231
2238
|
if (!pickedApps?.has(key))
|
|
2232
2239
|
return { key, name, folder, state: 'unpicked' };
|
|
2240
|
+
// Impeccable's own last check of its preview met a wall: its reviews will meet it too (#383).
|
|
2241
|
+
if (method !== undefined && checkWalls.has(key))
|
|
2242
|
+
return { key, name, folder, state: 'walled', left: `${name}: Impeccable's check of its preview met ${checkWalls.get(key)}; open its Settings.` };
|
|
2233
2243
|
if (method !== undefined && (method !== 'preview-deploy' || proven)) {
|
|
2234
2244
|
// With a test drive open, each app's preview is checked once (pull-requests' step 6): one the setup API never answered is named (#338).
|
|
2235
2245
|
return ctx.sync.payload.test_pr && !checkedApps.has(key)
|
|
@@ -2251,6 +2261,8 @@ export function createTools(ctx) {
|
|
|
2251
2261
|
return `- ${where}: not set up. Its previews are behind ${lock}, and no key was saved for it.`;
|
|
2252
2262
|
if (a.state === 'unset')
|
|
2253
2263
|
return `- ${where}: not set up. No preview method was saved for it.`;
|
|
2264
|
+
if (a.state === 'walled')
|
|
2265
|
+
return `- ${where}: not set up. Method: ${appMethods.get(a.key)}, but Impeccable's own check of its preview met ${checkWalls.get(a.key)}.`;
|
|
2254
2266
|
return `- ${where}: not set up this run.`;
|
|
2255
2267
|
}).join('\n')}`;
|
|
2256
2268
|
/** Each app's Vercel project, found once a run (a failure is read again, the CLI may have signed in since). */
|
|
@@ -2444,6 +2456,16 @@ export function createTools(ctx) {
|
|
|
2444
2456
|
// A check that answered, a preview not running yet included, ran for its app; one still turned away did not.
|
|
2445
2457
|
if (check && (response.ok || response.status === 409))
|
|
2446
2458
|
checkedApps.add(typeof object(p.body)?.appKey === 'string' ? String(object(p.body)?.appKey) : '');
|
|
2459
|
+
if (check && response.ok) {
|
|
2460
|
+
// A login wall the check met is the one the review meets: the run is not called set up while it stands (#383).
|
|
2461
|
+
const key = typeof object(p.body)?.appKey === 'string' ? String(object(p.body)?.appKey) : '';
|
|
2462
|
+
const result = object(object(response.data)?.result), pulled = object(object(response.data)?.check);
|
|
2463
|
+
const walled = result?.access === 'auth-wall' || pulled?.outcome === 'sign-in';
|
|
2464
|
+
if (walled)
|
|
2465
|
+
checkWalls.set(key, typeof result?.detail === 'string' && plainText(result.detail) === result.detail && result.detail.length <= 120 ? `a ${result.detail}` : 'a login wall');
|
|
2466
|
+
else if (result?.access === 'ok' || pulled?.outcome === 'reached')
|
|
2467
|
+
checkWalls.delete(key);
|
|
2468
|
+
}
|
|
2447
2469
|
if (target.url.endsWith('/apps') && response.ok && object(p.body)?.action === 'confirm')
|
|
2448
2470
|
confirmedApps = true;
|
|
2449
2471
|
if (target.url.endsWith('/preview') && response.ok) {
|
|
@@ -2673,7 +2695,7 @@ export function createTools(ctx) {
|
|
|
2673
2695
|
}
|
|
2674
2696
|
// Otherwise final: a completed write whose answer was lost may still have landed, so it is never taken back.
|
|
2675
2697
|
const hosts = hostLines(hostsUsed());
|
|
2676
|
-
ctx.onReport({ markdown, left, testPr: drive, setupPr: ctx.sync.payload.setup_pr, ...(savedHost ? { hostedBy: savedHost } : {}), ...(apps ? { apps } : {}), methodSaved, ...(lastLock && !hostedPreviewProven ? { lock: lastLock === 'netlify-protection' && lastTeamLogin ? 'netlify-login' : lastLock } : {}), ...(hosts.length ? { hosts } : {}) });
|
|
2698
|
+
ctx.onReport({ markdown, left, testPr: drive, setupPr: ctx.sync.payload.setup_pr, ...(savedHost ? { hostedBy: savedHost } : {}), ...(apps ? { apps } : {}), methodSaved, ...(lastLock && !hostedPreviewProven ? { lock: lastLock === 'netlify-protection' && lastTeamLogin ? 'netlify-login' : lastLock } : {}), ...(!apps && checkWalls.size ? { walled: [...checkWalls.values()].at(-1) } : {}), ...(hosts.length ? { hosts } : {}) });
|
|
2677
2699
|
pi.shutdown();
|
|
2678
2700
|
const saved = res?.ok ? 'Saved.' : `The web page did not take it (${res?.error ?? 'no answer'}).`;
|
|
2679
2701
|
return { text: `${saved} The terminal shows the summary and the command exits.`, terminate: true };
|
|
@@ -2912,7 +2934,7 @@ export function createTools(ctx) {
|
|
|
2912
2934
|
fetch: ctx.previewFetch, signal, live: live ? (known?.host ?? true) : false,
|
|
2913
2935
|
// The project's bypass secret opens the project's own addresses: the previews it proved, and the production address read from the same project.
|
|
2914
2936
|
...(bypassFor(origin, live) ? { bypass: bypassFor(origin, live) } : {}),
|
|
2915
|
-
...(
|
|
2937
|
+
...(site && sitePasswords.has(site) ? { password: sitePasswords.get(site).value } : {}),
|
|
2916
2938
|
...(token ? { access: token } : {}),
|
|
2917
2939
|
});
|
|
2918
2940
|
// A live address proven is the before, and nothing else: no method, no pattern and no lock to get past follow from it.
|
|
@@ -2923,6 +2945,14 @@ export function createTools(ctx) {
|
|
|
2923
2945
|
}
|
|
2924
2946
|
// On a repository with several apps, the app the address was listed for: its proof, its lock and its key are that app's alone (#281).
|
|
2925
2947
|
const app = listedApps.get(origin);
|
|
2948
|
+
// A site's password opened the preview of an app it was not saved for (another app built from the same site): it is saved as that app's own before the proof stands, since that app's reviews read its own (#384).
|
|
2949
|
+
const held = site ? sitePasswords.get(site) : undefined;
|
|
2950
|
+
if (proof.proven && held && app && !held.apps.has(app.appKey)) {
|
|
2951
|
+
const saved = await saveLock(pi, 'password', { method: 'password', site: site }, held.value, app.appKey);
|
|
2952
|
+
if (saved.failed)
|
|
2953
|
+
return JSON.stringify({ proven: false, status: proof.status, appKey: app.appKey, reason: `The site's password opened the preview, but it is not saved for this app: ${saved.failed} Prove this address again to save it.` });
|
|
2954
|
+
held.apps.add(app.appKey);
|
|
2955
|
+
}
|
|
2926
2956
|
hostedPreviewProven ||= proof.proven;
|
|
2927
2957
|
if (proof.proven)
|
|
2928
2958
|
provenHost = previewHostName(url) ?? provenHost;
|
|
@@ -3279,7 +3309,7 @@ export function createTools(ctx) {
|
|
|
3279
3309
|
}
|
|
3280
3310
|
return JSON.stringify(outcome);
|
|
3281
3311
|
}),
|
|
3282
|
-
tool('impeccable_netlify_password', "Netlify's password", 'Get past the lock that impeccable_prove_preview met (gate "netlify-protection") in front of a Netlify deploy preview, signed in with Netlify\'s site password on Netlify\'s own form, as reviews sign in (no request header carries it). It does the rest with the Netlify CLI signed in as the person: it reads the site\'s protection from Netlify, which is what makes the site the person\'s (a site the CLI cannot read as them gets no password: refused, with the reason); for a password Netlify gives back, it shows one screen asking to store it for Impeccable, or, when Netlify does not give the password back, one screen taking a pasted one; opens the preview with it; and saves it for reviews. For a site behind Netlify\'s team login it shows one screen asking to make the site public, signs the Netlify CLI in, in the browser, on that yes where it is signed out, makes it public as the person, and where Netlify refuses opens Netlify\'s Visitor access page with a screen to try again once the person changed it there. Answers {"status": …}: saved (the preview is proven, so the method may be saved; password says whether it was existing or typed, and site which Netlify site it is), opened (the site is public and the preview proven, so the method may be saved; madePublic says whether the run or the person made it public, and on any other answer that the run made it public all the same), login (Netlify kept its team login in front after the retries; reason says so), skipped (the person skipped the screen; reason says what came before), refused (nothing opened the preview; reason says why) or save-failed (the setup API did not take it all; reason says how far it got: call it once more). No password ever comes back.', NO_INPUT, async (_p, pi, signal) => {
|
|
3312
|
+
tool('impeccable_netlify_password', "Netlify's password", 'Get past the lock that impeccable_prove_preview met (gate "netlify-protection") in front of a Netlify deploy preview, signed in with Netlify\'s site password on Netlify\'s own form, as reviews sign in (no request header carries it). It does the rest with the Netlify CLI signed in as the person: it reads the site\'s protection from Netlify, which is what makes the site the person\'s (a site the CLI cannot read as them gets no password: refused, with the reason); for a password Netlify gives back, it shows one screen asking to store it for Impeccable, or, when Netlify does not give the password back, one screen taking a pasted one (after one offering the password the person typed for another site of this run, where there is one); opens the preview with it; and saves it for reviews. For a site behind Netlify\'s team login it shows one screen asking to make the site public, signs the Netlify CLI in, in the browser, on that yes where it is signed out, makes it public as the person, and where Netlify refuses opens Netlify\'s Visitor access page with a screen to try again once the person changed it there. Answers {"status": …}: saved (the preview is proven, so the method may be saved; password says whether it was existing or typed, and site which Netlify site it is), opened (the site is public and the preview proven, so the method may be saved; madePublic says whether the run or the person made it public, and on any other answer that the run made it public all the same), login (Netlify kept its team login in front after the retries; reason says so), skipped (the person skipped the screen; reason says what came before), refused (nothing opened the preview; reason says why) or save-failed (the setup API did not take it all; reason says how far it got: call it once more). No password ever comes back.', NO_INPUT, async (_p, pi, signal) => {
|
|
3283
3313
|
const site = gated?.gate === 'netlify-protection' ? netlifySiteOf(gated.url) : null;
|
|
3284
3314
|
if (!gated || !site)
|
|
3285
3315
|
return 'Refused: this is for a Netlify deploy preview, listed by impeccable_netlify_previews, which impeccable_prove_preview answered with gate "netlify-protection". Prove one first.';
|
|
@@ -3307,12 +3337,20 @@ export function createTools(ctx) {
|
|
|
3307
3337
|
}
|
|
3308
3338
|
else if (stands)
|
|
3309
3339
|
return JSON.stringify(stands);
|
|
3310
|
-
// A password in hand already: another deploy preview of the same site opens with it. Another site's lock is read for that site.
|
|
3311
|
-
|
|
3340
|
+
// A password in hand already: another deploy preview of the same site opens with it, and another app built from the same site gets it saved as its own. Another site's lock is read for that site.
|
|
3341
|
+
const held = sitePasswords.get(site);
|
|
3342
|
+
if (held && (await prove(held.value)).proven) {
|
|
3343
|
+
const store = at.app?.appKey ?? '';
|
|
3344
|
+
if (!held.apps.has(store)) {
|
|
3345
|
+
const saved = await saveLock(pi, 'password', { method: 'password', site }, held.value, at.app?.appKey);
|
|
3346
|
+
if (saved.failed)
|
|
3347
|
+
return JSON.stringify({ status: 'save-failed', site, protection: 'password', reason: saved.failed });
|
|
3348
|
+
held.apps.add(store);
|
|
3349
|
+
}
|
|
3312
3350
|
provePattern(pattern, at.app?.appKey);
|
|
3313
3351
|
if (at.app)
|
|
3314
3352
|
proveApp(at.app.appKey, at.url);
|
|
3315
|
-
return JSON.stringify(
|
|
3353
|
+
return JSON.stringify(held.saved);
|
|
3316
3354
|
}
|
|
3317
3355
|
/** The store took this attempt's password: what was in hand no longer matches it, unless the setting followed. */
|
|
3318
3356
|
let stored = false;
|
|
@@ -3342,6 +3380,12 @@ export function createTools(ctx) {
|
|
|
3342
3380
|
return !(await ask({ ...screen, hint: `${screen.hint} If it did not open, open this link: ${page}` })).skipped;
|
|
3343
3381
|
},
|
|
3344
3382
|
paste: async () => {
|
|
3383
|
+
// One password for several sites is common: the one typed for another site of this run is offered first, once, never after it was refused here (#384).
|
|
3384
|
+
const other = passwordRefused.has(site) ? undefined : [...sitePasswords].find(([name, h]) => name !== site && h.typed);
|
|
3385
|
+
if (other && !(await ask(netlifySamePassword(site, other[0]))).skipped) {
|
|
3386
|
+
typed = true;
|
|
3387
|
+
return other[1].value;
|
|
3388
|
+
}
|
|
3345
3389
|
const screen = sentTo(HOST_SCREENS['netlify-password-key'], at.url);
|
|
3346
3390
|
const answer = await ask(passwordRefused.has(site) ? { ...screen, prompt: `The password typed last didn't open the preview on Netlify's form, so nothing was saved. Check it and type it again. ${screen.prompt}` } : screen);
|
|
3347
3391
|
typed = !answer.skipped;
|
|
@@ -3378,23 +3422,18 @@ export function createTools(ctx) {
|
|
|
3378
3422
|
provePattern(pattern, at.app?.appKey);
|
|
3379
3423
|
}
|
|
3380
3424
|
if (value) {
|
|
3381
|
-
// What is in hand follows the store:
|
|
3382
|
-
|
|
3383
|
-
passwordSites.clear();
|
|
3384
|
-
passwordSites.add(site);
|
|
3425
|
+
// What is in hand follows the store: the password saved now is this site's, beside every other site's.
|
|
3426
|
+
sitePasswords.set(site, { value, saved: passwordOutcome, typed: told.password === 'typed', apps: new Set([at.app?.appKey ?? '']) });
|
|
3385
3427
|
hostedPreviewProven = true;
|
|
3386
3428
|
provenHost = 'Netlify';
|
|
3387
3429
|
usedPreviews.add(at.url);
|
|
3388
3430
|
if (at.app)
|
|
3389
3431
|
proveApp(at.app.appKey, at.url);
|
|
3390
3432
|
provePattern(pattern, at.app?.appKey);
|
|
3391
|
-
passwordSaved = passwordOutcome;
|
|
3392
3433
|
}
|
|
3393
3434
|
else if (told.status === 'save-failed' && stored) {
|
|
3394
|
-
// The store holds the password this attempt wrote without the setting that sends it: nothing in hand stands until a save goes through.
|
|
3395
|
-
|
|
3396
|
-
passwordSites.clear();
|
|
3397
|
-
passwordSaved = undefined;
|
|
3435
|
+
// The store holds the password this attempt wrote without the setting that sends it: nothing in hand stands for this site until a save goes through.
|
|
3436
|
+
sitePasswords.delete(site);
|
|
3398
3437
|
}
|
|
3399
3438
|
return JSON.stringify(passwordOutcome);
|
|
3400
3439
|
}),
|