revyse 0.2.0 → 0.2.1
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/agent.js +1 -1
- package/dist/{chunk-FYWZYXR4.js → chunk-FVZWKW32.js} +417 -184
- package/dist/headless.js +429 -196
- package/dist/index.js +1 -1
- package/dist/overlay.js +26 -26
- package/package.json +3 -3
package/dist/headless.js
CHANGED
|
@@ -3116,9 +3116,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3116
3116
|
* @param {string} [path]
|
|
3117
3117
|
* @return {(string|null|Command)}
|
|
3118
3118
|
*/
|
|
3119
|
-
executableDir(
|
|
3120
|
-
if (
|
|
3121
|
-
this._executableDir =
|
|
3119
|
+
executableDir(path31) {
|
|
3120
|
+
if (path31 === void 0) return this._executableDir;
|
|
3121
|
+
this._executableDir = path31;
|
|
3122
3122
|
return this;
|
|
3123
3123
|
}
|
|
3124
3124
|
/**
|
|
@@ -3375,7 +3375,7 @@ var program = new Command();
|
|
|
3375
3375
|
|
|
3376
3376
|
// src/commands/change.ts
|
|
3377
3377
|
import { readFileSync as readFileSync13 } from "fs";
|
|
3378
|
-
import
|
|
3378
|
+
import path30 from "path";
|
|
3379
3379
|
|
|
3380
3380
|
// ../agent-core/src/path-policy.ts
|
|
3381
3381
|
import { realpathSync } from "fs";
|
|
@@ -4292,10 +4292,10 @@ function mergeDefs(...defs) {
|
|
|
4292
4292
|
function cloneDef(schema) {
|
|
4293
4293
|
return mergeDefs(schema._zod.def);
|
|
4294
4294
|
}
|
|
4295
|
-
function getElementAtPath(obj,
|
|
4296
|
-
if (!
|
|
4295
|
+
function getElementAtPath(obj, path31) {
|
|
4296
|
+
if (!path31)
|
|
4297
4297
|
return obj;
|
|
4298
|
-
return
|
|
4298
|
+
return path31.reduce((acc, key) => acc?.[key], obj);
|
|
4299
4299
|
}
|
|
4300
4300
|
function promiseAllObject(promisesObj) {
|
|
4301
4301
|
const keys = Object.keys(promisesObj);
|
|
@@ -4635,11 +4635,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
4635
4635
|
}
|
|
4636
4636
|
return false;
|
|
4637
4637
|
}
|
|
4638
|
-
function prefixIssues(
|
|
4638
|
+
function prefixIssues(path31, issues) {
|
|
4639
4639
|
return issues.map((iss) => {
|
|
4640
4640
|
var _a3;
|
|
4641
4641
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
4642
|
-
iss.path.unshift(
|
|
4642
|
+
iss.path.unshift(path31);
|
|
4643
4643
|
return iss;
|
|
4644
4644
|
});
|
|
4645
4645
|
}
|
|
@@ -5101,16 +5101,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
|
|
|
5101
5101
|
}
|
|
5102
5102
|
function formatError(error62, mapper = (issue2) => issue2.message) {
|
|
5103
5103
|
const fieldErrors = { _errors: [] };
|
|
5104
|
-
const processError = (error63,
|
|
5104
|
+
const processError = (error63, path31 = []) => {
|
|
5105
5105
|
for (const issue2 of error63.issues) {
|
|
5106
5106
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
5107
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
5107
|
+
issue2.errors.map((issues) => processError({ issues }, [...path31, ...issue2.path]));
|
|
5108
5108
|
} else if (issue2.code === "invalid_key") {
|
|
5109
|
-
processError({ issues: issue2.issues }, [...
|
|
5109
|
+
processError({ issues: issue2.issues }, [...path31, ...issue2.path]);
|
|
5110
5110
|
} else if (issue2.code === "invalid_element") {
|
|
5111
|
-
processError({ issues: issue2.issues }, [...
|
|
5111
|
+
processError({ issues: issue2.issues }, [...path31, ...issue2.path]);
|
|
5112
5112
|
} else {
|
|
5113
|
-
const fullpath = [...
|
|
5113
|
+
const fullpath = [...path31, ...issue2.path];
|
|
5114
5114
|
if (fullpath.length === 0) {
|
|
5115
5115
|
fieldErrors._errors.push(mapper(issue2));
|
|
5116
5116
|
} else {
|
|
@@ -5149,17 +5149,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
|
|
|
5149
5149
|
}
|
|
5150
5150
|
function treeifyError(error62, mapper = (issue2) => issue2.message) {
|
|
5151
5151
|
const result = { errors: [] };
|
|
5152
|
-
const processError = (error63,
|
|
5152
|
+
const processError = (error63, path31 = []) => {
|
|
5153
5153
|
var _a3;
|
|
5154
5154
|
for (const issue2 of error63.issues) {
|
|
5155
5155
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
5156
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
5156
|
+
issue2.errors.map((issues) => processError({ issues }, [...path31, ...issue2.path]));
|
|
5157
5157
|
} else if (issue2.code === "invalid_key") {
|
|
5158
|
-
processError({ issues: issue2.issues }, [...
|
|
5158
|
+
processError({ issues: issue2.issues }, [...path31, ...issue2.path]);
|
|
5159
5159
|
} else if (issue2.code === "invalid_element") {
|
|
5160
|
-
processError({ issues: issue2.issues }, [...
|
|
5160
|
+
processError({ issues: issue2.issues }, [...path31, ...issue2.path]);
|
|
5161
5161
|
} else {
|
|
5162
|
-
const fullpath = [...
|
|
5162
|
+
const fullpath = [...path31, ...issue2.path];
|
|
5163
5163
|
if (fullpath.length === 0) {
|
|
5164
5164
|
result.errors.push(mapper(issue2));
|
|
5165
5165
|
continue;
|
|
@@ -5198,8 +5198,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
|
|
|
5198
5198
|
}
|
|
5199
5199
|
function toDotPath(_path) {
|
|
5200
5200
|
const segs = [];
|
|
5201
|
-
const
|
|
5202
|
-
for (const seg of
|
|
5201
|
+
const path31 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
5202
|
+
for (const seg of path31) {
|
|
5203
5203
|
if (typeof seg === "number")
|
|
5204
5204
|
segs.push(`[${seg}]`);
|
|
5205
5205
|
else if (typeof seg === "symbol")
|
|
@@ -22337,13 +22337,13 @@ function resolveRef(ref, ctx) {
|
|
|
22337
22337
|
if (!ref.startsWith("#")) {
|
|
22338
22338
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
22339
22339
|
}
|
|
22340
|
-
const
|
|
22341
|
-
if (
|
|
22340
|
+
const path31 = ref.slice(1).split("/").filter(Boolean);
|
|
22341
|
+
if (path31.length === 0) {
|
|
22342
22342
|
return ctx.rootSchema;
|
|
22343
22343
|
}
|
|
22344
22344
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
22345
|
-
if (
|
|
22346
|
-
const key =
|
|
22345
|
+
if (path31[0] === defsKey) {
|
|
22346
|
+
const key = path31[1] === void 0 ? void 0 : decodeJSONPointerSegment(path31[1]);
|
|
22347
22347
|
if (!key || !ctx.defs[key]) {
|
|
22348
22348
|
throw new Error(`Reference not found: ${ref}`);
|
|
22349
22349
|
}
|
|
@@ -23647,6 +23647,14 @@ var agentEventSchema = external_exports.object({
|
|
|
23647
23647
|
event: external_exports.string(),
|
|
23648
23648
|
data: external_exports.unknown().optional()
|
|
23649
23649
|
});
|
|
23650
|
+
var changedFileSchema = external_exports.object({
|
|
23651
|
+
path: external_exports.string(),
|
|
23652
|
+
additions: external_exports.number().int(),
|
|
23653
|
+
deletions: external_exports.number().int(),
|
|
23654
|
+
explanation: external_exports.string().optional(),
|
|
23655
|
+
/** Agent engine: whether the file was created, modified or deleted. */
|
|
23656
|
+
kind: external_exports.enum(["create", "modify", "delete"]).optional()
|
|
23657
|
+
});
|
|
23650
23658
|
var agentChangeEventSchema = external_exports.discriminatedUnion("type", [
|
|
23651
23659
|
external_exports.object({ type: external_exports.literal("planning"), changeId: external_exports.uuid() }),
|
|
23652
23660
|
external_exports.object({
|
|
@@ -23654,6 +23662,8 @@ var agentChangeEventSchema = external_exports.discriminatedUnion("type", [
|
|
|
23654
23662
|
changeId: external_exports.uuid(),
|
|
23655
23663
|
plan: changePlanSchema,
|
|
23656
23664
|
model: external_exports.string(),
|
|
23665
|
+
/** Models tried first that couldn't run (A7, A11): the plan says it ran on another. */
|
|
23666
|
+
fellBackFrom: external_exports.array(external_exports.string()).optional(),
|
|
23657
23667
|
/** Credits the change has cost so far (B1). */
|
|
23658
23668
|
cost: external_exports.number().nonnegative().optional()
|
|
23659
23669
|
}),
|
|
@@ -23673,22 +23683,23 @@ var agentChangeEventSchema = external_exports.discriminatedUnion("type", [
|
|
|
23673
23683
|
external_exports.object({
|
|
23674
23684
|
type: external_exports.literal("checking"),
|
|
23675
23685
|
changeId: external_exports.uuid(),
|
|
23676
|
-
checks: external_exports.array(
|
|
23686
|
+
checks: external_exports.array(
|
|
23687
|
+
external_exports.object({
|
|
23688
|
+
kind: checkKindSchema,
|
|
23689
|
+
status: checkStatusSchema,
|
|
23690
|
+
/** How long a finished check took (L16). */
|
|
23691
|
+
durationMs: external_exports.number().int().nonnegative().optional()
|
|
23692
|
+
})
|
|
23693
|
+
),
|
|
23694
|
+
/** The change as written, shown while its checks run (L17, ADR 0042). */
|
|
23695
|
+
files: external_exports.array(changedFileSchema).optional(),
|
|
23696
|
+
diff: external_exports.string().optional()
|
|
23677
23697
|
}),
|
|
23678
23698
|
external_exports.object({
|
|
23679
23699
|
type: external_exports.literal("result"),
|
|
23680
23700
|
changeId: external_exports.uuid(),
|
|
23681
23701
|
state: changeStateSchema,
|
|
23682
|
-
files: external_exports.array(
|
|
23683
|
-
external_exports.object({
|
|
23684
|
-
path: external_exports.string(),
|
|
23685
|
-
additions: external_exports.number().int(),
|
|
23686
|
-
deletions: external_exports.number().int(),
|
|
23687
|
-
explanation: external_exports.string().optional(),
|
|
23688
|
-
/** Agent engine: whether the file was created, modified or deleted. */
|
|
23689
|
-
kind: external_exports.enum(["create", "modify", "delete"]).optional()
|
|
23690
|
-
})
|
|
23691
|
-
),
|
|
23702
|
+
files: external_exports.array(changedFileSchema),
|
|
23692
23703
|
diff: external_exports.string(),
|
|
23693
23704
|
checks: external_exports.array(checkResultSchema),
|
|
23694
23705
|
/** Credits the change has cost so far (B1). */
|
|
@@ -24641,16 +24652,16 @@ var Diff = class {
|
|
|
24641
24652
|
}
|
|
24642
24653
|
}
|
|
24643
24654
|
}
|
|
24644
|
-
addToPath(
|
|
24645
|
-
const last =
|
|
24655
|
+
addToPath(path31, added, removed, oldPosInc, options) {
|
|
24656
|
+
const last = path31.lastComponent;
|
|
24646
24657
|
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
|
|
24647
24658
|
return {
|
|
24648
|
-
oldPos:
|
|
24659
|
+
oldPos: path31.oldPos + oldPosInc,
|
|
24649
24660
|
lastComponent: { count: last.count + 1, added, removed, previousComponent: last.previousComponent }
|
|
24650
24661
|
};
|
|
24651
24662
|
} else {
|
|
24652
24663
|
return {
|
|
24653
|
-
oldPos:
|
|
24664
|
+
oldPos: path31.oldPos + oldPosInc,
|
|
24654
24665
|
lastComponent: { count: 1, added, removed, previousComponent: last }
|
|
24655
24666
|
};
|
|
24656
24667
|
}
|
|
@@ -30455,17 +30466,17 @@ var writeToFiles = (chunks, stdioItems, outputFiles) => {
|
|
|
30455
30466
|
const writtenPaths = /* @__PURE__ */ new Set();
|
|
30456
30467
|
for (const stdioItem of fileItems) {
|
|
30457
30468
|
const { type, append } = stdioItem;
|
|
30458
|
-
const
|
|
30459
|
-
if (writtenPaths.has(
|
|
30469
|
+
const path31 = type === "fileNumber" ? stdioItem.path : getFileTarget(stdioItem);
|
|
30470
|
+
if (writtenPaths.has(path31)) {
|
|
30460
30471
|
continue;
|
|
30461
30472
|
}
|
|
30462
|
-
writtenPaths.add(
|
|
30463
|
-
if (append || outputFiles.has(
|
|
30464
|
-
appendFileSync(
|
|
30473
|
+
writtenPaths.add(path31);
|
|
30474
|
+
if (append || outputFiles.has(path31)) {
|
|
30475
|
+
appendFileSync(path31, contents);
|
|
30465
30476
|
} else {
|
|
30466
|
-
writeFileSync2(
|
|
30477
|
+
writeFileSync2(path31, contents);
|
|
30467
30478
|
}
|
|
30468
|
-
outputFiles.add(
|
|
30479
|
+
outputFiles.add(path31);
|
|
30469
30480
|
}
|
|
30470
30481
|
};
|
|
30471
30482
|
|
|
@@ -33447,6 +33458,71 @@ ${summary}` : summary
|
|
|
33447
33458
|
// ../agent-core/src/checks/browser.ts
|
|
33448
33459
|
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
33449
33460
|
import path21 from "path";
|
|
33461
|
+
var LOC_ATTRIBUTE = "data-revyse-loc";
|
|
33462
|
+
var FRAME_PADDING = 24;
|
|
33463
|
+
var FRAME_MIN_WIDTH = 480;
|
|
33464
|
+
var FRAME_MAX_RATIO = 2;
|
|
33465
|
+
function frameAround(box, viewport) {
|
|
33466
|
+
const width = Math.min(Math.max(box.width + 2 * FRAME_PADDING, FRAME_MIN_WIDTH), viewport.width);
|
|
33467
|
+
const height = Math.min(
|
|
33468
|
+
Math.max(box.height + 2 * FRAME_PADDING, width / FRAME_MAX_RATIO),
|
|
33469
|
+
viewport.height
|
|
33470
|
+
);
|
|
33471
|
+
const place = (start, size, frame, limit) => Math.round(Math.min(Math.max(start + size / 2 - frame / 2, 0), limit - frame));
|
|
33472
|
+
return {
|
|
33473
|
+
x: place(box.x, box.width, width, viewport.width),
|
|
33474
|
+
y: place(box.y, box.height, height, viewport.height),
|
|
33475
|
+
width: Math.round(width),
|
|
33476
|
+
height: Math.round(height)
|
|
33477
|
+
};
|
|
33478
|
+
}
|
|
33479
|
+
function nearestLoc(locs, source) {
|
|
33480
|
+
const distance = (loc) => {
|
|
33481
|
+
const [line, column] = loc.split(":").slice(-2).map(Number);
|
|
33482
|
+
if (!Number.isFinite(line) || source.line === void 0) return Infinity;
|
|
33483
|
+
return Math.abs(line - source.line) * 1e4 + Math.abs((column ?? 0) - (source.column ?? 0));
|
|
33484
|
+
};
|
|
33485
|
+
let best = 0;
|
|
33486
|
+
for (let i2 = 1; i2 < locs.length; i2++) if (distance(locs[i2]) < distance(locs[best])) best = i2;
|
|
33487
|
+
return best;
|
|
33488
|
+
}
|
|
33489
|
+
async function findSelected(page, selector, source) {
|
|
33490
|
+
const bySelector = page.locator(selector).first();
|
|
33491
|
+
const selectorFound = await page.locator(selector).count().catch(() => 0) > 0;
|
|
33492
|
+
if (source?.line) {
|
|
33493
|
+
const exact = source.column === void 0 ? `${source.file}:${source.line}:` : `${source.file}:${source.line}:${source.column}`;
|
|
33494
|
+
const isExact = (loc) => source.column === void 0 ? !!loc?.startsWith(exact) : loc === exact;
|
|
33495
|
+
if (selectorFound) {
|
|
33496
|
+
const loc = await bySelector.evaluate((el, attr) => el.closest(`[${attr}]`)?.getAttribute(attr) ?? null, LOC_ATTRIBUTE).catch(() => null);
|
|
33497
|
+
if (isExact(loc)) return bySelector;
|
|
33498
|
+
}
|
|
33499
|
+
const op = source.column === void 0 ? "^=" : "=";
|
|
33500
|
+
const byLoc = page.locator(`[${LOC_ATTRIBUTE}${op}${JSON.stringify(exact)}]`);
|
|
33501
|
+
if (await byLoc.count().catch(() => 0) > 0) return byLoc.first();
|
|
33502
|
+
}
|
|
33503
|
+
if (selectorFound) return bySelector;
|
|
33504
|
+
if (!source) return null;
|
|
33505
|
+
const fromFile = page.locator(`[${LOC_ATTRIBUTE}^=${JSON.stringify(`${source.file}:`)}]`);
|
|
33506
|
+
const locs = await fromFile.evaluateAll((els, attr) => els.map((el) => el.getAttribute(attr) ?? ""), LOC_ATTRIBUTE).catch(() => []);
|
|
33507
|
+
return locs.length > 0 ? fromFile.nth(nearestLoc(locs, source)) : null;
|
|
33508
|
+
}
|
|
33509
|
+
async function frameSelected(page, target) {
|
|
33510
|
+
await target.scrollIntoViewIfNeeded({ timeout: 2e3 }).catch(() => {
|
|
33511
|
+
});
|
|
33512
|
+
await page.evaluate(
|
|
33513
|
+
() => Promise.race([
|
|
33514
|
+
Promise.all(
|
|
33515
|
+
Array.from(document.images).filter((img) => !img.complete).map((img) => new Promise((done) => img.addEventListener("loadend", done)))
|
|
33516
|
+
),
|
|
33517
|
+
new Promise((done) => setTimeout(done, 2e3))
|
|
33518
|
+
])
|
|
33519
|
+
).catch(() => {
|
|
33520
|
+
});
|
|
33521
|
+
const box = await target.boundingBox().catch(() => null);
|
|
33522
|
+
const viewport = page.viewportSize();
|
|
33523
|
+
if (!box || !viewport || box.width <= 0 || box.height <= 0) return null;
|
|
33524
|
+
return frameAround(box, viewport);
|
|
33525
|
+
}
|
|
33450
33526
|
var BrowserUnavailableError = class extends Error {
|
|
33451
33527
|
};
|
|
33452
33528
|
var MAX_ERRORS = 10;
|
|
@@ -33474,7 +33550,7 @@ async function createPlaywrightDriver(env = process.env) {
|
|
|
33474
33550
|
if (!browser) throw new BrowserUnavailableError(`No Chromium could be started. ${NOT_RUN_HINT}`);
|
|
33475
33551
|
const b = browser;
|
|
33476
33552
|
return {
|
|
33477
|
-
async capture({ url: url2, selector,
|
|
33553
|
+
async capture({ url: url2, selector, source, timeoutMs }) {
|
|
33478
33554
|
const context = await b.newContext({ viewport: { width: 1280, height: 800 } });
|
|
33479
33555
|
const page = await context.newPage();
|
|
33480
33556
|
const errors = [];
|
|
@@ -33494,9 +33570,15 @@ async function createPlaywrightDriver(env = process.env) {
|
|
|
33494
33570
|
let found = null;
|
|
33495
33571
|
if (await page.locator(selector).count().catch(() => 0) > 0)
|
|
33496
33572
|
found = "selector";
|
|
33497
|
-
else if (
|
|
33573
|
+
else if (source && await page.locator(`[${LOC_ATTRIBUTE}^=${JSON.stringify(`${source.file}:`)}]`).count() > 0)
|
|
33498
33574
|
found = "source";
|
|
33499
|
-
const
|
|
33575
|
+
const target = await findSelected(page, selector, source);
|
|
33576
|
+
const clip = target ? await frameSelected(page, target) : null;
|
|
33577
|
+
const screenshot = await page.screenshot({
|
|
33578
|
+
type: "png",
|
|
33579
|
+
animations: "disabled",
|
|
33580
|
+
...clip ? { clip } : {}
|
|
33581
|
+
});
|
|
33500
33582
|
return { status: res?.status() ?? null, found, screenshot, errors };
|
|
33501
33583
|
} finally {
|
|
33502
33584
|
await context.close();
|
|
@@ -33611,7 +33693,7 @@ function createAgentApi(opts) {
|
|
|
33611
33693
|
const f = opts.fetch ?? fetch;
|
|
33612
33694
|
const base = opts.apiUrl.replace(/\/+$/, "");
|
|
33613
33695
|
const auth = { authorization: `Bearer ${opts.token}` };
|
|
33614
|
-
const call = (
|
|
33696
|
+
const call = (path31, body, signal) => f(`${base}${path31}`, {
|
|
33615
33697
|
method: "POST",
|
|
33616
33698
|
headers: { "content-type": "application/json", ...auth },
|
|
33617
33699
|
body: JSON.stringify(body),
|
|
@@ -33627,18 +33709,18 @@ function createAgentApi(opts) {
|
|
|
33627
33709
|
}
|
|
33628
33710
|
return {
|
|
33629
33711
|
baseUrl: base,
|
|
33630
|
-
async get(
|
|
33631
|
-
const res = await f(`${base}${
|
|
33712
|
+
async get(path31) {
|
|
33713
|
+
const res = await f(`${base}${path31}`, { headers: auth });
|
|
33632
33714
|
if (!res.ok) throw await failure2(res);
|
|
33633
33715
|
return await res.json();
|
|
33634
33716
|
},
|
|
33635
|
-
async post(
|
|
33636
|
-
const res = await call(
|
|
33717
|
+
async post(path31, body) {
|
|
33718
|
+
const res = await call(path31, body);
|
|
33637
33719
|
if (!res.ok) throw await failure2(res);
|
|
33638
33720
|
return await res.json();
|
|
33639
33721
|
},
|
|
33640
|
-
async *stream(
|
|
33641
|
-
const res = await call(
|
|
33722
|
+
async *stream(path31, body, signal) {
|
|
33723
|
+
const res = await call(path31, body, signal);
|
|
33642
33724
|
if (!res.ok) throw await failure2(res);
|
|
33643
33725
|
if (!res.body) return;
|
|
33644
33726
|
const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
@@ -33671,11 +33753,51 @@ function parseBlock(block) {
|
|
|
33671
33753
|
|
|
33672
33754
|
// ../agent-core/src/change/runner.ts
|
|
33673
33755
|
import { readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
33674
|
-
import
|
|
33756
|
+
import path25 from "path";
|
|
33757
|
+
|
|
33758
|
+
// ../agent-core/src/patch/originals.ts
|
|
33759
|
+
import { existsSync as existsSync4 } from "fs";
|
|
33760
|
+
import path22 from "path";
|
|
33761
|
+
async function withOriginals(opts) {
|
|
33762
|
+
const io = opts.io ?? nodeIO;
|
|
33763
|
+
const dir = changeDir(opts.projectRoot, opts.changeId);
|
|
33764
|
+
const manifest = JSON.parse(io.readFile(path22.join(dir, "manifest.json")));
|
|
33765
|
+
const files = manifest.files.map((f) => ({
|
|
33766
|
+
...f,
|
|
33767
|
+
real: resolveProjectPath(opts.projectRoot, f.path),
|
|
33768
|
+
pre: f.preHash === null ? null : io.readFile(path22.join(dir, "pre", f.path)),
|
|
33769
|
+
post: f.postHash === null ? null : io.readFile(path22.join(dir, "post", f.path))
|
|
33770
|
+
}));
|
|
33771
|
+
const hashOf = (real) => existsSync4(real) ? hashContent(io.readFile(real)) : null;
|
|
33772
|
+
const put = (real, content) => {
|
|
33773
|
+
if (content === null) io.unlink(real);
|
|
33774
|
+
else {
|
|
33775
|
+
io.mkdir(path22.dirname(real));
|
|
33776
|
+
io.writeFile(real, content);
|
|
33777
|
+
}
|
|
33778
|
+
};
|
|
33779
|
+
const changed = files.filter((f) => hashOf(f.real) !== f.postHash).map((f) => f.path);
|
|
33780
|
+
if (changed.length > 0) return { status: "changed", paths: changed };
|
|
33781
|
+
for (const f of files) put(f.real, f.pre);
|
|
33782
|
+
const putBack = () => {
|
|
33783
|
+
const kept2 = files.filter((f) => hashOf(f.real) !== f.preHash).map((f) => f.path);
|
|
33784
|
+
for (const f of files) if (!kept2.includes(f.path)) put(f.real, f.post);
|
|
33785
|
+
return kept2;
|
|
33786
|
+
};
|
|
33787
|
+
let value;
|
|
33788
|
+
try {
|
|
33789
|
+
value = await opts.work();
|
|
33790
|
+
} catch (err) {
|
|
33791
|
+
putBack();
|
|
33792
|
+
throw err;
|
|
33793
|
+
}
|
|
33794
|
+
const kept = putBack();
|
|
33795
|
+
return kept.length > 0 ? { status: "kept-edit", value, paths: kept } : { status: "done", value };
|
|
33796
|
+
}
|
|
33675
33797
|
|
|
33676
33798
|
// ../agent-core/src/tools/file-host.ts
|
|
33677
33799
|
import { readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
|
|
33678
|
-
import
|
|
33800
|
+
import path23 from "path";
|
|
33679
33801
|
var SKIPPED_DIRS = /* @__PURE__ */ new Set([
|
|
33680
33802
|
"node_modules",
|
|
33681
33803
|
".git",
|
|
@@ -33691,7 +33813,7 @@ var SKIPPED_DIRS = /* @__PURE__ */ new Set([
|
|
|
33691
33813
|
]);
|
|
33692
33814
|
var MAX_HOST_FILE_BYTES = 1e6;
|
|
33693
33815
|
var MAX_LISTED = 2e4;
|
|
33694
|
-
var toPosix2 = (p) => p.split(
|
|
33816
|
+
var toPosix2 = (p) => p.split(path23.sep).join("/");
|
|
33695
33817
|
function localFileHost(projectRoot) {
|
|
33696
33818
|
return {
|
|
33697
33819
|
async read(relativePath) {
|
|
@@ -33719,12 +33841,12 @@ function localFileHost(projectRoot) {
|
|
|
33719
33841
|
if (out.length >= MAX_LISTED) return;
|
|
33720
33842
|
let entries;
|
|
33721
33843
|
try {
|
|
33722
|
-
entries = readdirSync3(
|
|
33844
|
+
entries = readdirSync3(path23.join(projectRoot, dir), { withFileTypes: true });
|
|
33723
33845
|
} catch {
|
|
33724
33846
|
return;
|
|
33725
33847
|
}
|
|
33726
33848
|
for (const e of entries.sort((a2, b) => a2.name.localeCompare(b.name))) {
|
|
33727
|
-
const rel = toPosix2(
|
|
33849
|
+
const rel = toPosix2(path23.join(dir, e.name));
|
|
33728
33850
|
if (e.isDirectory()) {
|
|
33729
33851
|
if (!SKIPPED_DIRS.has(e.name) && !isSensitivePath(rel)) walk(rel);
|
|
33730
33852
|
} else if (e.isFile() && !isSensitivePath(rel)) {
|
|
@@ -33740,7 +33862,7 @@ function localFileHost(projectRoot) {
|
|
|
33740
33862
|
}
|
|
33741
33863
|
|
|
33742
33864
|
// ../agent-core/src/tools/integrations.ts
|
|
33743
|
-
import
|
|
33865
|
+
import path24 from "path";
|
|
33744
33866
|
var CODE = /\.(tsx?|jsx?|mjs|cjs)$/;
|
|
33745
33867
|
var ENV_TEMPLATE = /^\.env\.(example|sample|template)$/;
|
|
33746
33868
|
var ENV_IN_CODE = /process\.env\.([A-Z][A-Z0-9_]*)|process\.env\[\s*["']([A-Z][A-Z0-9_]*)["']\s*\]|import\.meta\.env\.([A-Z][A-Z0-9_]*)/g;
|
|
@@ -33763,7 +33885,7 @@ async function projectSignals(read, paths) {
|
|
|
33763
33885
|
for (const m of text.matchAll(ENV_IN_CODE)) env.add(m[1] ?? m[2] ?? m[3]);
|
|
33764
33886
|
for (const m of text.matchAll(IMPORT)) imports.add(packageOf(m[1] ?? m[2]));
|
|
33765
33887
|
}
|
|
33766
|
-
for (const p of paths.filter((p2) => ENV_TEMPLATE.test(
|
|
33888
|
+
for (const p of paths.filter((p2) => ENV_TEMPLATE.test(path24.posix.basename(p2)))) {
|
|
33767
33889
|
for (const line of (await read(p) ?? "").split("\n")) {
|
|
33768
33890
|
const name = line.match(/^\s*(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/)?.[1];
|
|
33769
33891
|
if (name) env.add(name);
|
|
@@ -33823,27 +33945,27 @@ var StagedFiles = class {
|
|
|
33823
33945
|
/** Paths written by the package manager (I7), not by the model: the write rules don't apply. */
|
|
33824
33946
|
fromPackageManager = /* @__PURE__ */ new Set();
|
|
33825
33947
|
/** Current content as the agent sees it: staged if written, else the project's. */
|
|
33826
|
-
async read(
|
|
33827
|
-
const s = this.staged.get(
|
|
33948
|
+
async read(path31) {
|
|
33949
|
+
const s = this.staged.get(path31);
|
|
33828
33950
|
if (s) return s.content;
|
|
33829
|
-
const content = await this.host.read(
|
|
33830
|
-
if (!this.seen.has(
|
|
33951
|
+
const content = await this.host.read(path31);
|
|
33952
|
+
if (!this.seen.has(path31)) this.seen.set(path31, content === null ? null : hashContent(content));
|
|
33831
33953
|
return content;
|
|
33832
33954
|
}
|
|
33833
33955
|
/** Like `read`, without recording a snapshot: for searching, which only looks. */
|
|
33834
|
-
async peek(
|
|
33835
|
-
const s = this.staged.get(
|
|
33836
|
-
return s ? s.content : this.host.read(
|
|
33956
|
+
async peek(path31) {
|
|
33957
|
+
const s = this.staged.get(path31);
|
|
33958
|
+
return s ? s.content : this.host.read(path31);
|
|
33837
33959
|
}
|
|
33838
33960
|
/** Writes `content` (or deletes, with null) in the staging layer. */
|
|
33839
|
-
async write(
|
|
33840
|
-
const existing = this.staged.get(
|
|
33961
|
+
async write(path31, content) {
|
|
33962
|
+
const existing = this.staged.get(path31);
|
|
33841
33963
|
if (existing) {
|
|
33842
33964
|
existing.content = content;
|
|
33843
33965
|
return;
|
|
33844
33966
|
}
|
|
33845
|
-
const before = await this.read(
|
|
33846
|
-
this.staged.set(
|
|
33967
|
+
const before = await this.read(path31);
|
|
33968
|
+
this.staged.set(path31, {
|
|
33847
33969
|
content,
|
|
33848
33970
|
existed: before !== null,
|
|
33849
33971
|
baseHash: before === null ? null : hashContent(before)
|
|
@@ -33854,9 +33976,9 @@ var StagedFiles = class {
|
|
|
33854
33976
|
* a supported SDK). The patch engine applies it with the rest, so it lands, is checked and is
|
|
33855
33977
|
* reverted together; the write rules that keep the model out of these files don't apply.
|
|
33856
33978
|
*/
|
|
33857
|
-
async writeFromPackageManager(
|
|
33858
|
-
await this.write(
|
|
33859
|
-
this.fromPackageManager.add(
|
|
33979
|
+
async writeFromPackageManager(path31, content) {
|
|
33980
|
+
await this.write(path31, content);
|
|
33981
|
+
this.fromPackageManager.add(path31);
|
|
33860
33982
|
}
|
|
33861
33983
|
/** The paths the package manager wrote. */
|
|
33862
33984
|
packageManagerPaths() {
|
|
@@ -33874,18 +33996,18 @@ var StagedFiles = class {
|
|
|
33874
33996
|
/** What the apply phase changed, net: a file created then deleted is no change at all. */
|
|
33875
33997
|
changes() {
|
|
33876
33998
|
const out = [];
|
|
33877
|
-
for (const [
|
|
33999
|
+
for (const [path31, s] of [...this.staged].sort(([a2], [b]) => a2.localeCompare(b))) {
|
|
33878
34000
|
if (!s.existed && s.content === null) continue;
|
|
33879
|
-
if (!s.existed) out.push({ path:
|
|
33880
|
-
else if (s.content === null) out.push({ path:
|
|
34001
|
+
if (!s.existed) out.push({ path: path31, kind: "create", content: s.content });
|
|
34002
|
+
else if (s.content === null) out.push({ path: path31, kind: "delete", baseHash: s.baseHash });
|
|
33881
34003
|
else if (hashContent(s.content) !== s.baseHash)
|
|
33882
|
-
out.push({ path:
|
|
34004
|
+
out.push({ path: path31, kind: "modify", content: s.content, baseHash: s.baseHash });
|
|
33883
34005
|
}
|
|
33884
34006
|
return out;
|
|
33885
34007
|
}
|
|
33886
34008
|
/** Every file the agent read or wrote, with its hash when first seen (null: didn't exist). */
|
|
33887
34009
|
snapshots() {
|
|
33888
|
-
return [...this.seen].map(([
|
|
34010
|
+
return [...this.seen].map(([path31, hash2]) => ({ path: path31, hash: hash2 })).sort((a2, b) => a2.path.localeCompare(b.path));
|
|
33889
34011
|
}
|
|
33890
34012
|
};
|
|
33891
34013
|
|
|
@@ -33922,7 +34044,7 @@ var ok = (callId, output2) => ({
|
|
|
33922
34044
|
output: output2,
|
|
33923
34045
|
isError: false
|
|
33924
34046
|
});
|
|
33925
|
-
var secretFile = (
|
|
34047
|
+
var secretFile = (path31) => `Can't change ${path31}: it looks like it contains a secret. Leave it, and name it in notDone so the developer moves the secret to an environment variable.`;
|
|
33926
34048
|
var fail = (callId, output2) => ({
|
|
33927
34049
|
callId,
|
|
33928
34050
|
output: output2,
|
|
@@ -33965,12 +34087,12 @@ function createToolRuntime(opts) {
|
|
|
33965
34087
|
const found = [];
|
|
33966
34088
|
let withheld = 0;
|
|
33967
34089
|
let stopped = false;
|
|
33968
|
-
for (const
|
|
34090
|
+
for (const path31 of await visible(input2.path, input2.glob)) {
|
|
33969
34091
|
if (found.length >= max || Date.now() - started > budget) {
|
|
33970
34092
|
stopped = true;
|
|
33971
34093
|
break;
|
|
33972
34094
|
}
|
|
33973
|
-
const content = await staging.peek(
|
|
34095
|
+
const content = await staging.peek(path31);
|
|
33974
34096
|
if (content === null) continue;
|
|
33975
34097
|
if (containsSecret(content)) {
|
|
33976
34098
|
withheld++;
|
|
@@ -33982,7 +34104,7 @@ function createToolRuntime(opts) {
|
|
|
33982
34104
|
if (line.length > MAX_SEARCH_LINE || !matches(line)) continue;
|
|
33983
34105
|
const text = line.trim();
|
|
33984
34106
|
found.push(
|
|
33985
|
-
`${
|
|
34107
|
+
`${path31}:${i2 + 1}: ${text.length > MAX_LINE ? `${text.slice(0, MAX_LINE)}\u2026` : text}`
|
|
33986
34108
|
);
|
|
33987
34109
|
}
|
|
33988
34110
|
}
|
|
@@ -34190,11 +34312,13 @@ function createChangeRunner(opts) {
|
|
|
34190
34312
|
function run(session, work) {
|
|
34191
34313
|
if (session.busy) throw new Busy("Revyse is still working on this change");
|
|
34192
34314
|
session.busy = true;
|
|
34193
|
-
|
|
34194
|
-
|
|
34315
|
+
const controller = new AbortController();
|
|
34316
|
+
session.abort = controller;
|
|
34317
|
+
const signal = controller.signal;
|
|
34195
34318
|
void work(signal).catch((err) => {
|
|
34196
34319
|
if (!signal.aborted) report(session.id, err);
|
|
34197
34320
|
}).finally(() => {
|
|
34321
|
+
if (session.abort !== controller) return;
|
|
34198
34322
|
session.busy = false;
|
|
34199
34323
|
session.abort = void 0;
|
|
34200
34324
|
});
|
|
@@ -34222,9 +34346,9 @@ function createChangeRunner(opts) {
|
|
|
34222
34346
|
const stored = [];
|
|
34223
34347
|
for (const key of localKeys) {
|
|
34224
34348
|
try {
|
|
34225
|
-
const bytes2 = readFileSync8(
|
|
34349
|
+
const bytes2 = readFileSync8(path25.join(opts.projectRoot, key));
|
|
34226
34350
|
const { artifact, upload } = await opts.api.post(`/api/changes/${changeId}/artifacts`, {
|
|
34227
|
-
name:
|
|
34351
|
+
name: path25.basename(key),
|
|
34228
34352
|
contentType: "image/png",
|
|
34229
34353
|
size: bytes2.length
|
|
34230
34354
|
});
|
|
@@ -34269,7 +34393,7 @@ function createChangeRunner(opts) {
|
|
|
34269
34393
|
const before = await driver.capture({
|
|
34270
34394
|
url: url2,
|
|
34271
34395
|
selector: session.element.selector,
|
|
34272
|
-
|
|
34396
|
+
source: session.element.source,
|
|
34273
34397
|
timeoutMs: 3e4
|
|
34274
34398
|
}).catch((err) => {
|
|
34275
34399
|
log(`browser check: before-capture failed: ${err.message}`);
|
|
@@ -34302,7 +34426,7 @@ function createChangeRunner(opts) {
|
|
|
34302
34426
|
`/api/changes/${session.id}/plan`,
|
|
34303
34427
|
{
|
|
34304
34428
|
element: session.element,
|
|
34305
|
-
files: gathered.files.map(({ path:
|
|
34429
|
+
files: gathered.files.map(({ path: path31, content, hash: hash2 }) => ({ path: path31, content, hash: hash2 })),
|
|
34306
34430
|
modelTier: session.tier,
|
|
34307
34431
|
model: session.model,
|
|
34308
34432
|
...extra
|
|
@@ -34314,16 +34438,24 @@ function createChangeRunner(opts) {
|
|
|
34314
34438
|
const {
|
|
34315
34439
|
plan: result,
|
|
34316
34440
|
model,
|
|
34317
|
-
cost
|
|
34441
|
+
cost,
|
|
34442
|
+
fellBackFrom
|
|
34318
34443
|
} = ev.data;
|
|
34319
|
-
onPlan(session, result, model, cost);
|
|
34444
|
+
onPlan(session, result, model, cost, void 0, fellBackFrom);
|
|
34320
34445
|
}
|
|
34321
34446
|
}
|
|
34322
|
-
function onPlan(session, result, model, cost, connectPath) {
|
|
34447
|
+
function onPlan(session, result, model, cost, connectPath, fellBackFrom) {
|
|
34323
34448
|
session.plan = result;
|
|
34324
34449
|
if (cost !== void 0) session.cost = cost;
|
|
34325
34450
|
if (result.decision === "proceed")
|
|
34326
|
-
opts.emit({
|
|
34451
|
+
opts.emit({
|
|
34452
|
+
type: "plan",
|
|
34453
|
+
changeId: session.id,
|
|
34454
|
+
plan: result,
|
|
34455
|
+
model,
|
|
34456
|
+
...fellBackFrom?.length ? { fellBackFrom } : {},
|
|
34457
|
+
cost: session.cost
|
|
34458
|
+
});
|
|
34327
34459
|
else if (result.decision === "clarify")
|
|
34328
34460
|
opts.emit({
|
|
34329
34461
|
type: "clarification",
|
|
@@ -34353,19 +34485,23 @@ function createChangeRunner(opts) {
|
|
|
34353
34485
|
}
|
|
34354
34486
|
async function apply(session, signal) {
|
|
34355
34487
|
const browser = prepareBrowser(session);
|
|
34356
|
-
const baseline = hasLocalChecks() ? runChecks(session.contextPaths).catch((err) => {
|
|
34357
|
-
log(`baseline checks failed to run: ${err.message}`);
|
|
34358
|
-
return [];
|
|
34359
|
-
}) : Promise.resolve([]);
|
|
34360
34488
|
try {
|
|
34361
|
-
await applyWithBrowser(session, signal, browser
|
|
34489
|
+
await applyWithBrowser(session, signal, browser);
|
|
34362
34490
|
} finally {
|
|
34363
34491
|
void browser.then((b) => b.driver?.close()).catch(() => {
|
|
34364
34492
|
});
|
|
34365
34493
|
}
|
|
34366
34494
|
}
|
|
34367
|
-
|
|
34368
|
-
|
|
34495
|
+
async function beforePage(prep) {
|
|
34496
|
+
const late = /* @__PURE__ */ Symbol("late");
|
|
34497
|
+
const ready = await Promise.race([
|
|
34498
|
+
prep,
|
|
34499
|
+
new Promise((r) => setTimeout(() => r(late), opts.beforeWaitMs ?? 3e3))
|
|
34500
|
+
]);
|
|
34501
|
+
if (ready !== late) return ready;
|
|
34502
|
+
return { before: null, reason: "The page took too long to load before the change." };
|
|
34503
|
+
}
|
|
34504
|
+
async function applyWithBrowser(session, signal, browserPrep) {
|
|
34369
34505
|
const { files, missing } = readFiles(session.contextPaths);
|
|
34370
34506
|
const stale = [
|
|
34371
34507
|
...missing,
|
|
@@ -34382,10 +34518,16 @@ function createChangeRunner(opts) {
|
|
|
34382
34518
|
return conflict(session.id, "changed_since_plan", stale);
|
|
34383
34519
|
}
|
|
34384
34520
|
session.stage = "patching";
|
|
34521
|
+
const planned = new Set(session.plan?.steps.flatMap((s) => s.files) ?? []);
|
|
34522
|
+
const toPatch = planned.size > 0 ? files.filter((f) => planned.has(f.path)) : files;
|
|
34385
34523
|
let patch;
|
|
34386
|
-
for await (const ev of opts.api.stream(
|
|
34524
|
+
for await (const ev of opts.api.stream(
|
|
34525
|
+
`/api/changes/${session.id}/patch`,
|
|
34526
|
+
{ files: toPatch },
|
|
34527
|
+
signal
|
|
34528
|
+
)) {
|
|
34387
34529
|
if (ev.event === "progress" && ev.data.stage === "patching")
|
|
34388
|
-
opts.emit({ type: "applying", changeId: session.id, files:
|
|
34530
|
+
opts.emit({ type: "applying", changeId: session.id, files: toPatch.length });
|
|
34389
34531
|
if (ev.event === "error") return emitStreamError(session, ev.data);
|
|
34390
34532
|
if (ev.event === "result") {
|
|
34391
34533
|
const data = ev.data;
|
|
@@ -34394,13 +34536,13 @@ function createChangeRunner(opts) {
|
|
|
34394
34536
|
}
|
|
34395
34537
|
}
|
|
34396
34538
|
if (!patch) return;
|
|
34397
|
-
const
|
|
34539
|
+
const browser = await beforePage(browserPrep);
|
|
34398
34540
|
session.stage = "applying";
|
|
34399
|
-
const snapshots = files.map(({ path:
|
|
34541
|
+
const snapshots = files.map(({ path: path31, hash: hash2 }) => ({ path: path31, hash: hash2 }));
|
|
34400
34542
|
const applied = applyPatch({
|
|
34401
34543
|
projectRoot: opts.projectRoot,
|
|
34402
34544
|
changeId: session.id,
|
|
34403
|
-
patch: { files: patch.files.map(({ path:
|
|
34545
|
+
patch: { files: patch.files.map(({ path: path31, edits }) => ({ path: path31, edits })) },
|
|
34404
34546
|
snapshots
|
|
34405
34547
|
});
|
|
34406
34548
|
if (applied.status === "conflict") {
|
|
@@ -34423,23 +34565,56 @@ function createChangeRunner(opts) {
|
|
|
34423
34565
|
}
|
|
34424
34566
|
session.applied = true;
|
|
34425
34567
|
const explanations = new Map(patch.files.map((f) => [f.path, f.explanation]));
|
|
34426
|
-
await checkAndReport(session, applied, browser,
|
|
34568
|
+
await checkAndReport(session, applied, browser, signal, { explanations });
|
|
34427
34569
|
}
|
|
34428
|
-
async function checkAndReport(session, applied, browser,
|
|
34570
|
+
async function checkAndReport(session, applied, browser, signal, extras = {}) {
|
|
34429
34571
|
session.stage = "checking";
|
|
34430
|
-
const
|
|
34431
|
-
|
|
34432
|
-
|
|
34433
|
-
|
|
34434
|
-
|
|
34435
|
-
|
|
34436
|
-
|
|
34437
|
-
|
|
34438
|
-
});
|
|
34572
|
+
const ALL = ["typecheck", "lint", "test", "build"];
|
|
34573
|
+
const kinds = ALL.filter((k) => opts.config.scripts[k]);
|
|
34574
|
+
const files = applied.files.map((f) => ({
|
|
34575
|
+
path: f.path,
|
|
34576
|
+
additions: f.additions,
|
|
34577
|
+
deletions: f.deletions,
|
|
34578
|
+
explanation: extras.explanations?.get(f.path),
|
|
34579
|
+
...f.kind ? { kind: f.kind } : {}
|
|
34580
|
+
}));
|
|
34581
|
+
const progress = new Map([
|
|
34582
|
+
...kinds.map((k) => [k, { status: "running" }]),
|
|
34583
|
+
...browser.driver ? [["browser", { status: "running" }]] : []
|
|
34584
|
+
]);
|
|
34585
|
+
const report2 = () => {
|
|
34586
|
+
if (signal.aborted) return;
|
|
34587
|
+
opts.emit({
|
|
34588
|
+
type: "checking",
|
|
34589
|
+
changeId: session.id,
|
|
34590
|
+
checks: [...progress].map(([kind, p]) => ({
|
|
34591
|
+
kind,
|
|
34592
|
+
status: p.status,
|
|
34593
|
+
...p.ms !== void 0 ? { durationMs: p.ms } : {}
|
|
34594
|
+
})),
|
|
34595
|
+
files,
|
|
34596
|
+
diff: applied.diff
|
|
34597
|
+
});
|
|
34598
|
+
};
|
|
34599
|
+
const finished7 = (c3) => {
|
|
34600
|
+
progress.set(c3.kind, { status: c3.status, ms: c3.durationMs });
|
|
34601
|
+
report2();
|
|
34602
|
+
return c3;
|
|
34603
|
+
};
|
|
34604
|
+
report2();
|
|
34439
34605
|
const changed = applied.files.filter((f) => f.kind !== "delete").map((f) => f.path);
|
|
34440
34606
|
const startedBrowser = performance.now();
|
|
34441
34607
|
const [local, browserRaw] = await Promise.all([
|
|
34442
|
-
|
|
34608
|
+
Promise.all(
|
|
34609
|
+
ALL.map(async (kind) => {
|
|
34610
|
+
const [r] = await runChecks(changed, [kind]);
|
|
34611
|
+
if (!r) {
|
|
34612
|
+
if (progress.delete(kind)) report2();
|
|
34613
|
+
return null;
|
|
34614
|
+
}
|
|
34615
|
+
return progress.has(kind) ? finished7(r) : r;
|
|
34616
|
+
})
|
|
34617
|
+
).then((results) => results.filter((r) => r !== null)),
|
|
34443
34618
|
browser.driver && browser.url ? runBrowserCheck({
|
|
34444
34619
|
projectRoot: opts.projectRoot,
|
|
34445
34620
|
changeId: session.id,
|
|
@@ -34451,26 +34626,45 @@ function createChangeRunner(opts) {
|
|
|
34451
34626
|
return browser.driver.capture({
|
|
34452
34627
|
url: browser.url,
|
|
34453
34628
|
selector: session.element.selector,
|
|
34454
|
-
|
|
34629
|
+
source: session.element.source,
|
|
34455
34630
|
timeoutMs: 6e4
|
|
34456
34631
|
});
|
|
34457
34632
|
},
|
|
34458
34633
|
durationMs: () => performance.now() - startedBrowser
|
|
34459
|
-
}) : Promise.resolve(browserNotRun(browser.reason ?? "The browser check didn't run."))
|
|
34634
|
+
}).then((r) => finished7(redactCheck(r))) : Promise.resolve(browserNotRun(browser.reason ?? "The browser check didn't run."))
|
|
34460
34635
|
]);
|
|
34461
|
-
|
|
34636
|
+
if (signal.aborted) return;
|
|
34637
|
+
const browserResult = browserRaw;
|
|
34462
34638
|
const localKeys = browserResult.evidence?.artifactKeys ?? [];
|
|
34463
34639
|
if (localKeys.length > 0) {
|
|
34464
34640
|
const stored = await uploadArtifacts(session.id, localKeys);
|
|
34465
34641
|
browserResult.evidence.artifactKeys = [...localKeys, ...stored];
|
|
34466
34642
|
}
|
|
34467
|
-
const
|
|
34468
|
-
|
|
34469
|
-
|
|
34470
|
-
|
|
34471
|
-
|
|
34472
|
-
|
|
34473
|
-
|
|
34643
|
+
const failed = local.filter((c3) => c3.status === "failed").map((c3) => c3.kind);
|
|
34644
|
+
let baseline = [];
|
|
34645
|
+
if (failed.length > 0) {
|
|
34646
|
+
const before = await withOriginals({
|
|
34647
|
+
projectRoot: opts.projectRoot,
|
|
34648
|
+
changeId: session.id,
|
|
34649
|
+
work: () => runChecks(changed, failed)
|
|
34650
|
+
}).catch((err) => {
|
|
34651
|
+
log(`couldn't re-run the failed checks on the original files: ${err.message}`);
|
|
34652
|
+
return null;
|
|
34653
|
+
});
|
|
34654
|
+
if (signal.aborted) return;
|
|
34655
|
+
if (before?.status === "kept-edit") {
|
|
34656
|
+
await transition(
|
|
34657
|
+
session.id,
|
|
34658
|
+
"failed",
|
|
34659
|
+
`Files changed while Revyse was checking: ${before.paths.join(", ")}`,
|
|
34660
|
+
"conflict"
|
|
34661
|
+
).catch(() => {
|
|
34662
|
+
});
|
|
34663
|
+
return conflict(session.id, "changed_while_applying", before.paths);
|
|
34664
|
+
}
|
|
34665
|
+
if (before?.status === "done") baseline = before.value;
|
|
34666
|
+
}
|
|
34667
|
+
const checks = [...compareWithBaseline(local, baseline), browserResult];
|
|
34474
34668
|
session.stage = "reporting";
|
|
34475
34669
|
const { change } = await opts.api.post(
|
|
34476
34670
|
`/api/changes/${session.id}/result`,
|
|
@@ -34492,13 +34686,7 @@ function createChangeRunner(opts) {
|
|
|
34492
34686
|
type: "result",
|
|
34493
34687
|
changeId: session.id,
|
|
34494
34688
|
state: change.state,
|
|
34495
|
-
files
|
|
34496
|
-
path: f.path,
|
|
34497
|
-
additions: f.additions,
|
|
34498
|
-
deletions: f.deletions,
|
|
34499
|
-
explanation: extras.explanations?.get(f.path),
|
|
34500
|
-
...f.kind ? { kind: f.kind } : {}
|
|
34501
|
-
})),
|
|
34689
|
+
files,
|
|
34502
34690
|
diff: applied.diff,
|
|
34503
34691
|
checks,
|
|
34504
34692
|
cost: session.cost,
|
|
@@ -34624,21 +34812,19 @@ function createChangeRunner(opts) {
|
|
|
34624
34812
|
const planned = new Set(session.plan?.steps.flatMap((s) => s.files) ?? []);
|
|
34625
34813
|
opts.emit({ type: "applying", changeId: session.id, files: planned.size });
|
|
34626
34814
|
const browserPrep = prepareBrowser(session);
|
|
34627
|
-
const baselinePrep = hasLocalChecks() ? runChecks([]).catch((err) => {
|
|
34628
|
-
log(`baseline checks failed to run: ${err.message}`);
|
|
34629
|
-
return [];
|
|
34630
|
-
}) : Promise.resolve([]);
|
|
34631
34815
|
try {
|
|
34632
|
-
await agentApplyWith(session, signal, browserPrep
|
|
34816
|
+
await agentApplyWith(session, signal, browserPrep);
|
|
34633
34817
|
} finally {
|
|
34634
34818
|
void browserPrep.then((b) => b.driver?.close()).catch(() => {
|
|
34635
34819
|
});
|
|
34636
34820
|
}
|
|
34637
34821
|
}
|
|
34638
|
-
async function agentApplyWith(session, signal, browserPrep
|
|
34822
|
+
async function agentApplyWith(session, signal, browserPrep) {
|
|
34823
|
+
let ready;
|
|
34824
|
+
const browserReady = () => ready ??= beforePage(browserPrep);
|
|
34639
34825
|
const stop = {};
|
|
34640
34826
|
const flush = async () => {
|
|
34641
|
-
await
|
|
34827
|
+
await browserReady();
|
|
34642
34828
|
if (session.applied) {
|
|
34643
34829
|
const undone = revertChange({ projectRoot: opts.projectRoot, changeId: session.id });
|
|
34644
34830
|
if (undone.status === "conflict")
|
|
@@ -34702,21 +34888,20 @@ ${detail}` : "."}`,
|
|
|
34702
34888
|
});
|
|
34703
34889
|
}
|
|
34704
34890
|
if (final.status !== "applied") return landingFailed(session, final);
|
|
34705
|
-
|
|
34706
|
-
await checkAndReport(session, final, browser, baseline, { summary: done.summary });
|
|
34891
|
+
await checkAndReport(session, final, await browserReady(), signal, { summary: done.summary });
|
|
34707
34892
|
}
|
|
34708
34893
|
async function addDependency(rt, pkg, version2) {
|
|
34709
34894
|
const pm = opts.config.packageManager;
|
|
34710
34895
|
const files = ["package.json", LOCKFILE[pm]];
|
|
34711
34896
|
const disk = (rel) => {
|
|
34712
34897
|
try {
|
|
34713
|
-
return readFileSync8(
|
|
34898
|
+
return readFileSync8(path25.join(opts.projectRoot, rel), "utf8");
|
|
34714
34899
|
} catch {
|
|
34715
34900
|
return null;
|
|
34716
34901
|
}
|
|
34717
34902
|
};
|
|
34718
34903
|
const put = (rel, content) => {
|
|
34719
|
-
const file2 =
|
|
34904
|
+
const file2 = path25.join(opts.projectRoot, rel);
|
|
34720
34905
|
if (content === null) rmSync2(file2, { force: true });
|
|
34721
34906
|
else writeFileSync4(file2, content);
|
|
34722
34907
|
};
|
|
@@ -34928,6 +35113,11 @@ ${r.output.slice(-3e3)}`,
|
|
|
34928
35113
|
},
|
|
34929
35114
|
revert(changeId) {
|
|
34930
35115
|
const s = sessionOf(changeId);
|
|
35116
|
+
if (s.busy && s.stage === "checking") {
|
|
35117
|
+
s.abort?.abort();
|
|
35118
|
+
s.abort = void 0;
|
|
35119
|
+
s.busy = false;
|
|
35120
|
+
}
|
|
34931
35121
|
run(s, async () => {
|
|
34932
35122
|
s.stage = "reverting";
|
|
34933
35123
|
const files = restore(s);
|
|
@@ -34994,8 +35184,8 @@ var ApiError = class extends Error {
|
|
|
34994
35184
|
code;
|
|
34995
35185
|
};
|
|
34996
35186
|
function createApiClient({ apiUrl, token, fetch: f = fetch }) {
|
|
34997
|
-
async function request(method,
|
|
34998
|
-
const res = await f(`${apiUrl}${
|
|
35187
|
+
async function request(method, path31, body) {
|
|
35188
|
+
const res = await f(`${apiUrl}${path31}`, {
|
|
34999
35189
|
method,
|
|
35000
35190
|
headers: {
|
|
35001
35191
|
"content-type": "application/json",
|
|
@@ -35011,8 +35201,8 @@ function createApiClient({ apiUrl, token, fetch: f = fetch }) {
|
|
|
35011
35201
|
return data;
|
|
35012
35202
|
}
|
|
35013
35203
|
return {
|
|
35014
|
-
get: (
|
|
35015
|
-
post: (
|
|
35204
|
+
get: (path31) => request("GET", path31),
|
|
35205
|
+
post: (path31, body) => request("POST", path31, body ?? {})
|
|
35016
35206
|
};
|
|
35017
35207
|
}
|
|
35018
35208
|
|
|
@@ -35021,28 +35211,28 @@ import { spawn as spawn2 } from "child_process";
|
|
|
35021
35211
|
import { hostname as hostname3 } from "os";
|
|
35022
35212
|
|
|
35023
35213
|
// src/credentials.ts
|
|
35024
|
-
import { chmodSync, existsSync as
|
|
35214
|
+
import { chmodSync, existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
|
|
35025
35215
|
import { homedir } from "os";
|
|
35026
|
-
import
|
|
35216
|
+
import path26 from "path";
|
|
35027
35217
|
function credentialsPath(env = process.env) {
|
|
35028
|
-
const base = env.XDG_CONFIG_HOME ||
|
|
35029
|
-
return
|
|
35218
|
+
const base = env.XDG_CONFIG_HOME || path26.join(homedir(), ".config");
|
|
35219
|
+
return path26.join(base, "revyse", "credentials.json");
|
|
35030
35220
|
}
|
|
35031
35221
|
function readAll(file2) {
|
|
35032
|
-
if (!
|
|
35222
|
+
if (!existsSync5(file2)) return {};
|
|
35033
35223
|
return JSON.parse(readFileSync9(file2, "utf8"));
|
|
35034
35224
|
}
|
|
35035
35225
|
function writeAll(file2, data) {
|
|
35036
|
-
mkdirSync3(
|
|
35226
|
+
mkdirSync3(path26.dirname(file2), { recursive: true, mode: 448 });
|
|
35037
35227
|
writeFileSync5(file2, `${JSON.stringify(data, null, 2)}
|
|
35038
35228
|
`, { mode: 384 });
|
|
35039
35229
|
chmodSync(file2, 384);
|
|
35040
35230
|
}
|
|
35041
35231
|
function loadCredential(projectRoot, file2 = credentialsPath()) {
|
|
35042
|
-
return readAll(file2)[
|
|
35232
|
+
return readAll(file2)[path26.resolve(projectRoot)] ?? null;
|
|
35043
35233
|
}
|
|
35044
35234
|
function saveCredential(projectRoot, cred, file2 = credentialsPath()) {
|
|
35045
|
-
writeAll(file2, { ...readAll(file2), [
|
|
35235
|
+
writeAll(file2, { ...readAll(file2), [path26.resolve(projectRoot)]: cred });
|
|
35046
35236
|
}
|
|
35047
35237
|
|
|
35048
35238
|
// src/device-login.ts
|
|
@@ -35079,12 +35269,12 @@ async function deviceLogin({
|
|
|
35079
35269
|
}
|
|
35080
35270
|
|
|
35081
35271
|
// src/init-flow.ts
|
|
35082
|
-
import { existsSync as
|
|
35083
|
-
import
|
|
35272
|
+
import { existsSync as existsSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "fs";
|
|
35273
|
+
import path29 from "path";
|
|
35084
35274
|
|
|
35085
35275
|
// src/detect.ts
|
|
35086
|
-
import { existsSync as
|
|
35087
|
-
import
|
|
35276
|
+
import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync10, statSync as statSync6 } from "fs";
|
|
35277
|
+
import path27 from "path";
|
|
35088
35278
|
var LOCKFILES2 = [
|
|
35089
35279
|
["pnpm-lock.yaml", "pnpm"],
|
|
35090
35280
|
["yarn.lock", "yarn"],
|
|
@@ -35094,8 +35284,8 @@ var LOCKFILES2 = [
|
|
|
35094
35284
|
];
|
|
35095
35285
|
var SOURCE_DIR_CANDIDATES = ["src", "app", "pages", "components", "lib"];
|
|
35096
35286
|
function detectProject(root) {
|
|
35097
|
-
const pkgPath =
|
|
35098
|
-
if (!
|
|
35287
|
+
const pkgPath = path27.join(root, "package.json");
|
|
35288
|
+
if (!existsSync6(pkgPath)) {
|
|
35099
35289
|
throw new Error(`No package.json found in ${root}. Run revyse init from your project root.`);
|
|
35100
35290
|
}
|
|
35101
35291
|
const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
|
|
@@ -35107,13 +35297,13 @@ function detectProject(root) {
|
|
|
35107
35297
|
} else if (framework !== "nextjs") {
|
|
35108
35298
|
warnings.push(`${framework} support is experimental; Next.js is the supported target.`);
|
|
35109
35299
|
}
|
|
35110
|
-
const managers = LOCKFILES2.filter(([file2]) =>
|
|
35300
|
+
const managers = LOCKFILES2.filter(([file2]) => existsSync6(path27.join(root, file2)));
|
|
35111
35301
|
const packageManager = managers[0]?.[1] ?? "npm";
|
|
35112
35302
|
if (managers.length === 0) warnings.push("No lockfile found; defaulting to npm.");
|
|
35113
35303
|
if (new Set(managers.map(([, m]) => m)).size > 1) {
|
|
35114
35304
|
warnings.push(`Multiple lockfiles found; using ${packageManager}.`);
|
|
35115
35305
|
}
|
|
35116
|
-
const sourceDirs = SOURCE_DIR_CANDIDATES.filter((d) =>
|
|
35306
|
+
const sourceDirs = SOURCE_DIR_CANDIDATES.filter((d) => existsSync6(path27.join(root, d)));
|
|
35117
35307
|
for (const d of aliasedDirs(root)) if (!sourceDirs.includes(d)) sourceDirs.push(d);
|
|
35118
35308
|
if (sourceDirs.length === 0) {
|
|
35119
35309
|
warnings.push("No source directories found; edit sourceDirs in revyse.config.json.");
|
|
@@ -35131,14 +35321,16 @@ function detectProject(root) {
|
|
|
35131
35321
|
dev: pick2("dev"),
|
|
35132
35322
|
typecheck: pick2("typecheck", "type-check", "tsc"),
|
|
35133
35323
|
lint: pick2("lint"),
|
|
35134
|
-
|
|
35324
|
+
// Tests are opt-in (L19, ADR 0042): a suite can take minutes, and a local change should
|
|
35325
|
+
// take seconds. The build stays: it only ever runs in cloud workspaces.
|
|
35135
35326
|
build: pick2("build")
|
|
35136
35327
|
},
|
|
35137
35328
|
exclude: [],
|
|
35138
35329
|
agentPort: 4545,
|
|
35139
35330
|
appOrigins: devOrigins(scripts.dev)
|
|
35140
35331
|
},
|
|
35141
|
-
warnings
|
|
35332
|
+
warnings,
|
|
35333
|
+
optional: { test: pick2("test") }
|
|
35142
35334
|
};
|
|
35143
35335
|
}
|
|
35144
35336
|
function detectFramework(deps) {
|
|
@@ -35186,13 +35378,13 @@ function hasSource(dir, depth = 3) {
|
|
|
35186
35378
|
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
35187
35379
|
if (entry.isFile() && SOURCE_FILE.test(entry.name)) return true;
|
|
35188
35380
|
if (entry.isDirectory() && depth > 1 && !NOT_SOURCE.has(entry.name)) {
|
|
35189
|
-
if (hasSource(
|
|
35381
|
+
if (hasSource(path27.join(dir, entry.name), depth - 1)) return true;
|
|
35190
35382
|
}
|
|
35191
35383
|
}
|
|
35192
35384
|
return false;
|
|
35193
35385
|
}
|
|
35194
35386
|
function aliasedDirs(root) {
|
|
35195
|
-
const file2 = ["tsconfig.json", "jsconfig.json"].map((f) =>
|
|
35387
|
+
const file2 = ["tsconfig.json", "jsconfig.json"].map((f) => path27.join(root, f)).find((f) => existsSync6(f));
|
|
35196
35388
|
if (!file2) return [];
|
|
35197
35389
|
let compilerOptions;
|
|
35198
35390
|
try {
|
|
@@ -35204,15 +35396,15 @@ function aliasedDirs(root) {
|
|
|
35204
35396
|
const baseUrl = compilerOptions?.baseUrl ?? ".";
|
|
35205
35397
|
const dirs = /* @__PURE__ */ new Set();
|
|
35206
35398
|
for (const target of Object.values(paths).flat()) {
|
|
35207
|
-
const rel =
|
|
35399
|
+
const rel = path27.relative(root, path27.join(root, baseUrl, target)).split(path27.sep).join("/");
|
|
35208
35400
|
const top = rel.split("/")[0];
|
|
35209
35401
|
if (rel.startsWith("..") || NOT_SOURCE.has(top)) continue;
|
|
35210
35402
|
if (top === "*" || top === "") {
|
|
35211
35403
|
for (const entry of readdirSync4(root, { withFileTypes: true }))
|
|
35212
35404
|
if (entry.isDirectory() && !NOT_SOURCE.has(entry.name) && !entry.name.startsWith(".")) {
|
|
35213
|
-
if (hasSource(
|
|
35405
|
+
if (hasSource(path27.join(root, entry.name))) dirs.add(entry.name);
|
|
35214
35406
|
}
|
|
35215
|
-
} else if (
|
|
35407
|
+
} else if (existsSync6(path27.join(root, top)) && statSync6(path27.join(root, top)).isDirectory()) {
|
|
35216
35408
|
dirs.add(top);
|
|
35217
35409
|
}
|
|
35218
35410
|
}
|
|
@@ -35254,8 +35446,8 @@ Full policy: ${DATA_HANDLING_URL}
|
|
|
35254
35446
|
`;
|
|
35255
35447
|
|
|
35256
35448
|
// src/next-config.ts
|
|
35257
|
-
import { existsSync as
|
|
35258
|
-
import
|
|
35449
|
+
import { existsSync as existsSync7, readFileSync as readFileSync11 } from "fs";
|
|
35450
|
+
import path28 from "path";
|
|
35259
35451
|
|
|
35260
35452
|
// ../../node_modules/.pnpm/@babel+parser@8.0.7/node_modules/@babel/parser/lib/index.js
|
|
35261
35453
|
var Position = class {
|
|
@@ -51013,7 +51205,7 @@ var NEXT_CONFIG_FILES = [
|
|
|
51013
51205
|
];
|
|
51014
51206
|
function findNextConfig(root) {
|
|
51015
51207
|
for (const name of NEXT_CONFIG_FILES) {
|
|
51016
|
-
if (
|
|
51208
|
+
if (existsSync7(path28.join(root, name))) return name;
|
|
51017
51209
|
}
|
|
51018
51210
|
return null;
|
|
51019
51211
|
}
|
|
@@ -51068,7 +51260,7 @@ ${importLine}`);
|
|
|
51068
51260
|
return { status: "unsupported", reason: "it has no `export default` or `module.exports`" };
|
|
51069
51261
|
}
|
|
51070
51262
|
function readNextConfig(root, file2) {
|
|
51071
|
-
return readFileSync11(
|
|
51263
|
+
return readFileSync11(path28.join(root, file2), "utf8");
|
|
51072
51264
|
}
|
|
51073
51265
|
|
|
51074
51266
|
// src/version.ts
|
|
@@ -51095,9 +51287,9 @@ async function runInit(opts, io) {
|
|
|
51095
51287
|
const ask = (q) => opts.yes ? Promise.resolve(true) : io.confirm(q);
|
|
51096
51288
|
let configured;
|
|
51097
51289
|
let status;
|
|
51098
|
-
if (
|
|
51290
|
+
if (existsSync8(path29.join(root, CONFIG_FILE)) && !opts.force) {
|
|
51099
51291
|
const config3 = projectConfigSchema.parse(
|
|
51100
|
-
JSON.parse(readFileSync12(
|
|
51292
|
+
JSON.parse(readFileSync12(path29.join(root, CONFIG_FILE), "utf8"))
|
|
51101
51293
|
);
|
|
51102
51294
|
io.log(`${CONFIG_FILE} is already here; checking the rest of the setup.`);
|
|
51103
51295
|
if (!config3.dataDisclosure || config3.dataDisclosure.version < DISCLOSURE_VERSION)
|
|
@@ -51134,7 +51326,7 @@ Next: ${steps.join(", then ")}. Then start your app as usual.`
|
|
|
51134
51326
|
async function configure(opts, io, ask) {
|
|
51135
51327
|
const { root } = opts;
|
|
51136
51328
|
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
51137
|
-
const { config: config2, warnings } = detectProject(root);
|
|
51329
|
+
const { config: config2, warnings, optional: optional2 } = detectProject(root);
|
|
51138
51330
|
io.log(`Detected ${config2.framework} (${config2.packageManager})`);
|
|
51139
51331
|
io.log(`Source directories: ${config2.sourceDirs.join(", ")}`);
|
|
51140
51332
|
for (const w of warnings) io.warn(`warning: ${w}`);
|
|
@@ -51178,27 +51370,68 @@ Revyse needs this change to ${file2} (it only has an effect in \`next dev\`):
|
|
|
51178
51370
|
}
|
|
51179
51371
|
}
|
|
51180
51372
|
}
|
|
51181
|
-
writeFileSync6(
|
|
51373
|
+
writeFileSync6(path29.join(root, CONFIG_FILE), `${JSON.stringify(accepted, null, 2)}
|
|
51182
51374
|
`);
|
|
51183
|
-
if (nextConfigEdit) writeFileSync6(
|
|
51375
|
+
if (nextConfigEdit) writeFileSync6(path29.join(root, nextConfigEdit.file), nextConfigEdit.code);
|
|
51184
51376
|
if (ensureGitignored(root))
|
|
51185
51377
|
io.log(`Added ${GITIGNORE_ENTRY} to .gitignore (local change history).`);
|
|
51186
51378
|
io.log(`
|
|
51187
51379
|
Wrote ${CONFIG_FILE}.`);
|
|
51380
|
+
const checks = [
|
|
51381
|
+
...config2.scripts.typecheck ? ["typecheck"] : [],
|
|
51382
|
+
...config2.scripts.lint ? ["lint"] : [],
|
|
51383
|
+
...config2.framework === "nextjs" ? ["the page in a browser"] : []
|
|
51384
|
+
];
|
|
51385
|
+
if (checks.length > 0)
|
|
51386
|
+
io.log(
|
|
51387
|
+
`After each change, Revyse checks ${checks.join(", ").replace(/, ([^,]*)$/, " and $1")}.`
|
|
51388
|
+
);
|
|
51389
|
+
if (optional2.test)
|
|
51390
|
+
io.log(
|
|
51391
|
+
`Your tests (\`${optional2.test}\`) don't run on each change, so changes stay fast. To run them too, add "test": "${optional2.test}" to scripts in ${CONFIG_FILE}.`
|
|
51392
|
+
);
|
|
51188
51393
|
return { config: accepted, nextConfigEdited: Boolean(nextConfigEdit) };
|
|
51189
51394
|
}
|
|
51395
|
+
function olderThan(a2, b) {
|
|
51396
|
+
const parse4 = (v) => /^(\d+)\.(\d+)\.(\d+)/.exec(v)?.slice(1).map(Number);
|
|
51397
|
+
const [x, y] = [parse4(a2), parse4(b)];
|
|
51398
|
+
if (!x || !y) return false;
|
|
51399
|
+
for (let i2 = 0; i2 < 3; i2++) if (x[i2] !== y[i2]) return x[i2] < y[i2];
|
|
51400
|
+
return false;
|
|
51401
|
+
}
|
|
51402
|
+
function installedVersion(root, name) {
|
|
51403
|
+
try {
|
|
51404
|
+
const pkg = JSON.parse(
|
|
51405
|
+
readFileSync12(path29.join(root, "node_modules", name, "package.json"), "utf8")
|
|
51406
|
+
);
|
|
51407
|
+
return pkg.version ?? null;
|
|
51408
|
+
} catch {
|
|
51409
|
+
return null;
|
|
51410
|
+
}
|
|
51411
|
+
}
|
|
51190
51412
|
async function installPackages(root, config2, setup2, io, ask) {
|
|
51191
|
-
const
|
|
51192
|
-
|
|
51193
|
-
|
|
51413
|
+
const listed2 = devDependencies(root);
|
|
51414
|
+
const todo = PACKAGES.flatMap((name) => {
|
|
51415
|
+
const installed = installedVersion(root, name);
|
|
51416
|
+
if (!listed2[name] || !installed) return [{ name, from: null }];
|
|
51417
|
+
return olderThan(installed, VERSION) ? [{ name, from: installed }] : [];
|
|
51418
|
+
});
|
|
51419
|
+
if (todo.length === 0) return true;
|
|
51420
|
+
const specs = todo.map(({ name }) => `${name}@${VERSION}`);
|
|
51194
51421
|
const command = installCommand(config2.packageManager, specs).join(" ");
|
|
51195
|
-
|
|
51196
|
-
|
|
51422
|
+
const installs = todo.filter((t) => !t.from).map((t) => t.name);
|
|
51423
|
+
const updates = todo.filter((t) => t.from).map((t) => `${t.name} from ${t.from} to ${VERSION}`);
|
|
51424
|
+
const what = [
|
|
51425
|
+
...installs.length ? [`install ${installs.join(" and ")} as dev dependencies`] : [],
|
|
51426
|
+
...updates.length ? [`update ${updates.join(" and ")}`] : []
|
|
51427
|
+
].join(", and ");
|
|
51428
|
+
if (!await ask(`${what[0].toUpperCase()}${what.slice(1)} (${command})?`)) {
|
|
51429
|
+
io.log(`Skipped. Run it yourself: ${command}`);
|
|
51197
51430
|
return false;
|
|
51198
51431
|
}
|
|
51199
51432
|
try {
|
|
51200
51433
|
await setup2.install(root, config2.packageManager, specs);
|
|
51201
|
-
io.log(`
|
|
51434
|
+
io.log(`Revyse's packages are at ${VERSION}.`);
|
|
51202
51435
|
return true;
|
|
51203
51436
|
} catch (err) {
|
|
51204
51437
|
io.warn(`Couldn't install them (${err.message}). Run: ${command}`);
|
|
@@ -51230,11 +51463,11 @@ async function connectProject(root, config2, setup2, io, ask) {
|
|
|
51230
51463
|
}
|
|
51231
51464
|
function projectName(root) {
|
|
51232
51465
|
const name = readPackageJson(root)?.name?.replace(/^@[^/]+\//, "").trim();
|
|
51233
|
-
return (name ||
|
|
51466
|
+
return (name || path29.basename(root)).slice(0, 100);
|
|
51234
51467
|
}
|
|
51235
51468
|
function ensureGitignored(root) {
|
|
51236
|
-
const file2 =
|
|
51237
|
-
const current =
|
|
51469
|
+
const file2 = path29.join(root, ".gitignore");
|
|
51470
|
+
const current = existsSync8(file2) ? readFileSync12(file2, "utf8") : "";
|
|
51238
51471
|
const lines = current.split(/\r?\n/).map((l) => l.trim());
|
|
51239
51472
|
if (lines.includes(GITIGNORE_ENTRY) || lines.includes(".revyse")) return false;
|
|
51240
51473
|
const separator = current === "" ? "" : current.endsWith("\n") ? "\n" : "\n\n";
|
|
@@ -51245,7 +51478,7 @@ ${GITIGNORE_ENTRY}
|
|
|
51245
51478
|
}
|
|
51246
51479
|
function readPackageJson(root) {
|
|
51247
51480
|
try {
|
|
51248
|
-
return JSON.parse(readFileSync12(
|
|
51481
|
+
return JSON.parse(readFileSync12(path29.join(root, "package.json"), "utf8"));
|
|
51249
51482
|
} catch {
|
|
51250
51483
|
return null;
|
|
51251
51484
|
}
|
|
@@ -51380,7 +51613,7 @@ var common2 = (cmd) => cmd.requiredOption("--api-url <url>", "Revyse API URL").r
|
|
|
51380
51613
|
function setup(opts) {
|
|
51381
51614
|
const root = process.cwd();
|
|
51382
51615
|
const config2 = projectConfigSchema.parse(
|
|
51383
|
-
JSON.parse(readFileSync13(
|
|
51616
|
+
JSON.parse(readFileSync13(path30.join(root, CONFIG_FILE), "utf8"))
|
|
51384
51617
|
);
|
|
51385
51618
|
const token = opts.token ?? process.env.REVYSE_TOKEN;
|
|
51386
51619
|
if (!token) throw new Error("A project token is required (--token or REVYSE_TOKEN)");
|