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.
Files changed (2) hide show
  1. package/bin/installer.js +121 -27
  2. 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 response = await fetch(
3281
- `https://api.github.com/search/repositories?q=topic:${REPO_TOPIC}+org:${GITHUB_ORG}`,
3282
- {
3283
- headers: {
3284
- "Accept": "application/vnd.github+json",
3285
- "Authorization": `Bearer ${token}`
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
- if (!response.ok) {
3290
- throw new Error(
3291
- `Failed to discover data repository (HTTP ${response.status}). Check your GitHub token and network.`
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
- const data = await response.json();
3295
- if (data.total_count === 0) {
3296
- throw new Error(
3297
- "No data repository found. Verify your GitHub account has access to the organization."
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
- if (data.total_count > 1) {
3301
- throw new Error(
3302
- "Multiple data repositories found. Contact your administrator."
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 data.items[0].full_name;
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 MAX_RETRIES = 3;
3470
- var RETRY_DELAY_MS = 2e3;
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 fetchWithRetry(releaseUrl, { headers }, MAX_RETRIES);
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 fetchWithRetry(downloadUrl, { headers: downloadHeaders }, MAX_RETRIES);
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 fetchWithRetry(url, options, maxRetries) {
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 = RETRY_DELAY_MS * Math.pow(2, attempt - 1);
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:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dlw-machine-setup",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "One-shot installer for The Machine toolchain",
5
5
  "bin": {
6
6
  "dlw-machine-setup": "bin/installer.js"