@autter/runtime-cli 1.1.0 → 1.3.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 +127 -26
- package/dist/{build-CPXR9Xd3.d.cts → build-DGeoDkTh.d.cts} +57 -7
- package/dist/{build-CPXR9Xd3.d.ts → build-DGeoDkTh.d.ts} +57 -7
- package/dist/{chunk-DQLLM2NT.js → chunk-Q23FB5X3.js} +81 -16
- package/dist/{chunk-I2743JCN.js → chunk-V66S2YHN.js} +13 -3
- package/dist/cli.js +1949 -48
- package/dist/index.cjs +348 -22
- package/dist/index.d.cts +68 -5
- package/dist/index.d.ts +68 -5
- package/dist/index.js +267 -2
- package/dist/next.cjs +120 -19
- package/dist/next.d.cts +6 -3
- package/dist/next.d.ts +6 -3
- package/dist/next.js +41 -4
- package/dist/vite.cjs +92 -18
- package/dist/vite.d.cts +5 -1
- package/dist/vite.d.ts +5 -1
- package/dist/vite.js +22 -4
- package/dist/webpack.cjs +86 -19
- package/dist/webpack.d.cts +18 -4
- package/dist/webpack.d.ts +18 -4
- package/dist/webpack.js +5 -3
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { spawnSync } from "child_process";
|
|
5
|
-
import { existsSync as
|
|
4
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
5
|
+
import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
|
|
6
6
|
import { createRequire } from "module";
|
|
7
|
-
import { dirname as
|
|
7
|
+
import { dirname as dirname4, join as join5, relative as relative5, resolve as resolve4, sep as sep5 } from "path";
|
|
8
8
|
import { parseArgs } from "util";
|
|
9
9
|
|
|
10
10
|
// src/build.ts
|
|
11
11
|
import { existsSync as existsSync2 } from "fs";
|
|
12
|
-
import { relative as relative2, sep as sep2 } from "path";
|
|
12
|
+
import { isAbsolute, relative as relative2, sep as sep2 } from "path";
|
|
13
13
|
|
|
14
14
|
// src/files.ts
|
|
15
15
|
import { existsSync } from "fs";
|
|
@@ -63,6 +63,48 @@ async function walk(dir, out, pattern = BUNDLE_RE) {
|
|
|
63
63
|
else if (entry.isFile() && pattern.test(entry.name)) out.push(full);
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
+
async function findMapFiles(dir) {
|
|
67
|
+
const out = [];
|
|
68
|
+
await walk(resolve(dir), out, /\.map$/);
|
|
69
|
+
return out.sort();
|
|
70
|
+
}
|
|
71
|
+
var CSS_SOURCE_MAPPING_URL_RE = /\/\*[#@] sourceMappingURL=([^\s*]+)\s*\*\/\s*$/;
|
|
72
|
+
function pointsAt(file, url, map) {
|
|
73
|
+
if (!url || /^[a-z][\w+.-]*:/i.test(url) || url.startsWith("/")) return false;
|
|
74
|
+
let path = url.split(/[?#]/)[0];
|
|
75
|
+
try {
|
|
76
|
+
path = decodeURIComponent(path);
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
return resolve(dirname(file), path) === map;
|
|
80
|
+
}
|
|
81
|
+
async function stripReference(file, map) {
|
|
82
|
+
let code;
|
|
83
|
+
try {
|
|
84
|
+
code = await readFile(file, "utf8");
|
|
85
|
+
} catch {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
let stripped = code;
|
|
89
|
+
if (BUNDLE_RE.test(file)) {
|
|
90
|
+
if (pointsAt(file, readSourceMappingUrl(code), map)) stripped = stripSourceMappingUrl(code);
|
|
91
|
+
} else if (file.endsWith(".css")) {
|
|
92
|
+
const match = CSS_SOURCE_MAPPING_URL_RE.exec(code);
|
|
93
|
+
if (match && pointsAt(file, match[1], map)) stripped = code.slice(0, match.index).replace(/\n?$/, "\n");
|
|
94
|
+
}
|
|
95
|
+
if (stripped !== code) await writeFile(file, stripped);
|
|
96
|
+
}
|
|
97
|
+
async function removeSourceMaps(maps, owners = /* @__PURE__ */ new Map()) {
|
|
98
|
+
const removed = [];
|
|
99
|
+
for (const map of [...new Set(maps)].sort()) {
|
|
100
|
+
if (!existsSync(map)) continue;
|
|
101
|
+
await rm(map, { force: true });
|
|
102
|
+
removed.push(map);
|
|
103
|
+
const owner = owners.get(map) ?? map.replace(/\.map$/, "");
|
|
104
|
+
await stripReference(owner, map);
|
|
105
|
+
}
|
|
106
|
+
return removed;
|
|
107
|
+
}
|
|
66
108
|
async function findBundles(dir, files) {
|
|
67
109
|
const root = resolve(dir);
|
|
68
110
|
const candidates = [];
|
|
@@ -642,6 +684,9 @@ var DEFAULT_ENDPOINT = "https://otlp.autter.dev";
|
|
|
642
684
|
var MAX_MAP_CHARS = 32 * 1024 * 1024;
|
|
643
685
|
var MAX_UPLOAD_BYTES = MAX_MAP_CHARS + 1024 * 1024;
|
|
644
686
|
var LEGACY_MAX_BYTES = 5 * 1024 * 1024;
|
|
687
|
+
var UPLOAD_KEY_PREFIX = "autter_rtu_";
|
|
688
|
+
var CLIENT_KEY_PREFIX = "autter_rtc_";
|
|
689
|
+
var KEY_ADVICE = "set AUTTER_RUNTIME_KEY to an upload-only key (autter_rtu_\u2026, recommended for CI: it can only upload source maps) or the full server key (autter_rt_\u2026, also works)";
|
|
645
690
|
var gzip = promisify(gzipCallback);
|
|
646
691
|
var CHECK_BATCH = 500;
|
|
647
692
|
var MAX_ATTEMPTS = 3;
|
|
@@ -655,9 +700,56 @@ function servedFilename(urlPrefix, path) {
|
|
|
655
700
|
if (!/^[a-z][\w+.-]*:\/\//i.test(prefix) && !prefix.startsWith("/")) prefix = `/${prefix}`;
|
|
656
701
|
return `${prefix.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
657
702
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
703
|
+
var NOT_IN_APP_PATH_PARTS = ["node_modules/", "/site-packages/", "/dist-packages/", "/vendor/", "/.pnpm/"];
|
|
704
|
+
var NOT_IN_APP_PATH_PREFIXES = ["node:", "internal/"];
|
|
705
|
+
var EXTENSION_SCHEMES = ["chrome-extension:", "moz-extension:", "safari-extension:", "safari-web-extension:", "webkit-masked-url:"];
|
|
706
|
+
function isInAppSource(path) {
|
|
707
|
+
if (!path) return true;
|
|
708
|
+
const normalized = path.replace(/\\/g, "/");
|
|
709
|
+
if (NOT_IN_APP_PATH_PARTS.some((part) => normalized.includes(part))) return false;
|
|
710
|
+
if (NOT_IN_APP_PATH_PREFIXES.some((prefix) => normalized.startsWith(prefix))) return false;
|
|
711
|
+
const lower = normalized.toLowerCase();
|
|
712
|
+
return !EXTENSION_SCHEMES.some((scheme) => lower.startsWith(scheme));
|
|
713
|
+
}
|
|
714
|
+
var SOURCES_CONTENT_WRAPPER_BYTES = Buffer.byteLength(',"sourcesContent":[]');
|
|
715
|
+
var NULL_ENTRY_BYTES = 4;
|
|
716
|
+
function storedSourcesContent(fields, baseBytes, limit = MAX_MAP_CHARS) {
|
|
717
|
+
const { sources, sourcesContent } = fields;
|
|
718
|
+
if (!Array.isArray(sources) || !Array.isArray(sourcesContent) || sources.length === 0) return null;
|
|
719
|
+
const ignored = /* @__PURE__ */ new Set();
|
|
720
|
+
for (const list of [fields.ignoreList, fields.x_google_ignoreList]) {
|
|
721
|
+
if (!Array.isArray(list)) continue;
|
|
722
|
+
for (const index of list) if (Number.isInteger(index)) ignored.add(index);
|
|
723
|
+
}
|
|
724
|
+
const count = sources.length;
|
|
725
|
+
const candidates = [];
|
|
726
|
+
for (let index = 0; index < count; index++) {
|
|
727
|
+
const source = sources[index];
|
|
728
|
+
const content = sourcesContent[index];
|
|
729
|
+
if (typeof source !== "string" || typeof content !== "string") continue;
|
|
730
|
+
if (ignored.has(index) || !isInAppSource(source)) continue;
|
|
731
|
+
candidates.push({ index, content, bytes: Buffer.byteLength(JSON.stringify(content)) });
|
|
732
|
+
}
|
|
733
|
+
if (candidates.length === 0) return null;
|
|
734
|
+
candidates.sort((a, b) => a.bytes - b.bytes || a.index - b.index);
|
|
735
|
+
let remaining = limit - baseBytes - SOURCES_CONTENT_WRAPPER_BYTES - NULL_ENTRY_BYTES * count - (count - 1);
|
|
736
|
+
const kept = new Array(count).fill(null);
|
|
737
|
+
let any = false;
|
|
738
|
+
for (const candidate of candidates) {
|
|
739
|
+
const extra = candidate.bytes - NULL_ENTRY_BYTES;
|
|
740
|
+
if (extra > remaining) break;
|
|
741
|
+
remaining -= extra;
|
|
742
|
+
kept[candidate.index] = candidate.content;
|
|
743
|
+
any = true;
|
|
744
|
+
}
|
|
745
|
+
return any ? kept : null;
|
|
746
|
+
}
|
|
747
|
+
function storedMapJson(map, options = {}) {
|
|
748
|
+
const { sourcesContent: _content, ...rest } = map;
|
|
749
|
+
const base = JSON.stringify(rest);
|
|
750
|
+
if (options.sourcesContent === false) return base;
|
|
751
|
+
const content = storedSourcesContent(map, Buffer.byteLength(base));
|
|
752
|
+
return content ? JSON.stringify({ ...rest, sourcesContent: content }) : base;
|
|
661
753
|
}
|
|
662
754
|
function bytesOf(buffer) {
|
|
663
755
|
return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
@@ -668,7 +760,7 @@ function fits(json, body) {
|
|
|
668
760
|
function uploadBody(release, filename, debugId, json) {
|
|
669
761
|
return `{"release":${JSON.stringify(release)},"filename":${JSON.stringify(filename)},${debugId ? `"debugId":${JSON.stringify(debugId)},` : ""}"map":${json}}`;
|
|
670
762
|
}
|
|
671
|
-
async function prepare(bundle, urlPrefix, release, log, compress) {
|
|
763
|
+
async function prepare(bundle, urlPrefix, release, log, compress, sourcesContent) {
|
|
672
764
|
let map;
|
|
673
765
|
try {
|
|
674
766
|
map = JSON.parse(await readFile3(bundle.map, "utf8"));
|
|
@@ -683,9 +775,10 @@ async function prepare(bundle, urlPrefix, release, log, compress) {
|
|
|
683
775
|
const filename = servedFilename(urlPrefix, bundle.path);
|
|
684
776
|
const { [SCOPES_KEY]: _stale, ...plain } = map;
|
|
685
777
|
const scopes = computeScopes(code, map);
|
|
686
|
-
|
|
778
|
+
const stored = { sourcesContent };
|
|
779
|
+
let json = storedMapJson(plain, stored);
|
|
687
780
|
if ("scopes" in scopes) {
|
|
688
|
-
const scoped = storedMapJson({ ...plain, [SCOPES_KEY]: scopes.scopes });
|
|
781
|
+
const scoped = storedMapJson({ ...plain, [SCOPES_KEY]: scopes.scopes }, stored);
|
|
689
782
|
if (fits(scoped, uploadBody(release, filename, debugId, scoped))) json = scoped;
|
|
690
783
|
else if (fits(json, uploadBody(release, filename, debugId, json))) {
|
|
691
784
|
log(`warning: ${bundle.path}: its map is over 32 MB with function scopes, so it is uploaded without them (frames keep call-site names)`);
|
|
@@ -710,7 +803,7 @@ async function prepare(bundle, urlPrefix, release, log, compress) {
|
|
|
710
803
|
var FatalUploadError = class extends Error {
|
|
711
804
|
};
|
|
712
805
|
function sleep(ms) {
|
|
713
|
-
return new Promise((
|
|
806
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
714
807
|
}
|
|
715
808
|
async function uploadDir(dir, options = {}) {
|
|
716
809
|
const env = options.env ?? process.env;
|
|
@@ -732,7 +825,7 @@ async function uploadDir(dir, options = {}) {
|
|
|
732
825
|
};
|
|
733
826
|
const items = [];
|
|
734
827
|
for (const bundle of await findBundles(dir, options.files)) {
|
|
735
|
-
const prepared = await prepare(bundle, options.urlPrefix, release, log, !options.dryRun);
|
|
828
|
+
const prepared = await prepare(bundle, options.urlPrefix, release, log, !options.dryRun, options.sourcesContent !== false);
|
|
736
829
|
if ("error" in prepared) {
|
|
737
830
|
summary.failed.push({ path: bundle.path, filename: servedFilename(options.urlPrefix, bundle.path), error: prepared.error });
|
|
738
831
|
} else if (prepared.mapChars > MAX_MAP_CHARS || prepared.bytes > MAX_UPLOAD_BYTES) {
|
|
@@ -750,10 +843,13 @@ async function uploadDir(dir, options = {}) {
|
|
|
750
843
|
return summary;
|
|
751
844
|
}
|
|
752
845
|
const key = (options.key ?? env.AUTTER_RUNTIME_KEY ?? "").trim();
|
|
753
|
-
if (!key) throw new FatalUploadError(
|
|
754
|
-
if (key.startsWith(
|
|
755
|
-
throw new FatalUploadError(
|
|
846
|
+
if (!key) throw new FatalUploadError(`no ingest key: ${KEY_ADVICE}`);
|
|
847
|
+
if (key.startsWith(CLIENT_KEY_PREFIX)) {
|
|
848
|
+
throw new FatalUploadError(`AUTTER_RUNTIME_KEY is a publishable client key (autter_rtc_\u2026), which cannot upload source maps: ${KEY_ADVICE}`);
|
|
756
849
|
}
|
|
850
|
+
const rejected = (status, detail) => new FatalUploadError(
|
|
851
|
+
`the ingester rejected the key (${status}): ${detail}` + (key.startsWith(UPLOAD_KEY_PREFIX) ? " (upload keys, autter_rtu_\u2026, need ingester 1.7.0 or later; the server key works with any)" : "")
|
|
852
|
+
);
|
|
757
853
|
const headers = { "content-type": "application/json", authorization: `Bearer ${key}` };
|
|
758
854
|
const uploadHeaders = { ...headers, "content-encoding": "gzip" };
|
|
759
855
|
const skipped = /* @__PURE__ */ new Set();
|
|
@@ -775,9 +871,7 @@ async function uploadDir(dir, options = {}) {
|
|
|
775
871
|
);
|
|
776
872
|
break;
|
|
777
873
|
}
|
|
778
|
-
if (res.status === 401 || res.status === 403)
|
|
779
|
-
throw new FatalUploadError(`the ingester rejected the key (${res.status}): ${(await res.text()).slice(0, 300)}`);
|
|
780
|
-
}
|
|
874
|
+
if (res.status === 401 || res.status === 403) throw rejected(res.status, (await res.text()).slice(0, 300));
|
|
781
875
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
782
876
|
const body = await res.json();
|
|
783
877
|
const present = new Set(Array.isArray(body.present) ? body.present : []);
|
|
@@ -808,7 +902,7 @@ async function uploadDir(dir, options = {}) {
|
|
|
808
902
|
detail = err.message;
|
|
809
903
|
}
|
|
810
904
|
if (status === 401 || status === 403) {
|
|
811
|
-
fatal =
|
|
905
|
+
fatal = rejected(status, detail);
|
|
812
906
|
return;
|
|
813
907
|
}
|
|
814
908
|
const retryable = status === 0 || status === 429 || status >= 500;
|
|
@@ -864,44 +958,1777 @@ function formatUploadSummary(summary) {
|
|
|
864
958
|
return lines;
|
|
865
959
|
}
|
|
866
960
|
|
|
961
|
+
// src/setup.ts
|
|
962
|
+
import { spawnSync } from "child_process";
|
|
963
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
964
|
+
import { join as join3 } from "path";
|
|
965
|
+
|
|
966
|
+
// src/setup-detect.ts
|
|
967
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
968
|
+
import { existsSync as existsSync3, readdirSync, readFileSync, statSync } from "fs";
|
|
969
|
+
import { dirname as dirname2, join as join2, relative as relative3, resolve as resolve2, sep as sep3 } from "path";
|
|
970
|
+
|
|
971
|
+
// src/setup-edit.ts
|
|
972
|
+
import { Parser } from "acorn";
|
|
973
|
+
import { tsPlugin } from "@sveltejs/acorn-typescript";
|
|
974
|
+
var TsParser = Parser.extend(tsPlugin());
|
|
975
|
+
var isTypeScriptFile = (file) => /\.[cm]?tsx?$/.test(file);
|
|
976
|
+
function parseFile(code, file) {
|
|
977
|
+
const parser = isTypeScriptFile(file) ? TsParser : Parser;
|
|
978
|
+
for (const sourceType of ["module", "script"]) {
|
|
979
|
+
const comments = [];
|
|
980
|
+
try {
|
|
981
|
+
const ast = parser.parse(code, {
|
|
982
|
+
ecmaVersion: "latest",
|
|
983
|
+
sourceType,
|
|
984
|
+
locations: true,
|
|
985
|
+
allowHashBang: true,
|
|
986
|
+
onComment: (_block, _text, start, end) => comments.push({ start, end })
|
|
987
|
+
});
|
|
988
|
+
ast.comments = comments;
|
|
989
|
+
return ast;
|
|
990
|
+
} catch {
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
return null;
|
|
994
|
+
}
|
|
995
|
+
function walk2(node, visit) {
|
|
996
|
+
if (!node || typeof node !== "object") return;
|
|
997
|
+
if (Array.isArray(node)) {
|
|
998
|
+
for (const child of node) walk2(child, visit);
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
const n = node;
|
|
1002
|
+
if (typeof n.type !== "string") return;
|
|
1003
|
+
visit(n);
|
|
1004
|
+
for (const key of Object.keys(n)) {
|
|
1005
|
+
if (key === "loc" || key === "range" || key === "start" || key === "end") continue;
|
|
1006
|
+
const value = n[key];
|
|
1007
|
+
if (value && typeof value === "object") walk2(value, visit);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
var TS_WRAPPERS = /* @__PURE__ */ new Set(["TSAsExpression", "TSSatisfiesExpression", "TSNonNullExpression", "TSTypeAssertion", "ParenthesizedExpression"]);
|
|
1011
|
+
function unwrap(node) {
|
|
1012
|
+
let n = node ?? null;
|
|
1013
|
+
while (n && TS_WRAPPERS.has(n.type)) n = n.expression;
|
|
1014
|
+
return n;
|
|
1015
|
+
}
|
|
1016
|
+
var isModuleExports = (node) => !!node && node.type === "MemberExpression" && !node.computed && node.object?.type === "Identifier" && node.object.name === "module" && node.property?.type === "Identifier" && node.property.name === "exports";
|
|
1017
|
+
function findExport(ast) {
|
|
1018
|
+
const sites = [];
|
|
1019
|
+
for (const statement of ast.body) {
|
|
1020
|
+
if (statement.type === "ExportDefaultDeclaration") {
|
|
1021
|
+
const declaration = statement.declaration;
|
|
1022
|
+
if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") {
|
|
1023
|
+
return { error: "the default export is a function or class declaration" };
|
|
1024
|
+
}
|
|
1025
|
+
sites.push({ kind: "esm", expr: declaration });
|
|
1026
|
+
} else if (statement.type === "ExpressionStatement" && statement.expression.type === "AssignmentExpression" && statement.expression.operator === "=" && isModuleExports(statement.expression.left)) {
|
|
1027
|
+
sites.push({ kind: "cjs", expr: statement.expression.right });
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
if (sites.length === 0) return { error: "no `export default` or `module.exports =` found" };
|
|
1031
|
+
if (sites.length > 1) return { error: "the config is exported more than once" };
|
|
1032
|
+
return sites[0];
|
|
1033
|
+
}
|
|
1034
|
+
function resolveBinding(ast, name) {
|
|
1035
|
+
let init = null;
|
|
1036
|
+
for (const statement of ast.body) {
|
|
1037
|
+
const declaration = statement.type === "ExportNamedDeclaration" ? statement.declaration : statement;
|
|
1038
|
+
if (declaration?.type !== "VariableDeclaration") continue;
|
|
1039
|
+
for (const declarator of declaration.declarations) {
|
|
1040
|
+
if (declarator.id?.type === "Identifier" && declarator.id.name === name) init = declarator.init ?? null;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
if (!init) return null;
|
|
1044
|
+
let reassigned = false;
|
|
1045
|
+
walk2(ast, (node) => {
|
|
1046
|
+
if (node.type === "AssignmentExpression" && node.left?.type === "Identifier" && node.left.name === name) reassigned = true;
|
|
1047
|
+
});
|
|
1048
|
+
return reassigned ? null : init;
|
|
1049
|
+
}
|
|
1050
|
+
function returnedObject(fn) {
|
|
1051
|
+
const body = fn.body;
|
|
1052
|
+
if (body.type !== "BlockStatement") {
|
|
1053
|
+
const value2 = unwrap(body);
|
|
1054
|
+
return value2?.type === "ObjectExpression" ? value2 : null;
|
|
1055
|
+
}
|
|
1056
|
+
const returns = [];
|
|
1057
|
+
walk2(body, (node) => {
|
|
1058
|
+
if (node.type === "ReturnStatement") returns.push(node);
|
|
1059
|
+
});
|
|
1060
|
+
if (returns.length !== 1) return null;
|
|
1061
|
+
const last = body.body.at(-1);
|
|
1062
|
+
if (last !== returns[0]) return null;
|
|
1063
|
+
const value = unwrap(returns[0].argument);
|
|
1064
|
+
return value?.type === "ObjectExpression" ? value : null;
|
|
1065
|
+
}
|
|
1066
|
+
function configObject(ast, expr, depth = 0) {
|
|
1067
|
+
const node = unwrap(expr);
|
|
1068
|
+
if (!node || depth > 4) return null;
|
|
1069
|
+
switch (node.type) {
|
|
1070
|
+
case "ObjectExpression":
|
|
1071
|
+
return node;
|
|
1072
|
+
case "Identifier": {
|
|
1073
|
+
const init = resolveBinding(ast, node.name);
|
|
1074
|
+
return init ? configObject(ast, init, depth + 1) : null;
|
|
1075
|
+
}
|
|
1076
|
+
case "CallExpression":
|
|
1077
|
+
if (node.callee?.type === "Identifier" && /^define\w*Config$/.test(node.callee.name) && node.arguments.length === 1) {
|
|
1078
|
+
return configObject(ast, node.arguments[0], depth + 1);
|
|
1079
|
+
}
|
|
1080
|
+
return null;
|
|
1081
|
+
case "ArrowFunctionExpression":
|
|
1082
|
+
case "FunctionExpression":
|
|
1083
|
+
return returnedObject(node);
|
|
1084
|
+
default:
|
|
1085
|
+
return null;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
function findProperty(obj, name) {
|
|
1089
|
+
const matches = obj.properties.filter(
|
|
1090
|
+
(p) => p.type === "Property" && !p.computed && (p.key.type === "Identifier" && p.key.name === name || p.key.type === "Literal" && p.key.value === name)
|
|
1091
|
+
);
|
|
1092
|
+
if (matches.length > 1) return "ambiguous";
|
|
1093
|
+
const [match] = matches;
|
|
1094
|
+
if (!match) return null;
|
|
1095
|
+
if (match.kind !== "init" || match.method) return "ambiguous";
|
|
1096
|
+
return match;
|
|
1097
|
+
}
|
|
1098
|
+
function literalProperty(obj, path) {
|
|
1099
|
+
let node = obj;
|
|
1100
|
+
for (const [i, name] of path.entries()) {
|
|
1101
|
+
if (!node || node.type !== "ObjectExpression") return void 0;
|
|
1102
|
+
const property = findProperty(node, name);
|
|
1103
|
+
if (!property || property === "ambiguous") return void 0;
|
|
1104
|
+
node = unwrap(property.value);
|
|
1105
|
+
if (i === path.length - 1) {
|
|
1106
|
+
if (node?.type === "Literal" && (typeof node.value !== "object" || node.value === null)) return node.value;
|
|
1107
|
+
if (node?.type === "TemplateLiteral" && node.expressions.length === 0) return node.quasis[0].value.cooked;
|
|
1108
|
+
return void 0;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
return void 0;
|
|
1112
|
+
}
|
|
1113
|
+
function applyEdits(code, edits) {
|
|
1114
|
+
let out = code;
|
|
1115
|
+
for (const edit of [...edits].sort((a, b) => b.start - a.start)) out = `${out.slice(0, edit.start)}${edit.text}${out.slice(edit.end)}`;
|
|
1116
|
+
return out;
|
|
1117
|
+
}
|
|
1118
|
+
function lineIndent(code, pos) {
|
|
1119
|
+
const lineStart = code.lastIndexOf("\n", pos - 1) + 1;
|
|
1120
|
+
return /^[ \t]*/.exec(code.slice(lineStart))[0];
|
|
1121
|
+
}
|
|
1122
|
+
var spansLines = (code, node) => code.slice(node.start, node.end).includes("\n");
|
|
1123
|
+
function appendAfter(code, container, last, text) {
|
|
1124
|
+
if (!spansLines(code, container)) return { start: last.end, end: last.end, text: `, ${text}` };
|
|
1125
|
+
const indent = lineIndent(code, last.start);
|
|
1126
|
+
const trailing = /^[^\S\n]*,[^\S\n]*(?:\/\/[^\n]*|\/\*[^\n]*?\*\/)?[^\S\n]*(?=\n)/.exec(code.slice(last.end, container.end));
|
|
1127
|
+
if (trailing) {
|
|
1128
|
+
const at = last.end + trailing[0].length;
|
|
1129
|
+
return { start: at, end: at, text: `
|
|
1130
|
+
${indent}${text},` };
|
|
1131
|
+
}
|
|
1132
|
+
return { start: last.end, end: last.end, text: `,
|
|
1133
|
+
${indent}${text}` };
|
|
1134
|
+
}
|
|
1135
|
+
function appendElement(code, array, text) {
|
|
1136
|
+
const elements = array.elements.filter((e) => !!e);
|
|
1137
|
+
const last = elements.at(-1);
|
|
1138
|
+
if (!last) {
|
|
1139
|
+
const inner = code.slice(array.start + 1, array.end - 1);
|
|
1140
|
+
return inner.trim() === "" ? { start: array.start + 1, end: array.end - 1, text } : { start: array.end - 1, end: array.end - 1, text };
|
|
1141
|
+
}
|
|
1142
|
+
return appendAfter(code, array, last, text);
|
|
1143
|
+
}
|
|
1144
|
+
function appendProperty(code, obj, text) {
|
|
1145
|
+
const last = obj.properties.at(-1);
|
|
1146
|
+
if (!last) {
|
|
1147
|
+
if (code.slice(obj.start + 1, obj.end - 1).trim() !== "") return null;
|
|
1148
|
+
return { start: obj.start, end: obj.end, text: `{ ${text} }` };
|
|
1149
|
+
}
|
|
1150
|
+
return appendAfter(code, obj, last, text);
|
|
1151
|
+
}
|
|
1152
|
+
function addPluginEdit(code, obj, element, container) {
|
|
1153
|
+
let target = obj;
|
|
1154
|
+
if (container) {
|
|
1155
|
+
const property = findProperty(obj, container);
|
|
1156
|
+
if (property === "ambiguous") return { error: `\`${container}\` is defined more than once or as a method` };
|
|
1157
|
+
if (!property) {
|
|
1158
|
+
if (obj.properties.some((p) => p.type === "SpreadElement")) {
|
|
1159
|
+
return { error: `the config spreads another object, which may already set \`${container}\`` };
|
|
1160
|
+
}
|
|
1161
|
+
return appendProperty(code, obj, `${container}: { plugins: [${element}] }`) ?? { error: "the config object holds only comments" };
|
|
1162
|
+
}
|
|
1163
|
+
const value2 = unwrap(property.value);
|
|
1164
|
+
if (value2?.type !== "ObjectExpression") return { error: `\`${container}\` is not an object literal` };
|
|
1165
|
+
target = value2;
|
|
1166
|
+
}
|
|
1167
|
+
const plugins = findProperty(target, "plugins");
|
|
1168
|
+
if (plugins === "ambiguous") return { error: "`plugins` is defined more than once or as a method" };
|
|
1169
|
+
if (!plugins) {
|
|
1170
|
+
if (target.properties.some((p) => p.type === "SpreadElement")) {
|
|
1171
|
+
return { error: "the config spreads another object, which may already set `plugins`" };
|
|
1172
|
+
}
|
|
1173
|
+
return appendProperty(code, target, `plugins: [${element}]`) ?? { error: "the config object holds only comments" };
|
|
1174
|
+
}
|
|
1175
|
+
const value = unwrap(plugins.value);
|
|
1176
|
+
if (value?.type !== "ArrayExpression") return { error: "`plugins` is not an array literal" };
|
|
1177
|
+
return appendElement(code, value, element);
|
|
1178
|
+
}
|
|
1179
|
+
function requireCall(init) {
|
|
1180
|
+
let node = unwrap(init);
|
|
1181
|
+
while (node) {
|
|
1182
|
+
if (node.type === "CallExpression") {
|
|
1183
|
+
if (node.callee?.type === "Identifier" && node.callee.name === "require") return node;
|
|
1184
|
+
node = unwrap(node.callee);
|
|
1185
|
+
} else if (node.type === "MemberExpression") node = unwrap(node.object);
|
|
1186
|
+
else return null;
|
|
1187
|
+
}
|
|
1188
|
+
return null;
|
|
1189
|
+
}
|
|
1190
|
+
function importEdit(code, ast, name, spec, kind) {
|
|
1191
|
+
const body = ast.body;
|
|
1192
|
+
let last;
|
|
1193
|
+
let source = null;
|
|
1194
|
+
if (kind === "esm") {
|
|
1195
|
+
last = body.filter((s) => s.type === "ImportDeclaration").at(-1);
|
|
1196
|
+
source = last?.source ?? null;
|
|
1197
|
+
} else {
|
|
1198
|
+
last = body.filter((s) => s.type === "VariableDeclaration" && s.declarations.every((d) => requireCall(d.init))).at(-1);
|
|
1199
|
+
source = last ? requireCall(last.declarations[0].init)?.arguments[0] ?? null : null;
|
|
1200
|
+
}
|
|
1201
|
+
const quote = source && code[source.start] === "'" ? "'" : '"';
|
|
1202
|
+
const semi = last ? code[last.end - 1] === ";" ? ";" : "" : ";";
|
|
1203
|
+
const line = kind === "esm" ? `import { ${name} } from ${quote}${spec}${quote}${semi}` : `const { ${name} } = require(${quote}${spec}${quote})${semi}`;
|
|
1204
|
+
if (last) return { start: last.end, end: last.end, text: `
|
|
1205
|
+
${line}` };
|
|
1206
|
+
const first = body.find((s) => !(s.type === "ExpressionStatement" && typeof s.directive === "string"));
|
|
1207
|
+
if (!first) return { start: code.length, end: code.length, text: `${code.endsWith("\n") || !code ? "" : "\n"}${line}
|
|
1208
|
+
` };
|
|
1209
|
+
let at = first.start;
|
|
1210
|
+
const comments = (ast.comments ?? []).filter((c) => c.end <= first.start);
|
|
1211
|
+
for (let i = comments.length - 1; i >= 0; i--) {
|
|
1212
|
+
const gap = code.slice(comments[i].end, at);
|
|
1213
|
+
if (gap.trim() !== "" || (gap.match(/\n/g) ?? []).length > 1) break;
|
|
1214
|
+
at = comments[i].start;
|
|
1215
|
+
}
|
|
1216
|
+
at = code.lastIndexOf("\n", at - 1) + 1;
|
|
1217
|
+
return { start: at, end: at, text: `${line}
|
|
1218
|
+
|
|
1219
|
+
` };
|
|
1220
|
+
}
|
|
1221
|
+
function calls(ast, name) {
|
|
1222
|
+
const found = [];
|
|
1223
|
+
walk2(ast, (node) => {
|
|
1224
|
+
if ((node.type === "CallExpression" || node.type === "NewExpression") && node.callee?.type === "Identifier" && node.callee.name === name) {
|
|
1225
|
+
found.push(node);
|
|
1226
|
+
}
|
|
1227
|
+
});
|
|
1228
|
+
return found;
|
|
1229
|
+
}
|
|
1230
|
+
function finish(original, file, edits, name) {
|
|
1231
|
+
const code = applyEdits(original, edits);
|
|
1232
|
+
const ast = parseFile(code, file);
|
|
1233
|
+
if (!ast || calls(ast, name).length === 0) return { status: "manual", reason: "the edited file did not parse back as expected" };
|
|
1234
|
+
return { status: "edited", code };
|
|
1235
|
+
}
|
|
1236
|
+
function addBundlerPlugin(code, file, plugin) {
|
|
1237
|
+
const ast = parseFile(code, file);
|
|
1238
|
+
if (!ast) return { status: "manual", reason: `${file} does not parse as ${isTypeScriptFile(file) ? "TypeScript" : "JavaScript"}` };
|
|
1239
|
+
if (calls(ast, plugin.name).length) return { status: "already" };
|
|
1240
|
+
if (code.includes(plugin.spec)) return { status: "manual", reason: `${file} imports ${plugin.spec} but never uses ${plugin.name}` };
|
|
1241
|
+
const site = findExport(ast);
|
|
1242
|
+
if ("error" in site) return { status: "manual", reason: site.error };
|
|
1243
|
+
const obj = configObject(ast, site.expr);
|
|
1244
|
+
if (!obj) return { status: "manual", reason: "the exported config is not an object literal, a define\u2026Config({...}) call, a variable holding one, or a function returning one" };
|
|
1245
|
+
const edit = addPluginEdit(code, obj, plugin.element, plugin.container);
|
|
1246
|
+
if ("error" in edit) return { status: "manual", reason: edit.error };
|
|
1247
|
+
return finish(code, file, [edit, importEdit(code, ast, plugin.name, plugin.spec, site.kind)], plugin.name);
|
|
1248
|
+
}
|
|
1249
|
+
var NEXT_WRAPPABLE = /* @__PURE__ */ new Set(["ObjectExpression", "Identifier", "CallExpression", "ArrowFunctionExpression", "FunctionExpression", "MemberExpression"]);
|
|
1250
|
+
function wrapNextConfig(code, file) {
|
|
1251
|
+
const name = "withAutterSourceMaps";
|
|
1252
|
+
const spec = "@autter/runtime-cli/next";
|
|
1253
|
+
const ast = parseFile(code, file);
|
|
1254
|
+
if (!ast) return { status: "manual", reason: `${file} does not parse as ${isTypeScriptFile(file) ? "TypeScript" : "JavaScript"}` };
|
|
1255
|
+
const site = findExport(ast);
|
|
1256
|
+
const existing = calls(ast, name);
|
|
1257
|
+
if (existing.length) {
|
|
1258
|
+
if (!("error" in site) && unwrap(site.expr) === existing[0]) return { status: "already" };
|
|
1259
|
+
return {
|
|
1260
|
+
status: "manual",
|
|
1261
|
+
reason: `${name} is not the outermost wrapper of the exported config, so other wrappers' webpack and runAfterProductionCompile hooks may not be chained`
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
if (code.includes(spec)) return { status: "manual", reason: `${file} imports ${spec} but never calls ${name}` };
|
|
1265
|
+
if ("error" in site) return { status: "manual", reason: site.error };
|
|
1266
|
+
const target = unwrap(site.expr);
|
|
1267
|
+
if (!target || !NEXT_WRAPPABLE.has(target.type)) {
|
|
1268
|
+
return { status: "manual", reason: "the exported config is not an object, a variable, a wrapper call or a function" };
|
|
1269
|
+
}
|
|
1270
|
+
const edits = [
|
|
1271
|
+
{ start: site.expr.start, end: site.expr.start, text: `${name}(` },
|
|
1272
|
+
{ start: site.expr.end, end: site.expr.end, text: ")" },
|
|
1273
|
+
importEdit(code, ast, name, spec, site.kind)
|
|
1274
|
+
];
|
|
1275
|
+
return finish(code, file, edits, name);
|
|
1276
|
+
}
|
|
1277
|
+
function nextConfigObject(ast) {
|
|
1278
|
+
const site = findExport(ast);
|
|
1279
|
+
if ("error" in site) return null;
|
|
1280
|
+
let node = unwrap(site.expr);
|
|
1281
|
+
for (let depth = 0; node && depth < 6; depth++) {
|
|
1282
|
+
const obj = configObject(ast, node);
|
|
1283
|
+
if (obj) return obj;
|
|
1284
|
+
if (node.type === "CallExpression" && node.arguments.length) node = unwrap(node.arguments[0]);
|
|
1285
|
+
else return null;
|
|
1286
|
+
}
|
|
1287
|
+
return null;
|
|
1288
|
+
}
|
|
1289
|
+
function exportedConfigObject(ast) {
|
|
1290
|
+
const site = findExport(ast);
|
|
1291
|
+
return "error" in site ? null : configObject(ast, site.expr);
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
// src/setup-detect.ts
|
|
1295
|
+
var posix = (path) => path.split(sep3).join("/");
|
|
1296
|
+
var rel = (from, to) => posix(relative3(from, to)) || ".";
|
|
1297
|
+
function readText(path) {
|
|
1298
|
+
try {
|
|
1299
|
+
return readFileSync(path, "utf8");
|
|
1300
|
+
} catch {
|
|
1301
|
+
return null;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
function readJson(path) {
|
|
1305
|
+
const text = readText(path);
|
|
1306
|
+
if (text === null) return null;
|
|
1307
|
+
try {
|
|
1308
|
+
return JSON.parse(text);
|
|
1309
|
+
} catch {
|
|
1310
|
+
return null;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
var firstExisting = (dir, names) => names.find((name) => existsSync3(join2(dir, name))) ?? null;
|
|
1314
|
+
function ancestors(dir, stop) {
|
|
1315
|
+
const out = [];
|
|
1316
|
+
for (let current = dir; ; current = dirname2(current)) {
|
|
1317
|
+
out.push(current);
|
|
1318
|
+
if (current === stop || dirname2(current) === current) break;
|
|
1319
|
+
}
|
|
1320
|
+
return out;
|
|
1321
|
+
}
|
|
1322
|
+
function gitRoot(dir) {
|
|
1323
|
+
for (const current of ancestors(dir, null)) if (existsSync3(join2(current, ".git"))) return current;
|
|
1324
|
+
return null;
|
|
1325
|
+
}
|
|
1326
|
+
function githubRepo(root) {
|
|
1327
|
+
if (!root) return null;
|
|
1328
|
+
const git = (args) => {
|
|
1329
|
+
try {
|
|
1330
|
+
return execFileSync2("git", args, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 1e4 }).trim();
|
|
1331
|
+
} catch {
|
|
1332
|
+
return "";
|
|
1333
|
+
}
|
|
1334
|
+
};
|
|
1335
|
+
const remotes = git(["remote"]).split("\n").filter(Boolean);
|
|
1336
|
+
for (const remote of remotes.includes("origin") ? ["origin", ...remotes.filter((r) => r !== "origin")] : remotes) {
|
|
1337
|
+
const url = git(["remote", "get-url", remote]);
|
|
1338
|
+
const match = /github\.com[:/]([\w.-]+)\/([\w.-]+?)(?:\.git)?\/?$/.exec(url);
|
|
1339
|
+
if (match) return `${match[1]}/${match[2]}`;
|
|
1340
|
+
}
|
|
1341
|
+
return null;
|
|
1342
|
+
}
|
|
1343
|
+
function semver(version) {
|
|
1344
|
+
const match = version ? /(\d+)\.(\d+)\.(\d+)/.exec(version) : null;
|
|
1345
|
+
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
|
|
1346
|
+
}
|
|
1347
|
+
function atLeast(version, minimum) {
|
|
1348
|
+
const a = semver(version);
|
|
1349
|
+
const b = semver(minimum);
|
|
1350
|
+
if (!a) return null;
|
|
1351
|
+
for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] > b[i];
|
|
1352
|
+
return true;
|
|
1353
|
+
}
|
|
1354
|
+
function installedVersion(dir, name) {
|
|
1355
|
+
for (const current of ancestors(dir, null)) {
|
|
1356
|
+
const manifest = readJson(join2(current, "node_modules", ...name.split("/"), "package.json"));
|
|
1357
|
+
if (manifest?.version) return manifest.version;
|
|
1358
|
+
}
|
|
1359
|
+
return null;
|
|
1360
|
+
}
|
|
1361
|
+
function packageManager(dir, pkg, stop) {
|
|
1362
|
+
for (const current of ancestors(dir, stop)) {
|
|
1363
|
+
const manifest = current === dir ? pkg : readJson(join2(current, "package.json"));
|
|
1364
|
+
const declared = /^(npm|pnpm|yarn|bun)@(\S+)/.exec(manifest?.packageManager ?? "");
|
|
1365
|
+
if (declared) {
|
|
1366
|
+
const name = declared[1];
|
|
1367
|
+
const lockfile2 = lockfileIn(current);
|
|
1368
|
+
return { name, lockfile: lockfile2 ? rel(dir, join2(current, lockfile2)) : null, ...name === "yarn" ? { yarnBerry: !declared[2].startsWith("1.") } : {} };
|
|
1369
|
+
}
|
|
1370
|
+
const lockfile = lockfileIn(current);
|
|
1371
|
+
if (lockfile) {
|
|
1372
|
+
const name = lockfile.startsWith("pnpm") ? "pnpm" : lockfile === "yarn.lock" ? "yarn" : lockfile.startsWith("bun") ? "bun" : "npm";
|
|
1373
|
+
const berry = name === "yarn" && (existsSync3(join2(current, ".yarnrc.yml")) || (readText(join2(current, lockfile)) ?? "").includes("__metadata:"));
|
|
1374
|
+
return { name, lockfile: rel(dir, join2(current, lockfile)), ...name === "yarn" ? { yarnBerry: berry } : {} };
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
return { name: "npm", lockfile: null };
|
|
1378
|
+
}
|
|
1379
|
+
function lockfileIn(dir) {
|
|
1380
|
+
return firstExisting(dir, ["pnpm-lock.yaml", "yarn.lock", "bun.lock", "bun.lockb", "package-lock.json", "npm-shrinkwrap.json"]);
|
|
1381
|
+
}
|
|
1382
|
+
function scriptFlag(script, ...flags) {
|
|
1383
|
+
if (!script) return null;
|
|
1384
|
+
for (const flag of flags) {
|
|
1385
|
+
const match = new RegExp(`(?:^|\\s)${flag.replace(/[-]/g, "\\-")}(?:=|\\s+)(?:"([^"]+)"|'([^']+)'|([^\\s&|;]+))`).exec(script);
|
|
1386
|
+
if (match) return match[1] ?? match[2] ?? match[3] ?? null;
|
|
1387
|
+
}
|
|
1388
|
+
return null;
|
|
1389
|
+
}
|
|
1390
|
+
var CONFIG_NAMES = {
|
|
1391
|
+
vite: ["vite.config.ts", "vite.config.mts", "vite.config.js", "vite.config.mjs", "vite.config.cts", "vite.config.cjs"],
|
|
1392
|
+
next: ["next.config.ts", "next.config.mts", "next.config.mjs", "next.config.js", "next.config.cjs"],
|
|
1393
|
+
webpack: ["webpack.config.js", "webpack.config.cjs", "webpack.config.mjs", "webpack.config.ts"],
|
|
1394
|
+
rollup: ["rollup.config.js", "rollup.config.mjs", "rollup.config.cjs", "rollup.config.ts"],
|
|
1395
|
+
nuxt: ["nuxt.config.ts", "nuxt.config.js", "nuxt.config.mjs"],
|
|
1396
|
+
astro: ["astro.config.mjs", "astro.config.ts", "astro.config.js", "astro.config.mts", "astro.config.cjs"],
|
|
1397
|
+
"vue-cli": ["vue.config.js", "vue.config.cjs", "vue.config.mjs"],
|
|
1398
|
+
"solid-start": ["app.config.ts", "app.config.js"]
|
|
1399
|
+
};
|
|
1400
|
+
function buildTool(dir, pkg) {
|
|
1401
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1402
|
+
const has = (name) => typeof deps[name] === "string";
|
|
1403
|
+
const buildScript = pkg.scripts?.build ?? null;
|
|
1404
|
+
const scriptConfig = (tool2) => {
|
|
1405
|
+
if (!buildScript || !new RegExp(`\\b${tool2}\\b`).test(buildScript)) return null;
|
|
1406
|
+
const file = scriptFlag(buildScript, "--config", "-c");
|
|
1407
|
+
return file && existsSync3(join2(dir, file)) ? file : null;
|
|
1408
|
+
};
|
|
1409
|
+
const tool = (id, framework, config, versionOf) => ({
|
|
1410
|
+
id,
|
|
1411
|
+
framework,
|
|
1412
|
+
config,
|
|
1413
|
+
version: deps[versionOf] ?? null,
|
|
1414
|
+
buildScript
|
|
1415
|
+
});
|
|
1416
|
+
if (has("next")) return tool("next", null, firstExisting(dir, [...CONFIG_NAMES.next]), "next");
|
|
1417
|
+
if (has("react-scripts")) return tool("cra", has("@craco/craco") ? "craco" : null, null, "react-scripts");
|
|
1418
|
+
if (existsSync3(join2(dir, "angular.json")) && (has("@angular/core") || has("@angular/cli"))) {
|
|
1419
|
+
return tool("angular", null, "angular.json", has("@angular/cli") ? "@angular/cli" : "@angular/core");
|
|
1420
|
+
}
|
|
1421
|
+
const viteConfig = scriptConfig("vite") ?? firstExisting(dir, [...CONFIG_NAMES.vite]);
|
|
1422
|
+
if (has("nuxt")) return tool("vite", "nuxt", firstExisting(dir, [...CONFIG_NAMES.nuxt]), "nuxt");
|
|
1423
|
+
if (has("astro")) return tool("vite", "astro", firstExisting(dir, [...CONFIG_NAMES.astro]), "astro");
|
|
1424
|
+
if (has("@solidjs/start")) return tool("vite", "solid-start", firstExisting(dir, [...CONFIG_NAMES["solid-start"]]), "@solidjs/start");
|
|
1425
|
+
const viteFrameworks = [
|
|
1426
|
+
["@sveltejs/kit", "sveltekit"],
|
|
1427
|
+
["@remix-run/dev", "remix"],
|
|
1428
|
+
["@react-router/dev", "react-router"],
|
|
1429
|
+
["@builder.io/qwik", "qwik"],
|
|
1430
|
+
["vite-plugin-solid", "solid"]
|
|
1431
|
+
];
|
|
1432
|
+
for (const [dep, framework] of viteFrameworks) {
|
|
1433
|
+
if (has(dep) && (viteConfig || has("vite"))) return tool("vite", framework, viteConfig, has("vite") ? "vite" : dep);
|
|
1434
|
+
}
|
|
1435
|
+
const builds = (name) => has(name) && new RegExp(`\\b${name}\\b`).test(buildScript ?? "");
|
|
1436
|
+
if (viteConfig || builds("vite")) return tool("vite", null, viteConfig, "vite");
|
|
1437
|
+
if (has("@vue/cli-service")) return tool("webpack", "vue-cli", firstExisting(dir, [...CONFIG_NAMES["vue-cli"]]), "@vue/cli-service");
|
|
1438
|
+
const webpackConfig = scriptConfig("webpack") ?? firstExisting(dir, [...CONFIG_NAMES.webpack]);
|
|
1439
|
+
if (webpackConfig || builds("webpack")) return tool("webpack", null, webpackConfig, "webpack");
|
|
1440
|
+
const rollupConfig = scriptConfig("rollup") ?? firstExisting(dir, [...CONFIG_NAMES.rollup]);
|
|
1441
|
+
if (rollupConfig || builds("rollup")) return tool("rollup", null, rollupConfig, "rollup");
|
|
1442
|
+
if (has("parcel") || /\bparcel\b/.test(buildScript ?? "")) return tool("parcel", null, null, "parcel");
|
|
1443
|
+
if (/\besbuild\b/.test(buildScript ?? "")) return tool("esbuild", null, null, "esbuild");
|
|
1444
|
+
return tool("unknown", null, null, "");
|
|
1445
|
+
}
|
|
1446
|
+
function ciProviders(dir, root, env) {
|
|
1447
|
+
const found = [];
|
|
1448
|
+
const top = root ?? dir;
|
|
1449
|
+
const near = (names) => {
|
|
1450
|
+
for (const current of ancestors(dir, top)) {
|
|
1451
|
+
const name = firstExisting(current, names);
|
|
1452
|
+
if (name) return rel(dir, join2(current, name));
|
|
1453
|
+
}
|
|
1454
|
+
return null;
|
|
1455
|
+
};
|
|
1456
|
+
const workflows = join2(top, ".github", "workflows");
|
|
1457
|
+
let workflowFile = null;
|
|
1458
|
+
try {
|
|
1459
|
+
workflowFile = readdirSync(workflows).filter((f) => /\.ya?ml$/.test(f)).sort()[0] ?? null;
|
|
1460
|
+
} catch {
|
|
1461
|
+
}
|
|
1462
|
+
if (workflowFile) {
|
|
1463
|
+
found.push({ id: "github", name: "GitHub Actions", evidence: rel(dir, join2(workflows, workflowFile)), repo: githubRepo(root) });
|
|
1464
|
+
} else if (env.GITHUB_ACTIONS === "true") {
|
|
1465
|
+
found.push({ id: "github", name: "GitHub Actions", evidence: "GITHUB_ACTIONS", repo: env.GITHUB_REPOSITORY ?? githubRepo(root) });
|
|
1466
|
+
}
|
|
1467
|
+
const vercel = near(["vercel.json", ".vercel"]);
|
|
1468
|
+
if (vercel || env.VERCEL) found.push({ id: "vercel", name: "Vercel", evidence: vercel ?? "VERCEL" });
|
|
1469
|
+
const netlify = near(["netlify.toml"]);
|
|
1470
|
+
if (netlify || env.NETLIFY) found.push({ id: "netlify", name: "Netlify", evidence: netlify ?? "NETLIFY" });
|
|
1471
|
+
const wrangler = near(["wrangler.toml", "wrangler.json", "wrangler.jsonc"]);
|
|
1472
|
+
const pagesConfig = wrangler && /pages_build_output_dir/.test(readText(join2(dir, wrangler)) ?? "") ? wrangler : null;
|
|
1473
|
+
const pkg = readJson(join2(dir, "package.json"));
|
|
1474
|
+
const pagesScript = Object.values(pkg?.scripts ?? {}).some((s) => /wrangler\s+pages\b/.test(s)) ? "package.json (wrangler pages)" : null;
|
|
1475
|
+
if (pagesConfig || pagesScript || env.CF_PAGES) {
|
|
1476
|
+
found.push({ id: "cloudflare-pages", name: "Cloudflare Pages", evidence: pagesConfig ?? pagesScript ?? "CF_PAGES" });
|
|
1477
|
+
}
|
|
1478
|
+
const gitlab = near([".gitlab-ci.yml"]);
|
|
1479
|
+
if (gitlab || env.GITLAB_CI) found.push({ id: "gitlab", name: "GitLab CI", evidence: gitlab ?? "GITLAB_CI" });
|
|
1480
|
+
return found;
|
|
1481
|
+
}
|
|
1482
|
+
function workflowsPassKey(dir, root) {
|
|
1483
|
+
const workflows = join2(root ?? dir, ".github", "workflows");
|
|
1484
|
+
try {
|
|
1485
|
+
return readdirSync(workflows).some((f) => /\.ya?ml$/.test(f) && /secrets\.AUTTER_RUNTIME_KEY\b/.test(readText(join2(workflows, f)) ?? ""));
|
|
1486
|
+
} catch {
|
|
1487
|
+
return false;
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
function angularInfo(json) {
|
|
1491
|
+
const projects = json.projects ?? {};
|
|
1492
|
+
const names = Object.keys(projects);
|
|
1493
|
+
const name = (typeof json.defaultProject === "string" && projects[json.defaultProject] ? json.defaultProject : null) ?? names.find((n) => projects[n].projectType === "application") ?? names[0];
|
|
1494
|
+
if (!name) return null;
|
|
1495
|
+
const project = projects[name];
|
|
1496
|
+
const build = (project.architect ?? project.targets)?.build;
|
|
1497
|
+
if (!build) return null;
|
|
1498
|
+
const options = build.options ?? {};
|
|
1499
|
+
const production = build.configurations?.production ?? {};
|
|
1500
|
+
const application = typeof build.builder === "string" && /:application$/.test(build.builder);
|
|
1501
|
+
const outputPath = production.outputPath ?? options.outputPath ?? `dist/${name}`;
|
|
1502
|
+
let outputDir;
|
|
1503
|
+
if (outputPath && typeof outputPath === "object") {
|
|
1504
|
+
const { base, browser } = outputPath;
|
|
1505
|
+
outputDir = browser === "" ? String(base) : `${base}/${browser ?? "browser"}`;
|
|
1506
|
+
} else {
|
|
1507
|
+
outputDir = application ? `${outputPath}/browser` : String(outputPath);
|
|
1508
|
+
}
|
|
1509
|
+
const prefix = production.deployUrl ?? options.deployUrl ?? production.baseHref ?? options.baseHref;
|
|
1510
|
+
return {
|
|
1511
|
+
project: name,
|
|
1512
|
+
outputDir,
|
|
1513
|
+
sourceMap: "sourceMap" in production ? production.sourceMap : options.sourceMap,
|
|
1514
|
+
urlPrefix: typeof prefix === "string" && prefix.startsWith("/") ? prefix : "/"
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
function mapStateFromLiteral(value, onTrue = "public") {
|
|
1518
|
+
if (value === true) return onTrue;
|
|
1519
|
+
if (value === false) return "off";
|
|
1520
|
+
if (value === "hidden") return "hidden";
|
|
1521
|
+
if (value === "inline") return "inline";
|
|
1522
|
+
return "unknown";
|
|
1523
|
+
}
|
|
1524
|
+
var CRA_ENV_FILES = [".env.production.local", ".env.local", ".env.production", ".env"];
|
|
1525
|
+
function craEnv(dir, name) {
|
|
1526
|
+
for (const file of CRA_ENV_FILES) {
|
|
1527
|
+
const match = new RegExp(`^\\s*${name}\\s*=\\s*["']?([^"'\\s#]*)`, "m").exec(readText(join2(dir, file)) ?? "");
|
|
1528
|
+
if (match) return { value: match[1], file };
|
|
1529
|
+
}
|
|
1530
|
+
return null;
|
|
1531
|
+
}
|
|
1532
|
+
function parseConfig(dir, config) {
|
|
1533
|
+
if (!config) return null;
|
|
1534
|
+
const code = readText(join2(dir, config));
|
|
1535
|
+
return code === null ? null : parseFile(code, config);
|
|
1536
|
+
}
|
|
1537
|
+
function outputFacts(dir, pkg, tool) {
|
|
1538
|
+
const script = tool.buildScript;
|
|
1539
|
+
const none = (state, setting = null, file = null) => ({ state, setting, file });
|
|
1540
|
+
switch (tool.id) {
|
|
1541
|
+
case "vite": {
|
|
1542
|
+
const ast = parseConfig(dir, tool.config);
|
|
1543
|
+
const obj = ast ? exportedConfigObject(ast) : null;
|
|
1544
|
+
const base = tool.framework === "nuxt" || tool.framework === "astro" ? ["vite"] : [];
|
|
1545
|
+
const sourcemap = literalProperty(obj, [...base, "build", "sourcemap"]);
|
|
1546
|
+
const viteBase = literalProperty(obj, [...base, "base"]);
|
|
1547
|
+
const outDir = (tool.framework === "astro" ? literalProperty(obj, ["outDir"]) : literalProperty(obj, [...base, "build", "outDir"])) ?? scriptFlag(script, "--outDir");
|
|
1548
|
+
const defaults = {
|
|
1549
|
+
nuxt: ".output/public",
|
|
1550
|
+
"solid-start": ".output/public",
|
|
1551
|
+
sveltekit: "build",
|
|
1552
|
+
remix: "build/client",
|
|
1553
|
+
"react-router": "build/client"
|
|
1554
|
+
};
|
|
1555
|
+
return {
|
|
1556
|
+
outputDir: typeof outDir === "string" ? outDir : defaults[tool.framework ?? ""] ?? "dist",
|
|
1557
|
+
urlPrefix: typeof viteBase === "string" && viteBase.startsWith("/") ? viteBase : "/",
|
|
1558
|
+
sourceMaps: sourcemap === void 0 ? none(obj || !tool.config ? "default-off" : "unknown") : none(mapStateFromLiteral(sourcemap), `build.sourcemap: ${JSON.stringify(sourcemap)}`, tool.config)
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
case "next": {
|
|
1562
|
+
const ast = parseConfig(dir, tool.config);
|
|
1563
|
+
const obj = ast ? nextConfigObject(ast) : null;
|
|
1564
|
+
const distDir = literalProperty(obj, ["distDir"]);
|
|
1565
|
+
const exported = literalProperty(obj, ["output"]) === "export";
|
|
1566
|
+
const publicMaps = literalProperty(obj, ["productionBrowserSourceMaps"]);
|
|
1567
|
+
const basePath = literalProperty(obj, ["assetPrefix"]) ?? literalProperty(obj, ["basePath"]);
|
|
1568
|
+
return {
|
|
1569
|
+
outputDir: exported ? "out" : `${typeof distDir === "string" ? distDir : ".next"}/static`,
|
|
1570
|
+
urlPrefix: exported ? `${typeof basePath === "string" ? basePath.replace(/\/+$/, "") : ""}/` : `${typeof basePath === "string" ? basePath.replace(/\/+$/, "") : ""}/_next/static/`,
|
|
1571
|
+
sourceMaps: publicMaps === true ? none("public", "productionBrowserSourceMaps: true", tool.config) : none(obj || !tool.config ? "default-off" : "unknown")
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
case "webpack":
|
|
1575
|
+
case "rollup": {
|
|
1576
|
+
const ast = parseConfig(dir, tool.config);
|
|
1577
|
+
const obj = ast ? exportedConfigObject(ast) : null;
|
|
1578
|
+
if (tool.framework === "vue-cli") {
|
|
1579
|
+
const productionSourceMap = literalProperty(obj, ["productionSourceMap"]);
|
|
1580
|
+
const outputDir = literalProperty(obj, ["outputDir"]);
|
|
1581
|
+
const publicPath = literalProperty(obj, ["publicPath"]);
|
|
1582
|
+
return {
|
|
1583
|
+
outputDir: typeof outputDir === "string" ? outputDir : "dist",
|
|
1584
|
+
urlPrefix: typeof publicPath === "string" && publicPath.startsWith("/") ? publicPath : "/",
|
|
1585
|
+
sourceMaps: productionSourceMap === false ? none("off", "productionSourceMap: false", tool.config) : none("default-public", productionSourceMap === true ? "productionSourceMap: true" : null, tool.config)
|
|
1586
|
+
};
|
|
1587
|
+
}
|
|
1588
|
+
if (tool.id === "webpack") {
|
|
1589
|
+
const devtool = literalProperty(obj, ["devtool"]);
|
|
1590
|
+
const path = obj ? outputPathOf(obj) : null;
|
|
1591
|
+
const publicPath = literalProperty(obj, ["output", "publicPath"]);
|
|
1592
|
+
return {
|
|
1593
|
+
outputDir: path ?? "dist",
|
|
1594
|
+
urlPrefix: typeof publicPath === "string" && publicPath.startsWith("/") ? publicPath : "/",
|
|
1595
|
+
sourceMaps: devtool === void 0 ? none(obj || !tool.config ? "default-off" : "unknown") : none(
|
|
1596
|
+
devtool === false ? "off" : typeof devtool === "string" ? devtool.includes("hidden") ? "hidden" : devtool.includes("inline") || devtool.startsWith("eval") ? "inline" : "public" : "unknown",
|
|
1597
|
+
`devtool: ${JSON.stringify(devtool)}`,
|
|
1598
|
+
tool.config
|
|
1599
|
+
)
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
const outDir = literalProperty(obj, ["output", "dir"]);
|
|
1603
|
+
const outFile = literalProperty(obj, ["output", "file"]);
|
|
1604
|
+
const sourcemap = literalProperty(obj, ["output", "sourcemap"]);
|
|
1605
|
+
return {
|
|
1606
|
+
outputDir: typeof outDir === "string" ? outDir : typeof outFile === "string" ? posix(dirname2(outFile)) : "dist",
|
|
1607
|
+
urlPrefix: "/",
|
|
1608
|
+
sourceMaps: sourcemap === void 0 ? none(obj || !tool.config ? "default-off" : "unknown") : none(mapStateFromLiteral(sourcemap), `output.sourcemap: ${JSON.stringify(sourcemap)}`, tool.config)
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
case "cra": {
|
|
1612
|
+
const buildPath = (script && /BUILD_PATH=(\S+)/.exec(script)?.[1]) ?? craEnv(dir, "BUILD_PATH")?.value;
|
|
1613
|
+
const inScript = script ? /GENERATE_SOURCEMAP=(\w+)/.exec(script) : null;
|
|
1614
|
+
const fromEnv = craEnv(dir, "GENERATE_SOURCEMAP");
|
|
1615
|
+
const homepage = pkg.homepage ? homepagePath(pkg.homepage) : "/";
|
|
1616
|
+
const setting = inScript ? { value: inScript[1], file: "package.json" } : fromEnv;
|
|
1617
|
+
return {
|
|
1618
|
+
outputDir: buildPath || "build",
|
|
1619
|
+
urlPrefix: homepage,
|
|
1620
|
+
sourceMaps: setting ? none(setting.value === "false" ? "off" : "default-public", `GENERATE_SOURCEMAP=${setting.value}`, setting.file) : none("default-public")
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
case "angular": {
|
|
1624
|
+
const json = readJson(join2(dir, "angular.json"));
|
|
1625
|
+
const info = json ? angularInfo(json) : null;
|
|
1626
|
+
if (!info) return { outputDir: null, urlPrefix: "/", sourceMaps: none("unknown", null, "angular.json") };
|
|
1627
|
+
const sm = info.sourceMap;
|
|
1628
|
+
const state = sm === void 0 ? "default-off" : sm === false ? "off" : sm === true ? "public" : sm.scripts === false ? "off" : sm.hidden ? "hidden" : "public";
|
|
1629
|
+
return {
|
|
1630
|
+
outputDir: info.outputDir,
|
|
1631
|
+
urlPrefix: info.urlPrefix,
|
|
1632
|
+
sourceMaps: none(state, sm === void 0 ? null : `sourceMap: ${JSON.stringify(sm)}`, "angular.json")
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
case "parcel": {
|
|
1636
|
+
const off = /--no-source-maps\b/.test(script ?? "");
|
|
1637
|
+
const publicUrl = scriptFlag(script, "--public-url");
|
|
1638
|
+
return {
|
|
1639
|
+
outputDir: scriptFlag(script, "--dist-dir") ?? "dist",
|
|
1640
|
+
urlPrefix: publicUrl && publicUrl.startsWith("/") ? publicUrl : "/",
|
|
1641
|
+
sourceMaps: off ? none("off", "--no-source-maps", "package.json") : none("default-public")
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
case "esbuild": {
|
|
1645
|
+
const flag = script ? /--sourcemap(?:=(\w+))?/.exec(script) : null;
|
|
1646
|
+
const outfile = scriptFlag(script, "--outfile");
|
|
1647
|
+
return {
|
|
1648
|
+
outputDir: scriptFlag(script, "--outdir") ?? (outfile ? posix(dirname2(outfile)) : "dist"),
|
|
1649
|
+
urlPrefix: "/",
|
|
1650
|
+
sourceMaps: flag ? none(flag[1] === "external" ? "hidden" : flag[1] === "inline" ? "inline" : "public", flag[0], "package.json") : none(script && /\besbuild\b/.test(script) ? "off" : "unknown")
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
default: {
|
|
1654
|
+
const existing = ["dist", "build", "out"].find((d) => existsSync3(join2(dir, d)) && statSync(join2(dir, d)).isDirectory());
|
|
1655
|
+
return { outputDir: existing ?? null, urlPrefix: "/", sourceMaps: none("unknown") };
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
function outputPathOf(obj) {
|
|
1660
|
+
const output = findProperty(obj, "output");
|
|
1661
|
+
if (!output || output === "ambiguous") return null;
|
|
1662
|
+
const value = unwrap(output.value);
|
|
1663
|
+
if (value?.type !== "ObjectExpression") return null;
|
|
1664
|
+
const path = findProperty(value, "path");
|
|
1665
|
+
if (!path || path === "ambiguous") return null;
|
|
1666
|
+
const node = unwrap(path.value);
|
|
1667
|
+
if (node?.type === "Literal" && typeof node.value === "string") return node.value;
|
|
1668
|
+
if (node?.type === "CallExpression" && node.callee?.type === "MemberExpression" && ["resolve", "join"].includes(node.callee.property?.name)) {
|
|
1669
|
+
const parts = node.arguments.filter((a) => !(a.type === "Identifier" && a.name === "__dirname"));
|
|
1670
|
+
if (parts.every((a) => a.type === "Literal" && typeof a.value === "string")) return parts.map((a) => a.value).join("/") || null;
|
|
1671
|
+
}
|
|
1672
|
+
return null;
|
|
1673
|
+
}
|
|
1674
|
+
function homepagePath(homepage) {
|
|
1675
|
+
if (homepage === "." || homepage === "./") return "/";
|
|
1676
|
+
try {
|
|
1677
|
+
const path = new URL(homepage, "https://autter.invalid").pathname;
|
|
1678
|
+
return path.endsWith("/") ? path : `${path}/`;
|
|
1679
|
+
} catch {
|
|
1680
|
+
return "/";
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
var SOURCE_EXTENSIONS = /\.(?:[cm]?[jt]sx?|vue|svelte|astro)$/;
|
|
1684
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", "coverage", "public", "static", "storybook-static"]);
|
|
1685
|
+
function releaseFacts(dir, skip) {
|
|
1686
|
+
const found = [];
|
|
1687
|
+
const visit = (current) => {
|
|
1688
|
+
let entries;
|
|
1689
|
+
try {
|
|
1690
|
+
entries = readdirSync(current, { withFileTypes: true });
|
|
1691
|
+
} catch {
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
for (const entry of entries) {
|
|
1695
|
+
if (entry.name.startsWith(".")) continue;
|
|
1696
|
+
const full = join2(current, entry.name);
|
|
1697
|
+
if (entry.isDirectory()) {
|
|
1698
|
+
if (!SKIP_DIRS.has(entry.name) && !skip.has(full)) visit(full);
|
|
1699
|
+
} else if (entry.isFile() && SOURCE_EXTENSIONS.test(entry.name)) {
|
|
1700
|
+
const code = readText(full);
|
|
1701
|
+
if (!code?.includes("initAutterBrowser")) continue;
|
|
1702
|
+
for (const passed of initCalls(code)) found.push({ file: rel(dir, full), passed });
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
};
|
|
1706
|
+
visit(dir);
|
|
1707
|
+
if (!found.length) return { file: null, passed: null };
|
|
1708
|
+
const missing = found.find((f) => f.passed === false);
|
|
1709
|
+
const unknown = found.find((f) => f.passed === null);
|
|
1710
|
+
const pick = missing ?? unknown ?? found[0];
|
|
1711
|
+
return { file: pick.file, passed: missing ? false : unknown ? null : true };
|
|
1712
|
+
}
|
|
1713
|
+
function initCalls(code) {
|
|
1714
|
+
const out = [];
|
|
1715
|
+
const re = /\binitAutterBrowser\s*\(/g;
|
|
1716
|
+
for (let match = re.exec(code); match; match = re.exec(code)) {
|
|
1717
|
+
let i = match.index + match[0].length;
|
|
1718
|
+
while (/\s/.test(code[i] ?? "")) i++;
|
|
1719
|
+
if (code[i] !== "{") {
|
|
1720
|
+
out.push(code[i] === ")" ? false : null);
|
|
1721
|
+
continue;
|
|
1722
|
+
}
|
|
1723
|
+
let depth = 0;
|
|
1724
|
+
let quote = null;
|
|
1725
|
+
let sawRelease = false;
|
|
1726
|
+
let sawSpread = false;
|
|
1727
|
+
for (; i < code.length; i++) {
|
|
1728
|
+
const ch = code[i];
|
|
1729
|
+
if (quote) {
|
|
1730
|
+
if (ch === "\\") i++;
|
|
1731
|
+
else if (ch === quote) quote = null;
|
|
1732
|
+
continue;
|
|
1733
|
+
}
|
|
1734
|
+
if (ch === '"' || ch === "'" || ch === "`") quote = ch;
|
|
1735
|
+
else if (ch === "{" || ch === "(" || ch === "[") depth++;
|
|
1736
|
+
else if (ch === "}" || ch === ")" || ch === "]") {
|
|
1737
|
+
depth--;
|
|
1738
|
+
if (depth === 0) break;
|
|
1739
|
+
} else if (depth === 1) {
|
|
1740
|
+
if (code.startsWith("...", i)) sawSpread = true;
|
|
1741
|
+
if (/[\s{,]/.test(code[i - 1] ?? "") && /^release\s*[:,}(]/.test(code.slice(i, i + 20))) sawRelease = true;
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
out.push(sawRelease ? true : sawSpread ? null : false);
|
|
1745
|
+
}
|
|
1746
|
+
return out;
|
|
1747
|
+
}
|
|
1748
|
+
function detect(dir, env = process.env) {
|
|
1749
|
+
const root = resolve2(dir);
|
|
1750
|
+
const pkg = readJson(join2(root, "package.json"));
|
|
1751
|
+
if (!pkg) throw new Error(`no readable package.json in ${root}`);
|
|
1752
|
+
const git = gitRoot(root);
|
|
1753
|
+
const tool = buildTool(root, pkg);
|
|
1754
|
+
const facts = outputFacts(root, pkg, tool);
|
|
1755
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1756
|
+
const sdkPackage = deps["@autter/runtime-next"] ? "@autter/runtime-next" : deps["@autter/runtime-browser"] ? "@autter/runtime-browser" : null;
|
|
1757
|
+
const sdkInstalled = sdkPackage ? installedVersion(root, sdkPackage) : null;
|
|
1758
|
+
const skip = new Set(facts.outputDir ? [resolve2(root, facts.outputDir.split("/")[0])] : []);
|
|
1759
|
+
return {
|
|
1760
|
+
dir: root,
|
|
1761
|
+
buildTool: tool,
|
|
1762
|
+
packageManager: packageManager(root, pkg, git),
|
|
1763
|
+
ci: ciProviders(root, git, env),
|
|
1764
|
+
outputDir: facts.outputDir,
|
|
1765
|
+
urlPrefix: facts.urlPrefix,
|
|
1766
|
+
sourceMaps: facts.sourceMaps,
|
|
1767
|
+
sdk: {
|
|
1768
|
+
package: sdkPackage,
|
|
1769
|
+
range: sdkPackage ? deps[sdkPackage] : null,
|
|
1770
|
+
installed: sdkInstalled,
|
|
1771
|
+
debugIds: sdkPackage ? atLeast(sdkInstalled ?? deps[sdkPackage], "1.6.0") : null
|
|
1772
|
+
},
|
|
1773
|
+
release: releaseFacts(root, skip),
|
|
1774
|
+
cli: { range: deps["@autter/runtime-cli"] ?? null, installed: installedVersion(root, "@autter/runtime-cli") },
|
|
1775
|
+
gitRoot: git
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
// src/setup.ts
|
|
1780
|
+
var PKG = "@autter/runtime-cli";
|
|
1781
|
+
var KEY_TEXT = "An upload-only key (autter_rtu_\u2026) is recommended: it can only upload source maps. The full server key (autter_rt_\u2026) also works.";
|
|
1782
|
+
var MIN_CLI = "1.2.0";
|
|
1783
|
+
function ownVersion() {
|
|
1784
|
+
try {
|
|
1785
|
+
return JSON.parse(readFileSync2(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
1786
|
+
} catch {
|
|
1787
|
+
return MIN_CLI;
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
var Changes = class {
|
|
1791
|
+
constructor(dir) {
|
|
1792
|
+
this.dir = dir;
|
|
1793
|
+
}
|
|
1794
|
+
dir;
|
|
1795
|
+
files = /* @__PURE__ */ new Map();
|
|
1796
|
+
read(file) {
|
|
1797
|
+
const path = join3(this.dir, file);
|
|
1798
|
+
return this.files.get(path) ?? readText(path);
|
|
1799
|
+
}
|
|
1800
|
+
write(file, content) {
|
|
1801
|
+
this.files.set(join3(this.dir, file), content);
|
|
1802
|
+
}
|
|
1803
|
+
};
|
|
1804
|
+
function rewriteJson(text, value) {
|
|
1805
|
+
const indent = /^\{\r?\n([ \t]+)"/.exec(text)?.[1] ?? " ";
|
|
1806
|
+
return `${JSON.stringify(value, null, indent)}${text.endsWith("\n") ? "\n" : ""}`;
|
|
1807
|
+
}
|
|
1808
|
+
function insertAfter(obj, after, key, value) {
|
|
1809
|
+
const out = {};
|
|
1810
|
+
let placed = false;
|
|
1811
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
1812
|
+
out[k] = v;
|
|
1813
|
+
if (k === after) {
|
|
1814
|
+
out[key] = value;
|
|
1815
|
+
placed = true;
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
if (!placed) out[key] = value;
|
|
1819
|
+
return out;
|
|
1820
|
+
}
|
|
1821
|
+
function addCommand(detected, spec, dev = true) {
|
|
1822
|
+
const pm = detected.packageManager.name;
|
|
1823
|
+
const verb = pm === "npm" ? "install" : "add";
|
|
1824
|
+
const flag = pm === "npm" || pm === "pnpm" ? "--save-dev" : "--dev";
|
|
1825
|
+
const argv = [pm, verb, ...dev ? [flag] : [], spec];
|
|
1826
|
+
return { command: argv.join(" "), argv };
|
|
1827
|
+
}
|
|
1828
|
+
var shellArg = (value) => /^[\w./@:~-]+$/.test(value) ? value : JSON.stringify(value);
|
|
1829
|
+
function sourcemapsScript(outputDir, urlPrefix) {
|
|
1830
|
+
const out = shellArg(outputDir);
|
|
1831
|
+
return `autter-runtime sourcemaps inject ${out} && autter-runtime sourcemaps upload ${out}${urlPrefix && urlPrefix !== "/" ? ` --url-prefix ${shellArg(urlPrefix)}` : ""} --delete-after-upload --best-effort`;
|
|
1832
|
+
}
|
|
1833
|
+
function commitVars(ci) {
|
|
1834
|
+
const byCi = {
|
|
1835
|
+
vercel: "VERCEL_GIT_COMMIT_SHA",
|
|
1836
|
+
netlify: "COMMIT_REF",
|
|
1837
|
+
"cloudflare-pages": "CF_PAGES_COMMIT_SHA",
|
|
1838
|
+
gitlab: "CI_COMMIT_SHA",
|
|
1839
|
+
github: "GITHUB_SHA"
|
|
1840
|
+
};
|
|
1841
|
+
const order = ["vercel", "netlify", "cloudflare-pages", "gitlab", "github"];
|
|
1842
|
+
const vars = order.filter((id) => ci.some((c) => c.id === id)).map((id) => byCi[id]);
|
|
1843
|
+
return [...vars, "GIT_SHA"];
|
|
1844
|
+
}
|
|
1845
|
+
function releaseSnippet(detected) {
|
|
1846
|
+
const vars = commitVars(detected.ci);
|
|
1847
|
+
const expr = `${vars.map((v) => `process.env.${v}`).join(" ?? ")} ?? ""`;
|
|
1848
|
+
const shellVar = `$${vars[0]}`;
|
|
1849
|
+
const init = detected.release.file ?? "the file that calls initAutterBrowser";
|
|
1850
|
+
const tool = detected.buildTool;
|
|
1851
|
+
const config = tool.config ?? void 0;
|
|
1852
|
+
const call = (value) => `// ${init}
|
|
1853
|
+
initAutterBrowser({ /* existing options */ release: ${value} });`;
|
|
1854
|
+
switch (tool.id) {
|
|
1855
|
+
case "vite": {
|
|
1856
|
+
const define = `define: {
|
|
1857
|
+
"import.meta.env.VITE_AUTTER_RELEASE": JSON.stringify(${expr}),
|
|
1858
|
+
},`;
|
|
1859
|
+
const wrapped = tool.framework === "nuxt" || tool.framework === "astro" ? `vite: {
|
|
1860
|
+
${define.replace(/\n/g, "\n ")}
|
|
1861
|
+
},` : define;
|
|
1862
|
+
return {
|
|
1863
|
+
file: config,
|
|
1864
|
+
snippet: `// ${config ?? "vite.config.ts"}: the commit SHA is not a secret
|
|
1865
|
+
${wrapped}
|
|
1866
|
+
|
|
1867
|
+
${call("import.meta.env.VITE_AUTTER_RELEASE")}`
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
case "next":
|
|
1871
|
+
return {
|
|
1872
|
+
file: config,
|
|
1873
|
+
snippet: `// ${config ?? "next.config.mjs"}: the commit SHA is not a secret
|
|
1874
|
+
env: { NEXT_PUBLIC_AUTTER_RELEASE: ${expr} },
|
|
1875
|
+
|
|
1876
|
+
${call("process.env.NEXT_PUBLIC_AUTTER_RELEASE")}`
|
|
1877
|
+
};
|
|
1878
|
+
case "webpack":
|
|
1879
|
+
return {
|
|
1880
|
+
file: config,
|
|
1881
|
+
snippet: `// ${config ?? "webpack.config.js"}
|
|
1882
|
+
new webpack.DefinePlugin({ "process.env.AUTTER_RELEASE": JSON.stringify(${expr}) }),
|
|
1883
|
+
|
|
1884
|
+
${call("process.env.AUTTER_RELEASE")}`
|
|
1885
|
+
};
|
|
1886
|
+
case "rollup":
|
|
1887
|
+
return {
|
|
1888
|
+
file: config,
|
|
1889
|
+
snippet: `// ${config ?? "rollup.config.js"} (@rollup/plugin-replace)
|
|
1890
|
+
replace({ preventAssignment: true, "process.env.AUTTER_RELEASE": JSON.stringify(${expr}) }),
|
|
1891
|
+
|
|
1892
|
+
${call("process.env.AUTTER_RELEASE")}`
|
|
1893
|
+
};
|
|
1894
|
+
case "cra":
|
|
1895
|
+
return {
|
|
1896
|
+
file: "package.json",
|
|
1897
|
+
snippet: `// package.json: CRA inlines REACT_APP_* variables
|
|
1898
|
+
"build": "REACT_APP_AUTTER_RELEASE=${shellVar} react-scripts build",
|
|
1899
|
+
|
|
1900
|
+
${call("process.env.REACT_APP_AUTTER_RELEASE")}`
|
|
1901
|
+
};
|
|
1902
|
+
case "angular":
|
|
1903
|
+
return {
|
|
1904
|
+
file: "package.json",
|
|
1905
|
+
snippet: `// package.json (Angular 17.2+ application builder)
|
|
1906
|
+
"build": "ng build --define \\"AUTTER_RELEASE='${shellVar}'\\"",
|
|
1907
|
+
|
|
1908
|
+
// ${init}
|
|
1909
|
+
declare const AUTTER_RELEASE: string | undefined;
|
|
1910
|
+
initAutterBrowser({ /* existing options */ release: typeof AUTTER_RELEASE === "string" ? AUTTER_RELEASE : undefined });`
|
|
1911
|
+
};
|
|
1912
|
+
case "parcel":
|
|
1913
|
+
return {
|
|
1914
|
+
file: "package.json",
|
|
1915
|
+
snippet: `// package.json: Parcel inlines process.env values at build time
|
|
1916
|
+
"build": "AUTTER_RELEASE=${shellVar} ${tool.buildScript ?? "parcel build"}",
|
|
1917
|
+
|
|
1918
|
+
${call("process.env.AUTTER_RELEASE")}`
|
|
1919
|
+
};
|
|
1920
|
+
case "esbuild":
|
|
1921
|
+
return {
|
|
1922
|
+
file: "package.json",
|
|
1923
|
+
snippet: `// the esbuild command
|
|
1924
|
+
--define:process.env.AUTTER_RELEASE=\\"${shellVar}\\"
|
|
1925
|
+
|
|
1926
|
+
${call("process.env.AUTTER_RELEASE")}`
|
|
1927
|
+
};
|
|
1928
|
+
default:
|
|
1929
|
+
return {
|
|
1930
|
+
file: void 0,
|
|
1931
|
+
snippet: `// Inline the commit SHA (${vars.join(", ")}) into the browser bundle with your build tool's define/env mechanism, then:
|
|
1932
|
+
${call("<the inlined commit SHA>")}`
|
|
1933
|
+
};
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
function secretTargets(detected) {
|
|
1937
|
+
const where = [];
|
|
1938
|
+
for (const ci of detected.ci) {
|
|
1939
|
+
switch (ci.id) {
|
|
1940
|
+
case "github":
|
|
1941
|
+
where.push({
|
|
1942
|
+
ci: ci.id,
|
|
1943
|
+
name: ci.name,
|
|
1944
|
+
command: `gh secret set AUTTER_RUNTIME_KEY${ci.repo ? ` --repo ${ci.repo}` : ""}`,
|
|
1945
|
+
note: "gh prompts for the value (or reads it from stdin), so the key never reaches your shell history. Then pass it to the step that runs the production build: env: AUTTER_RUNTIME_KEY: ${{ secrets.AUTTER_RUNTIME_KEY }}"
|
|
1946
|
+
});
|
|
1947
|
+
break;
|
|
1948
|
+
case "vercel":
|
|
1949
|
+
where.push({
|
|
1950
|
+
ci: ci.id,
|
|
1951
|
+
name: ci.name,
|
|
1952
|
+
command: "vercel env add AUTTER_RUNTIME_KEY production",
|
|
1953
|
+
note: "vercel prompts for the value (or reads it from stdin). Add it for preview too (vercel env add AUTTER_RUNTIME_KEY preview) if preview builds should upload."
|
|
1954
|
+
});
|
|
1955
|
+
break;
|
|
1956
|
+
case "netlify":
|
|
1957
|
+
where.push({
|
|
1958
|
+
ci: ci.id,
|
|
1959
|
+
name: ci.name,
|
|
1960
|
+
command: 'netlify env:set AUTTER_RUNTIME_KEY "$(cat)" --context production --scope builds --secret',
|
|
1961
|
+
note: "Paste the key, press Enter, then Ctrl-D: $(cat) reads it from stdin, so it stays out of your shell history. netlify env:set has no prompt; without a value it stores an empty one."
|
|
1962
|
+
});
|
|
1963
|
+
break;
|
|
1964
|
+
case "cloudflare-pages":
|
|
1965
|
+
where.push({
|
|
1966
|
+
ci: ci.id,
|
|
1967
|
+
name: ci.name,
|
|
1968
|
+
note: "Cloudflare dashboard \u2192 Workers & Pages \u2192 your project \u2192 Settings \u2192 Variables and Secrets \u2192 Add: type Secret, name AUTTER_RUNTIME_KEY, for Production (and Preview if preview builds should upload). Builds read it from the environment."
|
|
1969
|
+
});
|
|
1970
|
+
break;
|
|
1971
|
+
case "gitlab":
|
|
1972
|
+
where.push({
|
|
1973
|
+
ci: ci.id,
|
|
1974
|
+
name: ci.name,
|
|
1975
|
+
note: "GitLab \u2192 Settings \u2192 CI/CD \u2192 Variables \u2192 Add variable: key AUTTER_RUNTIME_KEY, visibility Masked (and Protected when only protected branches deploy). Jobs get it as an environment variable."
|
|
1976
|
+
});
|
|
1977
|
+
break;
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
if (!where.length) {
|
|
1981
|
+
where.push({
|
|
1982
|
+
ci: "other",
|
|
1983
|
+
name: "your build environment",
|
|
1984
|
+
note: "Add AUTTER_RUNTIME_KEY as a secret environment variable wherever the production build runs. Never in a committed .env file, and never under a public prefix (VITE_, NEXT_PUBLIC_, REACT_APP_, PUBLIC_)."
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
return where;
|
|
1988
|
+
}
|
|
1989
|
+
var PLUGINS = {
|
|
1990
|
+
vite: { name: "autterSourceMaps", spec: "@autter/runtime-cli/vite", element: "autterSourceMaps()" },
|
|
1991
|
+
rollup: { name: "autterSourceMaps", spec: "@autter/runtime-cli/rollup", element: "autterSourceMaps()" },
|
|
1992
|
+
webpack: { name: "AutterSourceMapsWebpackPlugin", spec: "@autter/runtime-cli/webpack", element: "new AutterSourceMapsWebpackPlugin()" }
|
|
1993
|
+
};
|
|
1994
|
+
function pluginSnippet(kind, container, cjs) {
|
|
1995
|
+
const plugin = PLUGINS[kind];
|
|
1996
|
+
const importLine = cjs ? `const { ${plugin.name} } = require("${plugin.spec}");` : `import { ${plugin.name} } from "${plugin.spec}";`;
|
|
1997
|
+
const plugins = `plugins: [/* existing plugins */ ${plugin.element}],`;
|
|
1998
|
+
const body = container ? `${container}: {
|
|
1999
|
+
${plugins}
|
|
2000
|
+
},` : plugins;
|
|
2001
|
+
return `${importLine}
|
|
2002
|
+
|
|
2003
|
+
// in the exported config object:
|
|
2004
|
+
${body}`;
|
|
2005
|
+
}
|
|
2006
|
+
function runSetup(dir, options = {}) {
|
|
2007
|
+
const env = options.env ?? process.env;
|
|
2008
|
+
const detected = detect(dir, env);
|
|
2009
|
+
const changes = new Changes(detected.dir);
|
|
2010
|
+
const actions = [];
|
|
2011
|
+
const next = [];
|
|
2012
|
+
const tool = detected.buildTool;
|
|
2013
|
+
const pkgText = changes.read("package.json");
|
|
2014
|
+
let pkg = JSON.parse(pkgText);
|
|
2015
|
+
const version = ownVersion();
|
|
2016
|
+
const range = `^${atLeast(version, MIN_CLI) ? version : MIN_CLI}`;
|
|
2017
|
+
const install = addCommand(detected, `${PKG}@${range}`);
|
|
2018
|
+
const manual = (description, file, snippet, command) => actions.push({ type: "manual", ...file ? { file } : {}, description, ...snippet ? { snippet } : {}, ...command ? { command } : {} });
|
|
2019
|
+
const savePkg = () => changes.write("package.json", rewriteJson(changes.read("package.json"), pkg));
|
|
2020
|
+
if (tool.id === "unknown" && !pkg.scripts?.build) {
|
|
2021
|
+
manual(
|
|
2022
|
+
"No build tool and no build script: if this app ships hand-written, unminified scripts there is nothing to upload. If another step builds its JavaScript, run this after it, on the output directory",
|
|
2023
|
+
"package.json",
|
|
2024
|
+
sourcemapsScript("<output dir>", "/")
|
|
2025
|
+
);
|
|
2026
|
+
return {
|
|
2027
|
+
dir: detected.dir,
|
|
2028
|
+
dryRun: Boolean(options.dryRun),
|
|
2029
|
+
detected,
|
|
2030
|
+
actions,
|
|
2031
|
+
secret: { name: "AUTTER_RUNTIME_KEY", key: KEY_TEXT, where: [] },
|
|
2032
|
+
next: ["Nothing was changed."],
|
|
2033
|
+
exitCode: 2
|
|
2034
|
+
};
|
|
2035
|
+
}
|
|
2036
|
+
const current = pkg.devDependencies?.[PKG] ?? pkg.dependencies?.[PKG];
|
|
2037
|
+
let dependencyChanged = false;
|
|
2038
|
+
if (!current) {
|
|
2039
|
+
const devDependencies = { ...pkg.devDependencies };
|
|
2040
|
+
const keys = Object.keys(devDependencies);
|
|
2041
|
+
const sorted = keys.every((k, i) => i === 0 || keys[i - 1].localeCompare(k) <= 0);
|
|
2042
|
+
devDependencies[PKG] = range;
|
|
2043
|
+
pkg = {
|
|
2044
|
+
...pkg,
|
|
2045
|
+
devDependencies: sorted ? Object.fromEntries(Object.entries(devDependencies).sort(([a], [b]) => a.localeCompare(b))) : devDependencies
|
|
2046
|
+
};
|
|
2047
|
+
if (!("devDependencies" in JSON.parse(pkgText))) pkg = insertAfter(pkg, "dependencies", "devDependencies", pkg.devDependencies);
|
|
2048
|
+
dependencyChanged = true;
|
|
2049
|
+
actions.push({ type: "add-dependency", file: "package.json", description: `Add ${PKG}@${range} to devDependencies`, command: install.command });
|
|
2050
|
+
} else if (atLeast(current, MIN_CLI) === false) {
|
|
2051
|
+
const section = pkg.devDependencies?.[PKG] ? "devDependencies" : "dependencies";
|
|
2052
|
+
pkg = { ...pkg, [section]: { ...pkg[section], [PKG]: range } };
|
|
2053
|
+
dependencyChanged = true;
|
|
2054
|
+
actions.push({
|
|
2055
|
+
type: "add-dependency",
|
|
2056
|
+
file: "package.json",
|
|
2057
|
+
description: `Raise ${PKG} from ${current} to ${range} (setup, verify and upload --best-effort need ${MIN_CLI})`,
|
|
2058
|
+
command: install.command
|
|
2059
|
+
});
|
|
2060
|
+
}
|
|
2061
|
+
if (dependencyChanged) savePkg();
|
|
2062
|
+
const applyEdit = (result, file, description, fallback) => {
|
|
2063
|
+
if (result.status === "edited") {
|
|
2064
|
+
changes.write(file, result.code);
|
|
2065
|
+
actions.push({ type: "edit", file, description });
|
|
2066
|
+
} else if (result.status === "manual") {
|
|
2067
|
+
manual(`${result.reason}: ${description[0].toLowerCase()}${description.slice(1)} by hand`, file, fallback);
|
|
2068
|
+
} else if (result.note) {
|
|
2069
|
+
next.push(result.note);
|
|
2070
|
+
}
|
|
2071
|
+
};
|
|
2072
|
+
const addScript = () => {
|
|
2073
|
+
const out = detected.outputDir;
|
|
2074
|
+
const scripts = { ...pkg.scripts };
|
|
2075
|
+
if (Object.values(scripts).some((s) => /autter-runtime\s+sourcemaps\s+(inject|upload)/.test(s))) return;
|
|
2076
|
+
if (!out) {
|
|
2077
|
+
manual("Could not tell where the build writes its output: after the production build, inject and upload from that directory", void 0, sourcemapsScript("<output dir>", "/"));
|
|
2078
|
+
return;
|
|
2079
|
+
}
|
|
2080
|
+
const script = sourcemapsScript(out, detected.urlPrefix);
|
|
2081
|
+
if (!scripts.build) {
|
|
2082
|
+
manual(`No build script in package.json: run this after the production build (it writes ${out})`, "package.json", script);
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
const { name: pm, yarnBerry } = detected.packageManager;
|
|
2086
|
+
const post = pm === "npm" || pm === "yarn" && !yarnBerry;
|
|
2087
|
+
if (post) {
|
|
2088
|
+
pkg = { ...pkg, scripts: scripts.postbuild ? { ...scripts, postbuild: `${scripts.postbuild} && ${script}` } : insertAfter(scripts, "build", "postbuild", script) };
|
|
2089
|
+
actions.push({
|
|
2090
|
+
type: "add-script",
|
|
2091
|
+
file: "package.json",
|
|
2092
|
+
description: `${scripts.postbuild ? "Extend" : "Add"} the postbuild script: inject debug ids into ${out}, upload its maps, then delete them (with or without a key, so maps are never deployed; never fails the build)`,
|
|
2093
|
+
snippet: `"postbuild": ${JSON.stringify(pkg.scripts.postbuild)}`
|
|
2094
|
+
});
|
|
2095
|
+
} else {
|
|
2096
|
+
pkg = { ...pkg, scripts: { ...scripts, build: `${scripts.build} && ${script}` } };
|
|
2097
|
+
actions.push({
|
|
2098
|
+
type: "add-script",
|
|
2099
|
+
file: "package.json",
|
|
2100
|
+
description: `Append to the build script (${pm}${pm === "yarn" ? " 2+" : ""} does not run postbuild): inject debug ids into ${out}, upload its maps, then delete them (never fails the build)`,
|
|
2101
|
+
snippet: `"build": ${JSON.stringify(pkg.scripts.build)}`
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
2104
|
+
savePkg();
|
|
2105
|
+
};
|
|
2106
|
+
switch (tool.id) {
|
|
2107
|
+
case "vite":
|
|
2108
|
+
case "rollup": {
|
|
2109
|
+
const kind = tool.id;
|
|
2110
|
+
const container = tool.framework === "nuxt" || tool.framework === "astro" ? "vite" : void 0;
|
|
2111
|
+
if (tool.framework === "solid-start") {
|
|
2112
|
+
manual(
|
|
2113
|
+
"SolidStart's app.config wraps Vite through vinxi: add the plugin to the client router's Vite config by hand",
|
|
2114
|
+
tool.config ?? "app.config.ts",
|
|
2115
|
+
pluginSnippet("vite", "vite", false)
|
|
2116
|
+
);
|
|
2117
|
+
break;
|
|
2118
|
+
}
|
|
2119
|
+
if (!tool.config) {
|
|
2120
|
+
if (kind === "vite" && !tool.framework) {
|
|
2121
|
+
const file = existsSync4(join3(detected.dir, "tsconfig.json")) ? "vite.config.ts" : "vite.config.js";
|
|
2122
|
+
changes.write(
|
|
2123
|
+
file,
|
|
2124
|
+
`import { defineConfig } from "vite";
|
|
2125
|
+
import { autterSourceMaps } from "@autter/runtime-cli/vite";
|
|
2126
|
+
|
|
2127
|
+
export default defineConfig({
|
|
2128
|
+
plugins: [autterSourceMaps()],
|
|
2129
|
+
});
|
|
2130
|
+
`
|
|
2131
|
+
);
|
|
2132
|
+
actions.push({ type: "edit", file, description: `Create ${file} with the autterSourceMaps() plugin (the project had no Vite config)` });
|
|
2133
|
+
} else {
|
|
2134
|
+
manual(`No ${tool.framework ?? kind} config file found: create one with the plugin`, void 0, pluginSnippet(kind, container, false));
|
|
2135
|
+
}
|
|
2136
|
+
break;
|
|
2137
|
+
}
|
|
2138
|
+
const code = changes.read(tool.config);
|
|
2139
|
+
const cjs = /\bmodule\.exports\b/.test(code);
|
|
2140
|
+
applyEdit(
|
|
2141
|
+
addBundlerPlugin(code, tool.config, { ...PLUGINS[kind], container }),
|
|
2142
|
+
tool.config,
|
|
2143
|
+
`Add ${PLUGINS[kind].element} to the ${container ? `${container}.` : ""}plugins in ${tool.config}`,
|
|
2144
|
+
pluginSnippet(kind, container, cjs)
|
|
2145
|
+
);
|
|
2146
|
+
if (detected.sourceMaps.state === "inline") {
|
|
2147
|
+
manual(
|
|
2148
|
+
`${tool.config} writes inline source maps (${detected.sourceMaps.setting}): there are no map files to upload. Use "hidden"`,
|
|
2149
|
+
tool.config,
|
|
2150
|
+
kind === "vite" ? `build: { sourcemap: "hidden" },` : `output: { sourcemap: "hidden" },`
|
|
2151
|
+
);
|
|
2152
|
+
}
|
|
2153
|
+
break;
|
|
2154
|
+
}
|
|
2155
|
+
case "webpack": {
|
|
2156
|
+
const container = tool.framework === "vue-cli" ? "configureWebpack" : void 0;
|
|
2157
|
+
if (!tool.config) {
|
|
2158
|
+
manual(
|
|
2159
|
+
`No ${tool.framework === "vue-cli" ? "vue.config.js" : "webpack config"} found: add the plugin to the config your build uses`,
|
|
2160
|
+
void 0,
|
|
2161
|
+
pluginSnippet("webpack", container, true)
|
|
2162
|
+
);
|
|
2163
|
+
break;
|
|
2164
|
+
}
|
|
2165
|
+
const code = changes.read(tool.config);
|
|
2166
|
+
const cjs = /\bmodule\.exports\b/.test(code) || !/\b(import|export)\b/.test(code);
|
|
2167
|
+
applyEdit(
|
|
2168
|
+
addBundlerPlugin(code, tool.config, { ...PLUGINS.webpack, container }),
|
|
2169
|
+
tool.config,
|
|
2170
|
+
`Add new AutterSourceMapsWebpackPlugin() to the ${container ? `${container}.` : ""}plugins in ${tool.config}`,
|
|
2171
|
+
pluginSnippet("webpack", container, cjs)
|
|
2172
|
+
);
|
|
2173
|
+
if (detected.sourceMaps.state === "inline") {
|
|
2174
|
+
manual(`${tool.config} uses an inline/eval devtool: there are no map files to upload. Use "hidden-source-map" for production`, tool.config, `devtool: "hidden-source-map",`);
|
|
2175
|
+
}
|
|
2176
|
+
break;
|
|
2177
|
+
}
|
|
2178
|
+
case "next": {
|
|
2179
|
+
const snippet = `import { withAutterSourceMaps } from "@autter/runtime-cli/next";
|
|
2180
|
+
|
|
2181
|
+
// outermost, around any other wrappers:
|
|
2182
|
+
export default withAutterSourceMaps(withOtherPlugins(nextConfig));`;
|
|
2183
|
+
if (!tool.config) {
|
|
2184
|
+
changes.write("next.config.mjs", `import { withAutterSourceMaps } from "@autter/runtime-cli/next";
|
|
2185
|
+
|
|
2186
|
+
/** @type {import("next").NextConfig} */
|
|
2187
|
+
const nextConfig = {};
|
|
2188
|
+
|
|
2189
|
+
export default withAutterSourceMaps(nextConfig);
|
|
2190
|
+
`);
|
|
2191
|
+
actions.push({ type: "edit", file: "next.config.mjs", description: "Create next.config.mjs exporting withAutterSourceMaps(nextConfig) (the project had none)" });
|
|
2192
|
+
} else {
|
|
2193
|
+
applyEdit(wrapNextConfig(changes.read(tool.config), tool.config), tool.config, `Wrap the exported config in withAutterSourceMaps(...), outermost, in ${tool.config}`, snippet);
|
|
2194
|
+
}
|
|
2195
|
+
if (/--turbo(pack)?\b/.test(tool.buildScript ?? "") && atLeast(tool.version, "15.4.1") === false) {
|
|
2196
|
+
next.push(`Next.js ${tool.version} builds with Turbopack: withAutterSourceMaps covers Turbopack production builds from Next.js 15.4.1 (runAfterProductionCompile). Upgrade Next.js, or build with webpack.`);
|
|
2197
|
+
}
|
|
2198
|
+
break;
|
|
2199
|
+
}
|
|
2200
|
+
case "cra": {
|
|
2201
|
+
addScript();
|
|
2202
|
+
const sm = detected.sourceMaps;
|
|
2203
|
+
if (sm.state === "off" && sm.file && CRA_ENV_FILES.includes(sm.file)) {
|
|
2204
|
+
const text = changes.read(sm.file);
|
|
2205
|
+
changes.write(sm.file, text.replace(/^(\s*GENERATE_SOURCEMAP\s*=\s*)["']?false["']?/m, "$1true"));
|
|
2206
|
+
actions.push({
|
|
2207
|
+
type: "edit",
|
|
2208
|
+
file: sm.file,
|
|
2209
|
+
description: `Set GENERATE_SOURCEMAP=true in ${sm.file}: the postbuild step uploads the maps and deletes them, so they are never deployed`
|
|
2210
|
+
});
|
|
2211
|
+
} else if (sm.state === "off") {
|
|
2212
|
+
manual("The build script sets GENERATE_SOURCEMAP=false: remove it (or set true) so there are maps to upload; the postbuild step deletes them", "package.json", `"build": "react-scripts build"`);
|
|
2213
|
+
}
|
|
2214
|
+
break;
|
|
2215
|
+
}
|
|
2216
|
+
case "angular": {
|
|
2217
|
+
addScript();
|
|
2218
|
+
const text = changes.read("angular.json");
|
|
2219
|
+
let json = null;
|
|
2220
|
+
try {
|
|
2221
|
+
json = text ? JSON.parse(text) : null;
|
|
2222
|
+
} catch {
|
|
2223
|
+
json = null;
|
|
2224
|
+
}
|
|
2225
|
+
const info = json ? angularInfo(json) : null;
|
|
2226
|
+
const hidden = { scripts: true, styles: false, hidden: true, vendor: false };
|
|
2227
|
+
const sm = detected.sourceMaps.state;
|
|
2228
|
+
if (!json || !info) {
|
|
2229
|
+
manual("angular.json could not be read: turn on hidden script source maps for the production build", "angular.json", `"sourceMap": ${JSON.stringify(hidden)}`);
|
|
2230
|
+
} else if (sm === "default-off" || sm === "off") {
|
|
2231
|
+
const architect = json.projects[info.project].architect ?? json.projects[info.project].targets;
|
|
2232
|
+
const build = architect.build;
|
|
2233
|
+
const configurations = build.configurations ?? {};
|
|
2234
|
+
if (configurations.production) configurations.production.sourceMap = hidden;
|
|
2235
|
+
else build.options = { ...build.options, sourceMap: hidden };
|
|
2236
|
+
changes.write("angular.json", rewriteJson(text, json));
|
|
2237
|
+
actions.push({
|
|
2238
|
+
type: "edit",
|
|
2239
|
+
file: "angular.json",
|
|
2240
|
+
description: `Turn on hidden script source maps for ${info.project}'s ${configurations.production ? "production " : ""}build (written next to the bundles, not referenced by them; the postbuild step uploads and deletes them)`,
|
|
2241
|
+
snippet: `"sourceMap": ${JSON.stringify(hidden)}`
|
|
2242
|
+
});
|
|
2243
|
+
} else if (sm === "public") {
|
|
2244
|
+
next.push('angular.json emits public source maps (referenced by the bundles); the postbuild step deletes them after upload. Use "sourceMap": { "scripts": true, "hidden": true } to stop referencing them.');
|
|
2245
|
+
}
|
|
2246
|
+
break;
|
|
2247
|
+
}
|
|
2248
|
+
case "parcel":
|
|
2249
|
+
addScript();
|
|
2250
|
+
if (detected.sourceMaps.state === "off") {
|
|
2251
|
+
manual("The build script passes --no-source-maps: remove it so there are maps to upload (the postbuild step deletes them)", "package.json", (tool.buildScript ?? "").replace(/\s*--no-source-maps\b/, ""));
|
|
2252
|
+
}
|
|
2253
|
+
break;
|
|
2254
|
+
case "esbuild":
|
|
2255
|
+
addScript();
|
|
2256
|
+
if (detected.sourceMaps.state === "off" || detected.sourceMaps.state === "unknown") {
|
|
2257
|
+
manual(
|
|
2258
|
+
'Make esbuild write external source maps (written next to the bundles, not referenced by them): add --sourcemap=external to the esbuild command, or sourcemap: "external" to its build() options',
|
|
2259
|
+
"package.json",
|
|
2260
|
+
"--sourcemap=external"
|
|
2261
|
+
);
|
|
2262
|
+
} else if (detected.sourceMaps.state === "inline") {
|
|
2263
|
+
manual("esbuild writes inline source maps: there are no map files to upload. Use --sourcemap=external", "package.json", "--sourcemap=external");
|
|
2264
|
+
}
|
|
2265
|
+
break;
|
|
2266
|
+
default:
|
|
2267
|
+
if (detected.outputDir && pkg.scripts?.build) {
|
|
2268
|
+
addScript();
|
|
2269
|
+
manual(
|
|
2270
|
+
`Build tool not recognised: make sure the production build writes source maps (hidden if it can) into ${detected.outputDir}, and that ${detected.outputDir} is the deployed output`,
|
|
2271
|
+
void 0
|
|
2272
|
+
);
|
|
2273
|
+
} else {
|
|
2274
|
+
manual(
|
|
2275
|
+
"Build tool not recognised: after the production build, inject debug ids and upload the maps from the output directory",
|
|
2276
|
+
void 0,
|
|
2277
|
+
sourcemapsScript(detected.outputDir ?? "<output dir>", "/")
|
|
2278
|
+
);
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
if (detected.sdk.package && detected.release.file && detected.release.passed === false) {
|
|
2282
|
+
const { file, snippet } = releaseSnippet(detected);
|
|
2283
|
+
manual(
|
|
2284
|
+
`initAutterBrowser in ${detected.release.file} gets no release: pass the commit SHA through ${tool.id === "unknown" ? "your build tool's" : `${tool.id}'s`} public env or define mechanism, so release-based features match the upload (debug ids match maps either way)`,
|
|
2285
|
+
file ?? detected.release.file,
|
|
2286
|
+
snippet
|
|
2287
|
+
);
|
|
2288
|
+
} else if (detected.sdk.package && detected.release.file && detected.release.passed === null) {
|
|
2289
|
+
next.push(`Check that initAutterBrowser in ${detected.release.file} passes release: the commit SHA (setup could not read its options).`);
|
|
2290
|
+
}
|
|
2291
|
+
if (detected.sdk.package && detected.sdk.debugIds === false) {
|
|
2292
|
+
manual(
|
|
2293
|
+
`${detected.sdk.package} ${detected.sdk.installed ?? detected.sdk.range} predates 1.6.0, so exceptions carry no debug ids: upgrade it`,
|
|
2294
|
+
"package.json",
|
|
2295
|
+
void 0,
|
|
2296
|
+
addCommand(detected, `${detected.sdk.package}@latest`, false).command
|
|
2297
|
+
);
|
|
2298
|
+
}
|
|
2299
|
+
if (!detected.sdk.package) {
|
|
2300
|
+
next.push("No Autter browser SDK in package.json: source maps resolve browser errors that @autter/runtime-browser (or @autter/runtime-next) 1.6.0+ reports. Set it up first, or alongside.");
|
|
2301
|
+
}
|
|
2302
|
+
if (detected.sourceMaps.state === "public" && tool.id !== "angular") {
|
|
2303
|
+
next.push(
|
|
2304
|
+
`${detected.sourceMaps.file ?? "The build"} emits public source maps (${detected.sourceMaps.setting}): your source is downloadable from production. Switch to hidden maps, or set deleteAfterUpload: true on the plugin, if you don't publish them on purpose.`
|
|
2305
|
+
);
|
|
2306
|
+
}
|
|
2307
|
+
const where = secretTargets(detected);
|
|
2308
|
+
for (const target of where) {
|
|
2309
|
+
actions.push({
|
|
2310
|
+
type: "set-secret",
|
|
2311
|
+
description: `Store AUTTER_RUNTIME_KEY in ${target.name}${target.command ? "" : `: ${target.note}`}`,
|
|
2312
|
+
...target.command ? { command: target.command } : {}
|
|
2313
|
+
});
|
|
2314
|
+
}
|
|
2315
|
+
const hosting = detected.ci.some((c) => c.id === "vercel" || c.id === "netlify" || c.id === "cloudflare-pages");
|
|
2316
|
+
if (detected.ci.some((c) => c.id === "github") && !workflowsPassKey(detected.dir, detected.gitRoot)) {
|
|
2317
|
+
const snippet = `- run: ${detected.packageManager.name === "npm" ? "npm run" : detected.packageManager.name} build
|
|
2318
|
+
env:
|
|
2319
|
+
AUTTER_RUNTIME_KEY: \${{ secrets.AUTTER_RUNTIME_KEY }}`;
|
|
2320
|
+
if (hosting) {
|
|
2321
|
+
next.push(`If a GitHub workflow builds the deployed output, give that step the key:
|
|
2322
|
+
${snippet}`);
|
|
2323
|
+
} else {
|
|
2324
|
+
manual(
|
|
2325
|
+
"Pass the secret to the GitHub Actions step that runs the production build (only that job; never to jobs that run untrusted pull request code)",
|
|
2326
|
+
detected.ci.find((c) => c.id === "github").evidence,
|
|
2327
|
+
snippet
|
|
2328
|
+
);
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
if (!options.dryRun) {
|
|
2332
|
+
for (const [path, content] of changes.files) writeFileSync(path, content);
|
|
2333
|
+
}
|
|
2334
|
+
const needsInstall = dependencyChanged || !detected.cli.installed;
|
|
2335
|
+
if (needsInstall && options.install && !options.dryRun) {
|
|
2336
|
+
const [command, ...args] = install.argv;
|
|
2337
|
+
const result = spawnSync(command, args, {
|
|
2338
|
+
cwd: detected.dir,
|
|
2339
|
+
stdio: options.installOutput === "stderr" ? ["ignore", 2, 2] : "inherit",
|
|
2340
|
+
shell: process.platform === "win32"
|
|
2341
|
+
});
|
|
2342
|
+
if (result.status !== 0) throw new Error(`${install.command} failed (exit ${result.status ?? result.signal ?? "unknown"})`);
|
|
2343
|
+
} else if (needsInstall) {
|
|
2344
|
+
next.push(`Install it: ${install.command}`);
|
|
2345
|
+
}
|
|
2346
|
+
next.push(
|
|
2347
|
+
"Get an upload-only key (autter_rtu_\u2026) for this repository from Autter: it can only upload source maps, so it is the right key for CI. The full server key (autter_rt_\u2026) also works. Never paste it into chat or commit it.",
|
|
2348
|
+
`Store it as AUTTER_RUNTIME_KEY where the production build runs (see secret.where). Without it, builds still pass and upload nothing.`
|
|
2349
|
+
);
|
|
2350
|
+
if (detected.outputDir) {
|
|
2351
|
+
next.push(`After a production build with the key set, check it: npx autter-runtime sourcemaps verify ${shellArg(detected.outputDir)}`);
|
|
2352
|
+
}
|
|
2353
|
+
return {
|
|
2354
|
+
dir: detected.dir,
|
|
2355
|
+
dryRun: Boolean(options.dryRun),
|
|
2356
|
+
detected,
|
|
2357
|
+
actions,
|
|
2358
|
+
secret: {
|
|
2359
|
+
name: "AUTTER_RUNTIME_KEY",
|
|
2360
|
+
key: KEY_TEXT,
|
|
2361
|
+
where
|
|
2362
|
+
},
|
|
2363
|
+
next,
|
|
2364
|
+
exitCode: actions.some((a) => a.type === "manual") ? 2 : 0
|
|
2365
|
+
};
|
|
2366
|
+
}
|
|
2367
|
+
var LABEL = {
|
|
2368
|
+
edit: "edit",
|
|
2369
|
+
"add-dependency": "dependency",
|
|
2370
|
+
"add-script": "script",
|
|
2371
|
+
manual: "manual",
|
|
2372
|
+
"set-secret": "secret"
|
|
2373
|
+
};
|
|
2374
|
+
function formatSetup(result) {
|
|
2375
|
+
const d = result.detected;
|
|
2376
|
+
const lines = [`autter-runtime sourcemaps setup${result.dryRun ? " (dry run: nothing written)" : ""}: ${d.dir}`, "", "Detected"];
|
|
2377
|
+
const row = (label, value) => lines.push(` ${label.padEnd(16)} ${value}`);
|
|
2378
|
+
const tool = d.buildTool;
|
|
2379
|
+
row("build tool", `${tool.id}${tool.framework ? ` (${tool.framework})` : ""}${tool.config ? `, ${tool.config}` : ""}${tool.version ? `, ${tool.version}` : ""}`);
|
|
2380
|
+
row("package manager", `${d.packageManager.name}${d.packageManager.lockfile ? ` (${d.packageManager.lockfile})` : ""}`);
|
|
2381
|
+
row("CI", d.ci.length ? d.ci.map((c) => `${c.name} (${c.evidence}${c.repo ? `, ${c.repo}` : ""})`).join("; ") : "none found");
|
|
2382
|
+
row("output", `${d.outputDir ?? "unknown"}${d.urlPrefix !== "/" ? `, served from ${d.urlPrefix}` : ""}`);
|
|
2383
|
+
row("source maps", `${d.sourceMaps.state}${d.sourceMaps.setting ? ` (${d.sourceMaps.setting})` : ""}`);
|
|
2384
|
+
row(
|
|
2385
|
+
"browser SDK",
|
|
2386
|
+
d.sdk.package ? `${d.sdk.package} ${d.sdk.installed ?? d.sdk.range}${d.sdk.debugIds === false ? " (no debug ids: needs 1.6.0+)" : ""}` : "none in package.json"
|
|
2387
|
+
);
|
|
2388
|
+
row(
|
|
2389
|
+
"release",
|
|
2390
|
+
d.release.file ? `${d.release.file}: ${d.release.passed === true ? "passed" : d.release.passed === false ? "not passed" : "could not tell"}` : "no initAutterBrowser call found"
|
|
2391
|
+
);
|
|
2392
|
+
row("runtime-cli", d.cli.range ? `${d.cli.range}${d.cli.installed ? ` (installed ${d.cli.installed})` : " (not installed)"}` : "not in package.json");
|
|
2393
|
+
lines.push("", "Actions");
|
|
2394
|
+
if (!result.actions.length) lines.push(" nothing to do");
|
|
2395
|
+
for (const action of result.actions) {
|
|
2396
|
+
const status = action.type === "manual" ? "TODO" : action.type === "set-secret" ? "KEY " : result.dryRun ? "PLAN" : "DONE";
|
|
2397
|
+
lines.push(` ${status} ${LABEL[action.type].padEnd(10)} ${action.file ? `${action.file}: ` : ""}${action.description}`);
|
|
2398
|
+
if (action.command) lines.push(` run: ${action.command}`);
|
|
2399
|
+
if (action.snippet && action.type === "manual") for (const line of action.snippet.split("\n")) lines.push(` | ${line}`);
|
|
2400
|
+
}
|
|
2401
|
+
lines.push("", `Secret: ${result.secret.name}. ${result.secret.key}`);
|
|
2402
|
+
for (const target of result.secret.where) {
|
|
2403
|
+
lines.push(` ${target.name}: ${target.command ?? ""}${target.command ? "" : target.note}`);
|
|
2404
|
+
if (target.command) lines.push(` ${target.note}`);
|
|
2405
|
+
}
|
|
2406
|
+
lines.push("", "Next");
|
|
2407
|
+
result.next.forEach((item, i) => lines.push(` ${i + 1}. ${item.replace(/\n/g, "\n ")}`));
|
|
2408
|
+
return lines;
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2411
|
+
// src/verify.ts
|
|
2412
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2413
|
+
import { readdir as readdir2, readFile as readFile4 } from "fs/promises";
|
|
2414
|
+
import { dirname as dirname3, join as join4, relative as relative4, resolve as resolve3, sep as sep4 } from "path";
|
|
2415
|
+
var CHECK_BATCH2 = 500;
|
|
2416
|
+
var SNIPPET_ID_RE = /\(g\._autterDebugIds=g\._autterDebugIds\|\|\{\}\)\[s\]="([0-9a-f-]{36})"/;
|
|
2417
|
+
var NOT_BUNDLES = [
|
|
2418
|
+
[/(^|\/)_(build|ssg|middleware|clientMiddleware)Manifest\.js$/, "Next.js build manifest"],
|
|
2419
|
+
// .next/server: middleware-build-manifest.js, next-font-manifest.js,
|
|
2420
|
+
// server-reference-manifest.js, page_client-reference-manifest.js, …
|
|
2421
|
+
[/(^|\/)[\w.-]*[-_]manifest\.js$/, "Next.js server manifest"],
|
|
2422
|
+
[/(^|\/)chunks\/polyfills-[\w-]+\.js$/, "Next.js polyfills, copied prebuilt without a source map"],
|
|
2423
|
+
[/(^|\/)(sw|service-worker|serviceWorker|firebase-messaging-sw|mockServiceWorker)\.js$/, "service worker (the browser SDK doesn't run there)"],
|
|
2424
|
+
[/(^|\/)workbox-[\w-]+\.js$/, "service worker runtime (the browser SDK doesn't run there)"]
|
|
2425
|
+
];
|
|
2426
|
+
function globToRegExp(glob) {
|
|
2427
|
+
let re = "";
|
|
2428
|
+
for (let i = 0; i < glob.length; i++) {
|
|
2429
|
+
const ch = glob[i];
|
|
2430
|
+
if (ch === "*" && glob[i + 1] === "*") {
|
|
2431
|
+
re += glob[i + 2] === "/" ? "(?:.*/)?" : ".*";
|
|
2432
|
+
i += glob[i + 2] === "/" ? 2 : 1;
|
|
2433
|
+
} else if (ch === "*") re += "[^/]*";
|
|
2434
|
+
else if (ch === "?") re += "[^/]";
|
|
2435
|
+
else re += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
2436
|
+
}
|
|
2437
|
+
return new RegExp(glob.includes("/") ? `^${re.replace(/^\.\//, "")}$` : `(^|/)${re}$`);
|
|
2438
|
+
}
|
|
2439
|
+
async function walk3(dir, out) {
|
|
2440
|
+
let entries;
|
|
2441
|
+
try {
|
|
2442
|
+
entries = await readdir2(dir, { withFileTypes: true });
|
|
2443
|
+
} catch {
|
|
2444
|
+
return;
|
|
2445
|
+
}
|
|
2446
|
+
for (const entry of entries) {
|
|
2447
|
+
if (entry.name === "node_modules") continue;
|
|
2448
|
+
const full = join4(dir, entry.name);
|
|
2449
|
+
if (entry.isDirectory()) await walk3(full, out);
|
|
2450
|
+
else if (entry.isFile()) out.push(full);
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
function staticRoots(dir) {
|
|
2454
|
+
for (let current = dirname3(dir); dirname3(current) !== current; current = dirname3(current)) {
|
|
2455
|
+
if (existsSync5(join4(current, "package.json"))) return ["public", "static", "src"].map((name) => join4(current, name));
|
|
2456
|
+
}
|
|
2457
|
+
return [];
|
|
2458
|
+
}
|
|
2459
|
+
async function verifyDir(dir, options = {}) {
|
|
2460
|
+
const root = resolve3(dir);
|
|
2461
|
+
const env = options.env ?? process.env;
|
|
2462
|
+
const files = [];
|
|
2463
|
+
await walk3(root, files);
|
|
2464
|
+
files.sort();
|
|
2465
|
+
const relPath = (path) => relative4(root, path).split(sep4).join("/");
|
|
2466
|
+
const ignore = (options.ignore ?? []).map(globToRegExp);
|
|
2467
|
+
const roots = staticRoots(root);
|
|
2468
|
+
const result = {
|
|
2469
|
+
dir: root,
|
|
2470
|
+
ok: true,
|
|
2471
|
+
bundles: [],
|
|
2472
|
+
maps: [],
|
|
2473
|
+
...options.server ? { server: true } : {},
|
|
2474
|
+
upload: { status: "unverified", endpoint: null, reason: null, checked: 0, missing: 0 },
|
|
2475
|
+
failures: []
|
|
2476
|
+
};
|
|
2477
|
+
const fail = (path, reason) => result.failures.push({ path, reason });
|
|
2478
|
+
for (const file of files) {
|
|
2479
|
+
const path = relPath(file);
|
|
2480
|
+
if (file.endsWith(".map")) {
|
|
2481
|
+
result.maps.push(path);
|
|
2482
|
+
if (!options.allowMaps && !options.server) {
|
|
2483
|
+
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");
|
|
2484
|
+
}
|
|
2485
|
+
continue;
|
|
2486
|
+
}
|
|
2487
|
+
if (!BUNDLE_RE.test(file)) continue;
|
|
2488
|
+
const bytes = await readFile4(file);
|
|
2489
|
+
const bundle = { path, status: "ok", debugId: null, reasons: [], uploaded: null };
|
|
2490
|
+
result.bundles.push(bundle);
|
|
2491
|
+
const skip = (reason) => {
|
|
2492
|
+
bundle.status = "skipped";
|
|
2493
|
+
bundle.reasons.push(reason);
|
|
2494
|
+
};
|
|
2495
|
+
if (ignore.some((re) => re.test(path))) {
|
|
2496
|
+
skip("matches --ignore");
|
|
2497
|
+
continue;
|
|
2498
|
+
}
|
|
2499
|
+
const known = NOT_BUNDLES.find(([re]) => re.test(path));
|
|
2500
|
+
if (known) {
|
|
2501
|
+
skip(known[1]);
|
|
2502
|
+
continue;
|
|
2503
|
+
}
|
|
2504
|
+
let copied = null;
|
|
2505
|
+
for (const staticRoot of roots) {
|
|
2506
|
+
const candidate = join4(staticRoot, path);
|
|
2507
|
+
if (existsSync5(candidate) && (await readFile4(candidate)).equals(bytes)) copied = relative4(dirname3(staticRoot), candidate).split(sep4).join("/");
|
|
2508
|
+
}
|
|
2509
|
+
if (copied) {
|
|
2510
|
+
skip(`copied unchanged from ${copied}, not built`);
|
|
2511
|
+
continue;
|
|
2512
|
+
}
|
|
2513
|
+
const code = bytes.toString("utf8");
|
|
2514
|
+
const commentId = readDebugIdComment(code);
|
|
2515
|
+
const snippetId = SNIPPET_ID_RE.exec(code)?.[1] ?? null;
|
|
2516
|
+
bundle.debugId = commentId;
|
|
2517
|
+
if (!commentId && !hasDebugIdSnippet(code)) {
|
|
2518
|
+
bundle.reasons.push(
|
|
2519
|
+
"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"
|
|
2520
|
+
);
|
|
2521
|
+
} else if (!commentId) {
|
|
2522
|
+
bundle.reasons.push("has the debug id snippet but no //# debugId= comment: run `autter-runtime sourcemaps inject` on it again");
|
|
2523
|
+
} else if (!hasDebugIdSnippet(code)) {
|
|
2524
|
+
bundle.reasons.push(
|
|
2525
|
+
"has a //# debugId= comment but no registration snippet, so the SDK can't report its id: run `autter-runtime sourcemaps inject` on it"
|
|
2526
|
+
);
|
|
2527
|
+
} else if (snippetId && snippetId !== commentId) {
|
|
2528
|
+
bundle.reasons.push(`its snippet registers debug id ${snippetId} but its comment says ${commentId}: run inject on the original build output`);
|
|
2529
|
+
}
|
|
2530
|
+
const url = readSourceMappingUrl(code);
|
|
2531
|
+
if (url?.startsWith("data:")) {
|
|
2532
|
+
if (!options.server) {
|
|
2533
|
+
bundle.reasons.push("carries an inline source map (data: URL): your original source ships with the bundle. Use hidden or external maps");
|
|
2534
|
+
}
|
|
2535
|
+
} else if (url && !/^[a-z][\w+.-]*:/i.test(url) && !url.startsWith("/")) {
|
|
2536
|
+
let target = url.split(/[?#]/)[0];
|
|
2537
|
+
try {
|
|
2538
|
+
target = decodeURIComponent(target);
|
|
2539
|
+
} catch {
|
|
2540
|
+
}
|
|
2541
|
+
if (!existsSync5(resolve3(dirname3(file), target))) {
|
|
2542
|
+
bundle.reasons.push(
|
|
2543
|
+
`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`
|
|
2544
|
+
);
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
if (bundle.reasons.length) {
|
|
2548
|
+
bundle.status = "failed";
|
|
2549
|
+
for (const reason of bundle.reasons) fail(path, reason);
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
const counted = result.bundles.filter((b) => b.status !== "skipped");
|
|
2553
|
+
if (!counted.length) fail(".", `no .js/.mjs/.cjs bundles under ${dir}: is this the build output?`);
|
|
2554
|
+
await checkUploads(result, counted, options, env);
|
|
2555
|
+
result.ok = result.failures.length === 0;
|
|
2556
|
+
return result;
|
|
2557
|
+
}
|
|
2558
|
+
async function checkUploads(result, bundles, options, env) {
|
|
2559
|
+
const upload = result.upload;
|
|
2560
|
+
const key = (options.key ?? env.AUTTER_RUNTIME_KEY ?? "").trim();
|
|
2561
|
+
if (!key) {
|
|
2562
|
+
upload.reason = "AUTTER_RUNTIME_KEY is not set: only the local checks ran";
|
|
2563
|
+
return;
|
|
2564
|
+
}
|
|
2565
|
+
upload.endpoint = resolveEndpoint(options.endpoint, env);
|
|
2566
|
+
if (key.startsWith(CLIENT_KEY_PREFIX)) {
|
|
2567
|
+
upload.status = "error";
|
|
2568
|
+
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)";
|
|
2569
|
+
result.failures.push({ path: ".", reason: upload.reason });
|
|
2570
|
+
return;
|
|
2571
|
+
}
|
|
2572
|
+
const withIds = bundles.filter((b) => b.debugId);
|
|
2573
|
+
if (!withIds.length) {
|
|
2574
|
+
upload.reason = "no bundle has a debug id to check";
|
|
2575
|
+
return;
|
|
2576
|
+
}
|
|
2577
|
+
let release = "verify";
|
|
2578
|
+
try {
|
|
2579
|
+
release = resolveRelease(options.release, env, options.cwd).release;
|
|
2580
|
+
} catch {
|
|
2581
|
+
}
|
|
2582
|
+
const doFetch = options.fetch ?? fetch;
|
|
2583
|
+
const missing = [];
|
|
2584
|
+
for (let start = 0; start < withIds.length; start += CHECK_BATCH2) {
|
|
2585
|
+
const batch = withIds.slice(start, start + CHECK_BATCH2);
|
|
2586
|
+
let res;
|
|
2587
|
+
try {
|
|
2588
|
+
res = await doFetch(`${upload.endpoint}/v1/sourcemaps/check`, {
|
|
2589
|
+
method: "POST",
|
|
2590
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${key}` },
|
|
2591
|
+
body: JSON.stringify({ release, files: batch.map((b) => ({ filename: servedFilename(options.urlPrefix, b.path), debugId: b.debugId })) })
|
|
2592
|
+
});
|
|
2593
|
+
} catch (err) {
|
|
2594
|
+
upload.status = "error";
|
|
2595
|
+
upload.reason = `could not reach the ingester: ${err.message}`;
|
|
2596
|
+
result.failures.push({ path: ".", reason: upload.reason });
|
|
2597
|
+
return;
|
|
2598
|
+
}
|
|
2599
|
+
if (res.status === 404 || res.status === 400) {
|
|
2600
|
+
upload.status = "unverified";
|
|
2601
|
+
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";
|
|
2602
|
+
return;
|
|
2603
|
+
}
|
|
2604
|
+
if (res.status === 401 || res.status === 403) {
|
|
2605
|
+
upload.status = "error";
|
|
2606
|
+
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)" : "");
|
|
2607
|
+
result.failures.push({ path: ".", reason: upload.reason });
|
|
2608
|
+
return;
|
|
2609
|
+
}
|
|
2610
|
+
if (!res.ok) {
|
|
2611
|
+
upload.status = "error";
|
|
2612
|
+
upload.reason = `the source map check failed: HTTP ${res.status}`;
|
|
2613
|
+
result.failures.push({ path: ".", reason: upload.reason });
|
|
2614
|
+
return;
|
|
2615
|
+
}
|
|
2616
|
+
const body = await res.json();
|
|
2617
|
+
const present = new Set(Array.isArray(body.present) ? body.present : []);
|
|
2618
|
+
batch.forEach((bundle, index) => {
|
|
2619
|
+
bundle.uploaded = present.has(index);
|
|
2620
|
+
if (!bundle.uploaded) missing.push(bundle);
|
|
2621
|
+
});
|
|
2622
|
+
}
|
|
2623
|
+
upload.checked = withIds.length;
|
|
2624
|
+
upload.missing = missing.length;
|
|
2625
|
+
upload.status = missing.length ? "missing" : "verified";
|
|
2626
|
+
upload.reason = missing.length ? `${missing.length} of ${withIds.length} bundle(s) have no map on the ingester` : null;
|
|
2627
|
+
for (const bundle of missing) {
|
|
2628
|
+
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)`;
|
|
2629
|
+
bundle.status = "failed";
|
|
2630
|
+
bundle.reasons.push(reason);
|
|
2631
|
+
result.failures.push({ path: bundle.path, reason });
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
function formatVerify(result) {
|
|
2635
|
+
const lines = [];
|
|
2636
|
+
for (const bundle of result.bundles) {
|
|
2637
|
+
if (bundle.status === "ok") lines.push(`ok ${bundle.path} (debug id ${bundle.debugId}${bundle.uploaded ? ", map uploaded" : ""})`);
|
|
2638
|
+
else if (bundle.status === "skipped") lines.push(`skipped ${bundle.path}: ${bundle.reasons.join("; ")}`);
|
|
2639
|
+
else for (const reason of bundle.reasons) lines.push(`FAILED ${bundle.path}: ${reason}`);
|
|
2640
|
+
}
|
|
2641
|
+
for (const map of result.maps) {
|
|
2642
|
+
const failure = result.failures.find((f) => f.path === map);
|
|
2643
|
+
lines.push(
|
|
2644
|
+
failure ? `FAILED ${map}: ${failure.reason}` : result.server ? `note ${map}: source map in the server output (browsers never load it)` : `warning ${map}: source map in the output (allowed by --allow-maps; remove it before deploying)`
|
|
2645
|
+
);
|
|
2646
|
+
}
|
|
2647
|
+
for (const failure of result.failures.filter((f) => f.path === ".")) lines.push(`FAILED ${failure.reason}`);
|
|
2648
|
+
const u = result.upload;
|
|
2649
|
+
lines.push(
|
|
2650
|
+
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}` : ""}`
|
|
2651
|
+
);
|
|
2652
|
+
const counted = result.bundles.filter((b) => b.status !== "skipped");
|
|
2653
|
+
lines.push(
|
|
2654
|
+
result.ok ? `ok: ${counted.length} bundle(s) with debug ids, ${result.maps.length ? `${result.maps.length} source map(s) left (${result.server ? "server output" : "--allow-maps"})` : "no source maps left"}${u.status === "unverified" ? ", upload not verified" : ""}` : `${result.failures.length} problem(s) in ${result.dir}`
|
|
2655
|
+
);
|
|
2656
|
+
return lines;
|
|
2657
|
+
}
|
|
2658
|
+
|
|
867
2659
|
// src/cli.ts
|
|
868
2660
|
var USAGE = `autter-runtime \u2014 readable stack traces for Autter Runtime
|
|
869
2661
|
|
|
870
2662
|
Usage:
|
|
871
|
-
autter-runtime sourcemaps
|
|
2663
|
+
autter-runtime sourcemaps setup [dir] [--dry-run] [--install] [--json]
|
|
2664
|
+
autter-runtime sourcemaps verify <dir> [--endpoint <url>] [--url-prefix <prefix>]
|
|
2665
|
+
[--ignore <glob>]... [--allow-maps] [--server] [--json]
|
|
2666
|
+
autter-runtime sourcemaps inject <dir> [--dry-run] [--json]
|
|
872
2667
|
autter-runtime sourcemaps upload <dir> [--release <r>] [--url-prefix <prefix>]
|
|
873
|
-
[--endpoint <url>] [--concurrency <n>] [--delete-after-upload]
|
|
2668
|
+
[--endpoint <url>] [--concurrency <n>] [--delete-after-upload]
|
|
2669
|
+
[--no-sources-content] [--best-effort] [--dry-run] [--json]
|
|
874
2670
|
autter-runtime doctor ... (provided by @autter/runtime-node)
|
|
875
2671
|
|
|
2672
|
+
setup Wires source map upload into the project in [dir] (default "."):
|
|
2673
|
+
detects the build tool, package manager, CI and browser SDK, adds
|
|
2674
|
+
@autter/runtime-cli to devDependencies, adds the Vite/Rollup, webpack
|
|
2675
|
+
or Next.js plugin when the config is safe to edit (else a postbuild
|
|
2676
|
+
step), turns on hidden maps, and prints what is left to do by hand,
|
|
2677
|
+
including where to store AUTTER_RUNTIME_KEY. Never writes a key.
|
|
2678
|
+
Safe to re-run. Exit 0: all applied; 2: manual actions remain; 1: error.
|
|
2679
|
+
verify Checks a production build before deploy: every bundle has a debug
|
|
2680
|
+
id, no .map file is left, no sourceMappingURL points at a missing
|
|
2681
|
+
map, and (with AUTTER_RUNTIME_KEY) every bundle's map is on the
|
|
2682
|
+
ingester. Exit 0: ok (upload unverified without a key); 1: problems.
|
|
876
2683
|
inject Gives every .js/.mjs/.cjs bundle that has a source map a debug id
|
|
877
2684
|
derived from its bytes: a "//# debugId=" comment, a one-line snippet
|
|
878
|
-
that registers it for
|
|
879
|
-
.map.
|
|
2685
|
+
that registers it for the Autter SDKs (browser, Node and edge), and
|
|
2686
|
+
"debugId" in the .map. Works on browser and server output alike
|
|
2687
|
+
(Vite, webpack, Next.js, tsup, esbuild). Running it again changes
|
|
2688
|
+
nothing.
|
|
880
2689
|
upload Sends every map to <endpoint>/v1/sourcemaps with AUTTER_RUNTIME_KEY,
|
|
881
2690
|
in parallel, skipping maps the ingester already has. Each map carries
|
|
882
2691
|
the bundle's function scopes ("x_autter_scopes"), so stack frames are
|
|
883
|
-
named after the function they are in, not the call site
|
|
2692
|
+
named after the function they are in, not the call site, and the
|
|
2693
|
+
original source of your own files ("sourcesContent", never your
|
|
2694
|
+
dependencies'), so issues show the lines around each frame.
|
|
884
2695
|
|
|
885
2696
|
Options:
|
|
886
2697
|
--release <r> Default: $GITHUB_SHA, $VERCEL_GIT_COMMIT_SHA,
|
|
887
2698
|
$CF_PAGES_COMMIT_SHA, $GIT_SHA, then git rev-parse HEAD
|
|
888
2699
|
--url-prefix <prefix> Where <dir> is served from: "/" (default), "/static/",
|
|
889
|
-
or "https://cdn.example.com/app/"
|
|
2700
|
+
or "https://cdn.example.com/app/". For server output,
|
|
2701
|
+
any tail of the path the server runs it from
|
|
2702
|
+
("/dist/" for /app/dist/server.js) or "/"
|
|
890
2703
|
--endpoint <url> Ingester base URL. Default: $AUTTER_ENDPOINT,
|
|
891
2704
|
$AUTTER_RUNTIME_ENDPOINT, $OTEL_EXPORTER_OTLP_ENDPOINT,
|
|
892
2705
|
then https://otlp.autter.dev
|
|
893
2706
|
--concurrency <n> Parallel uploads (default 8)
|
|
894
2707
|
--delete-after-upload Delete each .map once the ingester has it
|
|
2708
|
+
--no-sources-content upload: send maps without your files' source (issues
|
|
2709
|
+
then show no lines around a frame)
|
|
2710
|
+
--best-effort For build scripts: never fail. Without a key, or when
|
|
2711
|
+
the upload fails, report it and exit 0; with
|
|
2712
|
+
--delete-after-upload, every .map under <dir> is then
|
|
2713
|
+
deleted anyway, so a build never ships maps
|
|
2714
|
+
--install setup: also run the package manager's install
|
|
2715
|
+
--ignore <glob> verify: JS files that are not bundles (repeatable)
|
|
2716
|
+
--allow-maps verify: report leftover .map files without failing
|
|
2717
|
+
--server verify: <dir> is server output (tsup, esbuild, webpack
|
|
2718
|
+
or Next server bundles): leftover maps are fine there
|
|
895
2719
|
--dry-run Show what would change or be uploaded; write and send nothing
|
|
896
2720
|
--json Print the result as JSON
|
|
897
2721
|
-h, --help Show this help
|
|
898
2722
|
-v, --version Print the version
|
|
899
2723
|
|
|
900
2724
|
Environment:
|
|
901
|
-
AUTTER_RUNTIME_KEY
|
|
902
|
-
|
|
2725
|
+
AUTTER_RUNTIME_KEY Upload-only key (autter_rtu_\u2026, recommended for CI: it
|
|
2726
|
+
can only upload source maps) or the server key
|
|
2727
|
+
(autter_rt_\u2026, also works). Required by upload; used by
|
|
2728
|
+
verify to check the ingester. Never a client key.`;
|
|
2729
|
+
function ownVersion2() {
|
|
903
2730
|
try {
|
|
904
|
-
return JSON.parse(
|
|
2731
|
+
return JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
905
2732
|
} catch {
|
|
906
2733
|
return "unknown";
|
|
907
2734
|
}
|
|
@@ -909,16 +2736,16 @@ function ownVersion() {
|
|
|
909
2736
|
function delegate(pkg, args) {
|
|
910
2737
|
let manifest;
|
|
911
2738
|
try {
|
|
912
|
-
manifest = createRequire(
|
|
2739
|
+
manifest = createRequire(join5(process.cwd(), "package.json")).resolve(`${pkg}/package.json`);
|
|
913
2740
|
} catch {
|
|
914
2741
|
return null;
|
|
915
2742
|
}
|
|
916
|
-
const bin = JSON.parse(
|
|
917
|
-
const
|
|
918
|
-
if (!
|
|
919
|
-
const script =
|
|
920
|
-
if (!
|
|
921
|
-
const result =
|
|
2743
|
+
const bin = JSON.parse(readFileSync3(manifest, "utf8")).bin;
|
|
2744
|
+
const relative6 = typeof bin === "string" ? bin : bin?.["autter-runtime"];
|
|
2745
|
+
if (!relative6) return null;
|
|
2746
|
+
const script = resolve4(dirname4(manifest), relative6);
|
|
2747
|
+
if (!existsSync6(script)) return null;
|
|
2748
|
+
const result = spawnSync2(process.execPath, [script, ...args], { stdio: "inherit" });
|
|
922
2749
|
return result.status ?? 1;
|
|
923
2750
|
}
|
|
924
2751
|
var SOURCEMAPS_OPTIONS = {
|
|
@@ -927,10 +2754,22 @@ var SOURCEMAPS_OPTIONS = {
|
|
|
927
2754
|
endpoint: { type: "string" },
|
|
928
2755
|
concurrency: { type: "string" },
|
|
929
2756
|
"delete-after-upload": { type: "boolean" },
|
|
2757
|
+
"best-effort": { type: "boolean" },
|
|
2758
|
+
install: { type: "boolean" },
|
|
2759
|
+
ignore: { type: "string", multiple: true },
|
|
2760
|
+
"allow-maps": { type: "boolean" },
|
|
2761
|
+
server: { type: "boolean" },
|
|
2762
|
+
"no-sources-content": { type: "boolean" },
|
|
930
2763
|
"dry-run": { type: "boolean" },
|
|
931
2764
|
json: { type: "boolean" },
|
|
932
2765
|
help: { type: "boolean", short: "h" }
|
|
933
2766
|
};
|
|
2767
|
+
var ACTION_FLAGS = {
|
|
2768
|
+
setup: ["dry-run", "install"],
|
|
2769
|
+
inject: ["dry-run"],
|
|
2770
|
+
upload: ["release", "url-prefix", "endpoint", "concurrency", "delete-after-upload", "no-sources-content", "best-effort", "dry-run"],
|
|
2771
|
+
verify: ["endpoint", "url-prefix", "release", "ignore", "allow-maps", "server"]
|
|
2772
|
+
};
|
|
934
2773
|
async function sourcemaps(argv) {
|
|
935
2774
|
let parsed;
|
|
936
2775
|
try {
|
|
@@ -942,22 +2781,56 @@ ${USAGE}`);
|
|
|
942
2781
|
return 64;
|
|
943
2782
|
}
|
|
944
2783
|
const { values, positionals } = parsed;
|
|
945
|
-
const [action,
|
|
2784
|
+
const [action, dirArg, ...extra] = positionals;
|
|
946
2785
|
if (values.help) {
|
|
947
2786
|
console.log(USAGE);
|
|
948
2787
|
return 0;
|
|
949
2788
|
}
|
|
950
|
-
|
|
951
|
-
|
|
2789
|
+
const known = action !== void 0 && action in ACTION_FLAGS;
|
|
2790
|
+
const dir = dirArg ?? (action === "setup" ? "." : void 0);
|
|
2791
|
+
if (!known || !dir || extra.length) {
|
|
2792
|
+
console.error(`${action && !known ? `unknown command: sourcemaps ${action}
|
|
952
2793
|
|
|
953
2794
|
` : ""}${USAGE}`);
|
|
954
2795
|
return 64;
|
|
955
2796
|
}
|
|
956
|
-
|
|
957
|
-
|
|
2797
|
+
const allowed = /* @__PURE__ */ new Set([...ACTION_FLAGS[action], "json", "help"]);
|
|
2798
|
+
const stray = Object.keys(values).filter((flag) => !allowed.has(flag));
|
|
2799
|
+
if (stray.length) {
|
|
2800
|
+
console.error(`autter-runtime: sourcemaps ${action} does not take --${stray.join(", --")}
|
|
2801
|
+
|
|
2802
|
+
${USAGE}`);
|
|
958
2803
|
return 64;
|
|
959
2804
|
}
|
|
2805
|
+
if (!existsSync6(dir)) {
|
|
2806
|
+
console.error(`autter-runtime: ${dir} does not exist`);
|
|
2807
|
+
return action === "setup" ? 1 : 64;
|
|
2808
|
+
}
|
|
960
2809
|
const dryRun = Boolean(values["dry-run"]);
|
|
2810
|
+
if (action === "setup") {
|
|
2811
|
+
try {
|
|
2812
|
+
const result = runSetup(dir, { dryRun, install: Boolean(values.install), installOutput: values.json ? "stderr" : "inherit" });
|
|
2813
|
+
if (values.json) console.log(JSON.stringify(result, null, 2));
|
|
2814
|
+
else for (const line of formatSetup(result)) console.log(line);
|
|
2815
|
+
return result.exitCode;
|
|
2816
|
+
} catch (err) {
|
|
2817
|
+
console.error(`autter-runtime: sourcemaps setup: ${err instanceof Error ? err.message : String(err)}`);
|
|
2818
|
+
return 1;
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
if (action === "verify") {
|
|
2822
|
+
const result = await verifyDir(dir, {
|
|
2823
|
+
endpoint: values.endpoint,
|
|
2824
|
+
urlPrefix: values["url-prefix"],
|
|
2825
|
+
release: values.release,
|
|
2826
|
+
ignore: values.ignore,
|
|
2827
|
+
allowMaps: Boolean(values["allow-maps"]),
|
|
2828
|
+
server: Boolean(values.server)
|
|
2829
|
+
});
|
|
2830
|
+
if (values.json) console.log(JSON.stringify(result, null, 2));
|
|
2831
|
+
else for (const line of formatVerify(result)) console.log(line);
|
|
2832
|
+
return result.ok ? 0 : 1;
|
|
2833
|
+
}
|
|
961
2834
|
if (action === "inject") {
|
|
962
2835
|
const results = await injectDir(dir, { dryRun });
|
|
963
2836
|
if (values.json) {
|
|
@@ -979,28 +2852,56 @@ ${USAGE}`);
|
|
|
979
2852
|
console.error("autter-runtime: --concurrency must be a positive integer");
|
|
980
2853
|
return 64;
|
|
981
2854
|
}
|
|
982
|
-
const
|
|
2855
|
+
const deleteAfterUpload = Boolean(values["delete-after-upload"]);
|
|
2856
|
+
const bestEffort = Boolean(values["best-effort"]);
|
|
2857
|
+
let summary = null;
|
|
2858
|
+
let error = null;
|
|
2859
|
+
const run = () => uploadDir(dir, {
|
|
983
2860
|
release: values.release,
|
|
984
2861
|
urlPrefix: values["url-prefix"],
|
|
985
2862
|
endpoint: values.endpoint,
|
|
986
2863
|
concurrency,
|
|
987
2864
|
dryRun,
|
|
988
|
-
deleteAfterUpload
|
|
2865
|
+
deleteAfterUpload,
|
|
2866
|
+
...values["no-sources-content"] ? { sourcesContent: false } : {},
|
|
989
2867
|
log: (line) => console.error(line)
|
|
990
2868
|
});
|
|
991
|
-
if (
|
|
2869
|
+
if (!bestEffort) summary = await run();
|
|
2870
|
+
else if (!dryRun && !(process.env.AUTTER_RUNTIME_KEY ?? "").trim()) error = `AUTTER_RUNTIME_KEY is not set, so nothing was uploaded (${KEY_ADVICE})`;
|
|
992
2871
|
else {
|
|
993
|
-
|
|
994
|
-
|
|
2872
|
+
try {
|
|
2873
|
+
summary = await run();
|
|
2874
|
+
} catch (err) {
|
|
2875
|
+
error = err instanceof Error ? err.message : String(err);
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
let removedMaps = [];
|
|
2879
|
+
if (bestEffort && deleteAfterUpload && !dryRun) {
|
|
2880
|
+
const owners = new Map((await findBundles(dir)).map((bundle) => [bundle.map, bundle.js]));
|
|
2881
|
+
removedMaps = (await removeSourceMaps(await findMapFiles(dir), owners)).map((map) => relative5(resolve4(dir), map).split(sep5).join("/"));
|
|
2882
|
+
}
|
|
2883
|
+
if (values.json) {
|
|
2884
|
+
console.log(JSON.stringify({ ...summary ?? { uploaded: [], skipped: [], failed: [] }, ...bestEffort ? { error, removedMaps } : {} }, null, 2));
|
|
2885
|
+
} else {
|
|
2886
|
+
if (summary) {
|
|
2887
|
+
if (!summary.uploaded.length && !summary.skipped.length && !summary.failed.length) {
|
|
2888
|
+
console.log(`no .js/.mjs/.cjs bundles with source maps under ${dir}`);
|
|
2889
|
+
}
|
|
2890
|
+
for (const line of formatUploadSummary(summary)) console.log(line);
|
|
2891
|
+
}
|
|
2892
|
+
if (error) console.error(`autter-runtime: ${error}`);
|
|
2893
|
+
if (removedMaps.length) {
|
|
2894
|
+
const why = error ? "the upload did not run" : summary?.failed.length ? `${summary.failed.length} could not be uploaded` : "they were uploaded";
|
|
2895
|
+
console.log(`removed ${removedMaps.length} source map(s) under ${dir}, so they are not deployed: ${why}`);
|
|
995
2896
|
}
|
|
996
|
-
|
|
2897
|
+
if (bestEffort && (error || summary?.failed.length)) console.error("autter-runtime: --best-effort: the build continues");
|
|
997
2898
|
}
|
|
998
|
-
return summary
|
|
2899
|
+
return bestEffort ? 0 : summary?.failed.length ? 1 : 0;
|
|
999
2900
|
}
|
|
1000
2901
|
async function main(argv) {
|
|
1001
2902
|
const [command, ...rest] = argv;
|
|
1002
2903
|
if (command === "-v" || command === "--version") {
|
|
1003
|
-
console.log(
|
|
2904
|
+
console.log(ownVersion2());
|
|
1004
2905
|
return 0;
|
|
1005
2906
|
}
|
|
1006
2907
|
if (command === void 0 || command === "-h" || command === "--help" || command === "help") {
|