swallowkit 1.0.0-beta.55 → 1.0.0-beta.56
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/dist/cli/commands/add-auth.d.ts.map +1 -1
- package/dist/cli/commands/add-auth.js +154 -157
- package/dist/cli/commands/add-auth.js.map +1 -1
- package/dist/cli/commands/init.d.ts +5 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +178 -25
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts +7 -2
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +111 -123
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +127 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/core/config.d.ts +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +8 -4
- package/dist/core/config.js.map +1 -1
- package/dist/core/operations/auth-operations.d.ts +57 -0
- package/dist/core/operations/auth-operations.d.ts.map +1 -0
- package/dist/core/operations/auth-operations.js +269 -0
- package/dist/core/operations/auth-operations.js.map +1 -0
- package/dist/core/operations/create-model.d.ts.map +1 -1
- package/dist/core/operations/create-model.js +23 -13
- package/dist/core/operations/create-model.js.map +1 -1
- package/dist/core/operations/file-session.d.ts +53 -0
- package/dist/core/operations/file-session.d.ts.map +1 -0
- package/dist/core/operations/file-session.js +195 -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 +5 -0
- package/dist/core/operations/runtime.d.ts.map +1 -1
- package/dist/core/operations/runtime.js +23 -0
- package/dist/core/operations/runtime.js.map +1 -1
- 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.map +1 -1
- package/dist/core/operations/scaffold-machine.js +1 -13
- package/dist/core/operations/scaffold-machine.js.map +1 -1
- 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 +195 -0
- package/dist/core/operations/scaffold-plan.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/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/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/workflows.d.ts +43 -0
- package/dist/core/project/workflows.d.ts.map +1 -0
- package/dist/core/project/workflows.js +255 -0
- package/dist/core/project/workflows.js.map +1 -0
- package/dist/core/scaffold/auth-generator.d.ts.map +1 -1
- package/dist/core/scaffold/auth-generator.js +24 -27
- package/dist/core/scaffold/auth-generator.js.map +1 -1
- package/dist/core/scaffold/functions-generator.js +9 -9
- package/dist/core/scaffold/nextjs-generator.js +6 -6
- package/dist/core/scaffold/ui-generator.js +19 -19
- package/dist/core/scaffold/ui-generator.js.map +1 -1
- package/dist/core/verify/index.d.ts +62 -0
- package/dist/core/verify/index.d.ts.map +1 -0
- package/dist/core/verify/index.js +343 -0
- package/dist/core/verify/index.js.map +1 -0
- package/dist/machine/contracts.d.ts +8 -0
- package/dist/machine/contracts.d.ts.map +1 -1
- package/dist/machine/errors.d.ts +4 -1
- package/dist/machine/errors.d.ts.map +1 -1
- package/dist/machine/errors.js +9 -1
- package/dist/machine/errors.js.map +1 -1
- package/dist/machine/index.d.ts.map +1 -1
- package/dist/machine/index.js +350 -16
- package/dist/machine/index.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +202 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/types/index.d.ts +14 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +17 -17
- package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +6 -6
- package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +11 -11
- package/src/__tests__/agent-loop-phase34.test.ts +449 -0
- package/src/__tests__/file-session.test.ts +98 -0
- package/src/__tests__/init.test.ts +50 -0
- package/src/__tests__/mcp.test.ts +113 -5
- package/src/__tests__/plan-apply.test.ts +294 -0
- package/src/cli/commands/add-auth.ts +164 -164
- package/src/cli/commands/init.ts +186 -24
- package/src/cli/commands/scaffold.ts +128 -129
- package/src/cli/index.ts +148 -2
- package/src/core/config.ts +5 -4
- package/src/core/operations/auth-operations.ts +353 -0
- package/src/core/operations/create-model.ts +25 -13
- package/src/core/operations/file-session.ts +210 -0
- package/src/core/operations/provision-operations.ts +297 -0
- package/src/core/operations/runtime.ts +23 -0
- package/src/core/operations/scaffold-apply.ts +106 -0
- package/src/core/operations/scaffold-machine.ts +2 -16
- package/src/core/operations/scaffold-plan.ts +224 -0
- package/src/core/project/artifacts.ts +205 -0
- package/src/core/project/boundaries.ts +170 -0
- package/src/core/project/drift.ts +202 -0
- package/src/core/project/infra.ts +196 -0
- package/src/core/project/state.ts +77 -0
- package/src/core/project/workflows.ts +249 -0
- package/src/core/scaffold/auth-generator.ts +24 -27
- package/src/core/scaffold/functions-generator.ts +9 -9
- package/src/core/scaffold/nextjs-generator.ts +6 -6
- package/src/core/scaffold/ui-generator.ts +19 -19
- package/src/core/verify/index.ts +392 -0
- package/src/machine/contracts.ts +16 -0
- package/src/machine/errors.ts +12 -1
- package/src/machine/index.ts +444 -15
- package/src/mcp/index.ts +198 -2
- package/src/types/index.ts +17 -0
|
@@ -44,6 +44,14 @@ import {
|
|
|
44
44
|
} from "../../types";
|
|
45
45
|
import { syncProjectManifest } from "../../core/project/manifest";
|
|
46
46
|
import { generateBFFCallFunctionWithAuth, generateBFFCallFunctionWithMultipleAuth, generateBFFCallFunctionWithSwaAuth, generateNamedAuthRouterCSharp, generateNamedAuthRouterPython, generateNamedAuthRouterTS } from "../../core/scaffold/auth-generator";
|
|
47
|
+
import {
|
|
48
|
+
FileOperationSession,
|
|
49
|
+
getActiveFileSession,
|
|
50
|
+
hashFileIfExists,
|
|
51
|
+
} from "../../core/operations/file-session";
|
|
52
|
+
import { recordSessionOperations } from "../../core/project/artifacts";
|
|
53
|
+
|
|
54
|
+
const SCAFFOLD_GENERATOR = "scaffold";
|
|
47
55
|
|
|
48
56
|
interface ScaffoldOptions {
|
|
49
57
|
model: string; // モデルファイルのパス(例: "lib/models/todo.ts" or "todo")
|
|
@@ -79,6 +87,9 @@ export async function scaffoldCommand(options: ScaffoldOptions) {
|
|
|
79
87
|
const configValidation = validateConfig(getFullConfig());
|
|
80
88
|
if (!configValidation.valid) throw new Error(`Invalid SwallowKit configuration:\n${configValidation.errors.map(error => `- ${error}`).join("\n")}`);
|
|
81
89
|
|
|
90
|
+
// Plan(dry-run) や Apply からセッションが供給される。単体実行時は commit セッションを生成。
|
|
91
|
+
const session = getActiveFileSession() ?? new FileOperationSession("commit");
|
|
92
|
+
|
|
82
93
|
console.log("🏗️ SwallowKit Scaffold: Generating CRUD operations...\n");
|
|
83
94
|
|
|
84
95
|
try {
|
|
@@ -130,32 +141,38 @@ export async function scaffoldCommand(options: ScaffoldOptions) {
|
|
|
130
141
|
}
|
|
131
142
|
|
|
132
143
|
// 5. Generate BFF callFunction helper
|
|
133
|
-
await generateCallFunctionHelper();
|
|
144
|
+
await generateCallFunctionHelper(session);
|
|
134
145
|
|
|
135
146
|
// 6. Generate Azure Functions code
|
|
136
147
|
if (isConnectorModel) {
|
|
137
|
-
await generateConnectorFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage);
|
|
148
|
+
await generateConnectorFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage, session);
|
|
138
149
|
// 6b. Install connector driver dependencies
|
|
139
|
-
await installConnectorDriverDependencies(modelInfo, functionsDir, backendLanguage);
|
|
150
|
+
await installConnectorDriverDependencies(modelInfo, functionsDir, backendLanguage, session);
|
|
140
151
|
} else {
|
|
141
|
-
await generateFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage);
|
|
152
|
+
await generateFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage, session);
|
|
142
153
|
|
|
143
154
|
if (backendLanguage !== "typescript") {
|
|
144
|
-
|
|
155
|
+
if (session.mode === "collect") {
|
|
156
|
+
session.addWarning(
|
|
157
|
+
`external-codegen: ${functionsDir}/openapi and ${functionsDir}/generated are regenerated by external tooling on apply and are not included in this plan.`
|
|
158
|
+
);
|
|
159
|
+
} else {
|
|
160
|
+
await generateLanguageSchemaArtifacts(relatedModels, modelInfo, functionsDir, backendLanguage);
|
|
161
|
+
}
|
|
145
162
|
}
|
|
146
163
|
}
|
|
147
164
|
|
|
148
165
|
// 7. Generate Next.js BFF API Routes
|
|
149
166
|
const apiDir = options.apiDir || "app/api";
|
|
150
167
|
if (isConnectorModel) {
|
|
151
|
-
await generateConnectorBFFRoutesFiles(modelInfo, apiDir, sharedPackageName);
|
|
168
|
+
await generateConnectorBFFRoutesFiles(modelInfo, apiDir, sharedPackageName, session);
|
|
152
169
|
} else {
|
|
153
|
-
await generateBFFRoutes(modelInfo, apiDir, sharedPackageName);
|
|
170
|
+
await generateBFFRoutes(modelInfo, apiDir, sharedPackageName, session);
|
|
154
171
|
}
|
|
155
172
|
|
|
156
173
|
// 8. Generate Cosmos DB container Bicep file (skip for connector models)
|
|
157
174
|
if (!isConnectorModel) {
|
|
158
|
-
await generateCosmosContainer(modelInfo);
|
|
175
|
+
await generateCosmosContainer(modelInfo, session);
|
|
159
176
|
}
|
|
160
177
|
|
|
161
178
|
// 9. Generate UI components (unless --api-only)
|
|
@@ -166,14 +183,20 @@ export async function scaffoldCommand(options: ScaffoldOptions) {
|
|
|
166
183
|
uiAuthPolicy && uiAuthConfig && uiAuthConfig.provider !== 'none'
|
|
167
184
|
? createUIAuthOptions(uiAuthPolicy, uiAuthConfig)
|
|
168
185
|
: undefined;
|
|
169
|
-
await generateUIComponents(modelInfo, sharedPackageName, uiAuthOptions);
|
|
186
|
+
await generateUIComponents(modelInfo, sharedPackageName, uiAuthOptions, session);
|
|
170
187
|
if (uiAuthOptions?.authenticatedFetchImport) {
|
|
171
|
-
applyExternalTokenFetchToGeneratedUi(modelInfo, uiAuthOptions.authenticatedFetchImport);
|
|
188
|
+
applyExternalTokenFetchToGeneratedUi(modelInfo, session, uiAuthOptions.authenticatedFetchImport);
|
|
172
189
|
}
|
|
173
|
-
await updateNavigationMenu(modelInfo);
|
|
190
|
+
await updateNavigationMenu(modelInfo, session);
|
|
174
191
|
}
|
|
175
192
|
|
|
176
|
-
|
|
193
|
+
if (session.mode === "commit") {
|
|
194
|
+
recordSessionOperations(session.operations, {
|
|
195
|
+
schemaHash: hashFileIfExists(modelPath) ?? undefined,
|
|
196
|
+
sourceModel: modelInfo.name,
|
|
197
|
+
});
|
|
198
|
+
await syncProjectManifest();
|
|
199
|
+
}
|
|
177
200
|
|
|
178
201
|
console.log("\n✅ Scaffold completed successfully!");
|
|
179
202
|
console.log("\n📝 Next steps:");
|
|
@@ -198,29 +221,27 @@ export async function scaffoldCommand(options: ScaffoldOptions) {
|
|
|
198
221
|
}
|
|
199
222
|
}
|
|
200
223
|
|
|
201
|
-
function applyExternalTokenFetchToGeneratedUi(modelInfo: ModelInfo, importPath = '@/lib/auth/authenticated-fetch'): void {
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
);
|
|
216
|
-
if (!content.includes(importPath)) {
|
|
217
|
-
const directive = content.match(/^'use client';\s*\r?\n/)?.[0] ?? '';
|
|
218
|
-
content = `${directive}import { authenticatedFetch } from '${importPath}';\n${content.slice(directive.length)}`;
|
|
219
|
-
}
|
|
220
|
-
fs.writeFileSync(target, content, 'utf-8');
|
|
224
|
+
function applyExternalTokenFetchToGeneratedUi(modelInfo: ModelInfo, session: FileOperationSession, importPath = '@/lib/auth/authenticated-fetch'): void {
|
|
225
|
+
const uiPrefix = `app/${toKebabCase(modelInfo.name)}/`;
|
|
226
|
+
for (const operation of session.operations) {
|
|
227
|
+
if (!operation.path.startsWith(uiPrefix) || !operation.path.endsWith('.tsx') || operation.action === 'delete') continue;
|
|
228
|
+
let content = session.readFile(operation.absolutePath);
|
|
229
|
+
if (!content.includes('fetch(')) continue;
|
|
230
|
+
content = content.replace(/\bfetch\(/g, 'authenticatedFetch(');
|
|
231
|
+
content = content.replace(
|
|
232
|
+
/(if \(!res\.ok\) throw new Error\([^\n]+\);)/g,
|
|
233
|
+
`if (res.status === 401) throw new Error('Authentication required');\n $1`
|
|
234
|
+
);
|
|
235
|
+
if (!content.includes(importPath)) {
|
|
236
|
+
const directive = content.match(/^'use client';\s*\r?\n/)?.[0] ?? '';
|
|
237
|
+
content = `${directive}import { authenticatedFetch } from '${importPath}';\n${content.slice(directive.length)}`;
|
|
221
238
|
}
|
|
222
|
-
|
|
223
|
-
|
|
239
|
+
session.writeFile(operation.absolutePath, content, {
|
|
240
|
+
ownership: 'managed',
|
|
241
|
+
generator: SCAFFOLD_GENERATOR,
|
|
242
|
+
sourceModel: modelInfo.name,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
224
245
|
}
|
|
225
246
|
|
|
226
247
|
/**
|
|
@@ -259,7 +280,7 @@ export function resolveAuthPolicy(modelInfo: ModelInfo, authConfig: AuthConfig |
|
|
|
259
280
|
/**
|
|
260
281
|
* モデルファイルのパスを解決
|
|
261
282
|
*/
|
|
262
|
-
function resolveModelPath(modelInput: string): string {
|
|
283
|
+
export function resolveModelPath(modelInput: string): string {
|
|
263
284
|
const cwd = process.cwd();
|
|
264
285
|
|
|
265
286
|
// 絶対パスの場合
|
|
@@ -327,7 +348,7 @@ function readSharedPackageName(): string {
|
|
|
327
348
|
/**
|
|
328
349
|
* Generate BFF callFunction helper (lib/api/call-function.ts)
|
|
329
350
|
*/
|
|
330
|
-
async function generateCallFunctionHelper(): Promise<void> {
|
|
351
|
+
async function generateCallFunctionHelper(session: FileOperationSession): Promise<void> {
|
|
331
352
|
console.log("\n📦 Generating BFF callFunction helper...");
|
|
332
353
|
|
|
333
354
|
const cwd = process.cwd();
|
|
@@ -335,10 +356,6 @@ async function generateCallFunctionHelper(): Promise<void> {
|
|
|
335
356
|
const helperDir = path.join(cwd, "lib", "api");
|
|
336
357
|
const helperPath = path.join(helperDir, "call-function.ts");
|
|
337
358
|
|
|
338
|
-
if (!fs.existsSync(helperDir)) {
|
|
339
|
-
fs.mkdirSync(helperDir, { recursive: true });
|
|
340
|
-
}
|
|
341
|
-
|
|
342
359
|
// auth 設定がある場合は Authorization ヘッダー転送版を生成
|
|
343
360
|
const authConfig = getAuthConfig();
|
|
344
361
|
const normalizedAuth = normalizeAuthConfig(authConfig);
|
|
@@ -353,7 +370,7 @@ async function generateCallFunctionHelper(): Promise<void> {
|
|
|
353
370
|
helperCode = generateBFFCallFunction();
|
|
354
371
|
}
|
|
355
372
|
|
|
356
|
-
|
|
373
|
+
session.writeFile(helperPath, helperCode, { ownership: "managed", generator: SCAFFOLD_GENERATOR });
|
|
357
374
|
console.log(`✅ Created: ${helperPath}`);
|
|
358
375
|
}
|
|
359
376
|
|
|
@@ -364,7 +381,8 @@ async function generateFunctionsCode(
|
|
|
364
381
|
modelInfo: ModelInfo,
|
|
365
382
|
functionsDir: string,
|
|
366
383
|
sharedPackageName: string,
|
|
367
|
-
backendLanguage: BackendLanguage
|
|
384
|
+
backendLanguage: BackendLanguage,
|
|
385
|
+
session: FileOperationSession
|
|
368
386
|
): Promise<void> {
|
|
369
387
|
console.log("\n🔨 Generating Azure Functions CRUD code...");
|
|
370
388
|
|
|
@@ -376,12 +394,12 @@ async function generateFunctionsCode(
|
|
|
376
394
|
}
|
|
377
395
|
|
|
378
396
|
const modelKebab = toKebabCase(modelInfo.name);
|
|
397
|
+
const managedMeta = { ownership: "managed" as const, generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name };
|
|
379
398
|
if (backendLanguage === "typescript") {
|
|
380
399
|
const normalizedAuth = getNormalizedAuthConfig();
|
|
381
400
|
if (normalizedAuth && Object.keys(normalizedAuth.schemes).some(name => name !== "default")) {
|
|
382
401
|
const routerPath = path.join(process.cwd(), functionsDir, "src", "auth", "auth-router.ts");
|
|
383
|
-
|
|
384
|
-
fs.writeFileSync(routerPath, generateNamedAuthRouterTS(normalizedAuth), "utf-8");
|
|
402
|
+
session.writeFile(routerPath, generateNamedAuthRouterTS(normalizedAuth), { ownership: "managed", generator: SCAFFOLD_GENERATOR });
|
|
385
403
|
}
|
|
386
404
|
const functionFilePath = path.join(
|
|
387
405
|
process.cwd(),
|
|
@@ -390,13 +408,8 @@ async function generateFunctionsCode(
|
|
|
390
408
|
`${modelKebab}.ts`
|
|
391
409
|
);
|
|
392
410
|
|
|
393
|
-
const functionDir = path.dirname(functionFilePath);
|
|
394
|
-
if (!fs.existsSync(functionDir)) {
|
|
395
|
-
fs.mkdirSync(functionDir, { recursive: true });
|
|
396
|
-
}
|
|
397
|
-
|
|
398
411
|
const code = generateCompactAzureFunctionsCRUD(modelInfo, sharedPackageName, authPolicy, authConfig?.provider);
|
|
399
|
-
|
|
412
|
+
session.writeFile(functionFilePath, code, managedMeta);
|
|
400
413
|
console.log(`✅ Created: ${functionFilePath}`);
|
|
401
414
|
return;
|
|
402
415
|
}
|
|
@@ -405,21 +418,19 @@ async function generateFunctionsCode(
|
|
|
405
418
|
const normalizedAuth = getNormalizedAuthConfig();
|
|
406
419
|
if (normalizedAuth && Object.keys(normalizedAuth.schemes).some(name => name !== "default")) {
|
|
407
420
|
const routerPath = path.join(process.cwd(), functionsDir, "Auth", "AuthRouter.cs");
|
|
408
|
-
|
|
409
|
-
fs.writeFileSync(routerPath, generateNamedAuthRouterCSharp(normalizedAuth), "utf-8");
|
|
421
|
+
session.writeFile(routerPath, generateNamedAuthRouterCSharp(normalizedAuth), { ownership: "managed", generator: SCAFFOLD_GENERATOR });
|
|
410
422
|
}
|
|
411
423
|
const crudDir = path.join(process.cwd(), functionsDir, "Crud");
|
|
412
424
|
const functionFilePath = path.join(crudDir, `${modelInfo.name}CrudFunctions.cs`);
|
|
413
|
-
fs.mkdirSync(crudDir, { recursive: true });
|
|
414
425
|
|
|
415
426
|
// Remove init-generated template (singular) to avoid route conflicts
|
|
416
427
|
const templatePath = path.join(crudDir, `${modelInfo.name}Function.cs`);
|
|
417
|
-
if (
|
|
418
|
-
|
|
428
|
+
if (session.fileExists(templatePath)) {
|
|
429
|
+
session.deleteFile(templatePath, managedMeta);
|
|
419
430
|
console.log(`🗑️ Removed template: ${templatePath}`);
|
|
420
431
|
}
|
|
421
432
|
|
|
422
|
-
|
|
433
|
+
session.writeFile(functionFilePath, generateCSharpAzureFunctionsCRUD(modelInfo, authPolicy), managedMeta);
|
|
423
434
|
console.log(`✅ Created: ${functionFilePath}`);
|
|
424
435
|
return;
|
|
425
436
|
}
|
|
@@ -427,17 +438,15 @@ async function generateFunctionsCode(
|
|
|
427
438
|
const normalizedAuth = getNormalizedAuthConfig();
|
|
428
439
|
if (normalizedAuth && Object.keys(normalizedAuth.schemes).some(name => name !== "default")) {
|
|
429
440
|
const routerPath = path.join(process.cwd(), functionsDir, "auth", "auth_router.py");
|
|
430
|
-
|
|
431
|
-
fs.writeFileSync(routerPath, generateNamedAuthRouterPython(normalizedAuth), "utf-8");
|
|
441
|
+
session.writeFile(routerPath, generateNamedAuthRouterPython(normalizedAuth), { ownership: "managed", generator: SCAFFOLD_GENERATOR });
|
|
432
442
|
}
|
|
433
443
|
|
|
434
444
|
const blueprintsDir = path.join(process.cwd(), functionsDir, "blueprints");
|
|
435
445
|
const blueprintPath = path.join(blueprintsDir, `${modelKebab.replace(/-/g, "_")}.py`);
|
|
436
|
-
fs.mkdirSync(blueprintsDir, { recursive: true });
|
|
437
446
|
|
|
438
447
|
const { blueprint, registration } = generatePythonAzureFunctionsCRUD(modelInfo, authPolicy);
|
|
439
|
-
|
|
440
|
-
updatePythonFunctionRegistrations(path.join(process.cwd(), functionsDir, "function_app.py"), registration);
|
|
448
|
+
session.writeFile(blueprintPath, blueprint, managedMeta);
|
|
449
|
+
updatePythonFunctionRegistrations(path.join(process.cwd(), functionsDir, "function_app.py"), registration, session);
|
|
441
450
|
console.log(`✅ Created: ${blueprintPath}`);
|
|
442
451
|
}
|
|
443
452
|
|
|
@@ -462,7 +471,8 @@ async function generateConnectorFunctionsCode(
|
|
|
462
471
|
modelInfo: ModelInfo,
|
|
463
472
|
functionsDir: string,
|
|
464
473
|
sharedPackageName: string,
|
|
465
|
-
backendLanguage: BackendLanguage
|
|
474
|
+
backendLanguage: BackendLanguage,
|
|
475
|
+
session: FileOperationSession
|
|
466
476
|
): Promise<void> {
|
|
467
477
|
console.log("\n🔌 Generating Connector Azure Functions code...");
|
|
468
478
|
|
|
@@ -484,6 +494,7 @@ async function generateConnectorFunctionsCode(
|
|
|
484
494
|
}
|
|
485
495
|
|
|
486
496
|
const modelKebab = toKebabCase(modelInfo.name);
|
|
497
|
+
const connectorManagedMeta = { ownership: "managed" as const, generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name };
|
|
487
498
|
|
|
488
499
|
if (backendLanguage === "typescript") {
|
|
489
500
|
const functionFilePath = path.join(
|
|
@@ -492,7 +503,6 @@ async function generateConnectorFunctionsCode(
|
|
|
492
503
|
"src",
|
|
493
504
|
`${modelKebab}.ts`
|
|
494
505
|
);
|
|
495
|
-
fs.mkdirSync(path.dirname(functionFilePath), { recursive: true });
|
|
496
506
|
|
|
497
507
|
let code: string;
|
|
498
508
|
if (connectorDef.type === "rdb") {
|
|
@@ -513,7 +523,7 @@ async function generateConnectorFunctionsCode(
|
|
|
513
523
|
);
|
|
514
524
|
}
|
|
515
525
|
|
|
516
|
-
|
|
526
|
+
session.writeFile(functionFilePath, code, connectorManagedMeta);
|
|
517
527
|
console.log(`✅ Created: ${functionFilePath}`);
|
|
518
528
|
return;
|
|
519
529
|
}
|
|
@@ -525,7 +535,6 @@ async function generateConnectorFunctionsCode(
|
|
|
525
535
|
"Connectors",
|
|
526
536
|
`${modelInfo.name}ConnectorFunctions.cs`
|
|
527
537
|
);
|
|
528
|
-
fs.mkdirSync(path.dirname(functionFilePath), { recursive: true });
|
|
529
538
|
|
|
530
539
|
let code: string;
|
|
531
540
|
if (connectorDef.type === "rdb") {
|
|
@@ -542,7 +551,7 @@ async function generateConnectorFunctionsCode(
|
|
|
542
551
|
);
|
|
543
552
|
}
|
|
544
553
|
|
|
545
|
-
|
|
554
|
+
session.writeFile(functionFilePath, code, connectorManagedMeta);
|
|
546
555
|
console.log(`✅ Created: ${functionFilePath}`);
|
|
547
556
|
return;
|
|
548
557
|
}
|
|
@@ -550,7 +559,6 @@ async function generateConnectorFunctionsCode(
|
|
|
550
559
|
// Python
|
|
551
560
|
const blueprintsDir = path.join(process.cwd(), functionsDir, "blueprints");
|
|
552
561
|
const blueprintPath = path.join(blueprintsDir, `${modelKebab.replace(/-/g, "_")}.py`);
|
|
553
|
-
fs.mkdirSync(blueprintsDir, { recursive: true });
|
|
554
562
|
|
|
555
563
|
let result: { blueprint: string; registration: string };
|
|
556
564
|
if (connectorDef.type === "rdb") {
|
|
@@ -567,8 +575,8 @@ async function generateConnectorFunctionsCode(
|
|
|
567
575
|
);
|
|
568
576
|
}
|
|
569
577
|
|
|
570
|
-
|
|
571
|
-
updatePythonFunctionRegistrations(path.join(process.cwd(), functionsDir, "function_app.py"), result.registration);
|
|
578
|
+
session.writeFile(blueprintPath, result.blueprint, connectorManagedMeta);
|
|
579
|
+
updatePythonFunctionRegistrations(path.join(process.cwd(), functionsDir, "function_app.py"), result.registration, session);
|
|
572
580
|
console.log(`✅ Created: ${blueprintPath}`);
|
|
573
581
|
}
|
|
574
582
|
|
|
@@ -579,7 +587,8 @@ async function generateConnectorFunctionsCode(
|
|
|
579
587
|
async function installConnectorDriverDependencies(
|
|
580
588
|
modelInfo: ModelInfo,
|
|
581
589
|
functionsDir: string,
|
|
582
|
-
backendLanguage: BackendLanguage
|
|
590
|
+
backendLanguage: BackendLanguage,
|
|
591
|
+
session: FileOperationSession
|
|
583
592
|
): Promise<void> {
|
|
584
593
|
const connectorConfig = modelInfo.connectorConfig!;
|
|
585
594
|
const connectorDef = getConnectorDefinition(connectorConfig.connector);
|
|
@@ -607,6 +616,13 @@ async function installConnectorDriverDependencies(
|
|
|
607
616
|
if (missingDeps.length === 0 && missingDevDeps.length === 0) return;
|
|
608
617
|
}
|
|
609
618
|
|
|
619
|
+
if (session.mode === "collect") {
|
|
620
|
+
session.addWarning(
|
|
621
|
+
`external-install: ${rdbDef.provider} driver dependencies (${[...entry.deps, ...entry.devDeps].join(", ")}) will be installed into ${functionsDir}/ on apply.`
|
|
622
|
+
);
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
|
|
610
626
|
console.log(`\n📦 Installing ${rdbDef.provider} driver dependencies...`);
|
|
611
627
|
const pm = detectFromProject(functionsPath);
|
|
612
628
|
const cmds = getCommands(pm);
|
|
@@ -629,6 +645,10 @@ async function installConnectorDriverDependencies(
|
|
|
629
645
|
};
|
|
630
646
|
const pkg = nugetMap[rdbDef.provider];
|
|
631
647
|
if (!pkg) return;
|
|
648
|
+
if (session.mode === "collect") {
|
|
649
|
+
session.addWarning(`external-install: NuGet package ${pkg} will be added to ${functionsDir}/functions.csproj on apply.`);
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
632
652
|
console.log(`\n📦 Installing ${rdbDef.provider} NuGet package...`);
|
|
633
653
|
runSpawnSyncCommand("dotnet", ["add", path.join(functionsPath, "functions.csproj"), "package", pkg], functionsPath);
|
|
634
654
|
console.log(`✅ ${pkg} installed`);
|
|
@@ -644,11 +664,11 @@ async function installConnectorDriverDependencies(
|
|
|
644
664
|
const pipPkg = pipMap[rdbDef.provider];
|
|
645
665
|
if (!pipPkg) return;
|
|
646
666
|
const reqPath = path.join(functionsPath, "requirements.txt");
|
|
647
|
-
if (
|
|
648
|
-
const existing =
|
|
667
|
+
if (session.fileExists(reqPath)) {
|
|
668
|
+
const existing = session.readFile(reqPath);
|
|
649
669
|
if (!existing.includes(pipPkg)) {
|
|
650
670
|
console.log(`\n📦 Adding ${pipPkg} to requirements.txt...`);
|
|
651
|
-
|
|
671
|
+
session.writeFile(reqPath, `${existing}\n${pipPkg}\n`, { ownership: "extension-point", generator: SCAFFOLD_GENERATOR });
|
|
652
672
|
console.log(`✅ ${pipPkg} added`);
|
|
653
673
|
}
|
|
654
674
|
}
|
|
@@ -660,7 +680,8 @@ async function installConnectorDriverDependencies(
|
|
|
660
680
|
async function generateConnectorBFFRoutesFiles(
|
|
661
681
|
modelInfo: ModelInfo,
|
|
662
682
|
apiDir: string,
|
|
663
|
-
sharedPackageName: string
|
|
683
|
+
sharedPackageName: string,
|
|
684
|
+
session: FileOperationSession
|
|
664
685
|
): Promise<void> {
|
|
665
686
|
console.log("\n🔌 Generating Connector BFF API routes...");
|
|
666
687
|
|
|
@@ -670,13 +691,11 @@ async function generateConnectorBFFRoutesFiles(
|
|
|
670
691
|
const listRoutePath = path.join(process.cwd(), apiDir, modelCamel, "route.ts");
|
|
671
692
|
const detailRoutePath = path.join(process.cwd(), apiDir, modelCamel, "[id]", "route.ts");
|
|
672
693
|
|
|
673
|
-
fs.mkdirSync(path.dirname(listRoutePath), { recursive: true });
|
|
674
|
-
fs.mkdirSync(path.dirname(detailRoutePath), { recursive: true });
|
|
675
|
-
|
|
676
694
|
const routes = generateConnectorBFFRoutes(modelInfo, sharedPackageName, operations);
|
|
677
695
|
|
|
678
|
-
|
|
679
|
-
|
|
696
|
+
const meta = { ownership: "managed" as const, generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name };
|
|
697
|
+
session.writeFile(listRoutePath, routes.listRoute, meta);
|
|
698
|
+
session.writeFile(detailRoutePath, routes.detailRoute, meta);
|
|
680
699
|
|
|
681
700
|
console.log(`✅ Created: ${listRoutePath}`);
|
|
682
701
|
console.log(`✅ Created: ${detailRoutePath}`);
|
|
@@ -717,12 +736,12 @@ function describeFunctionsOutputPath(functionsDir: string, backendLanguage: Back
|
|
|
717
736
|
return `${functionsDir}/blueprints/`;
|
|
718
737
|
}
|
|
719
738
|
|
|
720
|
-
function updatePythonFunctionRegistrations(functionAppPath: string, registration: string): void {
|
|
721
|
-
if (!
|
|
739
|
+
function updatePythonFunctionRegistrations(functionAppPath: string, registration: string, session: FileOperationSession): void {
|
|
740
|
+
if (!session.fileExists(functionAppPath)) {
|
|
722
741
|
throw new Error(`Python Functions entrypoint not found: ${functionAppPath}`);
|
|
723
742
|
}
|
|
724
743
|
|
|
725
|
-
const content =
|
|
744
|
+
const content = session.readFile(functionAppPath);
|
|
726
745
|
|
|
727
746
|
// Check if import line already exists (handles init-generated layout)
|
|
728
747
|
const importLine = registration.split("\n").find((l) => l.startsWith("from ") || l.startsWith("import "));
|
|
@@ -739,7 +758,7 @@ function updatePythonFunctionRegistrations(functionAppPath: string, registration
|
|
|
739
758
|
}
|
|
740
759
|
|
|
741
760
|
const updated = content.replace(marker, `${registration}\n${marker}`);
|
|
742
|
-
|
|
761
|
+
session.writeFile(functionAppPath, updated, { ownership: "extension-point", generator: SCAFFOLD_GENERATOR });
|
|
743
762
|
}
|
|
744
763
|
|
|
745
764
|
/**
|
|
@@ -748,7 +767,8 @@ function updatePythonFunctionRegistrations(functionAppPath: string, registration
|
|
|
748
767
|
async function generateBFFRoutes(
|
|
749
768
|
modelInfo: any,
|
|
750
769
|
apiDir: string,
|
|
751
|
-
sharedPackageName: string
|
|
770
|
+
sharedPackageName: string,
|
|
771
|
+
session: FileOperationSession
|
|
752
772
|
): Promise<void> {
|
|
753
773
|
console.log("\n🔨 Generating Next.js BFF API routes...");
|
|
754
774
|
|
|
@@ -771,24 +791,13 @@ async function generateBFFRoutes(
|
|
|
771
791
|
"route.ts"
|
|
772
792
|
);
|
|
773
793
|
|
|
774
|
-
// ディレクトリを作成
|
|
775
|
-
const listRouteDir = path.dirname(listRoutePath);
|
|
776
|
-
const detailRouteDir = path.dirname(detailRoutePath);
|
|
777
|
-
|
|
778
|
-
if (!fs.existsSync(listRouteDir)) {
|
|
779
|
-
fs.mkdirSync(listRouteDir, { recursive: true });
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
if (!fs.existsSync(detailRouteDir)) {
|
|
783
|
-
fs.mkdirSync(detailRouteDir, { recursive: true });
|
|
784
|
-
}
|
|
785
|
-
|
|
786
794
|
// コードを生成
|
|
787
795
|
const routes = generateCompactBFFRoutes(modelInfo, sharedPackageName);
|
|
788
796
|
|
|
789
797
|
// ファイルに書き込み
|
|
790
|
-
|
|
791
|
-
|
|
798
|
+
const meta = { ownership: "managed" as const, generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name };
|
|
799
|
+
session.writeFile(listRoutePath, routes.listRoute, meta);
|
|
800
|
+
session.writeFile(detailRoutePath, routes.detailRoute, meta);
|
|
792
801
|
|
|
793
802
|
console.log(`✅ Created: ${listRoutePath}`);
|
|
794
803
|
console.log(`✅ Created: ${detailRoutePath}`);
|
|
@@ -797,7 +806,7 @@ async function generateBFFRoutes(
|
|
|
797
806
|
/**
|
|
798
807
|
* Generate Cosmos DB container Bicep file
|
|
799
808
|
*/
|
|
800
|
-
export async function generateCosmosContainer(modelInfo: any): Promise<void> {
|
|
809
|
+
export async function generateCosmosContainer(modelInfo: any, session: FileOperationSession = new FileOperationSession("commit")): Promise<void> {
|
|
801
810
|
console.log("\n🗄️ Generating Cosmos DB container Bicep file...");
|
|
802
811
|
|
|
803
812
|
const modelKebab = toKebabCase(modelInfo.name);
|
|
@@ -811,13 +820,8 @@ export async function generateCosmosContainer(modelInfo: any): Promise<void> {
|
|
|
811
820
|
return;
|
|
812
821
|
}
|
|
813
822
|
|
|
814
|
-
// Check if containers directory exists, if not create it
|
|
815
|
-
const containersDir = path.join(infraDir, "containers");
|
|
816
|
-
if (!fs.existsSync(containersDir)) {
|
|
817
|
-
fs.mkdirSync(containersDir, { recursive: true });
|
|
818
|
-
}
|
|
819
|
-
|
|
820
823
|
// Generate container Bicep file
|
|
824
|
+
const containersDir = path.join(infraDir, "containers");
|
|
821
825
|
const containerFileName = `${modelKebab}-container.bicep`;
|
|
822
826
|
const containerFilePath = path.join(containersDir, containerFileName);
|
|
823
827
|
|
|
@@ -879,26 +883,26 @@ output containerName string = container.name
|
|
|
879
883
|
`;
|
|
880
884
|
|
|
881
885
|
// Write Bicep file (overwrite if exists)
|
|
882
|
-
|
|
886
|
+
session.writeFile(containerFilePath, bicepContent, { ownership: "managed", generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name });
|
|
883
887
|
console.log(`✅ Created: ${containerFilePath}`);
|
|
884
888
|
|
|
885
889
|
// Update main.bicep to include this container module
|
|
886
|
-
await updateMainBicepWithContainer(modelKebab, modelPascal);
|
|
890
|
+
await updateMainBicepWithContainer(modelKebab, modelPascal, session);
|
|
887
891
|
}
|
|
888
892
|
|
|
889
893
|
/**
|
|
890
894
|
* Update main.bicep to include new container module
|
|
891
895
|
*/
|
|
892
|
-
async function updateMainBicepWithContainer(modelKebab: string, modelPascal: string): Promise<void> {
|
|
896
|
+
async function updateMainBicepWithContainer(modelKebab: string, modelPascal: string, session: FileOperationSession): Promise<void> {
|
|
893
897
|
const cwd = process.cwd();
|
|
894
898
|
const mainBicepPath = path.join(cwd, "infra", "main.bicep");
|
|
895
899
|
|
|
896
|
-
if (!
|
|
900
|
+
if (!session.fileExists(mainBicepPath)) {
|
|
897
901
|
console.log("ℹ️ main.bicep not found. Please manually add the container module.");
|
|
898
902
|
return;
|
|
899
903
|
}
|
|
900
904
|
|
|
901
|
-
let mainBicepContent =
|
|
905
|
+
let mainBicepContent = session.readFile(mainBicepPath);
|
|
902
906
|
|
|
903
907
|
// Check if container module already exists
|
|
904
908
|
const containerModuleName = `${modelPascal.charAt(0).toLowerCase()}${modelPascal.slice(1)}Container`;
|
|
@@ -950,14 +954,14 @@ module ${containerModuleName} 'containers/${modelKebab}-container.bicep' = {
|
|
|
950
954
|
containerModule +
|
|
951
955
|
mainBicepContent.slice(insertPosition);
|
|
952
956
|
|
|
953
|
-
|
|
957
|
+
session.writeFile(mainBicepPath, mainBicepContent, { ownership: "extension-point", generator: SCAFFOLD_GENERATOR });
|
|
954
958
|
console.log(`✅ Added container module to main.bicep`);
|
|
955
959
|
}
|
|
956
960
|
|
|
957
961
|
/**
|
|
958
962
|
* Generate UI components (list, detail, form, create, edit pages)
|
|
959
963
|
*/
|
|
960
|
-
export async function generateUIComponents(modelInfo: ModelInfo, sharedPackageName: string, authOptions?: UIAuthOptions): Promise<void> {
|
|
964
|
+
export async function generateUIComponents(modelInfo: ModelInfo, sharedPackageName: string, authOptions?: UIAuthOptions, session: FileOperationSession = new FileOperationSession("commit")): Promise<void> {
|
|
961
965
|
console.log("\n🎨 Generating UI components...");
|
|
962
966
|
|
|
963
967
|
const modelKebab = toKebabCase(modelInfo.name);
|
|
@@ -971,13 +975,6 @@ export async function generateUIComponents(modelInfo: ModelInfo, sharedPackageNa
|
|
|
971
975
|
const idDir = path.join(modelDir, "[id]");
|
|
972
976
|
const editDir = path.join(idDir, "edit");
|
|
973
977
|
|
|
974
|
-
// Create directories
|
|
975
|
-
[componentsDir, newDir, editDir].forEach(dir => {
|
|
976
|
-
if (!fs.existsSync(dir)) {
|
|
977
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
978
|
-
}
|
|
979
|
-
});
|
|
980
|
-
|
|
981
978
|
// Generate and write files
|
|
982
979
|
const listPage = generateListPage(modelInfo, sharedPackageName, authOptions);
|
|
983
980
|
const detailPage = generateDetailPage(modelInfo, sharedPackageName, authOptions);
|
|
@@ -986,13 +983,14 @@ export async function generateUIComponents(modelInfo: ModelInfo, sharedPackageNa
|
|
|
986
983
|
const editPage = generateEditPage(modelInfo, sharedPackageName, authOptions);
|
|
987
984
|
const authLayout = generateAuthLayout(modelInfo, authOptions);
|
|
988
985
|
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
986
|
+
const meta = { ownership: "managed" as const, generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name };
|
|
987
|
+
session.writeFile(path.join(modelDir, "page.tsx"), listPage, meta);
|
|
988
|
+
session.writeFile(path.join(idDir, "page.tsx"), detailPage, meta);
|
|
989
|
+
session.writeFile(path.join(componentsDir, `${modelName}Form.tsx`), formComponent, meta);
|
|
990
|
+
session.writeFile(path.join(newDir, "page.tsx"), newPage, meta);
|
|
991
|
+
session.writeFile(path.join(editDir, "page.tsx"), editPage, meta);
|
|
994
992
|
if (authLayout) {
|
|
995
|
-
|
|
993
|
+
session.writeFile(path.join(modelDir, "layout.tsx"), authLayout, meta);
|
|
996
994
|
}
|
|
997
995
|
|
|
998
996
|
console.log(`✅ Created: ${path.join(modelDir, "page.tsx")}`);
|
|
@@ -1008,7 +1006,7 @@ export async function generateUIComponents(modelInfo: ModelInfo, sharedPackageNa
|
|
|
1008
1006
|
/**
|
|
1009
1007
|
* Update navigation menu on the homepage
|
|
1010
1008
|
*/
|
|
1011
|
-
async function updateNavigationMenu(modelInfo: any): Promise<void> {
|
|
1009
|
+
async function updateNavigationMenu(modelInfo: any, session: FileOperationSession): Promise<void> {
|
|
1012
1010
|
console.log("\n📋 Updating navigation menu...");
|
|
1013
1011
|
|
|
1014
1012
|
const modelKebab = toKebabCase(modelInfo.name);
|
|
@@ -1017,13 +1015,13 @@ async function updateNavigationMenu(modelInfo: any): Promise<void> {
|
|
|
1017
1015
|
// Update scaffold config
|
|
1018
1016
|
const configPath = path.join(cwd, "lib", "scaffold-config.ts");
|
|
1019
1017
|
|
|
1020
|
-
if (!
|
|
1018
|
+
if (!session.fileExists(configPath)) {
|
|
1021
1019
|
console.log("⚠️ scaffold-config.ts not found. Skipping navigation menu update.");
|
|
1022
1020
|
return;
|
|
1023
1021
|
}
|
|
1024
1022
|
|
|
1025
1023
|
// Read existing config
|
|
1026
|
-
const configContent =
|
|
1024
|
+
const configContent = session.readFile(configPath);
|
|
1027
1025
|
|
|
1028
1026
|
// Parse models array - extract each model entry
|
|
1029
1027
|
const models: Array<{ name: string; path: string; label: string }> = [];
|
|
@@ -1070,6 +1068,7 @@ ${models.map(m => ` { name: '${m.name}', path: '${m.path}', label: '${m.label
|
|
|
1070
1068
|
};
|
|
1071
1069
|
`;
|
|
1072
1070
|
|
|
1073
|
-
|
|
1071
|
+
session.writeFile(configPath, newConfigContent, { ownership: "extension-point", generator: SCAFFOLD_GENERATOR });
|
|
1074
1072
|
console.log(`✅ Added ${modelInfo.name} to navigation menu`);
|
|
1075
1073
|
}
|
|
1074
|
+
|