@reddoorla/maintenance 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/bin.js +228 -109
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.js +116 -73
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.js +176 -81
- package/dist/index.js.map +1 -1
- package/dist/util/pkg.d.ts +6 -2
- package/dist/util/pkg.js +3 -1
- package/dist/util/pkg.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
// src/audits/util/spawn.ts
|
|
2
2
|
import { spawn } from "child_process";
|
|
3
3
|
var defaultSpawn = (cmd, args, opts = {}) => new Promise((resolve, reject) => {
|
|
4
|
+
const streaming = opts.streaming === true;
|
|
4
5
|
const child = spawn(cmd, [...args], {
|
|
5
6
|
cwd: opts.cwd,
|
|
6
7
|
env: opts.env ?? process.env,
|
|
7
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
8
|
+
stdio: streaming ? ["ignore", "inherit", "inherit"] : ["ignore", "pipe", "pipe"]
|
|
8
9
|
});
|
|
9
10
|
let stdout = "";
|
|
10
11
|
let stderr = "";
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
if (!streaming) {
|
|
13
|
+
child.stdout?.on("data", (chunk) => stdout += String(chunk));
|
|
14
|
+
child.stderr?.on("data", (chunk) => stderr += String(chunk));
|
|
15
|
+
}
|
|
13
16
|
const timer = opts.timeoutMs ? setTimeout(() => {
|
|
14
17
|
child.kill("SIGTERM");
|
|
15
18
|
reject(new Error(`spawn timeout after ${opts.timeoutMs}ms: ${cmd}`));
|
|
@@ -137,7 +140,7 @@ async function depsAudit(ctx) {
|
|
|
137
140
|
// src/audits/lint.ts
|
|
138
141
|
import { existsSync } from "fs";
|
|
139
142
|
import { readFile as readFile2 } from "fs/promises";
|
|
140
|
-
import { join as join2
|
|
143
|
+
import { join as join2 } from "path";
|
|
141
144
|
import { ESLint } from "eslint";
|
|
142
145
|
import { check as prettierCheck, resolveConfig as prettierResolveConfig } from "prettier";
|
|
143
146
|
import { glob } from "tinyglobby";
|
|
@@ -166,19 +169,19 @@ async function lintAudit(ctx) {
|
|
|
166
169
|
errorOnUnmatchedPattern: false
|
|
167
170
|
});
|
|
168
171
|
const relFiles = await listFiles(site.path);
|
|
169
|
-
const
|
|
170
|
-
const eslintResults = await eslint2.lintFiles(filesToLint);
|
|
172
|
+
const eslintResults = await eslint2.lintFiles(relFiles);
|
|
171
173
|
const eslintErrors = eslintResults.reduce((n, r) => n + r.errorCount, 0);
|
|
172
174
|
const eslintWarnings = eslintResults.reduce((n, r) => n + r.warningCount, 0);
|
|
173
175
|
const prettierUnformatted = [];
|
|
174
|
-
for (const
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
const
|
|
178
|
-
|
|
176
|
+
for (const rel of relFiles) {
|
|
177
|
+
const absForResolve = join2(site.path, rel);
|
|
178
|
+
const source = await readFile2(absForResolve, "utf-8");
|
|
179
|
+
const options = await prettierResolveConfig(absForResolve) ?? {};
|
|
180
|
+
const ok = await prettierCheck(source, { ...options, filepath: absForResolve });
|
|
181
|
+
if (!ok) prettierUnformatted.push(rel);
|
|
179
182
|
}
|
|
180
183
|
const status = eslintErrors > 0 || prettierUnformatted.length > 0 ? "fail" : eslintWarnings > 0 ? "warn" : "pass";
|
|
181
|
-
const summary = status === "pass" ? `lint clean across ${
|
|
184
|
+
const summary = status === "pass" ? `lint clean across ${relFiles.length} files` : `${eslintErrors} eslint errors, ${eslintWarnings} warnings, ${prettierUnformatted.length} unformatted`;
|
|
182
185
|
return {
|
|
183
186
|
audit: "lint",
|
|
184
187
|
site: siteLabel2(site),
|
|
@@ -188,7 +191,7 @@ async function lintAudit(ctx) {
|
|
|
188
191
|
eslintErrors,
|
|
189
192
|
eslintWarnings,
|
|
190
193
|
prettierUnformatted,
|
|
191
|
-
files:
|
|
194
|
+
files: relFiles.length
|
|
192
195
|
}
|
|
193
196
|
};
|
|
194
197
|
}
|
|
@@ -204,7 +207,7 @@ function classify(v) {
|
|
|
204
207
|
}
|
|
205
208
|
function normalizeSeverity(s) {
|
|
206
209
|
if (s === "low" || s === "moderate" || s === "high" || s === "critical") return s;
|
|
207
|
-
return "
|
|
210
|
+
return "low";
|
|
208
211
|
}
|
|
209
212
|
function extractAdvisoriesFromPnpm(parsed) {
|
|
210
213
|
const out = [];
|
|
@@ -220,83 +223,101 @@ function extractAdvisoriesFromPnpm(parsed) {
|
|
|
220
223
|
}
|
|
221
224
|
return out;
|
|
222
225
|
}
|
|
226
|
+
function resolveNpmAdvisoryRoot(startName, vulnerabilities) {
|
|
227
|
+
const seen = /* @__PURE__ */ new Set();
|
|
228
|
+
let current = startName;
|
|
229
|
+
while (!seen.has(current)) {
|
|
230
|
+
seen.add(current);
|
|
231
|
+
const entry = vulnerabilities[current];
|
|
232
|
+
if (!entry || !Array.isArray(entry.via)) return { rootName: current };
|
|
233
|
+
const detailed = entry.via.find(
|
|
234
|
+
(e) => typeof e === "object" && e !== null
|
|
235
|
+
);
|
|
236
|
+
if (detailed) return { rootName: current, detail: detailed };
|
|
237
|
+
const next = entry.via.find((e) => typeof e === "string");
|
|
238
|
+
if (!next || next === current) return { rootName: current };
|
|
239
|
+
current = next;
|
|
240
|
+
}
|
|
241
|
+
return { rootName: current };
|
|
242
|
+
}
|
|
223
243
|
function extractAdvisoriesFromNpm(parsed) {
|
|
224
|
-
const
|
|
225
|
-
|
|
244
|
+
const vulnerabilities = parsed.vulnerabilities ?? {};
|
|
245
|
+
const roots = /* @__PURE__ */ new Map();
|
|
246
|
+
for (const [name, v] of Object.entries(vulnerabilities)) {
|
|
226
247
|
if (!v) continue;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
out.push({
|
|
239
|
-
module: v.name ?? name,
|
|
240
|
-
severity: normalizeSeverity(v.severity),
|
|
248
|
+
const { rootName, detail } = resolveNpmAdvisoryRoot(name, vulnerabilities);
|
|
249
|
+
if (roots.has(rootName)) continue;
|
|
250
|
+
const rootEntry = vulnerabilities[rootName];
|
|
251
|
+
const severity = normalizeSeverity(rootEntry?.severity ?? v.severity);
|
|
252
|
+
const title = detail?.title ?? rootName;
|
|
253
|
+
const url = detail?.url;
|
|
254
|
+
roots.set(rootName, {
|
|
255
|
+
module: rootEntry?.name ?? rootName,
|
|
256
|
+
severity,
|
|
241
257
|
title,
|
|
242
258
|
...url ? { url } : {}
|
|
243
259
|
});
|
|
244
260
|
}
|
|
245
|
-
return
|
|
261
|
+
return [...roots.values()];
|
|
246
262
|
}
|
|
247
|
-
async function
|
|
263
|
+
async function runAuditTool(spawn2, cmd, args, cwd) {
|
|
264
|
+
let raw;
|
|
248
265
|
try {
|
|
249
|
-
|
|
266
|
+
raw = await spawn2(cmd, args, { cwd });
|
|
250
267
|
} catch (err) {
|
|
251
268
|
const e = err;
|
|
252
|
-
if (e.code === "ENOENT" || /ENOENT/.test(String(err))) return {
|
|
253
|
-
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
async function securityAudit(ctx) {
|
|
257
|
-
const spawn2 = ctx.spawn ?? defaultSpawn;
|
|
258
|
-
const site = ctx.site;
|
|
259
|
-
const label = siteLabel3(site);
|
|
260
|
-
let used = "pnpm audit";
|
|
261
|
-
let raw = await tryRun(
|
|
262
|
-
spawn2,
|
|
263
|
-
"pnpm",
|
|
264
|
-
["audit", "--json", "--prod"],
|
|
265
|
-
site.path
|
|
266
|
-
);
|
|
267
|
-
if ("missing" in raw) {
|
|
268
|
-
used = "npm audit";
|
|
269
|
-
raw = await tryRun(spawn2, "npm", ["audit", "--json", "--omit=dev"], site.path);
|
|
270
|
-
}
|
|
271
|
-
if ("missing" in raw) {
|
|
272
|
-
return {
|
|
273
|
-
audit: "security",
|
|
274
|
-
site: label,
|
|
275
|
-
status: "skip",
|
|
276
|
-
summary: "neither pnpm nor npm is available on PATH"
|
|
277
|
-
};
|
|
269
|
+
if (e.code === "ENOENT" || /ENOENT/.test(String(err))) return { kind: "missing" };
|
|
270
|
+
return { kind: "error", reason: `spawn failed: ${String(err).slice(0, 200)}` };
|
|
278
271
|
}
|
|
279
272
|
if (raw.code !== 0 && raw.code !== 1) {
|
|
280
273
|
return {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
status: "skip",
|
|
284
|
-
summary: `${used} exited with code ${raw.code}`,
|
|
285
|
-
details: { stderr: raw.stderr }
|
|
274
|
+
kind: "error",
|
|
275
|
+
reason: `exit ${raw.code}${raw.stderr ? `: ${raw.stderr.slice(0, 150)}` : ""}`
|
|
286
276
|
};
|
|
287
277
|
}
|
|
288
278
|
let parsed;
|
|
289
279
|
try {
|
|
290
|
-
parsed = JSON.parse(raw.stdout);
|
|
280
|
+
parsed = JSON.parse(raw.stdout || "{}");
|
|
291
281
|
} catch (err) {
|
|
292
|
-
return {
|
|
293
|
-
audit: "security",
|
|
294
|
-
site: label,
|
|
295
|
-
status: "skip",
|
|
296
|
-
summary: `${used} produced unparseable JSON`,
|
|
297
|
-
details: { error: String(err), stdout: raw.stdout.slice(0, 500) }
|
|
298
|
-
};
|
|
282
|
+
return { kind: "error", reason: `unparseable JSON: ${String(err).slice(0, 100)}` };
|
|
299
283
|
}
|
|
284
|
+
const errEnvelope = parsed.error;
|
|
285
|
+
if (errEnvelope && typeof errEnvelope === "object") {
|
|
286
|
+
return { kind: "error", reason: errEnvelope.code ?? "error envelope returned" };
|
|
287
|
+
}
|
|
288
|
+
if (!parsed.metadata?.vulnerabilities) {
|
|
289
|
+
return { kind: "error", reason: "no metadata.vulnerabilities in output" };
|
|
290
|
+
}
|
|
291
|
+
return { kind: "ok", parsed };
|
|
292
|
+
}
|
|
293
|
+
async function securityAudit(ctx) {
|
|
294
|
+
const spawn2 = ctx.spawn ?? defaultSpawn;
|
|
295
|
+
const site = ctx.site;
|
|
296
|
+
const label = siteLabel3(site);
|
|
297
|
+
let used = "pnpm audit";
|
|
298
|
+
let result = await runAuditTool(spawn2, "pnpm", ["audit", "--json", "--prod"], site.path);
|
|
299
|
+
if (result.kind !== "ok") {
|
|
300
|
+
const pnpmReason = result.kind === "missing" ? "not installed" : result.reason;
|
|
301
|
+
const npmResult = await runAuditTool(
|
|
302
|
+
spawn2,
|
|
303
|
+
"npm",
|
|
304
|
+
["audit", "--json", "--omit=dev"],
|
|
305
|
+
site.path
|
|
306
|
+
);
|
|
307
|
+
if (npmResult.kind === "ok") {
|
|
308
|
+
result = npmResult;
|
|
309
|
+
used = "npm audit";
|
|
310
|
+
} else {
|
|
311
|
+
const npmReason = npmResult.kind === "missing" ? "not installed" : npmResult.reason;
|
|
312
|
+
return {
|
|
313
|
+
audit: "security",
|
|
314
|
+
site: label,
|
|
315
|
+
status: "skip",
|
|
316
|
+
summary: `cannot run audit \u2014 pnpm: ${pnpmReason}; npm: ${npmReason}`
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const parsed = result.parsed;
|
|
300
321
|
const counts = {
|
|
301
322
|
low: parsed.metadata?.vulnerabilities?.low ?? 0,
|
|
302
323
|
moderate: parsed.metadata?.vulnerabilities?.moderate ?? 0,
|
|
@@ -507,6 +528,10 @@ import { dirname } from "node:path";
|
|
|
507
528
|
const pages = ${JSON.stringify(a11yRoutes)};
|
|
508
529
|
const OUTPUT = process.env.REDDOOR_A11Y_OUTPUT;
|
|
509
530
|
|
|
531
|
+
// Playwright's default per-test timeout is 30s. We loop through every
|
|
532
|
+
// configured route in a single test, so the budget needs to scale.
|
|
533
|
+
test.setTimeout(5 * 60_000);
|
|
534
|
+
|
|
510
535
|
test("a11y across configured routes", async ({ page }) => {
|
|
511
536
|
const violations = [];
|
|
512
537
|
for (const { path, name } of pages) {
|
|
@@ -781,6 +806,7 @@ async function bumpDeps(site, opts = {}) {
|
|
|
781
806
|
const label = siteLabel7(site);
|
|
782
807
|
const group = opts.group ?? "minor";
|
|
783
808
|
const spawn2 = opts.spawn ?? defaultSpawn;
|
|
809
|
+
await spawn2("pnpm", ["install"], { cwd: site.path, streaming: true });
|
|
784
810
|
const outdated = await spawn2("pnpm", ["outdated", "--json", ...outdatedFlagsForGroup(group)], {
|
|
785
811
|
cwd: site.path
|
|
786
812
|
});
|
|
@@ -805,7 +831,10 @@ async function bumpDeps(site, opts = {}) {
|
|
|
805
831
|
}
|
|
806
832
|
const branch = branchName("bump-deps");
|
|
807
833
|
await createBranch(site.path, branch);
|
|
808
|
-
await spawn2("pnpm", ["up", ...upFlagsForGroup(group)], {
|
|
834
|
+
await spawn2("pnpm", ["up", ...upFlagsForGroup(group)], {
|
|
835
|
+
cwd: site.path,
|
|
836
|
+
streaming: true
|
|
837
|
+
});
|
|
809
838
|
const sha = await commit(site.path, `chore(deps): bump dependencies (${group})`);
|
|
810
839
|
const shas = sha ? [sha] : [];
|
|
811
840
|
return {
|
|
@@ -830,7 +859,8 @@ async function writePackageJson(path, pkg) {
|
|
|
830
859
|
const content = JSON.stringify(pkg, null, 2) + "\n";
|
|
831
860
|
await writeFile4(path, content, "utf-8");
|
|
832
861
|
}
|
|
833
|
-
function bumpDep(pkg, name, version) {
|
|
862
|
+
function bumpDep(pkg, name, version, opts = {}) {
|
|
863
|
+
const mode = opts.mode ?? "ensure";
|
|
834
864
|
const next = {
|
|
835
865
|
...pkg
|
|
836
866
|
};
|
|
@@ -850,6 +880,7 @@ function bumpDep(pkg, name, version) {
|
|
|
850
880
|
next.devDependencies[name] = version;
|
|
851
881
|
return next;
|
|
852
882
|
}
|
|
883
|
+
if (mode === "bump-only") return pkg;
|
|
853
884
|
next.devDependencies = { ...next.devDependencies ?? {}, [name]: version };
|
|
854
885
|
return next;
|
|
855
886
|
}
|
|
@@ -872,7 +903,7 @@ async function bumpToSvelte5Versions(cwd) {
|
|
|
872
903
|
const pkg = await readPackageJson(pkgPath);
|
|
873
904
|
let next = pkg;
|
|
874
905
|
for (const [name, version] of Object.entries(SVELTE_5_VERSIONS)) {
|
|
875
|
-
next = bumpDep(next, name, version);
|
|
906
|
+
next = bumpDep(next, name, version, { mode: "bump-only" });
|
|
876
907
|
}
|
|
877
908
|
if (next === pkg) return false;
|
|
878
909
|
await writePackageJson(pkgPath, next);
|
|
@@ -882,6 +913,45 @@ async function bumpToSvelte5Versions(cwd) {
|
|
|
882
913
|
// src/recipes/svelte-5/step-svelte-config.ts
|
|
883
914
|
import { readFile as readFile7, writeFile as writeFile5 } from "fs/promises";
|
|
884
915
|
import { join as join7 } from "path";
|
|
916
|
+
var VITE_PLUGIN_PKG = "@sveltejs/vite-plugin-svelte";
|
|
917
|
+
var IMPORT_FROM_VITE_PLUGIN = new RegExp(
|
|
918
|
+
String.raw`^import\s+\{\s*([^}]+?)\s*\}\s+from\s+["']` + VITE_PLUGIN_PKG.replace(/[/]/g, "\\/") + String.raw`["'];?[ \t]*\n`,
|
|
919
|
+
"m"
|
|
920
|
+
);
|
|
921
|
+
function dropVitePreprocessImport(source) {
|
|
922
|
+
return source.replace(IMPORT_FROM_VITE_PLUGIN, (full, names) => {
|
|
923
|
+
const remaining = names.split(",").map((n) => n.trim()).filter((n) => n.length > 0 && n !== "vitePreprocess");
|
|
924
|
+
if (remaining.length === 0) return "";
|
|
925
|
+
return `import { ${remaining.join(", ")} } from "${VITE_PLUGIN_PKG}";
|
|
926
|
+
`;
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
function findMatchingParen(source, openIdx) {
|
|
930
|
+
if (source[openIdx] !== "(") return -1;
|
|
931
|
+
let depth = 0;
|
|
932
|
+
for (let i = openIdx; i < source.length; i++) {
|
|
933
|
+
const ch = source[i];
|
|
934
|
+
if (ch === "(") depth++;
|
|
935
|
+
else if (ch === ")") {
|
|
936
|
+
depth--;
|
|
937
|
+
if (depth === 0) return i;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
return -1;
|
|
941
|
+
}
|
|
942
|
+
function dropPreprocessKey(source) {
|
|
943
|
+
const startRe = /^(\s*)preprocess:\s*vitePreprocess\(/m;
|
|
944
|
+
const m = startRe.exec(source);
|
|
945
|
+
if (!m) return source;
|
|
946
|
+
const indent = m[1] ?? "";
|
|
947
|
+
const parenOpenAbs = m.index + m[0].length - 1;
|
|
948
|
+
const parenCloseAbs = findMatchingParen(source, parenOpenAbs);
|
|
949
|
+
if (parenCloseAbs < 0) return source;
|
|
950
|
+
let tailIdx = parenCloseAbs + 1;
|
|
951
|
+
while (tailIdx < source.length && /[ \t,]/.test(source[tailIdx] ?? "")) tailIdx++;
|
|
952
|
+
if (source[tailIdx] === "\n") tailIdx++;
|
|
953
|
+
return source.slice(0, m.index) + source.slice(tailIdx).replace(new RegExp(`^${indent}\\n`), "");
|
|
954
|
+
}
|
|
885
955
|
async function migrateSvelteConfig(cwd) {
|
|
886
956
|
const path = join7(cwd, "svelte.config.js");
|
|
887
957
|
let src;
|
|
@@ -891,11 +961,8 @@ async function migrateSvelteConfig(cwd) {
|
|
|
891
961
|
return false;
|
|
892
962
|
}
|
|
893
963
|
let next = src;
|
|
894
|
-
next = next
|
|
895
|
-
|
|
896
|
-
""
|
|
897
|
-
);
|
|
898
|
-
next = next.replace(/^\s*preprocess:\s*vitePreprocess\(\)\s*,?\s*\n/m, "");
|
|
964
|
+
next = dropPreprocessKey(next);
|
|
965
|
+
next = dropVitePreprocessImport(next);
|
|
899
966
|
if (next === src) return false;
|
|
900
967
|
await writeFile5(path, next, "utf-8");
|
|
901
968
|
return true;
|
|
@@ -1005,10 +1072,32 @@ function exportLetToProps(source) {
|
|
|
1005
1072
|
}
|
|
1006
1073
|
|
|
1007
1074
|
// src/recipes/svelte-5/codemods/dollar-restprops.ts
|
|
1075
|
+
function removeInterfaceBlock(source) {
|
|
1076
|
+
const re = /^\s*interface\s+\$\$Props\s*\{/m;
|
|
1077
|
+
let out = source;
|
|
1078
|
+
while (true) {
|
|
1079
|
+
const match = re.exec(out);
|
|
1080
|
+
if (!match) return out;
|
|
1081
|
+
const openBraceIdx = match.index + match[0].length - 1;
|
|
1082
|
+
let depth = 1;
|
|
1083
|
+
let i = openBraceIdx + 1;
|
|
1084
|
+
while (i < out.length && depth > 0) {
|
|
1085
|
+
const ch = out[i];
|
|
1086
|
+
if (ch === "{") depth++;
|
|
1087
|
+
else if (ch === "}") depth--;
|
|
1088
|
+
i++;
|
|
1089
|
+
}
|
|
1090
|
+
if (depth !== 0) return out;
|
|
1091
|
+
let endIdx = i;
|
|
1092
|
+
while (endIdx < out.length && /[ \t]/.test(out[endIdx] ?? "")) endIdx++;
|
|
1093
|
+
if (out[endIdx] === "\n") endIdx++;
|
|
1094
|
+
out = out.slice(0, match.index) + out.slice(endIdx);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1008
1097
|
function removeDollarRestProps(source) {
|
|
1009
1098
|
let next = source;
|
|
1010
1099
|
next = next.replace(/\$\$restProps/g, "rest");
|
|
1011
|
-
next = next
|
|
1100
|
+
next = removeInterfaceBlock(next);
|
|
1012
1101
|
return next;
|
|
1013
1102
|
}
|
|
1014
1103
|
|
|
@@ -1169,6 +1258,7 @@ function localPath(path, opts = {}) {
|
|
|
1169
1258
|
|
|
1170
1259
|
// src/inventory/json.ts
|
|
1171
1260
|
import { readFile as readFile9 } from "fs/promises";
|
|
1261
|
+
import { isAbsolute } from "path";
|
|
1172
1262
|
function validate(raw) {
|
|
1173
1263
|
if (!Array.isArray(raw)) {
|
|
1174
1264
|
throw new Error("inventory JSON must be an array of sites");
|
|
@@ -1181,6 +1271,11 @@ function validate(raw) {
|
|
|
1181
1271
|
if (typeof e.path !== "string" || e.path.length === 0) {
|
|
1182
1272
|
throw new Error(`inventory entry ${i} is missing required field: path`);
|
|
1183
1273
|
}
|
|
1274
|
+
if (!isAbsolute(e.path)) {
|
|
1275
|
+
throw new Error(
|
|
1276
|
+
`inventory entry ${i}: path must be absolute (got "${e.path}"). Relative paths are rejected so cwd at invocation can't change which site is targeted.`
|
|
1277
|
+
);
|
|
1278
|
+
}
|
|
1184
1279
|
const site = { path: e.path };
|
|
1185
1280
|
if (typeof e.name === "string") site.name = e.name;
|
|
1186
1281
|
if (typeof e.repoUrl === "string") site.repoUrl = e.repoUrl;
|