swallowkit 1.0.0-beta.2 → 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/LICENSE +21 -21
- package/README.ja.md +312 -215
- package/README.md +369 -216
- package/dist/__tests__/fixtures.d.ts +22 -0
- package/dist/__tests__/fixtures.d.ts.map +1 -0
- package/dist/__tests__/fixtures.js +146 -0
- package/dist/__tests__/fixtures.js.map +1 -0
- package/dist/cli/commands/add-auth.d.ts +10 -0
- package/dist/cli/commands/add-auth.d.ts.map +1 -0
- package/dist/cli/commands/add-auth.js +444 -0
- package/dist/cli/commands/add-auth.js.map +1 -0
- package/dist/cli/commands/add-connector.d.ts +20 -0
- package/dist/cli/commands/add-connector.d.ts.map +1 -0
- package/dist/cli/commands/add-connector.js +163 -0
- package/dist/cli/commands/add-connector.js.map +1 -0
- package/dist/cli/commands/create-model.d.ts +1 -4
- package/dist/cli/commands/create-model.d.ts.map +1 -1
- package/dist/cli/commands/create-model.js +21 -82
- package/dist/cli/commands/create-model.js.map +1 -1
- package/dist/cli/commands/dev-seeds.d.ts +35 -0
- package/dist/cli/commands/dev-seeds.d.ts.map +1 -0
- package/dist/cli/commands/dev-seeds.js +292 -0
- package/dist/cli/commands/dev-seeds.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +19 -0
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +476 -117
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +3 -1
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/init.d.ts +13 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +2627 -1708
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts +3 -0
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +617 -129
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/index.d.ts +4 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +162 -42
- package/dist/cli/index.js.map +1 -1
- package/dist/core/config.d.ts +8 -2
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +90 -4
- package/dist/core/config.js.map +1 -1
- package/dist/core/mock/connector-mock-server.d.ts +101 -0
- package/dist/core/mock/connector-mock-server.d.ts.map +1 -0
- package/dist/core/mock/connector-mock-server.js +480 -0
- package/dist/core/mock/connector-mock-server.js.map +1 -0
- package/dist/core/mock/zod-mock-generator.d.ts +14 -0
- package/dist/core/mock/zod-mock-generator.d.ts.map +1 -0
- package/dist/core/mock/zod-mock-generator.js +163 -0
- package/dist/core/mock/zod-mock-generator.js.map +1 -0
- 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/core/scaffold/auth-generator.d.ts +38 -0
- package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
- package/dist/core/scaffold/auth-generator.js +1244 -0
- package/dist/core/scaffold/auth-generator.js.map +1 -0
- package/dist/core/scaffold/connector-functions-generator.d.ts +41 -0
- package/dist/core/scaffold/connector-functions-generator.d.ts.map +1 -0
- package/dist/core/scaffold/connector-functions-generator.js +1027 -0
- package/dist/core/scaffold/connector-functions-generator.js.map +1 -0
- package/dist/core/scaffold/functions-generator.d.ts +7 -1
- package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
- package/dist/core/scaffold/functions-generator.js +920 -213
- package/dist/core/scaffold/functions-generator.js.map +1 -1
- package/dist/core/scaffold/model-parser.d.ts +20 -1
- package/dist/core/scaffold/model-parser.d.ts.map +1 -1
- package/dist/core/scaffold/model-parser.js +329 -135
- package/dist/core/scaffold/model-parser.js.map +1 -1
- package/dist/core/scaffold/nextjs-generator.d.ts +8 -0
- package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
- package/dist/core/scaffold/nextjs-generator.js +314 -182
- package/dist/core/scaffold/nextjs-generator.js.map +1 -1
- package/dist/core/scaffold/openapi-generator.d.ts +3 -0
- package/dist/core/scaffold/openapi-generator.d.ts.map +1 -0
- package/dist/core/scaffold/openapi-generator.js +190 -0
- package/dist/core/scaffold/openapi-generator.js.map +1 -0
- package/dist/core/scaffold/ui-generator.d.ts +10 -4
- package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
- package/dist/core/scaffold/ui-generator.js +768 -663
- package/dist/core/scaffold/ui-generator.js.map +1 -1
- package/dist/database/base-model.d.ts +3 -3
- package/dist/database/base-model.js +3 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- 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/dist/types/index.d.ts +65 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/package-manager.d.ts +109 -0
- package/dist/utils/package-manager.d.ts.map +1 -0
- package/dist/utils/package-manager.js +215 -0
- package/dist/utils/package-manager.js.map +1 -0
- package/package.json +85 -73
- package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +1139 -0
- package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +194 -0
- package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +532 -0
- package/src/__tests__/auth.test.ts +654 -0
- package/src/__tests__/config.test.ts +263 -0
- package/src/__tests__/connector-functions-generator.test.ts +288 -0
- package/src/__tests__/connector-mock-server.test.ts +439 -0
- package/src/__tests__/connector-model-bff.test.ts +162 -0
- package/src/__tests__/dev-seeds.test.ts +112 -0
- package/src/__tests__/dev.test.ts +148 -0
- package/src/__tests__/fixtures.ts +144 -0
- package/src/__tests__/functions-generator.test.ts +237 -0
- package/src/__tests__/init.test.ts +80 -0
- package/src/__tests__/machine.test.ts +212 -0
- package/src/__tests__/mcp.test.ts +56 -0
- package/src/__tests__/model-parser.test.ts +72 -0
- package/src/__tests__/nextjs-generator.test.ts +97 -0
- package/src/__tests__/openapi-generator.test.ts +43 -0
- package/src/__tests__/package-manager.test.ts +189 -0
- package/src/__tests__/scaffold.test.ts +39 -0
- package/src/__tests__/string-utils.test.ts +75 -0
- package/src/__tests__/ui-generator.test.ts +144 -0
- package/src/__tests__/zod-mock-generator.test.ts +132 -0
- package/src/cli/commands/add-auth.ts +500 -0
- package/src/cli/commands/add-connector.ts +158 -0
- package/src/cli/commands/create-model.ts +62 -0
- package/src/cli/commands/dev-seeds.ts +358 -0
- package/src/cli/commands/dev.ts +962 -0
- package/src/cli/commands/index.ts +9 -0
- package/src/cli/commands/init.ts +3371 -0
- package/src/cli/commands/provision.ts +193 -0
- package/src/cli/commands/scaffold.ts +1211 -0
- package/src/cli/index.ts +191 -0
- package/src/core/config.ts +308 -0
- package/src/core/mock/connector-mock-server.ts +555 -0
- package/src/core/mock/zod-mock-generator.ts +205 -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/core/scaffold/auth-generator.ts +1284 -0
- package/src/core/scaffold/connector-functions-generator.ts +1128 -0
- package/src/core/scaffold/functions-generator.ts +970 -0
- package/src/core/scaffold/model-parser.ts +841 -0
- package/src/core/scaffold/nextjs-generator.ts +370 -0
- package/src/core/scaffold/openapi-generator.ts +212 -0
- package/src/core/scaffold/ui-generator.ts +1061 -0
- package/src/database/base-model.ts +184 -0
- package/src/database/client.ts +140 -0
- package/src/database/repository.ts +104 -0
- package/src/database/runtime-check.ts +25 -0
- package/src/index.ts +27 -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
- package/src/types/index.ts +134 -0
- package/src/utils/package-manager.ts +229 -0
|
@@ -38,13 +38,36 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
38
38
|
})();
|
|
39
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
40
|
exports.scaffoldCommand = scaffoldCommand;
|
|
41
|
+
exports.getOpenApiGeneratorArgs = getOpenApiGeneratorArgs;
|
|
42
|
+
exports.getCSharpSchemaArtifactPruneTargets = getCSharpSchemaArtifactPruneTargets;
|
|
41
43
|
const fs = __importStar(require("fs"));
|
|
42
44
|
const path = __importStar(require("path"));
|
|
45
|
+
const child_process_1 = require("child_process");
|
|
46
|
+
const config_1 = require("../../core/config");
|
|
43
47
|
const model_parser_1 = require("../../core/scaffold/model-parser");
|
|
44
48
|
const functions_generator_1 = require("../../core/scaffold/functions-generator");
|
|
49
|
+
const connector_functions_generator_1 = require("../../core/scaffold/connector-functions-generator");
|
|
45
50
|
const nextjs_generator_1 = require("../../core/scaffold/nextjs-generator");
|
|
51
|
+
const openapi_generator_1 = require("../../core/scaffold/openapi-generator");
|
|
46
52
|
const ui_generator_1 = require("../../core/scaffold/ui-generator");
|
|
47
|
-
const
|
|
53
|
+
const package_manager_1 = require("../../utils/package-manager");
|
|
54
|
+
const manifest_1 = require("../../core/project/manifest");
|
|
55
|
+
function getMachineAwareStdio() {
|
|
56
|
+
return process.env.SWALLOWKIT_MACHINE_OUTPUT === "1" ? "pipe" : "inherit";
|
|
57
|
+
}
|
|
58
|
+
function runSpawnSyncCommand(command, args, cwd) {
|
|
59
|
+
const result = (0, child_process_1.spawnSync)(command, args, {
|
|
60
|
+
cwd,
|
|
61
|
+
stdio: getMachineAwareStdio(),
|
|
62
|
+
shell: true,
|
|
63
|
+
});
|
|
64
|
+
if (typeof result.status === "number" && result.status !== 0) {
|
|
65
|
+
throw new Error(`${command} ${args.join(" ")} exited with code ${result.status}`);
|
|
66
|
+
}
|
|
67
|
+
if (result.error) {
|
|
68
|
+
throw result.error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
48
71
|
async function scaffoldCommand(options) {
|
|
49
72
|
// SwallowKit プロジェクトディレクトリかどうかを検証
|
|
50
73
|
(0, config_1.ensureSwallowKitProject)("scaffold");
|
|
@@ -57,6 +80,14 @@ async function scaffoldCommand(options) {
|
|
|
57
80
|
console.log("🔍 Parsing model file...");
|
|
58
81
|
const modelInfo = await (0, model_parser_1.parseModelFile)(modelPath);
|
|
59
82
|
console.log(`✅ Model parsed: ${modelInfo.name} (${modelInfo.schemaName})`);
|
|
83
|
+
const backendLanguage = (0, config_1.getBackendLanguage)();
|
|
84
|
+
console.log(`🧠 Backend language: ${backendLanguage}`);
|
|
85
|
+
// コネクタモデルかどうかを判定
|
|
86
|
+
const isConnectorModel = !!modelInfo.connectorConfig;
|
|
87
|
+
if (isConnectorModel) {
|
|
88
|
+
console.log(`🔌 Connector model detected: ${modelInfo.connectorConfig.connector}`);
|
|
89
|
+
console.log(` Operations: ${modelInfo.connectorConfig.operations.join(", ")}`);
|
|
90
|
+
}
|
|
60
91
|
// ネストスキーマ参照があれば表示
|
|
61
92
|
if (modelInfo.nestedSchemaRefs.length > 0) {
|
|
62
93
|
console.log(`🔗 Nested schema references detected:`);
|
|
@@ -73,36 +104,88 @@ async function scaffoldCommand(options) {
|
|
|
73
104
|
// 4. Read shared package name
|
|
74
105
|
const functionsDir = options.functionsDir || "functions";
|
|
75
106
|
const sharedPackageName = readSharedPackageName();
|
|
107
|
+
const relatedModels = backendLanguage === "typescript"
|
|
108
|
+
? [modelInfo]
|
|
109
|
+
: await collectModelGraph(modelPath);
|
|
76
110
|
// 5. Generate BFF callFunction helper
|
|
77
111
|
await generateCallFunctionHelper();
|
|
78
112
|
// 6. Generate Azure Functions code
|
|
79
|
-
|
|
113
|
+
if (isConnectorModel) {
|
|
114
|
+
await generateConnectorFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage);
|
|
115
|
+
// 6b. Install connector driver dependencies
|
|
116
|
+
await installConnectorDriverDependencies(modelInfo, functionsDir, backendLanguage);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
await generateFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage);
|
|
120
|
+
if (backendLanguage !== "typescript") {
|
|
121
|
+
await generateLanguageSchemaArtifacts(relatedModels, modelInfo, functionsDir, backendLanguage);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
80
124
|
// 7. Generate Next.js BFF API Routes
|
|
81
125
|
const apiDir = options.apiDir || "app/api";
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
126
|
+
if (isConnectorModel) {
|
|
127
|
+
await generateConnectorBFFRoutesFiles(modelInfo, apiDir, sharedPackageName);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
await generateBFFRoutes(modelInfo, apiDir, sharedPackageName);
|
|
131
|
+
}
|
|
132
|
+
// 8. Generate Cosmos DB container Bicep file (skip for connector models)
|
|
133
|
+
if (!isConnectorModel) {
|
|
134
|
+
await generateCosmosContainer(modelInfo);
|
|
135
|
+
}
|
|
85
136
|
// 9. Generate UI components (unless --api-only)
|
|
86
137
|
if (!options.apiOnly) {
|
|
87
|
-
|
|
138
|
+
const uiAuthConfig = (0, config_1.getAuthConfig)();
|
|
139
|
+
const uiAuthPolicy = resolveAuthPolicy(modelInfo, uiAuthConfig);
|
|
140
|
+
const uiAuthOptions = uiAuthPolicy && uiAuthConfig && uiAuthConfig.provider !== 'none'
|
|
141
|
+
? { authPolicy: uiAuthPolicy }
|
|
142
|
+
: undefined;
|
|
143
|
+
await generateUIComponents(modelInfo, sharedPackageName, uiAuthOptions);
|
|
88
144
|
await updateNavigationMenu(modelInfo);
|
|
89
145
|
}
|
|
146
|
+
await (0, manifest_1.syncProjectManifest)();
|
|
90
147
|
console.log("\n✅ Scaffold completed successfully!");
|
|
91
148
|
console.log("\n📝 Next steps:");
|
|
92
|
-
console.log(` 1. Review generated files in ${functionsDir}
|
|
149
|
+
console.log(` 1. Review generated files in ${describeFunctionsOutputPath(functionsDir, backendLanguage)} and ${apiDir}/`);
|
|
93
150
|
if (!options.apiOnly) {
|
|
94
151
|
console.log(` 2. Check the generated UI pages in app/${(0, model_parser_1.toKebabCase)(modelInfo.name)}/`);
|
|
95
152
|
console.log(" 3. Navigate to the model from the homepage menu");
|
|
96
153
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
154
|
+
if (backendLanguage !== "typescript") {
|
|
155
|
+
console.log(` ${options.apiOnly ? "2" : "4"}. Review generated OpenAPI assets in ${functionsDir}/openapi/ and ${functionsDir}/generated/`);
|
|
156
|
+
}
|
|
157
|
+
console.log(` ${options.apiOnly ? (backendLanguage === "typescript" ? "2" : "3") : (backendLanguage === "typescript" ? "4" : "5")}. Ensure BACKEND_FUNCTIONS_BASE_URL is set in your .env.local file`);
|
|
158
|
+
console.log(` ${options.apiOnly ? (backendLanguage === "typescript" ? "3" : "4") : (backendLanguage === "typescript" ? "5" : "6")}. Configure CosmosDBConnection in functions/local.settings.json`);
|
|
159
|
+
console.log(` ${options.apiOnly ? (backendLanguage === "typescript" ? "4" : "5") : (backendLanguage === "typescript" ? "6" : "7")}. Run '${(0, package_manager_1.getCommands)((0, package_manager_1.detectFromProject)()).dlx} swallowkit dev' to test the generated code`);
|
|
100
160
|
}
|
|
101
161
|
catch (error) {
|
|
102
162
|
console.error("\n❌ Scaffold failed:", error.message);
|
|
103
163
|
process.exit(1);
|
|
104
164
|
}
|
|
105
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* モデルの authPolicy と auth config から実効的な認可ポリシーを解決
|
|
168
|
+
* - モデルに authPolicy があればそれを使用
|
|
169
|
+
* - なければ auth.authorization.defaultPolicy に従う
|
|
170
|
+
* - auth 設定がなければ undefined(ガードなし)
|
|
171
|
+
*/
|
|
172
|
+
function resolveAuthPolicy(modelInfo, authConfig) {
|
|
173
|
+
// モデルに明示的な authPolicy がある場合はそれを使用
|
|
174
|
+
if (modelInfo.authPolicy) {
|
|
175
|
+
return modelInfo.authPolicy;
|
|
176
|
+
}
|
|
177
|
+
// auth 設定がない場合はガードなし
|
|
178
|
+
if (!authConfig || authConfig.provider === 'none') {
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
// defaultPolicy が 'authenticated' なら認証のみ(ロール指定なし)のポリシーを返す
|
|
182
|
+
const defaultPolicy = authConfig.authorization?.defaultPolicy ?? 'authenticated';
|
|
183
|
+
if (defaultPolicy === 'authenticated') {
|
|
184
|
+
return {}; // 空のポリシー = 認証のみ、ロール制限なし
|
|
185
|
+
}
|
|
186
|
+
// defaultPolicy が 'anonymous' なら認証不要
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
106
189
|
/**
|
|
107
190
|
* モデルファイルのパスを解決
|
|
108
191
|
*/
|
|
@@ -152,7 +235,7 @@ function readSharedPackageName() {
|
|
|
152
235
|
if (!fs.existsSync(sharedPkgPath)) {
|
|
153
236
|
throw new Error("shared/package.json not found.\n" +
|
|
154
237
|
"The shared package is required for model imports.\n" +
|
|
155
|
-
|
|
238
|
+
`Run "${(0, package_manager_1.getCommands)((0, package_manager_1.detectFromProject)()).dlx} swallowkit init" to set up your project.`);
|
|
156
239
|
}
|
|
157
240
|
const pkg = JSON.parse(fs.readFileSync(sharedPkgPath, "utf-8"));
|
|
158
241
|
return pkg.name;
|
|
@@ -168,34 +251,439 @@ async function generateCallFunctionHelper() {
|
|
|
168
251
|
if (!fs.existsSync(helperDir)) {
|
|
169
252
|
fs.mkdirSync(helperDir, { recursive: true });
|
|
170
253
|
}
|
|
171
|
-
|
|
254
|
+
// auth 設定がある場合は Authorization ヘッダー転送版を生成
|
|
255
|
+
const authConfig = (0, config_1.getAuthConfig)();
|
|
256
|
+
const hasAuth = authConfig && authConfig.provider !== 'none';
|
|
257
|
+
let helperCode;
|
|
258
|
+
if (hasAuth) {
|
|
259
|
+
const { generateBFFCallFunctionWithAuth } = await Promise.resolve().then(() => __importStar(require("../../core/scaffold/auth-generator")));
|
|
260
|
+
helperCode = generateBFFCallFunctionWithAuth();
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
helperCode = (0, nextjs_generator_1.generateBFFCallFunction)();
|
|
264
|
+
}
|
|
172
265
|
fs.writeFileSync(helperPath, helperCode, "utf-8");
|
|
173
266
|
console.log(`✅ Created: ${helperPath}`);
|
|
174
267
|
}
|
|
175
268
|
/**
|
|
176
269
|
* Generate Azure Functions CRUD code
|
|
177
270
|
*/
|
|
178
|
-
async function generateFunctionsCode(modelInfo, functionsDir, sharedPackageName) {
|
|
271
|
+
async function generateFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage) {
|
|
179
272
|
console.log("\n🔨 Generating Azure Functions CRUD code...");
|
|
273
|
+
// Resolve auth policy: model-level authPolicy or global defaultPolicy
|
|
274
|
+
const authConfig = (0, config_1.getAuthConfig)();
|
|
275
|
+
const authPolicy = resolveAuthPolicy(modelInfo, authConfig);
|
|
276
|
+
if (authPolicy) {
|
|
277
|
+
console.log(`🔐 Auth policy detected: ${JSON.stringify(authPolicy)}`);
|
|
278
|
+
}
|
|
180
279
|
const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
280
|
+
if (backendLanguage === "typescript") {
|
|
281
|
+
const functionFilePath = path.join(process.cwd(), functionsDir, "src", `${modelKebab}.ts`);
|
|
282
|
+
const functionDir = path.dirname(functionFilePath);
|
|
283
|
+
if (!fs.existsSync(functionDir)) {
|
|
284
|
+
fs.mkdirSync(functionDir, { recursive: true });
|
|
285
|
+
}
|
|
286
|
+
const code = (0, functions_generator_1.generateCompactAzureFunctionsCRUD)(modelInfo, sharedPackageName, authPolicy);
|
|
287
|
+
fs.writeFileSync(functionFilePath, code, "utf-8");
|
|
288
|
+
console.log(`✅ Created: ${functionFilePath}`);
|
|
289
|
+
return;
|
|
186
290
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
291
|
+
if (backendLanguage === "csharp") {
|
|
292
|
+
const crudDir = path.join(process.cwd(), functionsDir, "Crud");
|
|
293
|
+
const functionFilePath = path.join(crudDir, `${modelInfo.name}Functions.cs`);
|
|
294
|
+
fs.mkdirSync(crudDir, { recursive: true });
|
|
295
|
+
// Remove init-generated template (singular) to avoid route conflicts
|
|
296
|
+
const templatePath = path.join(crudDir, `${modelInfo.name}Function.cs`);
|
|
297
|
+
if (fs.existsSync(templatePath)) {
|
|
298
|
+
fs.unlinkSync(templatePath);
|
|
299
|
+
console.log(`🗑️ Removed template: ${templatePath}`);
|
|
300
|
+
}
|
|
301
|
+
fs.writeFileSync(functionFilePath, (0, functions_generator_1.generateCSharpAzureFunctionsCRUD)(modelInfo, authPolicy), "utf-8");
|
|
302
|
+
console.log(`✅ Created: ${functionFilePath}`);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const blueprintsDir = path.join(process.cwd(), functionsDir, "blueprints");
|
|
306
|
+
const blueprintPath = path.join(blueprintsDir, `${modelKebab.replace(/-/g, "_")}.py`);
|
|
307
|
+
fs.mkdirSync(blueprintsDir, { recursive: true });
|
|
308
|
+
const { blueprint, registration } = (0, functions_generator_1.generatePythonAzureFunctionsCRUD)(modelInfo, authPolicy);
|
|
309
|
+
fs.writeFileSync(blueprintPath, blueprint, "utf-8");
|
|
310
|
+
updatePythonFunctionRegistrations(path.join(process.cwd(), functionsDir, "function_app.py"), registration);
|
|
311
|
+
console.log(`✅ Created: ${blueprintPath}`);
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* コネクタモデル用 Azure Functions コード生成
|
|
315
|
+
*/
|
|
316
|
+
async function generateConnectorFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage) {
|
|
317
|
+
console.log("\n🔌 Generating Connector Azure Functions code...");
|
|
318
|
+
const connectorConfig = modelInfo.connectorConfig;
|
|
319
|
+
const connectorDef = (0, config_1.getConnectorDefinition)(connectorConfig.connector);
|
|
320
|
+
if (!connectorDef) {
|
|
321
|
+
throw new Error(`Connector '${connectorConfig.connector}' not found in swallowkit.config.js.\n` +
|
|
322
|
+
` Please add it to the 'connectors' section of your configuration.`);
|
|
323
|
+
}
|
|
324
|
+
// Resolve auth policy (same logic as Cosmos model scaffolding)
|
|
325
|
+
const authConfig = (0, config_1.getAuthConfig)();
|
|
326
|
+
const authPolicy = resolveAuthPolicy(modelInfo, authConfig);
|
|
327
|
+
if (authPolicy) {
|
|
328
|
+
console.log(`🔐 Auth policy detected: ${JSON.stringify(authPolicy)}`);
|
|
329
|
+
}
|
|
330
|
+
const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
|
|
331
|
+
if (backendLanguage === "typescript") {
|
|
332
|
+
const functionFilePath = path.join(process.cwd(), functionsDir, "src", `${modelKebab}.ts`);
|
|
333
|
+
fs.mkdirSync(path.dirname(functionFilePath), { recursive: true });
|
|
334
|
+
let code;
|
|
335
|
+
if (connectorDef.type === "rdb") {
|
|
336
|
+
code = (0, connector_functions_generator_1.generateRdbConnectorFunctionTS)(modelInfo, sharedPackageName, connectorDef, connectorConfig, authPolicy);
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
code = (0, connector_functions_generator_1.generateApiConnectorFunctionTS)(modelInfo, sharedPackageName, connectorDef, connectorConfig, authPolicy);
|
|
340
|
+
}
|
|
341
|
+
fs.writeFileSync(functionFilePath, code, "utf-8");
|
|
342
|
+
console.log(`✅ Created: ${functionFilePath}`);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (backendLanguage === "csharp") {
|
|
346
|
+
const functionFilePath = path.join(process.cwd(), functionsDir, "Connectors", `${modelInfo.name}ConnectorFunctions.cs`);
|
|
347
|
+
fs.mkdirSync(path.dirname(functionFilePath), { recursive: true });
|
|
348
|
+
let code;
|
|
349
|
+
if (connectorDef.type === "rdb") {
|
|
350
|
+
code = (0, connector_functions_generator_1.generateRdbConnectorFunctionCSharp)(modelInfo, connectorDef, connectorConfig);
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
code = (0, connector_functions_generator_1.generateApiConnectorFunctionCSharp)(modelInfo, connectorDef, connectorConfig);
|
|
354
|
+
}
|
|
355
|
+
fs.writeFileSync(functionFilePath, code, "utf-8");
|
|
356
|
+
console.log(`✅ Created: ${functionFilePath}`);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
// Python
|
|
360
|
+
const blueprintsDir = path.join(process.cwd(), functionsDir, "blueprints");
|
|
361
|
+
const blueprintPath = path.join(blueprintsDir, `${modelKebab.replace(/-/g, "_")}.py`);
|
|
362
|
+
fs.mkdirSync(blueprintsDir, { recursive: true });
|
|
363
|
+
let result;
|
|
364
|
+
if (connectorDef.type === "rdb") {
|
|
365
|
+
result = (0, connector_functions_generator_1.generateRdbConnectorFunctionPython)(modelInfo, connectorDef, connectorConfig);
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
result = (0, connector_functions_generator_1.generateApiConnectorFunctionPython)(modelInfo, connectorDef, connectorConfig);
|
|
369
|
+
}
|
|
370
|
+
fs.writeFileSync(blueprintPath, result.blueprint, "utf-8");
|
|
371
|
+
updatePythonFunctionRegistrations(path.join(process.cwd(), functionsDir, "function_app.py"), result.registration);
|
|
372
|
+
console.log(`✅ Created: ${blueprintPath}`);
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* コネクタモデルの scaffold 後に、生成コードが必要とする RDB/API ドライバを
|
|
376
|
+
* functions ディレクトリにインストールする。
|
|
377
|
+
*/
|
|
378
|
+
async function installConnectorDriverDependencies(modelInfo, functionsDir, backendLanguage) {
|
|
379
|
+
const connectorConfig = modelInfo.connectorConfig;
|
|
380
|
+
const connectorDef = (0, config_1.getConnectorDefinition)(connectorConfig.connector);
|
|
381
|
+
if (!connectorDef || connectorDef.type !== "rdb")
|
|
382
|
+
return;
|
|
383
|
+
const rdbDef = connectorDef;
|
|
384
|
+
const functionsPath = path.join(process.cwd(), functionsDir);
|
|
385
|
+
if (backendLanguage === "typescript") {
|
|
386
|
+
const driverMap = {
|
|
387
|
+
mysql: { deps: ["mysql2"], devDeps: [] },
|
|
388
|
+
postgres: { deps: ["pg"], devDeps: ["@types/pg"] },
|
|
389
|
+
sqlserver: { deps: ["mssql"], devDeps: [] },
|
|
390
|
+
};
|
|
391
|
+
const entry = driverMap[rdbDef.provider];
|
|
392
|
+
if (!entry)
|
|
393
|
+
return;
|
|
394
|
+
// package.json を読んで、既にインストール済みならスキップ
|
|
395
|
+
const pkgJsonPath = path.join(functionsPath, "package.json");
|
|
396
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
397
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
|
|
398
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
399
|
+
const missingDeps = entry.deps.filter(d => !allDeps[d]);
|
|
400
|
+
const missingDevDeps = entry.devDeps.filter(d => !allDeps[d]);
|
|
401
|
+
if (missingDeps.length === 0 && missingDevDeps.length === 0)
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
console.log(`\n📦 Installing ${rdbDef.provider} driver dependencies...`);
|
|
405
|
+
const pm = (0, package_manager_1.detectFromProject)(functionsPath);
|
|
406
|
+
const cmds = (0, package_manager_1.getCommands)(pm);
|
|
407
|
+
if (entry.deps.length > 0) {
|
|
408
|
+
runSpawnSyncCommand(cmds.name, [pm === "pnpm" ? "add" : "install", ...entry.deps], functionsPath);
|
|
409
|
+
}
|
|
410
|
+
if (entry.devDeps.length > 0) {
|
|
411
|
+
runSpawnSyncCommand(cmds.name, [pm === "pnpm" ? "add" : "install", "-D", ...entry.devDeps], functionsPath);
|
|
412
|
+
}
|
|
413
|
+
console.log(`✅ ${rdbDef.provider} driver installed`);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (backendLanguage === "csharp") {
|
|
417
|
+
const nugetMap = {
|
|
418
|
+
mysql: "MySqlConnector",
|
|
419
|
+
postgres: "Npgsql",
|
|
420
|
+
sqlserver: "Microsoft.Data.SqlClient",
|
|
421
|
+
};
|
|
422
|
+
const pkg = nugetMap[rdbDef.provider];
|
|
423
|
+
if (!pkg)
|
|
424
|
+
return;
|
|
425
|
+
console.log(`\n📦 Installing ${rdbDef.provider} NuGet package...`);
|
|
426
|
+
runSpawnSyncCommand("dotnet", ["add", path.join(functionsPath, "functions.csproj"), "package", pkg], functionsPath);
|
|
427
|
+
console.log(`✅ ${pkg} installed`);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
// Python — requirements.txt に追記
|
|
431
|
+
const pipMap = {
|
|
432
|
+
mysql: "mysql-connector-python",
|
|
433
|
+
postgres: "psycopg2-binary",
|
|
434
|
+
sqlserver: "pymssql",
|
|
435
|
+
};
|
|
436
|
+
const pipPkg = pipMap[rdbDef.provider];
|
|
437
|
+
if (!pipPkg)
|
|
438
|
+
return;
|
|
439
|
+
const reqPath = path.join(functionsPath, "requirements.txt");
|
|
440
|
+
if (fs.existsSync(reqPath)) {
|
|
441
|
+
const existing = fs.readFileSync(reqPath, "utf-8");
|
|
442
|
+
if (!existing.includes(pipPkg)) {
|
|
443
|
+
console.log(`\n📦 Adding ${pipPkg} to requirements.txt...`);
|
|
444
|
+
fs.appendFileSync(reqPath, `\n${pipPkg}\n`);
|
|
445
|
+
console.log(`✅ ${pipPkg} added`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* コネクタモデル用 BFF ルート生成(操作制限に対応)
|
|
451
|
+
*/
|
|
452
|
+
async function generateConnectorBFFRoutesFiles(modelInfo, apiDir, sharedPackageName) {
|
|
453
|
+
console.log("\n🔌 Generating Connector BFF API routes...");
|
|
454
|
+
const modelCamel = (0, model_parser_1.toCamelCase)(modelInfo.name);
|
|
455
|
+
const operations = modelInfo.connectorConfig.operations;
|
|
456
|
+
const listRoutePath = path.join(process.cwd(), apiDir, modelCamel, "route.ts");
|
|
457
|
+
const detailRoutePath = path.join(process.cwd(), apiDir, modelCamel, "[id]", "route.ts");
|
|
458
|
+
fs.mkdirSync(path.dirname(listRoutePath), { recursive: true });
|
|
459
|
+
fs.mkdirSync(path.dirname(detailRoutePath), { recursive: true });
|
|
460
|
+
const routes = (0, nextjs_generator_1.generateConnectorBFFRoutes)(modelInfo, sharedPackageName, operations);
|
|
461
|
+
fs.writeFileSync(listRoutePath, routes.listRoute, "utf-8");
|
|
462
|
+
fs.writeFileSync(detailRoutePath, routes.detailRoute, "utf-8");
|
|
463
|
+
console.log(`✅ Created: ${listRoutePath}`);
|
|
464
|
+
console.log(`✅ Created: ${detailRoutePath}`);
|
|
465
|
+
}
|
|
466
|
+
async function collectModelGraph(modelPath, seen = new Map()) {
|
|
467
|
+
const resolvedPath = path.resolve(modelPath);
|
|
468
|
+
if (seen.has(resolvedPath)) {
|
|
469
|
+
return Array.from(seen.values());
|
|
470
|
+
}
|
|
471
|
+
const modelInfo = await (0, model_parser_1.parseModelFile)(resolvedPath);
|
|
472
|
+
seen.set(resolvedPath, modelInfo);
|
|
473
|
+
for (const ref of modelInfo.nestedSchemaRefs) {
|
|
474
|
+
const nestedPath = resolveNestedModelPath(resolvedPath, ref.importPath);
|
|
475
|
+
await collectModelGraph(nestedPath, seen);
|
|
476
|
+
}
|
|
477
|
+
return Array.from(seen.values());
|
|
478
|
+
}
|
|
479
|
+
function resolveNestedModelPath(modelPath, importPath) {
|
|
480
|
+
let resolvedPath = path.resolve(path.dirname(modelPath), importPath);
|
|
481
|
+
if (!resolvedPath.endsWith(".ts")) {
|
|
482
|
+
resolvedPath += ".ts";
|
|
483
|
+
}
|
|
484
|
+
return resolvedPath;
|
|
485
|
+
}
|
|
486
|
+
function describeFunctionsOutputPath(functionsDir, backendLanguage) {
|
|
487
|
+
if (backendLanguage === "typescript") {
|
|
488
|
+
return `${functionsDir}/src/`;
|
|
489
|
+
}
|
|
490
|
+
if (backendLanguage === "csharp") {
|
|
491
|
+
return `${functionsDir}/Crud/`;
|
|
492
|
+
}
|
|
493
|
+
return `${functionsDir}/blueprints/`;
|
|
494
|
+
}
|
|
495
|
+
async function generateLanguageSchemaArtifacts(models, rootModel, functionsDir, backendLanguage) {
|
|
496
|
+
console.log("\n🧬 Generating OpenAPI-backed schema artifacts...");
|
|
497
|
+
const openApiDir = path.join(process.cwd(), functionsDir, "openapi");
|
|
498
|
+
fs.mkdirSync(openApiDir, { recursive: true });
|
|
499
|
+
const specPath = path.join(openApiDir, `${(0, model_parser_1.toKebabCase)(rootModel.name)}.openapi.json`);
|
|
500
|
+
fs.writeFileSync(specPath, (0, openapi_generator_1.generateOpenApiDocument)(models, rootModel), "utf-8");
|
|
501
|
+
console.log(`✅ Created: ${specPath}`);
|
|
502
|
+
const outputDir = path.join(process.cwd(), functionsDir, "generated", backendLanguage === "csharp" ? "csharp-models" : "python-models");
|
|
503
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
504
|
+
await runOpenApiGenerator(specPath, outputDir, backendLanguage);
|
|
505
|
+
if (backendLanguage === "csharp") {
|
|
506
|
+
pruneGeneratedCSharpArtifacts(outputDir);
|
|
507
|
+
}
|
|
508
|
+
console.log(`✅ Generated ${backendLanguage} schema assets: ${outputDir}`);
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* OpenAPI Generator の実行前に Java バージョンを確認する。
|
|
512
|
+
* Java 11 未満の場合は明確なエラーメッセージを表示して処理を中断する。
|
|
513
|
+
*/
|
|
514
|
+
function checkJavaVersion() {
|
|
515
|
+
try {
|
|
516
|
+
const result = (0, child_process_1.spawnSync)("java", ["-version"], { encoding: "utf8" });
|
|
517
|
+
// java -version は stderr に出力する
|
|
518
|
+
const versionOutput = (result.stderr || "") + (result.stdout || "");
|
|
519
|
+
const versionMatch = versionOutput.match(/version "(\d+)(?:\.(\d+))?/);
|
|
520
|
+
if (!versionMatch)
|
|
521
|
+
return;
|
|
522
|
+
const major = parseInt(versionMatch[1]);
|
|
523
|
+
// Java 8 以前は "1.8" 形式、Java 9 以降は "17" 形式
|
|
524
|
+
const effectiveMajor = major === 1 ? parseInt(versionMatch[2] ?? "0") : major;
|
|
525
|
+
if (effectiveMajor < 11) {
|
|
526
|
+
const versionStr = versionOutput.match(/version "([^"]+)"/)?.[1] ?? "unknown";
|
|
527
|
+
console.error(`\n❌ OpenAPI Generator requires Java 11 or later.`);
|
|
528
|
+
console.error(` Detected: Java ${versionStr}`);
|
|
529
|
+
if (process.env.JAVA_HOME) {
|
|
530
|
+
console.error(` Current JAVA_HOME: ${process.env.JAVA_HOME}`);
|
|
531
|
+
}
|
|
532
|
+
console.error(` Please set JAVA_HOME to a Java 11+ installation and retry.`);
|
|
533
|
+
console.error(` Example (Windows): $env:JAVA_HOME = "C:\\path\\to\\jdk-17"`);
|
|
534
|
+
process.exit(1);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
catch {
|
|
538
|
+
// java コマンドが見つからない場合は OpenAPI Generator 自身がエラーを出す
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
async function runOpenApiGenerator(specPath, outputDir, backendLanguage) {
|
|
542
|
+
checkJavaVersion();
|
|
543
|
+
const pm = (0, package_manager_1.detectFromProject)();
|
|
544
|
+
const command = pm === "pnpm" ? "pnpm" : "npx";
|
|
545
|
+
const args = pm === "pnpm"
|
|
546
|
+
? ["exec", "openapi-generator-cli", ...getOpenApiGeneratorArgs(specPath, outputDir, backendLanguage)]
|
|
547
|
+
: ["openapi-generator-cli", ...getOpenApiGeneratorArgs(specPath, outputDir, backendLanguage)];
|
|
548
|
+
await new Promise((resolve, reject) => {
|
|
549
|
+
const child = (0, child_process_1.spawn)(command, args, {
|
|
550
|
+
cwd: process.cwd(),
|
|
551
|
+
shell: true,
|
|
552
|
+
stdio: getMachineAwareStdio(),
|
|
553
|
+
});
|
|
554
|
+
child.on("close", (code) => {
|
|
555
|
+
if (code === 0) {
|
|
556
|
+
resolve();
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
reject(new Error(`OpenAPI generator exited with code ${code}`));
|
|
560
|
+
});
|
|
561
|
+
child.on("error", reject);
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
function getOpenApiGeneratorArgs(specPath, outputDir, backendLanguage) {
|
|
565
|
+
const globalProperty = backendLanguage === "csharp"
|
|
566
|
+
? "models,apis=false,modelDocs=false,modelTests=false"
|
|
567
|
+
: "models,apis=false,supportingFiles=false,modelDocs=false,modelTests=false";
|
|
568
|
+
const baseArgs = [
|
|
569
|
+
"generate",
|
|
570
|
+
"-i",
|
|
571
|
+
specPath,
|
|
572
|
+
"-o",
|
|
573
|
+
outputDir,
|
|
574
|
+
"--global-property",
|
|
575
|
+
globalProperty,
|
|
576
|
+
];
|
|
577
|
+
if (backendLanguage === "csharp") {
|
|
578
|
+
return [
|
|
579
|
+
...baseArgs,
|
|
580
|
+
"-g",
|
|
581
|
+
"csharp",
|
|
582
|
+
"--additional-properties",
|
|
583
|
+
"packageName=SwallowKitBackendModels,targetFramework=net8.0,nullableReferenceTypes=true",
|
|
584
|
+
];
|
|
585
|
+
}
|
|
586
|
+
return [
|
|
587
|
+
...baseArgs,
|
|
588
|
+
"-g",
|
|
589
|
+
"python",
|
|
590
|
+
"--additional-properties",
|
|
591
|
+
"packageName=backend_models,projectName=backend-models",
|
|
592
|
+
];
|
|
593
|
+
}
|
|
594
|
+
function getCSharpSchemaArtifactPruneTargets(outputDir) {
|
|
595
|
+
return [
|
|
596
|
+
path.join(outputDir, "src", "SwallowKitBackendModels.Test"),
|
|
597
|
+
path.join(outputDir, "src", "SwallowKitBackendModels", "Api"),
|
|
598
|
+
path.join(outputDir, "src", "SwallowKitBackendModels", "Extensions"),
|
|
599
|
+
];
|
|
600
|
+
}
|
|
601
|
+
function pruneGeneratedCSharpArtifacts(outputDir) {
|
|
602
|
+
for (const target of getCSharpSchemaArtifactPruneTargets(outputDir)) {
|
|
603
|
+
if (fs.existsSync(target)) {
|
|
604
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
const clientDir = path.join(outputDir, "src", "SwallowKitBackendModels", "Client");
|
|
608
|
+
if (!fs.existsSync(clientDir)) {
|
|
609
|
+
// --global-property models ではClient/が生成されないため、
|
|
610
|
+
// モデルが依存する最小限の Option<T> を自前で作成する
|
|
611
|
+
fs.mkdirSync(clientDir, { recursive: true });
|
|
612
|
+
fs.writeFileSync(path.join(clientDir, "Option.cs"), generateMinimalOptionCs(), "utf-8");
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
for (const entry of fs.readdirSync(clientDir, { withFileTypes: true })) {
|
|
616
|
+
if (!entry.isFile() || entry.name === "Option.cs") {
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
fs.rmSync(path.join(clientDir, entry.name), { force: true });
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* OpenAPI Generator の csharp テンプレートが生成するモデルは Option<T> に依存するが、
|
|
624
|
+
* supportingFiles を除外しているため Client/Option.cs が生成されない。
|
|
625
|
+
* Polly 等の不要な依存を避けつつモデルをコンパイル可能にするため、最小限の Option<T> を提供する。
|
|
626
|
+
*/
|
|
627
|
+
function generateMinimalOptionCs() {
|
|
628
|
+
return `// <auto-generated>
|
|
629
|
+
// Minimal Option<T> for OpenAPI Generator model compatibility.
|
|
630
|
+
// Full client supporting files are excluded to avoid Polly version conflicts.
|
|
631
|
+
// </auto-generated>
|
|
632
|
+
|
|
633
|
+
#nullable enable
|
|
634
|
+
|
|
635
|
+
namespace SwallowKitBackendModels.Client
|
|
636
|
+
{
|
|
637
|
+
/// <summary>
|
|
638
|
+
/// A wrapper for nullable/optional properties generated by OpenAPI Generator.
|
|
639
|
+
/// Tracks whether a value has been explicitly set (distinguishing null from absent).
|
|
640
|
+
/// </summary>
|
|
641
|
+
public readonly struct Option<TValue>
|
|
642
|
+
{
|
|
643
|
+
/// <summary>Whether this option has been explicitly set.</summary>
|
|
644
|
+
public bool IsSet { get; }
|
|
645
|
+
|
|
646
|
+
/// <summary>The contained value (may be default if not set).</summary>
|
|
647
|
+
public TValue Value { get; }
|
|
648
|
+
|
|
649
|
+
/// <summary>Create an Option with an explicit value.</summary>
|
|
650
|
+
public Option(TValue value)
|
|
651
|
+
{
|
|
652
|
+
IsSet = true;
|
|
653
|
+
Value = value;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/// <summary>Implicit conversion from Option to its inner value.</summary>
|
|
657
|
+
public static implicit operator TValue(Option<TValue> option) => option.Value;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
`;
|
|
661
|
+
}
|
|
662
|
+
function updatePythonFunctionRegistrations(functionAppPath, registration) {
|
|
663
|
+
if (!fs.existsSync(functionAppPath)) {
|
|
664
|
+
throw new Error(`Python Functions entrypoint not found: ${functionAppPath}`);
|
|
665
|
+
}
|
|
666
|
+
const content = fs.readFileSync(functionAppPath, "utf-8");
|
|
667
|
+
// Check if import line already exists (handles init-generated layout)
|
|
668
|
+
const importLine = registration.split("\n").find((l) => l.startsWith("from ") || l.startsWith("import "));
|
|
669
|
+
if (importLine && content.includes(importLine)) {
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
if (content.includes(registration)) {
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
const marker = "# SwallowKit scaffold registrations";
|
|
676
|
+
if (!content.includes(marker)) {
|
|
677
|
+
throw new Error(`Could not find scaffold registration marker in ${functionAppPath}`);
|
|
678
|
+
}
|
|
679
|
+
const updated = content.replace(marker, `${registration}\n${marker}`);
|
|
680
|
+
fs.writeFileSync(functionAppPath, updated, "utf-8");
|
|
192
681
|
}
|
|
193
682
|
/**
|
|
194
683
|
* Next.js BFF API Routes を生成
|
|
195
684
|
*/
|
|
196
685
|
async function generateBFFRoutes(modelInfo, apiDir, sharedPackageName) {
|
|
197
686
|
console.log("\n🔨 Generating Next.js BFF API routes...");
|
|
198
|
-
const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
|
|
199
687
|
const modelCamel = modelInfo.name.charAt(0).toLowerCase() + modelInfo.name.slice(1);
|
|
200
688
|
// List route: app/api/[model]/route.ts
|
|
201
689
|
const listRoutePath = path.join(process.cwd(), apiDir, modelCamel, "route.ts");
|
|
@@ -240,70 +728,70 @@ async function generateCosmosContainer(modelInfo) {
|
|
|
240
728
|
// Generate container Bicep file
|
|
241
729
|
const containerFileName = `${modelKebab}-container.bicep`;
|
|
242
730
|
const containerFilePath = path.join(containersDir, containerFileName);
|
|
243
|
-
const bicepContent = `@description('Cosmos DB account name')
|
|
244
|
-
param cosmosAccountName string
|
|
245
|
-
|
|
246
|
-
@description('Database name')
|
|
247
|
-
param databaseName string
|
|
248
|
-
|
|
249
|
-
@description('Container name')
|
|
250
|
-
param containerName string = '${modelPascal}s'
|
|
251
|
-
|
|
252
|
-
@description('Partition key path')
|
|
253
|
-
param partitionKeyPath string = '
|
|
254
|
-
|
|
255
|
-
@description('Throughput (RU/s) - only used for Free Tier')
|
|
256
|
-
param throughput int = 400
|
|
257
|
-
|
|
258
|
-
@description('Cosmos DB mode: freetier or serverless')
|
|
259
|
-
param cosmosDbMode string
|
|
260
|
-
|
|
261
|
-
// Reference existing Cosmos DB account
|
|
262
|
-
resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2023-11-15' existing = {
|
|
263
|
-
name: cosmosAccountName
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Reference existing database
|
|
267
|
-
resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2023-11-15' existing = {
|
|
268
|
-
parent: cosmosAccount
|
|
269
|
-
name: databaseName
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Container for ${modelPascal}
|
|
273
|
-
resource container 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2023-11-15' = {
|
|
274
|
-
parent: database
|
|
275
|
-
name: containerName
|
|
276
|
-
properties: {
|
|
277
|
-
resource: {
|
|
278
|
-
id: containerName
|
|
279
|
-
partitionKey: {
|
|
280
|
-
paths: [
|
|
281
|
-
partitionKeyPath
|
|
282
|
-
]
|
|
283
|
-
kind: 'Hash'
|
|
284
|
-
}
|
|
285
|
-
indexingPolicy: {
|
|
286
|
-
automatic: true
|
|
287
|
-
indexingMode: 'consistent'
|
|
288
|
-
includedPaths: [
|
|
289
|
-
{
|
|
290
|
-
path: '/*'
|
|
291
|
-
}
|
|
292
|
-
]
|
|
293
|
-
excludedPaths: [
|
|
294
|
-
{
|
|
295
|
-
path: '/_etag/?'
|
|
296
|
-
}
|
|
297
|
-
]
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
options: cosmosDbMode == 'freetier' ? {
|
|
301
|
-
throughput: throughput
|
|
302
|
-
} : {}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
output containerName string = container.name
|
|
731
|
+
const bicepContent = `@description('Cosmos DB account name')
|
|
732
|
+
param cosmosAccountName string
|
|
733
|
+
|
|
734
|
+
@description('Database name')
|
|
735
|
+
param databaseName string
|
|
736
|
+
|
|
737
|
+
@description('Container name')
|
|
738
|
+
param containerName string = '${modelPascal}s'
|
|
739
|
+
|
|
740
|
+
@description('Partition key path')
|
|
741
|
+
param partitionKeyPath string = '${modelInfo.partitionKey}'
|
|
742
|
+
|
|
743
|
+
@description('Throughput (RU/s) - only used for Free Tier')
|
|
744
|
+
param throughput int = 400
|
|
745
|
+
|
|
746
|
+
@description('Cosmos DB mode: freetier or serverless')
|
|
747
|
+
param cosmosDbMode string
|
|
748
|
+
|
|
749
|
+
// Reference existing Cosmos DB account
|
|
750
|
+
resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2023-11-15' existing = {
|
|
751
|
+
name: cosmosAccountName
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Reference existing database
|
|
755
|
+
resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2023-11-15' existing = {
|
|
756
|
+
parent: cosmosAccount
|
|
757
|
+
name: databaseName
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// Container for ${modelPascal}
|
|
761
|
+
resource container 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2023-11-15' = {
|
|
762
|
+
parent: database
|
|
763
|
+
name: containerName
|
|
764
|
+
properties: {
|
|
765
|
+
resource: {
|
|
766
|
+
id: containerName
|
|
767
|
+
partitionKey: {
|
|
768
|
+
paths: [
|
|
769
|
+
partitionKeyPath
|
|
770
|
+
]
|
|
771
|
+
kind: 'Hash'
|
|
772
|
+
}
|
|
773
|
+
indexingPolicy: {
|
|
774
|
+
automatic: true
|
|
775
|
+
indexingMode: 'consistent'
|
|
776
|
+
includedPaths: [
|
|
777
|
+
{
|
|
778
|
+
path: '/*'
|
|
779
|
+
}
|
|
780
|
+
]
|
|
781
|
+
excludedPaths: [
|
|
782
|
+
{
|
|
783
|
+
path: '/_etag/?'
|
|
784
|
+
}
|
|
785
|
+
]
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
options: cosmosDbMode == 'freetier' ? {
|
|
789
|
+
throughput: throughput
|
|
790
|
+
} : {}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
output containerName string = container.name
|
|
307
795
|
`;
|
|
308
796
|
// Write Bicep file (overwrite if exists)
|
|
309
797
|
fs.writeFileSync(containerFilePath, bicepContent, "utf-8");
|
|
@@ -334,37 +822,37 @@ async function updateMainBicepWithContainer(modelKebab, modelPascal) {
|
|
|
334
822
|
const cosmosModuleMatch = mainBicepContent.match(cosmosModulePattern);
|
|
335
823
|
if (!cosmosModuleMatch) {
|
|
336
824
|
console.log("⚠️ Could not find Cosmos DB Serverless module in main.bicep. Please manually add the container module:");
|
|
337
|
-
console.log(`\nmodule ${containerModuleName} 'containers/${modelKebab}-container.bicep' = {
|
|
338
|
-
name: '${modelKebab}-container'
|
|
339
|
-
params: {
|
|
340
|
-
cosmosAccountName: cosmosDbMode == 'freetier' ? cosmosDbFreeTier.outputs.accountName : cosmosDbServerless.outputs.accountName
|
|
341
|
-
databaseName: databaseName
|
|
342
|
-
cosmosDbMode: cosmosDbMode
|
|
343
|
-
}
|
|
344
|
-
dependsOn: [
|
|
345
|
-
cosmosDbFreeTier
|
|
346
|
-
cosmosDbServerless
|
|
347
|
-
]
|
|
825
|
+
console.log(`\nmodule ${containerModuleName} 'containers/${modelKebab}-container.bicep' = {
|
|
826
|
+
name: '${modelKebab}-container'
|
|
827
|
+
params: {
|
|
828
|
+
cosmosAccountName: cosmosDbMode == 'freetier' ? cosmosDbFreeTier.outputs.accountName : cosmosDbServerless.outputs.accountName
|
|
829
|
+
databaseName: databaseName
|
|
830
|
+
cosmosDbMode: cosmosDbMode
|
|
831
|
+
}
|
|
832
|
+
dependsOn: [
|
|
833
|
+
cosmosDbFreeTier
|
|
834
|
+
cosmosDbServerless
|
|
835
|
+
]
|
|
348
836
|
}\n`);
|
|
349
837
|
return;
|
|
350
838
|
}
|
|
351
839
|
// Find the end of the cosmosDbServerless module
|
|
352
840
|
const insertPosition = cosmosModuleMatch.index + cosmosModuleMatch[0].length;
|
|
353
841
|
// Create the container module declaration
|
|
354
|
-
const containerModule = `
|
|
355
|
-
|
|
356
|
-
// ${modelPascal} Container
|
|
357
|
-
module ${containerModuleName} 'containers/${modelKebab}-container.bicep' = {
|
|
358
|
-
name: '${modelKebab}-container'
|
|
359
|
-
params: {
|
|
360
|
-
cosmosAccountName: cosmosDbMode == 'freetier' ? cosmosDbFreeTier.outputs.accountName : cosmosDbServerless.outputs.accountName
|
|
361
|
-
databaseName: '$` + `{projectName}Database'
|
|
362
|
-
cosmosDbMode: cosmosDbMode
|
|
363
|
-
}
|
|
364
|
-
dependsOn: [
|
|
365
|
-
cosmosDbFreeTier
|
|
366
|
-
cosmosDbServerless
|
|
367
|
-
]
|
|
842
|
+
const containerModule = `
|
|
843
|
+
|
|
844
|
+
// ${modelPascal} Container
|
|
845
|
+
module ${containerModuleName} 'containers/${modelKebab}-container.bicep' = {
|
|
846
|
+
name: '${modelKebab}-container'
|
|
847
|
+
params: {
|
|
848
|
+
cosmosAccountName: cosmosDbMode == 'freetier' ? cosmosDbFreeTier.outputs.accountName : cosmosDbServerless.outputs.accountName
|
|
849
|
+
databaseName: '$` + `{projectName}Database'
|
|
850
|
+
cosmosDbMode: cosmosDbMode
|
|
851
|
+
}
|
|
852
|
+
dependsOn: [
|
|
853
|
+
cosmosDbFreeTier
|
|
854
|
+
cosmosDbServerless
|
|
855
|
+
]
|
|
368
856
|
}`;
|
|
369
857
|
// Insert the module
|
|
370
858
|
mainBicepContent =
|
|
@@ -377,7 +865,7 @@ module ${containerModuleName} 'containers/${modelKebab}-container.bicep' = {
|
|
|
377
865
|
/**
|
|
378
866
|
* Generate UI components (list, detail, form, create, edit pages)
|
|
379
867
|
*/
|
|
380
|
-
async function generateUIComponents(modelInfo, sharedPackageName) {
|
|
868
|
+
async function generateUIComponents(modelInfo, sharedPackageName, authOptions) {
|
|
381
869
|
console.log("\n🎨 Generating UI components...");
|
|
382
870
|
const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
|
|
383
871
|
const modelName = modelInfo.name;
|
|
@@ -395,11 +883,11 @@ async function generateUIComponents(modelInfo, sharedPackageName) {
|
|
|
395
883
|
}
|
|
396
884
|
});
|
|
397
885
|
// Generate and write files
|
|
398
|
-
const listPage = (0, ui_generator_1.generateListPage)(modelInfo, sharedPackageName);
|
|
399
|
-
const detailPage = (0, ui_generator_1.generateDetailPage)(modelInfo, sharedPackageName);
|
|
886
|
+
const listPage = (0, ui_generator_1.generateListPage)(modelInfo, sharedPackageName, authOptions);
|
|
887
|
+
const detailPage = (0, ui_generator_1.generateDetailPage)(modelInfo, sharedPackageName, authOptions);
|
|
400
888
|
const formComponent = (0, ui_generator_1.generateFormComponent)(modelInfo, sharedPackageName);
|
|
401
|
-
const newPage = (0, ui_generator_1.generateNewPage)(modelInfo);
|
|
402
|
-
const editPage = (0, ui_generator_1.generateEditPage)(modelInfo, sharedPackageName);
|
|
889
|
+
const newPage = (0, ui_generator_1.generateNewPage)(modelInfo, authOptions);
|
|
890
|
+
const editPage = (0, ui_generator_1.generateEditPage)(modelInfo, sharedPackageName, authOptions);
|
|
403
891
|
fs.writeFileSync(path.join(modelDir, "page.tsx"), listPage, "utf-8");
|
|
404
892
|
fs.writeFileSync(path.join(idDir, "page.tsx"), detailPage, "utf-8");
|
|
405
893
|
fs.writeFileSync(path.join(componentsDir, `${modelName}Form.tsx`), formComponent, "utf-8");
|
|
@@ -454,17 +942,17 @@ async function updateNavigationMenu(modelInfo) {
|
|
|
454
942
|
label: modelInfo.displayName
|
|
455
943
|
});
|
|
456
944
|
// Generate new scaffold-config.ts content
|
|
457
|
-
const newConfigContent = `export interface ScaffoldModel {
|
|
458
|
-
name: string;
|
|
459
|
-
path: string;
|
|
460
|
-
label: string;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
export const scaffoldConfig = {
|
|
464
|
-
models: [
|
|
465
|
-
${models.map(m => ` { name: '${m.name}', path: '${m.path}', label: '${m.label}' },`).join('\n')}
|
|
466
|
-
] as ScaffoldModel[]
|
|
467
|
-
};
|
|
945
|
+
const newConfigContent = `export interface ScaffoldModel {
|
|
946
|
+
name: string;
|
|
947
|
+
path: string;
|
|
948
|
+
label: string;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
export const scaffoldConfig = {
|
|
952
|
+
models: [
|
|
953
|
+
${models.map(m => ` { name: '${m.name}', path: '${m.path}', label: '${m.label}' },`).join('\n')}
|
|
954
|
+
] as ScaffoldModel[]
|
|
955
|
+
};
|
|
468
956
|
`;
|
|
469
957
|
fs.writeFileSync(configPath, newConfigContent, "utf-8");
|
|
470
958
|
console.log(`✅ Added ${modelInfo.name} to navigation menu`);
|