dlw-machine-setup 0.3.2 → 0.4.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/bin/installer.js +121 -27
- package/package.json +1 -1
package/bin/installer.js
CHANGED
|
@@ -3276,33 +3276,87 @@ async function fetchWizardOptions(token, repo) {
|
|
|
3276
3276
|
// src/utils/data/discover-repo.ts
|
|
3277
3277
|
var GITHUB_ORG = "DLW-INT-SAP-DEV";
|
|
3278
3278
|
var REPO_TOPIC = "one-shot-data";
|
|
3279
|
+
var MAX_RETRIES = 3;
|
|
3280
|
+
var RETRY_DELAY_MS = 2e3;
|
|
3279
3281
|
async function discoverRepo(token) {
|
|
3280
|
-
const
|
|
3281
|
-
|
|
3282
|
-
{
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3282
|
+
const headers = {
|
|
3283
|
+
"Accept": "application/vnd.github+json",
|
|
3284
|
+
"Authorization": `Bearer ${token}`
|
|
3285
|
+
};
|
|
3286
|
+
const searchResult = await trySearchAPI(headers);
|
|
3287
|
+
if (searchResult) return searchResult;
|
|
3288
|
+
const listResult = await tryOrgReposList(headers);
|
|
3289
|
+
if (listResult) return listResult;
|
|
3290
|
+
throw new Error(
|
|
3291
|
+
"No data repository found. Verify your GitHub account has access to the organization and the repository topic is configured."
|
|
3288
3292
|
);
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3293
|
+
}
|
|
3294
|
+
async function trySearchAPI(headers) {
|
|
3295
|
+
try {
|
|
3296
|
+
const response = await fetchWithRetry(
|
|
3297
|
+
`https://api.github.com/search/repositories?q=topic:${REPO_TOPIC}+org:${GITHUB_ORG}`,
|
|
3298
|
+
{ headers }
|
|
3292
3299
|
);
|
|
3300
|
+
if (!response.ok) return null;
|
|
3301
|
+
const data = await response.json();
|
|
3302
|
+
if (data.total_count === 1) return data.items[0].full_name;
|
|
3303
|
+
if (data.total_count > 1) {
|
|
3304
|
+
throw new Error("Multiple data repositories found. Contact your administrator.");
|
|
3305
|
+
}
|
|
3306
|
+
return null;
|
|
3307
|
+
} catch (error) {
|
|
3308
|
+
if (error instanceof Error && error.message.includes("Multiple")) throw error;
|
|
3309
|
+
return null;
|
|
3293
3310
|
}
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3311
|
+
}
|
|
3312
|
+
async function tryOrgReposList(headers) {
|
|
3313
|
+
try {
|
|
3314
|
+
const matched = [];
|
|
3315
|
+
let page = 1;
|
|
3316
|
+
while (true) {
|
|
3317
|
+
const response = await fetchWithRetry(
|
|
3318
|
+
`https://api.github.com/orgs/${GITHUB_ORG}/repos?type=all&per_page=100&page=${page}`,
|
|
3319
|
+
{ headers }
|
|
3320
|
+
);
|
|
3321
|
+
if (!response.ok) return null;
|
|
3322
|
+
const repos = await response.json();
|
|
3323
|
+
if (repos.length === 0) break;
|
|
3324
|
+
for (const repo of repos) {
|
|
3325
|
+
if (repo.topics?.includes(REPO_TOPIC)) {
|
|
3326
|
+
matched.push(repo.full_name);
|
|
3327
|
+
}
|
|
3328
|
+
}
|
|
3329
|
+
if (repos.length < 100) break;
|
|
3330
|
+
page++;
|
|
3331
|
+
}
|
|
3332
|
+
if (matched.length === 1) return matched[0];
|
|
3333
|
+
if (matched.length > 1) {
|
|
3334
|
+
throw new Error("Multiple data repositories found. Contact your administrator.");
|
|
3335
|
+
}
|
|
3336
|
+
return null;
|
|
3337
|
+
} catch (error) {
|
|
3338
|
+
if (error instanceof Error && error.message.includes("Multiple")) throw error;
|
|
3339
|
+
return null;
|
|
3299
3340
|
}
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3341
|
+
}
|
|
3342
|
+
async function fetchWithRetry(url, options) {
|
|
3343
|
+
let lastError = null;
|
|
3344
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
3345
|
+
try {
|
|
3346
|
+
const response = await fetch(url, options);
|
|
3347
|
+
if (!response.ok && response.status >= 400 && response.status < 500 && response.status !== 429) {
|
|
3348
|
+
return response;
|
|
3349
|
+
}
|
|
3350
|
+
if (response.ok) return response;
|
|
3351
|
+
lastError = new Error(`HTTP ${response.status}`);
|
|
3352
|
+
} catch (error) {
|
|
3353
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
3354
|
+
}
|
|
3355
|
+
if (attempt < MAX_RETRIES) {
|
|
3356
|
+
await new Promise((resolve3) => setTimeout(resolve3, RETRY_DELAY_MS * Math.pow(2, attempt - 1)));
|
|
3357
|
+
}
|
|
3304
3358
|
}
|
|
3305
|
-
return
|
|
3359
|
+
return new Response(null, { status: 503, statusText: "Retry exhausted" });
|
|
3306
3360
|
}
|
|
3307
3361
|
|
|
3308
3362
|
// src/utils/data/fetch-contexts.ts
|
|
@@ -3466,8 +3520,8 @@ async function getGitHubToken() {
|
|
|
3466
3520
|
}
|
|
3467
3521
|
|
|
3468
3522
|
// src/utils/data/fetch-contexts.ts
|
|
3469
|
-
var
|
|
3470
|
-
var
|
|
3523
|
+
var MAX_RETRIES2 = 3;
|
|
3524
|
+
var RETRY_DELAY_MS2 = 2e3;
|
|
3471
3525
|
var MIN_FILE_SIZE = 1024;
|
|
3472
3526
|
async function fetchContexts(options = {}) {
|
|
3473
3527
|
const { domains = [], targetDir = process.cwd(), force = false, token, repo } = options;
|
|
@@ -3491,7 +3545,7 @@ async function fetchContexts(options = {}) {
|
|
|
3491
3545
|
"Accept": "application/vnd.github+json",
|
|
3492
3546
|
"Authorization": `Bearer ${githubToken}`
|
|
3493
3547
|
};
|
|
3494
|
-
const releaseResponse = await
|
|
3548
|
+
const releaseResponse = await fetchWithRetry2(releaseUrl, { headers }, MAX_RETRIES2);
|
|
3495
3549
|
if (!releaseResponse.ok) {
|
|
3496
3550
|
throw new Error(
|
|
3497
3551
|
`GitHub API error (${releaseResponse.status}): ${getReadableError(releaseResponse.status)}`
|
|
@@ -3529,7 +3583,7 @@ async function fetchContexts(options = {}) {
|
|
|
3529
3583
|
"Authorization": `Bearer ${githubToken}`
|
|
3530
3584
|
};
|
|
3531
3585
|
const downloadUrl = asset.url;
|
|
3532
|
-
const response = await
|
|
3586
|
+
const response = await fetchWithRetry2(downloadUrl, { headers: downloadHeaders }, MAX_RETRIES2);
|
|
3533
3587
|
if (!response.ok) {
|
|
3534
3588
|
throw new Error(`Download failed: ${getReadableError(response.status)}`);
|
|
3535
3589
|
}
|
|
@@ -3592,7 +3646,7 @@ async function checkPrerequisites() {
|
|
|
3592
3646
|
);
|
|
3593
3647
|
}
|
|
3594
3648
|
}
|
|
3595
|
-
async function
|
|
3649
|
+
async function fetchWithRetry2(url, options, maxRetries) {
|
|
3596
3650
|
let lastError = null;
|
|
3597
3651
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
3598
3652
|
try {
|
|
@@ -3608,7 +3662,7 @@ async function fetchWithRetry(url, options, maxRetries) {
|
|
|
3608
3662
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
3609
3663
|
}
|
|
3610
3664
|
if (attempt < maxRetries) {
|
|
3611
|
-
const delay =
|
|
3665
|
+
const delay = RETRY_DELAY_MS2 * Math.pow(2, attempt - 1);
|
|
3612
3666
|
await new Promise((resolve3) => setTimeout(resolve3, delay));
|
|
3613
3667
|
}
|
|
3614
3668
|
}
|
|
@@ -3729,6 +3783,7 @@ var import_path4 = require("path");
|
|
|
3729
3783
|
var CONTEXTS_DIR = (0, import_path4.join)(process.cwd(), "_ai-context");
|
|
3730
3784
|
var MARKER_START = "<!-- one-shot-installer:start -->";
|
|
3731
3785
|
var MARKER_END = "<!-- one-shot-installer:end -->";
|
|
3786
|
+
var SNIPPET_FILENAME = "context-instructions.md";
|
|
3732
3787
|
function upsertBlock(filePath, block) {
|
|
3733
3788
|
const marked = `${MARKER_START}
|
|
3734
3789
|
${block}
|
|
@@ -3748,6 +3803,36 @@ ${MARKER_END}`;
|
|
|
3748
3803
|
(0, import_fs4.writeFileSync)(filePath, existing + separator + marked, "utf-8");
|
|
3749
3804
|
}
|
|
3750
3805
|
}
|
|
3806
|
+
function downgradeHeadings(content) {
|
|
3807
|
+
let inCodeBlock = false;
|
|
3808
|
+
return content.split("\n").map((line) => {
|
|
3809
|
+
if (/^ {0,3}```/.test(line)) inCodeBlock = !inCodeBlock;
|
|
3810
|
+
if (inCodeBlock) return line;
|
|
3811
|
+
return line.replace(/^(#{1,5}) /, "$1# ");
|
|
3812
|
+
}).join("\n");
|
|
3813
|
+
}
|
|
3814
|
+
function buildWorkflowSection(domains) {
|
|
3815
|
+
const sections = [];
|
|
3816
|
+
for (const domain of domains) {
|
|
3817
|
+
const domainUpper = domain.toUpperCase();
|
|
3818
|
+
const snippetPath = (0, import_path4.join)(CONTEXTS_DIR, domainUpper, SNIPPET_FILENAME);
|
|
3819
|
+
if (!(0, import_fs4.existsSync)(snippetPath)) continue;
|
|
3820
|
+
const raw = (0, import_fs4.readFileSync)(snippetPath, "utf-8").trim();
|
|
3821
|
+
if (!raw) continue;
|
|
3822
|
+
sections.push(downgradeHeadings(raw));
|
|
3823
|
+
}
|
|
3824
|
+
return sections.length > 0 ? sections.join("\n\n") + "\n" : "";
|
|
3825
|
+
}
|
|
3826
|
+
function cleanupWorkflowSnippets(domains) {
|
|
3827
|
+
for (const domain of domains) {
|
|
3828
|
+
const domainUpper = domain.toUpperCase();
|
|
3829
|
+
const snippetPath = (0, import_path4.join)(CONTEXTS_DIR, domainUpper, SNIPPET_FILENAME);
|
|
3830
|
+
try {
|
|
3831
|
+
(0, import_fs4.unlinkSync)(snippetPath);
|
|
3832
|
+
} catch {
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
}
|
|
3751
3836
|
function buildMCPSection(mcpConfig) {
|
|
3752
3837
|
const entries = Object.entries(mcpConfig);
|
|
3753
3838
|
if (entries.length === 0) return "";
|
|
@@ -3811,6 +3896,12 @@ function buildCombinedInstructions(domains, mcpConfig) {
|
|
|
3811
3896
|
if (mcpConfig && Object.keys(mcpConfig).length > 0) {
|
|
3812
3897
|
lines2.push(buildMCPSection(mcpConfig));
|
|
3813
3898
|
}
|
|
3899
|
+
const workflowContent = buildWorkflowSection(domains);
|
|
3900
|
+
if (workflowContent) {
|
|
3901
|
+
lines2.push(`## Domain-Specific Development Workflows
|
|
3902
|
+
`);
|
|
3903
|
+
lines2.push(workflowContent);
|
|
3904
|
+
}
|
|
3814
3905
|
return lines2.join("\n");
|
|
3815
3906
|
}
|
|
3816
3907
|
async function setupInstructions(config) {
|
|
@@ -3819,6 +3910,7 @@ async function setupInstructions(config) {
|
|
|
3819
3910
|
case "claude-code": {
|
|
3820
3911
|
const content = buildCombinedInstructions(domains, mcpConfig);
|
|
3821
3912
|
upsertBlock((0, import_path4.join)(process.cwd(), "CLAUDE.md"), content);
|
|
3913
|
+
cleanupWorkflowSnippets(domains);
|
|
3822
3914
|
break;
|
|
3823
3915
|
}
|
|
3824
3916
|
case "github-copilot": {
|
|
@@ -3831,6 +3923,7 @@ applyTo: "**"
|
|
|
3831
3923
|
|
|
3832
3924
|
${body}`;
|
|
3833
3925
|
upsertBlock((0, import_path4.join)(agentsDir, `instructions.md`), withFrontmatter);
|
|
3926
|
+
cleanupWorkflowSnippets(domains);
|
|
3834
3927
|
break;
|
|
3835
3928
|
}
|
|
3836
3929
|
case "cursor": {
|
|
@@ -3838,6 +3931,7 @@ ${body}`;
|
|
|
3838
3931
|
if (!(0, import_fs4.existsSync)(cursorDir)) (0, import_fs4.mkdirSync)(cursorDir, { recursive: true });
|
|
3839
3932
|
const body = buildCombinedInstructions(domains, mcpConfig);
|
|
3840
3933
|
upsertBlock((0, import_path4.join)(cursorDir, `instructions.mdc`), body);
|
|
3934
|
+
cleanupWorkflowSnippets(domains);
|
|
3841
3935
|
break;
|
|
3842
3936
|
}
|
|
3843
3937
|
default:
|