dlw-machine-setup 0.8.10 → 0.9.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 +14 -1
- package/bin/installer.js +961 -182
- package/package.json +1 -1
package/bin/installer.js
CHANGED
|
@@ -2753,13 +2753,13 @@ var PromisePolyfill = class extends Promise {
|
|
|
2753
2753
|
// Available starting from Node 22
|
|
2754
2754
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers
|
|
2755
2755
|
static withResolver() {
|
|
2756
|
-
let
|
|
2756
|
+
let resolve6;
|
|
2757
2757
|
let reject;
|
|
2758
2758
|
const promise = new Promise((res, rej) => {
|
|
2759
|
-
|
|
2759
|
+
resolve6 = res;
|
|
2760
2760
|
reject = rej;
|
|
2761
2761
|
});
|
|
2762
|
-
return { promise, resolve:
|
|
2762
|
+
return { promise, resolve: resolve6, reject };
|
|
2763
2763
|
}
|
|
2764
2764
|
};
|
|
2765
2765
|
|
|
@@ -2776,7 +2776,7 @@ function createPrompt(view) {
|
|
|
2776
2776
|
output
|
|
2777
2777
|
});
|
|
2778
2778
|
const screen = new ScreenManager(rl);
|
|
2779
|
-
const { promise, resolve:
|
|
2779
|
+
const { promise, resolve: resolve6, reject } = PromisePolyfill.withResolver();
|
|
2780
2780
|
const cancel = () => reject(new CancelPromptError());
|
|
2781
2781
|
if (signal) {
|
|
2782
2782
|
const abort = () => reject(new AbortPromptError({ cause: signal.reason }));
|
|
@@ -2800,7 +2800,7 @@ function createPrompt(view) {
|
|
|
2800
2800
|
cycle(() => {
|
|
2801
2801
|
try {
|
|
2802
2802
|
const nextView = view(config, (value) => {
|
|
2803
|
-
setImmediate(() =>
|
|
2803
|
+
setImmediate(() => resolve6(value));
|
|
2804
2804
|
});
|
|
2805
2805
|
const [content, bottomContent] = typeof nextView === "string" ? [nextView] : nextView;
|
|
2806
2806
|
screen.render(content, bottomContent);
|
|
@@ -3244,9 +3244,9 @@ ${page}${helpTipBottom}${choiceDescription}${import_ansi_escapes3.default.cursor
|
|
|
3244
3244
|
});
|
|
3245
3245
|
|
|
3246
3246
|
// src/index.ts
|
|
3247
|
-
var
|
|
3248
|
-
var
|
|
3249
|
-
var
|
|
3247
|
+
var import_fs16 = require("fs");
|
|
3248
|
+
var import_readline2 = require("readline");
|
|
3249
|
+
var import_path16 = require("path");
|
|
3250
3250
|
|
|
3251
3251
|
// src/utils/fetch.ts
|
|
3252
3252
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
@@ -3301,7 +3301,7 @@ async function fetchWithRetry(url, options = {}, maxRetries = DEFAULT_MAX_RETRIE
|
|
|
3301
3301
|
}
|
|
3302
3302
|
if (attempt < maxRetries) {
|
|
3303
3303
|
const delay = retryDelayMs * Math.pow(2, attempt - 1);
|
|
3304
|
-
await new Promise((
|
|
3304
|
+
await new Promise((resolve6) => setTimeout(resolve6, delay));
|
|
3305
3305
|
}
|
|
3306
3306
|
}
|
|
3307
3307
|
throw lastError || new Error("Failed after maximum retries");
|
|
@@ -3548,7 +3548,7 @@ async function pollForToken(deviceCode, interval) {
|
|
|
3548
3548
|
const maxAttempts = 60;
|
|
3549
3549
|
let attempts = 0;
|
|
3550
3550
|
while (attempts < maxAttempts) {
|
|
3551
|
-
await new Promise((
|
|
3551
|
+
await new Promise((resolve6) => setTimeout(resolve6, interval * 1e3));
|
|
3552
3552
|
const response = await fetchWithTimeout(GITHUB_TOKEN_URL, {
|
|
3553
3553
|
method: "POST",
|
|
3554
3554
|
headers: {
|
|
@@ -3734,6 +3734,7 @@ async function runPipeline(steps, ctx) {
|
|
|
3734
3734
|
entries.push({ name: step.name, label: step.label, result });
|
|
3735
3735
|
if (result.status === "success") {
|
|
3736
3736
|
console.log("\u2713");
|
|
3737
|
+
ctx.journal?.recordStep(step.name, result.record);
|
|
3737
3738
|
} else {
|
|
3738
3739
|
console.log("\u2717");
|
|
3739
3740
|
}
|
|
@@ -3753,6 +3754,148 @@ async function runPipeline(steps, ctx) {
|
|
|
3753
3754
|
}
|
|
3754
3755
|
return { entries };
|
|
3755
3756
|
}
|
|
3757
|
+
async function runRollback(steps, ctx) {
|
|
3758
|
+
const entries = [];
|
|
3759
|
+
if (!ctx.journal) {
|
|
3760
|
+
return { entries };
|
|
3761
|
+
}
|
|
3762
|
+
const stepsByName = new Map(steps.map((s) => [s.name, s]));
|
|
3763
|
+
for (const journalEntry of ctx.journal.getEntriesReversed()) {
|
|
3764
|
+
const step = stepsByName.get(journalEntry.step);
|
|
3765
|
+
if (!step) {
|
|
3766
|
+
entries.push({
|
|
3767
|
+
name: journalEntry.step,
|
|
3768
|
+
label: journalEntry.step,
|
|
3769
|
+
result: {
|
|
3770
|
+
status: "skipped",
|
|
3771
|
+
detail: "step not found in this installer build \u2014 installed by a different version?"
|
|
3772
|
+
}
|
|
3773
|
+
});
|
|
3774
|
+
continue;
|
|
3775
|
+
}
|
|
3776
|
+
if (!step.inverse) {
|
|
3777
|
+
entries.push({
|
|
3778
|
+
name: step.name,
|
|
3779
|
+
label: step.label,
|
|
3780
|
+
result: { status: "skipped", detail: "no inverse defined" }
|
|
3781
|
+
});
|
|
3782
|
+
continue;
|
|
3783
|
+
}
|
|
3784
|
+
const label = step.inverse.label ?? `Reversing: ${step.label}`;
|
|
3785
|
+
process.stdout.write(` ${label}... `);
|
|
3786
|
+
try {
|
|
3787
|
+
const result = await step.inverse.execute(journalEntry.record, ctx);
|
|
3788
|
+
entries.push({ name: step.name, label, result });
|
|
3789
|
+
if (result.status === "success") {
|
|
3790
|
+
console.log("\u2713");
|
|
3791
|
+
} else if (result.status === "failed") {
|
|
3792
|
+
console.log("\u2717");
|
|
3793
|
+
} else {
|
|
3794
|
+
console.log("\u2014");
|
|
3795
|
+
}
|
|
3796
|
+
if (result.detail) {
|
|
3797
|
+
console.log(` ${result.detail}`);
|
|
3798
|
+
}
|
|
3799
|
+
} catch (err) {
|
|
3800
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
3801
|
+
entries.push({
|
|
3802
|
+
name: step.name,
|
|
3803
|
+
label,
|
|
3804
|
+
result: { status: "failed", detail }
|
|
3805
|
+
});
|
|
3806
|
+
console.log("\u2717");
|
|
3807
|
+
console.log(` ${detail}`);
|
|
3808
|
+
}
|
|
3809
|
+
}
|
|
3810
|
+
return { entries };
|
|
3811
|
+
}
|
|
3812
|
+
|
|
3813
|
+
// src/journal.ts
|
|
3814
|
+
var import_fs3 = require("fs");
|
|
3815
|
+
var import_path3 = require("path");
|
|
3816
|
+
var JOURNAL_SCHEMA_VERSION = 1;
|
|
3817
|
+
var Journal = class {
|
|
3818
|
+
filePath;
|
|
3819
|
+
entries = [];
|
|
3820
|
+
/** Top-level summary fields (everything outside `rollback`). Owned by
|
|
3821
|
+
* write-state.ts; the journal just carries them so the on-disk file
|
|
3822
|
+
* stays one coherent JSON document instead of two side-by-side files. */
|
|
3823
|
+
summary = {};
|
|
3824
|
+
constructor(filePath) {
|
|
3825
|
+
this.filePath = filePath;
|
|
3826
|
+
}
|
|
3827
|
+
/** Load existing state from disk. Used by uninstall. Tolerates a missing
|
|
3828
|
+
* or corrupt file — both surface as "no entries to roll back." */
|
|
3829
|
+
load() {
|
|
3830
|
+
this.entries = [];
|
|
3831
|
+
this.summary = {};
|
|
3832
|
+
if (!(0, import_fs3.existsSync)(this.filePath)) return;
|
|
3833
|
+
try {
|
|
3834
|
+
const raw = JSON.parse((0, import_fs3.readFileSync)(this.filePath, "utf-8"));
|
|
3835
|
+
const { rollback, ...summary } = raw;
|
|
3836
|
+
this.summary = summary;
|
|
3837
|
+
if (rollback && typeof rollback === "object") {
|
|
3838
|
+
const section = rollback;
|
|
3839
|
+
if (Array.isArray(section.entries)) {
|
|
3840
|
+
this.entries = section.entries.filter(
|
|
3841
|
+
(e) => !!e && typeof e === "object" && typeof e.step === "string"
|
|
3842
|
+
);
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3845
|
+
} catch {
|
|
3846
|
+
}
|
|
3847
|
+
}
|
|
3848
|
+
/** Clear in-memory state and flush an empty file to disk. Used by install
|
|
3849
|
+
* at the start of a run so a previous install's journal never leaks
|
|
3850
|
+
* into a fresh run's rollback list. */
|
|
3851
|
+
startFresh() {
|
|
3852
|
+
this.entries = [];
|
|
3853
|
+
this.summary = {};
|
|
3854
|
+
this.flush();
|
|
3855
|
+
}
|
|
3856
|
+
/** Append a completed step to the journal and flush immediately. The
|
|
3857
|
+
* flush-per-step is intentional: an install that crashes between step 4
|
|
3858
|
+
* and step 5 still leaves a journal that ends at step 4. */
|
|
3859
|
+
recordStep(name, record) {
|
|
3860
|
+
this.entries.push({
|
|
3861
|
+
step: name,
|
|
3862
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3863
|
+
record
|
|
3864
|
+
});
|
|
3865
|
+
this.flush();
|
|
3866
|
+
}
|
|
3867
|
+
/** Replace the top-level summary fields and flush. Called by
|
|
3868
|
+
* write-state.ts so the existing on-disk shape is preserved. */
|
|
3869
|
+
setSummary(summary) {
|
|
3870
|
+
this.summary = summary;
|
|
3871
|
+
this.flush();
|
|
3872
|
+
}
|
|
3873
|
+
/** Entries in install order (oldest first). */
|
|
3874
|
+
getEntries() {
|
|
3875
|
+
return this.entries;
|
|
3876
|
+
}
|
|
3877
|
+
/** Entries in rollback order (most-recent first). LIFO so each step's
|
|
3878
|
+
* inverse undoes its own changes before any earlier step's state is
|
|
3879
|
+
* touched — matters when later steps depend on earlier ones (e.g.
|
|
3880
|
+
* write-instructions reads _ai-context/ that fetch-contexts populated). */
|
|
3881
|
+
getEntriesReversed() {
|
|
3882
|
+
return [...this.entries].reverse();
|
|
3883
|
+
}
|
|
3884
|
+
/** Persist the current summary + entries to disk as one JSON document.
|
|
3885
|
+
* Summary fields come first so a human opening the file sees the
|
|
3886
|
+
* familiar shape; `rollback` is the last key. */
|
|
3887
|
+
flush() {
|
|
3888
|
+
(0, import_fs3.mkdirSync)((0, import_path3.dirname)(this.filePath), { recursive: true });
|
|
3889
|
+
const out = {
|
|
3890
|
+
...this.summary,
|
|
3891
|
+
rollback: {
|
|
3892
|
+
schemaVersion: JOURNAL_SCHEMA_VERSION,
|
|
3893
|
+
entries: this.entries
|
|
3894
|
+
}
|
|
3895
|
+
};
|
|
3896
|
+
(0, import_fs3.writeFileSync)(this.filePath, JSON.stringify(out, null, 2), "utf-8");
|
|
3897
|
+
}
|
|
3898
|
+
};
|
|
3756
3899
|
|
|
3757
3900
|
// src/steps/index.ts
|
|
3758
3901
|
var steps_exports = {};
|
|
@@ -3768,19 +3911,149 @@ __export(steps_exports, {
|
|
|
3768
3911
|
});
|
|
3769
3912
|
|
|
3770
3913
|
// src/steps/resources/fetch-contexts.ts
|
|
3771
|
-
var
|
|
3772
|
-
var
|
|
3914
|
+
var import_fs6 = require("fs");
|
|
3915
|
+
var import_path6 = require("path");
|
|
3773
3916
|
|
|
3774
3917
|
// src/utils/fs-copy.ts
|
|
3775
|
-
var
|
|
3776
|
-
var
|
|
3918
|
+
var import_fs4 = require("fs");
|
|
3919
|
+
var import_path4 = require("path");
|
|
3777
3920
|
function copyDirectory(source, target) {
|
|
3778
|
-
if (!(0,
|
|
3779
|
-
for (const entry of (0,
|
|
3780
|
-
const s = (0,
|
|
3781
|
-
const t = (0,
|
|
3921
|
+
if (!(0, import_fs4.existsSync)(target)) (0, import_fs4.mkdirSync)(target, { recursive: true });
|
|
3922
|
+
for (const entry of (0, import_fs4.readdirSync)(source, { withFileTypes: true })) {
|
|
3923
|
+
const s = (0, import_path4.join)(source, entry.name);
|
|
3924
|
+
const t = (0, import_path4.join)(target, entry.name);
|
|
3782
3925
|
if (entry.isDirectory()) copyDirectory(s, t);
|
|
3783
|
-
else (0,
|
|
3926
|
+
else (0, import_fs4.copyFileSync)(s, t);
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
|
|
3930
|
+
// src/inverse/primitives.ts
|
|
3931
|
+
var import_fs5 = require("fs");
|
|
3932
|
+
var import_path5 = require("path");
|
|
3933
|
+
function deleteFileIfExists(absPath) {
|
|
3934
|
+
if (!(0, import_fs5.existsSync)(absPath)) return false;
|
|
3935
|
+
try {
|
|
3936
|
+
(0, import_fs5.unlinkSync)(absPath);
|
|
3937
|
+
return true;
|
|
3938
|
+
} catch {
|
|
3939
|
+
return false;
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
function deleteDirIfExists(absPath) {
|
|
3943
|
+
if (!(0, import_fs5.existsSync)(absPath)) return false;
|
|
3944
|
+
try {
|
|
3945
|
+
(0, import_fs5.rmSync)(absPath, { recursive: true, force: true });
|
|
3946
|
+
return true;
|
|
3947
|
+
} catch {
|
|
3948
|
+
return false;
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
function pruneEmptyAncestors(relPaths, projectPath) {
|
|
3952
|
+
const root = (0, import_path5.resolve)(projectPath);
|
|
3953
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
3954
|
+
for (const rel of relPaths) {
|
|
3955
|
+
let cur = (0, import_path5.resolve)((0, import_path5.join)(root, rel));
|
|
3956
|
+
while (true) {
|
|
3957
|
+
cur = (0, import_path5.dirname)(cur);
|
|
3958
|
+
if (cur === root) break;
|
|
3959
|
+
if (!cur.startsWith(root + import_path5.sep)) break;
|
|
3960
|
+
ancestors.add(cur);
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3963
|
+
const sorted = [...ancestors].sort((a, b) => b.length - a.length);
|
|
3964
|
+
const removed = [];
|
|
3965
|
+
for (const abs of sorted) {
|
|
3966
|
+
if (!(0, import_fs5.existsSync)(abs)) continue;
|
|
3967
|
+
try {
|
|
3968
|
+
const entries = (0, import_fs5.readdirSync)(abs);
|
|
3969
|
+
if (entries.length > 0) continue;
|
|
3970
|
+
(0, import_fs5.rmdirSync)(abs);
|
|
3971
|
+
removed.push(abs);
|
|
3972
|
+
} catch {
|
|
3973
|
+
}
|
|
3974
|
+
}
|
|
3975
|
+
return removed;
|
|
3976
|
+
}
|
|
3977
|
+
function readJsonOrNull(absPath) {
|
|
3978
|
+
if (!(0, import_fs5.existsSync)(absPath)) return null;
|
|
3979
|
+
try {
|
|
3980
|
+
return JSON.parse((0, import_fs5.readFileSync)(absPath, "utf-8"));
|
|
3981
|
+
} catch {
|
|
3982
|
+
return null;
|
|
3983
|
+
}
|
|
3984
|
+
}
|
|
3985
|
+
function isPlainObject2(v) {
|
|
3986
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3987
|
+
}
|
|
3988
|
+
var OWNER_KEY = "_bundleOwner";
|
|
3989
|
+
function stripOwnedEntries(value, owner) {
|
|
3990
|
+
if (Array.isArray(value)) {
|
|
3991
|
+
return value.filter((item) => !(isPlainObject2(item) && item[OWNER_KEY] === owner)).map((item) => stripOwnedEntries(item, owner));
|
|
3992
|
+
}
|
|
3993
|
+
if (isPlainObject2(value)) {
|
|
3994
|
+
return Object.fromEntries(
|
|
3995
|
+
Object.entries(value).map(([k, v]) => [k, stripOwnedEntries(v, owner)])
|
|
3996
|
+
);
|
|
3997
|
+
}
|
|
3998
|
+
return value;
|
|
3999
|
+
}
|
|
4000
|
+
function deleteJsonIfEffectivelyEmpty(absPath) {
|
|
4001
|
+
if (!(0, import_fs5.existsSync)(absPath)) return false;
|
|
4002
|
+
const raw = readJsonOrNull(absPath);
|
|
4003
|
+
if (!isPlainObject2(raw)) return false;
|
|
4004
|
+
const keys = Object.keys(raw);
|
|
4005
|
+
if (keys.length === 0) {
|
|
4006
|
+
return deleteFileIfExists(absPath);
|
|
4007
|
+
}
|
|
4008
|
+
if (keys.length === 1) {
|
|
4009
|
+
const v = raw[keys[0]];
|
|
4010
|
+
if (isPlainObject2(v) && Object.keys(v).length === 0) return deleteFileIfExists(absPath);
|
|
4011
|
+
if (Array.isArray(v) && v.length === 0) return deleteFileIfExists(absPath);
|
|
4012
|
+
}
|
|
4013
|
+
return false;
|
|
4014
|
+
}
|
|
4015
|
+
function stripBundleOwnerFromJsonFile(absPath, owner) {
|
|
4016
|
+
const raw = readJsonOrNull(absPath);
|
|
4017
|
+
if (!isPlainObject2(raw)) return false;
|
|
4018
|
+
const stripped = stripOwnedEntries(raw, owner);
|
|
4019
|
+
const before = JSON.stringify(raw);
|
|
4020
|
+
const after = JSON.stringify(stripped);
|
|
4021
|
+
if (before === after) return false;
|
|
4022
|
+
(0, import_fs5.writeFileSync)(absPath, JSON.stringify(stripped, null, 2) + "\n", "utf-8");
|
|
4023
|
+
return true;
|
|
4024
|
+
}
|
|
4025
|
+
function stripMcpServers(absPath, rootKey, serverNames) {
|
|
4026
|
+
const raw = readJsonOrNull(absPath);
|
|
4027
|
+
if (!isPlainObject2(raw)) return { changed: false, rootEmpty: false };
|
|
4028
|
+
const servers = raw[rootKey];
|
|
4029
|
+
if (!isPlainObject2(servers)) return { changed: false, rootEmpty: false };
|
|
4030
|
+
let changed = false;
|
|
4031
|
+
for (const name of serverNames) {
|
|
4032
|
+
if (name in servers) {
|
|
4033
|
+
delete servers[name];
|
|
4034
|
+
changed = true;
|
|
4035
|
+
}
|
|
4036
|
+
}
|
|
4037
|
+
if (!changed) {
|
|
4038
|
+
return { changed: false, rootEmpty: Object.keys(servers).length === 0 };
|
|
4039
|
+
}
|
|
4040
|
+
raw[rootKey] = servers;
|
|
4041
|
+
(0, import_fs5.writeFileSync)(absPath, JSON.stringify(raw, null, 2) + "\n", "utf-8");
|
|
4042
|
+
return { changed: true, rootEmpty: Object.keys(servers).length === 0 };
|
|
4043
|
+
}
|
|
4044
|
+
function resolveProjectPath(rel, projectPath) {
|
|
4045
|
+
const base = (0, import_path5.resolve)(projectPath);
|
|
4046
|
+
const full = (0, import_path5.resolve)((0, import_path5.join)(base, rel));
|
|
4047
|
+
if (full !== base && !full.startsWith(base + import_path5.sep)) {
|
|
4048
|
+
throw new Error(`Inverse target escapes project path: ${rel}`);
|
|
4049
|
+
}
|
|
4050
|
+
return full;
|
|
4051
|
+
}
|
|
4052
|
+
function isDirectorySafe(absPath) {
|
|
4053
|
+
try {
|
|
4054
|
+
return (0, import_fs5.statSync)(absPath).isDirectory();
|
|
4055
|
+
} catch {
|
|
4056
|
+
return false;
|
|
3784
4057
|
}
|
|
3785
4058
|
}
|
|
3786
4059
|
|
|
@@ -3798,6 +4071,8 @@ var fetch_contexts_default = defineStep({
|
|
|
3798
4071
|
detail: "Context repo not found (topic: context-data)"
|
|
3799
4072
|
};
|
|
3800
4073
|
}
|
|
4074
|
+
const contextsDir = (0, import_path6.join)(ctx.config.projectPath, "_ai-context");
|
|
4075
|
+
const contextsDirExistedBefore = (0, import_fs6.existsSync)(contextsDir);
|
|
3801
4076
|
const downloadResult = await fetchContexts(domainValues, ctx.token, ctx.contextRepo, ctx.config.projectPath);
|
|
3802
4077
|
ctx.installed.domainsInstalled = downloadResult.successful;
|
|
3803
4078
|
ctx.installed.contextReleaseVersion = downloadResult.releaseVersion;
|
|
@@ -3809,16 +4084,42 @@ var fetch_contexts_default = defineStep({
|
|
|
3809
4084
|
const status = ok ? "\u2713" : `\u2717 ${reason ?? "Unknown error"}`;
|
|
3810
4085
|
console.log(` ${domain.padEnd(domainColWidth)}${status}`);
|
|
3811
4086
|
}
|
|
4087
|
+
const record = {
|
|
4088
|
+
domains: downloadResult.successful,
|
|
4089
|
+
contextsDirCreated: !contextsDirExistedBefore
|
|
4090
|
+
};
|
|
3812
4091
|
if (downloadResult.failed.length > 0) {
|
|
3813
4092
|
return {
|
|
3814
4093
|
status: "failed",
|
|
3815
|
-
message: `${downloadResult.successful.join(", ")} succeeded; ${downloadResult.failed.join(", ")} failed
|
|
4094
|
+
message: `${downloadResult.successful.join(", ")} succeeded; ${downloadResult.failed.join(", ")} failed`,
|
|
4095
|
+
record
|
|
3816
4096
|
};
|
|
3817
4097
|
}
|
|
3818
4098
|
return {
|
|
3819
4099
|
status: "success",
|
|
3820
|
-
message: downloadResult.successful.join(", ")
|
|
4100
|
+
message: downloadResult.successful.join(", "),
|
|
4101
|
+
record
|
|
3821
4102
|
};
|
|
4103
|
+
},
|
|
4104
|
+
inverse: {
|
|
4105
|
+
label: "Removing contexts",
|
|
4106
|
+
execute: async (raw, ctx) => {
|
|
4107
|
+
const rec = raw ?? {};
|
|
4108
|
+
const domains = rec.domains ?? [];
|
|
4109
|
+
const removed = [];
|
|
4110
|
+
const relPaths = [];
|
|
4111
|
+
for (const domain of domains) {
|
|
4112
|
+
const rel = (0, import_path6.join)("_ai-context", domain);
|
|
4113
|
+
relPaths.push(rel);
|
|
4114
|
+
const abs = resolveProjectPath(rel, ctx.config.projectPath);
|
|
4115
|
+
if (deleteDirIfExists(abs)) removed.push(domain);
|
|
4116
|
+
}
|
|
4117
|
+
pruneEmptyAncestors(relPaths, ctx.config.projectPath);
|
|
4118
|
+
return {
|
|
4119
|
+
status: "success",
|
|
4120
|
+
message: removed.length > 0 ? removed.join(", ") : "nothing to remove"
|
|
4121
|
+
};
|
|
4122
|
+
}
|
|
3822
4123
|
}
|
|
3823
4124
|
});
|
|
3824
4125
|
async function fetchContexts(domains, token, repo, targetDir) {
|
|
@@ -3834,11 +4135,11 @@ async function fetchContexts(domains, token, repo, targetDir) {
|
|
|
3834
4135
|
}
|
|
3835
4136
|
return result;
|
|
3836
4137
|
}
|
|
3837
|
-
const contextsDir = (0,
|
|
3838
|
-
if (!(0,
|
|
4138
|
+
const contextsDir = (0, import_path6.join)(targetDir, "_ai-context");
|
|
4139
|
+
if (!(0, import_fs6.existsSync)(contextsDir)) (0, import_fs6.mkdirSync)(contextsDir, { recursive: true });
|
|
3839
4140
|
const archive = await downloadAndExtractAsset(token, asset, targetDir, ".temp-download");
|
|
3840
4141
|
try {
|
|
3841
|
-
const extractedFolders = (0,
|
|
4142
|
+
const extractedFolders = (0, import_fs6.readdirSync)(archive.extractedRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
3842
4143
|
for (const domain of domains) {
|
|
3843
4144
|
try {
|
|
3844
4145
|
const match = extractedFolders.find((f) => f.toLowerCase() === domain.toLowerCase());
|
|
@@ -3847,8 +4148,8 @@ async function fetchContexts(domains, token, repo, targetDir) {
|
|
|
3847
4148
|
result.failureReasons[domain] = "Not found in archive";
|
|
3848
4149
|
continue;
|
|
3849
4150
|
}
|
|
3850
|
-
const domainPath = (0,
|
|
3851
|
-
copyDirectory((0,
|
|
4151
|
+
const domainPath = (0, import_path6.join)(contextsDir, domain);
|
|
4152
|
+
copyDirectory((0, import_path6.join)(archive.extractedRoot, match), domainPath);
|
|
3852
4153
|
result.successful.push(domain);
|
|
3853
4154
|
} catch (error) {
|
|
3854
4155
|
result.failed.push(domain);
|
|
@@ -3862,29 +4163,29 @@ async function fetchContexts(domains, token, repo, targetDir) {
|
|
|
3862
4163
|
}
|
|
3863
4164
|
|
|
3864
4165
|
// src/steps/resources/fetch-factory.ts
|
|
3865
|
-
var
|
|
3866
|
-
var
|
|
4166
|
+
var import_fs9 = require("fs");
|
|
4167
|
+
var import_path9 = require("path");
|
|
3867
4168
|
|
|
3868
4169
|
// src/bundles/run-bundle.ts
|
|
3869
|
-
var
|
|
3870
|
-
var
|
|
4170
|
+
var import_fs8 = require("fs");
|
|
4171
|
+
var import_path8 = require("path");
|
|
3871
4172
|
|
|
3872
4173
|
// src/bundles/types.ts
|
|
3873
4174
|
var SUPPORTED_SCHEMA_VERSIONS = [1, 2];
|
|
3874
4175
|
|
|
3875
4176
|
// src/utils/marker-block.ts
|
|
3876
|
-
var
|
|
3877
|
-
var
|
|
4177
|
+
var import_fs7 = require("fs");
|
|
4178
|
+
var import_path7 = require("path");
|
|
3878
4179
|
function upsertMarkerBlock(filePath, startMarker, endMarker, body, opts = {}) {
|
|
3879
4180
|
const block = `${startMarker}
|
|
3880
4181
|
${body}
|
|
3881
4182
|
${endMarker}`;
|
|
3882
|
-
(0,
|
|
3883
|
-
if (!(0,
|
|
3884
|
-
(0,
|
|
4183
|
+
(0, import_fs7.mkdirSync)((0, import_path7.dirname)(filePath), { recursive: true });
|
|
4184
|
+
if (!(0, import_fs7.existsSync)(filePath)) {
|
|
4185
|
+
(0, import_fs7.writeFileSync)(filePath, block + "\n", "utf-8");
|
|
3885
4186
|
return;
|
|
3886
4187
|
}
|
|
3887
|
-
const existing = (0,
|
|
4188
|
+
const existing = (0, import_fs7.readFileSync)(filePath, "utf-8");
|
|
3888
4189
|
const s = existing.indexOf(startMarker);
|
|
3889
4190
|
const e = existing.indexOf(endMarker);
|
|
3890
4191
|
const hasS = s !== -1;
|
|
@@ -3894,12 +4195,43 @@ ${endMarker}`;
|
|
|
3894
4195
|
}
|
|
3895
4196
|
if (hasS && hasE && e > s) {
|
|
3896
4197
|
const updated = existing.slice(0, s) + block + existing.slice(e + endMarker.length);
|
|
3897
|
-
(0,
|
|
4198
|
+
(0, import_fs7.writeFileSync)(filePath, updated, "utf-8");
|
|
3898
4199
|
} else {
|
|
3899
|
-
const
|
|
3900
|
-
(0,
|
|
4200
|
+
const sep4 = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
4201
|
+
(0, import_fs7.writeFileSync)(filePath, existing + sep4 + block + "\n", "utf-8");
|
|
3901
4202
|
}
|
|
3902
4203
|
}
|
|
4204
|
+
function removeMarkerBlock(filePath, startMarker, endMarker, opts = {}) {
|
|
4205
|
+
if (!(0, import_fs7.existsSync)(filePath)) return "absent";
|
|
4206
|
+
const existing = (0, import_fs7.readFileSync)(filePath, "utf-8");
|
|
4207
|
+
const s = existing.indexOf(startMarker);
|
|
4208
|
+
const e = existing.indexOf(endMarker);
|
|
4209
|
+
if (s === -1 && e === -1) {
|
|
4210
|
+
} else if (s === -1 || e === -1 || e < s) {
|
|
4211
|
+
return "corrupt";
|
|
4212
|
+
}
|
|
4213
|
+
let updated = existing;
|
|
4214
|
+
if (s !== -1 && e !== -1) {
|
|
4215
|
+
const blockEnd = e + endMarker.length;
|
|
4216
|
+
let cutStart = s;
|
|
4217
|
+
let cutEnd = blockEnd;
|
|
4218
|
+
if (cutStart > 0 && existing[cutStart - 1] === "\n") cutStart -= 1;
|
|
4219
|
+
if (existing[cutEnd] === "\n") cutEnd += 1;
|
|
4220
|
+
updated = existing.slice(0, cutStart) + existing.slice(cutEnd);
|
|
4221
|
+
}
|
|
4222
|
+
if (opts.deleteFileIfEmpty) {
|
|
4223
|
+
const stripped = opts.emptyIgnorePattern ? updated.replace(opts.emptyIgnorePattern, "") : updated;
|
|
4224
|
+
if (stripped.trim() === "") {
|
|
4225
|
+
(0, import_fs7.unlinkSync)(filePath);
|
|
4226
|
+
return "deleted";
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4229
|
+
if (updated !== existing) {
|
|
4230
|
+
(0, import_fs7.writeFileSync)(filePath, updated, "utf-8");
|
|
4231
|
+
return "removed";
|
|
4232
|
+
}
|
|
4233
|
+
return "absent";
|
|
4234
|
+
}
|
|
3903
4235
|
|
|
3904
4236
|
// src/profiles/claude-code.ts
|
|
3905
4237
|
var claudeCodeProfile = {
|
|
@@ -4038,7 +4370,7 @@ function getProfile(agent) {
|
|
|
4038
4370
|
}
|
|
4039
4371
|
|
|
4040
4372
|
// src/bundles/run-bundle.ts
|
|
4041
|
-
var
|
|
4373
|
+
var OWNER_KEY2 = "_bundleOwner";
|
|
4042
4374
|
async function runBundle(manifest, ctx) {
|
|
4043
4375
|
assertSchemaCompatible(manifest);
|
|
4044
4376
|
assertNameValid(manifest);
|
|
@@ -4054,7 +4386,10 @@ function runBundleV1(manifest, ctx) {
|
|
|
4054
4386
|
const result = {
|
|
4055
4387
|
name: manifest.name,
|
|
4056
4388
|
opsExecuted: 0,
|
|
4057
|
-
filesTouched: []
|
|
4389
|
+
filesTouched: [],
|
|
4390
|
+
filesCreated: [],
|
|
4391
|
+
filesPatched: [],
|
|
4392
|
+
markerBlockFiles: []
|
|
4058
4393
|
};
|
|
4059
4394
|
const ops = manifest.targets?.[ctx.agent] ?? [];
|
|
4060
4395
|
for (const op of ops) {
|
|
@@ -4074,7 +4409,10 @@ function runBundleV2(manifest, ctx) {
|
|
|
4074
4409
|
const result = {
|
|
4075
4410
|
name: manifest.name,
|
|
4076
4411
|
opsExecuted: 0,
|
|
4077
|
-
filesTouched: []
|
|
4412
|
+
filesTouched: [],
|
|
4413
|
+
filesCreated: [],
|
|
4414
|
+
filesPatched: [],
|
|
4415
|
+
markerBlockFiles: []
|
|
4078
4416
|
};
|
|
4079
4417
|
const hookPatches = /* @__PURE__ */ new Map();
|
|
4080
4418
|
for (const asset of manifest.assets ?? []) {
|
|
@@ -4122,17 +4460,18 @@ function runAsset(asset, profile, hookPatches, ctx, result) {
|
|
|
4122
4460
|
function runAssetCopy(asset, handler, ctx, result) {
|
|
4123
4461
|
if (!handler.supported || !handler.destination) return;
|
|
4124
4462
|
const source = resolveBundlePath(asset.source, ctx);
|
|
4125
|
-
if (!(0,
|
|
4126
|
-
const isDirectory = (0,
|
|
4463
|
+
if (!(0, import_fs8.existsSync)(source)) return;
|
|
4464
|
+
const isDirectory = (0, import_fs8.statSync)(source).isDirectory();
|
|
4127
4465
|
const targetRel = handler.destination(asset.name, isDirectory);
|
|
4128
|
-
const target =
|
|
4466
|
+
const target = resolveProjectPath2(targetRel, ctx);
|
|
4129
4467
|
if (isDirectory) {
|
|
4130
4468
|
copyDirectory(source, target);
|
|
4131
4469
|
} else {
|
|
4132
|
-
(0,
|
|
4133
|
-
(0,
|
|
4470
|
+
(0, import_fs8.mkdirSync)((0, import_path8.dirname)(target), { recursive: true });
|
|
4471
|
+
(0, import_fs8.copyFileSync)(source, target);
|
|
4134
4472
|
}
|
|
4135
4473
|
result.filesTouched.push(targetRel);
|
|
4474
|
+
result.filesCreated.push(targetRel);
|
|
4136
4475
|
}
|
|
4137
4476
|
function accumulateHook(asset, handler, hookPatches, ctx, result) {
|
|
4138
4477
|
if (!handler.supported) return;
|
|
@@ -4141,13 +4480,14 @@ function accumulateHook(asset, handler, hookPatches, ctx, result) {
|
|
|
4141
4480
|
if (eventName === null) return;
|
|
4142
4481
|
if (asset.script && handler.scriptDir) {
|
|
4143
4482
|
const scriptSource = resolveBundlePath(asset.script, ctx);
|
|
4144
|
-
if ((0,
|
|
4145
|
-
const scriptName = (0,
|
|
4483
|
+
if ((0, import_fs8.existsSync)(scriptSource)) {
|
|
4484
|
+
const scriptName = (0, import_path8.basename)(asset.script);
|
|
4146
4485
|
const scriptRelDest = `${handler.scriptDir}/${scriptName}`;
|
|
4147
|
-
const scriptDest =
|
|
4148
|
-
(0,
|
|
4149
|
-
(0,
|
|
4486
|
+
const scriptDest = resolveProjectPath2(scriptRelDest, ctx);
|
|
4487
|
+
(0, import_fs8.mkdirSync)((0, import_path8.dirname)(scriptDest), { recursive: true });
|
|
4488
|
+
(0, import_fs8.copyFileSync)(scriptSource, scriptDest);
|
|
4150
4489
|
result.filesTouched.push(scriptRelDest);
|
|
4490
|
+
result.filesCreated.push(scriptRelDest);
|
|
4151
4491
|
}
|
|
4152
4492
|
}
|
|
4153
4493
|
const command = handler.scriptDir ? asset.command.replace(/\{hookDir\}/g, handler.scriptDir) : asset.command;
|
|
@@ -4181,22 +4521,24 @@ function substituteHookDir(op, profile) {
|
|
|
4181
4521
|
function writeGitignoreBlock(manifest, ctx, result) {
|
|
4182
4522
|
if (!manifest.gitignore?.length) return;
|
|
4183
4523
|
upsertMarkerBlock(
|
|
4184
|
-
(0,
|
|
4524
|
+
(0, import_path8.join)(ctx.projectPath, ".gitignore"),
|
|
4185
4525
|
`# ${manifest.name}:start`,
|
|
4186
4526
|
`# ${manifest.name}:end`,
|
|
4187
4527
|
manifest.gitignore.join("\n")
|
|
4188
4528
|
);
|
|
4189
4529
|
result.filesTouched.push(".gitignore");
|
|
4530
|
+
result.markerBlockFiles.push(".gitignore");
|
|
4190
4531
|
}
|
|
4191
4532
|
function writeInstructionsBlock(bundleName, snippet, instructionsFile, ctx, result) {
|
|
4192
4533
|
if (ctx.skipInstructions) return;
|
|
4193
4534
|
upsertMarkerBlock(
|
|
4194
|
-
(0,
|
|
4535
|
+
(0, import_path8.join)(ctx.projectPath, instructionsFile),
|
|
4195
4536
|
`<!-- ${bundleName}:start -->`,
|
|
4196
4537
|
`<!-- ${bundleName}:end -->`,
|
|
4197
4538
|
snippet
|
|
4198
4539
|
);
|
|
4199
4540
|
result.filesTouched.push(instructionsFile);
|
|
4541
|
+
result.markerBlockFiles.push(instructionsFile);
|
|
4200
4542
|
}
|
|
4201
4543
|
function executeOp(op, owner, ctx, result) {
|
|
4202
4544
|
switch (op.op) {
|
|
@@ -4214,63 +4556,65 @@ function executeOp(op, owner, ctx, result) {
|
|
|
4214
4556
|
}
|
|
4215
4557
|
function runCopy(op, ctx, result) {
|
|
4216
4558
|
const source = resolveBundlePath(op.from, ctx);
|
|
4217
|
-
const target =
|
|
4218
|
-
if (!(0,
|
|
4219
|
-
if ((0,
|
|
4559
|
+
const target = resolveProjectPath2(op.to, ctx);
|
|
4560
|
+
if (!(0, import_fs8.existsSync)(source)) return;
|
|
4561
|
+
if ((0, import_fs8.statSync)(source).isDirectory()) {
|
|
4220
4562
|
copyDirectory(source, target);
|
|
4221
4563
|
} else {
|
|
4222
|
-
(0,
|
|
4223
|
-
(0,
|
|
4564
|
+
(0, import_fs8.mkdirSync)((0, import_path8.dirname)(target), { recursive: true });
|
|
4565
|
+
(0, import_fs8.copyFileSync)(source, target);
|
|
4224
4566
|
}
|
|
4225
4567
|
result.filesTouched.push(op.to);
|
|
4568
|
+
result.filesCreated.push(op.to);
|
|
4226
4569
|
}
|
|
4227
4570
|
function runMergeJson(op, owner, ctx, result) {
|
|
4228
|
-
const filePath =
|
|
4571
|
+
const filePath = resolveProjectPath2(op.file, ctx);
|
|
4229
4572
|
const existing = readJsonOrEmpty(filePath);
|
|
4230
|
-
const stripped =
|
|
4573
|
+
const stripped = stripOwnedEntries2(existing, owner);
|
|
4231
4574
|
const tagged = tagEntries(op.patch, owner);
|
|
4232
4575
|
const merged = deepMerge2(stripped, tagged);
|
|
4233
|
-
(0,
|
|
4234
|
-
(0,
|
|
4576
|
+
(0, import_fs8.mkdirSync)((0, import_path8.dirname)(filePath), { recursive: true });
|
|
4577
|
+
(0, import_fs8.writeFileSync)(filePath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
4235
4578
|
result.filesTouched.push(op.file);
|
|
4579
|
+
result.filesPatched.push(op.file);
|
|
4236
4580
|
}
|
|
4237
4581
|
function readJsonOrEmpty(path) {
|
|
4238
|
-
if (!(0,
|
|
4582
|
+
if (!(0, import_fs8.existsSync)(path)) return {};
|
|
4239
4583
|
try {
|
|
4240
|
-
return JSON.parse((0,
|
|
4584
|
+
return JSON.parse((0, import_fs8.readFileSync)(path, "utf-8"));
|
|
4241
4585
|
} catch {
|
|
4242
4586
|
return {};
|
|
4243
4587
|
}
|
|
4244
4588
|
}
|
|
4245
|
-
function
|
|
4589
|
+
function isPlainObject3(v) {
|
|
4246
4590
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4247
4591
|
}
|
|
4248
4592
|
function tagEntries(value, owner) {
|
|
4249
4593
|
if (Array.isArray(value)) {
|
|
4250
4594
|
return value.map(
|
|
4251
|
-
(item) =>
|
|
4595
|
+
(item) => isPlainObject3(item) ? { ...item, [OWNER_KEY2]: owner } : item
|
|
4252
4596
|
);
|
|
4253
4597
|
}
|
|
4254
|
-
if (
|
|
4598
|
+
if (isPlainObject3(value)) {
|
|
4255
4599
|
return Object.fromEntries(
|
|
4256
4600
|
Object.entries(value).map(([k, v]) => [k, tagEntries(v, owner)])
|
|
4257
4601
|
);
|
|
4258
4602
|
}
|
|
4259
4603
|
return value;
|
|
4260
4604
|
}
|
|
4261
|
-
function
|
|
4605
|
+
function stripOwnedEntries2(value, owner) {
|
|
4262
4606
|
if (Array.isArray(value)) {
|
|
4263
|
-
return value.filter((item) => !(
|
|
4607
|
+
return value.filter((item) => !(isPlainObject3(item) && item[OWNER_KEY2] === owner)).map((item) => stripOwnedEntries2(item, owner));
|
|
4264
4608
|
}
|
|
4265
|
-
if (
|
|
4609
|
+
if (isPlainObject3(value)) {
|
|
4266
4610
|
return Object.fromEntries(
|
|
4267
|
-
Object.entries(value).map(([k, v]) => [k,
|
|
4611
|
+
Object.entries(value).map(([k, v]) => [k, stripOwnedEntries2(v, owner)])
|
|
4268
4612
|
);
|
|
4269
4613
|
}
|
|
4270
4614
|
return value;
|
|
4271
4615
|
}
|
|
4272
4616
|
function deepMerge2(a, b) {
|
|
4273
|
-
if (
|
|
4617
|
+
if (isPlainObject3(a) && isPlainObject3(b)) {
|
|
4274
4618
|
const out = { ...a };
|
|
4275
4619
|
for (const [k, v] of Object.entries(b)) {
|
|
4276
4620
|
out[k] = k in a ? deepMerge2(a[k], v) : v;
|
|
@@ -4283,17 +4627,17 @@ function deepMerge2(a, b) {
|
|
|
4283
4627
|
return b;
|
|
4284
4628
|
}
|
|
4285
4629
|
function resolveBundlePath(rel, ctx) {
|
|
4286
|
-
const base = (0,
|
|
4287
|
-
const full = (0,
|
|
4288
|
-
if (full !== base && !full.startsWith(base +
|
|
4630
|
+
const base = (0, import_path8.resolve)(ctx.bundleRoot);
|
|
4631
|
+
const full = (0, import_path8.resolve)((0, import_path8.join)(base, rel));
|
|
4632
|
+
if (full !== base && !full.startsWith(base + import_path8.sep)) {
|
|
4289
4633
|
throw new Error(`Bundle op "from" escapes bundle root: ${rel}`);
|
|
4290
4634
|
}
|
|
4291
4635
|
return full;
|
|
4292
4636
|
}
|
|
4293
|
-
function
|
|
4294
|
-
const base = (0,
|
|
4295
|
-
const full = (0,
|
|
4296
|
-
if (full !== base && !full.startsWith(base +
|
|
4637
|
+
function resolveProjectPath2(rel, ctx) {
|
|
4638
|
+
const base = (0, import_path8.resolve)(ctx.projectPath);
|
|
4639
|
+
const full = (0, import_path8.resolve)((0, import_path8.join)(base, rel));
|
|
4640
|
+
if (full !== base && !full.startsWith(base + import_path8.sep)) {
|
|
4297
4641
|
throw new Error(`Bundle op "to" escapes project path: ${rel}`);
|
|
4298
4642
|
}
|
|
4299
4643
|
return full;
|
|
@@ -4314,7 +4658,7 @@ function assertNameValid(manifest) {
|
|
|
4314
4658
|
}
|
|
4315
4659
|
}
|
|
4316
4660
|
function assertBundleRootExists(ctx) {
|
|
4317
|
-
if (!(0,
|
|
4661
|
+
if (!(0, import_fs8.existsSync)(ctx.bundleRoot) || !(0, import_fs8.statSync)(ctx.bundleRoot).isDirectory()) {
|
|
4318
4662
|
throw new Error(`Bundle root not found or not a directory: ${ctx.bundleRoot}`);
|
|
4319
4663
|
}
|
|
4320
4664
|
}
|
|
@@ -4332,14 +4676,67 @@ var fetch_factory_default = defineStep({
|
|
|
4332
4676
|
if (result.instructionsSnippet) {
|
|
4333
4677
|
ctx.installed.factoryInstructionsSnippet = result.instructionsSnippet;
|
|
4334
4678
|
}
|
|
4679
|
+
const record = {
|
|
4680
|
+
bundleName: "factory",
|
|
4681
|
+
filesCreated: result.filesCreated,
|
|
4682
|
+
filesPatched: result.filesPatched,
|
|
4683
|
+
markerBlockFiles: result.markerBlockFiles
|
|
4684
|
+
};
|
|
4335
4685
|
if (!result.success) {
|
|
4336
|
-
return { status: "failed", detail: result.failureReason };
|
|
4686
|
+
return { status: "failed", detail: result.failureReason, record };
|
|
4687
|
+
}
|
|
4688
|
+
return { status: "success", message: result.filesInstalled.join(", "), record };
|
|
4689
|
+
},
|
|
4690
|
+
inverse: {
|
|
4691
|
+
label: "Removing Factory framework",
|
|
4692
|
+
execute: async (raw, ctx) => {
|
|
4693
|
+
const rec = raw ?? {};
|
|
4694
|
+
const bundle = rec.bundleName ?? "factory";
|
|
4695
|
+
const projectPath = ctx.config.projectPath;
|
|
4696
|
+
const summary = [];
|
|
4697
|
+
for (const rel of rec.filesPatched ?? []) {
|
|
4698
|
+
const abs = resolveProjectPath(rel, projectPath);
|
|
4699
|
+
if (stripBundleOwnerFromJsonFile(abs, bundle)) summary.push(`stripped ${rel}`);
|
|
4700
|
+
deleteJsonIfEffectivelyEmpty(abs);
|
|
4701
|
+
}
|
|
4702
|
+
for (const rel of rec.markerBlockFiles ?? []) {
|
|
4703
|
+
const abs = resolveProjectPath(rel, projectPath);
|
|
4704
|
+
if (rel === ".gitignore") {
|
|
4705
|
+
removeMarkerBlock(abs, `# ${bundle}:start`, `# ${bundle}:end`);
|
|
4706
|
+
} else {
|
|
4707
|
+
removeMarkerBlock(abs, `<!-- ${bundle}:start -->`, `<!-- ${bundle}:end -->`);
|
|
4708
|
+
}
|
|
4709
|
+
}
|
|
4710
|
+
const created = rec.filesCreated ?? [];
|
|
4711
|
+
const sortedCreated = [...created].sort((a, b) => a.length - b.length);
|
|
4712
|
+
for (const rel of sortedCreated) {
|
|
4713
|
+
const abs = resolveProjectPath(rel, projectPath);
|
|
4714
|
+
if (!(0, import_fs9.existsSync)(abs)) continue;
|
|
4715
|
+
if (isDirectorySafe(abs)) {
|
|
4716
|
+
deleteDirIfExists(abs);
|
|
4717
|
+
summary.push(`removed ${rel}`);
|
|
4718
|
+
} else {
|
|
4719
|
+
deleteFileIfExists(abs);
|
|
4720
|
+
summary.push(`removed ${rel}`);
|
|
4721
|
+
}
|
|
4722
|
+
}
|
|
4723
|
+
const pruned = pruneEmptyAncestors(created, projectPath);
|
|
4724
|
+
if (pruned.length > 0) summary.push(`pruned ${pruned.length} empty dir(s)`);
|
|
4725
|
+
return {
|
|
4726
|
+
status: "success",
|
|
4727
|
+
message: summary.length > 0 ? `${summary.length} action(s)` : "nothing to remove"
|
|
4728
|
+
};
|
|
4337
4729
|
}
|
|
4338
|
-
return { status: "success", message: result.filesInstalled.join(", ") };
|
|
4339
4730
|
}
|
|
4340
4731
|
});
|
|
4341
4732
|
async function fetchFactory(token, repo, targetDir, agent) {
|
|
4342
|
-
const result = {
|
|
4733
|
+
const result = {
|
|
4734
|
+
success: false,
|
|
4735
|
+
filesInstalled: [],
|
|
4736
|
+
filesCreated: [],
|
|
4737
|
+
filesPatched: [],
|
|
4738
|
+
markerBlockFiles: []
|
|
4739
|
+
};
|
|
4343
4740
|
let release;
|
|
4344
4741
|
try {
|
|
4345
4742
|
release = await fetchLatestRelease(token, repo);
|
|
@@ -4355,15 +4752,15 @@ async function fetchFactory(token, repo, targetDir, agent) {
|
|
|
4355
4752
|
let archive = null;
|
|
4356
4753
|
try {
|
|
4357
4754
|
archive = await downloadAndExtractAsset(token, asset, targetDir, ".temp-factory-download");
|
|
4358
|
-
const extractedEntries = (0,
|
|
4755
|
+
const extractedEntries = (0, import_fs9.readdirSync)(archive.extractedRoot);
|
|
4359
4756
|
const extractedFolder = extractedEntries.find((e) => e.toLowerCase() === FACTORY_ROOT_FOLDER.toLowerCase());
|
|
4360
4757
|
if (!extractedFolder) {
|
|
4361
4758
|
result.failureReason = `No ${FACTORY_ROOT_FOLDER}/ folder in archive`;
|
|
4362
4759
|
return result;
|
|
4363
4760
|
}
|
|
4364
|
-
const extractedPath = (0,
|
|
4365
|
-
const manifestPath = (0,
|
|
4366
|
-
if (!(0,
|
|
4761
|
+
const extractedPath = (0, import_path9.join)(archive.extractedRoot, extractedFolder);
|
|
4762
|
+
const manifestPath = (0, import_path9.join)(extractedPath, "install.json");
|
|
4763
|
+
if (!(0, import_fs9.existsSync)(manifestPath)) {
|
|
4367
4764
|
result.failureReason = "Factory archive has no install.json \u2014 upgrade Factory or downgrade installer";
|
|
4368
4765
|
return result;
|
|
4369
4766
|
}
|
|
@@ -4379,7 +4776,7 @@ async function fetchFactory(token, repo, targetDir, agent) {
|
|
|
4379
4776
|
async function installViaBundle(manifestPath, extractedPath, targetDir, agent, result) {
|
|
4380
4777
|
let manifest;
|
|
4381
4778
|
try {
|
|
4382
|
-
manifest = JSON.parse((0,
|
|
4779
|
+
manifest = JSON.parse((0, import_fs9.readFileSync)(manifestPath, "utf-8"));
|
|
4383
4780
|
} catch (e) {
|
|
4384
4781
|
throw new Error(`install.json is not valid JSON: ${e instanceof Error ? e.message : String(e)}`);
|
|
4385
4782
|
}
|
|
@@ -4394,11 +4791,14 @@ async function installViaBundle(manifestPath, extractedPath, targetDir, agent, r
|
|
|
4394
4791
|
});
|
|
4395
4792
|
result.instructionsSnippet = runResult.instructionsSnippet;
|
|
4396
4793
|
result.filesInstalled = runResult.filesTouched;
|
|
4794
|
+
result.filesCreated = runResult.filesCreated;
|
|
4795
|
+
result.filesPatched = runResult.filesPatched;
|
|
4796
|
+
result.markerBlockFiles = runResult.markerBlockFiles;
|
|
4397
4797
|
}
|
|
4398
4798
|
|
|
4399
4799
|
// src/steps/resources/fetch-abap-hooks.ts
|
|
4400
|
-
var
|
|
4401
|
-
var
|
|
4800
|
+
var import_fs10 = require("fs");
|
|
4801
|
+
var import_path10 = require("path");
|
|
4402
4802
|
var HOOKS_ASSET_NAME = "sap-hooks.tar.gz";
|
|
4403
4803
|
var HOOKS_TAG_PREFIX = "hooks-v";
|
|
4404
4804
|
var HOOKS_ROOT_FOLDER = "hooks";
|
|
@@ -4466,14 +4866,55 @@ var fetch_abap_hooks_default = defineStep({
|
|
|
4466
4866
|
if (result.instructionsSnippet) {
|
|
4467
4867
|
ctx.installed.abapHooksInstructionsSnippet = result.instructionsSnippet;
|
|
4468
4868
|
}
|
|
4869
|
+
const record = {
|
|
4870
|
+
bundleName: "abap-mcp-hooks",
|
|
4871
|
+
filesCreated: result.filesCreated,
|
|
4872
|
+
filesPatched: result.filesPatched,
|
|
4873
|
+
markerBlockFiles: result.markerBlockFiles
|
|
4874
|
+
};
|
|
4469
4875
|
if (!result.success) {
|
|
4470
|
-
return { status: "failed", detail: result.failureReason };
|
|
4876
|
+
return { status: "failed", detail: result.failureReason, record };
|
|
4877
|
+
}
|
|
4878
|
+
return { status: "success", message: result.filesInstalled.join(", "), record };
|
|
4879
|
+
},
|
|
4880
|
+
inverse: {
|
|
4881
|
+
label: "Removing ABAP MCP hooks",
|
|
4882
|
+
execute: async (raw, ctx) => {
|
|
4883
|
+
const rec = raw ?? {};
|
|
4884
|
+
const bundle = rec.bundleName ?? "abap-mcp-hooks";
|
|
4885
|
+
const projectPath = ctx.config.projectPath;
|
|
4886
|
+
for (const rel of rec.filesPatched ?? []) {
|
|
4887
|
+
const abs = resolveProjectPath(rel, projectPath);
|
|
4888
|
+
stripBundleOwnerFromJsonFile(abs, bundle);
|
|
4889
|
+
deleteJsonIfEffectivelyEmpty(abs);
|
|
4890
|
+
}
|
|
4891
|
+
for (const rel of rec.markerBlockFiles ?? []) {
|
|
4892
|
+
const abs = resolveProjectPath(rel, projectPath);
|
|
4893
|
+
if (rel === ".gitignore") {
|
|
4894
|
+
removeMarkerBlock(abs, `# ${bundle}:start`, `# ${bundle}:end`);
|
|
4895
|
+
} else {
|
|
4896
|
+
removeMarkerBlock(abs, `<!-- ${bundle}:start -->`, `<!-- ${bundle}:end -->`);
|
|
4897
|
+
}
|
|
4898
|
+
}
|
|
4899
|
+
const created = rec.filesCreated ?? [];
|
|
4900
|
+
for (const rel of created) {
|
|
4901
|
+
const abs = resolveProjectPath(rel, projectPath);
|
|
4902
|
+
if (isDirectorySafe(abs)) deleteDirIfExists(abs);
|
|
4903
|
+
else deleteFileIfExists(abs);
|
|
4904
|
+
}
|
|
4905
|
+
pruneEmptyAncestors(created, projectPath);
|
|
4906
|
+
return { status: "success" };
|
|
4471
4907
|
}
|
|
4472
|
-
return { status: "success", message: result.filesInstalled.join(", ") };
|
|
4473
4908
|
}
|
|
4474
4909
|
});
|
|
4475
4910
|
async function fetchAbapHooks(token, repo, targetDir, agent) {
|
|
4476
|
-
const result = {
|
|
4911
|
+
const result = {
|
|
4912
|
+
success: false,
|
|
4913
|
+
filesInstalled: [],
|
|
4914
|
+
filesCreated: [],
|
|
4915
|
+
filesPatched: [],
|
|
4916
|
+
markerBlockFiles: []
|
|
4917
|
+
};
|
|
4477
4918
|
let release;
|
|
4478
4919
|
try {
|
|
4479
4920
|
release = await fetchLatestReleaseByTagPrefix(token, repo, HOOKS_TAG_PREFIX);
|
|
@@ -4489,7 +4930,7 @@ async function fetchAbapHooks(token, repo, targetDir, agent) {
|
|
|
4489
4930
|
let archive = null;
|
|
4490
4931
|
try {
|
|
4491
4932
|
archive = await downloadAndExtractAsset(token, asset, targetDir, ".temp-abap-hooks-download");
|
|
4492
|
-
const extractedEntries = (0,
|
|
4933
|
+
const extractedEntries = (0, import_fs10.readdirSync)(archive.extractedRoot);
|
|
4493
4934
|
const innerFolder = extractedEntries.find(
|
|
4494
4935
|
(e) => e.toLowerCase() === HOOKS_ROOT_FOLDER.toLowerCase()
|
|
4495
4936
|
);
|
|
@@ -4497,12 +4938,12 @@ async function fetchAbapHooks(token, repo, targetDir, agent) {
|
|
|
4497
4938
|
result.failureReason = `No ${HOOKS_ROOT_FOLDER}/ folder in archive`;
|
|
4498
4939
|
return result;
|
|
4499
4940
|
}
|
|
4500
|
-
const bundleRoot = (0,
|
|
4501
|
-
if (!(0,
|
|
4941
|
+
const bundleRoot = (0, import_path10.join)(archive.extractedRoot, innerFolder);
|
|
4942
|
+
if (!(0, import_fs10.statSync)(bundleRoot).isDirectory()) {
|
|
4502
4943
|
result.failureReason = `${HOOKS_ROOT_FOLDER}/ entry is not a directory`;
|
|
4503
4944
|
return result;
|
|
4504
4945
|
}
|
|
4505
|
-
const presentDefs = HOOK_DEFINITIONS.filter((d) => (0,
|
|
4946
|
+
const presentDefs = HOOK_DEFINITIONS.filter((d) => (0, import_fs10.existsSync)((0, import_path10.join)(bundleRoot, d.script)));
|
|
4506
4947
|
if (presentDefs.length === 0) {
|
|
4507
4948
|
result.failureReason = "Archive contains no recognized hook scripts";
|
|
4508
4949
|
return result;
|
|
@@ -4542,6 +4983,9 @@ async function fetchAbapHooks(token, repo, targetDir, agent) {
|
|
|
4542
4983
|
skipInstructions: true
|
|
4543
4984
|
});
|
|
4544
4985
|
result.filesInstalled = runResult.filesTouched;
|
|
4986
|
+
result.filesCreated = runResult.filesCreated;
|
|
4987
|
+
result.filesPatched = runResult.filesPatched;
|
|
4988
|
+
result.markerBlockFiles = runResult.markerBlockFiles;
|
|
4545
4989
|
result.instructionsSnippet = runResult.instructionsSnippet;
|
|
4546
4990
|
result.success = true;
|
|
4547
4991
|
} catch (error) {
|
|
@@ -4553,8 +4997,8 @@ async function fetchAbapHooks(token, repo, targetDir, agent) {
|
|
|
4553
4997
|
}
|
|
4554
4998
|
|
|
4555
4999
|
// src/steps/setup/write-instructions.ts
|
|
4556
|
-
var
|
|
4557
|
-
var
|
|
5000
|
+
var import_fs11 = require("fs");
|
|
5001
|
+
var import_path11 = require("path");
|
|
4558
5002
|
|
|
4559
5003
|
// src/steps/shared.ts
|
|
4560
5004
|
var cached = null;
|
|
@@ -4589,17 +5033,23 @@ var write_instructions_default = defineStep({
|
|
|
4589
5033
|
const projectPath = ctx.config.projectPath;
|
|
4590
5034
|
const content = buildCombinedInstructions(domains, filteredMcpConfig, agent, projectPath);
|
|
4591
5035
|
const target = getAgentTarget(agent);
|
|
4592
|
-
const filePath = (0,
|
|
4593
|
-
const fileDir = (0,
|
|
4594
|
-
if (!(0,
|
|
4595
|
-
|
|
4596
|
-
|
|
5036
|
+
const filePath = (0, import_path11.join)(projectPath, target.instructions);
|
|
5037
|
+
const fileDir = (0, import_path11.dirname)(filePath);
|
|
5038
|
+
if (!(0, import_fs11.existsSync)(fileDir)) (0, import_fs11.mkdirSync)(fileDir, { recursive: true });
|
|
5039
|
+
const fileExistedBefore = (0, import_fs11.existsSync)(filePath);
|
|
5040
|
+
let cursorFrontmatterWritten = false;
|
|
5041
|
+
if (agent === "cursor" && !fileExistedBefore) {
|
|
5042
|
+
(0, import_fs11.writeFileSync)(filePath, `---
|
|
4597
5043
|
description: AI development instructions from One-Shot Installer
|
|
4598
5044
|
alwaysApply: true
|
|
4599
5045
|
---
|
|
4600
5046
|
|
|
4601
5047
|
`, "utf-8");
|
|
5048
|
+
cursorFrontmatterWritten = true;
|
|
4602
5049
|
}
|
|
5050
|
+
const markerBlocks = [
|
|
5051
|
+
{ start: MARKER_START, end: MARKER_END }
|
|
5052
|
+
];
|
|
4603
5053
|
upsertMarkerBlock(filePath, MARKER_START, MARKER_END, content, {
|
|
4604
5054
|
onCorrupt: (file) => {
|
|
4605
5055
|
const fileName = file.split(/[/\\]/).pop() ?? file;
|
|
@@ -4627,6 +5077,7 @@ alwaysApply: true
|
|
|
4627
5077
|
"<!-- factory:end -->",
|
|
4628
5078
|
ctx.installed.factoryInstructionsSnippet
|
|
4629
5079
|
);
|
|
5080
|
+
markerBlocks.push({ start: "<!-- factory:start -->", end: "<!-- factory:end -->" });
|
|
4630
5081
|
}
|
|
4631
5082
|
if (ctx.installed.abapHooksInstructionsSnippet) {
|
|
4632
5083
|
upsertMarkerBlock(
|
|
@@ -4635,21 +5086,50 @@ alwaysApply: true
|
|
|
4635
5086
|
"<!-- abap-mcp-hooks:end -->",
|
|
4636
5087
|
ctx.installed.abapHooksInstructionsSnippet
|
|
4637
5088
|
);
|
|
5089
|
+
markerBlocks.push({ start: "<!-- abap-mcp-hooks:start -->", end: "<!-- abap-mcp-hooks:end -->" });
|
|
5090
|
+
}
|
|
5091
|
+
const record = {
|
|
5092
|
+
filePath: target.instructions,
|
|
5093
|
+
markerBlocks,
|
|
5094
|
+
fileExistedBefore,
|
|
5095
|
+
cursorFrontmatterWritten
|
|
5096
|
+
};
|
|
5097
|
+
return { status: "success", message: target.instructions, record };
|
|
5098
|
+
},
|
|
5099
|
+
inverse: {
|
|
5100
|
+
label: "Restoring instructions file",
|
|
5101
|
+
execute: async (raw, ctx) => {
|
|
5102
|
+
const rec = raw ?? {};
|
|
5103
|
+
if (!rec.filePath) return { status: "skipped", detail: "no file recorded" };
|
|
5104
|
+
const abs = resolveProjectPath(rec.filePath, ctx.config.projectPath);
|
|
5105
|
+
const blocks = rec.markerBlocks ?? [];
|
|
5106
|
+
const cursorPreambleRe = /^---[\s\S]*?---\s*/;
|
|
5107
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
5108
|
+
const b = blocks[i];
|
|
5109
|
+
const isLast = i === blocks.length - 1;
|
|
5110
|
+
removeMarkerBlock(abs, b.start, b.end, {
|
|
5111
|
+
/* Only attempt to delete the file after the LAST block is
|
|
5112
|
+
* removed — if we delete on the first pass we'd lose later
|
|
5113
|
+
* blocks waiting in the same file. */
|
|
5114
|
+
deleteFileIfEmpty: isLast && !rec.fileExistedBefore,
|
|
5115
|
+
emptyIgnorePattern: rec.cursorFrontmatterWritten ? cursorPreambleRe : void 0
|
|
5116
|
+
});
|
|
5117
|
+
}
|
|
5118
|
+
return { status: "success", message: rec.filePath };
|
|
4638
5119
|
}
|
|
4639
|
-
return { status: "success", message: target.instructions };
|
|
4640
5120
|
}
|
|
4641
5121
|
});
|
|
4642
5122
|
function collectMdFiles(dir) {
|
|
4643
|
-
if (!(0,
|
|
4644
|
-
const entries = (0,
|
|
5123
|
+
if (!(0, import_fs11.existsSync)(dir)) return [];
|
|
5124
|
+
const entries = (0, import_fs11.readdirSync)(dir, { recursive: true });
|
|
4645
5125
|
return entries.filter((entry) => {
|
|
4646
|
-
const fullPath = (0,
|
|
4647
|
-
return entry.endsWith(".md") && (0,
|
|
5126
|
+
const fullPath = (0, import_path11.join)(dir, entry);
|
|
5127
|
+
return entry.endsWith(".md") && (0, import_fs11.statSync)(fullPath).isFile();
|
|
4648
5128
|
}).map((entry) => entry.replace(/\\/g, "/")).sort();
|
|
4649
5129
|
}
|
|
4650
5130
|
function extractFirstHeading(filePath) {
|
|
4651
5131
|
try {
|
|
4652
|
-
const content = (0,
|
|
5132
|
+
const content = (0, import_fs11.readFileSync)(filePath, "utf-8");
|
|
4653
5133
|
const withoutFrontmatter = content.replace(/^---[\s\S]*?---\s*/, "");
|
|
4654
5134
|
const match = withoutFrontmatter.match(/^#\s+(.+)/m);
|
|
4655
5135
|
if (match) {
|
|
@@ -4667,16 +5147,16 @@ function formatPathRef(contextPath, description, agent) {
|
|
|
4667
5147
|
return `- \`${contextPath}\` \u2014 ${description}`;
|
|
4668
5148
|
}
|
|
4669
5149
|
function resolveDomainFolder(domain, contextsDir) {
|
|
4670
|
-
if ((0,
|
|
5150
|
+
if ((0, import_fs11.existsSync)(contextsDir)) {
|
|
4671
5151
|
try {
|
|
4672
|
-
const entries = (0,
|
|
5152
|
+
const entries = (0, import_fs11.readdirSync)(contextsDir);
|
|
4673
5153
|
const match = entries.find((e) => e.toLowerCase() === domain.toLowerCase());
|
|
4674
|
-
if (match) return { folderName: match, folderPath: (0,
|
|
5154
|
+
if (match) return { folderName: match, folderPath: (0, import_path11.join)(contextsDir, match) };
|
|
4675
5155
|
} catch {
|
|
4676
5156
|
}
|
|
4677
5157
|
}
|
|
4678
5158
|
const fallback = domain.toUpperCase();
|
|
4679
|
-
return { folderName: fallback, folderPath: (0,
|
|
5159
|
+
return { folderName: fallback, folderPath: (0, import_path11.join)(contextsDir, fallback) };
|
|
4680
5160
|
}
|
|
4681
5161
|
function buildContextRefsSection(domains, agent, contextsDir) {
|
|
4682
5162
|
const lines2 = [
|
|
@@ -4688,34 +5168,34 @@ function buildContextRefsSection(domains, agent, contextsDir) {
|
|
|
4688
5168
|
let hasAnyFiles = false;
|
|
4689
5169
|
for (const domain of domains) {
|
|
4690
5170
|
const { folderName, folderPath: domainPath } = resolveDomainFolder(domain, contextsDir);
|
|
4691
|
-
if (!(0,
|
|
5171
|
+
if (!(0, import_fs11.existsSync)(domainPath)) continue;
|
|
4692
5172
|
const domainFiles = [];
|
|
4693
|
-
const ctxInstructions = (0,
|
|
4694
|
-
if ((0,
|
|
5173
|
+
const ctxInstructions = (0, import_path11.join)(domainPath, "context-instructions.md");
|
|
5174
|
+
if ((0, import_fs11.existsSync)(ctxInstructions)) {
|
|
4695
5175
|
const desc = extractFirstHeading(ctxInstructions);
|
|
4696
5176
|
domainFiles.push(formatPathRef(`_ai-context/${folderName}/context-instructions.md`, desc, agent));
|
|
4697
5177
|
}
|
|
4698
|
-
const instructionsMd = (0,
|
|
4699
|
-
if ((0,
|
|
5178
|
+
const instructionsMd = (0, import_path11.join)(domainPath, "core", "instructions.md");
|
|
5179
|
+
if ((0, import_fs11.existsSync)(instructionsMd)) {
|
|
4700
5180
|
const desc = extractFirstHeading(instructionsMd);
|
|
4701
5181
|
domainFiles.push(formatPathRef(`_ai-context/${folderName}/core/instructions.md`, `${desc} (start here)`, agent));
|
|
4702
5182
|
}
|
|
4703
|
-
const coreDir = (0,
|
|
4704
|
-
if ((0,
|
|
5183
|
+
const coreDir = (0, import_path11.join)(domainPath, "core");
|
|
5184
|
+
if ((0, import_fs11.existsSync)(coreDir)) {
|
|
4705
5185
|
const coreFiles = collectMdFiles(coreDir).filter((f) => f !== "instructions.md" && !f.startsWith("instructions/"));
|
|
4706
5186
|
for (const file of coreFiles) {
|
|
4707
|
-
const desc = extractFirstHeading((0,
|
|
5187
|
+
const desc = extractFirstHeading((0, import_path11.join)(coreDir, file));
|
|
4708
5188
|
domainFiles.push(formatPathRef(`_ai-context/${folderName}/core/${file}`, desc, agent));
|
|
4709
5189
|
}
|
|
4710
5190
|
}
|
|
4711
|
-
const refDir = (0,
|
|
4712
|
-
if ((0,
|
|
5191
|
+
const refDir = (0, import_path11.join)(domainPath, "reference");
|
|
5192
|
+
if ((0, import_fs11.existsSync)(refDir)) {
|
|
4713
5193
|
const refFiles = collectMdFiles(refDir);
|
|
4714
5194
|
if (refFiles.length > 0) {
|
|
4715
5195
|
domainFiles.push(``);
|
|
4716
5196
|
domainFiles.push(`**Reference & cheat sheets:**`);
|
|
4717
5197
|
for (const file of refFiles) {
|
|
4718
|
-
const desc = extractFirstHeading((0,
|
|
5198
|
+
const desc = extractFirstHeading((0, import_path11.join)(refDir, file));
|
|
4719
5199
|
domainFiles.push(formatPathRef(`_ai-context/${folderName}/reference/${file}`, desc, agent));
|
|
4720
5200
|
}
|
|
4721
5201
|
}
|
|
@@ -4746,7 +5226,7 @@ function buildMCPSection(mcpConfig) {
|
|
|
4746
5226
|
return lines2.join("\n");
|
|
4747
5227
|
}
|
|
4748
5228
|
function buildCombinedInstructions(domains, mcpConfig, agent, projectPath) {
|
|
4749
|
-
const contextsDir = (0,
|
|
5229
|
+
const contextsDir = (0, import_path11.join)(projectPath, "_ai-context");
|
|
4750
5230
|
const lines2 = [`# AI Development Instructions`, ``, `> Generated by One-Shot Installer`, ``];
|
|
4751
5231
|
lines2.push(buildContextRefsSection(domains, agent, contextsDir));
|
|
4752
5232
|
if (Object.keys(mcpConfig).length > 0) lines2.push(buildMCPSection(mcpConfig));
|
|
@@ -4754,8 +5234,8 @@ function buildCombinedInstructions(domains, mcpConfig, agent, projectPath) {
|
|
|
4754
5234
|
}
|
|
4755
5235
|
|
|
4756
5236
|
// src/steps/setup/write-mcp-config.ts
|
|
4757
|
-
var
|
|
4758
|
-
var
|
|
5237
|
+
var import_fs12 = require("fs");
|
|
5238
|
+
var import_path12 = require("path");
|
|
4759
5239
|
var red2 = (text) => `\x1B[31m${text}\x1B[0m`;
|
|
4760
5240
|
var write_mcp_config_default = defineStep({
|
|
4761
5241
|
name: "write-mcp-config",
|
|
@@ -4764,15 +5244,16 @@ var write_mcp_config_default = defineStep({
|
|
|
4764
5244
|
execute: async (ctx) => {
|
|
4765
5245
|
const filteredMcpConfig = getFilteredMcpConfig(ctx);
|
|
4766
5246
|
const target = getAgentTarget(ctx.config.agent);
|
|
4767
|
-
const mcpJsonPath = (0,
|
|
5247
|
+
const mcpJsonPath = (0, import_path12.join)(ctx.config.projectPath, target.mcpConfig);
|
|
5248
|
+
const fileExistedBefore = (0, import_fs12.existsSync)(mcpJsonPath);
|
|
4768
5249
|
if (target.mcpDir) {
|
|
4769
|
-
const dir = (0,
|
|
4770
|
-
if (!(0,
|
|
5250
|
+
const dir = (0, import_path12.join)(ctx.config.projectPath, target.mcpDir);
|
|
5251
|
+
if (!(0, import_fs12.existsSync)(dir)) (0, import_fs12.mkdirSync)(dir, { recursive: true });
|
|
4771
5252
|
}
|
|
4772
5253
|
let existingFile = {};
|
|
4773
|
-
if ((0,
|
|
5254
|
+
if ((0, import_fs12.existsSync)(mcpJsonPath)) {
|
|
4774
5255
|
try {
|
|
4775
|
-
existingFile = JSON.parse((0,
|
|
5256
|
+
existingFile = JSON.parse((0, import_fs12.readFileSync)(mcpJsonPath, "utf-8"));
|
|
4776
5257
|
} catch {
|
|
4777
5258
|
const box = [
|
|
4778
5259
|
"",
|
|
@@ -4798,12 +5279,35 @@ var write_mcp_config_default = defineStep({
|
|
|
4798
5279
|
}
|
|
4799
5280
|
const mergedServers = { ...existingServers, ...newServers };
|
|
4800
5281
|
const outputFile = { ...existingFile, [target.mcpRootKey]: mergedServers };
|
|
4801
|
-
(0,
|
|
5282
|
+
(0, import_fs12.writeFileSync)(mcpJsonPath, JSON.stringify(outputFile, null, 2), "utf-8");
|
|
4802
5283
|
ctx.installed.mcpServersAdded = addedServers;
|
|
5284
|
+
const record = {
|
|
5285
|
+
filePath: target.mcpConfig,
|
|
5286
|
+
rootKey: target.mcpRootKey,
|
|
5287
|
+
serversAdded: addedServers,
|
|
5288
|
+
fileExistedBefore
|
|
5289
|
+
};
|
|
4803
5290
|
return {
|
|
4804
5291
|
status: "success",
|
|
4805
|
-
message: addedServers.length > 0 ? addedServers.join(", ") : void 0
|
|
5292
|
+
message: addedServers.length > 0 ? addedServers.join(", ") : void 0,
|
|
5293
|
+
record
|
|
4806
5294
|
};
|
|
5295
|
+
},
|
|
5296
|
+
inverse: {
|
|
5297
|
+
label: "Restoring MCP configuration",
|
|
5298
|
+
execute: async (raw, ctx) => {
|
|
5299
|
+
const rec = raw ?? {};
|
|
5300
|
+
if (!rec.filePath || !rec.rootKey) return { status: "skipped" };
|
|
5301
|
+
const abs = resolveProjectPath(rec.filePath, ctx.config.projectPath);
|
|
5302
|
+
const { changed, rootEmpty } = stripMcpServers(abs, rec.rootKey, rec.serversAdded ?? []);
|
|
5303
|
+
if (rootEmpty && !rec.fileExistedBefore) {
|
|
5304
|
+
deleteFileIfExists(abs);
|
|
5305
|
+
}
|
|
5306
|
+
return {
|
|
5307
|
+
status: "success",
|
|
5308
|
+
message: changed ? rec.filePath : "no changes needed"
|
|
5309
|
+
};
|
|
5310
|
+
}
|
|
4807
5311
|
}
|
|
4808
5312
|
});
|
|
4809
5313
|
|
|
@@ -4844,21 +5348,54 @@ var run_mcp_install_commands_default = defineStep({
|
|
|
4844
5348
|
failed.push({ name, reason });
|
|
4845
5349
|
}
|
|
4846
5350
|
}
|
|
5351
|
+
const record = { registeredServers: succeeded };
|
|
4847
5352
|
if (failed.length === 0) {
|
|
4848
5353
|
return {
|
|
4849
5354
|
status: "success",
|
|
4850
|
-
message: succeeded.length > 0 ? succeeded.join(", ") : void 0
|
|
5355
|
+
message: succeeded.length > 0 ? succeeded.join(", ") : void 0,
|
|
5356
|
+
record
|
|
4851
5357
|
};
|
|
4852
5358
|
}
|
|
4853
5359
|
const failedSummary = failed.map((f) => `${f.name} (${f.reason})`).join("; ");
|
|
4854
5360
|
if (succeeded.length === 0) {
|
|
4855
|
-
return { status: "failed", detail: failedSummary };
|
|
5361
|
+
return { status: "failed", detail: failedSummary, record };
|
|
4856
5362
|
}
|
|
4857
5363
|
return {
|
|
4858
5364
|
status: "success",
|
|
4859
5365
|
message: `${succeeded.join(", ")}; failed: ${failed.map((f) => f.name).join(", ")}`,
|
|
4860
|
-
detail: `Some registrations failed: ${failedSummary}
|
|
5366
|
+
detail: `Some registrations failed: ${failedSummary}`,
|
|
5367
|
+
record
|
|
4861
5368
|
};
|
|
5369
|
+
},
|
|
5370
|
+
inverse: {
|
|
5371
|
+
label: "Unregistering MCP servers from Claude Code",
|
|
5372
|
+
execute: async (raw) => {
|
|
5373
|
+
const rec = raw ?? {};
|
|
5374
|
+
const servers = rec.registeredServers ?? [];
|
|
5375
|
+
if (servers.length === 0) return { status: "skipped", detail: "nothing was registered" };
|
|
5376
|
+
if (!isClaudeCliAvailable()) {
|
|
5377
|
+
return {
|
|
5378
|
+
status: "skipped",
|
|
5379
|
+
detail: "`claude` CLI not found \u2014 leftover global registrations: " + servers.join(", ")
|
|
5380
|
+
};
|
|
5381
|
+
}
|
|
5382
|
+
const removed = [];
|
|
5383
|
+
const failed = [];
|
|
5384
|
+
for (const name of servers) {
|
|
5385
|
+
const r = (0, import_child_process3.spawnSync)(`claude mcp remove ${name}`, {
|
|
5386
|
+
shell: true,
|
|
5387
|
+
stdio: "pipe",
|
|
5388
|
+
encoding: "utf-8"
|
|
5389
|
+
});
|
|
5390
|
+
if (r.status === 0) removed.push(name);
|
|
5391
|
+
else failed.push(name);
|
|
5392
|
+
}
|
|
5393
|
+
return {
|
|
5394
|
+
status: "success",
|
|
5395
|
+
message: removed.length > 0 ? removed.join(", ") : "none",
|
|
5396
|
+
...failed.length > 0 ? { detail: `also tried (already removed?): ${failed.join(", ")}` } : {}
|
|
5397
|
+
};
|
|
5398
|
+
}
|
|
4862
5399
|
}
|
|
4863
5400
|
});
|
|
4864
5401
|
function isClaudeCliAvailable() {
|
|
@@ -4867,7 +5404,8 @@ function isClaudeCliAvailable() {
|
|
|
4867
5404
|
}
|
|
4868
5405
|
|
|
4869
5406
|
// src/steps/setup/update-gitignore.ts
|
|
4870
|
-
var
|
|
5407
|
+
var import_fs13 = require("fs");
|
|
5408
|
+
var import_path13 = require("path");
|
|
4871
5409
|
var MARKER_START2 = "# one-shot-installer:start";
|
|
4872
5410
|
var MARKER_END2 = "# one-shot-installer:end";
|
|
4873
5411
|
var CORE_GITIGNORE_ENTRIES = [
|
|
@@ -4888,15 +5426,37 @@ var update_gitignore_default = defineStep({
|
|
|
4888
5426
|
name: "update-gitignore",
|
|
4889
5427
|
label: "Updating .gitignore",
|
|
4890
5428
|
execute: async (ctx) => {
|
|
4891
|
-
const gitignorePath = (0,
|
|
5429
|
+
const gitignorePath = (0, import_path13.join)(ctx.config.projectPath, ".gitignore");
|
|
5430
|
+
const fileExistedBefore = (0, import_fs13.existsSync)(gitignorePath);
|
|
4892
5431
|
upsertMarkerBlock(gitignorePath, MARKER_START2, MARKER_END2, CORE_GITIGNORE_ENTRIES.join("\n"));
|
|
4893
|
-
|
|
5432
|
+
const record = {
|
|
5433
|
+
filePath: ".gitignore",
|
|
5434
|
+
start: MARKER_START2,
|
|
5435
|
+
end: MARKER_END2,
|
|
5436
|
+
fileExistedBefore
|
|
5437
|
+
};
|
|
5438
|
+
return { status: "success", record };
|
|
5439
|
+
},
|
|
5440
|
+
inverse: {
|
|
5441
|
+
label: "Restoring .gitignore",
|
|
5442
|
+
execute: async (raw, ctx) => {
|
|
5443
|
+
const rec = raw ?? {};
|
|
5444
|
+
const filePath = rec.filePath ?? ".gitignore";
|
|
5445
|
+
const abs = resolveProjectPath(filePath, ctx.config.projectPath);
|
|
5446
|
+
removeMarkerBlock(
|
|
5447
|
+
abs,
|
|
5448
|
+
rec.start ?? MARKER_START2,
|
|
5449
|
+
rec.end ?? MARKER_END2,
|
|
5450
|
+
{ deleteFileIfEmpty: !rec.fileExistedBefore }
|
|
5451
|
+
);
|
|
5452
|
+
return { status: "success" };
|
|
5453
|
+
}
|
|
4894
5454
|
}
|
|
4895
5455
|
});
|
|
4896
5456
|
|
|
4897
5457
|
// src/steps/setup/write-state.ts
|
|
4898
|
-
var
|
|
4899
|
-
var
|
|
5458
|
+
var import_fs14 = require("fs");
|
|
5459
|
+
var import_path14 = require("path");
|
|
4900
5460
|
var import_os2 = require("os");
|
|
4901
5461
|
|
|
4902
5462
|
// package.json
|
|
@@ -4930,7 +5490,7 @@ var write_state_default = defineStep({
|
|
|
4930
5490
|
name: "write-state",
|
|
4931
5491
|
label: "Saving installation state",
|
|
4932
5492
|
execute: async (ctx) => {
|
|
4933
|
-
const statePath = (0,
|
|
5493
|
+
const statePath = (0, import_path14.join)(ctx.config.projectPath, ".one-shot-state.json");
|
|
4934
5494
|
const mcpServersAdded = ctx.installed.mcpServersAdded ?? [];
|
|
4935
5495
|
const filteredMcpConfig = Object.fromEntries(
|
|
4936
5496
|
Object.entries(ctx.config.mcpConfig).filter(([name]) => mcpServersAdded.includes(name))
|
|
@@ -4955,45 +5515,233 @@ var write_state_default = defineStep({
|
|
|
4955
5515
|
contexts: "_ai-context/",
|
|
4956
5516
|
factory: ctx.installed.factoryInstalled ? "factory/" : null,
|
|
4957
5517
|
abapHooks: ctx.installed.abapHooksInstalled ? ".claude/hooks/" : null,
|
|
4958
|
-
globalConfig: (0,
|
|
5518
|
+
globalConfig: (0, import_path14.join)((0, import_os2.homedir)(), ".one-shot-installer")
|
|
4959
5519
|
}
|
|
4960
5520
|
};
|
|
4961
|
-
|
|
5521
|
+
if (ctx.journal) {
|
|
5522
|
+
ctx.journal.setSummary(state);
|
|
5523
|
+
} else {
|
|
5524
|
+
(0, import_fs14.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf-8");
|
|
5525
|
+
}
|
|
4962
5526
|
return { status: "success" };
|
|
5527
|
+
},
|
|
5528
|
+
inverse: {
|
|
5529
|
+
label: "Clearing installation state",
|
|
5530
|
+
/* No-op during journal walk. The journal IS the state file, so
|
|
5531
|
+
* deleting it here would yank the rug from under runRollback's
|
|
5532
|
+
* own iteration. The uninstall orchestrator deletes the file as
|
|
5533
|
+
* the FINAL action, after all other inverses have completed. */
|
|
5534
|
+
execute: async () => ({ status: "skipped", detail: "handled by uninstall orchestrator" })
|
|
4963
5535
|
}
|
|
4964
5536
|
});
|
|
4965
5537
|
|
|
5538
|
+
// src/uninstall.ts
|
|
5539
|
+
var import_fs15 = require("fs");
|
|
5540
|
+
var import_readline = require("readline");
|
|
5541
|
+
var import_path15 = require("path");
|
|
5542
|
+
var dim = (text) => `\x1B[2m${text}\x1B[0m`;
|
|
5543
|
+
var yellow = (text) => `\x1B[33m${text}\x1B[0m`;
|
|
5544
|
+
var red3 = (text) => `\x1B[31m${text}\x1B[0m`;
|
|
5545
|
+
var green = (text) => `\x1B[32m${text}\x1B[0m`;
|
|
5546
|
+
function waitForEnter() {
|
|
5547
|
+
const rl = (0, import_readline.createInterface)({ input: process.stdin, output: process.stdout });
|
|
5548
|
+
return new Promise((resolve6) => {
|
|
5549
|
+
rl.question(" Press Enter to close...", () => {
|
|
5550
|
+
rl.close();
|
|
5551
|
+
resolve6();
|
|
5552
|
+
});
|
|
5553
|
+
});
|
|
5554
|
+
}
|
|
5555
|
+
async function uninstall() {
|
|
5556
|
+
console.clear();
|
|
5557
|
+
console.log("One-Shot Setup Uninstaller\n");
|
|
5558
|
+
try {
|
|
5559
|
+
const projectInput = await esm_default4({
|
|
5560
|
+
message: "Project directory to uninstall from:",
|
|
5561
|
+
default: (0, import_path15.resolve)(process.cwd())
|
|
5562
|
+
});
|
|
5563
|
+
const projectPath = (0, import_path15.resolve)(projectInput);
|
|
5564
|
+
const statePath = (0, import_path15.join)(projectPath, ".one-shot-state.json");
|
|
5565
|
+
if (!(0, import_fs15.existsSync)(statePath)) {
|
|
5566
|
+
console.log("");
|
|
5567
|
+
console.log(red3(" No .one-shot-state.json found at:"));
|
|
5568
|
+
console.log(` ${statePath}`);
|
|
5569
|
+
console.log("");
|
|
5570
|
+
console.log(dim(" Nothing to uninstall \u2014 either this directory is not an installer"));
|
|
5571
|
+
console.log(dim(" target, or the state file has already been removed."));
|
|
5572
|
+
console.log("");
|
|
5573
|
+
await waitForEnter();
|
|
5574
|
+
return;
|
|
5575
|
+
}
|
|
5576
|
+
const journal = new Journal(statePath);
|
|
5577
|
+
journal.load();
|
|
5578
|
+
const entries = journal.getEntries();
|
|
5579
|
+
if (entries.length === 0) {
|
|
5580
|
+
console.log("");
|
|
5581
|
+
console.log(yellow(" State file found, but its rollback journal is empty."));
|
|
5582
|
+
console.log(dim(" This file was likely written by an older installer version that"));
|
|
5583
|
+
console.log(dim(" did not yet emit a journal. Manual cleanup is required."));
|
|
5584
|
+
console.log("");
|
|
5585
|
+
const proceed2 = await esm_default3({
|
|
5586
|
+
message: "Delete the state file anyway?",
|
|
5587
|
+
default: false
|
|
5588
|
+
});
|
|
5589
|
+
if (proceed2) {
|
|
5590
|
+
(0, import_fs15.unlinkSync)(statePath);
|
|
5591
|
+
console.log(" Removed .one-shot-state.json");
|
|
5592
|
+
}
|
|
5593
|
+
await waitForEnter();
|
|
5594
|
+
return;
|
|
5595
|
+
}
|
|
5596
|
+
const summary = readSummary(statePath);
|
|
5597
|
+
printPreview(projectPath, summary, entries.length);
|
|
5598
|
+
const proceed = await esm_default3({
|
|
5599
|
+
message: "Proceed with uninstall?",
|
|
5600
|
+
default: false
|
|
5601
|
+
});
|
|
5602
|
+
if (!proceed) {
|
|
5603
|
+
console.log("\nNo changes made.");
|
|
5604
|
+
await waitForEnter();
|
|
5605
|
+
return;
|
|
5606
|
+
}
|
|
5607
|
+
const ctx = {
|
|
5608
|
+
config: {
|
|
5609
|
+
technologies: [],
|
|
5610
|
+
agent: typeof summary?.agent === "string" ? summary.agent : "claude-code",
|
|
5611
|
+
azureDevOpsOrg: "",
|
|
5612
|
+
projectPath,
|
|
5613
|
+
baseMcpServers: [],
|
|
5614
|
+
mcpConfig: {},
|
|
5615
|
+
releaseVersion: "",
|
|
5616
|
+
installFactory: false,
|
|
5617
|
+
installAbapHooks: false
|
|
5618
|
+
},
|
|
5619
|
+
token: "",
|
|
5620
|
+
repo: "",
|
|
5621
|
+
contextRepo: null,
|
|
5622
|
+
factoryRepo: null,
|
|
5623
|
+
abapHooksRepo: null,
|
|
5624
|
+
installed: {},
|
|
5625
|
+
journal
|
|
5626
|
+
};
|
|
5627
|
+
const stepList = Object.values(steps_exports);
|
|
5628
|
+
console.log("");
|
|
5629
|
+
const result = await runRollback(stepList, ctx);
|
|
5630
|
+
try {
|
|
5631
|
+
if ((0, import_fs15.existsSync)(statePath)) (0, import_fs15.unlinkSync)(statePath);
|
|
5632
|
+
} catch {
|
|
5633
|
+
}
|
|
5634
|
+
printSummary(result);
|
|
5635
|
+
await waitForEnter();
|
|
5636
|
+
} catch (error) {
|
|
5637
|
+
console.error("\n[ERROR]", error instanceof Error ? error.message : String(error));
|
|
5638
|
+
await waitForEnter();
|
|
5639
|
+
}
|
|
5640
|
+
}
|
|
5641
|
+
function readSummary(statePath) {
|
|
5642
|
+
try {
|
|
5643
|
+
const raw = JSON.parse((0, import_fs15.readFileSync)(statePath, "utf-8"));
|
|
5644
|
+
return raw;
|
|
5645
|
+
} catch {
|
|
5646
|
+
return {};
|
|
5647
|
+
}
|
|
5648
|
+
}
|
|
5649
|
+
function asArray(v) {
|
|
5650
|
+
return Array.isArray(v) ? v.filter((s) => typeof s === "string") : [];
|
|
5651
|
+
}
|
|
5652
|
+
function printPreview(projectPath, summary, entryCount) {
|
|
5653
|
+
const agent = typeof summary.agent === "string" ? summary.agent : "unknown";
|
|
5654
|
+
const domains = asArray(summary.domains);
|
|
5655
|
+
const mcpServers = asArray(summary.mcpServers);
|
|
5656
|
+
const factoryInstalled = summary.factoryInstalled === true;
|
|
5657
|
+
const abapHooksInstalled = summary.abapHooksInstalled === true;
|
|
5658
|
+
const files = summary.files ?? {};
|
|
5659
|
+
console.log("\n" + "\u2500".repeat(48));
|
|
5660
|
+
console.log(" Ready to uninstall");
|
|
5661
|
+
console.log("\u2500".repeat(48) + "\n");
|
|
5662
|
+
console.log(` Directory ${projectPath}`);
|
|
5663
|
+
console.log(` Agent ${agent}`);
|
|
5664
|
+
console.log(` Journal ${entryCount} recorded step(s)`);
|
|
5665
|
+
console.log("");
|
|
5666
|
+
console.log(` ${dim("Will remove:")}`);
|
|
5667
|
+
if (domains.length > 0) {
|
|
5668
|
+
console.log(` _ai-context/ ${yellow(domains.join(", "))}`);
|
|
5669
|
+
}
|
|
5670
|
+
if (factoryInstalled && typeof files.factory === "string") {
|
|
5671
|
+
console.log(` ${files.factory.padEnd(16)}${yellow("remove")}`);
|
|
5672
|
+
}
|
|
5673
|
+
if (abapHooksInstalled && typeof files.abapHooks === "string") {
|
|
5674
|
+
console.log(` ${files.abapHooks.padEnd(16)}${yellow("strip our scripts")}`);
|
|
5675
|
+
}
|
|
5676
|
+
if (typeof files.instructions === "string") {
|
|
5677
|
+
console.log(` ${files.instructions.padEnd(16)}${yellow("remove our markers")}`);
|
|
5678
|
+
}
|
|
5679
|
+
if (typeof files.mcpConfig === "string" && mcpServers.length > 0) {
|
|
5680
|
+
console.log(` ${files.mcpConfig.padEnd(16)}${yellow(`remove ${mcpServers.length} server(s)`)}`);
|
|
5681
|
+
}
|
|
5682
|
+
console.log(` ${".gitignore".padEnd(16)}${yellow("remove our markers")}`);
|
|
5683
|
+
console.log(` ${".one-shot-state.json".padEnd(16)}${yellow("delete")}`);
|
|
5684
|
+
if (mcpServers.length > 0) {
|
|
5685
|
+
console.log("");
|
|
5686
|
+
console.log(` ${dim("MCP servers being removed:")}`);
|
|
5687
|
+
console.log(` ${mcpServers.join(", ")}`);
|
|
5688
|
+
}
|
|
5689
|
+
console.log("\n" + dim(" User-edited files outside our marker blocks will be preserved."));
|
|
5690
|
+
console.log(dim(" Files we created from scratch will be deleted; files we modified"));
|
|
5691
|
+
console.log(dim(" will have only our changes stripped."));
|
|
5692
|
+
console.log("\n" + "\u2500".repeat(48));
|
|
5693
|
+
}
|
|
5694
|
+
function printSummary(result) {
|
|
5695
|
+
const failed = result.entries.filter((e) => e.result.status === "failed");
|
|
5696
|
+
const succeeded = result.entries.filter((e) => e.result.status === "success");
|
|
5697
|
+
const skipped = result.entries.filter((e) => e.result.status === "skipped");
|
|
5698
|
+
console.log("\u2500".repeat(48));
|
|
5699
|
+
console.log(failed.length > 0 ? " Done (with errors).\n" : " Done.\n");
|
|
5700
|
+
for (const entry of succeeded) {
|
|
5701
|
+
const msg = entry.result.message ? ` ${dim(entry.result.message)}` : "";
|
|
5702
|
+
console.log(` ${green("\u2713")} ${entry.label}${msg}`);
|
|
5703
|
+
}
|
|
5704
|
+
for (const entry of skipped) {
|
|
5705
|
+
const detail = entry.result.detail ? ` ${dim(entry.result.detail)}` : "";
|
|
5706
|
+
console.log(` ${dim("\u2014")} ${entry.label}${detail}`);
|
|
5707
|
+
}
|
|
5708
|
+
for (const entry of failed) {
|
|
5709
|
+
console.log(` ${red3("\u2717")} ${entry.label}: ${entry.result.detail ?? "unknown"}`);
|
|
5710
|
+
}
|
|
5711
|
+
console.log("\n" + "\u2500".repeat(48) + "\n");
|
|
5712
|
+
}
|
|
5713
|
+
|
|
4966
5714
|
// src/index.ts
|
|
4967
5715
|
function formatMCPCommand(server) {
|
|
4968
5716
|
if (server.url) return server.url;
|
|
4969
5717
|
if (server.command) return `${server.command} ${(server.args ?? []).join(" ")}`.trimEnd();
|
|
4970
5718
|
return "";
|
|
4971
5719
|
}
|
|
4972
|
-
var
|
|
4973
|
-
var
|
|
4974
|
-
var
|
|
5720
|
+
var dim2 = (text) => `\x1B[2m${text}\x1B[0m`;
|
|
5721
|
+
var yellow2 = (text) => `\x1B[33m${text}\x1B[0m`;
|
|
5722
|
+
var green2 = (text) => `\x1B[32m${text}\x1B[0m`;
|
|
4975
5723
|
function detectMarkerFileMode(filePath, markerStart) {
|
|
4976
|
-
if (!(0,
|
|
4977
|
-
const content = (0,
|
|
4978
|
-
if (content.includes(markerStart)) return
|
|
4979
|
-
return
|
|
5724
|
+
if (!(0, import_fs16.existsSync)(filePath)) return green2("create");
|
|
5725
|
+
const content = (0, import_fs16.readFileSync)(filePath, "utf-8");
|
|
5726
|
+
if (content.includes(markerStart)) return yellow2("update");
|
|
5727
|
+
return yellow2("append");
|
|
4980
5728
|
}
|
|
4981
5729
|
function detectMCPFileMode(filePath) {
|
|
4982
|
-
if (!(0,
|
|
4983
|
-
return
|
|
5730
|
+
if (!(0, import_fs16.existsSync)(filePath)) return green2("create");
|
|
5731
|
+
return yellow2("merge");
|
|
4984
5732
|
}
|
|
4985
5733
|
function detectContextMode(projectPath, domain) {
|
|
4986
|
-
const contextDir = (0,
|
|
4987
|
-
const contextDirLower = (0,
|
|
4988
|
-
if ((0,
|
|
4989
|
-
return
|
|
5734
|
+
const contextDir = (0, import_path16.join)(projectPath, "_ai-context", domain.toUpperCase());
|
|
5735
|
+
const contextDirLower = (0, import_path16.join)(projectPath, "_ai-context", domain);
|
|
5736
|
+
if ((0, import_fs16.existsSync)(contextDir) || (0, import_fs16.existsSync)(contextDirLower)) return yellow2("overwrite");
|
|
5737
|
+
return green2("create");
|
|
4990
5738
|
}
|
|
4991
|
-
function
|
|
4992
|
-
const rl = (0,
|
|
4993
|
-
return new Promise((
|
|
5739
|
+
function waitForEnter2() {
|
|
5740
|
+
const rl = (0, import_readline2.createInterface)({ input: process.stdin, output: process.stdout });
|
|
5741
|
+
return new Promise((resolve6) => {
|
|
4994
5742
|
rl.question(" Press Enter to close...", () => {
|
|
4995
5743
|
rl.close();
|
|
4996
|
-
|
|
5744
|
+
resolve6();
|
|
4997
5745
|
});
|
|
4998
5746
|
});
|
|
4999
5747
|
}
|
|
@@ -5022,15 +5770,18 @@ async function main() {
|
|
|
5022
5770
|
const options = await loadWizardOptions(token, repo);
|
|
5023
5771
|
const config = await collectInputs(options, options.releaseVersion, !!factoryRepo, !!abapHooksRepo);
|
|
5024
5772
|
if (!config) {
|
|
5025
|
-
await
|
|
5773
|
+
await waitForEnter2();
|
|
5026
5774
|
return;
|
|
5027
5775
|
}
|
|
5028
5776
|
const proceed = await previewAndConfirm(config, options);
|
|
5029
5777
|
if (!proceed) {
|
|
5030
5778
|
console.log("\nNo changes made.");
|
|
5031
|
-
await
|
|
5779
|
+
await waitForEnter2();
|
|
5032
5780
|
return;
|
|
5033
5781
|
}
|
|
5782
|
+
const statePath = (0, import_path16.join)(config.projectPath, ".one-shot-state.json");
|
|
5783
|
+
const journal = new Journal(statePath);
|
|
5784
|
+
journal.startFresh();
|
|
5034
5785
|
const ctx = {
|
|
5035
5786
|
config,
|
|
5036
5787
|
token,
|
|
@@ -5038,16 +5789,17 @@ async function main() {
|
|
|
5038
5789
|
contextRepo,
|
|
5039
5790
|
factoryRepo,
|
|
5040
5791
|
abapHooksRepo,
|
|
5041
|
-
installed: {}
|
|
5792
|
+
installed: {},
|
|
5793
|
+
journal
|
|
5042
5794
|
};
|
|
5043
5795
|
const stepList = Object.values(steps_exports);
|
|
5044
5796
|
console.log("");
|
|
5045
5797
|
const result = await runPipeline(stepList, ctx);
|
|
5046
|
-
|
|
5798
|
+
printSummary2(result, config);
|
|
5047
5799
|
return;
|
|
5048
5800
|
} catch (error) {
|
|
5049
5801
|
console.error("\n[ERROR]", error instanceof Error ? error.message : String(error));
|
|
5050
|
-
await
|
|
5802
|
+
await waitForEnter2();
|
|
5051
5803
|
}
|
|
5052
5804
|
}
|
|
5053
5805
|
async function collectInputs(options, releaseVersion, factoryAvailable = false, abapHooksAvailable = false) {
|
|
@@ -5090,7 +5842,7 @@ async function collectInputs(options, releaseVersion, factoryAvailable = false,
|
|
|
5090
5842
|
}
|
|
5091
5843
|
const projectInput = await esm_default4({
|
|
5092
5844
|
message: "Project directory:",
|
|
5093
|
-
default: (0,
|
|
5845
|
+
default: (0, import_path16.resolve)(process.cwd())
|
|
5094
5846
|
});
|
|
5095
5847
|
const isAbapSelected = selectedTechnologies.some((t) => t.domains.includes("ABAP"));
|
|
5096
5848
|
const installAbapHooks = abapHooksAvailable && agent === "claude-code" && isAbapSelected;
|
|
@@ -5098,7 +5850,7 @@ async function collectInputs(options, releaseVersion, factoryAvailable = false,
|
|
|
5098
5850
|
technologies: selectedTechnologies,
|
|
5099
5851
|
agent,
|
|
5100
5852
|
azureDevOpsOrg,
|
|
5101
|
-
projectPath: (0,
|
|
5853
|
+
projectPath: (0, import_path16.resolve)(projectInput),
|
|
5102
5854
|
baseMcpServers: options.baseMcpServers,
|
|
5103
5855
|
mcpConfig,
|
|
5104
5856
|
releaseVersion,
|
|
@@ -5113,9 +5865,9 @@ async function previewAndConfirm(config, options) {
|
|
|
5113
5865
|
const instructionFile = target.instructions;
|
|
5114
5866
|
const mcpConfigFile = target.mcpConfig;
|
|
5115
5867
|
const serverEntries = Object.entries(config.mcpConfig);
|
|
5116
|
-
const instructionFilePath = (0,
|
|
5117
|
-
const mcpConfigFilePath = (0,
|
|
5118
|
-
const gitignorePath = (0,
|
|
5868
|
+
const instructionFilePath = (0, import_path16.join)(config.projectPath, instructionFile);
|
|
5869
|
+
const mcpConfigFilePath = (0, import_path16.join)(config.projectPath, mcpConfigFile);
|
|
5870
|
+
const gitignorePath = (0, import_path16.join)(config.projectPath, ".gitignore");
|
|
5119
5871
|
const instructionMode = detectMarkerFileMode(instructionFilePath, "<!-- one-shot-installer:start -->");
|
|
5120
5872
|
const mcpMode = detectMCPFileMode(mcpConfigFilePath);
|
|
5121
5873
|
const gitignoreMode = detectMarkerFileMode(gitignorePath, "# one-shot-installer:start");
|
|
@@ -5133,7 +5885,7 @@ async function previewAndConfirm(config, options) {
|
|
|
5133
5885
|
console.log(` ABAP hooks ${config.installAbapHooks ? "yes" : "no"}`);
|
|
5134
5886
|
console.log(` Directory ${config.projectPath}`);
|
|
5135
5887
|
console.log("");
|
|
5136
|
-
console.log(` ${
|
|
5888
|
+
console.log(` ${dim2("File actions:")}`);
|
|
5137
5889
|
const domainColWidth = Math.max(...domainModes.map((d) => d.domain.length), 6) + 2;
|
|
5138
5890
|
for (const { domain, mode } of domainModes) {
|
|
5139
5891
|
console.log(` _ai-context/${domain.padEnd(domainColWidth)}${mode}`);
|
|
@@ -5142,18 +5894,18 @@ async function previewAndConfirm(config, options) {
|
|
|
5142
5894
|
console.log(` ${mcpConfigFile.padEnd(domainColWidth + 14)}${mcpMode}`);
|
|
5143
5895
|
console.log(` ${".gitignore".padEnd(domainColWidth + 14)}${gitignoreMode}`);
|
|
5144
5896
|
if (config.installFactory) {
|
|
5145
|
-
const factoryExists = (0,
|
|
5146
|
-
const factoryMode = factoryExists ?
|
|
5897
|
+
const factoryExists = (0, import_fs16.existsSync)((0, import_path16.join)(config.projectPath, "factory"));
|
|
5898
|
+
const factoryMode = factoryExists ? yellow2("overwrite") : green2("create");
|
|
5147
5899
|
console.log(` ${"factory/".padEnd(domainColWidth + 14)}${factoryMode}`);
|
|
5148
5900
|
}
|
|
5149
5901
|
if (config.installAbapHooks) {
|
|
5150
|
-
const hooksDir = (0,
|
|
5151
|
-
const hooksMode = (0,
|
|
5902
|
+
const hooksDir = (0, import_path16.join)(config.projectPath, ".claude", "hooks");
|
|
5903
|
+
const hooksMode = (0, import_fs16.existsSync)(hooksDir) ? yellow2("merge") : green2("create");
|
|
5152
5904
|
console.log(` ${".claude/hooks/".padEnd(domainColWidth + 14)}${hooksMode}`);
|
|
5153
5905
|
}
|
|
5154
5906
|
if (serverEntries.length > 0) {
|
|
5155
5907
|
console.log("");
|
|
5156
|
-
console.log(` ${
|
|
5908
|
+
console.log(` ${dim2("MCP servers:")}`);
|
|
5157
5909
|
const maxLen = Math.max(...serverEntries.map(([name]) => name.length));
|
|
5158
5910
|
for (const [name, server] of serverEntries) {
|
|
5159
5911
|
const cmd = formatMCPCommand(server);
|
|
@@ -5163,7 +5915,7 @@ async function previewAndConfirm(config, options) {
|
|
|
5163
5915
|
console.log("\n" + "\u2500".repeat(48));
|
|
5164
5916
|
return esm_default3({ message: "Proceed?", default: true });
|
|
5165
5917
|
}
|
|
5166
|
-
function
|
|
5918
|
+
function printSummary2(result, config) {
|
|
5167
5919
|
const failed = result.entries.filter((e) => e.result.status === "failed");
|
|
5168
5920
|
const succeeded = result.entries.filter((e) => e.result.status === "success");
|
|
5169
5921
|
const skipped = result.entries.filter((e) => e.result.status === "skipped");
|
|
@@ -5191,7 +5943,34 @@ function printSummary(result, config) {
|
|
|
5191
5943
|
}
|
|
5192
5944
|
console.log("\n" + "\u2500".repeat(48) + "\n");
|
|
5193
5945
|
}
|
|
5194
|
-
|
|
5946
|
+
function printHelp() {
|
|
5947
|
+
console.log("");
|
|
5948
|
+
console.log("Usage:");
|
|
5949
|
+
console.log(" npx dlw-machine-setup Install the toolchain (wizard).");
|
|
5950
|
+
console.log(" npx dlw-machine-setup uninstall Remove a previous install (wizard).");
|
|
5951
|
+
console.log(" npx dlw-machine-setup --help Show this help.");
|
|
5952
|
+
console.log("");
|
|
5953
|
+
}
|
|
5954
|
+
async function dispatch() {
|
|
5955
|
+
const subcommand = process.argv[2];
|
|
5956
|
+
switch (subcommand) {
|
|
5957
|
+
case void 0:
|
|
5958
|
+
case "":
|
|
5959
|
+
return main();
|
|
5960
|
+
case "uninstall":
|
|
5961
|
+
return uninstall();
|
|
5962
|
+
case "help":
|
|
5963
|
+
case "--help":
|
|
5964
|
+
case "-h":
|
|
5965
|
+
printHelp();
|
|
5966
|
+
return;
|
|
5967
|
+
default:
|
|
5968
|
+
console.error(`Unknown subcommand: ${subcommand}`);
|
|
5969
|
+
printHelp();
|
|
5970
|
+
process.exit(1);
|
|
5971
|
+
}
|
|
5972
|
+
}
|
|
5973
|
+
dispatch().catch((error) => {
|
|
5195
5974
|
console.error("\n[ERROR]", error instanceof Error ? error.message : String(error));
|
|
5196
5975
|
process.exit(1);
|
|
5197
5976
|
});
|