@impeccable/reviews 0.1.7 → 0.1.9
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/README.md +1 -0
- package/dist/bin.js +0 -0
- package/dist/cloudflare.js +26 -4
- package/dist/host-previews.js +16 -0
- package/dist/host-questions.js +21 -1
- package/dist/hosted.js +3 -3
- package/dist/main.js +16 -5
- package/dist/netlify.js +16 -3
- package/dist/playbook/cases/monorepo.md +8 -8
- package/dist/playbook/cases/preview-protection.md +2 -2
- package/dist/playbook/cases/vercel-previews.md +3 -1
- package/dist/playbook/cases/vercel-setup.md +2 -1
- package/dist/playbook.js +1 -1
- package/dist/preflight.js +8 -2
- package/dist/tools.js +486 -103
- package/dist/vercel-bypass.js +14 -10
- package/dist/vercel.js +43 -6
- package/package.json +1 -1
- package/dist/playbook/github-previews.js +0 -101
- package/dist/playbook/netlify-pattern.js +0 -24
- package/dist/playbook/netlify-previews.js +0 -144
- package/dist/playbook/probe-preview.js +0 -67
- package/dist/playbook/protection-secrets.js +0 -15
- package/dist/playbook/public-preview-request.js +0 -96
- package/dist/playbook/publication-wait.js +0 -46
- package/dist/playbook/vercel-protection.js +0 -121
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { setTimeout as sleep } from 'node:timers/promises';
|
|
2
|
-
const INTERVAL_MS = 30_000;
|
|
3
|
-
const DEADLINE_MS = 8 * 60_000;
|
|
4
|
-
/** One publication deadline per setup PR, including time spent checking the preview. */
|
|
5
|
-
export function createPublicationWait(dependencies = {}) {
|
|
6
|
-
const now = dependencies.now ?? (() => performance.now());
|
|
7
|
-
const pause = dependencies.sleep ?? ((ms, signal) => sleep(ms, undefined, { signal }));
|
|
8
|
-
const starts = new Map();
|
|
9
|
-
return {
|
|
10
|
-
begin(pr) {
|
|
11
|
-
if (!starts.has(pr))
|
|
12
|
-
starts.set(pr, now());
|
|
13
|
-
},
|
|
14
|
-
remainingMs(pr) {
|
|
15
|
-
const start = starts.get(pr);
|
|
16
|
-
return start === undefined ? null : Math.max(0, DEADLINE_MS - (now() - start));
|
|
17
|
-
},
|
|
18
|
-
async wait(pr, signal) {
|
|
19
|
-
const start = starts.get(pr);
|
|
20
|
-
if (start === undefined)
|
|
21
|
-
return null;
|
|
22
|
-
const result = (status) => {
|
|
23
|
-
const elapsed = Math.max(0, now() - start);
|
|
24
|
-
return {
|
|
25
|
-
status,
|
|
26
|
-
waited_seconds: Math.floor(elapsed / 1000),
|
|
27
|
-
remaining_seconds: Math.ceil(Math.max(0, DEADLINE_MS - elapsed) / 1000),
|
|
28
|
-
};
|
|
29
|
-
};
|
|
30
|
-
if (signal?.aborted)
|
|
31
|
-
return result('cancelled');
|
|
32
|
-
const remaining = DEADLINE_MS - (now() - start);
|
|
33
|
-
if (remaining <= 0)
|
|
34
|
-
return result('timed-out');
|
|
35
|
-
try {
|
|
36
|
-
await pause(Math.min(INTERVAL_MS, remaining), signal);
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
return result('cancelled');
|
|
40
|
-
}
|
|
41
|
-
if (signal?.aborted)
|
|
42
|
-
return result('cancelled');
|
|
43
|
-
return result(now() - start >= DEADLINE_MS ? 'timed-out' : 'retry');
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { execFile } from 'node:child_process';
|
|
2
|
-
import { constants } from 'node:fs';
|
|
3
|
-
import { lstat, open, realpath } from 'node:fs/promises';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
const METADATA_LIMIT = 16 * 1024;
|
|
6
|
-
const OUTPUT_LIMIT = 2 * 1024 * 1024;
|
|
7
|
-
function record(value) {
|
|
8
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
9
|
-
? value : undefined;
|
|
10
|
-
}
|
|
11
|
-
/** Only the root's small project-link metadata is read; CLI authentication stays with the CLI. */
|
|
12
|
-
async function linkedProject(cwd) {
|
|
13
|
-
try {
|
|
14
|
-
const root = await realpath(cwd);
|
|
15
|
-
const directory = join(root, '.vercel');
|
|
16
|
-
const directoryStat = await lstat(directory);
|
|
17
|
-
if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink() || await realpath(directory) !== directory)
|
|
18
|
-
return;
|
|
19
|
-
const path = join(directory, 'project.json');
|
|
20
|
-
if (!(await lstat(path)).isFile())
|
|
21
|
-
return;
|
|
22
|
-
const file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
23
|
-
try {
|
|
24
|
-
const stat = await file.stat();
|
|
25
|
-
const currentDirectory = await lstat(directory);
|
|
26
|
-
const currentFile = await lstat(path);
|
|
27
|
-
if (!stat.isFile() || stat.size > METADATA_LIMIT
|
|
28
|
-
|| currentDirectory.dev !== directoryStat.dev || currentDirectory.ino !== directoryStat.ino
|
|
29
|
-
|| currentFile.isSymbolicLink() || currentFile.dev !== stat.dev || currentFile.ino !== stat.ino
|
|
30
|
-
|| await realpath(path) !== path)
|
|
31
|
-
return;
|
|
32
|
-
const buffer = Buffer.alloc(METADATA_LIMIT + 1);
|
|
33
|
-
const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
|
|
34
|
-
if (bytesRead > METADATA_LIMIT)
|
|
35
|
-
return;
|
|
36
|
-
const metadata = record(JSON.parse(buffer.toString('utf8', 0, bytesRead)));
|
|
37
|
-
if (typeof metadata?.projectId !== 'string' || !/^prj_[A-Za-z0-9]{1,128}$/.test(metadata.projectId)
|
|
38
|
-
|| typeof metadata.orgId !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/.test(metadata.orgId))
|
|
39
|
-
return;
|
|
40
|
-
return { root, projectId: metadata.projectId, orgId: metadata.orgId };
|
|
41
|
-
}
|
|
42
|
-
finally {
|
|
43
|
-
await file.close();
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
/** Reuse one existing default automation bypass in memory, without exposing project responses or keys. */
|
|
51
|
-
export async function useExistingVercelBypass(input, dependencies) {
|
|
52
|
-
const { signal } = dependencies;
|
|
53
|
-
if (signal?.aborted)
|
|
54
|
-
return { status: 'cancelled' };
|
|
55
|
-
if (!/^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,37}[a-zA-Z0-9])?$/.test(input.owner)
|
|
56
|
-
|| !/^[a-zA-Z0-9_.-]{1,100}$/.test(input.repo) || input.repo === '.' || input.repo === '..') {
|
|
57
|
-
return { status: 'project-mismatch' };
|
|
58
|
-
}
|
|
59
|
-
const linked = await linkedProject(input.cwd);
|
|
60
|
-
if (signal?.aborted)
|
|
61
|
-
return { status: 'cancelled' };
|
|
62
|
-
if (!linked)
|
|
63
|
-
return { status: 'not-linked' };
|
|
64
|
-
const runVercel = dependencies.runVercel ?? ((args) => new Promise((resolve, reject) => {
|
|
65
|
-
const child = execFile('vercel', args, {
|
|
66
|
-
cwd: linked.root, encoding: 'utf8', timeout: 30_000, maxBuffer: OUTPUT_LIMIT, signal,
|
|
67
|
-
}, (error, stdout) => error ? reject(new Error('Vercel project lookup failed.')) : resolve(stdout));
|
|
68
|
-
child.stdin?.end();
|
|
69
|
-
}));
|
|
70
|
-
// teamId selects the linked team. Legacy personal projects omit it and still require accountId to match.
|
|
71
|
-
const route = `/v9/projects/${linked.projectId}${linked.orgId.startsWith('team_') ? `?teamId=${linked.orgId}` : ''}`;
|
|
72
|
-
let selected;
|
|
73
|
-
try {
|
|
74
|
-
const stdout = await runVercel(['api', route, '--method', 'GET', '--raw', '--non-interactive'], signal);
|
|
75
|
-
if (signal?.aborted)
|
|
76
|
-
return { status: 'cancelled' };
|
|
77
|
-
if (Buffer.byteLength(stdout, 'utf8') > OUTPUT_LIMIT)
|
|
78
|
-
return { status: 'unavailable' };
|
|
79
|
-
const project = record(JSON.parse(stdout));
|
|
80
|
-
const link = record(project?.link);
|
|
81
|
-
if (project?.id !== linked.projectId || project.accountId !== linked.orgId
|
|
82
|
-
|| !link || !['github', 'github-limited'].includes(String(link.type))
|
|
83
|
-
|| typeof link.org !== 'string' || link.org.toLowerCase() !== input.owner.toLowerCase()
|
|
84
|
-
|| typeof link.repo !== 'string' || link.repo.toLowerCase() !== input.repo.toLowerCase()) {
|
|
85
|
-
return { status: 'project-mismatch' };
|
|
86
|
-
}
|
|
87
|
-
const bypasses = record(project.protectionBypass);
|
|
88
|
-
if (!bypasses || Object.keys(bypasses).length > 128)
|
|
89
|
-
return { status: 'unavailable' };
|
|
90
|
-
const candidates = [];
|
|
91
|
-
for (const [value, metadata] of Object.entries(bypasses)) {
|
|
92
|
-
const bypass = record(metadata);
|
|
93
|
-
if (bypass?.scope !== 'automation-bypass')
|
|
94
|
-
continue;
|
|
95
|
-
if (!/^[A-Za-z0-9]{32}$/.test(value)
|
|
96
|
-
|| (bypass.isEnvVar !== undefined && typeof bypass.isEnvVar !== 'boolean'))
|
|
97
|
-
return { status: 'unavailable' };
|
|
98
|
-
// An omitted isEnvVar is the legacy default. Explicit false keys must never be chosen implicitly.
|
|
99
|
-
if (bypass.isEnvVar !== false)
|
|
100
|
-
candidates.push(value);
|
|
101
|
-
}
|
|
102
|
-
if (candidates.length > 1)
|
|
103
|
-
return { status: 'ambiguous' };
|
|
104
|
-
if (candidates.length === 0)
|
|
105
|
-
return { status: 'unavailable' };
|
|
106
|
-
selected = candidates[0];
|
|
107
|
-
dependencies.rememberSecret(selected);
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
return { status: signal?.aborted ? 'cancelled' : 'unavailable' };
|
|
111
|
-
}
|
|
112
|
-
if (signal?.aborted)
|
|
113
|
-
return { status: 'cancelled' };
|
|
114
|
-
try {
|
|
115
|
-
const status = await dependencies.save(selected);
|
|
116
|
-
return { status: ['saved', 'save-failed', 'partial-save', 'cancelled'].includes(status) ? status : 'save-failed' };
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
return { status: signal?.aborted ? 'cancelled' : 'save-failed' };
|
|
120
|
-
}
|
|
121
|
-
}
|