@autter/runtime-cli 1.0.0 → 1.2.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/README.md +97 -19
- package/dist/{build-BXFh_jbP.d.cts → build-BqjqqeLg.d.cts} +35 -9
- package/dist/{build-BXFh_jbP.d.ts → build-BqjqqeLg.d.ts} +35 -9
- package/dist/chunk-KIQRQY3X.js +1044 -0
- package/dist/{chunk-GGHHEUPQ.js → chunk-SIL3WZZ3.js} +26 -8
- package/dist/cli.js +2394 -154
- package/dist/index.cjs +817 -60
- package/dist/index.d.cts +111 -2
- package/dist/index.d.ts +111 -2
- package/dist/index.js +261 -2
- package/dist/next.cjs +645 -104
- package/dist/next.d.cts +3 -2
- package/dist/next.d.ts +3 -2
- package/dist/next.js +27 -8
- package/dist/vite.cjs +622 -95
- package/dist/vite.d.cts +3 -2
- package/dist/vite.d.ts +3 -2
- package/dist/vite.js +32 -6
- package/dist/webpack.cjs +615 -95
- package/dist/webpack.d.cts +11 -4
- package/dist/webpack.d.ts +11 -4
- package/dist/webpack.js +2 -2
- package/package.json +6 -1
- package/dist/chunk-SIRFO2QU.js +0 -524
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as AutterSourceMapsOptions, B as BUNDLE_RE, a as BuildOutputResult, b as BundleWithMap, D as DEFAULT_ENDPOINT, F as FatalUploadError, I as InjectOptions, c as InjectResult, M as MAX_UPLOAD_BYTES, S as SNIPPET_LINE_KEY, U as UploadItem,
|
|
1
|
+
export { A as AutterSourceMapsOptions, B as BUNDLE_RE, a as BuildOutputResult, b as BundleWithMap, C as CLIENT_KEY_PREFIX, D as DEFAULT_ENDPOINT, F as FatalUploadError, I as InjectOptions, c as InjectResult, K as KEY_ADVICE, M as MAX_MAP_CHARS, d as MAX_UPLOAD_BYTES, S as SERVER_KEY_PREFIX, e as SNIPPET_LINE_KEY, U as UPLOAD_KEY_PREFIX, f as UploadItem, g as UploadOptions, h as UploadSummary, i as findBundles, j as formatUploadSummary, k as injectBundle, l as injectDir, m as mapPathFor, p as processBuildOutput, r as readSourceMappingUrl, n as resolveEndpoint, s as servedFilename, o as shiftMappings, q as snippetLine, t as storedMapJson, u as stripSourceMappingUrl, v as uploadDir } from './build-BqjqqeLg.cjs';
|
|
2
2
|
|
|
3
3
|
/** 8-4-4-4-12 hex: the debug id shape shared with Rollup, Turbopack, esbuild
|
|
4
4
|
* and Sentry tooling, and what the ingester accepts. */
|
|
@@ -28,6 +28,115 @@ declare function debugIdSnippet(id: string): string;
|
|
|
28
28
|
* contains it too.) */
|
|
29
29
|
declare function hasDebugIdSnippet(code: string): boolean;
|
|
30
30
|
|
|
31
|
+
interface VerifyOptions {
|
|
32
|
+
/** Ingester base URL (default as for upload). */
|
|
33
|
+
endpoint?: string;
|
|
34
|
+
/** Default: $AUTTER_RUNTIME_KEY. */
|
|
35
|
+
key?: string;
|
|
36
|
+
/** Sent with the check (a debug id check ignores it). */
|
|
37
|
+
release?: string;
|
|
38
|
+
urlPrefix?: string;
|
|
39
|
+
/** Globs (relative to the directory) of JS files that are not bundles. */
|
|
40
|
+
ignore?: string[];
|
|
41
|
+
/** Report leftover .map files without failing (a step that runs before they are removed). */
|
|
42
|
+
allowMaps?: boolean;
|
|
43
|
+
env?: NodeJS.ProcessEnv;
|
|
44
|
+
cwd?: string;
|
|
45
|
+
fetch?: typeof fetch;
|
|
46
|
+
}
|
|
47
|
+
interface VerifyBundle {
|
|
48
|
+
/** Relative to the directory. */
|
|
49
|
+
path: string;
|
|
50
|
+
status: "ok" | "failed" | "skipped";
|
|
51
|
+
debugId: string | null;
|
|
52
|
+
/** Why it failed, or why it was skipped. */
|
|
53
|
+
reasons: string[];
|
|
54
|
+
/** Whether the ingester has a map for its debug id (null: not checked). */
|
|
55
|
+
uploaded: boolean | null;
|
|
56
|
+
}
|
|
57
|
+
interface VerifyResult {
|
|
58
|
+
dir: string;
|
|
59
|
+
ok: boolean;
|
|
60
|
+
bundles: VerifyBundle[];
|
|
61
|
+
/** `.map` files in the directory (deploy risks unless allowMaps). */
|
|
62
|
+
maps: string[];
|
|
63
|
+
upload: {
|
|
64
|
+
/** verified: every checked bundle's map is on the ingester; missing: some are not;
|
|
65
|
+
* unverified: not checked (no key, or an ingester that can't answer); error: the check failed. */
|
|
66
|
+
status: "verified" | "missing" | "unverified" | "error";
|
|
67
|
+
endpoint: string | null;
|
|
68
|
+
reason: string | null;
|
|
69
|
+
checked: number;
|
|
70
|
+
missing: number;
|
|
71
|
+
};
|
|
72
|
+
/** Every failure, one per file and reason. */
|
|
73
|
+
failures: Array<{
|
|
74
|
+
path: string;
|
|
75
|
+
reason: string;
|
|
76
|
+
}>;
|
|
77
|
+
}
|
|
78
|
+
/** A tiny glob: `**` any path, `*` any name part, `?` one character. A
|
|
79
|
+
* pattern without "/" matches the file name anywhere. */
|
|
80
|
+
declare function globToRegExp(glob: string): RegExp;
|
|
81
|
+
declare function verifyDir(dir: string, options?: VerifyOptions): Promise<VerifyResult>;
|
|
82
|
+
/** Human-readable report. */
|
|
83
|
+
declare function formatVerify(result: VerifyResult): string[];
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Function scopes for scope-based frame names (like Sentry's
|
|
87
|
+
* js-source-scopes and PostHog cymbal's `token.scope()`).
|
|
88
|
+
*
|
|
89
|
+
* The ingester used to name a resolved frame with the name the source map
|
|
90
|
+
* holds at its CALLER's position. That is right for `computeTotal()`, and
|
|
91
|
+
* wrong whenever the call goes through a variable: `const run =
|
|
92
|
+
* actions.captured; run()` reported `run`, and handlers a framework calls
|
|
93
|
+
* got the framework's identifier (`func`, `listener`, `fn`). So `upload`
|
|
94
|
+
* parses each bundle (the final file the browser loads, after `inject`),
|
|
95
|
+
* collects every function's generated range and the token that names it,
|
|
96
|
+
* maps that token to its ORIGINAL identifier, and sends the result with the
|
|
97
|
+
* map as `x_autter_scopes`. The ingester names a frame after the innermost
|
|
98
|
+
* function that contains it.
|
|
99
|
+
*/
|
|
100
|
+
/** Source map extension field that carries the bundle's function scopes. */
|
|
101
|
+
declare const SCOPES_KEY = "x_autter_scopes";
|
|
102
|
+
declare const SCOPES_VERSION = 1;
|
|
103
|
+
/**
|
|
104
|
+
* The `x_autter_scopes` value. `scopes` is encoded like `mappings`: base64
|
|
105
|
+
* VLQ, `;` between generated lines (the line each scope starts on), `,`
|
|
106
|
+
* between the scopes that start on one line, in start order (outer first on
|
|
107
|
+
* a tie). Each scope is 3 or 4 values, 0-based, in UTF-16 columns:
|
|
108
|
+
*
|
|
109
|
+
* 1. start column, relative to the previous scope's start on the same
|
|
110
|
+
* line (absolute for a line's first scope);
|
|
111
|
+
* 2. end line, relative to the start line;
|
|
112
|
+
* 3. end column (exclusive), relative to the start column when the scope
|
|
113
|
+
* ends on its start line, else absolute;
|
|
114
|
+
* 4. the name, when the function has one: an index into the map's own
|
|
115
|
+
* `names` followed by these `names`, relative to the last name index
|
|
116
|
+
* written (starting from 0).
|
|
117
|
+
*
|
|
118
|
+
* A 3-value scope is anonymous. `names` holds only names the map's own
|
|
119
|
+
* `names` lacks; a `null` entry stands for a minified name that could not
|
|
120
|
+
* be mapped back (the ingester then keeps the frame's call-site name).
|
|
121
|
+
*/
|
|
122
|
+
interface SourceMapScopes {
|
|
123
|
+
version: typeof SCOPES_VERSION;
|
|
124
|
+
names: Array<string | null>;
|
|
125
|
+
scopes: string;
|
|
126
|
+
}
|
|
127
|
+
type ScopesResult = {
|
|
128
|
+
scopes: SourceMapScopes;
|
|
129
|
+
} | {
|
|
130
|
+
error: string;
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* The function scopes of `code` (the bundle exactly as served), named
|
|
134
|
+
* through its source map. Never throws: a bundle that does not parse, or a
|
|
135
|
+
* map without string `mappings`, gives `{ error }` (an upload then goes
|
|
136
|
+
* ahead without scopes).
|
|
137
|
+
*/
|
|
138
|
+
declare function computeScopes(code: string, map: Record<string, unknown>): ScopesResult;
|
|
139
|
+
|
|
31
140
|
/** What the ingester accepts as a release. */
|
|
32
141
|
declare const RELEASE_RE: RegExp;
|
|
33
142
|
/** CI variables that carry the commit being built, in the order they are tried. */
|
|
@@ -47,4 +156,4 @@ declare function gitHead(cwd?: string): string | null;
|
|
|
47
156
|
*/
|
|
48
157
|
declare function resolveRelease(explicit?: string | null, env?: NodeJS.ProcessEnv, cwd?: string): ResolvedRelease;
|
|
49
158
|
|
|
50
|
-
export { DEBUG_ID_RE, DEBUG_ID_REGISTRY, RELEASE_ENV_VARS, RELEASE_RE, type ResolvedRelease, debugIdFor, debugIdSnippet, gitHead, hasDebugIdSnippet, normalizeDebugId, readDebugIdComment, resolveRelease };
|
|
159
|
+
export { DEBUG_ID_RE, DEBUG_ID_REGISTRY, RELEASE_ENV_VARS, RELEASE_RE, type ResolvedRelease, SCOPES_KEY, SCOPES_VERSION, type ScopesResult, type SourceMapScopes, type VerifyBundle, type VerifyOptions, type VerifyResult, computeScopes, debugIdFor, debugIdSnippet, formatVerify, gitHead, globToRegExp, hasDebugIdSnippet, normalizeDebugId, readDebugIdComment, resolveRelease, verifyDir };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as AutterSourceMapsOptions, B as BUNDLE_RE, a as BuildOutputResult, b as BundleWithMap, D as DEFAULT_ENDPOINT, F as FatalUploadError, I as InjectOptions, c as InjectResult, M as MAX_UPLOAD_BYTES, S as SNIPPET_LINE_KEY, U as UploadItem,
|
|
1
|
+
export { A as AutterSourceMapsOptions, B as BUNDLE_RE, a as BuildOutputResult, b as BundleWithMap, C as CLIENT_KEY_PREFIX, D as DEFAULT_ENDPOINT, F as FatalUploadError, I as InjectOptions, c as InjectResult, K as KEY_ADVICE, M as MAX_MAP_CHARS, d as MAX_UPLOAD_BYTES, S as SERVER_KEY_PREFIX, e as SNIPPET_LINE_KEY, U as UPLOAD_KEY_PREFIX, f as UploadItem, g as UploadOptions, h as UploadSummary, i as findBundles, j as formatUploadSummary, k as injectBundle, l as injectDir, m as mapPathFor, p as processBuildOutput, r as readSourceMappingUrl, n as resolveEndpoint, s as servedFilename, o as shiftMappings, q as snippetLine, t as storedMapJson, u as stripSourceMappingUrl, v as uploadDir } from './build-BqjqqeLg.js';
|
|
2
2
|
|
|
3
3
|
/** 8-4-4-4-12 hex: the debug id shape shared with Rollup, Turbopack, esbuild
|
|
4
4
|
* and Sentry tooling, and what the ingester accepts. */
|
|
@@ -28,6 +28,115 @@ declare function debugIdSnippet(id: string): string;
|
|
|
28
28
|
* contains it too.) */
|
|
29
29
|
declare function hasDebugIdSnippet(code: string): boolean;
|
|
30
30
|
|
|
31
|
+
interface VerifyOptions {
|
|
32
|
+
/** Ingester base URL (default as for upload). */
|
|
33
|
+
endpoint?: string;
|
|
34
|
+
/** Default: $AUTTER_RUNTIME_KEY. */
|
|
35
|
+
key?: string;
|
|
36
|
+
/** Sent with the check (a debug id check ignores it). */
|
|
37
|
+
release?: string;
|
|
38
|
+
urlPrefix?: string;
|
|
39
|
+
/** Globs (relative to the directory) of JS files that are not bundles. */
|
|
40
|
+
ignore?: string[];
|
|
41
|
+
/** Report leftover .map files without failing (a step that runs before they are removed). */
|
|
42
|
+
allowMaps?: boolean;
|
|
43
|
+
env?: NodeJS.ProcessEnv;
|
|
44
|
+
cwd?: string;
|
|
45
|
+
fetch?: typeof fetch;
|
|
46
|
+
}
|
|
47
|
+
interface VerifyBundle {
|
|
48
|
+
/** Relative to the directory. */
|
|
49
|
+
path: string;
|
|
50
|
+
status: "ok" | "failed" | "skipped";
|
|
51
|
+
debugId: string | null;
|
|
52
|
+
/** Why it failed, or why it was skipped. */
|
|
53
|
+
reasons: string[];
|
|
54
|
+
/** Whether the ingester has a map for its debug id (null: not checked). */
|
|
55
|
+
uploaded: boolean | null;
|
|
56
|
+
}
|
|
57
|
+
interface VerifyResult {
|
|
58
|
+
dir: string;
|
|
59
|
+
ok: boolean;
|
|
60
|
+
bundles: VerifyBundle[];
|
|
61
|
+
/** `.map` files in the directory (deploy risks unless allowMaps). */
|
|
62
|
+
maps: string[];
|
|
63
|
+
upload: {
|
|
64
|
+
/** verified: every checked bundle's map is on the ingester; missing: some are not;
|
|
65
|
+
* unverified: not checked (no key, or an ingester that can't answer); error: the check failed. */
|
|
66
|
+
status: "verified" | "missing" | "unverified" | "error";
|
|
67
|
+
endpoint: string | null;
|
|
68
|
+
reason: string | null;
|
|
69
|
+
checked: number;
|
|
70
|
+
missing: number;
|
|
71
|
+
};
|
|
72
|
+
/** Every failure, one per file and reason. */
|
|
73
|
+
failures: Array<{
|
|
74
|
+
path: string;
|
|
75
|
+
reason: string;
|
|
76
|
+
}>;
|
|
77
|
+
}
|
|
78
|
+
/** A tiny glob: `**` any path, `*` any name part, `?` one character. A
|
|
79
|
+
* pattern without "/" matches the file name anywhere. */
|
|
80
|
+
declare function globToRegExp(glob: string): RegExp;
|
|
81
|
+
declare function verifyDir(dir: string, options?: VerifyOptions): Promise<VerifyResult>;
|
|
82
|
+
/** Human-readable report. */
|
|
83
|
+
declare function formatVerify(result: VerifyResult): string[];
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Function scopes for scope-based frame names (like Sentry's
|
|
87
|
+
* js-source-scopes and PostHog cymbal's `token.scope()`).
|
|
88
|
+
*
|
|
89
|
+
* The ingester used to name a resolved frame with the name the source map
|
|
90
|
+
* holds at its CALLER's position. That is right for `computeTotal()`, and
|
|
91
|
+
* wrong whenever the call goes through a variable: `const run =
|
|
92
|
+
* actions.captured; run()` reported `run`, and handlers a framework calls
|
|
93
|
+
* got the framework's identifier (`func`, `listener`, `fn`). So `upload`
|
|
94
|
+
* parses each bundle (the final file the browser loads, after `inject`),
|
|
95
|
+
* collects every function's generated range and the token that names it,
|
|
96
|
+
* maps that token to its ORIGINAL identifier, and sends the result with the
|
|
97
|
+
* map as `x_autter_scopes`. The ingester names a frame after the innermost
|
|
98
|
+
* function that contains it.
|
|
99
|
+
*/
|
|
100
|
+
/** Source map extension field that carries the bundle's function scopes. */
|
|
101
|
+
declare const SCOPES_KEY = "x_autter_scopes";
|
|
102
|
+
declare const SCOPES_VERSION = 1;
|
|
103
|
+
/**
|
|
104
|
+
* The `x_autter_scopes` value. `scopes` is encoded like `mappings`: base64
|
|
105
|
+
* VLQ, `;` between generated lines (the line each scope starts on), `,`
|
|
106
|
+
* between the scopes that start on one line, in start order (outer first on
|
|
107
|
+
* a tie). Each scope is 3 or 4 values, 0-based, in UTF-16 columns:
|
|
108
|
+
*
|
|
109
|
+
* 1. start column, relative to the previous scope's start on the same
|
|
110
|
+
* line (absolute for a line's first scope);
|
|
111
|
+
* 2. end line, relative to the start line;
|
|
112
|
+
* 3. end column (exclusive), relative to the start column when the scope
|
|
113
|
+
* ends on its start line, else absolute;
|
|
114
|
+
* 4. the name, when the function has one: an index into the map's own
|
|
115
|
+
* `names` followed by these `names`, relative to the last name index
|
|
116
|
+
* written (starting from 0).
|
|
117
|
+
*
|
|
118
|
+
* A 3-value scope is anonymous. `names` holds only names the map's own
|
|
119
|
+
* `names` lacks; a `null` entry stands for a minified name that could not
|
|
120
|
+
* be mapped back (the ingester then keeps the frame's call-site name).
|
|
121
|
+
*/
|
|
122
|
+
interface SourceMapScopes {
|
|
123
|
+
version: typeof SCOPES_VERSION;
|
|
124
|
+
names: Array<string | null>;
|
|
125
|
+
scopes: string;
|
|
126
|
+
}
|
|
127
|
+
type ScopesResult = {
|
|
128
|
+
scopes: SourceMapScopes;
|
|
129
|
+
} | {
|
|
130
|
+
error: string;
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* The function scopes of `code` (the bundle exactly as served), named
|
|
134
|
+
* through its source map. Never throws: a bundle that does not parse, or a
|
|
135
|
+
* map without string `mappings`, gives `{ error }` (an upload then goes
|
|
136
|
+
* ahead without scopes).
|
|
137
|
+
*/
|
|
138
|
+
declare function computeScopes(code: string, map: Record<string, unknown>): ScopesResult;
|
|
139
|
+
|
|
31
140
|
/** What the ingester accepts as a release. */
|
|
32
141
|
declare const RELEASE_RE: RegExp;
|
|
33
142
|
/** CI variables that carry the commit being built, in the order they are tried. */
|
|
@@ -47,4 +156,4 @@ declare function gitHead(cwd?: string): string | null;
|
|
|
47
156
|
*/
|
|
48
157
|
declare function resolveRelease(explicit?: string | null, env?: NodeJS.ProcessEnv, cwd?: string): ResolvedRelease;
|
|
49
158
|
|
|
50
|
-
export { DEBUG_ID_RE, DEBUG_ID_REGISTRY, RELEASE_ENV_VARS, RELEASE_RE, type ResolvedRelease, debugIdFor, debugIdSnippet, gitHead, hasDebugIdSnippet, normalizeDebugId, readDebugIdComment, resolveRelease };
|
|
159
|
+
export { DEBUG_ID_RE, DEBUG_ID_REGISTRY, RELEASE_ENV_VARS, RELEASE_RE, type ResolvedRelease, SCOPES_KEY, SCOPES_VERSION, type ScopesResult, type SourceMapScopes, type VerifyBundle, type VerifyOptions, type VerifyResult, computeScopes, debugIdFor, debugIdSnippet, formatVerify, gitHead, globToRegExp, hasDebugIdSnippet, normalizeDebugId, readDebugIdComment, resolveRelease, verifyDir };
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BUNDLE_RE,
|
|
3
|
+
CLIENT_KEY_PREFIX,
|
|
3
4
|
DEBUG_ID_RE,
|
|
4
5
|
DEBUG_ID_REGISTRY,
|
|
5
6
|
DEFAULT_ENDPOINT,
|
|
6
7
|
FatalUploadError,
|
|
8
|
+
KEY_ADVICE,
|
|
9
|
+
MAX_MAP_CHARS,
|
|
7
10
|
MAX_UPLOAD_BYTES,
|
|
8
11
|
RELEASE_ENV_VARS,
|
|
9
12
|
RELEASE_RE,
|
|
13
|
+
SCOPES_KEY,
|
|
14
|
+
SCOPES_VERSION,
|
|
15
|
+
SERVER_KEY_PREFIX,
|
|
10
16
|
SNIPPET_LINE_KEY,
|
|
17
|
+
UPLOAD_KEY_PREFIX,
|
|
18
|
+
computeScopes,
|
|
11
19
|
debugIdFor,
|
|
12
20
|
debugIdSnippet,
|
|
13
21
|
findBundles,
|
|
@@ -29,22 +37,272 @@ import {
|
|
|
29
37
|
storedMapJson,
|
|
30
38
|
stripSourceMappingUrl,
|
|
31
39
|
uploadDir
|
|
32
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-KIQRQY3X.js";
|
|
41
|
+
|
|
42
|
+
// src/verify.ts
|
|
43
|
+
import { existsSync } from "fs";
|
|
44
|
+
import { readdir, readFile } from "fs/promises";
|
|
45
|
+
import { dirname, join, relative, resolve, sep } from "path";
|
|
46
|
+
var CHECK_BATCH = 500;
|
|
47
|
+
var SNIPPET_ID_RE = /\(g\._autterDebugIds=g\._autterDebugIds\|\|\{\}\)\[s\]="([0-9a-f-]{36})"/;
|
|
48
|
+
var NOT_BUNDLES = [
|
|
49
|
+
[/(^|\/)_(build|ssg|middleware|clientMiddleware)Manifest\.js$/, "Next.js build manifest"],
|
|
50
|
+
[/(^|\/)chunks\/polyfills-[\w-]+\.js$/, "Next.js polyfills, copied prebuilt without a source map"],
|
|
51
|
+
[/(^|\/)(sw|service-worker|serviceWorker|firebase-messaging-sw|mockServiceWorker)\.js$/, "service worker (the browser SDK doesn't run there)"],
|
|
52
|
+
[/(^|\/)workbox-[\w-]+\.js$/, "service worker runtime (the browser SDK doesn't run there)"]
|
|
53
|
+
];
|
|
54
|
+
function globToRegExp(glob) {
|
|
55
|
+
let re = "";
|
|
56
|
+
for (let i = 0; i < glob.length; i++) {
|
|
57
|
+
const ch = glob[i];
|
|
58
|
+
if (ch === "*" && glob[i + 1] === "*") {
|
|
59
|
+
re += glob[i + 2] === "/" ? "(?:.*/)?" : ".*";
|
|
60
|
+
i += glob[i + 2] === "/" ? 2 : 1;
|
|
61
|
+
} else if (ch === "*") re += "[^/]*";
|
|
62
|
+
else if (ch === "?") re += "[^/]";
|
|
63
|
+
else re += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
64
|
+
}
|
|
65
|
+
return new RegExp(glob.includes("/") ? `^${re.replace(/^\.\//, "")}$` : `(^|/)${re}$`);
|
|
66
|
+
}
|
|
67
|
+
async function walk(dir, out) {
|
|
68
|
+
let entries;
|
|
69
|
+
try {
|
|
70
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
71
|
+
} catch {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
if (entry.name === "node_modules") continue;
|
|
76
|
+
const full = join(dir, entry.name);
|
|
77
|
+
if (entry.isDirectory()) await walk(full, out);
|
|
78
|
+
else if (entry.isFile()) out.push(full);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function staticRoots(dir) {
|
|
82
|
+
for (let current = dirname(dir); dirname(current) !== current; current = dirname(current)) {
|
|
83
|
+
if (existsSync(join(current, "package.json"))) return ["public", "static", "src"].map((name) => join(current, name));
|
|
84
|
+
}
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
async function verifyDir(dir, options = {}) {
|
|
88
|
+
const root = resolve(dir);
|
|
89
|
+
const env = options.env ?? process.env;
|
|
90
|
+
const files = [];
|
|
91
|
+
await walk(root, files);
|
|
92
|
+
files.sort();
|
|
93
|
+
const relPath = (path) => relative(root, path).split(sep).join("/");
|
|
94
|
+
const ignore = (options.ignore ?? []).map(globToRegExp);
|
|
95
|
+
const roots = staticRoots(root);
|
|
96
|
+
const result = {
|
|
97
|
+
dir: root,
|
|
98
|
+
ok: true,
|
|
99
|
+
bundles: [],
|
|
100
|
+
maps: [],
|
|
101
|
+
upload: { status: "unverified", endpoint: null, reason: null, checked: 0, missing: 0 },
|
|
102
|
+
failures: []
|
|
103
|
+
};
|
|
104
|
+
const fail = (path, reason) => result.failures.push({ path, reason });
|
|
105
|
+
for (const file of files) {
|
|
106
|
+
const path = relPath(file);
|
|
107
|
+
if (file.endsWith(".map")) {
|
|
108
|
+
result.maps.push(path);
|
|
109
|
+
if (!options.allowMaps) {
|
|
110
|
+
fail(path, "source map in the deploy output: it exposes your original source. Delete it after upload (upload --delete-after-upload, or the bundler plugin) or keep it out of the deploy");
|
|
111
|
+
}
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (!BUNDLE_RE.test(file)) continue;
|
|
115
|
+
const bytes = await readFile(file);
|
|
116
|
+
const bundle = { path, status: "ok", debugId: null, reasons: [], uploaded: null };
|
|
117
|
+
result.bundles.push(bundle);
|
|
118
|
+
const skip = (reason) => {
|
|
119
|
+
bundle.status = "skipped";
|
|
120
|
+
bundle.reasons.push(reason);
|
|
121
|
+
};
|
|
122
|
+
if (ignore.some((re) => re.test(path))) {
|
|
123
|
+
skip("matches --ignore");
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const known = NOT_BUNDLES.find(([re]) => re.test(path));
|
|
127
|
+
if (known) {
|
|
128
|
+
skip(known[1]);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
let copied = null;
|
|
132
|
+
for (const staticRoot of roots) {
|
|
133
|
+
const candidate = join(staticRoot, path);
|
|
134
|
+
if (existsSync(candidate) && (await readFile(candidate)).equals(bytes)) copied = relative(dirname(staticRoot), candidate).split(sep).join("/");
|
|
135
|
+
}
|
|
136
|
+
if (copied) {
|
|
137
|
+
skip(`copied unchanged from ${copied}, not built`);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const code = bytes.toString("utf8");
|
|
141
|
+
const commentId = readDebugIdComment(code);
|
|
142
|
+
const snippetId = SNIPPET_ID_RE.exec(code)?.[1] ?? null;
|
|
143
|
+
bundle.debugId = commentId;
|
|
144
|
+
if (!commentId && !hasDebugIdSnippet(code)) {
|
|
145
|
+
bundle.reasons.push(
|
|
146
|
+
"no debug id: its errors can't be matched to a source map. Run `autter-runtime sourcemaps inject` on the build output (or use the bundler plugin with AUTTER_RUNTIME_KEY set) before deploying"
|
|
147
|
+
);
|
|
148
|
+
} else if (!commentId) {
|
|
149
|
+
bundle.reasons.push("has the debug id snippet but no //# debugId= comment: run `autter-runtime sourcemaps inject` on it again");
|
|
150
|
+
} else if (!hasDebugIdSnippet(code)) {
|
|
151
|
+
bundle.reasons.push(
|
|
152
|
+
"has a //# debugId= comment but no registration snippet, so the browser SDK can't report its id: run `autter-runtime sourcemaps inject` on it"
|
|
153
|
+
);
|
|
154
|
+
} else if (snippetId && snippetId !== commentId) {
|
|
155
|
+
bundle.reasons.push(`its snippet registers debug id ${snippetId} but its comment says ${commentId}: run inject on the original build output`);
|
|
156
|
+
}
|
|
157
|
+
const url = readSourceMappingUrl(code);
|
|
158
|
+
if (url?.startsWith("data:")) {
|
|
159
|
+
bundle.reasons.push("carries an inline source map (data: URL): your original source ships with the bundle. Use hidden or external maps");
|
|
160
|
+
} else if (url && !/^[a-z][\w+.-]*:/i.test(url) && !url.startsWith("/")) {
|
|
161
|
+
let target = url.split(/[?#]/)[0];
|
|
162
|
+
try {
|
|
163
|
+
target = decodeURIComponent(target);
|
|
164
|
+
} catch {
|
|
165
|
+
}
|
|
166
|
+
if (!existsSync(resolve(dirname(file), target))) {
|
|
167
|
+
bundle.reasons.push(
|
|
168
|
+
`its sourceMappingURL points at ${url}, which is not in the output (browsers' devtools request it and get a 404). Remove the comment: upload --delete-after-upload does`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (bundle.reasons.length) {
|
|
173
|
+
bundle.status = "failed";
|
|
174
|
+
for (const reason of bundle.reasons) fail(path, reason);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const counted = result.bundles.filter((b) => b.status !== "skipped");
|
|
178
|
+
if (!counted.length) fail(".", `no .js/.mjs/.cjs bundles under ${dir}: is this the build output?`);
|
|
179
|
+
await checkUploads(result, counted, options, env);
|
|
180
|
+
result.ok = result.failures.length === 0;
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
async function checkUploads(result, bundles, options, env) {
|
|
184
|
+
const upload = result.upload;
|
|
185
|
+
const key = (options.key ?? env.AUTTER_RUNTIME_KEY ?? "").trim();
|
|
186
|
+
if (!key) {
|
|
187
|
+
upload.reason = "AUTTER_RUNTIME_KEY is not set: only the local checks ran";
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
upload.endpoint = resolveEndpoint(options.endpoint, env);
|
|
191
|
+
if (key.startsWith(CLIENT_KEY_PREFIX)) {
|
|
192
|
+
upload.status = "error";
|
|
193
|
+
upload.reason = "AUTTER_RUNTIME_KEY is a publishable client key (autter_rtc_\u2026), which can't check source maps: use an upload key (autter_rtu_\u2026) or the server key (autter_rt_\u2026)";
|
|
194
|
+
result.failures.push({ path: ".", reason: upload.reason });
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const withIds = bundles.filter((b) => b.debugId);
|
|
198
|
+
if (!withIds.length) {
|
|
199
|
+
upload.reason = "no bundle has a debug id to check";
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
let release = "verify";
|
|
203
|
+
try {
|
|
204
|
+
release = resolveRelease(options.release, env, options.cwd).release;
|
|
205
|
+
} catch {
|
|
206
|
+
}
|
|
207
|
+
const doFetch = options.fetch ?? fetch;
|
|
208
|
+
const missing = [];
|
|
209
|
+
for (let start = 0; start < withIds.length; start += CHECK_BATCH) {
|
|
210
|
+
const batch = withIds.slice(start, start + CHECK_BATCH);
|
|
211
|
+
let res;
|
|
212
|
+
try {
|
|
213
|
+
res = await doFetch(`${upload.endpoint}/v1/sourcemaps/check`, {
|
|
214
|
+
method: "POST",
|
|
215
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${key}` },
|
|
216
|
+
body: JSON.stringify({ release, files: batch.map((b) => ({ filename: servedFilename(options.urlPrefix, b.path), debugId: b.debugId })) })
|
|
217
|
+
});
|
|
218
|
+
} catch (err) {
|
|
219
|
+
upload.status = "error";
|
|
220
|
+
upload.reason = `could not reach the ingester: ${err.message}`;
|
|
221
|
+
result.failures.push({ path: ".", reason: upload.reason });
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (res.status === 404 || res.status === 400) {
|
|
225
|
+
upload.status = "unverified";
|
|
226
|
+
upload.reason = res.status === 404 ? "the ingester predates 1.6.0 (no /v1/sourcemaps/check), so uploads can't be checked: upgrade it" : "the ingester predates 1.7.0, which checks maps by debug id alone, so uploads can't be checked after the maps were deleted: upgrade it";
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (res.status === 401 || res.status === 403) {
|
|
230
|
+
upload.status = "error";
|
|
231
|
+
upload.reason = `the ingester rejected the key (${res.status}): ${(await res.text()).slice(0, 300)}` + (key.startsWith(UPLOAD_KEY_PREFIX) ? " (upload keys, autter_rtu_\u2026, need ingester 1.7.0 or later)" : "");
|
|
232
|
+
result.failures.push({ path: ".", reason: upload.reason });
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (!res.ok) {
|
|
236
|
+
upload.status = "error";
|
|
237
|
+
upload.reason = `the source map check failed: HTTP ${res.status}`;
|
|
238
|
+
result.failures.push({ path: ".", reason: upload.reason });
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const body = await res.json();
|
|
242
|
+
const present = new Set(Array.isArray(body.present) ? body.present : []);
|
|
243
|
+
batch.forEach((bundle, index) => {
|
|
244
|
+
bundle.uploaded = present.has(index);
|
|
245
|
+
if (!bundle.uploaded) missing.push(bundle);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
upload.checked = withIds.length;
|
|
249
|
+
upload.missing = missing.length;
|
|
250
|
+
upload.status = missing.length ? "missing" : "verified";
|
|
251
|
+
upload.reason = missing.length ? `${missing.length} of ${withIds.length} bundle(s) have no map on the ingester` : null;
|
|
252
|
+
for (const bundle of missing) {
|
|
253
|
+
const reason = `no map with debug id ${bundle.debugId} on the ingester for this key's repository: upload this build's maps (autter-runtime sourcemaps upload, or the bundler plugin with AUTTER_RUNTIME_KEY set)`;
|
|
254
|
+
bundle.status = "failed";
|
|
255
|
+
bundle.reasons.push(reason);
|
|
256
|
+
result.failures.push({ path: bundle.path, reason });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function formatVerify(result) {
|
|
260
|
+
const lines = [];
|
|
261
|
+
for (const bundle of result.bundles) {
|
|
262
|
+
if (bundle.status === "ok") lines.push(`ok ${bundle.path} (debug id ${bundle.debugId}${bundle.uploaded ? ", map uploaded" : ""})`);
|
|
263
|
+
else if (bundle.status === "skipped") lines.push(`skipped ${bundle.path}: ${bundle.reasons.join("; ")}`);
|
|
264
|
+
else for (const reason of bundle.reasons) lines.push(`FAILED ${bundle.path}: ${reason}`);
|
|
265
|
+
}
|
|
266
|
+
for (const map of result.maps) {
|
|
267
|
+
const failure = result.failures.find((f) => f.path === map);
|
|
268
|
+
lines.push(failure ? `FAILED ${map}: ${failure.reason}` : `warning ${map}: source map in the output (allowed by --allow-maps; remove it before deploying)`);
|
|
269
|
+
}
|
|
270
|
+
for (const failure of result.failures.filter((f) => f.path === ".")) lines.push(`FAILED ${failure.reason}`);
|
|
271
|
+
const u = result.upload;
|
|
272
|
+
lines.push(
|
|
273
|
+
u.status === "verified" ? `upload: verified, ${u.checked} bundle map(s) on the ingester (${u.endpoint})` : u.status === "missing" ? `upload: ${u.reason} (${u.endpoint})` : `upload: ${u.status}${u.reason ? `, ${u.reason}` : ""}`
|
|
274
|
+
);
|
|
275
|
+
const counted = result.bundles.filter((b) => b.status !== "skipped");
|
|
276
|
+
lines.push(
|
|
277
|
+
result.ok ? `ok: ${counted.length} bundle(s) with debug ids, ${result.maps.length ? `${result.maps.length} source map(s) left (--allow-maps)` : "no source maps left"}${u.status === "unverified" ? ", upload not verified" : ""}` : `${result.failures.length} problem(s) in ${result.dir}`
|
|
278
|
+
);
|
|
279
|
+
return lines;
|
|
280
|
+
}
|
|
33
281
|
export {
|
|
34
282
|
BUNDLE_RE,
|
|
283
|
+
CLIENT_KEY_PREFIX,
|
|
35
284
|
DEBUG_ID_RE,
|
|
36
285
|
DEBUG_ID_REGISTRY,
|
|
37
286
|
DEFAULT_ENDPOINT,
|
|
38
287
|
FatalUploadError,
|
|
288
|
+
KEY_ADVICE,
|
|
289
|
+
MAX_MAP_CHARS,
|
|
39
290
|
MAX_UPLOAD_BYTES,
|
|
40
291
|
RELEASE_ENV_VARS,
|
|
41
292
|
RELEASE_RE,
|
|
293
|
+
SCOPES_KEY,
|
|
294
|
+
SCOPES_VERSION,
|
|
295
|
+
SERVER_KEY_PREFIX,
|
|
42
296
|
SNIPPET_LINE_KEY,
|
|
297
|
+
UPLOAD_KEY_PREFIX,
|
|
298
|
+
computeScopes,
|
|
43
299
|
debugIdFor,
|
|
44
300
|
debugIdSnippet,
|
|
45
301
|
findBundles,
|
|
46
302
|
formatUploadSummary,
|
|
303
|
+
formatVerify,
|
|
47
304
|
gitHead,
|
|
305
|
+
globToRegExp,
|
|
48
306
|
hasDebugIdSnippet,
|
|
49
307
|
injectBundle,
|
|
50
308
|
injectDir,
|
|
@@ -60,5 +318,6 @@ export {
|
|
|
60
318
|
snippetLine,
|
|
61
319
|
storedMapJson,
|
|
62
320
|
stripSourceMappingUrl,
|
|
63
|
-
uploadDir
|
|
321
|
+
uploadDir,
|
|
322
|
+
verifyDir
|
|
64
323
|
};
|