swallowkit 1.0.0-beta.6 → 1.0.0-beta.60
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 +86 -177
- package/README.md +86 -178
- package/dist/__tests__/fixtures.d.ts +12 -0
- package/dist/__tests__/fixtures.d.ts.map +1 -1
- package/dist/__tests__/fixtures.js +101 -0
- package/dist/__tests__/fixtures.js.map +1 -1
- package/dist/cli/commands/add-auth.d.ts +15 -0
- package/dist/cli/commands/add-auth.d.ts.map +1 -0
- package/dist/cli/commands/add-auth.js +790 -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/cosmos-worktree-isolation.d.ts +25 -0
- package/dist/cli/commands/cosmos-worktree-isolation.d.ts.map +1 -0
- package/dist/cli/commands/cosmos-worktree-isolation.js +124 -0
- package/dist/cli/commands/cosmos-worktree-isolation.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 +19 -81
- package/dist/cli/commands/create-model.js.map +1 -1
- package/dist/cli/commands/dev-seeds.d.ts +62 -0
- package/dist/cli/commands/dev-seeds.d.ts.map +1 -0
- package/dist/cli/commands/dev-seeds.js +474 -0
- package/dist/cli/commands/dev-seeds.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +38 -0
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +733 -155
- 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 +78 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +1453 -898
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts +24 -0
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +473 -90
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/index.d.ts +5 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +333 -55
- package/dist/cli/index.js.map +1 -1
- package/dist/core/config.d.ts +14 -3
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +179 -8
- 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/auth-operations.d.ts +61 -0
- package/dist/core/operations/auth-operations.d.ts.map +1 -0
- package/dist/core/operations/auth-operations.js +272 -0
- package/dist/core/operations/auth-operations.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 +185 -0
- package/dist/core/operations/create-model.js.map +1 -0
- package/dist/core/operations/file-session.d.ts +56 -0
- package/dist/core/operations/file-session.d.ts.map +1 -0
- package/dist/core/operations/file-session.js +203 -0
- package/dist/core/operations/file-session.js.map +1 -0
- package/dist/core/operations/provision-operations.d.ts +52 -0
- package/dist/core/operations/provision-operations.d.ts.map +1 -0
- package/dist/core/operations/provision-operations.js +211 -0
- package/dist/core/operations/provision-operations.js.map +1 -0
- package/dist/core/operations/runtime.d.ts +37 -0
- package/dist/core/operations/runtime.d.ts.map +1 -0
- package/dist/core/operations/runtime.js +248 -0
- package/dist/core/operations/runtime.js.map +1 -0
- package/dist/core/operations/scaffold-apply.d.ts +21 -0
- package/dist/core/operations/scaffold-apply.d.ts.map +1 -0
- package/dist/core/operations/scaffold-apply.js +57 -0
- package/dist/core/operations/scaffold-apply.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 +51 -0
- package/dist/core/operations/scaffold-machine.js.map +1 -0
- package/dist/core/operations/scaffold-plan.d.ts +43 -0
- package/dist/core/operations/scaffold-plan.d.ts.map +1 -0
- package/dist/core/operations/scaffold-plan.js +187 -0
- package/dist/core/operations/scaffold-plan.js.map +1 -0
- package/dist/core/project/agent-instructions.d.ts +29 -0
- package/dist/core/project/agent-instructions.d.ts.map +1 -0
- package/dist/core/project/agent-instructions.js +159 -0
- package/dist/core/project/agent-instructions.js.map +1 -0
- package/dist/core/project/artifacts.d.ts +58 -0
- package/dist/core/project/artifacts.d.ts.map +1 -0
- package/dist/core/project/artifacts.js +179 -0
- package/dist/core/project/artifacts.js.map +1 -0
- package/dist/core/project/boundaries.d.ts +50 -0
- package/dist/core/project/boundaries.d.ts.map +1 -0
- package/dist/core/project/boundaries.js +117 -0
- package/dist/core/project/boundaries.js.map +1 -0
- package/dist/core/project/capabilities.d.ts +58 -0
- package/dist/core/project/capabilities.d.ts.map +1 -0
- package/dist/core/project/capabilities.js +135 -0
- package/dist/core/project/capabilities.js.map +1 -0
- package/dist/core/project/drift.d.ts +29 -0
- package/dist/core/project/drift.d.ts.map +1 -0
- package/dist/core/project/drift.js +188 -0
- package/dist/core/project/drift.js.map +1 -0
- package/dist/core/project/infra.d.ts +49 -0
- package/dist/core/project/infra.d.ts.map +1 -0
- package/dist/core/project/infra.js +167 -0
- package/dist/core/project/infra.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/state.d.ts +15 -0
- package/dist/core/project/state.d.ts.map +1 -0
- package/dist/core/project/state.js +106 -0
- package/dist/core/project/state.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 +274 -0
- package/dist/core/project/validation.js.map +1 -0
- package/dist/core/project/workflows.d.ts +43 -0
- package/dist/core/project/workflows.d.ts.map +1 -0
- package/dist/core/project/workflows.js +299 -0
- package/dist/core/project/workflows.js.map +1 -0
- package/dist/core/scaffold/auth-generator.d.ts +63 -0
- package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
- package/dist/core/scaffold/auth-generator.js +1774 -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 +1029 -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 +771 -69
- package/dist/core/scaffold/functions-generator.js.map +1 -1
- package/dist/core/scaffold/model-parser.d.ts +21 -1
- package/dist/core/scaffold/model-parser.d.ts.map +1 -1
- package/dist/core/scaffold/model-parser.js +243 -43
- package/dist/core/scaffold/model-parser.js.map +1 -1
- package/dist/core/scaffold/native-schema-generator.d.ts +19 -0
- package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -0
- package/dist/core/scaffold/native-schema-generator.js +753 -0
- package/dist/core/scaffold/native-schema-generator.js.map +1 -0
- 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 +153 -9
- 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 +17 -4
- package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
- package/dist/core/scaffold/ui-generator.js +174 -34
- package/dist/core/scaffold/ui-generator.js.map +1 -1
- package/dist/core/verify/index.d.ts +69 -0
- package/dist/core/verify/index.d.ts.map +1 -0
- package/dist/core/verify/index.js +412 -0
- package/dist/core/verify/index.js.map +1 -0
- 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 +24 -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 +14 -0
- package/dist/machine/errors.d.ts.map +1 -0
- package/dist/machine/errors.js +42 -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 +566 -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 +425 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/types/index.d.ts +109 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/package-manager.d.ts +5 -2
- package/dist/utils/package-manager.d.ts.map +1 -1
- package/dist/utils/package-manager.js +30 -9
- package/dist/utils/package-manager.js.map +1 -1
- package/dist/utils/python-uv.d.ts +21 -0
- package/dist/utils/python-uv.d.ts.map +1 -0
- package/dist/utils/python-uv.js +111 -0
- package/dist/utils/python-uv.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +49 -0
- package/dist/version.js.map +1 -0
- package/package.json +9 -5
- package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +1139 -0
- package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +206 -0
- package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +532 -0
- package/src/__tests__/agent-instructions.test.ts +237 -0
- package/src/__tests__/agent-loop-phase34.test.ts +513 -0
- package/src/__tests__/auth.test.ts +770 -0
- package/src/__tests__/bff-swa-auth.test.ts +275 -0
- package/src/__tests__/config.test.ts +295 -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__/cosmos-worktree-isolation.test.ts +149 -0
- package/src/__tests__/dev-seeds.test.ts +236 -0
- package/src/__tests__/dev.test.ts +296 -0
- package/src/__tests__/file-session.test.ts +98 -0
- package/src/__tests__/fixtures.ts +184 -0
- package/src/__tests__/functions-generator.test.ts +237 -0
- package/src/__tests__/init.test.ts +411 -0
- package/src/__tests__/jest-globals.d.ts +8 -0
- package/src/__tests__/machine.test.ts +371 -0
- package/src/__tests__/mcp.test.ts +253 -0
- package/src/__tests__/model-parser.test.ts +81 -0
- package/src/__tests__/multi-auth.test.ts +160 -0
- package/src/__tests__/nextjs-generator.test.ts +105 -0
- package/src/__tests__/openapi-generator.test.ts +43 -0
- package/src/__tests__/package-manager.test.ts +227 -0
- package/src/__tests__/plan-apply.test.ts +499 -0
- package/src/__tests__/python-uv.test.ts +48 -0
- package/src/__tests__/scaffold.test.ts +302 -0
- package/src/__tests__/string-utils.test.ts +75 -0
- package/src/__tests__/tsconfig.json +15 -0
- package/src/__tests__/ui-generator.test.ts +248 -0
- package/src/__tests__/zod-mock-generator.test.ts +132 -0
- package/src/cli/commands/add-auth.ts +853 -0
- package/src/cli/commands/add-connector.ts +158 -0
- package/src/cli/commands/cosmos-worktree-isolation.ts +130 -0
- package/src/cli/commands/create-model.ts +62 -0
- package/src/cli/commands/dev-seeds.ts +629 -0
- package/src/cli/commands/dev.ts +1252 -0
- package/src/cli/commands/index.ts +9 -0
- package/src/cli/commands/init.ts +3541 -0
- package/src/cli/commands/provision.ts +193 -0
- package/src/cli/commands/scaffold.ts +1081 -0
- package/src/cli/index.ts +350 -0
- package/src/core/config.ts +380 -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/auth-operations.ts +360 -0
- package/src/core/operations/create-model.ts +191 -0
- package/src/core/operations/file-session.ts +222 -0
- package/src/core/operations/provision-operations.ts +297 -0
- package/src/core/operations/runtime.ts +258 -0
- package/src/core/operations/scaffold-apply.ts +106 -0
- package/src/core/operations/scaffold-machine.ts +77 -0
- package/src/core/operations/scaffold-plan.ts +215 -0
- package/src/core/project/agent-instructions.ts +167 -0
- package/src/core/project/agent-templates/AGENTS.md +502 -0
- package/src/core/project/agent-templates/CLAUDE.md +31 -0
- package/src/core/project/agent-templates/azure-functions.instructions.md +22 -0
- package/src/core/project/agent-templates/bff-routes.instructions.md +18 -0
- package/src/core/project/agent-templates/copilot-instructions.md +27 -0
- package/src/core/project/agent-templates/shared-models.instructions.md +72 -0
- package/src/core/project/artifacts.ts +205 -0
- package/src/core/project/boundaries.ts +170 -0
- package/src/core/project/capabilities.ts +193 -0
- package/src/core/project/drift.ts +202 -0
- package/src/core/project/infra.ts +196 -0
- package/src/core/project/manifest.ts +402 -0
- package/src/core/project/state.ts +77 -0
- package/src/core/project/validation.ts +295 -0
- package/src/core/project/workflows.ts +295 -0
- package/src/core/scaffold/auth-generator.ts +1824 -0
- package/src/core/scaffold/connector-functions-generator.ts +1133 -0
- package/src/core/scaffold/functions-generator.ts +972 -0
- package/src/core/scaffold/model-parser.ts +854 -0
- package/src/core/scaffold/native-schema-generator.ts +904 -0
- package/src/core/scaffold/nextjs-generator.ts +382 -0
- package/src/core/scaffold/openapi-generator.ts +212 -0
- package/src/core/scaffold/ui-generator.ts +1100 -0
- package/src/core/verify/index.ts +468 -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 +33 -0
- package/src/machine/errors.ts +45 -0
- package/src/machine/index.ts +690 -0
- package/src/mcp/index.ts +423 -0
- package/src/types/index.ts +182 -0
- package/src/utils/package-manager.ts +246 -0
- package/src/utils/python-uv.ts +96 -0
- package/src/version.ts +13 -0
|
@@ -38,17 +38,51 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
38
38
|
})();
|
|
39
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
40
|
exports.scaffoldCommand = scaffoldCommand;
|
|
41
|
+
exports.resolveAuthPolicy = resolveAuthPolicy;
|
|
42
|
+
exports.resolveModelPath = resolveModelPath;
|
|
43
|
+
exports.createUIAuthOptions = createUIAuthOptions;
|
|
44
|
+
exports.generateCosmosContainer = generateCosmosContainer;
|
|
45
|
+
exports.generateUIComponents = generateUIComponents;
|
|
41
46
|
const fs = __importStar(require("fs"));
|
|
42
47
|
const path = __importStar(require("path"));
|
|
48
|
+
const child_process_1 = require("child_process");
|
|
49
|
+
const config_1 = require("../../core/config");
|
|
43
50
|
const model_parser_1 = require("../../core/scaffold/model-parser");
|
|
44
51
|
const functions_generator_1 = require("../../core/scaffold/functions-generator");
|
|
52
|
+
const connector_functions_generator_1 = require("../../core/scaffold/connector-functions-generator");
|
|
45
53
|
const nextjs_generator_1 = require("../../core/scaffold/nextjs-generator");
|
|
46
54
|
const ui_generator_1 = require("../../core/scaffold/ui-generator");
|
|
47
|
-
const
|
|
55
|
+
const native_schema_generator_1 = require("../../core/scaffold/native-schema-generator");
|
|
48
56
|
const package_manager_1 = require("../../utils/package-manager");
|
|
57
|
+
const manifest_1 = require("../../core/project/manifest");
|
|
58
|
+
const auth_generator_1 = require("../../core/scaffold/auth-generator");
|
|
59
|
+
const file_session_1 = require("../../core/operations/file-session");
|
|
60
|
+
const artifacts_1 = require("../../core/project/artifacts");
|
|
61
|
+
const SCAFFOLD_GENERATOR = "scaffold";
|
|
62
|
+
function getMachineAwareStdio() {
|
|
63
|
+
return process.env.SWALLOWKIT_MACHINE_OUTPUT === "1" ? "pipe" : "inherit";
|
|
64
|
+
}
|
|
65
|
+
function runSpawnSyncCommand(command, args, cwd) {
|
|
66
|
+
const result = (0, child_process_1.spawnSync)(command, args, {
|
|
67
|
+
cwd,
|
|
68
|
+
stdio: getMachineAwareStdio(),
|
|
69
|
+
shell: true,
|
|
70
|
+
});
|
|
71
|
+
if (typeof result.status === "number" && result.status !== 0) {
|
|
72
|
+
throw new Error(`${command} ${args.join(" ")} exited with code ${result.status}`);
|
|
73
|
+
}
|
|
74
|
+
if (result.error) {
|
|
75
|
+
throw result.error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
49
78
|
async function scaffoldCommand(options) {
|
|
50
79
|
// SwallowKit プロジェクトディレクトリかどうかを検証
|
|
51
80
|
(0, config_1.ensureSwallowKitProject)("scaffold");
|
|
81
|
+
const configValidation = (0, config_1.validateConfig)((0, config_1.getFullConfig)());
|
|
82
|
+
if (!configValidation.valid)
|
|
83
|
+
throw new Error(`Invalid SwallowKit configuration:\n${configValidation.errors.map(error => `- ${error}`).join("\n")}`);
|
|
84
|
+
// Plan(dry-run) や Apply からセッションが供給される。単体実行時は commit セッションを生成。
|
|
85
|
+
const session = (0, file_session_1.getActiveFileSession)() ?? new file_session_1.FileOperationSession("commit");
|
|
52
86
|
console.log("🏗️ SwallowKit Scaffold: Generating CRUD operations...\n");
|
|
53
87
|
try {
|
|
54
88
|
// 1. Resolve model file path
|
|
@@ -58,6 +92,14 @@ async function scaffoldCommand(options) {
|
|
|
58
92
|
console.log("🔍 Parsing model file...");
|
|
59
93
|
const modelInfo = await (0, model_parser_1.parseModelFile)(modelPath);
|
|
60
94
|
console.log(`✅ Model parsed: ${modelInfo.name} (${modelInfo.schemaName})`);
|
|
95
|
+
const backendLanguage = (0, config_1.getBackendLanguage)();
|
|
96
|
+
console.log(`🧠 Backend language: ${backendLanguage}`);
|
|
97
|
+
// コネクタモデルかどうかを判定
|
|
98
|
+
const isConnectorModel = !!modelInfo.connectorConfig;
|
|
99
|
+
if (isConnectorModel) {
|
|
100
|
+
console.log(`🔌 Connector model detected: ${modelInfo.connectorConfig.connector}`);
|
|
101
|
+
console.log(` Operations: ${modelInfo.connectorConfig.operations.join(", ")}`);
|
|
102
|
+
}
|
|
61
103
|
// ネストスキーマ参照があれば表示
|
|
62
104
|
if (modelInfo.nestedSchemaRefs.length > 0) {
|
|
63
105
|
console.log(`🔗 Nested schema references detected:`);
|
|
@@ -74,36 +116,135 @@ async function scaffoldCommand(options) {
|
|
|
74
116
|
// 4. Read shared package name
|
|
75
117
|
const functionsDir = options.functionsDir || "functions";
|
|
76
118
|
const sharedPackageName = readSharedPackageName();
|
|
119
|
+
const relatedModels = backendLanguage === "typescript"
|
|
120
|
+
? [modelInfo]
|
|
121
|
+
: await collectModelGraph(modelPath);
|
|
122
|
+
for (const relatedModel of relatedModels) {
|
|
123
|
+
session.registerInput(relatedModel.filePath);
|
|
124
|
+
}
|
|
125
|
+
// 4b. 書き込み前に外部 codegen toolchain を検証し、失敗時はプロジェクト無変更で終了する
|
|
126
|
+
if (session.mode === "commit" && backendLanguage !== "typescript" && !isConnectorModel) {
|
|
127
|
+
await (0, native_schema_generator_1.validateNativeSchemaToolchain)(relatedModels, modelInfo, backendLanguage, path.join(process.cwd(), functionsDir));
|
|
128
|
+
}
|
|
129
|
+
if (options.apiOnly) {
|
|
130
|
+
console.log("ℹ️ --api-only skips UI generation only; Functions, BFF routes, OpenAPI, and native schema assets are still updated.");
|
|
131
|
+
}
|
|
77
132
|
// 5. Generate BFF callFunction helper
|
|
78
|
-
await generateCallFunctionHelper();
|
|
133
|
+
await generateCallFunctionHelper(session);
|
|
79
134
|
// 6. Generate Azure Functions code
|
|
80
|
-
|
|
135
|
+
if (isConnectorModel) {
|
|
136
|
+
await generateConnectorFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage, session);
|
|
137
|
+
// 6b. Install connector driver dependencies
|
|
138
|
+
await installConnectorDriverDependencies(modelInfo, functionsDir, backendLanguage, session);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
await generateFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage, session);
|
|
142
|
+
if (backendLanguage !== "typescript") {
|
|
143
|
+
await (0, native_schema_generator_1.generateLanguageSchemaArtifacts)(relatedModels, modelInfo, functionsDir, backendLanguage, session);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
81
146
|
// 7. Generate Next.js BFF API Routes
|
|
82
147
|
const apiDir = options.apiDir || "app/api";
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
148
|
+
if (isConnectorModel) {
|
|
149
|
+
await generateConnectorBFFRoutesFiles(modelInfo, apiDir, sharedPackageName, session);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
await generateBFFRoutes(modelInfo, apiDir, sharedPackageName, session);
|
|
153
|
+
}
|
|
154
|
+
// 8. Generate Cosmos DB container Bicep file (skip for connector models)
|
|
155
|
+
if (!isConnectorModel) {
|
|
156
|
+
await generateCosmosContainer(modelInfo, session);
|
|
157
|
+
}
|
|
86
158
|
// 9. Generate UI components (unless --api-only)
|
|
87
159
|
if (!options.apiOnly) {
|
|
88
|
-
|
|
89
|
-
|
|
160
|
+
const uiAuthConfig = (0, config_1.getAuthConfig)();
|
|
161
|
+
const uiAuthPolicy = resolveAuthPolicy(modelInfo, uiAuthConfig);
|
|
162
|
+
const uiAuthOptions = uiAuthPolicy && uiAuthConfig && uiAuthConfig.provider !== 'none'
|
|
163
|
+
? createUIAuthOptions(uiAuthPolicy, uiAuthConfig)
|
|
164
|
+
: undefined;
|
|
165
|
+
await generateUIComponents(modelInfo, sharedPackageName, uiAuthOptions, session);
|
|
166
|
+
if (uiAuthOptions?.authenticatedFetchImport) {
|
|
167
|
+
applyExternalTokenFetchToGeneratedUi(modelInfo, session, uiAuthOptions.authenticatedFetchImport);
|
|
168
|
+
}
|
|
169
|
+
await updateNavigationMenu(modelInfo, session);
|
|
170
|
+
}
|
|
171
|
+
if (session.mode === "commit") {
|
|
172
|
+
(0, artifacts_1.recordSessionOperations)(session.operations, {
|
|
173
|
+
schemaHash: (0, file_session_1.hashFileIfExists)(modelPath) ?? undefined,
|
|
174
|
+
sourceModel: modelInfo.name,
|
|
175
|
+
});
|
|
176
|
+
await (0, manifest_1.syncProjectManifest)();
|
|
90
177
|
}
|
|
91
178
|
console.log("\n✅ Scaffold completed successfully!");
|
|
92
179
|
console.log("\n📝 Next steps:");
|
|
93
|
-
console.log(` 1. Review generated files in ${functionsDir}
|
|
180
|
+
console.log(` 1. Review generated files in ${describeFunctionsOutputPath(functionsDir, backendLanguage)} and ${apiDir}/`);
|
|
94
181
|
if (!options.apiOnly) {
|
|
95
182
|
console.log(` 2. Check the generated UI pages in app/${(0, model_parser_1.toKebabCase)(modelInfo.name)}/`);
|
|
96
183
|
console.log(" 3. Navigate to the model from the homepage menu");
|
|
97
184
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
185
|
+
if (backendLanguage !== "typescript") {
|
|
186
|
+
console.log(` ${options.apiOnly ? "2" : "4"}. Review generated OpenAPI export and native schema assets in ${functionsDir}/openapi/ and ${functionsDir}/generated/`);
|
|
187
|
+
}
|
|
188
|
+
console.log(` ${options.apiOnly ? (backendLanguage === "typescript" ? "2" : "3") : (backendLanguage === "typescript" ? "4" : "5")}. Ensure BACKEND_FUNCTIONS_BASE_URL is set in your .env.local file`);
|
|
189
|
+
console.log(` ${options.apiOnly ? (backendLanguage === "typescript" ? "3" : "4") : (backendLanguage === "typescript" ? "5" : "6")}. Configure CosmosDBConnection in functions/local.settings.json`);
|
|
190
|
+
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`);
|
|
101
191
|
}
|
|
102
192
|
catch (error) {
|
|
103
193
|
console.error("\n❌ Scaffold failed:", error.message);
|
|
104
194
|
process.exit(1);
|
|
105
195
|
}
|
|
106
196
|
}
|
|
197
|
+
function applyExternalTokenFetchToGeneratedUi(modelInfo, session, importPath = '@/lib/auth/authenticated-fetch') {
|
|
198
|
+
const uiPrefix = `app/${(0, model_parser_1.toKebabCase)(modelInfo.name)}/`;
|
|
199
|
+
for (const operation of session.operations) {
|
|
200
|
+
if (!operation.path.startsWith(uiPrefix) || !operation.path.endsWith('.tsx') || operation.action === 'delete')
|
|
201
|
+
continue;
|
|
202
|
+
let content = session.readFile(operation.absolutePath);
|
|
203
|
+
if (!content.includes('fetch('))
|
|
204
|
+
continue;
|
|
205
|
+
content = content.replace(/\bfetch\(/g, 'authenticatedFetch(');
|
|
206
|
+
content = content.replace(/(if \(!res\.ok\) throw new Error\([^\n]+\);)/g, `if (res.status === 401) throw new Error('Authentication required');\n $1`);
|
|
207
|
+
if (!content.includes(importPath)) {
|
|
208
|
+
const directive = content.match(/^'use client';\s*\r?\n/)?.[0] ?? '';
|
|
209
|
+
content = `${directive}import { authenticatedFetch } from '${importPath}';\n${content.slice(directive.length)}`;
|
|
210
|
+
}
|
|
211
|
+
session.writeFile(operation.absolutePath, content, {
|
|
212
|
+
ownership: 'managed',
|
|
213
|
+
generator: SCAFFOLD_GENERATOR,
|
|
214
|
+
sourceModel: modelInfo.name,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* モデルの authPolicy と auth config から実効的な認可ポリシーを解決
|
|
220
|
+
* - モデルに authPolicy があればそれを使用
|
|
221
|
+
* - なければ auth.authorization.defaultPolicy に従う
|
|
222
|
+
* - auth 設定がなければ undefined(ガードなし)
|
|
223
|
+
*/
|
|
224
|
+
function resolveAuthPolicy(modelInfo, authConfig) {
|
|
225
|
+
// モデルに明示的な authPolicy がある場合はそれを使用
|
|
226
|
+
if (modelInfo.authPolicy) {
|
|
227
|
+
const normalized = (0, config_1.normalizeAuthConfig)(authConfig);
|
|
228
|
+
for (const policyName of [modelInfo.authPolicy.policy, typeof modelInfo.authPolicy.read === 'string' ? modelInfo.authPolicy.read : undefined, typeof modelInfo.authPolicy.write === 'string' ? modelInfo.authPolicy.write : undefined].filter((value) => Boolean(value))) {
|
|
229
|
+
if (!normalized?.authorization.policies[policyName])
|
|
230
|
+
throw new Error(`Model '${modelInfo.name}' references undefined auth policy '${policyName}'. Define auth.authorization.policies.${policyName}.`);
|
|
231
|
+
}
|
|
232
|
+
return modelInfo.authPolicy;
|
|
233
|
+
}
|
|
234
|
+
// auth 設定がない場合はガードなし
|
|
235
|
+
if (!authConfig || authConfig.provider === 'none') {
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
// defaultPolicy が 'authenticated' なら認証のみ(ロール指定なし)のポリシーを返す
|
|
239
|
+
const defaultPolicy = authConfig.authorization?.defaultPolicy === 'public' ? 'anonymous' : authConfig.authorization?.defaultPolicy ?? 'authenticated';
|
|
240
|
+
if (defaultPolicy === 'authenticated') {
|
|
241
|
+
return {}; // 空のポリシー = 認証のみ、ロール制限なし
|
|
242
|
+
}
|
|
243
|
+
if (defaultPolicy !== 'anonymous')
|
|
244
|
+
return { policy: defaultPolicy };
|
|
245
|
+
// defaultPolicy が 'anonymous' なら認証不要
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
107
248
|
/**
|
|
108
249
|
* モデルファイルのパスを解決
|
|
109
250
|
*/
|
|
@@ -161,68 +302,326 @@ function readSharedPackageName() {
|
|
|
161
302
|
/**
|
|
162
303
|
* Generate BFF callFunction helper (lib/api/call-function.ts)
|
|
163
304
|
*/
|
|
164
|
-
async function generateCallFunctionHelper() {
|
|
305
|
+
async function generateCallFunctionHelper(session) {
|
|
165
306
|
console.log("\n📦 Generating BFF callFunction helper...");
|
|
166
307
|
const cwd = process.cwd();
|
|
167
308
|
const helperDir = path.join(cwd, "lib", "api");
|
|
168
309
|
const helperPath = path.join(helperDir, "call-function.ts");
|
|
169
|
-
|
|
170
|
-
|
|
310
|
+
// auth 設定がある場合は Authorization ヘッダー転送版を生成
|
|
311
|
+
const authConfig = (0, config_1.getAuthConfig)();
|
|
312
|
+
const normalizedAuth = (0, config_1.normalizeAuthConfig)(authConfig);
|
|
313
|
+
const hasAuth = Boolean(normalizedAuth && Object.keys(normalizedAuth.schemes).length > 0);
|
|
314
|
+
let helperCode;
|
|
315
|
+
if (hasAuth) {
|
|
316
|
+
const providers = new Set(Object.values(normalizedAuth.schemes).map(scheme => scheme.provider));
|
|
317
|
+
helperCode = normalizedAuth.schemes.default && Object.keys(normalizedAuth.schemes).length === 1
|
|
318
|
+
? authConfig?.provider === "swa" ? (0, auth_generator_1.generateBFFCallFunctionWithSwaAuth)() : (0, auth_generator_1.generateBFFCallFunctionWithAuth)()
|
|
319
|
+
: providers.has("swa") ? (0, auth_generator_1.generateBFFCallFunctionWithMultipleAuth)() : (0, auth_generator_1.generateBFFCallFunctionWithAuth)();
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
helperCode = (0, nextjs_generator_1.generateBFFCallFunction)();
|
|
171
323
|
}
|
|
172
|
-
|
|
173
|
-
fs.writeFileSync(helperPath, helperCode, "utf-8");
|
|
324
|
+
session.writeFile(helperPath, helperCode, { ownership: "managed", generator: SCAFFOLD_GENERATOR });
|
|
174
325
|
console.log(`✅ Created: ${helperPath}`);
|
|
175
326
|
}
|
|
176
327
|
/**
|
|
177
328
|
* Generate Azure Functions CRUD code
|
|
178
329
|
*/
|
|
179
|
-
async function generateFunctionsCode(modelInfo, functionsDir, sharedPackageName) {
|
|
330
|
+
async function generateFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage, session) {
|
|
180
331
|
console.log("\n🔨 Generating Azure Functions CRUD code...");
|
|
332
|
+
// Resolve auth policy: model-level authPolicy or global defaultPolicy
|
|
333
|
+
const authConfig = (0, config_1.getAuthConfig)();
|
|
334
|
+
const authPolicy = resolveAuthPolicy(modelInfo, authConfig);
|
|
335
|
+
if (authPolicy) {
|
|
336
|
+
console.log(`🔐 Auth policy detected: ${JSON.stringify(authPolicy)}`);
|
|
337
|
+
}
|
|
181
338
|
const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
339
|
+
const managedMeta = { ownership: "managed", generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name };
|
|
340
|
+
if (backendLanguage === "typescript") {
|
|
341
|
+
const normalizedAuth = (0, config_1.getNormalizedAuthConfig)();
|
|
342
|
+
if (normalizedAuth && Object.keys(normalizedAuth.schemes).some(name => name !== "default")) {
|
|
343
|
+
const routerPath = path.join(process.cwd(), functionsDir, "src", "auth", "auth-router.ts");
|
|
344
|
+
session.writeFile(routerPath, (0, auth_generator_1.generateNamedAuthRouterTS)(normalizedAuth), { ownership: "managed", generator: SCAFFOLD_GENERATOR });
|
|
345
|
+
}
|
|
346
|
+
const functionFilePath = path.join(process.cwd(), functionsDir, "src", `${modelKebab}.ts`);
|
|
347
|
+
const code = (0, functions_generator_1.generateCompactAzureFunctionsCRUD)(modelInfo, sharedPackageName, authPolicy, authConfig?.provider);
|
|
348
|
+
session.writeFile(functionFilePath, code, managedMeta);
|
|
349
|
+
console.log(`✅ Created: ${functionFilePath}`);
|
|
350
|
+
return;
|
|
187
351
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
352
|
+
if (backendLanguage === "csharp") {
|
|
353
|
+
const normalizedAuth = (0, config_1.getNormalizedAuthConfig)();
|
|
354
|
+
if (normalizedAuth && Object.keys(normalizedAuth.schemes).some(name => name !== "default")) {
|
|
355
|
+
const routerPath = path.join(process.cwd(), functionsDir, "Auth", "AuthRouter.cs");
|
|
356
|
+
session.writeFile(routerPath, (0, auth_generator_1.generateNamedAuthRouterCSharp)(normalizedAuth), { ownership: "managed", generator: SCAFFOLD_GENERATOR });
|
|
357
|
+
}
|
|
358
|
+
const crudDir = path.join(process.cwd(), functionsDir, "Crud");
|
|
359
|
+
const functionFilePath = path.join(crudDir, `${modelInfo.name}CrudFunctions.cs`);
|
|
360
|
+
// Remove init-generated template (singular) to avoid route conflicts
|
|
361
|
+
const templatePath = path.join(crudDir, `${modelInfo.name}Function.cs`);
|
|
362
|
+
if (session.fileExists(templatePath)) {
|
|
363
|
+
session.deleteFile(templatePath, managedMeta);
|
|
364
|
+
console.log(`🗑️ Removed template: ${templatePath}`);
|
|
365
|
+
}
|
|
366
|
+
session.writeFile(functionFilePath, (0, functions_generator_1.generateCSharpAzureFunctionsCRUD)(modelInfo, authPolicy), managedMeta);
|
|
367
|
+
console.log(`✅ Created: ${functionFilePath}`);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const normalizedAuth = (0, config_1.getNormalizedAuthConfig)();
|
|
371
|
+
if (normalizedAuth && Object.keys(normalizedAuth.schemes).some(name => name !== "default")) {
|
|
372
|
+
const routerPath = path.join(process.cwd(), functionsDir, "auth", "auth_router.py");
|
|
373
|
+
session.writeFile(routerPath, (0, auth_generator_1.generateNamedAuthRouterPython)(normalizedAuth), { ownership: "managed", generator: SCAFFOLD_GENERATOR });
|
|
374
|
+
}
|
|
375
|
+
const blueprintsDir = path.join(process.cwd(), functionsDir, "blueprints");
|
|
376
|
+
const blueprintPath = path.join(blueprintsDir, `${modelKebab.replace(/-/g, "_")}.py`);
|
|
377
|
+
const { blueprint, registration } = (0, functions_generator_1.generatePythonAzureFunctionsCRUD)(modelInfo, authPolicy);
|
|
378
|
+
session.writeFile(blueprintPath, blueprint, managedMeta);
|
|
379
|
+
updatePythonFunctionRegistrations(path.join(process.cwd(), functionsDir, "function_app.py"), registration, session);
|
|
380
|
+
console.log(`✅ Created: ${blueprintPath}`);
|
|
381
|
+
}
|
|
382
|
+
function createUIAuthOptions(policy, authConfig) {
|
|
383
|
+
const normalized = (0, config_1.normalizeAuthConfig)(authConfig);
|
|
384
|
+
const readPolicyName = typeof policy.read === 'string' ? policy.read : policy.policy;
|
|
385
|
+
const writePolicyName = typeof policy.write === 'string' ? policy.write : policy.policy;
|
|
386
|
+
const readNamed = readPolicyName ? normalized?.authorization.policies[readPolicyName] : undefined;
|
|
387
|
+
const named = writePolicyName ? normalized?.authorization.policies[writePolicyName] : undefined;
|
|
388
|
+
const writeRoles = named?.roles ?? (Array.isArray(policy.write) ? policy.write : policy.roles) ?? [];
|
|
389
|
+
const externalScheme = [...(readNamed?.schemes ?? []), ...(named?.schemes ?? [])].find(name => normalized?.schemes[name]?.provider === 'external-token');
|
|
390
|
+
const authScheme = readNamed?.schemes[0] ?? named?.schemes[0];
|
|
391
|
+
const authenticatedFetchImport = externalScheme ? `@/lib/auth/schemes/${(0, model_parser_1.toKebabCase)(externalScheme)}/authenticated-fetch` : authConfig.provider === 'external-token' ? '@/lib/auth/authenticated-fetch' : undefined;
|
|
392
|
+
const authContextImport = authScheme ? `@/lib/auth/schemes/${(0, model_parser_1.toKebabCase)(authScheme)}/auth-context` : '@/lib/auth/auth-context';
|
|
393
|
+
return { authPolicy: policy, writeRoles, authenticatedFetchImport, authContextImport };
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* コネクタモデル用 Azure Functions コード生成
|
|
397
|
+
*/
|
|
398
|
+
async function generateConnectorFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage, session) {
|
|
399
|
+
console.log("\n🔌 Generating Connector Azure Functions code...");
|
|
400
|
+
const connectorConfig = modelInfo.connectorConfig;
|
|
401
|
+
const connectorDef = (0, config_1.getConnectorDefinition)(connectorConfig.connector);
|
|
402
|
+
if (!connectorDef) {
|
|
403
|
+
throw new Error(`Connector '${connectorConfig.connector}' not found in swallowkit.config.js.\n` +
|
|
404
|
+
` Please add it to the 'connectors' section of your configuration.`);
|
|
405
|
+
}
|
|
406
|
+
// Resolve auth policy (same logic as Cosmos model scaffolding)
|
|
407
|
+
const authConfig = (0, config_1.getAuthConfig)();
|
|
408
|
+
const authPolicy = resolveAuthPolicy(modelInfo, authConfig);
|
|
409
|
+
if (authPolicy) {
|
|
410
|
+
console.log(`🔐 Auth policy detected: ${JSON.stringify(authPolicy)}`);
|
|
411
|
+
}
|
|
412
|
+
const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
|
|
413
|
+
const connectorManagedMeta = { ownership: "managed", generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name };
|
|
414
|
+
if (backendLanguage === "typescript") {
|
|
415
|
+
const functionFilePath = path.join(process.cwd(), functionsDir, "src", `${modelKebab}.ts`);
|
|
416
|
+
let code;
|
|
417
|
+
if (connectorDef.type === "rdb") {
|
|
418
|
+
code = (0, connector_functions_generator_1.generateRdbConnectorFunctionTS)(modelInfo, sharedPackageName, connectorDef, connectorConfig, authPolicy, authConfig?.provider);
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
code = (0, connector_functions_generator_1.generateApiConnectorFunctionTS)(modelInfo, sharedPackageName, connectorDef, connectorConfig, authPolicy, authConfig?.provider);
|
|
422
|
+
}
|
|
423
|
+
session.writeFile(functionFilePath, code, connectorManagedMeta);
|
|
424
|
+
console.log(`✅ Created: ${functionFilePath}`);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
if (backendLanguage === "csharp") {
|
|
428
|
+
const functionFilePath = path.join(process.cwd(), functionsDir, "Connectors", `${modelInfo.name}ConnectorFunctions.cs`);
|
|
429
|
+
let code;
|
|
430
|
+
if (connectorDef.type === "rdb") {
|
|
431
|
+
code = (0, connector_functions_generator_1.generateRdbConnectorFunctionCSharp)(modelInfo, connectorDef, connectorConfig);
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
code = (0, connector_functions_generator_1.generateApiConnectorFunctionCSharp)(modelInfo, connectorDef, connectorConfig);
|
|
435
|
+
}
|
|
436
|
+
session.writeFile(functionFilePath, code, connectorManagedMeta);
|
|
437
|
+
console.log(`✅ Created: ${functionFilePath}`);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
// Python
|
|
441
|
+
const blueprintsDir = path.join(process.cwd(), functionsDir, "blueprints");
|
|
442
|
+
const blueprintPath = path.join(blueprintsDir, `${modelKebab.replace(/-/g, "_")}.py`);
|
|
443
|
+
let result;
|
|
444
|
+
if (connectorDef.type === "rdb") {
|
|
445
|
+
result = (0, connector_functions_generator_1.generateRdbConnectorFunctionPython)(modelInfo, connectorDef, connectorConfig);
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
result = (0, connector_functions_generator_1.generateApiConnectorFunctionPython)(modelInfo, connectorDef, connectorConfig);
|
|
449
|
+
}
|
|
450
|
+
session.writeFile(blueprintPath, result.blueprint, connectorManagedMeta);
|
|
451
|
+
updatePythonFunctionRegistrations(path.join(process.cwd(), functionsDir, "function_app.py"), result.registration, session);
|
|
452
|
+
console.log(`✅ Created: ${blueprintPath}`);
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* コネクタモデルの scaffold 後に、生成コードが必要とする RDB/API ドライバを
|
|
456
|
+
* functions ディレクトリにインストールする。
|
|
457
|
+
*/
|
|
458
|
+
async function installConnectorDriverDependencies(modelInfo, functionsDir, backendLanguage, session) {
|
|
459
|
+
const connectorConfig = modelInfo.connectorConfig;
|
|
460
|
+
const connectorDef = (0, config_1.getConnectorDefinition)(connectorConfig.connector);
|
|
461
|
+
if (!connectorDef || connectorDef.type !== "rdb")
|
|
462
|
+
return;
|
|
463
|
+
const rdbDef = connectorDef;
|
|
464
|
+
const functionsPath = path.join(process.cwd(), functionsDir);
|
|
465
|
+
if (backendLanguage === "typescript") {
|
|
466
|
+
const driverMap = {
|
|
467
|
+
mysql: { deps: ["mysql2"], devDeps: [] },
|
|
468
|
+
postgres: { deps: ["pg"], devDeps: ["@types/pg"] },
|
|
469
|
+
sqlserver: { deps: ["mssql"], devDeps: [] },
|
|
470
|
+
};
|
|
471
|
+
const entry = driverMap[rdbDef.provider];
|
|
472
|
+
if (!entry)
|
|
473
|
+
return;
|
|
474
|
+
// package.json を読んで、既にインストール済みならスキップ
|
|
475
|
+
const pkgJsonPath = path.join(functionsPath, "package.json");
|
|
476
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
477
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
|
|
478
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
479
|
+
const missingDeps = entry.deps.filter(d => !allDeps[d]);
|
|
480
|
+
const missingDevDeps = entry.devDeps.filter(d => !allDeps[d]);
|
|
481
|
+
if (missingDeps.length === 0 && missingDevDeps.length === 0)
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (session.mode === "collect") {
|
|
485
|
+
session.addWarning(`external-install: ${rdbDef.provider} driver dependencies (${[...entry.deps, ...entry.devDeps].join(", ")}) will be installed into ${functionsDir}/ on apply.`);
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
console.log(`\n📦 Installing ${rdbDef.provider} driver dependencies...`);
|
|
489
|
+
const pm = (0, package_manager_1.detectFromProject)(functionsPath);
|
|
490
|
+
const cmds = (0, package_manager_1.getCommands)(pm);
|
|
491
|
+
if (entry.deps.length > 0) {
|
|
492
|
+
runSpawnSyncCommand(cmds.name, [pm === "pnpm" ? "add" : "install", ...entry.deps], functionsPath);
|
|
493
|
+
}
|
|
494
|
+
if (entry.devDeps.length > 0) {
|
|
495
|
+
runSpawnSyncCommand(cmds.name, [pm === "pnpm" ? "add" : "install", "-D", ...entry.devDeps], functionsPath);
|
|
496
|
+
}
|
|
497
|
+
console.log(`✅ ${rdbDef.provider} driver installed`);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (backendLanguage === "csharp") {
|
|
501
|
+
const nugetMap = {
|
|
502
|
+
mysql: "MySqlConnector",
|
|
503
|
+
postgres: "Npgsql",
|
|
504
|
+
sqlserver: "Microsoft.Data.SqlClient",
|
|
505
|
+
};
|
|
506
|
+
const pkg = nugetMap[rdbDef.provider];
|
|
507
|
+
if (!pkg)
|
|
508
|
+
return;
|
|
509
|
+
if (session.mode === "collect") {
|
|
510
|
+
session.addWarning(`external-install: NuGet package ${pkg} will be added to ${functionsDir}/functions.csproj on apply.`);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
console.log(`\n📦 Installing ${rdbDef.provider} NuGet package...`);
|
|
514
|
+
runSpawnSyncCommand("dotnet", ["add", path.join(functionsPath, "functions.csproj"), "package", pkg], functionsPath);
|
|
515
|
+
console.log(`✅ ${pkg} installed`);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
// Python — requirements.txt に追記
|
|
519
|
+
const pipMap = {
|
|
520
|
+
mysql: "mysql-connector-python",
|
|
521
|
+
postgres: "psycopg2-binary",
|
|
522
|
+
sqlserver: "pymssql",
|
|
523
|
+
};
|
|
524
|
+
const pipPkg = pipMap[rdbDef.provider];
|
|
525
|
+
if (!pipPkg)
|
|
526
|
+
return;
|
|
527
|
+
const reqPath = path.join(functionsPath, "requirements.txt");
|
|
528
|
+
if (session.fileExists(reqPath)) {
|
|
529
|
+
const existing = session.readFile(reqPath);
|
|
530
|
+
if (!existing.includes(pipPkg)) {
|
|
531
|
+
console.log(`\n📦 Adding ${pipPkg} to requirements.txt...`);
|
|
532
|
+
session.writeFile(reqPath, `${existing}\n${pipPkg}\n`, { ownership: "extension-point", generator: SCAFFOLD_GENERATOR });
|
|
533
|
+
console.log(`✅ ${pipPkg} added`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* コネクタモデル用 BFF ルート生成(操作制限に対応)
|
|
539
|
+
*/
|
|
540
|
+
async function generateConnectorBFFRoutesFiles(modelInfo, apiDir, sharedPackageName, session) {
|
|
541
|
+
console.log("\n🔌 Generating Connector BFF API routes...");
|
|
542
|
+
const modelCamel = (0, model_parser_1.toCamelCase)(modelInfo.name);
|
|
543
|
+
const operations = modelInfo.connectorConfig.operations;
|
|
544
|
+
const listRoutePath = path.join(process.cwd(), apiDir, modelCamel, "route.ts");
|
|
545
|
+
const detailRoutePath = path.join(process.cwd(), apiDir, modelCamel, "[id]", "route.ts");
|
|
546
|
+
const routes = (0, nextjs_generator_1.generateConnectorBFFRoutes)(modelInfo, sharedPackageName, operations);
|
|
547
|
+
const meta = { ownership: "managed", generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name };
|
|
548
|
+
session.writeFile(listRoutePath, routes.listRoute, meta);
|
|
549
|
+
session.writeFile(detailRoutePath, routes.detailRoute, meta);
|
|
550
|
+
console.log(`✅ Created: ${listRoutePath}`);
|
|
551
|
+
console.log(`✅ Created: ${detailRoutePath}`);
|
|
552
|
+
}
|
|
553
|
+
async function collectModelGraph(modelPath, seen = new Map()) {
|
|
554
|
+
const resolvedPath = path.resolve(modelPath);
|
|
555
|
+
if (seen.has(resolvedPath)) {
|
|
556
|
+
return Array.from(seen.values());
|
|
557
|
+
}
|
|
558
|
+
const modelInfo = await (0, model_parser_1.parseModelFile)(resolvedPath);
|
|
559
|
+
seen.set(resolvedPath, modelInfo);
|
|
560
|
+
for (const ref of modelInfo.nestedSchemaRefs) {
|
|
561
|
+
const nestedPath = resolveNestedModelPath(resolvedPath, ref.importPath);
|
|
562
|
+
await collectModelGraph(nestedPath, seen);
|
|
563
|
+
}
|
|
564
|
+
return Array.from(seen.values());
|
|
565
|
+
}
|
|
566
|
+
function resolveNestedModelPath(modelPath, importPath) {
|
|
567
|
+
let resolvedPath = path.resolve(path.dirname(modelPath), importPath);
|
|
568
|
+
if (!resolvedPath.endsWith(".ts")) {
|
|
569
|
+
resolvedPath += ".ts";
|
|
570
|
+
}
|
|
571
|
+
return resolvedPath;
|
|
572
|
+
}
|
|
573
|
+
function describeFunctionsOutputPath(functionsDir, backendLanguage) {
|
|
574
|
+
if (backendLanguage === "typescript") {
|
|
575
|
+
return `${functionsDir}/src/`;
|
|
576
|
+
}
|
|
577
|
+
if (backendLanguage === "csharp") {
|
|
578
|
+
return `${functionsDir}/Crud/`;
|
|
579
|
+
}
|
|
580
|
+
return `${functionsDir}/blueprints/`;
|
|
581
|
+
}
|
|
582
|
+
function updatePythonFunctionRegistrations(functionAppPath, registration, session) {
|
|
583
|
+
if (!session.fileExists(functionAppPath)) {
|
|
584
|
+
throw new Error(`Python Functions entrypoint not found: ${functionAppPath}`);
|
|
585
|
+
}
|
|
586
|
+
const content = session.readFile(functionAppPath);
|
|
587
|
+
// Check if import line already exists (handles init-generated layout)
|
|
588
|
+
const importLine = registration.split("\n").find((l) => l.startsWith("from ") || l.startsWith("import "));
|
|
589
|
+
if (importLine && content.includes(importLine)) {
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
if (content.includes(registration)) {
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
const marker = "# SwallowKit scaffold registrations";
|
|
596
|
+
if (!content.includes(marker)) {
|
|
597
|
+
throw new Error(`Could not find scaffold registration marker in ${functionAppPath}`);
|
|
598
|
+
}
|
|
599
|
+
const updated = content.replace(marker, `${registration}\n${marker}`);
|
|
600
|
+
session.writeFile(functionAppPath, updated, { ownership: "extension-point", generator: SCAFFOLD_GENERATOR });
|
|
193
601
|
}
|
|
194
602
|
/**
|
|
195
603
|
* Next.js BFF API Routes を生成
|
|
196
604
|
*/
|
|
197
|
-
async function generateBFFRoutes(modelInfo, apiDir, sharedPackageName) {
|
|
605
|
+
async function generateBFFRoutes(modelInfo, apiDir, sharedPackageName, session) {
|
|
198
606
|
console.log("\n🔨 Generating Next.js BFF API routes...");
|
|
199
|
-
const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
|
|
200
607
|
const modelCamel = modelInfo.name.charAt(0).toLowerCase() + modelInfo.name.slice(1);
|
|
201
608
|
// List route: app/api/[model]/route.ts
|
|
202
609
|
const listRoutePath = path.join(process.cwd(), apiDir, modelCamel, "route.ts");
|
|
203
610
|
// Detail route: app/api/[model]/[id]/route.ts
|
|
204
611
|
const detailRoutePath = path.join(process.cwd(), apiDir, modelCamel, "[id]", "route.ts");
|
|
205
|
-
// ディレクトリを作成
|
|
206
|
-
const listRouteDir = path.dirname(listRoutePath);
|
|
207
|
-
const detailRouteDir = path.dirname(detailRoutePath);
|
|
208
|
-
if (!fs.existsSync(listRouteDir)) {
|
|
209
|
-
fs.mkdirSync(listRouteDir, { recursive: true });
|
|
210
|
-
}
|
|
211
|
-
if (!fs.existsSync(detailRouteDir)) {
|
|
212
|
-
fs.mkdirSync(detailRouteDir, { recursive: true });
|
|
213
|
-
}
|
|
214
612
|
// コードを生成
|
|
215
613
|
const routes = (0, nextjs_generator_1.generateCompactBFFRoutes)(modelInfo, sharedPackageName);
|
|
216
614
|
// ファイルに書き込み
|
|
217
|
-
|
|
218
|
-
|
|
615
|
+
const meta = { ownership: "managed", generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name };
|
|
616
|
+
session.writeFile(listRoutePath, routes.listRoute, meta);
|
|
617
|
+
session.writeFile(detailRoutePath, routes.detailRoute, meta);
|
|
219
618
|
console.log(`✅ Created: ${listRoutePath}`);
|
|
220
619
|
console.log(`✅ Created: ${detailRoutePath}`);
|
|
221
620
|
}
|
|
222
621
|
/**
|
|
223
622
|
* Generate Cosmos DB container Bicep file
|
|
224
623
|
*/
|
|
225
|
-
async function generateCosmosContainer(modelInfo) {
|
|
624
|
+
async function generateCosmosContainer(modelInfo, session = new file_session_1.FileOperationSession("commit")) {
|
|
226
625
|
console.log("\n🗄️ Generating Cosmos DB container Bicep file...");
|
|
227
626
|
const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
|
|
228
627
|
const modelPascal = (0, model_parser_1.toPascalCase)(modelInfo.name);
|
|
@@ -233,12 +632,8 @@ async function generateCosmosContainer(modelInfo) {
|
|
|
233
632
|
console.log("ℹ️ infra directory not found. Skipping Cosmos DB container generation.");
|
|
234
633
|
return;
|
|
235
634
|
}
|
|
236
|
-
// Check if containers directory exists, if not create it
|
|
237
|
-
const containersDir = path.join(infraDir, "containers");
|
|
238
|
-
if (!fs.existsSync(containersDir)) {
|
|
239
|
-
fs.mkdirSync(containersDir, { recursive: true });
|
|
240
|
-
}
|
|
241
635
|
// Generate container Bicep file
|
|
636
|
+
const containersDir = path.join(infraDir, "containers");
|
|
242
637
|
const containerFileName = `${modelKebab}-container.bicep`;
|
|
243
638
|
const containerFilePath = path.join(containersDir, containerFileName);
|
|
244
639
|
const bicepContent = `@description('Cosmos DB account name')
|
|
@@ -251,13 +646,7 @@ param databaseName string
|
|
|
251
646
|
param containerName string = '${modelPascal}s'
|
|
252
647
|
|
|
253
648
|
@description('Partition key path')
|
|
254
|
-
param partitionKeyPath string = '
|
|
255
|
-
|
|
256
|
-
@description('Throughput (RU/s) - only used for Free Tier')
|
|
257
|
-
param throughput int = 400
|
|
258
|
-
|
|
259
|
-
@description('Cosmos DB mode: freetier or serverless')
|
|
260
|
-
param cosmosDbMode string
|
|
649
|
+
param partitionKeyPath string = '${modelInfo.partitionKey}'
|
|
261
650
|
|
|
262
651
|
// Reference existing Cosmos DB account
|
|
263
652
|
resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2023-11-15' existing = {
|
|
@@ -298,33 +687,30 @@ resource container 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/container
|
|
|
298
687
|
]
|
|
299
688
|
}
|
|
300
689
|
}
|
|
301
|
-
options: cosmosDbMode == 'freetier' ? {
|
|
302
|
-
throughput: throughput
|
|
303
|
-
} : {}
|
|
304
690
|
}
|
|
305
691
|
}
|
|
306
692
|
|
|
307
693
|
output containerName string = container.name
|
|
308
694
|
`;
|
|
309
695
|
// Write Bicep file (overwrite if exists)
|
|
310
|
-
|
|
696
|
+
session.writeFile(containerFilePath, bicepContent, { ownership: "managed", generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name });
|
|
311
697
|
console.log(`✅ Created: ${containerFilePath}`);
|
|
312
698
|
// Update main.bicep to include this container module
|
|
313
|
-
await updateMainBicepWithContainer(modelKebab, modelPascal);
|
|
699
|
+
await updateMainBicepWithContainer(modelKebab, modelPascal, session);
|
|
314
700
|
}
|
|
315
701
|
/**
|
|
316
702
|
* Update main.bicep to include new container module
|
|
317
703
|
*/
|
|
318
|
-
async function updateMainBicepWithContainer(modelKebab, modelPascal) {
|
|
704
|
+
async function updateMainBicepWithContainer(modelKebab, modelPascal, session) {
|
|
319
705
|
const cwd = process.cwd();
|
|
320
706
|
const mainBicepPath = path.join(cwd, "infra", "main.bicep");
|
|
321
|
-
if (!
|
|
707
|
+
if (!session.fileExists(mainBicepPath)) {
|
|
322
708
|
console.log("ℹ️ main.bicep not found. Please manually add the container module.");
|
|
323
709
|
return;
|
|
324
710
|
}
|
|
325
|
-
let mainBicepContent =
|
|
711
|
+
let mainBicepContent = session.readFile(mainBicepPath);
|
|
326
712
|
// Check if container module already exists
|
|
327
|
-
const containerModuleName = `${
|
|
713
|
+
const containerModuleName = `${modelPascal.charAt(0).toLowerCase()}${modelPascal.slice(1)}Container`;
|
|
328
714
|
if (mainBicepContent.includes(`module ${containerModuleName}`)) {
|
|
329
715
|
console.log(`ℹ️ Container module '${containerModuleName}' already exists in main.bicep`);
|
|
330
716
|
return;
|
|
@@ -359,26 +745,21 @@ module ${containerModuleName} 'containers/${modelKebab}-container.bicep' = {
|
|
|
359
745
|
name: '${modelKebab}-container'
|
|
360
746
|
params: {
|
|
361
747
|
cosmosAccountName: cosmosDbMode == 'freetier' ? cosmosDbFreeTier.outputs.accountName : cosmosDbServerless.outputs.accountName
|
|
362
|
-
databaseName: '
|
|
363
|
-
cosmosDbMode: cosmosDbMode
|
|
748
|
+
databaseName: '\${projectName}Database'
|
|
364
749
|
}
|
|
365
|
-
dependsOn: [
|
|
366
|
-
cosmosDbFreeTier
|
|
367
|
-
cosmosDbServerless
|
|
368
|
-
]
|
|
369
750
|
}`;
|
|
370
751
|
// Insert the module
|
|
371
752
|
mainBicepContent =
|
|
372
753
|
mainBicepContent.slice(0, insertPosition) +
|
|
373
754
|
containerModule +
|
|
374
755
|
mainBicepContent.slice(insertPosition);
|
|
375
|
-
|
|
756
|
+
session.writeFile(mainBicepPath, mainBicepContent, { ownership: "extension-point", generator: SCAFFOLD_GENERATOR });
|
|
376
757
|
console.log(`✅ Added container module to main.bicep`);
|
|
377
758
|
}
|
|
378
759
|
/**
|
|
379
760
|
* Generate UI components (list, detail, form, create, edit pages)
|
|
380
761
|
*/
|
|
381
|
-
async function generateUIComponents(modelInfo, sharedPackageName) {
|
|
762
|
+
async function generateUIComponents(modelInfo, sharedPackageName, authOptions, session = new file_session_1.FileOperationSession("commit")) {
|
|
382
763
|
console.log("\n🎨 Generating UI components...");
|
|
383
764
|
const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
|
|
384
765
|
const modelName = modelInfo.name;
|
|
@@ -389,44 +770,46 @@ async function generateUIComponents(modelInfo, sharedPackageName) {
|
|
|
389
770
|
const newDir = path.join(modelDir, "new");
|
|
390
771
|
const idDir = path.join(modelDir, "[id]");
|
|
391
772
|
const editDir = path.join(idDir, "edit");
|
|
392
|
-
// Create directories
|
|
393
|
-
[componentsDir, newDir, editDir].forEach(dir => {
|
|
394
|
-
if (!fs.existsSync(dir)) {
|
|
395
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
396
|
-
}
|
|
397
|
-
});
|
|
398
773
|
// Generate and write files
|
|
399
|
-
const listPage = (0, ui_generator_1.generateListPage)(modelInfo, sharedPackageName);
|
|
400
|
-
const detailPage = (0, ui_generator_1.generateDetailPage)(modelInfo, sharedPackageName);
|
|
774
|
+
const listPage = (0, ui_generator_1.generateListPage)(modelInfo, sharedPackageName, authOptions);
|
|
775
|
+
const detailPage = (0, ui_generator_1.generateDetailPage)(modelInfo, sharedPackageName, authOptions);
|
|
401
776
|
const formComponent = (0, ui_generator_1.generateFormComponent)(modelInfo, sharedPackageName);
|
|
402
|
-
const newPage = (0, ui_generator_1.generateNewPage)(modelInfo);
|
|
403
|
-
const editPage = (0, ui_generator_1.generateEditPage)(modelInfo, sharedPackageName);
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
777
|
+
const newPage = (0, ui_generator_1.generateNewPage)(modelInfo, authOptions);
|
|
778
|
+
const editPage = (0, ui_generator_1.generateEditPage)(modelInfo, sharedPackageName, authOptions);
|
|
779
|
+
const authLayout = (0, ui_generator_1.generateAuthLayout)(modelInfo, authOptions);
|
|
780
|
+
const meta = { ownership: "managed", generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name };
|
|
781
|
+
session.writeFile(path.join(modelDir, "page.tsx"), listPage, meta);
|
|
782
|
+
session.writeFile(path.join(idDir, "page.tsx"), detailPage, meta);
|
|
783
|
+
session.writeFile(path.join(componentsDir, `${modelName}Form.tsx`), formComponent, meta);
|
|
784
|
+
session.writeFile(path.join(newDir, "page.tsx"), newPage, meta);
|
|
785
|
+
session.writeFile(path.join(editDir, "page.tsx"), editPage, meta);
|
|
786
|
+
if (authLayout) {
|
|
787
|
+
session.writeFile(path.join(modelDir, "layout.tsx"), authLayout, meta);
|
|
788
|
+
}
|
|
409
789
|
console.log(`✅ Created: ${path.join(modelDir, "page.tsx")}`);
|
|
410
790
|
console.log(`✅ Created: ${path.join(idDir, "page.tsx")}`);
|
|
411
791
|
console.log(`✅ Created: ${path.join(componentsDir, `${modelName}Form.tsx`)}`);
|
|
412
792
|
console.log(`✅ Created: ${path.join(newDir, "page.tsx")}`);
|
|
413
793
|
console.log(`✅ Created: ${path.join(editDir, "page.tsx")}`);
|
|
794
|
+
if (authLayout) {
|
|
795
|
+
console.log(`✅ Created: ${path.join(modelDir, "layout.tsx")}`);
|
|
796
|
+
}
|
|
414
797
|
}
|
|
415
798
|
/**
|
|
416
799
|
* Update navigation menu on the homepage
|
|
417
800
|
*/
|
|
418
|
-
async function updateNavigationMenu(modelInfo) {
|
|
801
|
+
async function updateNavigationMenu(modelInfo, session) {
|
|
419
802
|
console.log("\n📋 Updating navigation menu...");
|
|
420
803
|
const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
|
|
421
804
|
const cwd = process.cwd();
|
|
422
805
|
// Update scaffold config
|
|
423
806
|
const configPath = path.join(cwd, "lib", "scaffold-config.ts");
|
|
424
|
-
if (!
|
|
807
|
+
if (!session.fileExists(configPath)) {
|
|
425
808
|
console.log("⚠️ scaffold-config.ts not found. Skipping navigation menu update.");
|
|
426
809
|
return;
|
|
427
810
|
}
|
|
428
811
|
// Read existing config
|
|
429
|
-
const configContent =
|
|
812
|
+
const configContent = session.readFile(configPath);
|
|
430
813
|
// Parse models array - extract each model entry
|
|
431
814
|
const models = [];
|
|
432
815
|
const modelsMatch = configContent.match(/models:\s*\[([\s\S]*?)\]\s*as\s*ScaffoldModel\[\]/);
|
|
@@ -467,7 +850,7 @@ ${models.map(m => ` { name: '${m.name}', path: '${m.path}', label: '${m.label
|
|
|
467
850
|
] as ScaffoldModel[]
|
|
468
851
|
};
|
|
469
852
|
`;
|
|
470
|
-
|
|
853
|
+
session.writeFile(configPath, newConfigContent, { ownership: "extension-point", generator: SCAFFOLD_GENERATOR });
|
|
471
854
|
console.log(`✅ Added ${modelInfo.name} to navigation menu`);
|
|
472
855
|
}
|
|
473
856
|
//# sourceMappingURL=scaffold.js.map
|