swallowkit 1.0.0-beta.19 → 1.0.0-beta.20

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 (75) hide show
  1. package/README.ja.md +36 -4
  2. package/README.md +36 -4
  3. package/dist/cli/commands/add-auth.d.ts.map +1 -1
  4. package/dist/cli/commands/add-auth.js +2 -0
  5. package/dist/cli/commands/add-auth.js.map +1 -1
  6. package/dist/cli/commands/add-connector.d.ts.map +1 -1
  7. package/dist/cli/commands/add-connector.js +2 -0
  8. package/dist/cli/commands/add-connector.js.map +1 -1
  9. package/dist/cli/commands/create-model.d.ts +0 -4
  10. package/dist/cli/commands/create-model.d.ts.map +1 -1
  11. package/dist/cli/commands/create-model.js +19 -145
  12. package/dist/cli/commands/create-model.js.map +1 -1
  13. package/dist/cli/commands/init.d.ts.map +1 -1
  14. package/dist/cli/commands/init.js +2 -0
  15. package/dist/cli/commands/init.js.map +1 -1
  16. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  17. package/dist/cli/commands/scaffold.js +22 -10
  18. package/dist/cli/commands/scaffold.js.map +1 -1
  19. package/dist/cli/index.d.ts.map +1 -1
  20. package/dist/cli/index.js +5 -0
  21. package/dist/cli/index.js.map +1 -1
  22. package/dist/core/operations/create-model.d.ts +15 -0
  23. package/dist/core/operations/create-model.d.ts.map +1 -0
  24. package/dist/core/operations/create-model.js +171 -0
  25. package/dist/core/operations/create-model.js.map +1 -0
  26. package/dist/core/operations/runtime.d.ts +32 -0
  27. package/dist/core/operations/runtime.d.ts.map +1 -0
  28. package/dist/core/operations/runtime.js +225 -0
  29. package/dist/core/operations/runtime.js.map +1 -0
  30. package/dist/core/operations/scaffold-machine.d.ts +16 -0
  31. package/dist/core/operations/scaffold-machine.d.ts.map +1 -0
  32. package/dist/core/operations/scaffold-machine.js +63 -0
  33. package/dist/core/operations/scaffold-machine.js.map +1 -0
  34. package/dist/core/project/manifest.d.ts +92 -0
  35. package/dist/core/project/manifest.d.ts.map +1 -0
  36. package/dist/core/project/manifest.js +321 -0
  37. package/dist/core/project/manifest.js.map +1 -0
  38. package/dist/core/project/validation.d.ts +20 -0
  39. package/dist/core/project/validation.d.ts.map +1 -0
  40. package/dist/core/project/validation.js +204 -0
  41. package/dist/core/project/validation.js.map +1 -0
  42. package/dist/machine/contracts.d.ts +16 -0
  43. package/dist/machine/contracts.d.ts.map +1 -0
  44. package/dist/machine/contracts.js +3 -0
  45. package/dist/machine/contracts.js.map +1 -0
  46. package/dist/machine/errors.d.ts +11 -0
  47. package/dist/machine/errors.d.ts.map +1 -0
  48. package/dist/machine/errors.js +34 -0
  49. package/dist/machine/errors.js.map +1 -0
  50. package/dist/machine/index.d.ts +3 -0
  51. package/dist/machine/index.d.ts.map +1 -0
  52. package/dist/machine/index.js +156 -0
  53. package/dist/machine/index.js.map +1 -0
  54. package/dist/mcp/index.d.ts +25 -0
  55. package/dist/mcp/index.d.ts.map +1 -0
  56. package/dist/mcp/index.js +184 -0
  57. package/dist/mcp/index.js.map +1 -0
  58. package/package.json +6 -4
  59. package/src/__tests__/machine.test.ts +212 -0
  60. package/src/__tests__/mcp.test.ts +56 -0
  61. package/src/cli/commands/add-auth.ts +2 -0
  62. package/src/cli/commands/add-connector.ts +2 -0
  63. package/src/cli/commands/create-model.ts +19 -168
  64. package/src/cli/commands/init.ts +3 -0
  65. package/src/cli/commands/scaffold.ts +27 -10
  66. package/src/cli/index.ts +6 -0
  67. package/src/core/operations/create-model.ts +174 -0
  68. package/src/core/operations/runtime.ts +235 -0
  69. package/src/core/operations/scaffold-machine.ts +91 -0
  70. package/src/core/project/manifest.ts +402 -0
  71. package/src/core/project/validation.ts +221 -0
  72. package/src/machine/contracts.ts +17 -0
  73. package/src/machine/errors.ts +34 -0
  74. package/src/machine/index.ts +173 -0
  75. package/src/mcp/index.ts +185 -0
@@ -0,0 +1,212 @@
1
+ import * as fs from "fs";
2
+ import * as os from "os";
3
+ import * as path from "path";
4
+ import { runMachineCli } from "../machine";
5
+
6
+ const repoRoot = process.cwd();
7
+
8
+ function writeFile(filePath: string, content: string): void {
9
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
10
+ fs.writeFileSync(filePath, content, "utf-8");
11
+ }
12
+
13
+ function createModelSource(name: string): string {
14
+ return `import { z } from 'zod/v4';
15
+
16
+ export const ${name} = z.object({
17
+ id: z.string(),
18
+ name: z.string().min(1),
19
+ createdAt: z.string().optional(),
20
+ updatedAt: z.string().optional(),
21
+ });
22
+
23
+ export type ${name} = z.infer<typeof ${name}>;
24
+
25
+ export const displayName = '${name}';
26
+ `;
27
+ }
28
+
29
+ function createProjectFixture(rootDir: string, options: { includeGeneratedArtifacts?: boolean; forbiddenBffDependency?: boolean } = {}): void {
30
+ writeFile(path.join(rootDir, "package.json"), JSON.stringify({ name: "sample-app" }, null, 2));
31
+ writeFile(
32
+ path.join(rootDir, "swallowkit.config.js"),
33
+ `module.exports = {
34
+ database: {
35
+ connectionString: 'AccountEndpoint=https://example.local;',
36
+ },
37
+ backend: {
38
+ language: 'typescript',
39
+ },
40
+ api: {
41
+ endpoint: '/api/_swallowkit',
42
+ },
43
+ };
44
+ `
45
+ );
46
+ writeFile(path.join(rootDir, "shared", "package.json"), JSON.stringify({ name: "@sample-app/shared" }, null, 2));
47
+ writeFile(path.join(rootDir, "shared", "index.ts"), "export {};\n");
48
+ writeFile(path.join(rootDir, "shared", "models", "todo.ts"), createModelSource("Todo"));
49
+ fs.mkdirSync(path.join(rootDir, "node_modules"), { recursive: true });
50
+ fs.symlinkSync(
51
+ path.join(repoRoot, "node_modules", "zod"),
52
+ path.join(rootDir, "node_modules", "zod"),
53
+ "junction"
54
+ );
55
+
56
+ if (options.includeGeneratedArtifacts) {
57
+ writeFile(path.join(rootDir, "lib", "api", "call-function.ts"), "export function callFunction() {}\n");
58
+ writeFile(path.join(rootDir, "functions", "src", "todo.ts"), "export {};\n");
59
+ writeFile(path.join(rootDir, "app", "api", "todo", "route.ts"), "export async function GET() { return Response.json([]); }\n");
60
+ writeFile(path.join(rootDir, "app", "api", "todo", "[id]", "route.ts"), "export async function GET() { return Response.json({}); }\n");
61
+ }
62
+
63
+ if (options.forbiddenBffDependency) {
64
+ writeFile(
65
+ path.join(rootDir, "app", "api", "forbidden", "route.ts"),
66
+ "import { CosmosClient } from '@azure/cosmos';\nexport async function GET() { return Response.json({ ok: Boolean(CosmosClient) }); }\n"
67
+ );
68
+ }
69
+ }
70
+
71
+ async function runMachine(argv: string[]): Promise<{ response: any; exitCode: number }> {
72
+ const writes: string[] = [];
73
+ const originalWrite = process.stdout.write.bind(process.stdout);
74
+ const originalExitCode = process.exitCode;
75
+
76
+ (process.stdout.write as unknown as (chunk: string | Uint8Array) => boolean) = ((chunk: string | Uint8Array) => {
77
+ writes.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf-8"));
78
+ return true;
79
+ }) as typeof process.stdout.write;
80
+
81
+ process.exitCode = 0;
82
+
83
+ try {
84
+ await runMachineCli(argv);
85
+ return {
86
+ response: JSON.parse(writes.join("")),
87
+ exitCode: process.exitCode || 0,
88
+ };
89
+ } finally {
90
+ process.stdout.write = originalWrite;
91
+ process.exitCode = originalExitCode;
92
+ }
93
+ }
94
+
95
+ describe("machine CLI", () => {
96
+ const originalCwd = process.cwd();
97
+ let tempDir: string;
98
+
99
+ beforeEach(() => {
100
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "swallowkit-machine-"));
101
+ process.chdir(tempDir);
102
+ });
103
+
104
+ afterEach(() => {
105
+ process.chdir(originalCwd);
106
+ fs.rmSync(tempDir, { recursive: true, force: true });
107
+ });
108
+
109
+ it("inspects SwallowKit project metadata as JSON", async () => {
110
+ createProjectFixture(tempDir, { includeGeneratedArtifacts: true });
111
+
112
+ const { response, exitCode } = await runMachine(["node", "swallowkit", "machine", "inspect", "project"]);
113
+
114
+ expect(exitCode).toBe(0);
115
+ expect(response.ok).toBe(true);
116
+ expect(response.command).toBe("inspect-project");
117
+ expect(response.data.manifest.entities[0].name).toBe("Todo");
118
+ expect(response.data.manifest.routes).toEqual(
119
+ expect.arrayContaining([
120
+ expect.objectContaining({
121
+ name: "Todo-bff-list",
122
+ publicPath: "/api/todo",
123
+ exists: true,
124
+ }),
125
+ ])
126
+ );
127
+ });
128
+
129
+ it("validates forbidden BFF dependencies", async () => {
130
+ createProjectFixture(tempDir, {
131
+ includeGeneratedArtifacts: true,
132
+ forbiddenBffDependency: true,
133
+ });
134
+
135
+ const { response, exitCode } = await runMachine(["node", "swallowkit", "machine", "validate", "project"]);
136
+
137
+ expect(exitCode).toBe(0);
138
+ expect(response.ok).toBe(true);
139
+ expect(response.command).toBe("validate-project");
140
+ expect(response.data.violations).toEqual(
141
+ expect.arrayContaining([
142
+ expect.objectContaining({
143
+ code: "forbidden-bff-dependency",
144
+ severity: "error",
145
+ }),
146
+ ])
147
+ );
148
+ });
149
+
150
+ it("generates model templates through the machine interface", async () => {
151
+ writeFile(path.join(tempDir, "package.json"), JSON.stringify({ name: "sample-app" }, null, 2));
152
+ writeFile(
153
+ path.join(tempDir, "swallowkit.config.js"),
154
+ `module.exports = {
155
+ backend: { language: 'typescript' },
156
+ api: { endpoint: '/api/_swallowkit' },
157
+ };
158
+ `
159
+ );
160
+ fs.mkdirSync(path.join(tempDir, "node_modules"), { recursive: true });
161
+ fs.symlinkSync(
162
+ path.join(repoRoot, "node_modules", "zod"),
163
+ path.join(tempDir, "node_modules", "zod"),
164
+ "junction"
165
+ );
166
+ writeFile(path.join(tempDir, "shared", "index.ts"), "export {};\n");
167
+
168
+ const { response, exitCode } = await runMachine([
169
+ "node",
170
+ "swallowkit",
171
+ "machine",
172
+ "generate",
173
+ "model",
174
+ "Task",
175
+ "--overwrite",
176
+ "never",
177
+ ]);
178
+
179
+ expect(exitCode).toBe(0);
180
+ expect(response.ok).toBe(true);
181
+ expect(response.command).toBe("generate-model");
182
+ expect(response.data.createdFiles).toContain("shared/models/task.ts");
183
+ expect(fs.existsSync(path.join(tempDir, ".swallowkit", "project.json"))).toBe(true);
184
+ });
185
+
186
+ it("generates scaffold artifacts through the machine interface", async () => {
187
+ createProjectFixture(tempDir);
188
+
189
+ const { response, exitCode } = await runMachine([
190
+ "node",
191
+ "swallowkit",
192
+ "machine",
193
+ "generate",
194
+ "scaffold",
195
+ "todo",
196
+ "--api-only",
197
+ ]);
198
+
199
+ expect(exitCode).toBe(0);
200
+ expect(response.ok).toBe(true);
201
+ expect(response.command).toBe("generate-scaffold");
202
+ expect(response.data.createdFiles).toEqual(
203
+ expect.arrayContaining([
204
+ "app/api/todo/route.ts",
205
+ "app/api/todo/[id]/route.ts",
206
+ "functions/src/todo.ts",
207
+ "lib/api/call-function.ts",
208
+ ])
209
+ );
210
+ expect(fs.existsSync(path.join(tempDir, ".swallowkit", "project.json"))).toBe(true);
211
+ });
212
+ });
@@ -0,0 +1,56 @@
1
+ import { buildSwallowKitToolDefinitions } from "../mcp";
2
+
3
+ describe("SwallowKit MCP tool definitions", () => {
4
+ it("delegates inspect_project to the machine CLI", async () => {
5
+ const runner = jest.fn().mockResolvedValue({
6
+ stdout: JSON.stringify({
7
+ ok: true,
8
+ command: "inspect-project",
9
+ data: { manifestSource: "file" },
10
+ }),
11
+ stderr: "",
12
+ exitCode: 0,
13
+ });
14
+
15
+ const tool = buildSwallowKitToolDefinitions(runner).find((candidate) => candidate.name === "swallowkit_inspect_project");
16
+ expect(tool).toBeDefined();
17
+
18
+ const result = await tool!.handler({});
19
+ expect(runner).toHaveBeenCalledWith(["inspect", "project"]);
20
+ expect(JSON.parse(result.content[0].text)).toEqual({ manifestSource: "file" });
21
+ });
22
+
23
+ it("delegates scaffold_model with explicit args", async () => {
24
+ const runner = jest.fn().mockResolvedValue({
25
+ stdout: JSON.stringify({
26
+ ok: true,
27
+ command: "generate-scaffold",
28
+ data: { createdFiles: ["functions/src/todo.ts"] },
29
+ }),
30
+ stderr: "",
31
+ exitCode: 0,
32
+ });
33
+
34
+ const tool = buildSwallowKitToolDefinitions(runner).find((candidate) => candidate.name === "swallowkit_scaffold_model");
35
+ expect(tool).toBeDefined();
36
+
37
+ const result = await tool!.handler({
38
+ model: "todo",
39
+ functionsDir: "functions",
40
+ apiDir: "app/api",
41
+ apiOnly: true,
42
+ });
43
+
44
+ expect(runner).toHaveBeenCalledWith([
45
+ "generate",
46
+ "scaffold",
47
+ "todo",
48
+ "--functions-dir",
49
+ "functions",
50
+ "--api-dir",
51
+ "app/api",
52
+ "--api-only",
53
+ ]);
54
+ expect(JSON.parse(result.content[0].text)).toEqual({ createdFiles: ["functions/src/todo.ts"] });
55
+ });
56
+ });
@@ -23,6 +23,7 @@ import {
23
23
  generateAuthContext,
24
24
  generateBFFCallFunctionWithAuth,
25
25
  } from "../../core/scaffold/auth-generator";
26
+ import { syncProjectManifest } from "../../core/project/manifest";
26
27
 
27
28
  interface AddAuthOptions {
28
29
  provider?: string;
@@ -132,6 +133,7 @@ export async function addAuthCommand(options: AddAuthOptions) {
132
133
  // 10. Install dependencies
133
134
  console.log("\n Installing auth dependencies...");
134
135
  await installAuthDependencies(cwd, backendLanguage, rdbProvider);
136
+ await syncProjectManifest();
135
137
 
136
138
  console.log("\n Authentication setup complete!");
137
139
  console.log("\n Next steps:");
@@ -7,6 +7,7 @@ import * as fs from "fs";
7
7
  import * as path from "path";
8
8
  import { ensureSwallowKitProject } from "../../core/config";
9
9
  import { ApiConnectorConfig, ConnectorDefinition } from "../../types";
10
+ import { syncProjectManifest } from "../../core/project/manifest";
10
11
 
11
12
  interface AddConnectorOptions {
12
13
  name: string;
@@ -33,6 +34,7 @@ export async function addConnectorCommand(options: AddConnectorOptions) {
33
34
 
34
35
  // Update config file
35
36
  updateConfigWithConnector(configPath, options.name, connectorDef);
37
+ await syncProjectManifest();
36
38
 
37
39
  console.log(`\n✅ Connector '${options.name}' added successfully!`);
38
40
  console.log("\n📝 Next steps:");
@@ -1,13 +1,5 @@
1
- /**
2
- * SwallowKit Create-Model コマンド
3
- * Zod モデルの雛形を生成
4
- */
5
-
6
- import * as fs from "fs";
7
- import * as path from "path";
8
1
  import * as readline from "readline";
9
- import { toPascalCase } from "../../core/scaffold/model-parser";
10
- import { ensureSwallowKitProject, loadConfig } from "../../core/config";
2
+ import { createModelOperation } from "../../core/operations/create-model";
11
3
  import { detectFromProject, getCommands } from "../../utils/package-manager";
12
4
 
13
5
  interface CreateModelOptions {
@@ -16,81 +8,6 @@ interface CreateModelOptions {
16
8
  connector?: string; // コネクタ名(例: "mysql", "backlog")
17
9
  }
18
10
 
19
- /**
20
- * モデルテンプレートを生成
21
- */
22
- function generateModelTemplate(modelName: string): string {
23
- const pascalName = toPascalCase(modelName);
24
-
25
- return `import { z } from 'zod/v4';
26
-
27
- // ${pascalName} model (Zod official pattern: same name for value and type)
28
- export const ${pascalName} = z.object({
29
- id: z.string(),
30
- name: z.string().min(1),
31
- createdAt: z.string().optional(),
32
- updatedAt: z.string().optional(),
33
- });
34
-
35
- export type ${pascalName} = z.infer<typeof ${pascalName}>;
36
-
37
- // Display name for UI
38
- export const displayName = '${pascalName}';
39
- `;
40
- }
41
-
42
- /**
43
- * コネクタ付きモデルテンプレートを生成
44
- */
45
- function generateConnectorModelTemplate(modelName: string, connectorName: string, connectorType: 'rdb' | 'api'): string {
46
- const pascalName = toPascalCase(modelName);
47
- const kebabName = modelName.toLowerCase().replace(/[^a-z0-9]+/g, '-');
48
- const pluralName = kebabName.endsWith('s') ? kebabName : kebabName + 's';
49
-
50
- const schema = `import { z } from 'zod/v4';
51
-
52
- // ${pascalName} model (Zod official pattern: same name for value and type)
53
- export const ${pascalName} = z.object({
54
- id: z.string(),
55
- name: z.string().min(1),
56
- createdAt: z.string().optional(),
57
- updatedAt: z.string().optional(),
58
- });
59
-
60
- export type ${pascalName} = z.infer<typeof ${pascalName}>;
61
-
62
- // Display name for UI
63
- export const displayName = '${pascalName}';
64
- `;
65
-
66
- if (connectorType === 'rdb') {
67
- return schema + `
68
- // SwallowKit Connector Metadata
69
- export const connectorConfig = {
70
- connector: '${connectorName}',
71
- operations: ['getAll', 'getById'] as const,
72
- table: '${pluralName}',
73
- idColumn: 'id',
74
- };
75
- `;
76
- }
77
-
78
- // API connector
79
- return schema + `
80
- // SwallowKit Connector Metadata
81
- export const connectorConfig = {
82
- connector: '${connectorName}',
83
- operations: ['getAll', 'getById', 'create', 'update'] as const,
84
- endpoints: {
85
- getAll: 'GET /${pluralName}',
86
- getById: 'GET /${pluralName}/{id}',
87
- create: 'POST /${pluralName}',
88
- update: 'PATCH /${pluralName}/{id}',
89
- },
90
- };
91
- `;
92
- }
93
-
94
11
  /**
95
12
  * ユーザーに確認を求める
96
13
  */
@@ -112,100 +29,34 @@ function askConfirmation(question: string): Promise<boolean> {
112
29
  * create-model コマンド
113
30
  */
114
31
  export async function createModelCommand(options: CreateModelOptions) {
115
- // SwallowKit プロジェクトディレクトリかどうかを検証
116
- ensureSwallowKitProject("create-model");
117
-
118
32
  console.log("🏗️ SwallowKit Create-Model: Generating model templates...\n");
119
33
 
120
- // コネクタ指定時はコネクタの存在を検証
121
- let connectorType: 'rdb' | 'api' | undefined;
122
- if (options.connector) {
123
- const config = loadConfig();
124
- const connectorDef = config.connectors?.[options.connector];
125
- if (!connectorDef) {
126
- console.error(`❌ Connector '${options.connector}' not found in swallowkit.config.js.`);
127
- console.error(` Available connectors: ${Object.keys(config.connectors || {}).join(', ') || '(none)'}`);
128
- console.error(` Run 'swallowkit add-connector ${options.connector} --type=<rdb|api>' first.`);
129
- process.exit(1);
130
- }
131
- connectorType = connectorDef.type;
132
- console.log(`🔌 Using connector: ${options.connector} (${connectorType})\n`);
133
- }
134
-
135
- const modelsDir = options.modelsDir || "shared/models";
136
-
137
- // shared/models ディレクトリが存在しなければ作成
138
- if (!fs.existsSync(modelsDir)) {
139
- console.log(`📁 Creating directory: ${modelsDir}`);
140
- fs.mkdirSync(modelsDir, { recursive: true });
141
- }
142
-
143
- const created: string[] = [];
144
- const skipped: string[] = [];
145
-
146
- for (const name of options.names) {
147
- const kebabName = name.toLowerCase().replace(/[^a-z0-9]+/g, '-');
148
- const filePath = path.join(modelsDir, `${kebabName}.ts`);
149
- const pascalName = toPascalCase(name);
150
-
151
- // 既存ファイルチェック
152
- if (fs.existsSync(filePath)) {
153
- const shouldOverwrite = await askConfirmation(
154
- `⚠️ File ${filePath} already exists. Overwrite? (y/N): `
155
- );
156
-
157
- if (!shouldOverwrite) {
158
- console.log(`⏭️ Skipped: ${kebabName}.ts`);
159
- skipped.push(kebabName);
160
- continue;
161
- }
162
- }
163
-
164
- // モデルファイルを生成
165
- const content = options.connector && connectorType
166
- ? generateConnectorModelTemplate(name, options.connector, connectorType)
167
- : generateModelTemplate(name);
168
- fs.writeFileSync(filePath, content);
169
- console.log(`✅ Created: ${filePath}`);
170
- created.push(kebabName);
171
-
172
- // shared/index.ts に re-export を追加
173
- updateSharedIndex(kebabName, pascalName);
174
- }
34
+ const result = await createModelOperation({
35
+ names: options.names,
36
+ modelsDir: options.modelsDir,
37
+ connector: options.connector,
38
+ overwriteMode: "prompt",
39
+ confirmOverwrite: async (filePath) => askConfirmation(`⚠️ File ${filePath} already exists. Overwrite? (y/N): `),
40
+ });
175
41
 
176
42
  // サマリー表示
177
43
  console.log("\n📋 Summary:");
178
- if (created.length > 0) {
179
- console.log(` Created ${created.length} model(s): ${created.join(', ')}.ts`);
44
+ if (result.connectorType && options.connector) {
45
+ console.log(` 🔌 Connector: ${options.connector} (${result.connectorType})`);
46
+ }
47
+ if (result.createdFiles.length > 0) {
48
+ console.log(` ✅ Created ${result.createdFiles.length} model(s): ${result.createdFiles.join(", ")}`);
180
49
  }
181
- if (skipped.length > 0) {
182
- console.log(` ⏭️ Skipped ${skipped.length} model(s): ${skipped.join(', ')}.ts`);
50
+ if (result.skippedFiles.length > 0) {
51
+ console.log(` ⏭️ Skipped ${result.skippedFiles.length} model(s): ${result.skippedFiles.join(", ")}`);
52
+ }
53
+ if (result.updatedIndex) {
54
+ console.log(" 📦 Updated shared/index.ts");
183
55
  }
184
56
 
185
- if (created.length > 0) {
57
+ if (result.createdFiles.length > 0) {
186
58
  console.log("\n📝 Next steps:");
187
59
  console.log(" 1. Customize the generated model fields in shared/models/");
188
60
  console.log(` 2. Run '${getCommands(detectFromProject()).dlx} swallowkit scaffold <model>' to generate CRUD code`);
189
61
  }
190
62
  }
191
-
192
- /**
193
- * shared/index.ts に re-export エントリを追加
194
- */
195
- function updateSharedIndex(kebabName: string, pascalName: string): void {
196
- const indexPath = path.join("shared", "index.ts");
197
-
198
- if (!fs.existsSync(indexPath)) {
199
- return;
200
- }
201
-
202
- const content = fs.readFileSync(indexPath, "utf-8");
203
- const exportLine = `export { ${pascalName} } from './models/${kebabName}';`;
204
-
205
- // 既に存在する場合はスキップ
206
- if (content.includes(exportLine)) {
207
- return;
208
- }
209
-
210
- fs.appendFileSync(indexPath, exportLine + "\n");
211
- }
@@ -14,6 +14,7 @@ import {
14
14
  getFunctionsPrestart,
15
15
  getFunctionsStartScript,
16
16
  } from "../../utils/package-manager";
17
+ import { syncProjectManifest } from "../../core/project/manifest";
17
18
 
18
19
  interface InitOptions {
19
20
  name: string;
@@ -197,6 +198,8 @@ export async function initCommand(options: InitOptions) {
197
198
  await createAzurePipelines(projectDir, pm, backendLanguage);
198
199
  }
199
200
 
201
+ await syncProjectManifest(projectDir);
202
+
200
203
  // Initialize Git repository and create initial commit
201
204
  try {
202
205
  // Try git init with -b main (Git 2.28+), fallback to git init
@@ -41,6 +41,7 @@ import {
41
41
  ModelAuthPolicy,
42
42
  AuthConfig,
43
43
  } from "../../types";
44
+ import { syncProjectManifest } from "../../core/project/manifest";
44
45
 
45
46
  interface ScaffoldOptions {
46
47
  model: string; // モデルファイルのパス(例: "lib/models/todo.ts" or "todo")
@@ -49,6 +50,26 @@ interface ScaffoldOptions {
49
50
  apiOnly?: boolean; // true の場合、UI を生成しない(デフォルト: false)
50
51
  }
51
52
 
53
+ function getMachineAwareStdio(): "inherit" | "pipe" {
54
+ return process.env.SWALLOWKIT_MACHINE_OUTPUT === "1" ? "pipe" : "inherit";
55
+ }
56
+
57
+ function runSpawnSyncCommand(command: string, args: string[], cwd: string): void {
58
+ const result = spawnSync(command, args, {
59
+ cwd,
60
+ stdio: getMachineAwareStdio(),
61
+ shell: true,
62
+ });
63
+
64
+ if (typeof result.status === "number" && result.status !== 0) {
65
+ throw new Error(`${command} ${args.join(" ")} exited with code ${result.status}`);
66
+ }
67
+
68
+ if (result.error) {
69
+ throw result.error;
70
+ }
71
+ }
72
+
52
73
  export async function scaffoldCommand(options: ScaffoldOptions) {
53
74
  // SwallowKit プロジェクトディレクトリかどうかを検証
54
75
  ensureSwallowKitProject("scaffold");
@@ -140,6 +161,8 @@ export async function scaffoldCommand(options: ScaffoldOptions) {
140
161
  await updateNavigationMenu(modelInfo);
141
162
  }
142
163
 
164
+ await syncProjectManifest();
165
+
143
166
  console.log("\n✅ Scaffold completed successfully!");
144
167
  console.log("\n📝 Next steps:");
145
168
  console.log(` 1. Review generated files in ${describeFunctionsOutputPath(functionsDir, backendLanguage)} and ${apiDir}/`);
@@ -508,14 +531,10 @@ async function installConnectorDriverDependencies(
508
531
  const cmds = getCommands(pm);
509
532
 
510
533
  if (entry.deps.length > 0) {
511
- spawnSync(cmds.name, [pm === "pnpm" ? "add" : "install", ...entry.deps], {
512
- cwd: functionsPath, stdio: "inherit", shell: true,
513
- });
534
+ runSpawnSyncCommand(cmds.name, [pm === "pnpm" ? "add" : "install", ...entry.deps], functionsPath);
514
535
  }
515
536
  if (entry.devDeps.length > 0) {
516
- spawnSync(cmds.name, [pm === "pnpm" ? "add" : "install", "-D", ...entry.devDeps], {
517
- cwd: functionsPath, stdio: "inherit", shell: true,
518
- });
537
+ runSpawnSyncCommand(cmds.name, [pm === "pnpm" ? "add" : "install", "-D", ...entry.devDeps], functionsPath);
519
538
  }
520
539
  console.log(`✅ ${rdbDef.provider} driver installed`);
521
540
  return;
@@ -530,9 +549,7 @@ async function installConnectorDriverDependencies(
530
549
  const pkg = nugetMap[rdbDef.provider];
531
550
  if (!pkg) return;
532
551
  console.log(`\n📦 Installing ${rdbDef.provider} NuGet package...`);
533
- spawnSync("dotnet", ["add", path.join(functionsPath, "functions.csproj"), "package", pkg], {
534
- cwd: functionsPath, stdio: "inherit", shell: true,
535
- });
552
+ runSpawnSyncCommand("dotnet", ["add", path.join(functionsPath, "functions.csproj"), "package", pkg], functionsPath);
536
553
  console.log(`✅ ${pkg} installed`);
537
554
  return;
538
555
  }
@@ -697,7 +714,7 @@ async function runOpenApiGenerator(
697
714
  const child = spawn(command, args, {
698
715
  cwd: process.cwd(),
699
716
  shell: true,
700
- stdio: "inherit",
717
+ stdio: getMachineAwareStdio(),
701
718
  });
702
719
 
703
720
  child.on("close", (code) => {
package/src/cli/index.ts CHANGED
@@ -6,6 +6,7 @@ import { initCommand, devCommand, devSeedsCommand, scaffoldCommand, createModelC
6
6
  import { provisionCommand } from "./commands/provision";
7
7
  import { addConnectorCommand } from "./commands/add-connector";
8
8
  import { addAuthCommand } from "./commands/add-auth";
9
+ import { isMachineCommand, runMachineCli } from "../machine";
9
10
 
10
11
  const DEV_OPTION_ARITY = new Map<string, 0 | 1>([
11
12
  ["-p", 1],
@@ -174,6 +175,11 @@ export function createProgram(devCommandOverride: Command = devCommand): Command
174
175
 
175
176
  export async function runCli(argv: string[] = process.argv): Promise<void> {
176
177
  ensureUtf8ConsoleOnWindows();
178
+ if (isMachineCommand(argv)) {
179
+ await runMachineCli(argv);
180
+ return;
181
+ }
182
+
177
183
  await createProgram().parseAsync(normalizeDevCommandArgv(argv));
178
184
  }
179
185