dlw-machine-setup 0.8.1 → 0.8.3
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 +257 -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,160 @@ 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
|
+
{
|
|
4362
|
+
event: "PreToolUse",
|
|
4363
|
+
script: "sap-safety-gate.mjs",
|
|
4364
|
+
/* Fires on lifecycle/preview tools regardless of which MCP
|
|
4365
|
+
* server exposes them — the safety gate's job is to block
|
|
4366
|
+
* delete + transport release operations wherever they originate. */
|
|
4367
|
+
matcher: "mcp__.*__sap_(object_lifecycle|transport_lifecycle|data_preview)"
|
|
4368
|
+
},
|
|
4369
|
+
{
|
|
4370
|
+
event: "PostToolUse",
|
|
4371
|
+
script: "telemetry-post-tool.mjs",
|
|
4372
|
+
/* Telemetry only meaningful for the SAP ABAP MCP server itself. */
|
|
4373
|
+
matcher: "mcp__sap-abap-mcp__.*"
|
|
4374
|
+
},
|
|
4375
|
+
{
|
|
4376
|
+
event: "PostToolUseFailure",
|
|
4377
|
+
script: "telemetry-post-tool-failure.mjs",
|
|
4378
|
+
matcher: "mcp__sap-abap-mcp__.*"
|
|
4379
|
+
},
|
|
4380
|
+
{ event: "SessionStart", script: "telemetry-session-start.mjs" },
|
|
4381
|
+
{ event: "SessionEnd", script: "telemetry-session-end.mjs" }
|
|
4382
|
+
];
|
|
4383
|
+
var INSTRUCTIONS_SNIPPET = [
|
|
4384
|
+
`## SAP ABAP MCP Hooks`,
|
|
4385
|
+
``,
|
|
4386
|
+
`Installed by One-Shot Installer. Hook scripts live in \`.claude/hooks/\` and are wired into \`.claude/settings.json\`.`,
|
|
4387
|
+
``,
|
|
4388
|
+
`**What they do:**`,
|
|
4389
|
+
`- \`sap-safety-gate.mjs\` (PreToolUse) \u2014 blocks \`delete\` (any tool) and transport \`release\`.`,
|
|
4390
|
+
`- \`telemetry-*.mjs\` (PostToolUse, PostToolUseFailure, SessionStart, SessionEnd) \u2014 emit usage events.`,
|
|
4391
|
+
``,
|
|
4392
|
+
`**Optional environment variables:**`,
|
|
4393
|
+
`- \`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.`,
|
|
4394
|
+
`- \`SAP_MCP_TELEMETRY_TOKEN\` \u2014 Bearer token for the telemetry endpoint.`,
|
|
4395
|
+
``,
|
|
4396
|
+
`On network/HTTP failure, telemetry events fall back to \`~/.claude/telemetry/events.jsonl\`.`,
|
|
4397
|
+
``
|
|
4398
|
+
].join("\n");
|
|
4399
|
+
var fetch_abap_hooks_default = defineStep({
|
|
4400
|
+
name: "fetch-abap-hooks",
|
|
4401
|
+
label: "Installing ABAP MCP hooks",
|
|
4402
|
+
/* All eligibility (Claude-only + ABAP selected + repo discovered) is
|
|
4403
|
+
* resolved upfront in collectInputs() and frozen on InstallConfig.
|
|
4404
|
+
* Reading the single flag keeps the guard, the preview, and the
|
|
4405
|
+
* step in sync with no scattered conditionals. */
|
|
4406
|
+
when: (ctx) => ctx.config.installAbapHooks,
|
|
4407
|
+
execute: async (ctx) => {
|
|
4408
|
+
const result = await fetchAbapHooks(
|
|
4409
|
+
ctx.token,
|
|
4410
|
+
ctx.abapHooksRepo,
|
|
4411
|
+
ctx.config.projectPath,
|
|
4412
|
+
ctx.config.agent
|
|
4413
|
+
);
|
|
4414
|
+
ctx.installed.abapHooksInstalled = result.success;
|
|
4415
|
+
if (result.instructionsSnippet) {
|
|
4416
|
+
ctx.installed.abapHooksInstructionsSnippet = result.instructionsSnippet;
|
|
4417
|
+
}
|
|
4418
|
+
if (!result.success) {
|
|
4419
|
+
return { status: "failed", detail: result.failureReason };
|
|
4420
|
+
}
|
|
4421
|
+
return { status: "success", message: result.filesInstalled.join(", ") };
|
|
4422
|
+
}
|
|
4423
|
+
});
|
|
4424
|
+
async function fetchAbapHooks(token, repo, targetDir, agent) {
|
|
4425
|
+
const result = { success: false, filesInstalled: [] };
|
|
4426
|
+
let release;
|
|
4427
|
+
try {
|
|
4428
|
+
release = await fetchLatestReleaseByTagPrefix(token, repo, HOOKS_TAG_PREFIX);
|
|
4429
|
+
} catch (err) {
|
|
4430
|
+
result.failureReason = err instanceof Error ? err.message : String(err);
|
|
4431
|
+
return result;
|
|
4432
|
+
}
|
|
4433
|
+
const asset = release.assets.find((a) => a.name === HOOKS_ASSET_NAME);
|
|
4434
|
+
if (!asset) {
|
|
4435
|
+
result.failureReason = `${HOOKS_ASSET_NAME} not found in release ${release.tagName}`;
|
|
4436
|
+
return result;
|
|
4437
|
+
}
|
|
4438
|
+
let archive = null;
|
|
4439
|
+
try {
|
|
4440
|
+
archive = await downloadAndExtractAsset(token, asset, targetDir, ".temp-abap-hooks-download");
|
|
4441
|
+
const extractedEntries = (0, import_fs8.readdirSync)(archive.extractedRoot);
|
|
4442
|
+
const innerFolder = extractedEntries.find(
|
|
4443
|
+
(e) => e.toLowerCase() === HOOKS_ROOT_FOLDER.toLowerCase()
|
|
4444
|
+
);
|
|
4445
|
+
if (!innerFolder) {
|
|
4446
|
+
result.failureReason = `No ${HOOKS_ROOT_FOLDER}/ folder in archive`;
|
|
4447
|
+
return result;
|
|
4448
|
+
}
|
|
4449
|
+
const bundleRoot = (0, import_path8.join)(archive.extractedRoot, innerFolder);
|
|
4450
|
+
if (!(0, import_fs8.statSync)(bundleRoot).isDirectory()) {
|
|
4451
|
+
result.failureReason = `${HOOKS_ROOT_FOLDER}/ entry is not a directory`;
|
|
4452
|
+
return result;
|
|
4453
|
+
}
|
|
4454
|
+
const presentDefs = HOOK_DEFINITIONS.filter((d) => (0, import_fs8.existsSync)((0, import_path8.join)(bundleRoot, d.script)));
|
|
4455
|
+
if (presentDefs.length === 0) {
|
|
4456
|
+
result.failureReason = "Archive contains no recognized hook scripts";
|
|
4457
|
+
return result;
|
|
4458
|
+
}
|
|
4459
|
+
const assets = [
|
|
4460
|
+
...presentDefs.map((d) => ({
|
|
4461
|
+
type: "hook",
|
|
4462
|
+
event: d.event,
|
|
4463
|
+
/* {hookDir} is substituted by run-bundle.ts with the agent
|
|
4464
|
+
* profile's scriptDir (.claude/hooks for Claude Code), so the
|
|
4465
|
+
* resulting command is exactly `node .claude/hooks/<script>`,
|
|
4466
|
+
* matching the form Claude Code already uses for Factory hooks. */
|
|
4467
|
+
command: `node {hookDir}/${d.script}`,
|
|
4468
|
+
script: d.script,
|
|
4469
|
+
/* The runner copies the matcher straight into the merged JSON
|
|
4470
|
+
* entry. Omitted when undefined so session-level hooks don't
|
|
4471
|
+
* carry a useless `matcher` field. */
|
|
4472
|
+
...d.matcher ? { matcher: d.matcher } : {}
|
|
4473
|
+
})),
|
|
4474
|
+
{ type: "instructions-snippet", content: INSTRUCTIONS_SNIPPET }
|
|
4475
|
+
];
|
|
4476
|
+
const manifest = {
|
|
4477
|
+
schemaVersion: 2,
|
|
4478
|
+
name: BUNDLE_NAME,
|
|
4479
|
+
assets
|
|
4480
|
+
};
|
|
4481
|
+
const target = getAgentTarget(agent);
|
|
4482
|
+
const runResult = await runBundle(manifest, {
|
|
4483
|
+
bundleRoot,
|
|
4484
|
+
projectPath: targetDir,
|
|
4485
|
+
agent,
|
|
4486
|
+
instructionsFile: target.instructions,
|
|
4487
|
+
/* write-instructions.ts owns the ordering of marker blocks in
|
|
4488
|
+
* CLAUDE.md (one-shot-installer:start/end first, bundle blocks
|
|
4489
|
+
* after). Skipping here means we hand the snippet back; it gets
|
|
4490
|
+
* committed when write-instructions.ts runs in the setup phase. */
|
|
4491
|
+
skipInstructions: true
|
|
4492
|
+
});
|
|
4493
|
+
result.filesInstalled = runResult.filesTouched;
|
|
4494
|
+
result.instructionsSnippet = runResult.instructionsSnippet;
|
|
4495
|
+
result.success = true;
|
|
4496
|
+
} catch (error) {
|
|
4497
|
+
result.failureReason = error instanceof Error ? error.message : String(error);
|
|
4498
|
+
} finally {
|
|
4499
|
+
archive?.cleanup();
|
|
4500
|
+
}
|
|
4501
|
+
return result;
|
|
4502
|
+
}
|
|
4503
|
+
|
|
4504
|
+
// src/steps/setup/write-instructions.ts
|
|
4505
|
+
var import_fs9 = require("fs");
|
|
4506
|
+
var import_path9 = require("path");
|
|
4336
4507
|
|
|
4337
4508
|
// src/steps/shared.ts
|
|
4338
4509
|
var cached = null;
|
|
@@ -4359,7 +4530,7 @@ var red = (text) => `\x1B[31m${text}\x1B[0m`;
|
|
|
4359
4530
|
var write_instructions_default = defineStep({
|
|
4360
4531
|
name: "write-instructions",
|
|
4361
4532
|
label: "Writing instruction file",
|
|
4362
|
-
when: (ctx) => (ctx.installed.domainsInstalled?.length ?? 0) > 0 || Object.keys(ctx.config.mcpConfig).length > 0 || !!ctx.installed.factoryInstructionsSnippet,
|
|
4533
|
+
when: (ctx) => (ctx.installed.domainsInstalled?.length ?? 0) > 0 || Object.keys(ctx.config.mcpConfig).length > 0 || !!ctx.installed.factoryInstructionsSnippet || !!ctx.installed.abapHooksInstructionsSnippet,
|
|
4363
4534
|
execute: async (ctx) => {
|
|
4364
4535
|
const filteredMcpConfig = getFilteredMcpConfig(ctx);
|
|
4365
4536
|
const domains = ctx.installed.domainsInstalled ?? [];
|
|
@@ -4367,11 +4538,11 @@ var write_instructions_default = defineStep({
|
|
|
4367
4538
|
const projectPath = ctx.config.projectPath;
|
|
4368
4539
|
const content = buildCombinedInstructions(domains, filteredMcpConfig, agent, projectPath);
|
|
4369
4540
|
const target = getAgentTarget(agent);
|
|
4370
|
-
const filePath = (0,
|
|
4371
|
-
const fileDir = (0,
|
|
4372
|
-
if (!(0,
|
|
4373
|
-
if (agent === "cursor" && !(0,
|
|
4374
|
-
(0,
|
|
4541
|
+
const filePath = (0, import_path9.join)(projectPath, target.instructions);
|
|
4542
|
+
const fileDir = (0, import_path9.dirname)(filePath);
|
|
4543
|
+
if (!(0, import_fs9.existsSync)(fileDir)) (0, import_fs9.mkdirSync)(fileDir, { recursive: true });
|
|
4544
|
+
if (agent === "cursor" && !(0, import_fs9.existsSync)(filePath)) {
|
|
4545
|
+
(0, import_fs9.writeFileSync)(filePath, `---
|
|
4375
4546
|
description: AI development instructions from One-Shot Installer
|
|
4376
4547
|
alwaysApply: true
|
|
4377
4548
|
---
|
|
@@ -4406,20 +4577,28 @@ alwaysApply: true
|
|
|
4406
4577
|
ctx.installed.factoryInstructionsSnippet
|
|
4407
4578
|
);
|
|
4408
4579
|
}
|
|
4580
|
+
if (ctx.installed.abapHooksInstructionsSnippet) {
|
|
4581
|
+
upsertMarkerBlock(
|
|
4582
|
+
filePath,
|
|
4583
|
+
"<!-- abap-mcp-hooks:start -->",
|
|
4584
|
+
"<!-- abap-mcp-hooks:end -->",
|
|
4585
|
+
ctx.installed.abapHooksInstructionsSnippet
|
|
4586
|
+
);
|
|
4587
|
+
}
|
|
4409
4588
|
return { status: "success", message: target.instructions };
|
|
4410
4589
|
}
|
|
4411
4590
|
});
|
|
4412
4591
|
function collectMdFiles(dir) {
|
|
4413
|
-
if (!(0,
|
|
4414
|
-
const entries = (0,
|
|
4592
|
+
if (!(0, import_fs9.existsSync)(dir)) return [];
|
|
4593
|
+
const entries = (0, import_fs9.readdirSync)(dir, { recursive: true });
|
|
4415
4594
|
return entries.filter((entry) => {
|
|
4416
|
-
const fullPath = (0,
|
|
4417
|
-
return entry.endsWith(".md") && (0,
|
|
4595
|
+
const fullPath = (0, import_path9.join)(dir, entry);
|
|
4596
|
+
return entry.endsWith(".md") && (0, import_fs9.statSync)(fullPath).isFile();
|
|
4418
4597
|
}).map((entry) => entry.replace(/\\/g, "/")).sort();
|
|
4419
4598
|
}
|
|
4420
4599
|
function extractFirstHeading(filePath) {
|
|
4421
4600
|
try {
|
|
4422
|
-
const content = (0,
|
|
4601
|
+
const content = (0, import_fs9.readFileSync)(filePath, "utf-8");
|
|
4423
4602
|
const withoutFrontmatter = content.replace(/^---[\s\S]*?---\s*/, "");
|
|
4424
4603
|
const match = withoutFrontmatter.match(/^#\s+(.+)/m);
|
|
4425
4604
|
if (match) {
|
|
@@ -4437,16 +4616,16 @@ function formatPathRef(contextPath, description, agent) {
|
|
|
4437
4616
|
return `- \`${contextPath}\` \u2014 ${description}`;
|
|
4438
4617
|
}
|
|
4439
4618
|
function resolveDomainFolder(domain, contextsDir) {
|
|
4440
|
-
if ((0,
|
|
4619
|
+
if ((0, import_fs9.existsSync)(contextsDir)) {
|
|
4441
4620
|
try {
|
|
4442
|
-
const entries = (0,
|
|
4621
|
+
const entries = (0, import_fs9.readdirSync)(contextsDir);
|
|
4443
4622
|
const match = entries.find((e) => e.toLowerCase() === domain.toLowerCase());
|
|
4444
|
-
if (match) return { folderName: match, folderPath: (0,
|
|
4623
|
+
if (match) return { folderName: match, folderPath: (0, import_path9.join)(contextsDir, match) };
|
|
4445
4624
|
} catch {
|
|
4446
4625
|
}
|
|
4447
4626
|
}
|
|
4448
4627
|
const fallback = domain.toUpperCase();
|
|
4449
|
-
return { folderName: fallback, folderPath: (0,
|
|
4628
|
+
return { folderName: fallback, folderPath: (0, import_path9.join)(contextsDir, fallback) };
|
|
4450
4629
|
}
|
|
4451
4630
|
function buildContextRefsSection(domains, agent, contextsDir) {
|
|
4452
4631
|
const lines2 = [
|
|
@@ -4458,34 +4637,34 @@ function buildContextRefsSection(domains, agent, contextsDir) {
|
|
|
4458
4637
|
let hasAnyFiles = false;
|
|
4459
4638
|
for (const domain of domains) {
|
|
4460
4639
|
const { folderName, folderPath: domainPath } = resolveDomainFolder(domain, contextsDir);
|
|
4461
|
-
if (!(0,
|
|
4640
|
+
if (!(0, import_fs9.existsSync)(domainPath)) continue;
|
|
4462
4641
|
const domainFiles = [];
|
|
4463
|
-
const ctxInstructions = (0,
|
|
4464
|
-
if ((0,
|
|
4642
|
+
const ctxInstructions = (0, import_path9.join)(domainPath, "context-instructions.md");
|
|
4643
|
+
if ((0, import_fs9.existsSync)(ctxInstructions)) {
|
|
4465
4644
|
const desc = extractFirstHeading(ctxInstructions);
|
|
4466
4645
|
domainFiles.push(formatPathRef(`_ai-context/${folderName}/context-instructions.md`, desc, agent));
|
|
4467
4646
|
}
|
|
4468
|
-
const instructionsMd = (0,
|
|
4469
|
-
if ((0,
|
|
4647
|
+
const instructionsMd = (0, import_path9.join)(domainPath, "core", "instructions.md");
|
|
4648
|
+
if ((0, import_fs9.existsSync)(instructionsMd)) {
|
|
4470
4649
|
const desc = extractFirstHeading(instructionsMd);
|
|
4471
4650
|
domainFiles.push(formatPathRef(`_ai-context/${folderName}/core/instructions.md`, `${desc} (start here)`, agent));
|
|
4472
4651
|
}
|
|
4473
|
-
const coreDir = (0,
|
|
4474
|
-
if ((0,
|
|
4652
|
+
const coreDir = (0, import_path9.join)(domainPath, "core");
|
|
4653
|
+
if ((0, import_fs9.existsSync)(coreDir)) {
|
|
4475
4654
|
const coreFiles = collectMdFiles(coreDir).filter((f) => f !== "instructions.md" && !f.startsWith("instructions/"));
|
|
4476
4655
|
for (const file of coreFiles) {
|
|
4477
|
-
const desc = extractFirstHeading((0,
|
|
4656
|
+
const desc = extractFirstHeading((0, import_path9.join)(coreDir, file));
|
|
4478
4657
|
domainFiles.push(formatPathRef(`_ai-context/${folderName}/core/${file}`, desc, agent));
|
|
4479
4658
|
}
|
|
4480
4659
|
}
|
|
4481
|
-
const refDir = (0,
|
|
4482
|
-
if ((0,
|
|
4660
|
+
const refDir = (0, import_path9.join)(domainPath, "reference");
|
|
4661
|
+
if ((0, import_fs9.existsSync)(refDir)) {
|
|
4483
4662
|
const refFiles = collectMdFiles(refDir);
|
|
4484
4663
|
if (refFiles.length > 0) {
|
|
4485
4664
|
domainFiles.push(``);
|
|
4486
4665
|
domainFiles.push(`**Reference & cheat sheets:**`);
|
|
4487
4666
|
for (const file of refFiles) {
|
|
4488
|
-
const desc = extractFirstHeading((0,
|
|
4667
|
+
const desc = extractFirstHeading((0, import_path9.join)(refDir, file));
|
|
4489
4668
|
domainFiles.push(formatPathRef(`_ai-context/${folderName}/reference/${file}`, desc, agent));
|
|
4490
4669
|
}
|
|
4491
4670
|
}
|
|
@@ -4516,7 +4695,7 @@ function buildMCPSection(mcpConfig) {
|
|
|
4516
4695
|
return lines2.join("\n");
|
|
4517
4696
|
}
|
|
4518
4697
|
function buildCombinedInstructions(domains, mcpConfig, agent, projectPath) {
|
|
4519
|
-
const contextsDir = (0,
|
|
4698
|
+
const contextsDir = (0, import_path9.join)(projectPath, "_ai-context");
|
|
4520
4699
|
const lines2 = [`# AI Development Instructions`, ``, `> Generated by One-Shot Installer`, ``];
|
|
4521
4700
|
lines2.push(buildContextRefsSection(domains, agent, contextsDir));
|
|
4522
4701
|
if (Object.keys(mcpConfig).length > 0) lines2.push(buildMCPSection(mcpConfig));
|
|
@@ -4524,8 +4703,8 @@ function buildCombinedInstructions(domains, mcpConfig, agent, projectPath) {
|
|
|
4524
4703
|
}
|
|
4525
4704
|
|
|
4526
4705
|
// src/steps/setup/write-mcp-config.ts
|
|
4527
|
-
var
|
|
4528
|
-
var
|
|
4706
|
+
var import_fs10 = require("fs");
|
|
4707
|
+
var import_path10 = require("path");
|
|
4529
4708
|
var red2 = (text) => `\x1B[31m${text}\x1B[0m`;
|
|
4530
4709
|
var write_mcp_config_default = defineStep({
|
|
4531
4710
|
name: "write-mcp-config",
|
|
@@ -4534,15 +4713,15 @@ var write_mcp_config_default = defineStep({
|
|
|
4534
4713
|
execute: async (ctx) => {
|
|
4535
4714
|
const filteredMcpConfig = getFilteredMcpConfig(ctx);
|
|
4536
4715
|
const target = getAgentTarget(ctx.config.agent);
|
|
4537
|
-
const mcpJsonPath = (0,
|
|
4716
|
+
const mcpJsonPath = (0, import_path10.join)(ctx.config.projectPath, target.mcpConfig);
|
|
4538
4717
|
if (target.mcpDir) {
|
|
4539
|
-
const dir = (0,
|
|
4540
|
-
if (!(0,
|
|
4718
|
+
const dir = (0, import_path10.join)(ctx.config.projectPath, target.mcpDir);
|
|
4719
|
+
if (!(0, import_fs10.existsSync)(dir)) (0, import_fs10.mkdirSync)(dir, { recursive: true });
|
|
4541
4720
|
}
|
|
4542
4721
|
let existingFile = {};
|
|
4543
|
-
if ((0,
|
|
4722
|
+
if ((0, import_fs10.existsSync)(mcpJsonPath)) {
|
|
4544
4723
|
try {
|
|
4545
|
-
existingFile = JSON.parse((0,
|
|
4724
|
+
existingFile = JSON.parse((0, import_fs10.readFileSync)(mcpJsonPath, "utf-8"));
|
|
4546
4725
|
} catch {
|
|
4547
4726
|
const box = [
|
|
4548
4727
|
"",
|
|
@@ -4568,7 +4747,7 @@ var write_mcp_config_default = defineStep({
|
|
|
4568
4747
|
}
|
|
4569
4748
|
const mergedServers = { ...existingServers, ...newServers };
|
|
4570
4749
|
const outputFile = { ...existingFile, [target.mcpRootKey]: mergedServers };
|
|
4571
|
-
(0,
|
|
4750
|
+
(0, import_fs10.writeFileSync)(mcpJsonPath, JSON.stringify(outputFile, null, 2), "utf-8");
|
|
4572
4751
|
ctx.installed.mcpServersAdded = addedServers;
|
|
4573
4752
|
return {
|
|
4574
4753
|
status: "success",
|
|
@@ -4637,7 +4816,7 @@ function isClaudeCliAvailable() {
|
|
|
4637
4816
|
}
|
|
4638
4817
|
|
|
4639
4818
|
// src/steps/setup/update-gitignore.ts
|
|
4640
|
-
var
|
|
4819
|
+
var import_path11 = require("path");
|
|
4641
4820
|
var MARKER_START2 = "# one-shot-installer:start";
|
|
4642
4821
|
var MARKER_END2 = "# one-shot-installer:end";
|
|
4643
4822
|
var CORE_GITIGNORE_ENTRIES = [
|
|
@@ -4658,15 +4837,15 @@ var update_gitignore_default = defineStep({
|
|
|
4658
4837
|
name: "update-gitignore",
|
|
4659
4838
|
label: "Updating .gitignore",
|
|
4660
4839
|
execute: async (ctx) => {
|
|
4661
|
-
const gitignorePath = (0,
|
|
4840
|
+
const gitignorePath = (0, import_path11.join)(ctx.config.projectPath, ".gitignore");
|
|
4662
4841
|
upsertMarkerBlock(gitignorePath, MARKER_START2, MARKER_END2, CORE_GITIGNORE_ENTRIES.join("\n"));
|
|
4663
4842
|
return { status: "success" };
|
|
4664
4843
|
}
|
|
4665
4844
|
});
|
|
4666
4845
|
|
|
4667
4846
|
// src/steps/setup/write-state.ts
|
|
4668
|
-
var
|
|
4669
|
-
var
|
|
4847
|
+
var import_fs11 = require("fs");
|
|
4848
|
+
var import_path12 = require("path");
|
|
4670
4849
|
var import_os2 = require("os");
|
|
4671
4850
|
|
|
4672
4851
|
// package.json
|
|
@@ -4700,7 +4879,7 @@ var write_state_default = defineStep({
|
|
|
4700
4879
|
name: "write-state",
|
|
4701
4880
|
label: "Saving installation state",
|
|
4702
4881
|
execute: async (ctx) => {
|
|
4703
|
-
const statePath = (0,
|
|
4882
|
+
const statePath = (0, import_path12.join)(ctx.config.projectPath, ".one-shot-state.json");
|
|
4704
4883
|
const mcpServersAdded = ctx.installed.mcpServersAdded ?? [];
|
|
4705
4884
|
const filteredMcpConfig = Object.fromEntries(
|
|
4706
4885
|
Object.entries(ctx.config.mcpConfig).filter(([name]) => mcpServersAdded.includes(name))
|
|
@@ -4718,15 +4897,17 @@ var write_state_default = defineStep({
|
|
|
4718
4897
|
mcpServers: mcpServersAdded,
|
|
4719
4898
|
mcpConfigs: filteredMcpConfig,
|
|
4720
4899
|
factoryInstalled: ctx.installed.factoryInstalled ?? false,
|
|
4900
|
+
abapHooksInstalled: ctx.installed.abapHooksInstalled ?? false,
|
|
4721
4901
|
files: {
|
|
4722
4902
|
instructions: target.instructions,
|
|
4723
4903
|
mcpConfig: target.mcpConfig,
|
|
4724
4904
|
contexts: "_ai-context/",
|
|
4725
4905
|
factory: ctx.installed.factoryInstalled ? "factory/" : null,
|
|
4726
|
-
|
|
4906
|
+
abapHooks: ctx.installed.abapHooksInstalled ? ".claude/hooks/" : null,
|
|
4907
|
+
globalConfig: (0, import_path12.join)((0, import_os2.homedir)(), ".one-shot-installer")
|
|
4727
4908
|
}
|
|
4728
4909
|
};
|
|
4729
|
-
(0,
|
|
4910
|
+
(0, import_fs11.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf-8");
|
|
4730
4911
|
return { status: "success" };
|
|
4731
4912
|
}
|
|
4732
4913
|
});
|
|
@@ -4741,19 +4922,19 @@ var dim = (text) => `\x1B[2m${text}\x1B[0m`;
|
|
|
4741
4922
|
var yellow = (text) => `\x1B[33m${text}\x1B[0m`;
|
|
4742
4923
|
var green = (text) => `\x1B[32m${text}\x1B[0m`;
|
|
4743
4924
|
function detectMarkerFileMode(filePath, markerStart) {
|
|
4744
|
-
if (!(0,
|
|
4745
|
-
const content = (0,
|
|
4925
|
+
if (!(0, import_fs12.existsSync)(filePath)) return green("create");
|
|
4926
|
+
const content = (0, import_fs12.readFileSync)(filePath, "utf-8");
|
|
4746
4927
|
if (content.includes(markerStart)) return yellow("update");
|
|
4747
4928
|
return yellow("append");
|
|
4748
4929
|
}
|
|
4749
4930
|
function detectMCPFileMode(filePath) {
|
|
4750
|
-
if (!(0,
|
|
4931
|
+
if (!(0, import_fs12.existsSync)(filePath)) return green("create");
|
|
4751
4932
|
return yellow("merge");
|
|
4752
4933
|
}
|
|
4753
4934
|
function detectContextMode(projectPath, domain) {
|
|
4754
|
-
const contextDir = (0,
|
|
4755
|
-
const contextDirLower = (0,
|
|
4756
|
-
if ((0,
|
|
4935
|
+
const contextDir = (0, import_path13.join)(projectPath, "_ai-context", domain.toUpperCase());
|
|
4936
|
+
const contextDirLower = (0, import_path13.join)(projectPath, "_ai-context", domain);
|
|
4937
|
+
if ((0, import_fs12.existsSync)(contextDir) || (0, import_fs12.existsSync)(contextDirLower)) return yellow("overwrite");
|
|
4757
4938
|
return green("create");
|
|
4758
4939
|
}
|
|
4759
4940
|
function waitForEnter() {
|
|
@@ -4771,10 +4952,11 @@ async function main() {
|
|
|
4771
4952
|
try {
|
|
4772
4953
|
const token = await getGitHubToken();
|
|
4773
4954
|
console.log(" Locating repositories...");
|
|
4774
|
-
const [repo, contextRepo, factoryRepo] = await Promise.all([
|
|
4955
|
+
const [repo, contextRepo, factoryRepo, abapHooksRepo] = await Promise.all([
|
|
4775
4956
|
discoverRepo(token),
|
|
4776
4957
|
discoverRepo(token, "context-data").catch(() => null),
|
|
4777
|
-
discoverRepo(token, "factory-data").catch(() => null)
|
|
4958
|
+
discoverRepo(token, "factory-data").catch(() => null),
|
|
4959
|
+
discoverRepo(token, "abap-mcp-hooks").catch(() => null)
|
|
4778
4960
|
]);
|
|
4779
4961
|
if (!contextRepo) {
|
|
4780
4962
|
console.log(" \u26A0 Context repo not found \u2014 AI contexts will not be installed.");
|
|
@@ -4782,9 +4964,12 @@ async function main() {
|
|
|
4782
4964
|
if (!factoryRepo) {
|
|
4783
4965
|
console.log(" \u26A0 Factory repo not found \u2014 Factory will not be installed.");
|
|
4784
4966
|
}
|
|
4967
|
+
if (!abapHooksRepo) {
|
|
4968
|
+
console.log(" \u26A0 ABAP MCP hooks repo not found \u2014 hooks will not be installed.");
|
|
4969
|
+
}
|
|
4785
4970
|
console.log(" Loading configuration...");
|
|
4786
4971
|
const options = await loadWizardOptions(token, repo);
|
|
4787
|
-
const config = await collectInputs(options, options.releaseVersion, !!factoryRepo);
|
|
4972
|
+
const config = await collectInputs(options, options.releaseVersion, !!factoryRepo, !!abapHooksRepo);
|
|
4788
4973
|
if (!config) {
|
|
4789
4974
|
await waitForEnter();
|
|
4790
4975
|
return;
|
|
@@ -4801,6 +4986,7 @@ async function main() {
|
|
|
4801
4986
|
repo,
|
|
4802
4987
|
contextRepo,
|
|
4803
4988
|
factoryRepo,
|
|
4989
|
+
abapHooksRepo,
|
|
4804
4990
|
installed: {}
|
|
4805
4991
|
};
|
|
4806
4992
|
const stepList = Object.values(steps_exports);
|
|
@@ -4813,7 +4999,7 @@ async function main() {
|
|
|
4813
4999
|
await waitForEnter();
|
|
4814
5000
|
}
|
|
4815
5001
|
}
|
|
4816
|
-
async function collectInputs(options, releaseVersion, factoryAvailable = false) {
|
|
5002
|
+
async function collectInputs(options, releaseVersion, factoryAvailable = false, abapHooksAvailable = false) {
|
|
4817
5003
|
const selectedIds = await esm_default2({
|
|
4818
5004
|
message: "Technology:",
|
|
4819
5005
|
instructions: " Space to select \xB7 Enter to confirm",
|
|
@@ -4851,17 +5037,20 @@ async function collectInputs(options, releaseVersion, factoryAvailable = false)
|
|
|
4851
5037
|
}
|
|
4852
5038
|
const projectInput = await esm_default4({
|
|
4853
5039
|
message: "Project directory:",
|
|
4854
|
-
default: (0,
|
|
5040
|
+
default: (0, import_path13.resolve)(process.cwd())
|
|
4855
5041
|
});
|
|
5042
|
+
const isAbapSelected = selectedTechnologies.some((t) => t.domains.includes("ABAP"));
|
|
5043
|
+
const installAbapHooks = abapHooksAvailable && agent === "claude-code" && isAbapSelected;
|
|
4856
5044
|
return {
|
|
4857
5045
|
technologies: selectedTechnologies,
|
|
4858
5046
|
agent,
|
|
4859
5047
|
azureDevOpsOrg,
|
|
4860
|
-
projectPath: (0,
|
|
5048
|
+
projectPath: (0, import_path13.resolve)(projectInput),
|
|
4861
5049
|
baseMcpServers: options.baseMcpServers,
|
|
4862
5050
|
mcpConfig,
|
|
4863
5051
|
releaseVersion,
|
|
4864
|
-
installFactory: factoryAvailable
|
|
5052
|
+
installFactory: factoryAvailable,
|
|
5053
|
+
installAbapHooks
|
|
4865
5054
|
};
|
|
4866
5055
|
}
|
|
4867
5056
|
async function previewAndConfirm(config, options) {
|
|
@@ -4871,9 +5060,9 @@ async function previewAndConfirm(config, options) {
|
|
|
4871
5060
|
const instructionFile = target.instructions;
|
|
4872
5061
|
const mcpConfigFile = target.mcpConfig;
|
|
4873
5062
|
const serverEntries = Object.entries(config.mcpConfig);
|
|
4874
|
-
const instructionFilePath = (0,
|
|
4875
|
-
const mcpConfigFilePath = (0,
|
|
4876
|
-
const gitignorePath = (0,
|
|
5063
|
+
const instructionFilePath = (0, import_path13.join)(config.projectPath, instructionFile);
|
|
5064
|
+
const mcpConfigFilePath = (0, import_path13.join)(config.projectPath, mcpConfigFile);
|
|
5065
|
+
const gitignorePath = (0, import_path13.join)(config.projectPath, ".gitignore");
|
|
4877
5066
|
const instructionMode = detectMarkerFileMode(instructionFilePath, "<!-- one-shot-installer:start -->");
|
|
4878
5067
|
const mcpMode = detectMCPFileMode(mcpConfigFilePath);
|
|
4879
5068
|
const gitignoreMode = detectMarkerFileMode(gitignorePath, "# one-shot-installer:start");
|
|
@@ -4888,6 +5077,7 @@ async function previewAndConfirm(config, options) {
|
|
|
4888
5077
|
console.log(` Technology ${technologyNames}`);
|
|
4889
5078
|
console.log(` Tool ${agentDisplay}`);
|
|
4890
5079
|
console.log(` Factory ${config.installFactory ? "yes" : "no"}`);
|
|
5080
|
+
console.log(` ABAP hooks ${config.installAbapHooks ? "yes" : "no"}`);
|
|
4891
5081
|
console.log(` Directory ${config.projectPath}`);
|
|
4892
5082
|
console.log("");
|
|
4893
5083
|
console.log(` ${dim("File actions:")}`);
|
|
@@ -4899,10 +5089,15 @@ async function previewAndConfirm(config, options) {
|
|
|
4899
5089
|
console.log(` ${mcpConfigFile.padEnd(domainColWidth + 14)}${mcpMode}`);
|
|
4900
5090
|
console.log(` ${".gitignore".padEnd(domainColWidth + 14)}${gitignoreMode}`);
|
|
4901
5091
|
if (config.installFactory) {
|
|
4902
|
-
const factoryExists = (0,
|
|
5092
|
+
const factoryExists = (0, import_fs12.existsSync)((0, import_path13.join)(config.projectPath, "factory"));
|
|
4903
5093
|
const factoryMode = factoryExists ? yellow("overwrite") : green("create");
|
|
4904
5094
|
console.log(` ${"factory/".padEnd(domainColWidth + 14)}${factoryMode}`);
|
|
4905
5095
|
}
|
|
5096
|
+
if (config.installAbapHooks) {
|
|
5097
|
+
const hooksDir = (0, import_path13.join)(config.projectPath, ".claude", "hooks");
|
|
5098
|
+
const hooksMode = (0, import_fs12.existsSync)(hooksDir) ? yellow("merge") : green("create");
|
|
5099
|
+
console.log(` ${".claude/hooks/".padEnd(domainColWidth + 14)}${hooksMode}`);
|
|
5100
|
+
}
|
|
4906
5101
|
if (serverEntries.length > 0) {
|
|
4907
5102
|
console.log("");
|
|
4908
5103
|
console.log(` ${dim("MCP servers:")}`);
|