@impeccable/reviews 0.1.6 → 0.1.7

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.
Files changed (41) hide show
  1. package/README.md +21 -20
  2. package/dist/agent.js +9 -4
  3. package/dist/cloudflare-access.js +77 -47
  4. package/dist/cloudflare.js +325 -23
  5. package/dist/fence.js +939 -143
  6. package/dist/focus.js +5 -4
  7. package/dist/host-previews.js +107 -0
  8. package/dist/host-questions.js +90 -20
  9. package/dist/hosted.js +72 -21
  10. package/dist/http.js +2 -0
  11. package/dist/log.js +7 -1
  12. package/dist/login.js +45 -13
  13. package/dist/main.js +29 -14
  14. package/dist/netlify-password.js +8 -6
  15. package/dist/netlify.js +43 -14
  16. package/dist/pi-paths.js +69 -0
  17. package/dist/playbook/cases/cloudflare-previews.md +12 -4
  18. package/dist/playbook/cases/first-preview-failure.md +1 -1
  19. package/dist/playbook/cases/live-app-address.md +15 -0
  20. package/dist/playbook/cases/monorepo.md +1 -1
  21. package/dist/playbook/cases/netlify-previews.md +3 -1
  22. package/dist/playbook/cases/netlify-setup.md +7 -6
  23. package/dist/playbook/cases/preview-protection.md +2 -1
  24. package/dist/playbook/cases/pull-requests.md +3 -3
  25. package/dist/playbook/cases/static-site.md +2 -2
  26. package/dist/playbook/cases/vercel-previews.md +2 -0
  27. package/dist/playbook/cases/vercel-setup.md +8 -5
  28. package/dist/playbook.js +2 -2
  29. package/dist/preflight.js +36 -7
  30. package/dist/report.js +1 -0
  31. package/dist/sandbox.js +364 -50
  32. package/dist/scan.js +134 -0
  33. package/dist/sync.js +44 -7
  34. package/dist/tools.js +377 -88
  35. package/dist/ui/bridge.js +18 -4
  36. package/dist/ui/look.js +1 -1
  37. package/dist/ui/screens.js +5 -3
  38. package/dist/ui/sensitive.js +10 -0
  39. package/dist/vercel-bypass.js +12 -7
  40. package/dist/vercel.js +110 -17
  41. package/package.json +1 -1
@@ -9,8 +9,10 @@ Vercel deploys previews of the pull requests: impeccable_hosted_previews lists a
9
9
 
10
10
  ### Reference
11
11
 
12
+ - **The live app**: with the checkout linked to the project (`.vercel/project.json`) and a production deployment of it on GitHub's record, the evidence answers the project's own domain as `production`; live-app-address says how it becomes the before, in step 2's write. Unlinked, GitHub's record names no address of it that is open.
12
13
  - **The aim** holds here: the CLI does the work, and the person is asked only what the playbook's aim allows.
13
14
  - **Behind Vercel's login** (`gate: "vercel-protection"`): Deployment Protection stands in front of the previews. Get past it as preview-protection says, which proves the preview, and go on with step 2; when nothing got past it, that case says how the run ends.
15
+ - **`pullRequestPreviews` in impeccable_hosted_previews' answer**: what vercel.json and the linked project say of the test drive's branch, read with the evidence and never changed by the run, since the setting is the repository's own or has no command (impeccable_enable_previews takes no Vercel). `enabled: false` (`git.deploymentEnabled` in vercel.json turns the branch off, or Vercel records no deployments on GitHub, where Impeccable finds its previews): push no test drive on Vercel. Set the preview up as server-previews or static-site says, and say in one line under "What was skipped, and why" that Vercel builds no preview of the pull requests, with the `reason`, and the exact change for the person: the vercel.json rule to lift, or Vercel GitHub deployments under the project's Settings → Git. `enabled: 'unknown'` (an Ignored Build Step decides commit by commit, or the CLI could not read the project): go on, and say so in one line under "What is not verified".
14
16
  - **Another app, or no answer from any of the three**: Vercel's previews do not show this app. Set the preview up as server-previews or static-site says instead, and say under "What is not verified" what the Vercel address answered.
15
17
  - **The pull requests**: no setup pull request; the test drive branches from the default branch, and Vercel builds its preview from the push, which pull-requests' step 4 waits for and proves the test drive's page on before the pull request opens. In place of pull-requests' step 3, build the test drive branch once with the install and build Vercel runs (`vercel.json`'s `installCommand` and `buildCommand`, else the package's), as plain commands (`pnpm install`, then `pnpm run build`) and not with impeccable_verify_preview, which is for a preview Impeccable starts itself, so a page that does not build is never pushed.
16
18
  - The evidence is GitHub's: link, deploy and change nothing on Vercel, and save no address pattern, since Vercel reports each preview's address to GitHub itself.
@@ -4,20 +4,23 @@ The checkout is set up for Vercel (`vercel.json`, `.vercel/project.json`, a `ver
4
4
 
5
5
  ### Steps
6
6
 
7
- 1. **Sign in**, before any other preview work, with impeccable_ask: `kind: "confirm"`, `id: "vercel-setup"`. The tool does the rest: it shows the sign-in screen only when the Vercel CLI is signed out (a person signed in already is asked nothing), runs the login in the browser and offers a retry. Done when it answered `confirmed`, with `team` the slug the project goes into.
8
- - **`cli: "missing"`**: install the CLI with the person's package manager (`npm install -g vercel`, `pnpm add -g vercel`, `yarn global add vercel` or `bun add -g vercel`), then ask again: the screen shows no second time.
7
+ 1. **Sign in**, before any other preview work, with impeccable_ask: `kind: "confirm"`, `id: "vercel-setup"`. The tool does the rest: it shows the setup screen once (a person signed in already confirms the setup with that sign-in on it, with no login to run), runs the login in the browser when the Vercel CLI is signed out and offers a retry. Done when it answered `confirmed`, with `team` the slug the project goes into.
8
+ - **`cli: "missing"`**: install the CLI with `npm install -g vercel@62.7.0`, exactly so: the run installs it with npm alone, pinned to that version, and refuses another package manager, version or tag; then ask again: the screen shows no second time.
9
9
  - **`teams` in the answer instead of `team`** (several teams, none current): ask once which owns the project, `kind: "single"`, `id: "vercel-team"`, `options` the teams as given (the tool words the rest). The pick is the team.
10
10
  - **Neither `team` nor `teams`**: the account has no team, and `vercel link --yes` without `--team` links in the person's own scope.
11
11
  - **Skip**: set the preview up as server-previews or static-site says, and say under "What was skipped, and why" that Vercel was found and the sign-in was skipped. The steps below do not apply.
12
- 2. **Create the project** (run each Vercel command plain: its answer is short, and a pipe to `tail` is refused): `vercel link --yes --team <the team>` creates the project in that team, named after the folder (or links the one that has that name), and connects the GitHub `origin`; the CLI names no team on its own without a terminal, so the option is given whenever there is a team. Done when it prints the linked project.
13
- 3. **Connect the repository, and lock the project before its first deployment**, with impeccable_vercel_connect: it runs the connection on the linked project, asks the person for Vercel's GitHub app's access when the app cannot reach the repository, and then, on a project with no production deployment, puts Vercel's login in front of every address of the project, its own included. Vercel makes a new project's first deployment Production whatever branch it builds and serves it at the project's own address, and the test drive's push is that first deployment. When Vercel refuses the lock, the tool asks the person on one screen to turn it on in the project's settings and waits for it. Done when it answered `connected: true` with `production: "exists"` or `locked: true`; a project connected by the link already is, and it says so.
12
+ 2. **Create the project, then link it** (run each Vercel command plain, on its own: its answer is short, a pipe to `tail` is refused, and a command chained after it leaves the project unrecorded): `vercel project add <the folder's name> --team <the team>` creates the project in that team, under the name `vercel link` gives a project (the checkout folder's name, lowercase); then `vercel link --yes --team <the team>`, in the checkout's root (never with `--cwd` or after a `cd`), links the folder to the project of that name, which is now the run's. The CLI names no team on its own without a terminal, so the option is given whenever there is a team. Done when the link prints the linked project.
13
+ - **`vercel project add` fails** (the name is in use: the project is the person's, not this run's): run no link; the Vercel setup ends there, and with the checkout linked to no project its CLI stops and the hold on pushes goes. Go on as "A step fails before the test drive is pushed" says, naming the project under "What was skipped, and why". When `.vercel/project.json` was in the checkout before the run, that link is not this run's, and the hold on pushes stays, by design: no test drive, as "No preview for the pushed test drive" says, and the report says under "What was skipped, and why" that the person removes `.vercel/project.json` or links the project themselves and runs the setup again.
14
+ 3. **Connect the repository, and lock the project before its first deployment**, with impeccable_vercel_connect: it runs the connection on the linked project, when that project is the one step 2 created or one connected to this repository already, asks the person for Vercel's GitHub app's access when the app cannot reach the repository, and then, on a project with no production deployment, puts Vercel's login in front of every address of the project, its own included. Vercel makes a new project's first deployment Production whatever branch it builds and serves it at the project's own address, and the test drive's push is that first deployment. When Vercel refuses the lock, the tool asks the person on one screen to turn it on in the project's settings and waits for it. Done when it answered `connected: true` with `production: "exists"` or `locked: true`; a project connected by the link already is, and it says so.
14
15
  4. **Build and push the test drive** (the Test drive task) as pull-requests' steps 2 to 4 say, with the changes vercel-previews' reference gives: from the default branch, no setup pull request, built once with plain commands. Stage the new page and the page that links to it by name: `vercel link` writes `.vercel/` and a `.env.local` that holds a Vercel token, and may add `.vercel` to `.gitignore`, and none of that goes into the pull request. Its push starts the project's first deployment. Done when `git push -u origin impeccable/test-drive` succeeded.
15
16
  5. **Its preview, proven and saved, then its pull request** as pull-requests' step 4 (Vercel) and step 5 say: the wait for the pushed head's deployment, the test drive's page proven on the address it lists (past Vercel's login as preview-protection says), impeccable_write to `preview` as vercel-previews' step 2 says, then the pull request and its recording. The playbook's step 6 then has no pull request left to open: go on with the playbook's step 4. Done when impeccable_test_pr answered "Recorded".
16
17
 
17
18
  ### Reference
18
19
 
20
+ - **The live app**: a project created here has none yet, which the report says; one that deploys production already (`production: "exists"`) answers its address as `productionUrl`, and live-app-address says how it becomes the before.
19
21
  - **The aim** holds here: the CLI does the work, and the person is asked only what the playbook's aim allows.
20
- - **A step fails before the test drive is pushed** (the sign-in skipped or not completed, the team skipped, the project not created, the repository not connected): the Vercel setup ends there. Set the preview up as server-previews or static-site says, say under "What was skipped, and why" what stopped it, and put the person's step in `left` when there is one, such as "Give Vercel's GitHub app access to the repository", with the `access` page in the report.
22
+ - **A step fails before the test drive is pushed** (the sign-in skipped or not completed, the team skipped, the project not created, the repository not connected, or step 3 answering that the linked project is not this run's: `vercel link` linked one of the person's projects, named like the folder, which is left as it is): the Vercel setup ends there. Set the preview up as server-previews or static-site says, say under "What was skipped, and why" what stopped it, and put the person's step in `left` when there is one, such as "Give Vercel's GitHub app access to the repository", with the `access` page in the report.
23
+ - **`pullRequestPreviews` in impeccable_hosted_previews' answer**: what vercel.json and the linked project say of the test drive's branch, read with the evidence and never changed by the run, since the setting is the repository's own or has no command (impeccable_enable_previews takes no Vercel). `enabled: false` (`git.deploymentEnabled` in vercel.json turns the branch off, or Vercel records no deployments on GitHub, where Impeccable finds its previews): the Vercel setup ends before step 4, as "A step fails before the test drive is pushed" says, and say in one line under "What was skipped, and why" that Vercel builds no preview of the pull requests, with the `reason`, and the exact change for the person: the vercel.json rule to lift, or Vercel GitHub deployments under the project's Settings → Git. `enabled: 'unknown'` (an Ignored Build Step decides commit by commit, or the CLI could not read the project): go on, and say so in one line under "What is not verified".
21
24
  - **A push before step 3** is refused, since a connected project builds every push: run impeccable_vercel_connect first.
22
25
  - **`locked: false`** from step 3, with `production` not "exists" (the person skipped the tool's screen, or the lock was still off after its retries): push nothing, and set up no other preview either, since the connected project would build that push as its first deployment, public at its own address. Mark the Test drive task failed, and say under "What was skipped, and why" what stopped the lock (the answer's `reason`).
23
26
  - **No preview for the pushed test drive**: save no method, set up no other preview and open no pull request, as pull-requests' "No preview, no test drive" says; the project and the connection stay, and the report names them.
package/dist/playbook.js CHANGED
@@ -15,13 +15,13 @@ export function playbook(repo) {
15
15
  return `You are Impeccable's setup agent, in a checkout of ${repo.owner}/${repo.name} on the person's own machine. This setup covers one situation: Impeccable previews the repository's web front end, taking the previews its host deploys for pull requests (vercel-previews, netlify-previews, cloudflare-previews, or vercel-setup and netlify-setup, which turn them on) or, for a static site, building it itself through the workflow the setup state generates (\`static-upload\`), and signs in with the app's own username-or-email-and-password accounts when it has a sign-in. You read files and run commands here, save Impeccable's settings with the impeccable_* tools, and open pull requests as the person. The person watches this terminal, and a web page mirrors your tasks and the report.
16
16
 
17
17
  # The aim
18
- Set the repository up with whatever the host's CLI can do (Netlify's, Vercel's, Cloudflare's, signed in as the person), through the commands the fence runs and the impeccable_* tools, and find your own way past a roadblock instead of handing it to the person. Ask the fewest questions possible: a sign-in where a CLI is signed out, and a yes before anything in their account changes (*"Make your previews public, so reviews can open them?"*). **A step for the person is the last resort**, only where no command the fence runs and no tool can do it; a refusal is final, as Commands says.
18
+ Set the repository up with whatever the host's CLI can do (Netlify's, Vercel's, Cloudflare's, signed in as the person), through the commands the fence runs and the impeccable_* tools, and find your own way past a roadblock instead of handing it to the person. Ask the fewest questions possible: a sign-in where a CLI is signed out, and a yes before anything in their account changes (*"Make your previews public, so reviews can open them?"*). Set the live app's address as the before yourself where the host or the README names it (live-app-address): the host's own address of the connected project changes nothing of theirs, so it takes no question. **A step for the person is the last resort**, only where no command the fence runs and no tool can do it; a refusal is final, as Commands says.
19
19
 
20
20
  # Steps
21
21
 
22
22
  1. **Read the state.** The setup API's instructions and state came with the first message, trimmed to this setup; impeccable_state reads the state again. Done when you know the preview item's status, \`file\` and \`workflowStanding\`, whether there is a \`signin\` item, whether there is an \`apps\` item (the monorepo case comes first then), and the titles in \`notPartOfThisSetup\`.
23
23
  2. **Read the repository**: manifests, the lockfile, scripts, framework config, CI workflows, env examples, the routes, the auth code and the seed. Done when you can name the package manager, the install, build and start commands, whether the pages need a server, how people sign in, and which pages a visitor opens without signing in.
24
- 3. **Set up the preview** as the vercel-previews case says when impeccable_hosted_previews lists Vercel's previews of the pull requests, as the netlify-previews case says when impeccable_netlify_previews lists Netlify's (and as preview-protection says when the host's login or password stands in front of either), as the cloudflare-previews case says when impeccable_cloudflare_previews lists Cloudflare Pages', as the vercel-setup case says when the checkout is set up for Vercel and Vercel has never deployed it, as the netlify-setup case says when the checkout is set up for Netlify and it lists none, else as the server-previews case says when the pages need a server, and as the static-site case says when they do not. Done when that case's steps are done.
24
+ 3. **Set up the preview** as the vercel-previews case says when impeccable_hosted_previews lists Vercel's previews of the pull requests, as the netlify-previews case says when impeccable_netlify_previews lists Netlify's (and as preview-protection says when the host's login or password stands in front of either), as the cloudflare-previews case says when impeccable_cloudflare_previews lists Cloudflare Pages', as the vercel-setup case says when the checkout is set up for Vercel and Vercel has never deployed it, as the netlify-setup case says when the checkout is set up for Netlify and it lists none, else as the server-previews case says when the pages need a server, and as the static-site case says when they do not; then the live app's address as the live-app-address case says, in the method's write where it can be. Done when those cases' steps are done.
25
25
  4. **Set up the sign-in** as the sign-in case says, when the app has its own. Done when that case's steps are done.
26
26
  5. **Run impeccable_checkup**, do what \`next\` says, and run it again. Done when \`status\` is complete, or all that is left is in \`forPerson\` or \`waitingFor\`.
27
27
  6. **Open the pull requests** as the pull-requests case says. What waits on the person holds neither back. Done when that case's steps are done.
package/dist/preflight.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { execFileSync } from 'node:child_process';
2
- import { existsSync, readFileSync, statSync } from 'node:fs';
2
+ import { closeSync, constants, existsSync, fstatSync, openSync, readFileSync, statSync } from 'node:fs';
3
3
  import { join, resolve } from 'node:path';
4
4
  import { parseArgs } from 'node:util';
5
5
  import { UserError } from './log.js';
@@ -31,10 +31,13 @@ Options:
31
31
  where, what it did; never the text) to a JSON file beside
32
32
  the transcript, and print where it is
33
33
  --frames <dir> write each screen into this folder as it changes (for evals)
34
+ --no-sandbox run the setup's commands without a sandbox, with all your
35
+ access, where the command would otherwise stop (Linux without
36
+ bubblewrap, socat and ripgrep; other systems)
34
37
  --no-telemetry report none of the command's steps to Impeccable
35
38
  (DO_NOT_TRACK=1 does the same)
36
39
  -h, --help show this help`;
37
- const LOOPBACK = new Set(['localhost', '127.0.0.1', '[::1]']);
40
+ export const LOOPBACK = new Set(['localhost', '127.0.0.1', '[::1]']);
38
41
  export function parseFlags(argv, env = process.env, cwd = process.cwd()) {
39
42
  let values;
40
43
  try {
@@ -52,6 +55,7 @@ export function parseFlags(argv, env = process.env, cwd = process.cwd()) {
52
55
  model: { type: 'string' },
53
56
  effort: { type: 'string', default: 'high' },
54
57
  yes: { type: 'boolean', default: false },
58
+ 'no-sandbox': { type: 'boolean', default: false },
55
59
  'no-telemetry': { type: 'boolean', default: false },
56
60
  help: { type: 'boolean', short: 'h', default: false },
57
61
  },
@@ -88,7 +92,7 @@ export function parseFlags(argv, env = process.env, cwd = process.cwd()) {
88
92
  const installDir = named?.trim() ? { path: resolve(cwd, named), from: values['install-dir'] !== undefined ? '--install-dir' : INSTALL_DIR_ENV } : undefined;
89
93
  // Paths from the folder the command is run in too: it goes on from the checkout's top.
90
94
  const path = (value) => (value === undefined ? undefined : resolve(cwd, value.trim()));
91
- return { host: host.origin, installDir, debug: values.debug, transcript: path(values.transcript), scanReport: values['scan-report'], frames: path(values.frames), model: values.model?.trim(), effort: values.effort, yes: values.yes, noTelemetry: values['no-telemetry'], help: values.help };
95
+ return { host: host.origin, installDir, debug: values.debug, transcript: path(values.transcript), scanReport: values['scan-report'], frames: path(values.frames), model: values.model?.trim(), effort: values.effort, yes: values.yes, noSandbox: values['no-sandbox'], noTelemetry: values['no-telemetry'], help: values.help };
92
96
  }
93
97
  const NOT_A_CHECKOUT = { event: 'setup_command_check_failed', outcome: 'not_a_checkout' };
94
98
  /** owner/repo from a GitHub remote address, in any of the forms git accepts. */
@@ -118,6 +122,15 @@ function defaultBranch(cwd, git) {
118
122
  return '';
119
123
  }
120
124
  }
125
+ /**
126
+ * Windows starts a command named without a folder (git, gh, vercel) from the working folder before PATH, so a git.exe
127
+ * committed to the checkout would run as the person before anything is asked. Windows reads this setting from the
128
+ * environment of the process that starts the command, so it goes on this one, before its first command; every command
129
+ * it starts inherits it.
130
+ */
131
+ export function skipWorkingFolderForCommands(env = process.env) {
132
+ env.NoDefaultCurrentDirectoryInExePath = '1';
133
+ }
121
134
  /**
122
135
  * The checkout `cwd` is in. `from` is what named that folder (--install-dir or its environment variable) when it is not
123
136
  * the one the command is run in: the folder is then checked to be there, and what is wrong with it names it.
@@ -172,6 +185,8 @@ const MARKUP = /\.(html?|vue|svelte|astro|php|erb|ejs|hbs|handlebars|mustache|nj
172
185
  const STYLES = /\.(css|scss|sass|less|styl|pcss)$/i;
173
186
  /** Manifests of a project in some language: without one, a checkout with no page in it says nothing. */
174
187
  const MANIFEST = /(^|\/)(package\.json|pubspec\.yaml|go\.mod|Cargo\.toml|pyproject\.toml|requirements\.txt|setup\.py|Pipfile|Gemfile|pom\.xml|build\.gradle(\.kts)?|Package\.swift|mix\.exs|[^/]+\.csproj|project\.pbxproj|AndroidManifest\.xml)$/;
188
+ /** More than any manifest a person writes. */
189
+ const MANIFEST_BYTES = 1024 * 1024;
175
190
  /** The documents every repository has; other Markdown may be a site's own pages (Jekyll, MkDocs, Hugo). */
176
191
  const REPOSITORY_DOCS = /(^|\/)(README|CHANGELOG|CHANGES|HISTORY|LICEN[CS]E|CONTRIBUTING|CODE_OF_CONDUCT|SECURITY|SUPPORT|AUTHORS|NOTICE)[^/]*$|(^|\/)\.github\//i;
177
192
  /** Folders of other people's code, and the shells of the native platforms. */
@@ -186,7 +201,7 @@ const NATIVE_SHELL = /(^|\/)(ios|android|macos|windows|linux)\//;
186
201
  * (react-native-web, Flutter's web folder) may or may not ship it, and Markdown
187
202
  * pages may be a generated site, so those and every other doubt answer "unknown".
188
203
  */
189
- export function checkoutFrontEnd(root) {
204
+ export function checkoutFrontEnd(root, log) {
190
205
  let paths;
191
206
  try {
192
207
  paths = execFileSync('git', ['ls-files', '-z', '--cached', '--others', '--exclude-standard'], { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 256 * 1024 * 1024 })
@@ -196,13 +211,27 @@ export function checkoutFrontEnd(root) {
196
211
  catch {
197
212
  return 'unknown';
198
213
  }
214
+ // A manifest that is a pipe, a device or a huge file would hang or swamp the command before anything is asked: it is skipped.
215
+ // O_NONBLOCK lets a pipe open without a writer, so what it is can be seen first. One not read leaves the answer in doubt.
216
+ let unread = false;
199
217
  const read = (path) => {
218
+ let fd;
200
219
  try {
201
- return readFileSync(join(root, path), 'utf8');
220
+ fd = openSync(join(root, path), constants.O_RDONLY | (constants.O_NONBLOCK ?? 0));
221
+ const stat = fstatSync(fd);
222
+ if (stat.isFile() && stat.size <= MANIFEST_BYTES)
223
+ return readFileSync(fd, 'utf8');
224
+ log?.write('front end: skipped a manifest', { path, file: stat.isFile(), size: stat.size });
202
225
  }
203
226
  catch {
204
- return null;
227
+ // Unreadable: in doubt as well.
228
+ }
229
+ finally {
230
+ if (fd !== undefined)
231
+ closeSync(fd);
205
232
  }
233
+ unread = true;
234
+ return null;
206
235
  };
207
236
  let reactNative = false;
208
237
  let flutter = false;
@@ -243,7 +272,7 @@ export function checkoutFrontEnd(root) {
243
272
  }
244
273
  if (web)
245
274
  return 'web';
246
- if (webTarget || (!native && paths.some((p) => /\.(md|mdx|markdown)$/i.test(p) && !REPOSITORY_DOCS.test(p))))
275
+ if (unread || webTarget || (!native && paths.some((p) => /\.(md|mdx|markdown)$/i.test(p) && !REPOSITORY_DOCS.test(p))))
247
276
  return 'unknown';
248
277
  return native || paths.some((p) => MANIFEST.test(p)) ? 'none' : 'unknown';
249
278
  }
package/dist/report.js CHANGED
@@ -35,6 +35,7 @@ export function createReporter(opts) {
35
35
  method: 'POST',
36
36
  headers: { 'content-type': 'application/json', ...(token ? { authorization: `Bearer ${token}` } : {}) },
37
37
  body: JSON.stringify({ ...about, events }),
38
+ redirect: 'error',
38
39
  signal: AbortSignal.timeout(SEND_MS),
39
40
  }).catch(() => undefined);
40
41
  sending.add(sent);