sela-core 0.1.0-alpha.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/README.md +103 -0
- package/bin/sela.js +3 -0
- package/dist/cli/ErrorHandler.d.ts +10 -0
- package/dist/cli/ErrorHandler.d.ts.map +1 -0
- package/dist/cli/ErrorHandler.js +70 -0
- package/dist/cli/commands/bulk.d.ts +3 -0
- package/dist/cli/commands/bulk.d.ts.map +1 -0
- package/dist/cli/commands/bulk.js +158 -0
- package/dist/cli/commands/find.d.ts +3 -0
- package/dist/cli/commands/find.d.ts.map +1 -0
- package/dist/cli/commands/find.js +51 -0
- package/dist/cli/commands/init.d.ts +3 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +166 -0
- package/dist/cli/commands/list.d.ts +3 -0
- package/dist/cli/commands/list.d.ts.map +1 -0
- package/dist/cli/commands/list.js +56 -0
- package/dist/cli/commands/merge.d.ts +13 -0
- package/dist/cli/commands/merge.d.ts.map +1 -0
- package/dist/cli/commands/merge.js +119 -0
- package/dist/cli/commands/refactor.d.ts +3 -0
- package/dist/cli/commands/refactor.d.ts.map +1 -0
- package/dist/cli/commands/refactor.js +30 -0
- package/dist/cli/commands/showReport.d.ts +3 -0
- package/dist/cli/commands/showReport.d.ts.map +1 -0
- package/dist/cli/commands/showReport.js +82 -0
- package/dist/cli/commands/status.d.ts +3 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +51 -0
- package/dist/cli/commands/sync.d.ts +3 -0
- package/dist/cli/commands/sync.d.ts.map +1 -0
- package/dist/cli/commands/sync.js +131 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +94 -0
- package/dist/cli/ui/DnaTable.d.ts +3 -0
- package/dist/cli/ui/DnaTable.d.ts.map +1 -0
- package/dist/cli/ui/DnaTable.js +77 -0
- package/dist/cli/ui/LocatorPicker.d.ts +8 -0
- package/dist/cli/ui/LocatorPicker.d.ts.map +1 -0
- package/dist/cli/ui/LocatorPicker.js +33 -0
- package/dist/cli/ui/ProgressReporter.d.ts +11 -0
- package/dist/cli/ui/ProgressReporter.d.ts.map +1 -0
- package/dist/cli/ui/ProgressReporter.js +33 -0
- package/dist/cli/ui/RefactorWizard.d.ts +26 -0
- package/dist/cli/ui/RefactorWizard.d.ts.map +1 -0
- package/dist/cli/ui/RefactorWizard.js +295 -0
- package/dist/config/ConfigLoader.d.ts +16 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -0
- package/dist/config/ConfigLoader.js +185 -0
- package/dist/config/DryRunGuard.d.ts +14 -0
- package/dist/config/DryRunGuard.d.ts.map +1 -0
- package/dist/config/DryRunGuard.js +80 -0
- package/dist/config/PathScopeGuard.d.ts +42 -0
- package/dist/config/PathScopeGuard.d.ts.map +1 -0
- package/dist/config/PathScopeGuard.js +192 -0
- package/dist/config/SelaConfig.d.ts +178 -0
- package/dist/config/SelaConfig.d.ts.map +1 -0
- package/dist/config/SelaConfig.js +141 -0
- package/dist/config/defineConfig.d.ts +3 -0
- package/dist/config/defineConfig.d.ts.map +1 -0
- package/dist/config/defineConfig.js +9 -0
- package/dist/engine/FixwrightEngine.d.ts +24 -0
- package/dist/engine/FixwrightEngine.d.ts.map +1 -0
- package/dist/engine/FixwrightEngine.js +403 -0
- package/dist/engine/HealingRegistry.d.ts +40 -0
- package/dist/engine/HealingRegistry.d.ts.map +1 -0
- package/dist/engine/HealingRegistry.js +99 -0
- package/dist/engine/SelaEngine.d.ts +57 -0
- package/dist/engine/SelaEngine.d.ts.map +1 -0
- package/dist/engine/SelaEngine.js +1146 -0
- package/dist/engine/singleton.d.ts +3 -0
- package/dist/engine/singleton.d.ts.map +1 -0
- package/dist/engine/singleton.js +5 -0
- package/dist/errors/SelaError.d.ts +146 -0
- package/dist/errors/SelaError.d.ts.map +1 -0
- package/dist/errors/SelaError.js +168 -0
- package/dist/fixtures/expectProxy.d.ts +12 -0
- package/dist/fixtures/expectProxy.d.ts.map +1 -0
- package/dist/fixtures/expectProxy.js +246 -0
- package/dist/fixtures/index.d.ts +30 -0
- package/dist/fixtures/index.d.ts.map +1 -0
- package/dist/fixtures/index.js +765 -0
- package/dist/fixtures/moduleExpect.d.ts +2 -0
- package/dist/fixtures/moduleExpect.d.ts.map +1 -0
- package/dist/fixtures/moduleExpect.js +54 -0
- package/dist/fixtures/proxyTag.d.ts +12 -0
- package/dist/fixtures/proxyTag.d.ts.map +1 -0
- package/dist/fixtures/proxyTag.js +45 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/reporter/SelaReporter.d.ts +165 -0
- package/dist/reporter/SelaReporter.d.ts.map +1 -0
- package/dist/reporter/SelaReporter.js +517 -0
- package/dist/services/ASTSourceUpdater.d.ts +143 -0
- package/dist/services/ASTSourceUpdater.d.ts.map +1 -0
- package/dist/services/ASTSourceUpdater.js +3514 -0
- package/dist/services/AnchorResolver.d.ts +157 -0
- package/dist/services/AnchorResolver.d.ts.map +1 -0
- package/dist/services/AnchorResolver.js +289 -0
- package/dist/services/ArgumentTypeAnalyzer.d.ts +26 -0
- package/dist/services/ArgumentTypeAnalyzer.d.ts.map +1 -0
- package/dist/services/ArgumentTypeAnalyzer.js +92 -0
- package/dist/services/BlastRadiusAnalyzer.d.ts +15 -0
- package/dist/services/BlastRadiusAnalyzer.d.ts.map +1 -0
- package/dist/services/BlastRadiusAnalyzer.js +107 -0
- package/dist/services/ChainValidator.d.ts +76 -0
- package/dist/services/ChainValidator.d.ts.map +1 -0
- package/dist/services/ChainValidator.js +570 -0
- package/dist/services/CrossFileHealer.d.ts +6 -0
- package/dist/services/CrossFileHealer.d.ts.map +1 -0
- package/dist/services/CrossFileHealer.js +135 -0
- package/dist/services/DecisionEngine.d.ts +51 -0
- package/dist/services/DecisionEngine.d.ts.map +1 -0
- package/dist/services/DecisionEngine.js +260 -0
- package/dist/services/DefinitionTracer.d.ts +41 -0
- package/dist/services/DefinitionTracer.d.ts.map +1 -0
- package/dist/services/DefinitionTracer.js +366 -0
- package/dist/services/DnaEditorService.d.ts +31 -0
- package/dist/services/DnaEditorService.d.ts.map +1 -0
- package/dist/services/DnaEditorService.js +217 -0
- package/dist/services/DnaIndexService.d.ts +24 -0
- package/dist/services/DnaIndexService.d.ts.map +1 -0
- package/dist/services/DnaIndexService.js +131 -0
- package/dist/services/HealBudget.d.ts +32 -0
- package/dist/services/HealBudget.d.ts.map +1 -0
- package/dist/services/HealBudget.js +85 -0
- package/dist/services/HealReportService.d.ts +161 -0
- package/dist/services/HealReportService.d.ts.map +1 -0
- package/dist/services/HealReportService.js +1039 -0
- package/dist/services/HealingAdvisory.d.ts +30 -0
- package/dist/services/HealingAdvisory.d.ts.map +1 -0
- package/dist/services/HealingAdvisory.js +51 -0
- package/dist/services/HealingCacheService.d.ts +135 -0
- package/dist/services/HealingCacheService.d.ts.map +1 -0
- package/dist/services/HealingCacheService.js +404 -0
- package/dist/services/HealthReportService.d.ts +10 -0
- package/dist/services/HealthReportService.d.ts.map +1 -0
- package/dist/services/HealthReportService.js +84 -0
- package/dist/services/InitializerUpdater.d.ts +16 -0
- package/dist/services/InitializerUpdater.d.ts.map +1 -0
- package/dist/services/InitializerUpdater.js +73 -0
- package/dist/services/IntentAuditor.d.ts +56 -0
- package/dist/services/IntentAuditor.d.ts.map +1 -0
- package/dist/services/IntentAuditor.js +326 -0
- package/dist/services/InteractiveReview.d.ts +15 -0
- package/dist/services/InteractiveReview.d.ts.map +1 -0
- package/dist/services/InteractiveReview.js +128 -0
- package/dist/services/LLMService.d.ts +121 -0
- package/dist/services/LLMService.d.ts.map +1 -0
- package/dist/services/LLMService.js +475 -0
- package/dist/services/MutationApplier.d.ts +69 -0
- package/dist/services/MutationApplier.d.ts.map +1 -0
- package/dist/services/MutationApplier.js +360 -0
- package/dist/services/PRAutomationService.d.ts +50 -0
- package/dist/services/PRAutomationService.d.ts.map +1 -0
- package/dist/services/PRAutomationService.js +760 -0
- package/dist/services/PendingPromptLedger.d.ts +51 -0
- package/dist/services/PendingPromptLedger.d.ts.map +1 -0
- package/dist/services/PendingPromptLedger.js +205 -0
- package/dist/services/ReportGenerator.d.ts +156 -0
- package/dist/services/ReportGenerator.d.ts.map +1 -0
- package/dist/services/ReportGenerator.js +278 -0
- package/dist/services/ReportMergeService.d.ts +95 -0
- package/dist/services/ReportMergeService.d.ts.map +1 -0
- package/dist/services/ReportMergeService.js +0 -0
- package/dist/services/SafetyGuard.d.ts +112 -0
- package/dist/services/SafetyGuard.d.ts.map +1 -0
- package/dist/services/SafetyGuard.js +87 -0
- package/dist/services/Sanitizer.d.ts +28 -0
- package/dist/services/Sanitizer.d.ts.map +1 -0
- package/dist/services/Sanitizer.js +142 -0
- package/dist/services/SanitizingAnthropic.d.ts +19 -0
- package/dist/services/SanitizingAnthropic.d.ts.map +1 -0
- package/dist/services/SanitizingAnthropic.js +84 -0
- package/dist/services/SelectorSanitizer.d.ts +52 -0
- package/dist/services/SelectorSanitizer.d.ts.map +1 -0
- package/dist/services/SelectorSanitizer.js +318 -0
- package/dist/services/SnapshotService.d.ts +11 -0
- package/dist/services/SnapshotService.d.ts.map +1 -0
- package/dist/services/SnapshotService.js +352 -0
- package/dist/services/SourceLinkService.d.ts +26 -0
- package/dist/services/SourceLinkService.d.ts.map +1 -0
- package/dist/services/SourceLinkService.js +165 -0
- package/dist/services/SourceUpdater.d.ts +45 -0
- package/dist/services/SourceUpdater.d.ts.map +1 -0
- package/dist/services/SourceUpdater.js +1091 -0
- package/dist/services/TemplateDiffService.d.ts +25 -0
- package/dist/services/TemplateDiffService.d.ts.map +1 -0
- package/dist/services/TemplateDiffService.js +334 -0
- package/dist/services/TraceBackEngine.d.ts +67 -0
- package/dist/services/TraceBackEngine.d.ts.map +1 -0
- package/dist/services/TraceBackEngine.js +672 -0
- package/dist/services/WorkspaceSnapshotService.d.ts +71 -0
- package/dist/services/WorkspaceSnapshotService.d.ts.map +1 -0
- package/dist/services/WorkspaceSnapshotService.js +200 -0
- package/dist/services/safety/PipelineContext.d.ts +63 -0
- package/dist/services/safety/PipelineContext.d.ts.map +1 -0
- package/dist/services/safety/PipelineContext.js +34 -0
- package/dist/services/safety/roleClassifier.d.ts +4 -0
- package/dist/services/safety/roleClassifier.d.ts.map +1 -0
- package/dist/services/safety/roleClassifier.js +233 -0
- package/dist/services/safety/rules/ConfidenceTierRule.d.ts +3 -0
- package/dist/services/safety/rules/ConfidenceTierRule.d.ts.map +1 -0
- package/dist/services/safety/rules/ConfidenceTierRule.js +32 -0
- package/dist/services/safety/rules/DangerousSinkRule.d.ts +10 -0
- package/dist/services/safety/rules/DangerousSinkRule.d.ts.map +1 -0
- package/dist/services/safety/rules/DangerousSinkRule.js +53 -0
- package/dist/services/safety/rules/FunctionalRoleRule.d.ts +3 -0
- package/dist/services/safety/rules/FunctionalRoleRule.d.ts.map +1 -0
- package/dist/services/safety/rules/FunctionalRoleRule.js +64 -0
- package/dist/services/safety/rules/HardConfidenceRule.d.ts +3 -0
- package/dist/services/safety/rules/HardConfidenceRule.d.ts.map +1 -0
- package/dist/services/safety/rules/HardConfidenceRule.js +22 -0
- package/dist/services/safety/rules/IntentAuditorRule.d.ts +3 -0
- package/dist/services/safety/rules/IntentAuditorRule.d.ts.map +1 -0
- package/dist/services/safety/rules/IntentAuditorRule.js +97 -0
- package/dist/services/safety/rules/SemanticOppositeRule.d.ts +3 -0
- package/dist/services/safety/rules/SemanticOppositeRule.d.ts.map +1 -0
- package/dist/services/safety/rules/SemanticOppositeRule.js +30 -0
- package/dist/services/safety/rules/StructuralOnlyRule.d.ts +3 -0
- package/dist/services/safety/rules/StructuralOnlyRule.d.ts.map +1 -0
- package/dist/services/safety/rules/StructuralOnlyRule.js +38 -0
- package/dist/services/safety/rules/VisibilityRule.d.ts +3 -0
- package/dist/services/safety/rules/VisibilityRule.d.ts.map +1 -0
- package/dist/services/safety/rules/VisibilityRule.js +53 -0
- package/dist/services/safety/rules/ZeroTrustRule.d.ts +3 -0
- package/dist/services/safety/rules/ZeroTrustRule.d.ts.map +1 -0
- package/dist/services/safety/rules/ZeroTrustRule.js +37 -0
- package/dist/services/safety/rules/index.d.ts +3 -0
- package/dist/services/safety/rules/index.d.ts.map +1 -0
- package/dist/services/safety/rules/index.js +34 -0
- package/dist/services/types.d.ts +59 -0
- package/dist/services/types.d.ts.map +1 -0
- package/dist/services/types.js +2 -0
- package/dist/storage/SnapshotManager.d.ts +5 -0
- package/dist/storage/SnapshotManager.d.ts.map +1 -0
- package/dist/storage/SnapshotManager.js +32 -0
- package/dist/types/index.d.ts +95 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/utils/DOMUtils.d.ts +49 -0
- package/dist/utils/DOMUtils.d.ts.map +1 -0
- package/dist/utils/DOMUtils.js +466 -0
- package/dist/utils/FileLock.d.ts +44 -0
- package/dist/utils/FileLock.d.ts.map +1 -0
- package/dist/utils/FileLock.js +189 -0
- package/dist/utils/IsolatedDiff.d.ts +45 -0
- package/dist/utils/IsolatedDiff.d.ts.map +1 -0
- package/dist/utils/IsolatedDiff.js +382 -0
- package/dist/utils/StackUtils.d.ts +11 -0
- package/dist/utils/StackUtils.d.ts.map +1 -0
- package/dist/utils/StackUtils.js +120 -0
- package/dist/utils/SummaryTable.d.ts +17 -0
- package/dist/utils/SummaryTable.d.ts.map +1 -0
- package/dist/utils/SummaryTable.js +37 -0
- package/dist/utils/atomicWrite.d.ts +9 -0
- package/dist/utils/atomicWrite.d.ts.map +1 -0
- package/dist/utils/atomicWrite.js +83 -0
- package/dist/utils/logger.d.ts +59 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +123 -0
- package/dist/vendor/enquirer.d.ts +33 -0
- package/dist/vendor/enquirer.d.ts.map +1 -0
- package/dist/vendor/enquirer.js +11 -0
- package/package.json +71 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { LocatorEnumeration } from '../../services/SourceLinkService.js';
|
|
2
|
+
/**
|
|
3
|
+
* Presents an interactive list when a source line contains multiple locators.
|
|
4
|
+
* Returns the 0-based index of the user's selection.
|
|
5
|
+
* Pre-selects the entry whose selector matches currentSelector (normalized comparison).
|
|
6
|
+
*/
|
|
7
|
+
export declare function pickLocator(locators: LocatorEnumeration[], currentSelector: string): Promise<number>;
|
|
8
|
+
//# sourceMappingURL=LocatorPicker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocatorPicker.d.ts","sourceRoot":"","sources":["../../../src/cli/ui/LocatorPicker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAE9E;;;;GAIG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,kBAAkB,EAAE,EAC9B,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,MAAM,CAAC,CAsBjB"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.pickLocator = pickLocator;
|
|
7
|
+
const enquirer_js_1 = require("../../vendor/enquirer.js");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
/**
|
|
10
|
+
* Presents an interactive list when a source line contains multiple locators.
|
|
11
|
+
* Returns the 0-based index of the user's selection.
|
|
12
|
+
* Pre-selects the entry whose selector matches currentSelector (normalized comparison).
|
|
13
|
+
*/
|
|
14
|
+
async function pickLocator(locators, currentSelector) {
|
|
15
|
+
const norm = currentSelector.trim();
|
|
16
|
+
const defaultIdx = locators.findIndex((l) => l.selector.trim() === norm);
|
|
17
|
+
const choices = locators.map((l) => {
|
|
18
|
+
const isCurrent = l.selector.trim() === norm;
|
|
19
|
+
const hint = isCurrent ? chalk_1.default.dim(' ← current DNA selector') : '';
|
|
20
|
+
return {
|
|
21
|
+
name: String(l.index),
|
|
22
|
+
message: `[${l.index}] ${chalk_1.default.yellow(l.preview)}${hint}`,
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
const prompt = new enquirer_js_1.Select({
|
|
26
|
+
name: 'locatorIndex',
|
|
27
|
+
message: `Line has ${locators.length} locators. Which are you refactoring?`,
|
|
28
|
+
choices,
|
|
29
|
+
initial: defaultIdx >= 0 ? defaultIdx : 0,
|
|
30
|
+
});
|
|
31
|
+
const result = (await prompt.run());
|
|
32
|
+
return parseInt(result, 10);
|
|
33
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps ora spinners. Pass silent=true (e.g. when --json active) to skip all
|
|
3
|
+
* spinner output and run the task directly. All spinner output goes to stderr
|
|
4
|
+
* so stdout stays pipe-clean.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ProgressReporter {
|
|
7
|
+
private silent;
|
|
8
|
+
constructor(silent?: boolean);
|
|
9
|
+
run<T>(label: string, fn: () => Promise<T>): Promise<T>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=ProgressReporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProgressReporter.d.ts","sourceRoot":"","sources":["../../../src/cli/ui/ProgressReporter.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAU;gBAEZ,MAAM,UAAQ;IAIpB,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAY9D"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ProgressReporter = void 0;
|
|
7
|
+
const ora_1 = __importDefault(require("ora"));
|
|
8
|
+
/**
|
|
9
|
+
* Wraps ora spinners. Pass silent=true (e.g. when --json active) to skip all
|
|
10
|
+
* spinner output and run the task directly. All spinner output goes to stderr
|
|
11
|
+
* so stdout stays pipe-clean.
|
|
12
|
+
*/
|
|
13
|
+
class ProgressReporter {
|
|
14
|
+
silent;
|
|
15
|
+
constructor(silent = false) {
|
|
16
|
+
this.silent = silent;
|
|
17
|
+
}
|
|
18
|
+
async run(label, fn) {
|
|
19
|
+
if (this.silent)
|
|
20
|
+
return fn();
|
|
21
|
+
const spinner = (0, ora_1.default)({ text: label, stream: process.stderr }).start();
|
|
22
|
+
try {
|
|
23
|
+
const result = await fn();
|
|
24
|
+
spinner.succeed();
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
spinner.fail();
|
|
29
|
+
throw err;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.ProgressReporter = ProgressReporter;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { IDnaIndexService } from "../../services/DnaIndexService.js";
|
|
2
|
+
import type { IDnaEditorService } from "../../services/DnaEditorService.js";
|
|
3
|
+
import type { ISourceLinkService } from "../../services/SourceLinkService.js";
|
|
4
|
+
export interface WizardOptions {
|
|
5
|
+
noSource?: boolean;
|
|
6
|
+
dryRun?: boolean;
|
|
7
|
+
}
|
|
8
|
+
interface WizardServices {
|
|
9
|
+
dnaIndex: IDnaIndexService;
|
|
10
|
+
dnaEditor: IDnaEditorService;
|
|
11
|
+
sourceLink: ISourceLinkService;
|
|
12
|
+
}
|
|
13
|
+
export declare class RefactorWizard {
|
|
14
|
+
private readonly svc;
|
|
15
|
+
private readonly opts;
|
|
16
|
+
constructor(svc: WizardServices, opts?: WizardOptions);
|
|
17
|
+
run(initialKey?: string): Promise<void>;
|
|
18
|
+
private resolveKey;
|
|
19
|
+
private selectDna;
|
|
20
|
+
private selectField;
|
|
21
|
+
private editValue;
|
|
22
|
+
private confirm;
|
|
23
|
+
private buildDnaPatch;
|
|
24
|
+
}
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=RefactorWizard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RefactorWizard.d.ts","sourceRoot":"","sources":["../../../src/cli/ui/RefactorWizard.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAC1E,OAAO,KAAK,EACV,iBAAiB,EAElB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAM9E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,UAAU,cAAc;IACtB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,SAAS,EAAE,iBAAiB,CAAC;IAC7B,UAAU,EAAE,kBAAkB,CAAC;CAChC;AA6HD,qBAAa,cAAc;IAEvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBADJ,GAAG,EAAE,cAAc,EACnB,IAAI,GAAE,aAAkB;IAGrC,GAAG,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwH7C,OAAO,CAAC,UAAU;YAUJ,SAAS;YAwBT,WAAW;YA+BX,SAAS;YA4BT,OAAO;IAKrB,OAAO,CAAC,aAAa;CAMtB"}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.RefactorWizard = void 0;
|
|
7
|
+
const enquirer_js_1 = require("../../vendor/enquirer.js");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const LocatorPicker_js_1 = require("./LocatorPicker.js");
|
|
11
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
12
|
+
function formatValue(v) {
|
|
13
|
+
if (v === null || v === undefined)
|
|
14
|
+
return chalk_1.default.dim("null");
|
|
15
|
+
if (typeof v === "object")
|
|
16
|
+
return JSON.stringify(v, null, 2);
|
|
17
|
+
return String(v);
|
|
18
|
+
}
|
|
19
|
+
function parseInput(field, raw) {
|
|
20
|
+
if (field === "text")
|
|
21
|
+
return raw.trim() === "" ? null : raw;
|
|
22
|
+
if (field === "attributes" || field === "anchors") {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(raw);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return raw;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return raw;
|
|
31
|
+
}
|
|
32
|
+
function getFieldValue(field, record, snap) {
|
|
33
|
+
switch (field) {
|
|
34
|
+
case "selector":
|
|
35
|
+
return record.selector;
|
|
36
|
+
case "text":
|
|
37
|
+
return record.text;
|
|
38
|
+
case "tagName":
|
|
39
|
+
return record.tagName;
|
|
40
|
+
case "attributes":
|
|
41
|
+
return snap.attributes;
|
|
42
|
+
case "anchors":
|
|
43
|
+
return snap.anchors;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function validateField(field, value) {
|
|
47
|
+
const errs = [];
|
|
48
|
+
switch (field) {
|
|
49
|
+
case "selector":
|
|
50
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
51
|
+
errs.push("selector must not be empty");
|
|
52
|
+
if (value === "undefined")
|
|
53
|
+
errs.push('selector must not be the string "undefined"');
|
|
54
|
+
if (typeof value === "string" && /[\r\n]/.test(value))
|
|
55
|
+
errs.push("selector must not contain raw newlines");
|
|
56
|
+
break;
|
|
57
|
+
case "text":
|
|
58
|
+
if (value !== null && typeof value !== "string")
|
|
59
|
+
errs.push("text must be a string or null");
|
|
60
|
+
if (typeof value === "string" && value.length >= 2000)
|
|
61
|
+
errs.push("text must be fewer than 2000 characters");
|
|
62
|
+
break;
|
|
63
|
+
case "tagName":
|
|
64
|
+
if (typeof value !== "string" || !/^[a-z][a-z0-9-]*$/.test(value))
|
|
65
|
+
errs.push("tagName must match /^[a-z][a-z0-9-]*$/");
|
|
66
|
+
break;
|
|
67
|
+
case "attributes":
|
|
68
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
69
|
+
errs.push("attributes must be a plain object (JSON)");
|
|
70
|
+
break;
|
|
71
|
+
case "anchors":
|
|
72
|
+
if (typeof value !== "object" || value === null)
|
|
73
|
+
errs.push("anchors must be an object (JSON)");
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
return errs;
|
|
77
|
+
}
|
|
78
|
+
function showRecordCard(record) {
|
|
79
|
+
const w = 62;
|
|
80
|
+
const line = (label, value) => {
|
|
81
|
+
const content = ` ${chalk_1.default.dim(label.padEnd(14))} ${value}`;
|
|
82
|
+
process.stdout.write(content + "\n");
|
|
83
|
+
};
|
|
84
|
+
process.stdout.write("\n" + chalk_1.default.dim("─".repeat(w)) + "\n");
|
|
85
|
+
line("Key", chalk_1.default.dim(record.key));
|
|
86
|
+
line("Selector", chalk_1.default.yellow(record.selector));
|
|
87
|
+
line("Tag", record.tagName);
|
|
88
|
+
line("Text", record.text ? chalk_1.default.cyan(record.text.slice(0, 40)) : chalk_1.default.dim("-"));
|
|
89
|
+
if (record.sourceFile) {
|
|
90
|
+
const rel = path_1.default.relative(process.cwd(), record.sourceFile);
|
|
91
|
+
line("Source", `${rel}:${record.sourceLine}`);
|
|
92
|
+
}
|
|
93
|
+
line("Last Healed", record.lastHealed
|
|
94
|
+
? new Date(record.lastHealed).toLocaleDateString()
|
|
95
|
+
: chalk_1.default.dim("never"));
|
|
96
|
+
line("Status", record.isOrphaned
|
|
97
|
+
? chalk_1.default.red("orphaned")
|
|
98
|
+
: record.lastHealed
|
|
99
|
+
? chalk_1.default.green("healed")
|
|
100
|
+
: chalk_1.default.cyan("active"));
|
|
101
|
+
process.stdout.write(chalk_1.default.dim("─".repeat(w)) + "\n\n");
|
|
102
|
+
}
|
|
103
|
+
function showDiff(field, oldVal, newVal) {
|
|
104
|
+
process.stdout.write("\n");
|
|
105
|
+
process.stdout.write(` ${chalk_1.default.bold("Preview - " + field)}\n`);
|
|
106
|
+
process.stdout.write(` ${"─".repeat(56)}\n`);
|
|
107
|
+
process.stdout.write(` ${chalk_1.default.red("−")} ${chalk_1.default.red(formatValue(oldVal))}\n`);
|
|
108
|
+
process.stdout.write(` ${chalk_1.default.green("+")} ${chalk_1.default.green(formatValue(newVal))}\n`);
|
|
109
|
+
process.stdout.write("\n");
|
|
110
|
+
}
|
|
111
|
+
// ── RefactorWizard ────────────────────────────────────────────────────────────
|
|
112
|
+
class RefactorWizard {
|
|
113
|
+
svc;
|
|
114
|
+
opts;
|
|
115
|
+
constructor(svc, opts = {}) {
|
|
116
|
+
this.svc = svc;
|
|
117
|
+
this.opts = opts;
|
|
118
|
+
}
|
|
119
|
+
async run(initialKey) {
|
|
120
|
+
// ── SELECT_DNA ────────────────────────────────────────────────
|
|
121
|
+
let record = initialKey
|
|
122
|
+
? this.resolveKey(initialKey)
|
|
123
|
+
: await this.selectDna();
|
|
124
|
+
// Load full snapshot once (needed for attributes/anchors fields)
|
|
125
|
+
let snap = await this.svc.dnaEditor.read(record.key);
|
|
126
|
+
// Edit loop - user can edit multiple fields per session
|
|
127
|
+
for (;;) {
|
|
128
|
+
// ── SHOW_CURRENT ──────────────────────────────────────────
|
|
129
|
+
showRecordCard(record);
|
|
130
|
+
// ── SELECT_FIELD ──────────────────────────────────────────
|
|
131
|
+
const field = await this.selectField();
|
|
132
|
+
// ── LOCATOR_DISAMBIGUATE (selector field only) ────────────
|
|
133
|
+
let locatorIndex = 0;
|
|
134
|
+
if (field === "selector" && record.sourceFile && record.sourceLine > 0) {
|
|
135
|
+
const locators = this.svc.sourceLink.enumerateLocatorsOnLine(record.sourceFile, record.sourceLine);
|
|
136
|
+
if (locators.length > 1) {
|
|
137
|
+
locatorIndex = await (0, LocatorPicker_js_1.pickLocator)(locators, record.selector);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// ── EDIT_VALUE + VALIDATE (loop on error) ─────────────────
|
|
141
|
+
const oldValue = getFieldValue(field, record, snap);
|
|
142
|
+
let newValue;
|
|
143
|
+
for (;;) {
|
|
144
|
+
newValue = await this.editValue(field, oldValue);
|
|
145
|
+
const errs = validateField(field, newValue);
|
|
146
|
+
if (errs.length === 0)
|
|
147
|
+
break;
|
|
148
|
+
process.stdout.write("\n");
|
|
149
|
+
for (const e of errs) {
|
|
150
|
+
process.stdout.write(` ${chalk_1.default.red("✗")} ${e}\n`);
|
|
151
|
+
}
|
|
152
|
+
process.stdout.write("\n");
|
|
153
|
+
}
|
|
154
|
+
// ── PREVIEW_DIFF ──────────────────────────────────────────
|
|
155
|
+
showDiff(field, oldValue, newValue);
|
|
156
|
+
// ── CONFIRM ───────────────────────────────────────────────
|
|
157
|
+
const apply = await this.confirm("Apply changes?");
|
|
158
|
+
if (!apply) {
|
|
159
|
+
const again = await this.confirm("Edit another field?");
|
|
160
|
+
if (!again)
|
|
161
|
+
break;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
// ── APPLY_SOURCE? ─────────────────────────────────────────
|
|
165
|
+
let applySource = false;
|
|
166
|
+
if (!this.opts.noSource &&
|
|
167
|
+
field === "selector" &&
|
|
168
|
+
record.sourceFile &&
|
|
169
|
+
record.sourceLine > 0) {
|
|
170
|
+
applySource = await this.confirm("Also update .spec.ts source file?");
|
|
171
|
+
}
|
|
172
|
+
// ── COMMIT ────────────────────────────────────────────────
|
|
173
|
+
const patch = {
|
|
174
|
+
key: record.key,
|
|
175
|
+
field,
|
|
176
|
+
oldValue,
|
|
177
|
+
newValue,
|
|
178
|
+
applySourceUpdate: applySource,
|
|
179
|
+
locatorIndex,
|
|
180
|
+
};
|
|
181
|
+
if (this.opts.dryRun) {
|
|
182
|
+
process.stdout.write(`\n ${chalk_1.default.yellow("[dry-run]")} Would write: ${JSON.stringify({ field, newValue })}\n\n`);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
const bakPath = await this.svc.dnaEditor.write(record.key, this.buildDnaPatch(field, newValue));
|
|
186
|
+
process.stdout.write(`\n ${chalk_1.default.green("✓")} DNA written. Backup: ${chalk_1.default.dim(path_1.default.basename(bakPath))}\n`);
|
|
187
|
+
if (applySource) {
|
|
188
|
+
const result = await this.svc.sourceLink.applySourceUpdate(record, patch);
|
|
189
|
+
if (result.success) {
|
|
190
|
+
process.stdout.write(` ${chalk_1.default.green("✓")} Source updated on line ${result.lineUpdated ?? record.sourceLine}\n`);
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
process.stdout.write(` ${chalk_1.default.yellow("⚠")} Source update: ${result.reason}\n`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// Refresh record from index after write
|
|
197
|
+
snap = await this.svc.dnaEditor.read(record.key);
|
|
198
|
+
const refreshed = this.svc.dnaIndex.getByKey(record.key);
|
|
199
|
+
if (refreshed)
|
|
200
|
+
record = refreshed;
|
|
201
|
+
}
|
|
202
|
+
// ── DONE / CONTINUE? ─────────────────────────────────────
|
|
203
|
+
const another = await this.confirm("Edit another field?");
|
|
204
|
+
if (!another)
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
process.stdout.write("\n" + chalk_1.default.dim("Wizard complete.") + "\n\n");
|
|
208
|
+
}
|
|
209
|
+
// ── Private helpers ───────────────────────────────────────────────────────
|
|
210
|
+
resolveKey(key) {
|
|
211
|
+
const record = this.svc.dnaIndex.getByKey(key);
|
|
212
|
+
if (!record) {
|
|
213
|
+
const err = new Error(`DNA not found: ${key}`);
|
|
214
|
+
err.code = "DNA_NOT_FOUND";
|
|
215
|
+
throw err;
|
|
216
|
+
}
|
|
217
|
+
return record;
|
|
218
|
+
}
|
|
219
|
+
async selectDna() {
|
|
220
|
+
const records = this.svc.dnaIndex.getIndex().records;
|
|
221
|
+
if (records.length === 0) {
|
|
222
|
+
const err = new Error("No DNA records found in snapshot directory");
|
|
223
|
+
err.code = "SNAPSHOT_DIR_MISSING";
|
|
224
|
+
throw err;
|
|
225
|
+
}
|
|
226
|
+
const choices = records.map((r) => ({
|
|
227
|
+
name: r.key,
|
|
228
|
+
message: `${chalk_1.default.dim(r.key.slice(0, 8))} ${r.selector.padEnd(40).slice(0, 40)} ${chalk_1.default.dim(r.testTitle.slice(0, 30))}`,
|
|
229
|
+
}));
|
|
230
|
+
const prompt = new enquirer_js_1.AutoComplete({
|
|
231
|
+
name: "dna",
|
|
232
|
+
message: "Search DNA record (type to filter)",
|
|
233
|
+
limit: 10,
|
|
234
|
+
choices,
|
|
235
|
+
});
|
|
236
|
+
const key = (await prompt.run());
|
|
237
|
+
return this.resolveKey(key);
|
|
238
|
+
}
|
|
239
|
+
async selectField() {
|
|
240
|
+
const prompt = new enquirer_js_1.Select({
|
|
241
|
+
name: "field",
|
|
242
|
+
message: "Which field to edit?",
|
|
243
|
+
choices: [
|
|
244
|
+
{
|
|
245
|
+
name: "selector",
|
|
246
|
+
message: `${chalk_1.default.yellow("selector")} CSS/Playwright selector`,
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
name: "text",
|
|
250
|
+
message: `${chalk_1.default.cyan("text")} Visible element text`,
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: "tagName",
|
|
254
|
+
message: `${chalk_1.default.dim("tagName")} HTML tag name`,
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
name: "attributes",
|
|
258
|
+
message: `${chalk_1.default.dim("attributes")} Element attributes (JSON)`,
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
name: "anchors",
|
|
262
|
+
message: `${chalk_1.default.dim("anchors")} Anchor context (JSON)`,
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
});
|
|
266
|
+
return (await prompt.run());
|
|
267
|
+
}
|
|
268
|
+
async editValue(field, current) {
|
|
269
|
+
const initialStr = current === null || current === undefined
|
|
270
|
+
? ""
|
|
271
|
+
: typeof current === "object"
|
|
272
|
+
? JSON.stringify(current, null, 2)
|
|
273
|
+
: String(current);
|
|
274
|
+
const hint = field === "text"
|
|
275
|
+
? " (leave blank for null)"
|
|
276
|
+
: field === "attributes" || field === "anchors"
|
|
277
|
+
? " (JSON)"
|
|
278
|
+
: "";
|
|
279
|
+
const prompt = new enquirer_js_1.Input({
|
|
280
|
+
name: "value",
|
|
281
|
+
message: `New ${field}${hint}`,
|
|
282
|
+
initial: initialStr,
|
|
283
|
+
});
|
|
284
|
+
const raw = (await prompt.run());
|
|
285
|
+
return parseInput(field, raw);
|
|
286
|
+
}
|
|
287
|
+
async confirm(message) {
|
|
288
|
+
const prompt = new enquirer_js_1.Confirm({ name: "ok", message });
|
|
289
|
+
return (await prompt.run());
|
|
290
|
+
}
|
|
291
|
+
buildDnaPatch(field, newValue) {
|
|
292
|
+
return { [field]: newValue };
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
exports.RefactorWizard = RefactorWizard;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ResolvedConfig } from "./SelaConfig";
|
|
2
|
+
export declare class ConfigLoader {
|
|
3
|
+
private static resolved;
|
|
4
|
+
private static branch;
|
|
5
|
+
/**
|
|
6
|
+
* Returns the fully resolved config. First call does all I/O; subsequent
|
|
7
|
+
* calls return the cached result at zero cost.
|
|
8
|
+
*/
|
|
9
|
+
static getInstance(): ResolvedConfig;
|
|
10
|
+
private static logEnvBanners;
|
|
11
|
+
/** Returns the detected Git branch name, or null if detection failed. */
|
|
12
|
+
static getBranch(): string | null;
|
|
13
|
+
/** Clears the singleton cache - useful for testing. */
|
|
14
|
+
static reset(): void;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=ConfigLoader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfigLoader.d.ts","sourceRoot":"","sources":["../../src/config/ConfigLoader.ts"],"names":[],"mappings":"AAQA,OAAO,EAAc,cAAc,EAAiB,MAAM,cAAc,CAAC;AA6GzE,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA+B;IACtD,OAAO,CAAC,MAAM,CAAC,MAAM,CAAuB;IAE5C;;;OAGG;IACH,MAAM,CAAC,WAAW,IAAI,cAAc;IAuCpC,OAAO,CAAC,MAAM,CAAC,aAAa;IAW5B,yEAAyE;IACzE,MAAM,CAAC,SAAS,IAAI,MAAM,GAAG,IAAI;IAMjC,uDAAuD;IACvD,MAAM,CAAC,KAAK,IAAI,IAAI;CAIrB"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/config/ConfigLoader.ts
|
|
3
|
+
// Locates sela.config.ts|js, detects the current Git branch, deep-merges
|
|
4
|
+
// the base config with any branch override, and caches the resolved result
|
|
5
|
+
// for the lifetime of the process (singleton pattern).
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.ConfigLoader = void 0;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const child_process_1 = require("child_process");
|
|
44
|
+
const SelaConfig_1 = require("./SelaConfig");
|
|
45
|
+
const logger_1 = require("../utils/logger");
|
|
46
|
+
// ─────────────────────────────────────────────────────────────────
|
|
47
|
+
// FILE DISCOVERY
|
|
48
|
+
// ─────────────────────────────────────────────────────────────────
|
|
49
|
+
function findConfigFile(startDir) {
|
|
50
|
+
let dir = startDir;
|
|
51
|
+
while (true) {
|
|
52
|
+
for (const name of ["sela.config.ts", "sela.config.js"]) {
|
|
53
|
+
const candidate = path.join(dir, name);
|
|
54
|
+
if (fs.existsSync(candidate))
|
|
55
|
+
return candidate;
|
|
56
|
+
}
|
|
57
|
+
const parent = path.dirname(dir);
|
|
58
|
+
if (parent === dir)
|
|
59
|
+
return null; // reached filesystem root
|
|
60
|
+
dir = parent;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// ─────────────────────────────────────────────────────────────────
|
|
64
|
+
// GIT BRANCH DETECTION
|
|
65
|
+
// ─────────────────────────────────────────────────────────────────
|
|
66
|
+
function getCurrentBranch() {
|
|
67
|
+
try {
|
|
68
|
+
// Fail fast if not a git repo or git is unavailable
|
|
69
|
+
const branch = (0, child_process_1.execSync)("git rev-parse --abbrev-ref HEAD", {
|
|
70
|
+
encoding: "utf-8",
|
|
71
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
72
|
+
}).trim();
|
|
73
|
+
if (branch === "HEAD") {
|
|
74
|
+
// Detached HEAD (common in CI with explicit SHA checkout)
|
|
75
|
+
logger_1.logger.warn("Detached HEAD detected - branch overrides skipped");
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return branch;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
logger_1.logger.warn("Git branch detection failed - branch overrides skipped");
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// ─────────────────────────────────────────────────────────────────
|
|
86
|
+
// CONFIG FILE LOADING
|
|
87
|
+
// ─────────────────────────────────────────────────────────────────
|
|
88
|
+
function loadRawConfig(configPath) {
|
|
89
|
+
try {
|
|
90
|
+
// Works in Playwright test context (TypeScript loader registered by @playwright/test)
|
|
91
|
+
// and in Jest context (ts-jest). Falls back gracefully if neither is active.
|
|
92
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
93
|
+
const mod = require(configPath);
|
|
94
|
+
const config = mod?.default ?? mod ?? {};
|
|
95
|
+
if (typeof config !== "object" || config === null) {
|
|
96
|
+
logger_1.logger.warn(`sela.config did not export an object - using defaults`);
|
|
97
|
+
return {};
|
|
98
|
+
}
|
|
99
|
+
return config;
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
logger_1.logger.warn(`Failed to load ${path.basename(configPath)}: ${err.message}`);
|
|
103
|
+
return {};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// ─────────────────────────────────────────────────────────────────
|
|
107
|
+
// DEEP MERGE (plain objects only - arrays are replaced, not merged)
|
|
108
|
+
// ─────────────────────────────────────────────────────────────────
|
|
109
|
+
function deepMerge(base, override) {
|
|
110
|
+
const result = { ...base };
|
|
111
|
+
for (const key of Object.keys(override)) {
|
|
112
|
+
const baseVal = result[key];
|
|
113
|
+
const overrideVal = override[key];
|
|
114
|
+
if (overrideVal !== null &&
|
|
115
|
+
typeof overrideVal === "object" &&
|
|
116
|
+
!Array.isArray(overrideVal) &&
|
|
117
|
+
baseVal !== null &&
|
|
118
|
+
typeof baseVal === "object" &&
|
|
119
|
+
!Array.isArray(baseVal)) {
|
|
120
|
+
result[key] = deepMerge(baseVal, overrideVal);
|
|
121
|
+
}
|
|
122
|
+
else if (overrideVal !== undefined) {
|
|
123
|
+
result[key] = overrideVal;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
// ─────────────────────────────────────────────────────────────────
|
|
129
|
+
// SINGLETON LOADER
|
|
130
|
+
// ─────────────────────────────────────────────────────────────────
|
|
131
|
+
class ConfigLoader {
|
|
132
|
+
static resolved = null;
|
|
133
|
+
static branch = null;
|
|
134
|
+
/**
|
|
135
|
+
* Returns the fully resolved config. First call does all I/O; subsequent
|
|
136
|
+
* calls return the cached result at zero cost.
|
|
137
|
+
*/
|
|
138
|
+
static getInstance() {
|
|
139
|
+
if (ConfigLoader.resolved)
|
|
140
|
+
return ConfigLoader.resolved;
|
|
141
|
+
const configPath = findConfigFile(process.cwd());
|
|
142
|
+
if (!configPath) {
|
|
143
|
+
// Zero-breaking-change guarantee: no config → pure defaults
|
|
144
|
+
ConfigLoader.resolved = (0, SelaConfig_1.resolveConfig)({});
|
|
145
|
+
ConfigLoader.logEnvBanners(ConfigLoader.resolved);
|
|
146
|
+
return ConfigLoader.resolved;
|
|
147
|
+
}
|
|
148
|
+
logger_1.logger.debug(`Loaded config from: ${path.relative(process.cwd(), configPath)}`);
|
|
149
|
+
let rawConfig = loadRawConfig(configPath);
|
|
150
|
+
// Apply branch overrides using exact-string matching
|
|
151
|
+
const detectedBranch = getCurrentBranch();
|
|
152
|
+
ConfigLoader.branch = detectedBranch;
|
|
153
|
+
if (rawConfig.branchOverrides && detectedBranch) {
|
|
154
|
+
const override = rawConfig.branchOverrides[detectedBranch];
|
|
155
|
+
if (override) {
|
|
156
|
+
logger_1.logger.debug(`Applying branch override for: ${detectedBranch}`);
|
|
157
|
+
rawConfig = deepMerge(rawConfig, override);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
ConfigLoader.resolved = (0, SelaConfig_1.resolveConfig)(rawConfig);
|
|
161
|
+
ConfigLoader.logEnvBanners(ConfigLoader.resolved);
|
|
162
|
+
return ConfigLoader.resolved;
|
|
163
|
+
}
|
|
164
|
+
static logEnvBanners(resolved) {
|
|
165
|
+
if (resolved.dryRun) {
|
|
166
|
+
logger_1.logger.info("DRY RUN active (SELA_DRY_RUN) - disk writes & git commits suppressed.");
|
|
167
|
+
}
|
|
168
|
+
if (resolved.noCache) {
|
|
169
|
+
logger_1.logger.info("Cache disabled (SELA_NO_CACHE).");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/** Returns the detected Git branch name, or null if detection failed. */
|
|
173
|
+
static getBranch() {
|
|
174
|
+
// Ensure getInstance() has been called at least once
|
|
175
|
+
if (!ConfigLoader.resolved)
|
|
176
|
+
ConfigLoader.getInstance();
|
|
177
|
+
return ConfigLoader.branch;
|
|
178
|
+
}
|
|
179
|
+
/** Clears the singleton cache - useful for testing. */
|
|
180
|
+
static reset() {
|
|
181
|
+
ConfigLoader.resolved = null;
|
|
182
|
+
ConfigLoader.branch = null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
exports.ConfigLoader = ConfigLoader;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const DryRunGuard: {
|
|
2
|
+
/**
|
|
3
|
+
* Returns true when `SELA_DRY_RUN` is set to a truthy value in the current
|
|
4
|
+
* process environment. Throws via `parseEnvBoolean` when the value is not
|
|
5
|
+
* one of the accepted tokens - fail-fast keeps misconfigured CI loud.
|
|
6
|
+
*/
|
|
7
|
+
active(env?: NodeJS.ProcessEnv): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Centralised log line for skipped writes so the diff between a dry run
|
|
10
|
+
* and a real run is auditable in test output and CI logs.
|
|
11
|
+
*/
|
|
12
|
+
logSkippedWrite(label: string, target: string): void;
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=DryRunGuard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DryRunGuard.d.ts","sourceRoot":"","sources":["../../src/config/DryRunGuard.ts"],"names":[],"mappings":"AA4BA,eAAO,MAAM,WAAW;IACtB;;;;OAIG;iBACS,MAAM,CAAC,UAAU,GAAiB,OAAO;IAIrD;;;OAGG;2BACoB,MAAM,UAAU,MAAM,GAAG,IAAI;CAIrD,CAAC"}
|