dlw-machine-setup 0.8.1 → 0.8.2
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/bin/installer.js +237 -62
- package/package.json +1 -1
package/bin/installer.js
CHANGED
|
@@ -3244,9 +3244,9 @@ ${page}${helpTipBottom}${choiceDescription}${import_ansi_escapes3.default.cursor
|
|
|
3244
3244
|
});
|
|
3245
3245
|
|
|
3246
3246
|
// src/index.ts
|
|
3247
|
-
var
|
|
3247
|
+
var import_fs12 = require("fs");
|
|
3248
3248
|
var import_readline = require("readline");
|
|
3249
|
-
var
|
|
3249
|
+
var import_path13 = require("path");
|
|
3250
3250
|
|
|
3251
3251
|
// src/utils/fetch.ts
|
|
3252
3252
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
@@ -3324,6 +3324,25 @@ async function fetchLatestRelease(token, repo) {
|
|
|
3324
3324
|
const data = await res.json();
|
|
3325
3325
|
return { tagName: data.tag_name ?? "unknown", assets: data.assets ?? [] };
|
|
3326
3326
|
}
|
|
3327
|
+
async function fetchLatestReleaseByTagPrefix(token, repo, tagPrefix) {
|
|
3328
|
+
const headers = {
|
|
3329
|
+
"Accept": "application/vnd.github+json",
|
|
3330
|
+
"Authorization": `Bearer ${token}`
|
|
3331
|
+
};
|
|
3332
|
+
const res = await fetchWithRetry(
|
|
3333
|
+
`https://api.github.com/repos/${repo}/releases?per_page=100`,
|
|
3334
|
+
{ headers }
|
|
3335
|
+
);
|
|
3336
|
+
if (!res.ok) {
|
|
3337
|
+
throw new Error(`GitHub API error (${res.status}): ${getReadableError(res.status)}`);
|
|
3338
|
+
}
|
|
3339
|
+
const releases = await res.json();
|
|
3340
|
+
const match = releases.find((r) => typeof r.tag_name === "string" && r.tag_name.startsWith(tagPrefix));
|
|
3341
|
+
if (!match) {
|
|
3342
|
+
throw new Error(`No release with tag prefix "${tagPrefix}" found in ${repo}`);
|
|
3343
|
+
}
|
|
3344
|
+
return { tagName: match.tag_name ?? "unknown", assets: match.assets ?? [] };
|
|
3345
|
+
}
|
|
3327
3346
|
async function downloadAndExtractAsset(token, asset, projectPath, tempDirName) {
|
|
3328
3347
|
const tarCheck = (0, import_child_process.spawnSync)("tar", ["--version"], { stdio: "ignore" });
|
|
3329
3348
|
if (tarCheck.status !== 0) {
|
|
@@ -3738,6 +3757,7 @@ async function runPipeline(steps, ctx) {
|
|
|
3738
3757
|
// src/steps/index.ts
|
|
3739
3758
|
var steps_exports = {};
|
|
3740
3759
|
__export(steps_exports, {
|
|
3760
|
+
fetchAbapHooks: () => fetch_abap_hooks_default,
|
|
3741
3761
|
fetchContexts: () => fetch_contexts_default,
|
|
3742
3762
|
fetchFactory: () => fetch_factory_default,
|
|
3743
3763
|
runMcpInstallCommands: () => run_mcp_install_commands_default,
|
|
@@ -4330,9 +4350,140 @@ async function installViaBundle(manifestPath, extractedPath, targetDir, agent, r
|
|
|
4330
4350
|
result.filesInstalled = runResult.filesTouched;
|
|
4331
4351
|
}
|
|
4332
4352
|
|
|
4333
|
-
// src/steps/
|
|
4353
|
+
// src/steps/resources/fetch-abap-hooks.ts
|
|
4334
4354
|
var import_fs8 = require("fs");
|
|
4335
4355
|
var import_path8 = require("path");
|
|
4356
|
+
var HOOKS_ASSET_NAME = "sap-hooks.tar.gz";
|
|
4357
|
+
var HOOKS_TAG_PREFIX = "hooks-v";
|
|
4358
|
+
var HOOKS_ROOT_FOLDER = "hooks";
|
|
4359
|
+
var BUNDLE_NAME = "abap-mcp-hooks";
|
|
4360
|
+
var HOOK_DEFINITIONS = [
|
|
4361
|
+
{ event: "PreToolUse", script: "sap-safety-gate.mjs" },
|
|
4362
|
+
{ event: "PostToolUse", script: "telemetry-post-tool.mjs" },
|
|
4363
|
+
{ event: "PostToolUseFailure", script: "telemetry-post-tool-failure.mjs" },
|
|
4364
|
+
{ event: "SessionStart", script: "telemetry-session-start.mjs" },
|
|
4365
|
+
{ event: "SessionEnd", script: "telemetry-session-end.mjs" }
|
|
4366
|
+
];
|
|
4367
|
+
var INSTRUCTIONS_SNIPPET = [
|
|
4368
|
+
`## SAP ABAP MCP Hooks`,
|
|
4369
|
+
``,
|
|
4370
|
+
`Installed by One-Shot Installer. Hook scripts live in \`.claude/hooks/\` and are wired into \`.claude/settings.json\`.`,
|
|
4371
|
+
``,
|
|
4372
|
+
`**What they do:**`,
|
|
4373
|
+
`- \`sap-safety-gate.mjs\` (PreToolUse) \u2014 blocks \`delete\` (any tool) and transport \`release\`.`,
|
|
4374
|
+
`- \`telemetry-*.mjs\` (PostToolUse, PostToolUseFailure, SessionStart, SessionEnd) \u2014 emit usage events.`,
|
|
4375
|
+
``,
|
|
4376
|
+
`**Optional environment variables:**`,
|
|
4377
|
+
`- \`SAP_MCP_TELEMETRY_URL\` \u2014 base URL of the MCP telemetry endpoint (e.g. \`https://mcp.<cluster>.kyma.ondemand.com\`). When unset, telemetry is silently skipped; the safety gate still blocks.`,
|
|
4378
|
+
`- \`SAP_MCP_TELEMETRY_TOKEN\` \u2014 Bearer token for the telemetry endpoint.`,
|
|
4379
|
+
``,
|
|
4380
|
+
`On network/HTTP failure, telemetry events fall back to \`~/.claude/telemetry/events.jsonl\`.`,
|
|
4381
|
+
``
|
|
4382
|
+
].join("\n");
|
|
4383
|
+
var fetch_abap_hooks_default = defineStep({
|
|
4384
|
+
name: "fetch-abap-hooks",
|
|
4385
|
+
label: "Installing ABAP MCP hooks",
|
|
4386
|
+
/* All eligibility (Claude-only + ABAP selected + repo discovered) is
|
|
4387
|
+
* resolved upfront in collectInputs() and frozen on InstallConfig.
|
|
4388
|
+
* Reading the single flag keeps the guard, the preview, and the
|
|
4389
|
+
* step in sync with no scattered conditionals. */
|
|
4390
|
+
when: (ctx) => ctx.config.installAbapHooks,
|
|
4391
|
+
execute: async (ctx) => {
|
|
4392
|
+
const result = await fetchAbapHooks(
|
|
4393
|
+
ctx.token,
|
|
4394
|
+
ctx.abapHooksRepo,
|
|
4395
|
+
ctx.config.projectPath,
|
|
4396
|
+
ctx.config.agent
|
|
4397
|
+
);
|
|
4398
|
+
ctx.installed.abapHooksInstalled = result.success;
|
|
4399
|
+
if (result.instructionsSnippet) {
|
|
4400
|
+
ctx.installed.abapHooksInstructionsSnippet = result.instructionsSnippet;
|
|
4401
|
+
}
|
|
4402
|
+
if (!result.success) {
|
|
4403
|
+
return { status: "failed", detail: result.failureReason };
|
|
4404
|
+
}
|
|
4405
|
+
return { status: "success", message: result.filesInstalled.join(", ") };
|
|
4406
|
+
}
|
|
4407
|
+
});
|
|
4408
|
+
async function fetchAbapHooks(token, repo, targetDir, agent) {
|
|
4409
|
+
const result = { success: false, filesInstalled: [] };
|
|
4410
|
+
let release;
|
|
4411
|
+
try {
|
|
4412
|
+
release = await fetchLatestReleaseByTagPrefix(token, repo, HOOKS_TAG_PREFIX);
|
|
4413
|
+
} catch (err) {
|
|
4414
|
+
result.failureReason = err instanceof Error ? err.message : String(err);
|
|
4415
|
+
return result;
|
|
4416
|
+
}
|
|
4417
|
+
const asset = release.assets.find((a) => a.name === HOOKS_ASSET_NAME);
|
|
4418
|
+
if (!asset) {
|
|
4419
|
+
result.failureReason = `${HOOKS_ASSET_NAME} not found in release ${release.tagName}`;
|
|
4420
|
+
return result;
|
|
4421
|
+
}
|
|
4422
|
+
let archive = null;
|
|
4423
|
+
try {
|
|
4424
|
+
archive = await downloadAndExtractAsset(token, asset, targetDir, ".temp-abap-hooks-download");
|
|
4425
|
+
const extractedEntries = (0, import_fs8.readdirSync)(archive.extractedRoot);
|
|
4426
|
+
const innerFolder = extractedEntries.find(
|
|
4427
|
+
(e) => e.toLowerCase() === HOOKS_ROOT_FOLDER.toLowerCase()
|
|
4428
|
+
);
|
|
4429
|
+
if (!innerFolder) {
|
|
4430
|
+
result.failureReason = `No ${HOOKS_ROOT_FOLDER}/ folder in archive`;
|
|
4431
|
+
return result;
|
|
4432
|
+
}
|
|
4433
|
+
const bundleRoot = (0, import_path8.join)(archive.extractedRoot, innerFolder);
|
|
4434
|
+
if (!(0, import_fs8.statSync)(bundleRoot).isDirectory()) {
|
|
4435
|
+
result.failureReason = `${HOOKS_ROOT_FOLDER}/ entry is not a directory`;
|
|
4436
|
+
return result;
|
|
4437
|
+
}
|
|
4438
|
+
const presentDefs = HOOK_DEFINITIONS.filter((d) => (0, import_fs8.existsSync)((0, import_path8.join)(bundleRoot, d.script)));
|
|
4439
|
+
if (presentDefs.length === 0) {
|
|
4440
|
+
result.failureReason = "Archive contains no recognized hook scripts";
|
|
4441
|
+
return result;
|
|
4442
|
+
}
|
|
4443
|
+
const assets = [
|
|
4444
|
+
...presentDefs.map((d) => ({
|
|
4445
|
+
type: "hook",
|
|
4446
|
+
event: d.event,
|
|
4447
|
+
/* {hookDir} is substituted by run-bundle.ts with the agent
|
|
4448
|
+
* profile's scriptDir (.claude/hooks for Claude Code), so the
|
|
4449
|
+
* resulting command is exactly `node .claude/hooks/<script>`,
|
|
4450
|
+
* matching the form Claude Code already uses for Factory hooks. */
|
|
4451
|
+
command: `node {hookDir}/${d.script}`,
|
|
4452
|
+
script: d.script
|
|
4453
|
+
})),
|
|
4454
|
+
{ type: "instructions-snippet", content: INSTRUCTIONS_SNIPPET }
|
|
4455
|
+
];
|
|
4456
|
+
const manifest = {
|
|
4457
|
+
schemaVersion: 2,
|
|
4458
|
+
name: BUNDLE_NAME,
|
|
4459
|
+
assets
|
|
4460
|
+
};
|
|
4461
|
+
const target = getAgentTarget(agent);
|
|
4462
|
+
const runResult = await runBundle(manifest, {
|
|
4463
|
+
bundleRoot,
|
|
4464
|
+
projectPath: targetDir,
|
|
4465
|
+
agent,
|
|
4466
|
+
instructionsFile: target.instructions,
|
|
4467
|
+
/* write-instructions.ts owns the ordering of marker blocks in
|
|
4468
|
+
* CLAUDE.md (one-shot-installer:start/end first, bundle blocks
|
|
4469
|
+
* after). Skipping here means we hand the snippet back; it gets
|
|
4470
|
+
* committed when write-instructions.ts runs in the setup phase. */
|
|
4471
|
+
skipInstructions: true
|
|
4472
|
+
});
|
|
4473
|
+
result.filesInstalled = runResult.filesTouched;
|
|
4474
|
+
result.instructionsSnippet = runResult.instructionsSnippet;
|
|
4475
|
+
result.success = true;
|
|
4476
|
+
} catch (error) {
|
|
4477
|
+
result.failureReason = error instanceof Error ? error.message : String(error);
|
|
4478
|
+
} finally {
|
|
4479
|
+
archive?.cleanup();
|
|
4480
|
+
}
|
|
4481
|
+
return result;
|
|
4482
|
+
}
|
|
4483
|
+
|
|
4484
|
+
// src/steps/setup/write-instructions.ts
|
|
4485
|
+
var import_fs9 = require("fs");
|
|
4486
|
+
var import_path9 = require("path");
|
|
4336
4487
|
|
|
4337
4488
|
// src/steps/shared.ts
|
|
4338
4489
|
var cached = null;
|
|
@@ -4359,7 +4510,7 @@ var red = (text) => `\x1B[31m${text}\x1B[0m`;
|
|
|
4359
4510
|
var write_instructions_default = defineStep({
|
|
4360
4511
|
name: "write-instructions",
|
|
4361
4512
|
label: "Writing instruction file",
|
|
4362
|
-
when: (ctx) => (ctx.installed.domainsInstalled?.length ?? 0) > 0 || Object.keys(ctx.config.mcpConfig).length > 0 || !!ctx.installed.factoryInstructionsSnippet,
|
|
4513
|
+
when: (ctx) => (ctx.installed.domainsInstalled?.length ?? 0) > 0 || Object.keys(ctx.config.mcpConfig).length > 0 || !!ctx.installed.factoryInstructionsSnippet || !!ctx.installed.abapHooksInstructionsSnippet,
|
|
4363
4514
|
execute: async (ctx) => {
|
|
4364
4515
|
const filteredMcpConfig = getFilteredMcpConfig(ctx);
|
|
4365
4516
|
const domains = ctx.installed.domainsInstalled ?? [];
|
|
@@ -4367,11 +4518,11 @@ var write_instructions_default = defineStep({
|
|
|
4367
4518
|
const projectPath = ctx.config.projectPath;
|
|
4368
4519
|
const content = buildCombinedInstructions(domains, filteredMcpConfig, agent, projectPath);
|
|
4369
4520
|
const target = getAgentTarget(agent);
|
|
4370
|
-
const filePath = (0,
|
|
4371
|
-
const fileDir = (0,
|
|
4372
|
-
if (!(0,
|
|
4373
|
-
if (agent === "cursor" && !(0,
|
|
4374
|
-
(0,
|
|
4521
|
+
const filePath = (0, import_path9.join)(projectPath, target.instructions);
|
|
4522
|
+
const fileDir = (0, import_path9.dirname)(filePath);
|
|
4523
|
+
if (!(0, import_fs9.existsSync)(fileDir)) (0, import_fs9.mkdirSync)(fileDir, { recursive: true });
|
|
4524
|
+
if (agent === "cursor" && !(0, import_fs9.existsSync)(filePath)) {
|
|
4525
|
+
(0, import_fs9.writeFileSync)(filePath, `---
|
|
4375
4526
|
description: AI development instructions from One-Shot Installer
|
|
4376
4527
|
alwaysApply: true
|
|
4377
4528
|
---
|
|
@@ -4406,20 +4557,28 @@ alwaysApply: true
|
|
|
4406
4557
|
ctx.installed.factoryInstructionsSnippet
|
|
4407
4558
|
);
|
|
4408
4559
|
}
|
|
4560
|
+
if (ctx.installed.abapHooksInstructionsSnippet) {
|
|
4561
|
+
upsertMarkerBlock(
|
|
4562
|
+
filePath,
|
|
4563
|
+
"<!-- abap-mcp-hooks:start -->",
|
|
4564
|
+
"<!-- abap-mcp-hooks:end -->",
|
|
4565
|
+
ctx.installed.abapHooksInstructionsSnippet
|
|
4566
|
+
);
|
|
4567
|
+
}
|
|
4409
4568
|
return { status: "success", message: target.instructions };
|
|
4410
4569
|
}
|
|
4411
4570
|
});
|
|
4412
4571
|
function collectMdFiles(dir) {
|
|
4413
|
-
if (!(0,
|
|
4414
|
-
const entries = (0,
|
|
4572
|
+
if (!(0, import_fs9.existsSync)(dir)) return [];
|
|
4573
|
+
const entries = (0, import_fs9.readdirSync)(dir, { recursive: true });
|
|
4415
4574
|
return entries.filter((entry) => {
|
|
4416
|
-
const fullPath = (0,
|
|
4417
|
-
return entry.endsWith(".md") && (0,
|
|
4575
|
+
const fullPath = (0, import_path9.join)(dir, entry);
|
|
4576
|
+
return entry.endsWith(".md") && (0, import_fs9.statSync)(fullPath).isFile();
|
|
4418
4577
|
}).map((entry) => entry.replace(/\\/g, "/")).sort();
|
|
4419
4578
|
}
|
|
4420
4579
|
function extractFirstHeading(filePath) {
|
|
4421
4580
|
try {
|
|
4422
|
-
const content = (0,
|
|
4581
|
+
const content = (0, import_fs9.readFileSync)(filePath, "utf-8");
|
|
4423
4582
|
const withoutFrontmatter = content.replace(/^---[\s\S]*?---\s*/, "");
|
|
4424
4583
|
const match = withoutFrontmatter.match(/^#\s+(.+)/m);
|
|
4425
4584
|
if (match) {
|
|
@@ -4437,16 +4596,16 @@ function formatPathRef(contextPath, description, agent) {
|
|
|
4437
4596
|
return `- \`${contextPath}\` \u2014 ${description}`;
|
|
4438
4597
|
}
|
|
4439
4598
|
function resolveDomainFolder(domain, contextsDir) {
|
|
4440
|
-
if ((0,
|
|
4599
|
+
if ((0, import_fs9.existsSync)(contextsDir)) {
|
|
4441
4600
|
try {
|
|
4442
|
-
const entries = (0,
|
|
4601
|
+
const entries = (0, import_fs9.readdirSync)(contextsDir);
|
|
4443
4602
|
const match = entries.find((e) => e.toLowerCase() === domain.toLowerCase());
|
|
4444
|
-
if (match) return { folderName: match, folderPath: (0,
|
|
4603
|
+
if (match) return { folderName: match, folderPath: (0, import_path9.join)(contextsDir, match) };
|
|
4445
4604
|
} catch {
|
|
4446
4605
|
}
|
|
4447
4606
|
}
|
|
4448
4607
|
const fallback = domain.toUpperCase();
|
|
4449
|
-
return { folderName: fallback, folderPath: (0,
|
|
4608
|
+
return { folderName: fallback, folderPath: (0, import_path9.join)(contextsDir, fallback) };
|
|
4450
4609
|
}
|
|
4451
4610
|
function buildContextRefsSection(domains, agent, contextsDir) {
|
|
4452
4611
|
const lines2 = [
|
|
@@ -4458,34 +4617,34 @@ function buildContextRefsSection(domains, agent, contextsDir) {
|
|
|
4458
4617
|
let hasAnyFiles = false;
|
|
4459
4618
|
for (const domain of domains) {
|
|
4460
4619
|
const { folderName, folderPath: domainPath } = resolveDomainFolder(domain, contextsDir);
|
|
4461
|
-
if (!(0,
|
|
4620
|
+
if (!(0, import_fs9.existsSync)(domainPath)) continue;
|
|
4462
4621
|
const domainFiles = [];
|
|
4463
|
-
const ctxInstructions = (0,
|
|
4464
|
-
if ((0,
|
|
4622
|
+
const ctxInstructions = (0, import_path9.join)(domainPath, "context-instructions.md");
|
|
4623
|
+
if ((0, import_fs9.existsSync)(ctxInstructions)) {
|
|
4465
4624
|
const desc = extractFirstHeading(ctxInstructions);
|
|
4466
4625
|
domainFiles.push(formatPathRef(`_ai-context/${folderName}/context-instructions.md`, desc, agent));
|
|
4467
4626
|
}
|
|
4468
|
-
const instructionsMd = (0,
|
|
4469
|
-
if ((0,
|
|
4627
|
+
const instructionsMd = (0, import_path9.join)(domainPath, "core", "instructions.md");
|
|
4628
|
+
if ((0, import_fs9.existsSync)(instructionsMd)) {
|
|
4470
4629
|
const desc = extractFirstHeading(instructionsMd);
|
|
4471
4630
|
domainFiles.push(formatPathRef(`_ai-context/${folderName}/core/instructions.md`, `${desc} (start here)`, agent));
|
|
4472
4631
|
}
|
|
4473
|
-
const coreDir = (0,
|
|
4474
|
-
if ((0,
|
|
4632
|
+
const coreDir = (0, import_path9.join)(domainPath, "core");
|
|
4633
|
+
if ((0, import_fs9.existsSync)(coreDir)) {
|
|
4475
4634
|
const coreFiles = collectMdFiles(coreDir).filter((f) => f !== "instructions.md" && !f.startsWith("instructions/"));
|
|
4476
4635
|
for (const file of coreFiles) {
|
|
4477
|
-
const desc = extractFirstHeading((0,
|
|
4636
|
+
const desc = extractFirstHeading((0, import_path9.join)(coreDir, file));
|
|
4478
4637
|
domainFiles.push(formatPathRef(`_ai-context/${folderName}/core/${file}`, desc, agent));
|
|
4479
4638
|
}
|
|
4480
4639
|
}
|
|
4481
|
-
const refDir = (0,
|
|
4482
|
-
if ((0,
|
|
4640
|
+
const refDir = (0, import_path9.join)(domainPath, "reference");
|
|
4641
|
+
if ((0, import_fs9.existsSync)(refDir)) {
|
|
4483
4642
|
const refFiles = collectMdFiles(refDir);
|
|
4484
4643
|
if (refFiles.length > 0) {
|
|
4485
4644
|
domainFiles.push(``);
|
|
4486
4645
|
domainFiles.push(`**Reference & cheat sheets:**`);
|
|
4487
4646
|
for (const file of refFiles) {
|
|
4488
|
-
const desc = extractFirstHeading((0,
|
|
4647
|
+
const desc = extractFirstHeading((0, import_path9.join)(refDir, file));
|
|
4489
4648
|
domainFiles.push(formatPathRef(`_ai-context/${folderName}/reference/${file}`, desc, agent));
|
|
4490
4649
|
}
|
|
4491
4650
|
}
|
|
@@ -4516,7 +4675,7 @@ function buildMCPSection(mcpConfig) {
|
|
|
4516
4675
|
return lines2.join("\n");
|
|
4517
4676
|
}
|
|
4518
4677
|
function buildCombinedInstructions(domains, mcpConfig, agent, projectPath) {
|
|
4519
|
-
const contextsDir = (0,
|
|
4678
|
+
const contextsDir = (0, import_path9.join)(projectPath, "_ai-context");
|
|
4520
4679
|
const lines2 = [`# AI Development Instructions`, ``, `> Generated by One-Shot Installer`, ``];
|
|
4521
4680
|
lines2.push(buildContextRefsSection(domains, agent, contextsDir));
|
|
4522
4681
|
if (Object.keys(mcpConfig).length > 0) lines2.push(buildMCPSection(mcpConfig));
|
|
@@ -4524,8 +4683,8 @@ function buildCombinedInstructions(domains, mcpConfig, agent, projectPath) {
|
|
|
4524
4683
|
}
|
|
4525
4684
|
|
|
4526
4685
|
// src/steps/setup/write-mcp-config.ts
|
|
4527
|
-
var
|
|
4528
|
-
var
|
|
4686
|
+
var import_fs10 = require("fs");
|
|
4687
|
+
var import_path10 = require("path");
|
|
4529
4688
|
var red2 = (text) => `\x1B[31m${text}\x1B[0m`;
|
|
4530
4689
|
var write_mcp_config_default = defineStep({
|
|
4531
4690
|
name: "write-mcp-config",
|
|
@@ -4534,15 +4693,15 @@ var write_mcp_config_default = defineStep({
|
|
|
4534
4693
|
execute: async (ctx) => {
|
|
4535
4694
|
const filteredMcpConfig = getFilteredMcpConfig(ctx);
|
|
4536
4695
|
const target = getAgentTarget(ctx.config.agent);
|
|
4537
|
-
const mcpJsonPath = (0,
|
|
4696
|
+
const mcpJsonPath = (0, import_path10.join)(ctx.config.projectPath, target.mcpConfig);
|
|
4538
4697
|
if (target.mcpDir) {
|
|
4539
|
-
const dir = (0,
|
|
4540
|
-
if (!(0,
|
|
4698
|
+
const dir = (0, import_path10.join)(ctx.config.projectPath, target.mcpDir);
|
|
4699
|
+
if (!(0, import_fs10.existsSync)(dir)) (0, import_fs10.mkdirSync)(dir, { recursive: true });
|
|
4541
4700
|
}
|
|
4542
4701
|
let existingFile = {};
|
|
4543
|
-
if ((0,
|
|
4702
|
+
if ((0, import_fs10.existsSync)(mcpJsonPath)) {
|
|
4544
4703
|
try {
|
|
4545
|
-
existingFile = JSON.parse((0,
|
|
4704
|
+
existingFile = JSON.parse((0, import_fs10.readFileSync)(mcpJsonPath, "utf-8"));
|
|
4546
4705
|
} catch {
|
|
4547
4706
|
const box = [
|
|
4548
4707
|
"",
|
|
@@ -4568,7 +4727,7 @@ var write_mcp_config_default = defineStep({
|
|
|
4568
4727
|
}
|
|
4569
4728
|
const mergedServers = { ...existingServers, ...newServers };
|
|
4570
4729
|
const outputFile = { ...existingFile, [target.mcpRootKey]: mergedServers };
|
|
4571
|
-
(0,
|
|
4730
|
+
(0, import_fs10.writeFileSync)(mcpJsonPath, JSON.stringify(outputFile, null, 2), "utf-8");
|
|
4572
4731
|
ctx.installed.mcpServersAdded = addedServers;
|
|
4573
4732
|
return {
|
|
4574
4733
|
status: "success",
|
|
@@ -4637,7 +4796,7 @@ function isClaudeCliAvailable() {
|
|
|
4637
4796
|
}
|
|
4638
4797
|
|
|
4639
4798
|
// src/steps/setup/update-gitignore.ts
|
|
4640
|
-
var
|
|
4799
|
+
var import_path11 = require("path");
|
|
4641
4800
|
var MARKER_START2 = "# one-shot-installer:start";
|
|
4642
4801
|
var MARKER_END2 = "# one-shot-installer:end";
|
|
4643
4802
|
var CORE_GITIGNORE_ENTRIES = [
|
|
@@ -4658,15 +4817,15 @@ var update_gitignore_default = defineStep({
|
|
|
4658
4817
|
name: "update-gitignore",
|
|
4659
4818
|
label: "Updating .gitignore",
|
|
4660
4819
|
execute: async (ctx) => {
|
|
4661
|
-
const gitignorePath = (0,
|
|
4820
|
+
const gitignorePath = (0, import_path11.join)(ctx.config.projectPath, ".gitignore");
|
|
4662
4821
|
upsertMarkerBlock(gitignorePath, MARKER_START2, MARKER_END2, CORE_GITIGNORE_ENTRIES.join("\n"));
|
|
4663
4822
|
return { status: "success" };
|
|
4664
4823
|
}
|
|
4665
4824
|
});
|
|
4666
4825
|
|
|
4667
4826
|
// src/steps/setup/write-state.ts
|
|
4668
|
-
var
|
|
4669
|
-
var
|
|
4827
|
+
var import_fs11 = require("fs");
|
|
4828
|
+
var import_path12 = require("path");
|
|
4670
4829
|
var import_os2 = require("os");
|
|
4671
4830
|
|
|
4672
4831
|
// package.json
|
|
@@ -4700,7 +4859,7 @@ var write_state_default = defineStep({
|
|
|
4700
4859
|
name: "write-state",
|
|
4701
4860
|
label: "Saving installation state",
|
|
4702
4861
|
execute: async (ctx) => {
|
|
4703
|
-
const statePath = (0,
|
|
4862
|
+
const statePath = (0, import_path12.join)(ctx.config.projectPath, ".one-shot-state.json");
|
|
4704
4863
|
const mcpServersAdded = ctx.installed.mcpServersAdded ?? [];
|
|
4705
4864
|
const filteredMcpConfig = Object.fromEntries(
|
|
4706
4865
|
Object.entries(ctx.config.mcpConfig).filter(([name]) => mcpServersAdded.includes(name))
|
|
@@ -4718,15 +4877,17 @@ var write_state_default = defineStep({
|
|
|
4718
4877
|
mcpServers: mcpServersAdded,
|
|
4719
4878
|
mcpConfigs: filteredMcpConfig,
|
|
4720
4879
|
factoryInstalled: ctx.installed.factoryInstalled ?? false,
|
|
4880
|
+
abapHooksInstalled: ctx.installed.abapHooksInstalled ?? false,
|
|
4721
4881
|
files: {
|
|
4722
4882
|
instructions: target.instructions,
|
|
4723
4883
|
mcpConfig: target.mcpConfig,
|
|
4724
4884
|
contexts: "_ai-context/",
|
|
4725
4885
|
factory: ctx.installed.factoryInstalled ? "factory/" : null,
|
|
4726
|
-
|
|
4886
|
+
abapHooks: ctx.installed.abapHooksInstalled ? ".claude/hooks/" : null,
|
|
4887
|
+
globalConfig: (0, import_path12.join)((0, import_os2.homedir)(), ".one-shot-installer")
|
|
4727
4888
|
}
|
|
4728
4889
|
};
|
|
4729
|
-
(0,
|
|
4890
|
+
(0, import_fs11.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf-8");
|
|
4730
4891
|
return { status: "success" };
|
|
4731
4892
|
}
|
|
4732
4893
|
});
|
|
@@ -4741,19 +4902,19 @@ var dim = (text) => `\x1B[2m${text}\x1B[0m`;
|
|
|
4741
4902
|
var yellow = (text) => `\x1B[33m${text}\x1B[0m`;
|
|
4742
4903
|
var green = (text) => `\x1B[32m${text}\x1B[0m`;
|
|
4743
4904
|
function detectMarkerFileMode(filePath, markerStart) {
|
|
4744
|
-
if (!(0,
|
|
4745
|
-
const content = (0,
|
|
4905
|
+
if (!(0, import_fs12.existsSync)(filePath)) return green("create");
|
|
4906
|
+
const content = (0, import_fs12.readFileSync)(filePath, "utf-8");
|
|
4746
4907
|
if (content.includes(markerStart)) return yellow("update");
|
|
4747
4908
|
return yellow("append");
|
|
4748
4909
|
}
|
|
4749
4910
|
function detectMCPFileMode(filePath) {
|
|
4750
|
-
if (!(0,
|
|
4911
|
+
if (!(0, import_fs12.existsSync)(filePath)) return green("create");
|
|
4751
4912
|
return yellow("merge");
|
|
4752
4913
|
}
|
|
4753
4914
|
function detectContextMode(projectPath, domain) {
|
|
4754
|
-
const contextDir = (0,
|
|
4755
|
-
const contextDirLower = (0,
|
|
4756
|
-
if ((0,
|
|
4915
|
+
const contextDir = (0, import_path13.join)(projectPath, "_ai-context", domain.toUpperCase());
|
|
4916
|
+
const contextDirLower = (0, import_path13.join)(projectPath, "_ai-context", domain);
|
|
4917
|
+
if ((0, import_fs12.existsSync)(contextDir) || (0, import_fs12.existsSync)(contextDirLower)) return yellow("overwrite");
|
|
4757
4918
|
return green("create");
|
|
4758
4919
|
}
|
|
4759
4920
|
function waitForEnter() {
|
|
@@ -4771,10 +4932,11 @@ async function main() {
|
|
|
4771
4932
|
try {
|
|
4772
4933
|
const token = await getGitHubToken();
|
|
4773
4934
|
console.log(" Locating repositories...");
|
|
4774
|
-
const [repo, contextRepo, factoryRepo] = await Promise.all([
|
|
4935
|
+
const [repo, contextRepo, factoryRepo, abapHooksRepo] = await Promise.all([
|
|
4775
4936
|
discoverRepo(token),
|
|
4776
4937
|
discoverRepo(token, "context-data").catch(() => null),
|
|
4777
|
-
discoverRepo(token, "factory-data").catch(() => null)
|
|
4938
|
+
discoverRepo(token, "factory-data").catch(() => null),
|
|
4939
|
+
discoverRepo(token, "abap-mcp-hooks").catch(() => null)
|
|
4778
4940
|
]);
|
|
4779
4941
|
if (!contextRepo) {
|
|
4780
4942
|
console.log(" \u26A0 Context repo not found \u2014 AI contexts will not be installed.");
|
|
@@ -4782,9 +4944,12 @@ async function main() {
|
|
|
4782
4944
|
if (!factoryRepo) {
|
|
4783
4945
|
console.log(" \u26A0 Factory repo not found \u2014 Factory will not be installed.");
|
|
4784
4946
|
}
|
|
4947
|
+
if (!abapHooksRepo) {
|
|
4948
|
+
console.log(" \u26A0 ABAP MCP hooks repo not found \u2014 hooks will not be installed.");
|
|
4949
|
+
}
|
|
4785
4950
|
console.log(" Loading configuration...");
|
|
4786
4951
|
const options = await loadWizardOptions(token, repo);
|
|
4787
|
-
const config = await collectInputs(options, options.releaseVersion, !!factoryRepo);
|
|
4952
|
+
const config = await collectInputs(options, options.releaseVersion, !!factoryRepo, !!abapHooksRepo);
|
|
4788
4953
|
if (!config) {
|
|
4789
4954
|
await waitForEnter();
|
|
4790
4955
|
return;
|
|
@@ -4801,6 +4966,7 @@ async function main() {
|
|
|
4801
4966
|
repo,
|
|
4802
4967
|
contextRepo,
|
|
4803
4968
|
factoryRepo,
|
|
4969
|
+
abapHooksRepo,
|
|
4804
4970
|
installed: {}
|
|
4805
4971
|
};
|
|
4806
4972
|
const stepList = Object.values(steps_exports);
|
|
@@ -4813,7 +4979,7 @@ async function main() {
|
|
|
4813
4979
|
await waitForEnter();
|
|
4814
4980
|
}
|
|
4815
4981
|
}
|
|
4816
|
-
async function collectInputs(options, releaseVersion, factoryAvailable = false) {
|
|
4982
|
+
async function collectInputs(options, releaseVersion, factoryAvailable = false, abapHooksAvailable = false) {
|
|
4817
4983
|
const selectedIds = await esm_default2({
|
|
4818
4984
|
message: "Technology:",
|
|
4819
4985
|
instructions: " Space to select \xB7 Enter to confirm",
|
|
@@ -4851,17 +5017,20 @@ async function collectInputs(options, releaseVersion, factoryAvailable = false)
|
|
|
4851
5017
|
}
|
|
4852
5018
|
const projectInput = await esm_default4({
|
|
4853
5019
|
message: "Project directory:",
|
|
4854
|
-
default: (0,
|
|
5020
|
+
default: (0, import_path13.resolve)(process.cwd())
|
|
4855
5021
|
});
|
|
5022
|
+
const isAbapSelected = selectedTechnologies.some((t) => t.domains.includes("ABAP"));
|
|
5023
|
+
const installAbapHooks = abapHooksAvailable && agent === "claude-code" && isAbapSelected;
|
|
4856
5024
|
return {
|
|
4857
5025
|
technologies: selectedTechnologies,
|
|
4858
5026
|
agent,
|
|
4859
5027
|
azureDevOpsOrg,
|
|
4860
|
-
projectPath: (0,
|
|
5028
|
+
projectPath: (0, import_path13.resolve)(projectInput),
|
|
4861
5029
|
baseMcpServers: options.baseMcpServers,
|
|
4862
5030
|
mcpConfig,
|
|
4863
5031
|
releaseVersion,
|
|
4864
|
-
installFactory: factoryAvailable
|
|
5032
|
+
installFactory: factoryAvailable,
|
|
5033
|
+
installAbapHooks
|
|
4865
5034
|
};
|
|
4866
5035
|
}
|
|
4867
5036
|
async function previewAndConfirm(config, options) {
|
|
@@ -4871,9 +5040,9 @@ async function previewAndConfirm(config, options) {
|
|
|
4871
5040
|
const instructionFile = target.instructions;
|
|
4872
5041
|
const mcpConfigFile = target.mcpConfig;
|
|
4873
5042
|
const serverEntries = Object.entries(config.mcpConfig);
|
|
4874
|
-
const instructionFilePath = (0,
|
|
4875
|
-
const mcpConfigFilePath = (0,
|
|
4876
|
-
const gitignorePath = (0,
|
|
5043
|
+
const instructionFilePath = (0, import_path13.join)(config.projectPath, instructionFile);
|
|
5044
|
+
const mcpConfigFilePath = (0, import_path13.join)(config.projectPath, mcpConfigFile);
|
|
5045
|
+
const gitignorePath = (0, import_path13.join)(config.projectPath, ".gitignore");
|
|
4877
5046
|
const instructionMode = detectMarkerFileMode(instructionFilePath, "<!-- one-shot-installer:start -->");
|
|
4878
5047
|
const mcpMode = detectMCPFileMode(mcpConfigFilePath);
|
|
4879
5048
|
const gitignoreMode = detectMarkerFileMode(gitignorePath, "# one-shot-installer:start");
|
|
@@ -4888,6 +5057,7 @@ async function previewAndConfirm(config, options) {
|
|
|
4888
5057
|
console.log(` Technology ${technologyNames}`);
|
|
4889
5058
|
console.log(` Tool ${agentDisplay}`);
|
|
4890
5059
|
console.log(` Factory ${config.installFactory ? "yes" : "no"}`);
|
|
5060
|
+
console.log(` ABAP hooks ${config.installAbapHooks ? "yes" : "no"}`);
|
|
4891
5061
|
console.log(` Directory ${config.projectPath}`);
|
|
4892
5062
|
console.log("");
|
|
4893
5063
|
console.log(` ${dim("File actions:")}`);
|
|
@@ -4899,10 +5069,15 @@ async function previewAndConfirm(config, options) {
|
|
|
4899
5069
|
console.log(` ${mcpConfigFile.padEnd(domainColWidth + 14)}${mcpMode}`);
|
|
4900
5070
|
console.log(` ${".gitignore".padEnd(domainColWidth + 14)}${gitignoreMode}`);
|
|
4901
5071
|
if (config.installFactory) {
|
|
4902
|
-
const factoryExists = (0,
|
|
5072
|
+
const factoryExists = (0, import_fs12.existsSync)((0, import_path13.join)(config.projectPath, "factory"));
|
|
4903
5073
|
const factoryMode = factoryExists ? yellow("overwrite") : green("create");
|
|
4904
5074
|
console.log(` ${"factory/".padEnd(domainColWidth + 14)}${factoryMode}`);
|
|
4905
5075
|
}
|
|
5076
|
+
if (config.installAbapHooks) {
|
|
5077
|
+
const hooksDir = (0, import_path13.join)(config.projectPath, ".claude", "hooks");
|
|
5078
|
+
const hooksMode = (0, import_fs12.existsSync)(hooksDir) ? yellow("merge") : green("create");
|
|
5079
|
+
console.log(` ${".claude/hooks/".padEnd(domainColWidth + 14)}${hooksMode}`);
|
|
5080
|
+
}
|
|
4906
5081
|
if (serverEntries.length > 0) {
|
|
4907
5082
|
console.log("");
|
|
4908
5083
|
console.log(` ${dim("MCP servers:")}`);
|