sela-core 1.0.7 → 1.0.8
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/ErrorHandler.d.ts +1 -1
- package/dist/cli/ErrorHandler.js +14 -14
- package/dist/cli/commands/bulk.d.ts +1 -1
- package/dist/cli/commands/bulk.d.ts.map +1 -1
- package/dist/cli/commands/bulk.js +51 -33
- package/dist/cli/commands/init.d.ts +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +26 -18
- package/dist/cli/commands/showReport.d.ts +1 -1
- package/dist/cli/commands/showReport.d.ts.map +1 -1
- package/dist/cli/commands/showReport.js +12 -10
- package/dist/cli/commands/status.d.ts +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +8 -8
- package/dist/cli/commands/sync.d.ts +1 -1
- package/dist/cli/commands/sync.d.ts.map +1 -1
- package/dist/cli/commands/sync.js +38 -30
- package/dist/cli/index.js +9 -9
- package/dist/cli/ui/DnaTable.d.ts +1 -1
- package/dist/cli/ui/DnaTable.d.ts.map +1 -1
- package/dist/cli/ui/DnaTable.js +23 -16
- package/dist/cli/ui/RefactorWizard.d.ts +3 -3
- package/dist/cli/ui/RefactorWizard.d.ts.map +1 -1
- package/dist/cli/ui/RefactorWizard.js +108 -82
- package/dist/config/ConfigLoader.d.ts +2 -2
- package/dist/config/ConfigLoader.d.ts.map +1 -1
- package/dist/config/ConfigLoader.js +15 -15
- package/dist/config/DryRunGuard.d.ts +1 -1
- package/dist/config/DryRunGuard.js +7 -7
- package/dist/config/SelaConfig.d.ts +12 -16
- package/dist/config/SelaConfig.d.ts.map +1 -1
- package/dist/config/SelaConfig.js +32 -26
- package/dist/engine/HealingRegistry.d.ts +1 -1
- package/dist/engine/HealingRegistry.js +3 -3
- package/dist/engine/SelaEngine.d.ts +1 -1
- package/dist/engine/SelaEngine.d.ts.map +1 -1
- package/dist/engine/SelaEngine.js +63 -49
- package/dist/errors/SelaError.d.ts +0 -1
- package/dist/errors/SelaError.d.ts.map +1 -1
- package/dist/errors/SelaError.js +6 -7
- package/dist/fixtures/expectProxy.d.ts +1 -1
- package/dist/fixtures/expectProxy.js +11 -11
- package/dist/fixtures/index.js +14 -14
- package/dist/fixtures/proxyTag.d.ts +1 -1
- package/dist/fixtures/proxyTag.js +3 -3
- package/dist/index.d.ts +8 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/reporter/SelaReporter.d.ts +45 -12
- package/dist/reporter/SelaReporter.d.ts.map +1 -1
- package/dist/reporter/SelaReporter.js +139 -39
- package/dist/services/ASTSourceUpdater.d.ts +3 -3
- package/dist/services/ASTSourceUpdater.d.ts.map +1 -1
- package/dist/services/ASTSourceUpdater.js +92 -68
- package/dist/services/ArgumentTypeAnalyzer.js +7 -7
- package/dist/services/BlastRadiusAnalyzer.d.ts.map +1 -1
- package/dist/services/BlastRadiusAnalyzer.js +5 -2
- package/dist/services/ChainValidator.js +22 -22
- package/dist/services/CrossFileHealer.js +7 -7
- package/dist/services/DefinitionTracer.d.ts +1 -1
- package/dist/services/DefinitionTracer.d.ts.map +1 -1
- package/dist/services/DefinitionTracer.js +28 -13
- package/dist/services/DnaEditorService.d.ts +2 -2
- package/dist/services/DnaEditorService.d.ts.map +1 -1
- package/dist/services/DnaEditorService.js +81 -62
- package/dist/services/DnaIndexService.d.ts +1 -1
- package/dist/services/DnaIndexService.d.ts.map +1 -1
- package/dist/services/DnaIndexService.js +14 -14
- package/dist/services/HealReportService.d.ts +6 -6
- package/dist/services/HealReportService.d.ts.map +1 -1
- package/dist/services/HealReportService.js +33 -29
- package/dist/services/HealingAdvisory.js +1 -1
- package/dist/services/HealingCacheService.d.ts +8 -8
- package/dist/services/HealingCacheService.d.ts.map +1 -1
- package/dist/services/HealingCacheService.js +24 -24
- package/dist/services/HealthReportService.d.ts +1 -1
- package/dist/services/HealthReportService.d.ts.map +1 -1
- package/dist/services/HealthReportService.js +25 -25
- package/dist/services/InitializerUpdater.js +2 -2
- package/dist/services/IntentAuditor.d.ts.map +1 -1
- package/dist/services/IntentAuditor.js +32 -21
- package/dist/services/InteractiveReview.d.ts +8 -17
- package/dist/services/InteractiveReview.d.ts.map +1 -1
- package/dist/services/InteractiveReview.js +31 -124
- package/dist/services/LLMService.d.ts +1 -1
- package/dist/services/LLMService.js +16 -16
- package/dist/services/PRAutomationService.d.ts +2 -2
- package/dist/services/PRAutomationService.d.ts.map +1 -1
- package/dist/services/PRAutomationService.js +43 -39
- package/dist/services/PendingPromptLedger.d.ts +2 -2
- package/dist/services/PendingPromptLedger.js +6 -6
- package/dist/services/ReportGenerator.d.ts +2 -2
- package/dist/services/ReportGenerator.d.ts.map +1 -1
- package/dist/services/ReportGenerator.js +5 -5
- package/dist/services/SafetyGuard.d.ts +3 -6
- package/dist/services/SafetyGuard.d.ts.map +1 -1
- package/dist/services/SafetyGuard.js +29 -106
- package/dist/services/SnapshotService.js +5 -5
- package/dist/services/SourceLinkService.d.ts +2 -2
- package/dist/services/SourceLinkService.d.ts.map +1 -1
- package/dist/services/SourceLinkService.js +31 -22
- package/dist/services/SourceUpdater.d.ts +1 -1
- package/dist/services/SourceUpdater.d.ts.map +1 -1
- package/dist/services/SourceUpdater.js +27 -26
- package/dist/services/TemplateDiffService.d.ts.map +1 -1
- package/dist/services/TemplateDiffService.js +18 -15
- package/dist/services/WorkspaceSnapshotService.d.ts +6 -6
- package/dist/services/WorkspaceSnapshotService.d.ts.map +1 -1
- package/dist/services/WorkspaceSnapshotService.js +10 -12
- package/dist/types/index.d.ts +1 -1
- package/dist/utils/IsolatedDiff.d.ts +2 -2
- package/dist/utils/IsolatedDiff.d.ts.map +1 -1
- package/dist/utils/IsolatedDiff.js +13 -10
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type CliErrorCode =
|
|
1
|
+
export type CliErrorCode = "CONFIG_NOT_FOUND" | "SNAPSHOT_DIR_MISSING" | "DNA_NOT_FOUND" | "SOURCE_FILE_MISSING" | "VALIDATION_FAILED" | "AST_ERROR" | "UNKNOWN";
|
|
2
2
|
export interface CliError {
|
|
3
3
|
code: CliErrorCode;
|
|
4
4
|
message: string;
|
package/dist/cli/ErrorHandler.js
CHANGED
|
@@ -17,28 +17,28 @@ const EXIT_CODES = {
|
|
|
17
17
|
UNKNOWN: 1,
|
|
18
18
|
};
|
|
19
19
|
const KNOWN_CODES = new Set([
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
"CONFIG_NOT_FOUND",
|
|
21
|
+
"SNAPSHOT_DIR_MISSING",
|
|
22
|
+
"DNA_NOT_FOUND",
|
|
23
|
+
"SOURCE_FILE_MISSING",
|
|
24
|
+
"VALIDATION_FAILED",
|
|
25
|
+
"AST_ERROR",
|
|
26
|
+
"UNKNOWN",
|
|
27
27
|
]);
|
|
28
28
|
function isKnownCode(val) {
|
|
29
|
-
return typeof val ===
|
|
29
|
+
return typeof val === "string" && KNOWN_CODES.has(val);
|
|
30
30
|
}
|
|
31
31
|
function classify(err) {
|
|
32
32
|
if (err instanceof commander_1.CommanderError) {
|
|
33
33
|
return {
|
|
34
|
-
code:
|
|
34
|
+
code: "UNKNOWN",
|
|
35
35
|
message: err.message,
|
|
36
36
|
exitCode: err.exitCode ?? EXIT_CODES.UNKNOWN,
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
39
|
if (err instanceof Error) {
|
|
40
40
|
const typed = err;
|
|
41
|
-
const code = isKnownCode(typed.code) ? typed.code :
|
|
41
|
+
const code = isKnownCode(typed.code) ? typed.code : "UNKNOWN";
|
|
42
42
|
return {
|
|
43
43
|
code,
|
|
44
44
|
message: err.message,
|
|
@@ -47,13 +47,13 @@ function classify(err) {
|
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
return {
|
|
50
|
-
code:
|
|
50
|
+
code: "UNKNOWN",
|
|
51
51
|
message: String(err),
|
|
52
52
|
exitCode: EXIT_CODES.UNKNOWN,
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
55
|
function handleError(err) {
|
|
56
|
-
// Commander uses exitCode 0 for --help/--version
|
|
56
|
+
// Commander uses exitCode 0 for --help/--version - not real errors
|
|
57
57
|
if (err instanceof commander_1.CommanderError && err.exitCode === 0) {
|
|
58
58
|
process.exit(0);
|
|
59
59
|
}
|
|
@@ -65,6 +65,6 @@ function handleError(err) {
|
|
|
65
65
|
process.exit(cliErr.exitCode);
|
|
66
66
|
}
|
|
67
67
|
function registerGlobalHandlers() {
|
|
68
|
-
process.on(
|
|
69
|
-
process.on(
|
|
68
|
+
process.on("uncaughtException", handleError);
|
|
69
|
+
process.on("unhandledRejection", handleError);
|
|
70
70
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bulk.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/bulk.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"bulk.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/bulk.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0EzC,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CA0IrD"}
|
|
@@ -14,51 +14,61 @@ const SourceLinkService_js_1 = require("../../services/SourceLinkService.js");
|
|
|
14
14
|
const ProgressReporter_js_1 = require("../ui/ProgressReporter.js");
|
|
15
15
|
function renderTargets(targets) {
|
|
16
16
|
const table = new cli_table3_1.default({
|
|
17
|
-
head: [
|
|
17
|
+
head: [
|
|
18
|
+
chalk_1.default.bold("Key"),
|
|
19
|
+
chalk_1.default.bold("Source File"),
|
|
20
|
+
chalk_1.default.bold("Selector"),
|
|
21
|
+
],
|
|
18
22
|
colWidths: [12, 48, 44],
|
|
19
|
-
style: { head: [], border: [
|
|
23
|
+
style: { head: [], border: ["dim"] },
|
|
20
24
|
});
|
|
21
25
|
for (const r of targets) {
|
|
22
|
-
table.push([
|
|
26
|
+
table.push([
|
|
27
|
+
chalk_1.default.dim(r.key.slice(0, 9)),
|
|
28
|
+
r.sourceFile || chalk_1.default.dim("-"),
|
|
29
|
+
chalk_1.default.yellow(r.selector),
|
|
30
|
+
]);
|
|
23
31
|
}
|
|
24
|
-
process.stdout.write(table.toString() +
|
|
32
|
+
process.stdout.write(table.toString() + "\n");
|
|
25
33
|
}
|
|
26
34
|
function renderResults(results) {
|
|
27
|
-
process.stdout.write(
|
|
35
|
+
process.stdout.write("\n");
|
|
28
36
|
const passed = results.filter((r) => r.success);
|
|
29
37
|
const failed = results.filter((r) => !r.success);
|
|
30
38
|
for (const r of passed) {
|
|
31
|
-
const src = r.sourceUpdated ? chalk_1.default.dim(
|
|
32
|
-
process.stdout.write(` ${chalk_1.default.green(
|
|
39
|
+
const src = r.sourceUpdated ? chalk_1.default.dim(" + source") : "";
|
|
40
|
+
process.stdout.write(` ${chalk_1.default.green("✓")} ${chalk_1.default.dim(r.key.slice(0, 9))}${src}\n`);
|
|
33
41
|
}
|
|
34
42
|
for (const r of failed) {
|
|
35
|
-
process.stdout.write(` ${chalk_1.default.red(
|
|
43
|
+
process.stdout.write(` ${chalk_1.default.red("✗")} ${chalk_1.default.dim(r.key.slice(0, 9))} ${chalk_1.default.red(r.reason)}\n`);
|
|
36
44
|
}
|
|
37
45
|
process.stdout.write(`\n ${chalk_1.default.bold(String(passed.length))} updated` +
|
|
38
|
-
(failed.length
|
|
39
|
-
|
|
46
|
+
(failed.length
|
|
47
|
+
? ` ${chalk_1.default.red(String(failed.length) + " failed")}`
|
|
48
|
+
: "") +
|
|
49
|
+
"\n\n");
|
|
40
50
|
}
|
|
41
51
|
function registerBulkUpdate(dna) {
|
|
42
52
|
dna
|
|
43
|
-
.command(
|
|
44
|
-
.description(
|
|
45
|
-
.requiredOption(
|
|
46
|
-
.requiredOption(
|
|
47
|
-
.option(
|
|
48
|
-
.option(
|
|
49
|
-
.option(
|
|
50
|
-
.option(
|
|
51
|
-
.option(
|
|
53
|
+
.command("bulk-update")
|
|
54
|
+
.description("Replace a selector across all matching DNAs")
|
|
55
|
+
.requiredOption("--old <selector>", "Selector to replace")
|
|
56
|
+
.requiredOption("--new <selector>", "Replacement selector")
|
|
57
|
+
.option("--dry-run", "Show affected DNAs without writing")
|
|
58
|
+
.option("--no-source", "Skip .spec.ts updates")
|
|
59
|
+
.option("--confirm", "Auto-confirm without interactive prompt")
|
|
60
|
+
.option("--json", "Emit JSON result instead of formatted output")
|
|
61
|
+
.option("--dir <path>", "Override snapshot directory")
|
|
52
62
|
.action(async (opts) => {
|
|
53
63
|
const config = ConfigLoader_js_1.ConfigLoader.getInstance();
|
|
54
64
|
const snapshotDir = opts.dir ?? config.dnaStoragePath;
|
|
55
65
|
const progress = new ProgressReporter_js_1.ProgressReporter(opts.json);
|
|
56
66
|
const dnaIndex = new DnaIndexService_js_1.DnaIndexService();
|
|
57
|
-
await progress.run(
|
|
67
|
+
await progress.run("Scanning DNA snapshots…", () => dnaIndex.buildIndex(snapshotDir));
|
|
58
68
|
const targets = dnaIndex.findBySelector(opts.old);
|
|
59
69
|
if (targets.length === 0) {
|
|
60
70
|
if (opts.json) {
|
|
61
|
-
process.stdout.write(JSON.stringify({ matched: 0, results: [] }, null, 2) +
|
|
71
|
+
process.stdout.write(JSON.stringify({ matched: 0, results: [] }, null, 2) + "\n");
|
|
62
72
|
}
|
|
63
73
|
else {
|
|
64
74
|
process.stdout.write(`No DNA records match selector: ${chalk_1.default.yellow(opts.old)}\n`);
|
|
@@ -66,51 +76,59 @@ function registerBulkUpdate(dna) {
|
|
|
66
76
|
return;
|
|
67
77
|
}
|
|
68
78
|
if (!opts.json) {
|
|
69
|
-
process.stdout.write(`\n ${chalk_1.default.bold(String(targets.length))} DNA${targets.length > 1 ?
|
|
79
|
+
process.stdout.write(`\n ${chalk_1.default.bold(String(targets.length))} DNA${targets.length > 1 ? "s" : ""} match ` +
|
|
70
80
|
`${chalk_1.default.yellow(opts.old)} → ${chalk_1.default.green(opts.new)}\n\n`);
|
|
71
81
|
renderTargets(targets);
|
|
72
82
|
}
|
|
73
83
|
// ── dry-run: stop here ────────────────────────────────────────────────
|
|
74
84
|
if (opts.dryRun) {
|
|
75
85
|
if (opts.json) {
|
|
76
|
-
process.stdout.write(JSON.stringify({
|
|
86
|
+
process.stdout.write(JSON.stringify({
|
|
87
|
+
dryRun: true,
|
|
88
|
+
matched: targets.length,
|
|
89
|
+
targets: targets.map((t) => t.key),
|
|
90
|
+
}, null, 2) + "\n");
|
|
77
91
|
}
|
|
78
92
|
else {
|
|
79
|
-
process.stdout.write(chalk_1.default.dim(
|
|
93
|
+
process.stdout.write(chalk_1.default.dim("\n [dry-run] No files written.\n\n"));
|
|
80
94
|
}
|
|
81
95
|
return;
|
|
82
96
|
}
|
|
83
|
-
// ── confirm (skip in --json mode
|
|
97
|
+
// ── confirm (skip in --json mode - requires --confirm flag) ──────────
|
|
84
98
|
if (!opts.confirm && !opts.json) {
|
|
85
99
|
const prompt = new enquirer_js_1.Confirm({
|
|
86
|
-
name:
|
|
87
|
-
message: `Update ${targets.length} DNA file${targets.length > 1 ?
|
|
100
|
+
name: "ok",
|
|
101
|
+
message: `Update ${targets.length} DNA file${targets.length > 1 ? "s" : ""}?`,
|
|
88
102
|
});
|
|
89
103
|
const yes = (await prompt.run());
|
|
90
104
|
if (!yes) {
|
|
91
|
-
process.stdout.write(chalk_1.default.dim(
|
|
105
|
+
process.stdout.write(chalk_1.default.dim("Aborted.\n"));
|
|
92
106
|
return;
|
|
93
107
|
}
|
|
94
108
|
}
|
|
95
109
|
else if (!opts.confirm && opts.json) {
|
|
96
110
|
// In JSON/CI mode without --confirm, abort safely rather than hanging on a prompt.
|
|
97
|
-
process.stderr.write(
|
|
111
|
+
process.stderr.write("bulk-update: pass --confirm to skip interactive prompt in --json mode\n");
|
|
98
112
|
process.exit(1);
|
|
99
113
|
}
|
|
100
114
|
// ── write sequentially (backup safety) ───────────────────────────────
|
|
101
115
|
const editor = new DnaEditorService_js_1.DnaEditorService(snapshotDir);
|
|
102
116
|
const sourceLink = opts.source ? new SourceLinkService_js_1.SourceLinkService() : null;
|
|
103
117
|
const results = [];
|
|
104
|
-
await progress.run(`Updating ${targets.length} DNA file${targets.length > 1 ?
|
|
118
|
+
await progress.run(`Updating ${targets.length} DNA file${targets.length > 1 ? "s" : ""}…`, async () => {
|
|
105
119
|
for (const record of targets) {
|
|
106
|
-
const result = {
|
|
120
|
+
const result = {
|
|
121
|
+
key: record.key,
|
|
122
|
+
success: false,
|
|
123
|
+
reason: "",
|
|
124
|
+
};
|
|
107
125
|
try {
|
|
108
126
|
await editor.write(record.key, { selector: opts.new });
|
|
109
127
|
result.success = true;
|
|
110
128
|
if (sourceLink && record.sourceFile && record.sourceLine > 0) {
|
|
111
129
|
const patch = {
|
|
112
130
|
key: record.key,
|
|
113
|
-
field:
|
|
131
|
+
field: "selector",
|
|
114
132
|
oldValue: opts.old,
|
|
115
133
|
newValue: opts.new,
|
|
116
134
|
applySourceUpdate: true,
|
|
@@ -131,7 +149,7 @@ function registerBulkUpdate(dna) {
|
|
|
131
149
|
}
|
|
132
150
|
});
|
|
133
151
|
if (opts.json) {
|
|
134
|
-
process.stdout.write(JSON.stringify({ matched: targets.length, results }, null, 2) +
|
|
152
|
+
process.stdout.write(JSON.stringify({ matched: targets.length, results }, null, 2) + "\n");
|
|
135
153
|
}
|
|
136
154
|
else {
|
|
137
155
|
renderResults(results);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA4BpC,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA4BpC,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAWnD"}
|
|
@@ -36,7 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.registerInit = registerInit;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
|
-
const PACKAGE_NAME =
|
|
39
|
+
const PACKAGE_NAME = "sela-core";
|
|
40
40
|
const CONFIG_TEMPLATE_TS = `import { defineConfig } from '${PACKAGE_NAME}';
|
|
41
41
|
|
|
42
42
|
export default defineConfig({
|
|
@@ -55,30 +55,30 @@ module.exports = defineConfig({
|
|
|
55
55
|
dnaStoragePath: 'sela-snapshots',
|
|
56
56
|
});
|
|
57
57
|
`;
|
|
58
|
-
const REQUIRED_ENV_VARS = [
|
|
58
|
+
const REQUIRED_ENV_VARS = ["ANTHROPIC_API_KEY"];
|
|
59
59
|
function registerInit(program) {
|
|
60
60
|
program
|
|
61
|
-
.command(
|
|
62
|
-
.description(
|
|
63
|
-
.option(
|
|
61
|
+
.command("init")
|
|
62
|
+
.description("Initialize Sela in the current project")
|
|
63
|
+
.option("--no-update-imports", "Skip swapping @playwright/test imports to sela-core")
|
|
64
64
|
.action(async (opts) => {
|
|
65
65
|
await runInit(opts);
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
68
|
async function runInit(opts) {
|
|
69
69
|
const cwd = process.cwd();
|
|
70
|
-
const isTs = fs.existsSync(path.join(cwd,
|
|
71
|
-
const configFile = isTs ?
|
|
70
|
+
const isTs = fs.existsSync(path.join(cwd, "tsconfig.json"));
|
|
71
|
+
const configFile = isTs ? "sela.config.ts" : "sela.config.js";
|
|
72
72
|
const configPath = path.join(cwd, configFile);
|
|
73
73
|
console.error(`\n🚀 Initializing Sela`);
|
|
74
|
-
console.error(` Project type : ${isTs ?
|
|
74
|
+
console.error(` Project type : ${isTs ? "TypeScript" : "JavaScript"}`);
|
|
75
75
|
console.error(` Directory : ${cwd}\n`);
|
|
76
76
|
// ── Config file ──────────────────────────────────────────────────
|
|
77
77
|
if (fs.existsSync(configPath)) {
|
|
78
|
-
console.error(`✅ ${configFile} already exists
|
|
78
|
+
console.error(`✅ ${configFile} already exists - skipping`);
|
|
79
79
|
}
|
|
80
80
|
else {
|
|
81
|
-
fs.writeFileSync(configPath, isTs ? CONFIG_TEMPLATE_TS : CONFIG_TEMPLATE_JS,
|
|
81
|
+
fs.writeFileSync(configPath, isTs ? CONFIG_TEMPLATE_TS : CONFIG_TEMPLATE_JS, "utf8");
|
|
82
82
|
console.error(`✅ Created ${configFile}`);
|
|
83
83
|
}
|
|
84
84
|
// ── Environment variables ────────────────────────────────────────
|
|
@@ -107,21 +107,29 @@ async function runInit(opts) {
|
|
|
107
107
|
console.error(` import { test, expect } from '${PACKAGE_NAME}'\n`);
|
|
108
108
|
}
|
|
109
109
|
function loadDotEnv(cwd) {
|
|
110
|
-
const envPath = path.join(cwd,
|
|
110
|
+
const envPath = path.join(cwd, ".env");
|
|
111
111
|
if (!fs.existsSync(envPath))
|
|
112
112
|
return;
|
|
113
113
|
try {
|
|
114
114
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
115
|
-
const dotenv = require(
|
|
115
|
+
const dotenv = require("dotenv");
|
|
116
116
|
dotenv.config({ path: envPath });
|
|
117
117
|
}
|
|
118
118
|
catch {
|
|
119
|
-
// dotenv missing
|
|
119
|
+
// dotenv missing - skip silently
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
function walkDir(dir, exts) {
|
|
123
123
|
const results = [];
|
|
124
|
-
const ignore = new Set([
|
|
124
|
+
const ignore = new Set([
|
|
125
|
+
"node_modules",
|
|
126
|
+
"dist",
|
|
127
|
+
".git",
|
|
128
|
+
"sela-snapshots",
|
|
129
|
+
"fixwright-snapshots",
|
|
130
|
+
"test-results",
|
|
131
|
+
"playwright-report",
|
|
132
|
+
]);
|
|
125
133
|
function walk(current) {
|
|
126
134
|
if (!fs.existsSync(current))
|
|
127
135
|
return;
|
|
@@ -140,16 +148,16 @@ function walkDir(dir, exts) {
|
|
|
140
148
|
}
|
|
141
149
|
function swapImports(cwd, isTs) {
|
|
142
150
|
const exts = isTs
|
|
143
|
-
? [
|
|
144
|
-
: [
|
|
151
|
+
? [".spec.ts", ".test.ts", ".spec.js", ".test.js"]
|
|
152
|
+
: [".spec.js", ".test.js"];
|
|
145
153
|
const files = walkDir(cwd, exts);
|
|
146
154
|
let swapped = 0;
|
|
147
155
|
const importRegex = /(from\s+|require\s*\(\s*)(['"])@playwright\/test\2/g;
|
|
148
156
|
for (const filePath of files) {
|
|
149
|
-
const content = fs.readFileSync(filePath,
|
|
157
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
150
158
|
const updated = content.replace(importRegex, (_m, prefix, quote) => `${prefix}${quote}${PACKAGE_NAME}${quote}`);
|
|
151
159
|
if (updated !== content) {
|
|
152
|
-
fs.writeFileSync(filePath, updated,
|
|
160
|
+
fs.writeFileSync(filePath, updated, "utf8");
|
|
153
161
|
swapped++;
|
|
154
162
|
}
|
|
155
163
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"showReport.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/showReport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuBzC,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"showReport.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/showReport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuBzC,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAyCzD"}
|
|
@@ -40,9 +40,9 @@ const child_process_1 = require("child_process");
|
|
|
40
40
|
function openInBrowser(absPath) {
|
|
41
41
|
return new Promise((resolve, reject) => {
|
|
42
42
|
const platform = process.platform;
|
|
43
|
-
const cmd = platform ===
|
|
43
|
+
const cmd = platform === "win32"
|
|
44
44
|
? `start "" "${absPath}"`
|
|
45
|
-
: platform ===
|
|
45
|
+
: platform === "darwin"
|
|
46
46
|
? `open "${absPath}"`
|
|
47
47
|
: `xdg-open "${absPath}"`;
|
|
48
48
|
(0, child_process_1.exec)(cmd, (err) => (err ? reject(err) : resolve()));
|
|
@@ -50,21 +50,23 @@ function openInBrowser(absPath) {
|
|
|
50
50
|
}
|
|
51
51
|
function registerShowReport(program) {
|
|
52
52
|
program
|
|
53
|
-
.command(
|
|
54
|
-
.description(
|
|
55
|
-
.option(
|
|
56
|
-
.option(
|
|
53
|
+
.command("show-report")
|
|
54
|
+
.description("Open the latest Sela Insights healing report (sela-report.html) in the default browser")
|
|
55
|
+
.option("--file <path>", "Override path to the report file", "sela-report.html")
|
|
56
|
+
.option("--print", "Print the resolved path instead of opening the browser")
|
|
57
57
|
.action(async (opts) => {
|
|
58
|
-
const requested = opts.file ??
|
|
59
|
-
const abs = path.isAbsolute(requested)
|
|
58
|
+
const requested = opts.file ?? "sela-report.html";
|
|
59
|
+
const abs = path.isAbsolute(requested)
|
|
60
|
+
? requested
|
|
61
|
+
: path.resolve(process.cwd(), requested);
|
|
60
62
|
if (!fs.existsSync(abs)) {
|
|
61
63
|
process.stderr.write(`[sela] No report found at ${abs}\n` +
|
|
62
|
-
` Run your test suite first
|
|
64
|
+
` Run your test suite first - the report is generated automatically when Sela performs at least one heal attempt.\n`);
|
|
63
65
|
process.exit(1);
|
|
64
66
|
return;
|
|
65
67
|
}
|
|
66
68
|
if (opts.print) {
|
|
67
|
-
process.stdout.write(abs +
|
|
69
|
+
process.stdout.write(abs + "\n");
|
|
68
70
|
return;
|
|
69
71
|
}
|
|
70
72
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoCzC,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA6BrD"}
|
|
@@ -5,7 +5,7 @@ const ConfigLoader_js_1 = require("../../config/ConfigLoader.js");
|
|
|
5
5
|
const DnaIndexService_js_1 = require("../../services/DnaIndexService.js");
|
|
6
6
|
const HealthReportService_js_1 = require("../../services/HealthReportService.js");
|
|
7
7
|
const ProgressReporter_js_1 = require("../ui/ProgressReporter.js");
|
|
8
|
-
// Compact JSON shape for CI/CD consumers
|
|
8
|
+
// Compact JSON shape for CI/CD consumers - avoids serialising full DnaRecord arrays.
|
|
9
9
|
function toJsonOutput(report) {
|
|
10
10
|
const { total, orphaned, recentlyHealed, neverHealed, topHealedFiles } = report;
|
|
11
11
|
const brokenTotal = orphaned.length + report.unverifiedHeals.length;
|
|
@@ -29,20 +29,20 @@ function toJsonOutput(report) {
|
|
|
29
29
|
}
|
|
30
30
|
function registerStatus(program) {
|
|
31
31
|
program
|
|
32
|
-
.command(
|
|
33
|
-
.description(
|
|
34
|
-
.option(
|
|
35
|
-
.option(
|
|
32
|
+
.command("status")
|
|
33
|
+
.description("Show ecosystem health report")
|
|
34
|
+
.option("--json", "Emit raw JSON instead of formatted output")
|
|
35
|
+
.option("--dir <path>", "Override snapshot directory")
|
|
36
36
|
.action(async (opts) => {
|
|
37
37
|
const config = ConfigLoader_js_1.ConfigLoader.getInstance();
|
|
38
38
|
const snapshotDir = opts.dir ?? config.dnaStoragePath;
|
|
39
39
|
const progress = new ProgressReporter_js_1.ProgressReporter(opts.json);
|
|
40
40
|
const dnaIndex = new DnaIndexService_js_1.DnaIndexService();
|
|
41
|
-
const index = await progress.run(
|
|
41
|
+
const index = await progress.run("Scanning DNA snapshots…", () => dnaIndex.buildIndex(snapshotDir));
|
|
42
42
|
const healthSvc = new HealthReportService_js_1.HealthReportService();
|
|
43
|
-
const report = await progress.run(
|
|
43
|
+
const report = await progress.run("Generating health report…", async () => healthSvc.generate(index));
|
|
44
44
|
if (opts.json) {
|
|
45
|
-
process.stdout.write(JSON.stringify(toJsonOutput(report), null, 2) +
|
|
45
|
+
process.stdout.write(JSON.stringify(toJsonOutput(report), null, 2) + "\n");
|
|
46
46
|
}
|
|
47
47
|
else {
|
|
48
48
|
process.stdout.write(healthSvc.format(report));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgBzC,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CA+
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgBzC,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CA+LrD"}
|
|
@@ -12,50 +12,56 @@ const SourceLinkService_js_1 = require("../../services/SourceLinkService.js");
|
|
|
12
12
|
const ProgressReporter_js_1 = require("../ui/ProgressReporter.js");
|
|
13
13
|
function registerSyncSource(dna) {
|
|
14
14
|
dna
|
|
15
|
-
.command(
|
|
16
|
-
.description(
|
|
17
|
-
.option(
|
|
18
|
-
.option(
|
|
19
|
-
.option(
|
|
20
|
-
.option(
|
|
15
|
+
.command("sync-source <key>")
|
|
16
|
+
.description("Write current DNA selector back to .spec.ts via AST")
|
|
17
|
+
.option("--locator-index <n>", "Which locator on the line (0-based)", "0")
|
|
18
|
+
.option("--dry-run", "Preview without writing")
|
|
19
|
+
.option("--json", "Emit JSON result")
|
|
20
|
+
.option("--dir <path>", "Override snapshot directory")
|
|
21
21
|
.action(async (key, opts) => {
|
|
22
22
|
const config = ConfigLoader_js_1.ConfigLoader.getInstance();
|
|
23
23
|
const snapshotDir = opts.dir ?? config.dnaStoragePath;
|
|
24
24
|
const locatorIndex = Math.max(0, parseInt(opts.locatorIndex, 10) || 0);
|
|
25
25
|
const progress = new ProgressReporter_js_1.ProgressReporter(opts.json);
|
|
26
26
|
const dnaIndex = new DnaIndexService_js_1.DnaIndexService();
|
|
27
|
-
await progress.run(
|
|
27
|
+
await progress.run("Scanning DNA snapshots…", () => dnaIndex.buildIndex(snapshotDir));
|
|
28
28
|
const record = dnaIndex.getByKey(key);
|
|
29
29
|
if (!record) {
|
|
30
30
|
if (opts.json) {
|
|
31
|
-
process.stdout.write(JSON.stringify({ success: false, reason: `DNA not found: ${key}` }, null, 2) +
|
|
31
|
+
process.stdout.write(JSON.stringify({ success: false, reason: `DNA not found: ${key}` }, null, 2) + "\n");
|
|
32
32
|
process.exit(3);
|
|
33
33
|
}
|
|
34
34
|
const err = new Error(`DNA not found: ${key}`);
|
|
35
|
-
err.code =
|
|
35
|
+
err.code = "DNA_NOT_FOUND";
|
|
36
36
|
throw err;
|
|
37
37
|
}
|
|
38
38
|
if (!record.sourceFile || record.sourceLine <= 0) {
|
|
39
39
|
if (opts.json) {
|
|
40
|
-
process.stdout.write(JSON.stringify({
|
|
40
|
+
process.stdout.write(JSON.stringify({
|
|
41
|
+
success: false,
|
|
42
|
+
reason: `DNA "${key}" has no sourceFile/sourceLine`,
|
|
43
|
+
}, null, 2) + "\n");
|
|
41
44
|
}
|
|
42
45
|
else {
|
|
43
|
-
process.stdout.write(chalk_1.default.yellow(` ⚠ DNA "${key}" has no sourceFile/sourceLine
|
|
46
|
+
process.stdout.write(chalk_1.default.yellow(` ⚠ DNA "${key}" has no sourceFile/sourceLine - cannot sync.\n`));
|
|
44
47
|
}
|
|
45
48
|
return;
|
|
46
49
|
}
|
|
47
50
|
const sourceLink = new SourceLinkService_js_1.SourceLinkService();
|
|
48
|
-
const locators = await progress.run(
|
|
51
|
+
const locators = await progress.run("Analyzing AST for locator update…", async () => sourceLink.enumerateLocatorsOnLine(record.sourceFile, record.sourceLine));
|
|
49
52
|
const target = locators[locatorIndex] ?? locators[0];
|
|
50
53
|
const relPath = path_1.default.relative(process.cwd(), record.sourceFile);
|
|
51
54
|
if (!opts.json) {
|
|
52
|
-
process.stdout.write(
|
|
53
|
-
process.stdout.write(` ${chalk_1.default.bold(
|
|
54
|
-
process.stdout.write(` ${chalk_1.default.bold(
|
|
55
|
+
process.stdout.write("\n");
|
|
56
|
+
process.stdout.write(` ${chalk_1.default.bold("DNA selector:")} ${chalk_1.default.green(record.selector)}\n`);
|
|
57
|
+
process.stdout.write(` ${chalk_1.default.bold("Source file:")} ${relPath}:${record.sourceLine}\n`);
|
|
55
58
|
}
|
|
56
59
|
if (!target) {
|
|
57
60
|
if (opts.json) {
|
|
58
|
-
process.stdout.write(JSON.stringify({
|
|
61
|
+
process.stdout.write(JSON.stringify({
|
|
62
|
+
success: false,
|
|
63
|
+
reason: `No locator at index ${locatorIndex} on line ${record.sourceLine}`,
|
|
64
|
+
}, null, 2) + "\n");
|
|
59
65
|
}
|
|
60
66
|
else {
|
|
61
67
|
process.stdout.write(chalk_1.default.yellow(` ⚠ No locator found at index ${locatorIndex} on line ${record.sourceLine}.\n\n`));
|
|
@@ -63,16 +69,16 @@ function registerSyncSource(dna) {
|
|
|
63
69
|
return;
|
|
64
70
|
}
|
|
65
71
|
if (!opts.json) {
|
|
66
|
-
process.stdout.write(` ${chalk_1.default.bold(
|
|
67
|
-
process.stdout.write(` ${chalk_1.default.bold(
|
|
68
|
-
process.stdout.write(` ${chalk_1.default.bold(
|
|
72
|
+
process.stdout.write(` ${chalk_1.default.bold("Locator found:")} [${locatorIndex}] ${chalk_1.default.yellow(target.preview)}\n`);
|
|
73
|
+
process.stdout.write(` ${chalk_1.default.bold("Current value:")} ${chalk_1.default.red(target.selector)}\n`);
|
|
74
|
+
process.stdout.write(` ${chalk_1.default.bold("Will become:")} ${chalk_1.default.green(record.selector)}\n\n`);
|
|
69
75
|
}
|
|
70
76
|
if (target.selector === record.selector) {
|
|
71
77
|
if (opts.json) {
|
|
72
|
-
process.stdout.write(JSON.stringify({ success: true, reason:
|
|
78
|
+
process.stdout.write(JSON.stringify({ success: true, reason: "Already in sync - nothing to do" }, null, 2) + "\n");
|
|
73
79
|
}
|
|
74
80
|
else {
|
|
75
|
-
process.stdout.write(chalk_1.default.dim(
|
|
81
|
+
process.stdout.write(chalk_1.default.dim(" Source already matches DNA selector. Nothing to do.\n\n"));
|
|
76
82
|
}
|
|
77
83
|
return;
|
|
78
84
|
}
|
|
@@ -86,36 +92,38 @@ function registerSyncSource(dna) {
|
|
|
86
92
|
locatorIndex,
|
|
87
93
|
oldSelector: target.selector,
|
|
88
94
|
newSelector: record.selector,
|
|
89
|
-
}, null, 2) +
|
|
95
|
+
}, null, 2) + "\n");
|
|
90
96
|
}
|
|
91
97
|
else {
|
|
92
|
-
process.stdout.write(chalk_1.default.dim(
|
|
98
|
+
process.stdout.write(chalk_1.default.dim(" [dry-run] No files written.\n\n"));
|
|
93
99
|
}
|
|
94
100
|
return;
|
|
95
101
|
}
|
|
96
102
|
const patch = {
|
|
97
103
|
key,
|
|
98
|
-
field:
|
|
104
|
+
field: "selector",
|
|
99
105
|
oldValue: target.selector,
|
|
100
106
|
newValue: record.selector,
|
|
101
107
|
applySourceUpdate: true,
|
|
102
108
|
locatorIndex,
|
|
103
109
|
};
|
|
104
|
-
const result = await progress.run(
|
|
110
|
+
const result = await progress.run("Applying AST source update…", () => sourceLink.applySourceUpdate(record, patch));
|
|
105
111
|
if (opts.json) {
|
|
106
|
-
process.stdout.write(JSON.stringify(result, null, 2) +
|
|
112
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
107
113
|
if (!result.success)
|
|
108
114
|
process.exit(5);
|
|
109
115
|
}
|
|
110
116
|
else {
|
|
111
117
|
if (result.success) {
|
|
112
|
-
const lineInfo = result.lineUpdated
|
|
113
|
-
|
|
118
|
+
const lineInfo = result.lineUpdated
|
|
119
|
+
? ` (line ${result.lineUpdated})`
|
|
120
|
+
: "";
|
|
121
|
+
process.stdout.write(` ${chalk_1.default.green("✓")} Source updated${lineInfo}\n\n`);
|
|
114
122
|
}
|
|
115
123
|
else {
|
|
116
|
-
process.stdout.write(` ${chalk_1.default.red(
|
|
124
|
+
process.stdout.write(` ${chalk_1.default.red("✗")} Source update failed: ${result.reason}\n\n`);
|
|
117
125
|
const err = new Error(result.reason);
|
|
118
|
-
err.code =
|
|
126
|
+
err.code = "AST_ERROR";
|
|
119
127
|
throw err;
|
|
120
128
|
}
|
|
121
129
|
}
|