dlw-machine-setup 0.3.3 → 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 +115 -32
  2. package/package.json +1 -1
package/bin/installer.js CHANGED
@@ -3276,44 +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
3282
  const headers = {
3281
3283
  "Accept": "application/vnd.github+json",
3282
3284
  "Authorization": `Bearer ${token}`
3283
3285
  };
3284
- const matched = [];
3285
- let page = 1;
3286
- while (true) {
3287
- const response = await fetch(
3288
- `https://api.github.com/orgs/${GITHUB_ORG}/repos?type=all&per_page=100&page=${page}`,
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."
3292
+ );
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}`,
3289
3298
  { headers }
3290
3299
  );
3291
- if (!response.ok) {
3292
- throw new Error(
3293
- `Failed to list organization repositories (HTTP ${response.status}). Check your GitHub token and network.`
3294
- );
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.");
3295
3305
  }
3296
- const repos = await response.json();
3297
- if (repos.length === 0) break;
3298
- for (const repo of repos) {
3299
- if (repo.topics?.includes(REPO_TOPIC)) {
3300
- matched.push(repo.full_name);
3306
+ return null;
3307
+ } catch (error) {
3308
+ if (error instanceof Error && error.message.includes("Multiple")) throw error;
3309
+ return null;
3310
+ }
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
+ }
3301
3328
  }
3329
+ if (repos.length < 100) break;
3330
+ page++;
3302
3331
  }
3303
- if (repos.length < 100) break;
3304
- page++;
3305
- }
3306
- if (matched.length === 0) {
3307
- throw new Error(
3308
- "No data repository found. Verify your GitHub account has access to the organization."
3309
- );
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;
3310
3340
  }
3311
- if (matched.length > 1) {
3312
- throw new Error(
3313
- "Multiple data repositories found. Contact your administrator."
3314
- );
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
+ }
3315
3358
  }
3316
- return matched[0];
3359
+ return new Response(null, { status: 503, statusText: "Retry exhausted" });
3317
3360
  }
3318
3361
 
3319
3362
  // src/utils/data/fetch-contexts.ts
@@ -3477,8 +3520,8 @@ async function getGitHubToken() {
3477
3520
  }
3478
3521
 
3479
3522
  // src/utils/data/fetch-contexts.ts
3480
- var MAX_RETRIES = 3;
3481
- var RETRY_DELAY_MS = 2e3;
3523
+ var MAX_RETRIES2 = 3;
3524
+ var RETRY_DELAY_MS2 = 2e3;
3482
3525
  var MIN_FILE_SIZE = 1024;
3483
3526
  async function fetchContexts(options = {}) {
3484
3527
  const { domains = [], targetDir = process.cwd(), force = false, token, repo } = options;
@@ -3502,7 +3545,7 @@ async function fetchContexts(options = {}) {
3502
3545
  "Accept": "application/vnd.github+json",
3503
3546
  "Authorization": `Bearer ${githubToken}`
3504
3547
  };
3505
- const releaseResponse = await fetchWithRetry(releaseUrl, { headers }, MAX_RETRIES);
3548
+ const releaseResponse = await fetchWithRetry2(releaseUrl, { headers }, MAX_RETRIES2);
3506
3549
  if (!releaseResponse.ok) {
3507
3550
  throw new Error(
3508
3551
  `GitHub API error (${releaseResponse.status}): ${getReadableError(releaseResponse.status)}`
@@ -3540,7 +3583,7 @@ async function fetchContexts(options = {}) {
3540
3583
  "Authorization": `Bearer ${githubToken}`
3541
3584
  };
3542
3585
  const downloadUrl = asset.url;
3543
- const response = await fetchWithRetry(downloadUrl, { headers: downloadHeaders }, MAX_RETRIES);
3586
+ const response = await fetchWithRetry2(downloadUrl, { headers: downloadHeaders }, MAX_RETRIES2);
3544
3587
  if (!response.ok) {
3545
3588
  throw new Error(`Download failed: ${getReadableError(response.status)}`);
3546
3589
  }
@@ -3603,7 +3646,7 @@ async function checkPrerequisites() {
3603
3646
  );
3604
3647
  }
3605
3648
  }
3606
- async function fetchWithRetry(url, options, maxRetries) {
3649
+ async function fetchWithRetry2(url, options, maxRetries) {
3607
3650
  let lastError = null;
3608
3651
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
3609
3652
  try {
@@ -3619,7 +3662,7 @@ async function fetchWithRetry(url, options, maxRetries) {
3619
3662
  lastError = error instanceof Error ? error : new Error(String(error));
3620
3663
  }
3621
3664
  if (attempt < maxRetries) {
3622
- const delay = RETRY_DELAY_MS * Math.pow(2, attempt - 1);
3665
+ const delay = RETRY_DELAY_MS2 * Math.pow(2, attempt - 1);
3623
3666
  await new Promise((resolve3) => setTimeout(resolve3, delay));
3624
3667
  }
3625
3668
  }
@@ -3740,6 +3783,7 @@ var import_path4 = require("path");
3740
3783
  var CONTEXTS_DIR = (0, import_path4.join)(process.cwd(), "_ai-context");
3741
3784
  var MARKER_START = "<!-- one-shot-installer:start -->";
3742
3785
  var MARKER_END = "<!-- one-shot-installer:end -->";
3786
+ var SNIPPET_FILENAME = "context-instructions.md";
3743
3787
  function upsertBlock(filePath, block) {
3744
3788
  const marked = `${MARKER_START}
3745
3789
  ${block}
@@ -3759,6 +3803,36 @@ ${MARKER_END}`;
3759
3803
  (0, import_fs4.writeFileSync)(filePath, existing + separator + marked, "utf-8");
3760
3804
  }
3761
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
+ }
3762
3836
  function buildMCPSection(mcpConfig) {
3763
3837
  const entries = Object.entries(mcpConfig);
3764
3838
  if (entries.length === 0) return "";
@@ -3822,6 +3896,12 @@ function buildCombinedInstructions(domains, mcpConfig) {
3822
3896
  if (mcpConfig && Object.keys(mcpConfig).length > 0) {
3823
3897
  lines2.push(buildMCPSection(mcpConfig));
3824
3898
  }
3899
+ const workflowContent = buildWorkflowSection(domains);
3900
+ if (workflowContent) {
3901
+ lines2.push(`## Domain-Specific Development Workflows
3902
+ `);
3903
+ lines2.push(workflowContent);
3904
+ }
3825
3905
  return lines2.join("\n");
3826
3906
  }
3827
3907
  async function setupInstructions(config) {
@@ -3830,6 +3910,7 @@ async function setupInstructions(config) {
3830
3910
  case "claude-code": {
3831
3911
  const content = buildCombinedInstructions(domains, mcpConfig);
3832
3912
  upsertBlock((0, import_path4.join)(process.cwd(), "CLAUDE.md"), content);
3913
+ cleanupWorkflowSnippets(domains);
3833
3914
  break;
3834
3915
  }
3835
3916
  case "github-copilot": {
@@ -3842,6 +3923,7 @@ applyTo: "**"
3842
3923
 
3843
3924
  ${body}`;
3844
3925
  upsertBlock((0, import_path4.join)(agentsDir, `instructions.md`), withFrontmatter);
3926
+ cleanupWorkflowSnippets(domains);
3845
3927
  break;
3846
3928
  }
3847
3929
  case "cursor": {
@@ -3849,6 +3931,7 @@ ${body}`;
3849
3931
  if (!(0, import_fs4.existsSync)(cursorDir)) (0, import_fs4.mkdirSync)(cursorDir, { recursive: true });
3850
3932
  const body = buildCombinedInstructions(domains, mcpConfig);
3851
3933
  upsertBlock((0, import_path4.join)(cursorDir, `instructions.mdc`), body);
3934
+ cleanupWorkflowSnippets(domains);
3852
3935
  break;
3853
3936
  }
3854
3937
  default:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dlw-machine-setup",
3
- "version": "0.3.3",
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"