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.
- package/README.ja.md +36 -4
- package/README.md +36 -4
- package/dist/cli/commands/add-auth.d.ts.map +1 -1
- package/dist/cli/commands/add-auth.js +2 -0
- package/dist/cli/commands/add-auth.js.map +1 -1
- package/dist/cli/commands/add-connector.d.ts.map +1 -1
- package/dist/cli/commands/add-connector.js +2 -0
- package/dist/cli/commands/add-connector.js.map +1 -1
- package/dist/cli/commands/create-model.d.ts +0 -4
- package/dist/cli/commands/create-model.d.ts.map +1 -1
- package/dist/cli/commands/create-model.js +19 -145
- package/dist/cli/commands/create-model.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +2 -0
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +22 -10
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +5 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/core/operations/create-model.d.ts +15 -0
- package/dist/core/operations/create-model.d.ts.map +1 -0
- package/dist/core/operations/create-model.js +171 -0
- package/dist/core/operations/create-model.js.map +1 -0
- package/dist/core/operations/runtime.d.ts +32 -0
- package/dist/core/operations/runtime.d.ts.map +1 -0
- package/dist/core/operations/runtime.js +225 -0
- package/dist/core/operations/runtime.js.map +1 -0
- package/dist/core/operations/scaffold-machine.d.ts +16 -0
- package/dist/core/operations/scaffold-machine.d.ts.map +1 -0
- package/dist/core/operations/scaffold-machine.js +63 -0
- package/dist/core/operations/scaffold-machine.js.map +1 -0
- package/dist/core/project/manifest.d.ts +92 -0
- package/dist/core/project/manifest.d.ts.map +1 -0
- package/dist/core/project/manifest.js +321 -0
- package/dist/core/project/manifest.js.map +1 -0
- package/dist/core/project/validation.d.ts +20 -0
- package/dist/core/project/validation.d.ts.map +1 -0
- package/dist/core/project/validation.js +204 -0
- package/dist/core/project/validation.js.map +1 -0
- package/dist/machine/contracts.d.ts +16 -0
- package/dist/machine/contracts.d.ts.map +1 -0
- package/dist/machine/contracts.js +3 -0
- package/dist/machine/contracts.js.map +1 -0
- package/dist/machine/errors.d.ts +11 -0
- package/dist/machine/errors.d.ts.map +1 -0
- package/dist/machine/errors.js +34 -0
- package/dist/machine/errors.js.map +1 -0
- package/dist/machine/index.d.ts +3 -0
- package/dist/machine/index.d.ts.map +1 -0
- package/dist/machine/index.js +156 -0
- package/dist/machine/index.js.map +1 -0
- package/dist/mcp/index.d.ts +25 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +184 -0
- package/dist/mcp/index.js.map +1 -0
- package/package.json +6 -4
- package/src/__tests__/machine.test.ts +212 -0
- package/src/__tests__/mcp.test.ts +56 -0
- package/src/cli/commands/add-auth.ts +2 -0
- package/src/cli/commands/add-connector.ts +2 -0
- package/src/cli/commands/create-model.ts +19 -168
- package/src/cli/commands/init.ts +3 -0
- package/src/cli/commands/scaffold.ts +27 -10
- package/src/cli/index.ts +6 -0
- package/src/core/operations/create-model.ts +174 -0
- package/src/core/operations/runtime.ts +235 -0
- package/src/core/operations/scaffold-machine.ts +91 -0
- package/src/core/project/manifest.ts +402 -0
- package/src/core/project/validation.ts +221 -0
- package/src/machine/contracts.ts +17 -0
- package/src/machine/errors.ts +34 -0
- package/src/machine/index.ts +173 -0
- 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 {
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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 (
|
|
179
|
-
console.log(`
|
|
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 (
|
|
182
|
-
console.log(` ⏭️ Skipped ${
|
|
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 (
|
|
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
|
-
}
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|