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.
- package/README.ja.md +84 -284
- package/README.md +85 -338
- package/dist/cli/commands/init.d.ts +5 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +140 -58
- package/dist/cli/commands/init.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/init.test.ts +21 -0
- package/src/cli/commands/init.ts +166 -71
package/src/cli/commands/init.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
-
|
|
372
|
-
|
|
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
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
|
|
392
|
-
|
|
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
|
-
|
|
401
|
-
|
|
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
|
-
-
|
|
1768
|
-
-
|
|
1769
|
-
-
|
|
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
|
-
|
|
1980
|
+
${runCmd} swallowkit create-model <name>
|
|
1891
1981
|
# Multiple models at once:
|
|
1892
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
2019
|
+
1. \`${runCmd} swallowkit create-model <name>\`
|
|
1930
2020
|
2. Edit \`shared/models/<name>.ts\` ā add fields
|
|
1931
|
-
3.
|
|
1932
|
-
4.
|
|
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
|
|
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 |
|
|
1984
|
-
| Generate CRUD |
|
|
1985
|
-
| Dev servers |
|
|
1986
|
-
| Provision Azure |
|
|
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.
|
|
2082
|
+
1. \`${runCmd} swallowkit create-model <name>\`
|
|
1991
2083
|
2. Edit \`shared/models/<name>.ts\` ā add your fields
|
|
1992
|
-
3.
|
|
1993
|
-
4.
|
|
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
|
|
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
|
|
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
|
|
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(
|