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