swallowkit 1.0.0-beta.36 → 1.0.0-beta.38

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.
@@ -341,65 +341,153 @@ async function createNextJsProject(projectName: string, pm: PackageManager): Pro
341
341
  }
342
342
 
343
343
  async function upgradeNextJs(projectDir: string, version: string, pm: PackageManager): Promise<void> {
344
- return new Promise((resolve, reject) => {
345
- console.log(`\nšŸ“¦ Installing Next.js ${version} (to ensure latest security patches)...\n`);
346
-
347
- // pnpm: pnpm add next@... ; npm: npm install next@...
348
- const args = pm === 'pnpm'
349
- ? ['add', `next@${version}`, `react@latest`, `react-dom@latest`, '--save-exact']
350
- : ['install', `next@${version}`, `react@latest`, `react-dom@latest`, '--save-exact'];
344
+ console.log(`\nšŸ“¦ Installing Next.js ${version} (to ensure latest security patches)...\n`);
351
345
 
352
- const child = spawn(
353
- pm,
354
- args,
355
- {
356
- cwd: projectDir,
357
- stdio: 'inherit',
358
- shell: true,
359
- }
360
- );
346
+ // pnpm: pnpm add next@... ; npm: npm install next@...
347
+ const args = pm === 'pnpm'
348
+ ? ['add', `next@${version}`, `react@latest`, `react-dom@latest`, '--save-exact']
349
+ : ['install', `next@${version}`, `react@latest`, `react-dom@latest`, '--save-exact'];
361
350
 
362
- child.on('close', (code: number | null) => {
363
- if (code !== 0) {
364
- reject(new Error(`${pm} add next@${version} exited with code ${code}`));
365
- } else {
366
- console.log(`\nāœ… Next.js ${version} installed\n`);
367
- resolve();
351
+ await runPackageManagerCommand(pm, args, projectDir, `${pm} add next@${version}`);
352
+
353
+ console.log(`\nāœ… Next.js ${version} installed\n`);
354
+ }
355
+
356
+ async function installDependencies(projectDir: string, pm: PackageManager = 'pnpm'): Promise<void> {
357
+ console.log('\nšŸ“¦ Installing dependencies...\n');
358
+ await runPackageManagerCommand(pm, ['install'], projectDir, `${pm} install`);
359
+ console.log('\nāœ… Dependencies installed\n');
360
+ }
361
+
362
+ /**
363
+ * Run a package-manager command with stdio passed through to the user.
364
+ *
365
+ * For pnpm, additionally tee stdout/stderr to a buffer so that if the command
366
+ * fails due to `ERR_PNPM_IGNORED_BUILDS`, we can:
367
+ * 1. detect which packages were ignored,
368
+ * 2. interactively ask the user whether to approve them, and
369
+ * 3. run `pnpm approve-builds` followed by a retry of the original command.
370
+ */
371
+ async function runPackageManagerCommand(
372
+ pm: PackageManager,
373
+ args: string[],
374
+ projectDir: string,
375
+ label: string,
376
+ ): Promise<void> {
377
+ const { code, output } = await spawnAndCapture(pm, args, projectDir, pm === 'pnpm');
378
+
379
+ if (code === 0) return;
380
+
381
+ if (pm === 'pnpm') {
382
+ const ignoredBuilds = parseIgnoredBuilds(output);
383
+ if (ignoredBuilds.length > 0) {
384
+ const approved = await maybeApproveBuilds(projectDir, ignoredBuilds);
385
+ if (approved) {
386
+ // Retry the original command now that build scripts are approved.
387
+ const retry = await spawnAndCapture(pm, args, projectDir, true);
388
+ if (retry.code === 0) return;
389
+ throw new Error(`${label} exited with code ${retry.code} after approving builds`);
368
390
  }
369
- });
391
+ }
392
+ }
370
393
 
371
- child.on('error', (error: Error) => {
372
- reject(error);
394
+ throw new Error(`${label} exited with code ${code}`);
395
+ }
396
+
397
+ /**
398
+ * Spawn a child process inheriting stdin (so interactive prompts still work)
399
+ * while either inheriting or capturing+teeing stdout/stderr.
400
+ */
401
+ function spawnAndCapture(
402
+ pm: PackageManager,
403
+ args: string[],
404
+ projectDir: string,
405
+ capture: boolean,
406
+ ): Promise<{ code: number; output: string }> {
407
+ return new Promise((resolve, reject) => {
408
+ const stdio: ('inherit' | 'pipe')[] = capture
409
+ ? ['inherit', 'pipe', 'pipe']
410
+ : ['inherit', 'inherit', 'inherit'];
411
+
412
+ const child = spawn(pm, args, {
413
+ cwd: projectDir,
414
+ stdio,
415
+ shell: true,
373
416
  });
417
+
418
+ let combined = '';
419
+ if (capture) {
420
+ child.stdout?.on('data', (chunk: Buffer) => {
421
+ process.stdout.write(chunk);
422
+ combined += chunk.toString('utf8');
423
+ });
424
+ child.stderr?.on('data', (chunk: Buffer) => {
425
+ process.stderr.write(chunk);
426
+ combined += chunk.toString('utf8');
427
+ });
428
+ }
429
+
430
+ child.on('close', (code) => resolve({ code: code ?? 0, output: combined }));
431
+ child.on('error', reject);
374
432
  });
375
433
  }
376
434
 
377
- async function installDependencies(projectDir: string, pm: PackageManager = 'pnpm'): Promise<void> {
378
- return new Promise((resolve, reject) => {
379
- console.log('\nšŸ“¦ Installing dependencies...\n');
380
-
381
- const child = spawn(
382
- pm,
383
- ['install'],
384
- {
385
- cwd: projectDir,
386
- stdio: 'inherit',
387
- shell: true,
388
- }
435
+ /**
436
+ * Parse pnpm's `Ignored build scripts: foo@1.0.0, bar@2.0.0` warning/error and
437
+ * return the bare package names (without versions), de-duplicated.
438
+ */
439
+ export function parseIgnoredBuilds(output: string): string[] {
440
+ const match = output.match(/Ignored build scripts:\s*([^\n\r]+)/i);
441
+ if (!match) return [];
442
+ const list = match[1]
443
+ .split(',')
444
+ .map((entry) => entry.trim())
445
+ // strip version suffix: `sharp@0.34.5` -> `sharp`, `@scope/pkg@1.0.0` -> `@scope/pkg`
446
+ .map((entry) => entry.replace(/@[^@]+$/, ''))
447
+ .filter((name) => name.length > 0);
448
+ return Array.from(new Set(list));
449
+ }
450
+
451
+ /**
452
+ * Prompt the user and, if approved, run `pnpm approve-builds` followed by
453
+ * `pnpm rebuild <packages>`. Returns true if the user approved (regardless of
454
+ * which packages they actually selected inside approve-builds).
455
+ */
456
+ async function maybeApproveBuilds(projectDir: string, ignoredBuilds: string[]): Promise<boolean> {
457
+ console.log(
458
+ `\nāš ļø pnpm refused to run build scripts for: ${ignoredBuilds.join(', ')}\n` +
459
+ ' These packages (e.g. sharp) need their build scripts to run correctly.\n',
460
+ );
461
+
462
+ const response = await prompts({
463
+ type: 'confirm',
464
+ name: 'approve',
465
+ message: 'Run `pnpm approve-builds` now to approve these build scripts?',
466
+ initial: true,
467
+ });
468
+
469
+ if (!response.approve) {
470
+ console.log(
471
+ '\nā„¹ļø Skipped. You can run `pnpm approve-builds` later inside the project directory.\n',
389
472
  );
473
+ return false;
474
+ }
390
475
 
391
- child.on('close', (code: number | null) => {
392
- if (code !== 0) {
393
- reject(new Error(`${pm} install exited with code ${code}`));
394
- } else {
395
- console.log('\nāœ… Dependencies installed\n');
396
- resolve();
397
- }
398
- });
476
+ await runSimple('pnpm', ['approve-builds'], projectDir);
477
+ await runSimple('pnpm', ['rebuild', ...ignoredBuilds], projectDir);
399
478
 
400
- child.on('error', (error: Error) => {
401
- reject(error);
479
+ console.log('\nāœ… Build scripts approved and packages rebuilt\n');
480
+ return true;
481
+ }
482
+
483
+ function runSimple(command: string, args: string[], cwd: string): Promise<void> {
484
+ return new Promise((resolve, reject) => {
485
+ const child = spawn(command, args, { cwd, stdio: 'inherit', shell: true });
486
+ child.on('close', (code) => {
487
+ if (code !== 0) reject(new Error(`${command} ${args.join(' ')} exited with code ${code}`));
488
+ else resolve();
402
489
  });
490
+ child.on('error', reject);
403
491
  });
404
492
  }
405
493
 
@@ -876,7 +964,7 @@ export async function register() {
876
964
  createReadme(projectDir, projectName, cicdChoice, azureConfig, pm, backendLanguage);
877
965
 
878
966
  // 19. Create AI agent instruction files (AGENTS.md, CLAUDE.md, .github/copilot-instructions.md, etc.)
879
- createAiAgentFiles(projectDir, projectName, backendLanguage);
967
+ createAiAgentFiles(projectDir, projectName, backendLanguage, pm);
880
968
  }
881
969
 
882
970
  async function createSharedPackage(projectDir: string, projectName: string) {
@@ -1700,9 +1788,10 @@ This project was generated by SwallowKit. If you encounter any issues or have su
1700
1788
  console.log('āœ… README.md created\n');
1701
1789
  }
1702
1790
 
1703
- function createAiAgentFiles(projectDir: string, projectName: string, backendLanguage: BackendLanguage) {
1791
+ function createAiAgentFiles(projectDir: string, projectName: string, backendLanguage: BackendLanguage, pm: PackageManager) {
1704
1792
  console.log('šŸ¤– Creating AI agent instruction files...\n');
1705
1793
  const backendLanguageLabel = getBackendLanguageLabel(backendLanguage);
1794
+ const runCmd = pm === 'pnpm' ? 'pnpm' : 'npx';
1706
1795
  const projectMcpConfigSource = buildSwallowKitMcpProjectConfigSource();
1707
1796
  const functionsStructureLine = backendLanguage === 'typescript'
1708
1797
  ? `│ └── src/ # HTTP trigger handlers with Cosmos DB bindings`
@@ -1764,11 +1853,12 @@ ${functionsStructureLine}
1764
1853
  - This repository includes a project-scoped \`.mcp.json\` file that starts the locally installed SwallowKit MCP server on runtimes that auto-load project MCP configurations.
1765
1854
  - Prefer the \`swallowkit_*\` MCP tools for framework-owned inspection, validation, and generation when they are available.
1766
1855
  - If MCP is unavailable in your runtime, fall back to the machine CLI:
1767
- - \`npx swallowkit machine inspect project\`
1768
- - \`npx swallowkit machine validate project\`
1769
- - \`npx swallowkit machine generate scaffold <name> --api-only\`
1856
+ - \`${runCmd} swallowkit machine inspect project\`
1857
+ - \`${runCmd} swallowkit machine validate project\`
1858
+ - \`${runCmd} swallowkit machine generate scaffold <name> --api-only\`
1770
1859
  - Do not hand-edit framework-owned artifacts when the MCP or machine interface can generate or validate them for you.
1771
1860
  - The local MCP bootstrap depends on project dependencies already being installed.
1861
+ - **Always invoke SwallowKit via \`${runCmd}\`.** Do not mix package manager commands.
1772
1862
 
1773
1863
  ## Critical Design Principles
1774
1864
 
@@ -1887,9 +1977,9 @@ Use the SwallowKit CLI — do **not** manually create model files or CRUD boiler
1887
1977
  ### Skill: Create a new data model
1888
1978
 
1889
1979
  \`\`\`bash
1890
- npx swallowkit create-model <name>
1980
+ ${runCmd} swallowkit create-model <name>
1891
1981
  # Multiple models at once:
1892
- npx swallowkit create-model user post comment
1982
+ ${runCmd} swallowkit create-model user post comment
1893
1983
  \`\`\`
1894
1984
 
1895
1985
  Creates \`shared/models/<name>.ts\` with a Zod schema template including \`id\`, \`createdAt\`, \`updatedAt\`.
@@ -1898,7 +1988,7 @@ Edit the generated file to add your domain-specific fields, then run scaffold.
1898
1988
  ### Skill: Generate full CRUD from a model
1899
1989
 
1900
1990
  \`\`\`bash
1901
- npx swallowkit scaffold shared/models/<name>.ts
1991
+ ${runCmd} swallowkit scaffold shared/models/<name>.ts
1902
1992
  \`\`\`
1903
1993
 
1904
1994
  Generates:
@@ -1910,7 +2000,7 @@ Generates:
1910
2000
  ### Skill: Start development servers
1911
2001
 
1912
2002
  \`\`\`bash
1913
- npx swallowkit dev
2003
+ ${runCmd} swallowkit dev
1914
2004
  \`\`\`
1915
2005
 
1916
2006
  Runs Next.js (http://localhost:3000) and Azure Functions (http://localhost:7071) concurrently.
@@ -1919,17 +2009,18 @@ Checks for Cosmos DB Emulator availability.
1919
2009
  ### Skill: Provision Azure resources
1920
2010
 
1921
2011
  \`\`\`bash
1922
- npx swallowkit provision --resource-group <name> --location <region>
2012
+ ${runCmd} swallowkit provision --resource-group <name> --location <region>
1923
2013
  \`\`\`
1924
2014
 
1925
2015
  Deploys Bicep infrastructure: Static Web Apps, Functions, Cosmos DB, Storage, Managed Identity.
1926
2016
 
1927
2017
  ### Typical workflow for "add a new feature/model"
1928
2018
 
1929
- 1. \`npx swallowkit create-model <name>\`
2019
+ 1. \`${runCmd} swallowkit create-model <name>\`
1930
2020
  2. Edit \`shared/models/<name>.ts\` — add fields
1931
- 3. \`npx swallowkit scaffold shared/models/<name>.ts\`
1932
- 4. \`npx swallowkit dev\` — verify at http://localhost:3000/<name>
2021
+ 3. \`${runCmd} swallowkit scaffold shared/models/<name>.ts\`
2022
+ 4. \`${runCmd} swallowkit dev\` — verify at http://localhost:3000/<name>
2023
+ 5. If \`dev-seeds/\` already exists, update the seed JSON files to include realistic data for the new model and adjust existing seeds if relationships changed.
1933
2024
 
1934
2025
  ## Do NOT
1935
2026
 
@@ -1974,23 +2065,25 @@ This file is for Claude Code. Read AGENTS.md in the project root for the full ar
1974
2065
 
1975
2066
  - This repository includes a project-scoped \`.mcp.json\` that registers the locally installed SwallowKit MCP server for runtimes that support project MCP files.
1976
2067
  - When the \`swallowkit_*\` tools are available, prefer them for inspect / validate / generate tasks.
1977
- - If MCP is unavailable, use \`npx swallowkit machine ...\` instead.
2068
+ - If MCP is unavailable, use \`${runCmd} swallowkit machine ...\` instead.
2069
+ - **Always invoke SwallowKit via \`${runCmd}\`.** Do not mix package manager commands.
1978
2070
 
1979
2071
  ## SwallowKit CLI Commands
1980
2072
 
1981
2073
  | Task | Command |
1982
2074
  |------|---------|
1983
- | Create model | \`npx swallowkit create-model <name>\` |
1984
- | Generate CRUD | \`npx swallowkit scaffold shared/models/<name>.ts\` |
1985
- | Dev servers | \`npx swallowkit dev\` |
1986
- | Provision Azure | \`npx swallowkit provision --resource-group <rg> --location <region>\` |
2075
+ | Create model | \`${runCmd} swallowkit create-model <name>\` |
2076
+ | Generate CRUD | \`${runCmd} swallowkit scaffold shared/models/<name>.ts\` |
2077
+ | Dev servers | \`${runCmd} swallowkit dev\` |
2078
+ | Provision Azure | \`${runCmd} swallowkit provision --resource-group <rg> --location <region>\` |
1987
2079
 
1988
2080
  ## Workflow: Add a new model
1989
2081
 
1990
- 1. \`npx swallowkit create-model <name>\`
2082
+ 1. \`${runCmd} swallowkit create-model <name>\`
1991
2083
  2. Edit \`shared/models/<name>.ts\` — add your fields
1992
- 3. \`npx swallowkit scaffold shared/models/<name>.ts\`
1993
- 4. \`npx swallowkit dev\` — verify at http://localhost:3000/<name>
2084
+ 3. \`${runCmd} swallowkit scaffold shared/models/<name>.ts\`
2085
+ 4. \`${runCmd} swallowkit dev\` — verify at http://localhost:3000/<name>
2086
+ 5. If \`dev-seeds/\` exists, update the seed JSON files to include data for the new model and adjust existing seeds if relationships changed.
1994
2087
  `;
1995
2088
 
1996
2089
  fs.writeFileSync(path.join(projectDir, 'CLAUDE.md'), claudeMd);
@@ -2019,13 +2112,15 @@ Frontend (Next.js App Router) → BFF (Next.js API Routes) → Backend (Azure Fu
2019
2112
  1. **BFF is proxy only** — \`app/api/\` routes call Azure Functions via \`callFunction()\`. No business logic, no direct DB access.
2020
2113
  2. **Zod = single source of truth** — Models live in \`shared/models/\`. Types are derived with \`z.infer<>\`. Never define types separately.
2021
2114
  3. **Backend owns data** — All CRUD and business logic stay in \`functions/\`, and generated contract assets under \`functions/generated/\` must stay aligned with \`shared/models/\`.
2022
- 4. **Use the CLI** — Run \`npx swallowkit create-model <name>\` then \`npx swallowkit scaffold shared/models/<name>.ts\` to add models. Do not create boilerplate manually.
2115
+ 4. **Use the CLI** — Run \`${runCmd} swallowkit create-model <name>\` then \`${runCmd} swallowkit scaffold shared/models/<name>.ts\` to add models. Do not create boilerplate manually.
2116
+ 5. **Maintain seed data** — When adding models or changing schemas, update the JSON files under \`dev-seeds/\` to keep seed data consistent with the current schema.
2023
2117
 
2024
2118
  ## SwallowKit Framework Operations
2025
2119
 
2026
2120
  - Prefer the SwallowKit MCP or machine interface for framework-owned inspection, validation, and generation instead of hand-editing generated files.
2027
2121
  - If your runtime loads project-scoped MCP config from \`.mcp.json\`, use the \`swallowkit_*\` tools.
2028
- - Otherwise use \`npx swallowkit machine inspect project\`, \`npx swallowkit machine validate project\`, and \`npx swallowkit machine generate scaffold <name> --api-only\`.
2122
+ - Otherwise use \`${runCmd} swallowkit machine inspect project\`, \`${runCmd} swallowkit machine validate project\`, and \`${runCmd} swallowkit machine generate scaffold <name> --api-only\`.
2123
+ - **Always invoke SwallowKit via \`${runCmd}\`.** Do not mix package manager commands.
2029
2124
 
2030
2125
  ## Naming
2031
2126
 
@@ -2067,7 +2162,7 @@ Files in this directory are the **single source of truth** for data models acros
2067
2162
  - Export a \`displayName\` string constant for UI display.
2068
2163
  - Re-export every model from \`shared/index.ts\`.
2069
2164
  - For relationships, use **nested schemas** (import and embed the related schema), not ID references.
2070
- - After editing a model, run \`npx swallowkit scaffold shared/models/<name>.ts\` to regenerate CRUD code.
2165
+ - After editing a model, run \`${runCmd} swallowkit scaffold shared/models/<name>.ts\` to regenerate CRUD code.
2071
2166
  `;
2072
2167
 
2073
2168
  fs.writeFileSync(