swallowkit 1.0.0-beta.55 → 1.0.0-beta.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/dist/cli/commands/add-auth.d.ts.map +1 -1
  2. package/dist/cli/commands/add-auth.js +154 -157
  3. package/dist/cli/commands/add-auth.js.map +1 -1
  4. package/dist/cli/commands/init.d.ts +5 -0
  5. package/dist/cli/commands/init.d.ts.map +1 -1
  6. package/dist/cli/commands/init.js +178 -25
  7. package/dist/cli/commands/init.js.map +1 -1
  8. package/dist/cli/commands/scaffold.d.ts +7 -2
  9. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  10. package/dist/cli/commands/scaffold.js +113 -123
  11. package/dist/cli/commands/scaffold.js.map +1 -1
  12. package/dist/cli/index.d.ts.map +1 -1
  13. package/dist/cli/index.js +127 -2
  14. package/dist/cli/index.js.map +1 -1
  15. package/dist/core/config.d.ts +1 -1
  16. package/dist/core/config.d.ts.map +1 -1
  17. package/dist/core/config.js +8 -4
  18. package/dist/core/config.js.map +1 -1
  19. package/dist/core/operations/auth-operations.d.ts +57 -0
  20. package/dist/core/operations/auth-operations.d.ts.map +1 -0
  21. package/dist/core/operations/auth-operations.js +269 -0
  22. package/dist/core/operations/auth-operations.js.map +1 -0
  23. package/dist/core/operations/create-model.d.ts.map +1 -1
  24. package/dist/core/operations/create-model.js +23 -13
  25. package/dist/core/operations/create-model.js.map +1 -1
  26. package/dist/core/operations/file-session.d.ts +56 -0
  27. package/dist/core/operations/file-session.d.ts.map +1 -0
  28. package/dist/core/operations/file-session.js +203 -0
  29. package/dist/core/operations/file-session.js.map +1 -0
  30. package/dist/core/operations/provision-operations.d.ts +52 -0
  31. package/dist/core/operations/provision-operations.d.ts.map +1 -0
  32. package/dist/core/operations/provision-operations.js +211 -0
  33. package/dist/core/operations/provision-operations.js.map +1 -0
  34. package/dist/core/operations/runtime.d.ts +5 -0
  35. package/dist/core/operations/runtime.d.ts.map +1 -1
  36. package/dist/core/operations/runtime.js +23 -0
  37. package/dist/core/operations/runtime.js.map +1 -1
  38. package/dist/core/operations/scaffold-apply.d.ts +21 -0
  39. package/dist/core/operations/scaffold-apply.d.ts.map +1 -0
  40. package/dist/core/operations/scaffold-apply.js +57 -0
  41. package/dist/core/operations/scaffold-apply.js.map +1 -0
  42. package/dist/core/operations/scaffold-machine.d.ts.map +1 -1
  43. package/dist/core/operations/scaffold-machine.js +1 -13
  44. package/dist/core/operations/scaffold-machine.js.map +1 -1
  45. package/dist/core/operations/scaffold-plan.d.ts +43 -0
  46. package/dist/core/operations/scaffold-plan.d.ts.map +1 -0
  47. package/dist/core/operations/scaffold-plan.js +187 -0
  48. package/dist/core/operations/scaffold-plan.js.map +1 -0
  49. package/dist/core/project/artifacts.d.ts +58 -0
  50. package/dist/core/project/artifacts.d.ts.map +1 -0
  51. package/dist/core/project/artifacts.js +179 -0
  52. package/dist/core/project/artifacts.js.map +1 -0
  53. package/dist/core/project/boundaries.d.ts +50 -0
  54. package/dist/core/project/boundaries.d.ts.map +1 -0
  55. package/dist/core/project/boundaries.js +117 -0
  56. package/dist/core/project/boundaries.js.map +1 -0
  57. package/dist/core/project/drift.d.ts +29 -0
  58. package/dist/core/project/drift.d.ts.map +1 -0
  59. package/dist/core/project/drift.js +188 -0
  60. package/dist/core/project/drift.js.map +1 -0
  61. package/dist/core/project/infra.d.ts +49 -0
  62. package/dist/core/project/infra.d.ts.map +1 -0
  63. package/dist/core/project/infra.js +167 -0
  64. package/dist/core/project/infra.js.map +1 -0
  65. package/dist/core/project/state.d.ts +15 -0
  66. package/dist/core/project/state.d.ts.map +1 -0
  67. package/dist/core/project/state.js +106 -0
  68. package/dist/core/project/state.js.map +1 -0
  69. package/dist/core/project/workflows.d.ts +43 -0
  70. package/dist/core/project/workflows.d.ts.map +1 -0
  71. package/dist/core/project/workflows.js +255 -0
  72. package/dist/core/project/workflows.js.map +1 -0
  73. package/dist/core/scaffold/auth-generator.d.ts.map +1 -1
  74. package/dist/core/scaffold/auth-generator.js +24 -27
  75. package/dist/core/scaffold/auth-generator.js.map +1 -1
  76. package/dist/core/scaffold/functions-generator.js +9 -9
  77. package/dist/core/scaffold/native-schema-generator.d.ts +7 -1
  78. package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -1
  79. package/dist/core/scaffold/native-schema-generator.js +104 -47
  80. package/dist/core/scaffold/native-schema-generator.js.map +1 -1
  81. package/dist/core/scaffold/nextjs-generator.js +6 -6
  82. package/dist/core/scaffold/ui-generator.js +19 -19
  83. package/dist/core/scaffold/ui-generator.js.map +1 -1
  84. package/dist/core/verify/index.d.ts +62 -0
  85. package/dist/core/verify/index.d.ts.map +1 -0
  86. package/dist/core/verify/index.js +343 -0
  87. package/dist/core/verify/index.js.map +1 -0
  88. package/dist/machine/contracts.d.ts +8 -0
  89. package/dist/machine/contracts.d.ts.map +1 -1
  90. package/dist/machine/errors.d.ts +4 -1
  91. package/dist/machine/errors.d.ts.map +1 -1
  92. package/dist/machine/errors.js +9 -1
  93. package/dist/machine/errors.js.map +1 -1
  94. package/dist/machine/index.d.ts.map +1 -1
  95. package/dist/machine/index.js +350 -16
  96. package/dist/machine/index.js.map +1 -1
  97. package/dist/mcp/index.d.ts.map +1 -1
  98. package/dist/mcp/index.js +202 -1
  99. package/dist/mcp/index.js.map +1 -1
  100. package/dist/types/index.d.ts +14 -0
  101. package/dist/types/index.d.ts.map +1 -1
  102. package/package.json +1 -1
  103. package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +17 -17
  104. package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +6 -6
  105. package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +11 -11
  106. package/src/__tests__/agent-loop-phase34.test.ts +449 -0
  107. package/src/__tests__/file-session.test.ts +98 -0
  108. package/src/__tests__/init.test.ts +50 -0
  109. package/src/__tests__/mcp.test.ts +113 -5
  110. package/src/__tests__/plan-apply.test.ts +461 -0
  111. package/src/__tests__/scaffold.test.ts +2 -2
  112. package/src/cli/commands/add-auth.ts +164 -164
  113. package/src/cli/commands/init.ts +186 -24
  114. package/src/cli/commands/scaffold.ts +136 -130
  115. package/src/cli/index.ts +148 -2
  116. package/src/core/config.ts +5 -4
  117. package/src/core/operations/auth-operations.ts +353 -0
  118. package/src/core/operations/create-model.ts +25 -13
  119. package/src/core/operations/file-session.ts +222 -0
  120. package/src/core/operations/provision-operations.ts +297 -0
  121. package/src/core/operations/runtime.ts +23 -0
  122. package/src/core/operations/scaffold-apply.ts +106 -0
  123. package/src/core/operations/scaffold-machine.ts +2 -16
  124. package/src/core/operations/scaffold-plan.ts +215 -0
  125. package/src/core/project/artifacts.ts +205 -0
  126. package/src/core/project/boundaries.ts +170 -0
  127. package/src/core/project/drift.ts +202 -0
  128. package/src/core/project/infra.ts +196 -0
  129. package/src/core/project/state.ts +77 -0
  130. package/src/core/project/workflows.ts +249 -0
  131. package/src/core/scaffold/auth-generator.ts +24 -27
  132. package/src/core/scaffold/functions-generator.ts +9 -9
  133. package/src/core/scaffold/native-schema-generator.ts +152 -70
  134. package/src/core/scaffold/nextjs-generator.ts +6 -6
  135. package/src/core/scaffold/ui-generator.ts +19 -19
  136. package/src/core/verify/index.ts +392 -0
  137. package/src/machine/contracts.ts +16 -0
  138. package/src/machine/errors.ts +12 -1
  139. package/src/machine/index.ts +444 -15
  140. package/src/mcp/index.ts +198 -2
  141. package/src/types/index.ts +17 -0
@@ -39,6 +39,7 @@ var __importStar = (this && this.__importStar) || (function () {
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
40
  exports.scaffoldCommand = scaffoldCommand;
41
41
  exports.resolveAuthPolicy = resolveAuthPolicy;
42
+ exports.resolveModelPath = resolveModelPath;
42
43
  exports.createUIAuthOptions = createUIAuthOptions;
43
44
  exports.generateCosmosContainer = generateCosmosContainer;
44
45
  exports.generateUIComponents = generateUIComponents;
@@ -55,6 +56,9 @@ const native_schema_generator_1 = require("../../core/scaffold/native-schema-gen
55
56
  const package_manager_1 = require("../../utils/package-manager");
56
57
  const manifest_1 = require("../../core/project/manifest");
57
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";
58
62
  function getMachineAwareStdio() {
59
63
  return process.env.SWALLOWKIT_MACHINE_OUTPUT === "1" ? "pipe" : "inherit";
60
64
  }
@@ -77,6 +81,8 @@ async function scaffoldCommand(options) {
77
81
  const configValidation = (0, config_1.validateConfig)((0, config_1.getFullConfig)());
78
82
  if (!configValidation.valid)
79
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");
80
86
  console.log("🏗️ SwallowKit Scaffold: Generating CRUD operations...\n");
81
87
  try {
82
88
  // 1. Resolve model file path
@@ -113,34 +119,41 @@ async function scaffoldCommand(options) {
113
119
  const relatedModels = backendLanguage === "typescript"
114
120
  ? [modelInfo]
115
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
+ }
116
129
  if (options.apiOnly) {
117
130
  console.log("ℹ️ --api-only skips UI generation only; Functions, BFF routes, OpenAPI, and native schema assets are still updated.");
118
131
  }
119
132
  // 5. Generate BFF callFunction helper
120
- await generateCallFunctionHelper();
133
+ await generateCallFunctionHelper(session);
121
134
  // 6. Generate Azure Functions code
122
135
  if (isConnectorModel) {
123
- await generateConnectorFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage);
136
+ await generateConnectorFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage, session);
124
137
  // 6b. Install connector driver dependencies
125
- await installConnectorDriverDependencies(modelInfo, functionsDir, backendLanguage);
138
+ await installConnectorDriverDependencies(modelInfo, functionsDir, backendLanguage, session);
126
139
  }
127
140
  else {
128
- await generateFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage);
141
+ await generateFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage, session);
129
142
  if (backendLanguage !== "typescript") {
130
- await (0, native_schema_generator_1.generateLanguageSchemaArtifacts)(relatedModels, modelInfo, functionsDir, backendLanguage);
143
+ await (0, native_schema_generator_1.generateLanguageSchemaArtifacts)(relatedModels, modelInfo, functionsDir, backendLanguage, session);
131
144
  }
132
145
  }
133
146
  // 7. Generate Next.js BFF API Routes
134
147
  const apiDir = options.apiDir || "app/api";
135
148
  if (isConnectorModel) {
136
- await generateConnectorBFFRoutesFiles(modelInfo, apiDir, sharedPackageName);
149
+ await generateConnectorBFFRoutesFiles(modelInfo, apiDir, sharedPackageName, session);
137
150
  }
138
151
  else {
139
- await generateBFFRoutes(modelInfo, apiDir, sharedPackageName);
152
+ await generateBFFRoutes(modelInfo, apiDir, sharedPackageName, session);
140
153
  }
141
154
  // 8. Generate Cosmos DB container Bicep file (skip for connector models)
142
155
  if (!isConnectorModel) {
143
- await generateCosmosContainer(modelInfo);
156
+ await generateCosmosContainer(modelInfo, session);
144
157
  }
145
158
  // 9. Generate UI components (unless --api-only)
146
159
  if (!options.apiOnly) {
@@ -149,13 +162,19 @@ async function scaffoldCommand(options) {
149
162
  const uiAuthOptions = uiAuthPolicy && uiAuthConfig && uiAuthConfig.provider !== 'none'
150
163
  ? createUIAuthOptions(uiAuthPolicy, uiAuthConfig)
151
164
  : undefined;
152
- await generateUIComponents(modelInfo, sharedPackageName, uiAuthOptions);
165
+ await generateUIComponents(modelInfo, sharedPackageName, uiAuthOptions, session);
153
166
  if (uiAuthOptions?.authenticatedFetchImport) {
154
- applyExternalTokenFetchToGeneratedUi(modelInfo, uiAuthOptions.authenticatedFetchImport);
167
+ applyExternalTokenFetchToGeneratedUi(modelInfo, session, uiAuthOptions.authenticatedFetchImport);
155
168
  }
156
- await updateNavigationMenu(modelInfo);
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)();
157
177
  }
158
- await (0, manifest_1.syncProjectManifest)();
159
178
  console.log("\n✅ Scaffold completed successfully!");
160
179
  console.log("\n📝 Next steps:");
161
180
  console.log(` 1. Review generated files in ${describeFunctionsOutputPath(functionsDir, backendLanguage)} and ${apiDir}/`);
@@ -175,32 +194,26 @@ async function scaffoldCommand(options) {
175
194
  process.exit(1);
176
195
  }
177
196
  }
178
- function applyExternalTokenFetchToGeneratedUi(modelInfo, importPath = '@/lib/auth/authenticated-fetch') {
179
- const uiRoot = path.join(process.cwd(), 'app', (0, model_parser_1.toKebabCase)(modelInfo.name));
180
- if (!fs.existsSync(uiRoot))
181
- return;
182
- const visit = (dir) => {
183
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
184
- const target = path.join(dir, entry.name);
185
- if (entry.isDirectory()) {
186
- visit(target);
187
- continue;
188
- }
189
- if (!entry.name.endsWith('.tsx'))
190
- continue;
191
- let content = fs.readFileSync(target, 'utf-8');
192
- if (!content.includes('fetch('))
193
- continue;
194
- content = content.replace(/\bfetch\(/g, 'authenticatedFetch(');
195
- content = content.replace(/(if \(!res\.ok\) throw new Error\([^\n]+\);)/g, `if (res.status === 401) throw new Error('Authentication required');\n $1`);
196
- if (!content.includes(importPath)) {
197
- const directive = content.match(/^'use client';\s*\r?\n/)?.[0] ?? '';
198
- content = `${directive}import { authenticatedFetch } from '${importPath}';\n${content.slice(directive.length)}`;
199
- }
200
- fs.writeFileSync(target, content, 'utf-8');
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)}`;
201
210
  }
202
- };
203
- visit(uiRoot);
211
+ session.writeFile(operation.absolutePath, content, {
212
+ ownership: 'managed',
213
+ generator: SCAFFOLD_GENERATOR,
214
+ sourceModel: modelInfo.name,
215
+ });
216
+ }
204
217
  }
205
218
  /**
206
219
  * モデルの authPolicy と auth config から実効的な認可ポリシーを解決
@@ -289,14 +302,11 @@ function readSharedPackageName() {
289
302
  /**
290
303
  * Generate BFF callFunction helper (lib/api/call-function.ts)
291
304
  */
292
- async function generateCallFunctionHelper() {
305
+ async function generateCallFunctionHelper(session) {
293
306
  console.log("\n📦 Generating BFF callFunction helper...");
294
307
  const cwd = process.cwd();
295
308
  const helperDir = path.join(cwd, "lib", "api");
296
309
  const helperPath = path.join(helperDir, "call-function.ts");
297
- if (!fs.existsSync(helperDir)) {
298
- fs.mkdirSync(helperDir, { recursive: true });
299
- }
300
310
  // auth 設定がある場合は Authorization ヘッダー転送版を生成
301
311
  const authConfig = (0, config_1.getAuthConfig)();
302
312
  const normalizedAuth = (0, config_1.normalizeAuthConfig)(authConfig);
@@ -311,13 +321,13 @@ async function generateCallFunctionHelper() {
311
321
  else {
312
322
  helperCode = (0, nextjs_generator_1.generateBFFCallFunction)();
313
323
  }
314
- fs.writeFileSync(helperPath, helperCode, "utf-8");
324
+ session.writeFile(helperPath, helperCode, { ownership: "managed", generator: SCAFFOLD_GENERATOR });
315
325
  console.log(`✅ Created: ${helperPath}`);
316
326
  }
317
327
  /**
318
328
  * Generate Azure Functions CRUD code
319
329
  */
320
- async function generateFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage) {
330
+ async function generateFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage, session) {
321
331
  console.log("\n🔨 Generating Azure Functions CRUD code...");
322
332
  // Resolve auth policy: model-level authPolicy or global defaultPolicy
323
333
  const authConfig = (0, config_1.getAuthConfig)();
@@ -326,20 +336,16 @@ async function generateFunctionsCode(modelInfo, functionsDir, sharedPackageName,
326
336
  console.log(`🔐 Auth policy detected: ${JSON.stringify(authPolicy)}`);
327
337
  }
328
338
  const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
339
+ const managedMeta = { ownership: "managed", generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name };
329
340
  if (backendLanguage === "typescript") {
330
341
  const normalizedAuth = (0, config_1.getNormalizedAuthConfig)();
331
342
  if (normalizedAuth && Object.keys(normalizedAuth.schemes).some(name => name !== "default")) {
332
343
  const routerPath = path.join(process.cwd(), functionsDir, "src", "auth", "auth-router.ts");
333
- fs.mkdirSync(path.dirname(routerPath), { recursive: true });
334
- fs.writeFileSync(routerPath, (0, auth_generator_1.generateNamedAuthRouterTS)(normalizedAuth), "utf-8");
344
+ session.writeFile(routerPath, (0, auth_generator_1.generateNamedAuthRouterTS)(normalizedAuth), { ownership: "managed", generator: SCAFFOLD_GENERATOR });
335
345
  }
336
346
  const functionFilePath = path.join(process.cwd(), functionsDir, "src", `${modelKebab}.ts`);
337
- const functionDir = path.dirname(functionFilePath);
338
- if (!fs.existsSync(functionDir)) {
339
- fs.mkdirSync(functionDir, { recursive: true });
340
- }
341
347
  const code = (0, functions_generator_1.generateCompactAzureFunctionsCRUD)(modelInfo, sharedPackageName, authPolicy, authConfig?.provider);
342
- fs.writeFileSync(functionFilePath, code, "utf-8");
348
+ session.writeFile(functionFilePath, code, managedMeta);
343
349
  console.log(`✅ Created: ${functionFilePath}`);
344
350
  return;
345
351
  }
@@ -347,34 +353,30 @@ async function generateFunctionsCode(modelInfo, functionsDir, sharedPackageName,
347
353
  const normalizedAuth = (0, config_1.getNormalizedAuthConfig)();
348
354
  if (normalizedAuth && Object.keys(normalizedAuth.schemes).some(name => name !== "default")) {
349
355
  const routerPath = path.join(process.cwd(), functionsDir, "Auth", "AuthRouter.cs");
350
- fs.mkdirSync(path.dirname(routerPath), { recursive: true });
351
- fs.writeFileSync(routerPath, (0, auth_generator_1.generateNamedAuthRouterCSharp)(normalizedAuth), "utf-8");
356
+ session.writeFile(routerPath, (0, auth_generator_1.generateNamedAuthRouterCSharp)(normalizedAuth), { ownership: "managed", generator: SCAFFOLD_GENERATOR });
352
357
  }
353
358
  const crudDir = path.join(process.cwd(), functionsDir, "Crud");
354
359
  const functionFilePath = path.join(crudDir, `${modelInfo.name}CrudFunctions.cs`);
355
- fs.mkdirSync(crudDir, { recursive: true });
356
360
  // Remove init-generated template (singular) to avoid route conflicts
357
361
  const templatePath = path.join(crudDir, `${modelInfo.name}Function.cs`);
358
- if (fs.existsSync(templatePath)) {
359
- fs.unlinkSync(templatePath);
362
+ if (session.fileExists(templatePath)) {
363
+ session.deleteFile(templatePath, managedMeta);
360
364
  console.log(`🗑️ Removed template: ${templatePath}`);
361
365
  }
362
- fs.writeFileSync(functionFilePath, (0, functions_generator_1.generateCSharpAzureFunctionsCRUD)(modelInfo, authPolicy), "utf-8");
366
+ session.writeFile(functionFilePath, (0, functions_generator_1.generateCSharpAzureFunctionsCRUD)(modelInfo, authPolicy), managedMeta);
363
367
  console.log(`✅ Created: ${functionFilePath}`);
364
368
  return;
365
369
  }
366
370
  const normalizedAuth = (0, config_1.getNormalizedAuthConfig)();
367
371
  if (normalizedAuth && Object.keys(normalizedAuth.schemes).some(name => name !== "default")) {
368
372
  const routerPath = path.join(process.cwd(), functionsDir, "auth", "auth_router.py");
369
- fs.mkdirSync(path.dirname(routerPath), { recursive: true });
370
- fs.writeFileSync(routerPath, (0, auth_generator_1.generateNamedAuthRouterPython)(normalizedAuth), "utf-8");
373
+ session.writeFile(routerPath, (0, auth_generator_1.generateNamedAuthRouterPython)(normalizedAuth), { ownership: "managed", generator: SCAFFOLD_GENERATOR });
371
374
  }
372
375
  const blueprintsDir = path.join(process.cwd(), functionsDir, "blueprints");
373
376
  const blueprintPath = path.join(blueprintsDir, `${modelKebab.replace(/-/g, "_")}.py`);
374
- fs.mkdirSync(blueprintsDir, { recursive: true });
375
377
  const { blueprint, registration } = (0, functions_generator_1.generatePythonAzureFunctionsCRUD)(modelInfo, authPolicy);
376
- fs.writeFileSync(blueprintPath, blueprint, "utf-8");
377
- updatePythonFunctionRegistrations(path.join(process.cwd(), functionsDir, "function_app.py"), registration);
378
+ session.writeFile(blueprintPath, blueprint, managedMeta);
379
+ updatePythonFunctionRegistrations(path.join(process.cwd(), functionsDir, "function_app.py"), registration, session);
378
380
  console.log(`✅ Created: ${blueprintPath}`);
379
381
  }
380
382
  function createUIAuthOptions(policy, authConfig) {
@@ -393,7 +395,7 @@ function createUIAuthOptions(policy, authConfig) {
393
395
  /**
394
396
  * コネクタモデル用 Azure Functions コード生成
395
397
  */
396
- async function generateConnectorFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage) {
398
+ async function generateConnectorFunctionsCode(modelInfo, functionsDir, sharedPackageName, backendLanguage, session) {
397
399
  console.log("\n🔌 Generating Connector Azure Functions code...");
398
400
  const connectorConfig = modelInfo.connectorConfig;
399
401
  const connectorDef = (0, config_1.getConnectorDefinition)(connectorConfig.connector);
@@ -408,9 +410,9 @@ async function generateConnectorFunctionsCode(modelInfo, functionsDir, sharedPac
408
410
  console.log(`🔐 Auth policy detected: ${JSON.stringify(authPolicy)}`);
409
411
  }
410
412
  const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
413
+ const connectorManagedMeta = { ownership: "managed", generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name };
411
414
  if (backendLanguage === "typescript") {
412
415
  const functionFilePath = path.join(process.cwd(), functionsDir, "src", `${modelKebab}.ts`);
413
- fs.mkdirSync(path.dirname(functionFilePath), { recursive: true });
414
416
  let code;
415
417
  if (connectorDef.type === "rdb") {
416
418
  code = (0, connector_functions_generator_1.generateRdbConnectorFunctionTS)(modelInfo, sharedPackageName, connectorDef, connectorConfig, authPolicy, authConfig?.provider);
@@ -418,13 +420,12 @@ async function generateConnectorFunctionsCode(modelInfo, functionsDir, sharedPac
418
420
  else {
419
421
  code = (0, connector_functions_generator_1.generateApiConnectorFunctionTS)(modelInfo, sharedPackageName, connectorDef, connectorConfig, authPolicy, authConfig?.provider);
420
422
  }
421
- fs.writeFileSync(functionFilePath, code, "utf-8");
423
+ session.writeFile(functionFilePath, code, connectorManagedMeta);
422
424
  console.log(`✅ Created: ${functionFilePath}`);
423
425
  return;
424
426
  }
425
427
  if (backendLanguage === "csharp") {
426
428
  const functionFilePath = path.join(process.cwd(), functionsDir, "Connectors", `${modelInfo.name}ConnectorFunctions.cs`);
427
- fs.mkdirSync(path.dirname(functionFilePath), { recursive: true });
428
429
  let code;
429
430
  if (connectorDef.type === "rdb") {
430
431
  code = (0, connector_functions_generator_1.generateRdbConnectorFunctionCSharp)(modelInfo, connectorDef, connectorConfig);
@@ -432,14 +433,13 @@ async function generateConnectorFunctionsCode(modelInfo, functionsDir, sharedPac
432
433
  else {
433
434
  code = (0, connector_functions_generator_1.generateApiConnectorFunctionCSharp)(modelInfo, connectorDef, connectorConfig);
434
435
  }
435
- fs.writeFileSync(functionFilePath, code, "utf-8");
436
+ session.writeFile(functionFilePath, code, connectorManagedMeta);
436
437
  console.log(`✅ Created: ${functionFilePath}`);
437
438
  return;
438
439
  }
439
440
  // Python
440
441
  const blueprintsDir = path.join(process.cwd(), functionsDir, "blueprints");
441
442
  const blueprintPath = path.join(blueprintsDir, `${modelKebab.replace(/-/g, "_")}.py`);
442
- fs.mkdirSync(blueprintsDir, { recursive: true });
443
443
  let result;
444
444
  if (connectorDef.type === "rdb") {
445
445
  result = (0, connector_functions_generator_1.generateRdbConnectorFunctionPython)(modelInfo, connectorDef, connectorConfig);
@@ -447,15 +447,15 @@ async function generateConnectorFunctionsCode(modelInfo, functionsDir, sharedPac
447
447
  else {
448
448
  result = (0, connector_functions_generator_1.generateApiConnectorFunctionPython)(modelInfo, connectorDef, connectorConfig);
449
449
  }
450
- fs.writeFileSync(blueprintPath, result.blueprint, "utf-8");
451
- updatePythonFunctionRegistrations(path.join(process.cwd(), functionsDir, "function_app.py"), result.registration);
450
+ session.writeFile(blueprintPath, result.blueprint, connectorManagedMeta);
451
+ updatePythonFunctionRegistrations(path.join(process.cwd(), functionsDir, "function_app.py"), result.registration, session);
452
452
  console.log(`✅ Created: ${blueprintPath}`);
453
453
  }
454
454
  /**
455
455
  * コネクタモデルの scaffold 後に、生成コードが必要とする RDB/API ドライバを
456
456
  * functions ディレクトリにインストールする。
457
457
  */
458
- async function installConnectorDriverDependencies(modelInfo, functionsDir, backendLanguage) {
458
+ async function installConnectorDriverDependencies(modelInfo, functionsDir, backendLanguage, session) {
459
459
  const connectorConfig = modelInfo.connectorConfig;
460
460
  const connectorDef = (0, config_1.getConnectorDefinition)(connectorConfig.connector);
461
461
  if (!connectorDef || connectorDef.type !== "rdb")
@@ -481,6 +481,10 @@ async function installConnectorDriverDependencies(modelInfo, functionsDir, backe
481
481
  if (missingDeps.length === 0 && missingDevDeps.length === 0)
482
482
  return;
483
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
+ }
484
488
  console.log(`\n📦 Installing ${rdbDef.provider} driver dependencies...`);
485
489
  const pm = (0, package_manager_1.detectFromProject)(functionsPath);
486
490
  const cmds = (0, package_manager_1.getCommands)(pm);
@@ -502,6 +506,10 @@ async function installConnectorDriverDependencies(modelInfo, functionsDir, backe
502
506
  const pkg = nugetMap[rdbDef.provider];
503
507
  if (!pkg)
504
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
+ }
505
513
  console.log(`\n📦 Installing ${rdbDef.provider} NuGet package...`);
506
514
  runSpawnSyncCommand("dotnet", ["add", path.join(functionsPath, "functions.csproj"), "package", pkg], functionsPath);
507
515
  console.log(`✅ ${pkg} installed`);
@@ -517,11 +525,11 @@ async function installConnectorDriverDependencies(modelInfo, functionsDir, backe
517
525
  if (!pipPkg)
518
526
  return;
519
527
  const reqPath = path.join(functionsPath, "requirements.txt");
520
- if (fs.existsSync(reqPath)) {
521
- const existing = fs.readFileSync(reqPath, "utf-8");
528
+ if (session.fileExists(reqPath)) {
529
+ const existing = session.readFile(reqPath);
522
530
  if (!existing.includes(pipPkg)) {
523
531
  console.log(`\n📦 Adding ${pipPkg} to requirements.txt...`);
524
- fs.appendFileSync(reqPath, `\n${pipPkg}\n`);
532
+ session.writeFile(reqPath, `${existing}\n${pipPkg}\n`, { ownership: "extension-point", generator: SCAFFOLD_GENERATOR });
525
533
  console.log(`✅ ${pipPkg} added`);
526
534
  }
527
535
  }
@@ -529,17 +537,16 @@ async function installConnectorDriverDependencies(modelInfo, functionsDir, backe
529
537
  /**
530
538
  * コネクタモデル用 BFF ルート生成(操作制限に対応)
531
539
  */
532
- async function generateConnectorBFFRoutesFiles(modelInfo, apiDir, sharedPackageName) {
540
+ async function generateConnectorBFFRoutesFiles(modelInfo, apiDir, sharedPackageName, session) {
533
541
  console.log("\n🔌 Generating Connector BFF API routes...");
534
542
  const modelCamel = (0, model_parser_1.toCamelCase)(modelInfo.name);
535
543
  const operations = modelInfo.connectorConfig.operations;
536
544
  const listRoutePath = path.join(process.cwd(), apiDir, modelCamel, "route.ts");
537
545
  const detailRoutePath = path.join(process.cwd(), apiDir, modelCamel, "[id]", "route.ts");
538
- fs.mkdirSync(path.dirname(listRoutePath), { recursive: true });
539
- fs.mkdirSync(path.dirname(detailRoutePath), { recursive: true });
540
546
  const routes = (0, nextjs_generator_1.generateConnectorBFFRoutes)(modelInfo, sharedPackageName, operations);
541
- fs.writeFileSync(listRoutePath, routes.listRoute, "utf-8");
542
- fs.writeFileSync(detailRoutePath, routes.detailRoute, "utf-8");
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);
543
550
  console.log(`✅ Created: ${listRoutePath}`);
544
551
  console.log(`✅ Created: ${detailRoutePath}`);
545
552
  }
@@ -572,11 +579,11 @@ function describeFunctionsOutputPath(functionsDir, backendLanguage) {
572
579
  }
573
580
  return `${functionsDir}/blueprints/`;
574
581
  }
575
- function updatePythonFunctionRegistrations(functionAppPath, registration) {
576
- if (!fs.existsSync(functionAppPath)) {
582
+ function updatePythonFunctionRegistrations(functionAppPath, registration, session) {
583
+ if (!session.fileExists(functionAppPath)) {
577
584
  throw new Error(`Python Functions entrypoint not found: ${functionAppPath}`);
578
585
  }
579
- const content = fs.readFileSync(functionAppPath, "utf-8");
586
+ const content = session.readFile(functionAppPath);
580
587
  // Check if import line already exists (handles init-generated layout)
581
588
  const importLine = registration.split("\n").find((l) => l.startsWith("from ") || l.startsWith("import "));
582
589
  if (importLine && content.includes(importLine)) {
@@ -590,39 +597,31 @@ function updatePythonFunctionRegistrations(functionAppPath, registration) {
590
597
  throw new Error(`Could not find scaffold registration marker in ${functionAppPath}`);
591
598
  }
592
599
  const updated = content.replace(marker, `${registration}\n${marker}`);
593
- fs.writeFileSync(functionAppPath, updated, "utf-8");
600
+ session.writeFile(functionAppPath, updated, { ownership: "extension-point", generator: SCAFFOLD_GENERATOR });
594
601
  }
595
602
  /**
596
603
  * Next.js BFF API Routes を生成
597
604
  */
598
- async function generateBFFRoutes(modelInfo, apiDir, sharedPackageName) {
605
+ async function generateBFFRoutes(modelInfo, apiDir, sharedPackageName, session) {
599
606
  console.log("\n🔨 Generating Next.js BFF API routes...");
600
607
  const modelCamel = modelInfo.name.charAt(0).toLowerCase() + modelInfo.name.slice(1);
601
608
  // List route: app/api/[model]/route.ts
602
609
  const listRoutePath = path.join(process.cwd(), apiDir, modelCamel, "route.ts");
603
610
  // Detail route: app/api/[model]/[id]/route.ts
604
611
  const detailRoutePath = path.join(process.cwd(), apiDir, modelCamel, "[id]", "route.ts");
605
- // ディレクトリを作成
606
- const listRouteDir = path.dirname(listRoutePath);
607
- const detailRouteDir = path.dirname(detailRoutePath);
608
- if (!fs.existsSync(listRouteDir)) {
609
- fs.mkdirSync(listRouteDir, { recursive: true });
610
- }
611
- if (!fs.existsSync(detailRouteDir)) {
612
- fs.mkdirSync(detailRouteDir, { recursive: true });
613
- }
614
612
  // コードを生成
615
613
  const routes = (0, nextjs_generator_1.generateCompactBFFRoutes)(modelInfo, sharedPackageName);
616
614
  // ファイルに書き込み
617
- fs.writeFileSync(listRoutePath, routes.listRoute, "utf-8");
618
- fs.writeFileSync(detailRoutePath, routes.detailRoute, "utf-8");
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);
619
618
  console.log(`✅ Created: ${listRoutePath}`);
620
619
  console.log(`✅ Created: ${detailRoutePath}`);
621
620
  }
622
621
  /**
623
622
  * Generate Cosmos DB container Bicep file
624
623
  */
625
- async function generateCosmosContainer(modelInfo) {
624
+ async function generateCosmosContainer(modelInfo, session = new file_session_1.FileOperationSession("commit")) {
626
625
  console.log("\n🗄️ Generating Cosmos DB container Bicep file...");
627
626
  const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
628
627
  const modelPascal = (0, model_parser_1.toPascalCase)(modelInfo.name);
@@ -633,12 +632,8 @@ async function generateCosmosContainer(modelInfo) {
633
632
  console.log("ℹ️ infra directory not found. Skipping Cosmos DB container generation.");
634
633
  return;
635
634
  }
636
- // Check if containers directory exists, if not create it
637
- const containersDir = path.join(infraDir, "containers");
638
- if (!fs.existsSync(containersDir)) {
639
- fs.mkdirSync(containersDir, { recursive: true });
640
- }
641
635
  // Generate container Bicep file
636
+ const containersDir = path.join(infraDir, "containers");
642
637
  const containerFileName = `${modelKebab}-container.bicep`;
643
638
  const containerFilePath = path.join(containersDir, containerFileName);
644
639
  const bicepContent = `@description('Cosmos DB account name')
@@ -698,22 +693,22 @@ resource container 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/container
698
693
  output containerName string = container.name
699
694
  `;
700
695
  // Write Bicep file (overwrite if exists)
701
- fs.writeFileSync(containerFilePath, bicepContent, "utf-8");
696
+ session.writeFile(containerFilePath, bicepContent, { ownership: "managed", generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name });
702
697
  console.log(`✅ Created: ${containerFilePath}`);
703
698
  // Update main.bicep to include this container module
704
- await updateMainBicepWithContainer(modelKebab, modelPascal);
699
+ await updateMainBicepWithContainer(modelKebab, modelPascal, session);
705
700
  }
706
701
  /**
707
702
  * Update main.bicep to include new container module
708
703
  */
709
- async function updateMainBicepWithContainer(modelKebab, modelPascal) {
704
+ async function updateMainBicepWithContainer(modelKebab, modelPascal, session) {
710
705
  const cwd = process.cwd();
711
706
  const mainBicepPath = path.join(cwd, "infra", "main.bicep");
712
- if (!fs.existsSync(mainBicepPath)) {
707
+ if (!session.fileExists(mainBicepPath)) {
713
708
  console.log("ℹ️ main.bicep not found. Please manually add the container module.");
714
709
  return;
715
710
  }
716
- let mainBicepContent = fs.readFileSync(mainBicepPath, "utf-8");
711
+ let mainBicepContent = session.readFile(mainBicepPath);
717
712
  // Check if container module already exists
718
713
  const containerModuleName = `${modelPascal.charAt(0).toLowerCase()}${modelPascal.slice(1)}Container`;
719
714
  if (mainBicepContent.includes(`module ${containerModuleName}`)) {
@@ -758,13 +753,13 @@ module ${containerModuleName} 'containers/${modelKebab}-container.bicep' = {
758
753
  mainBicepContent.slice(0, insertPosition) +
759
754
  containerModule +
760
755
  mainBicepContent.slice(insertPosition);
761
- fs.writeFileSync(mainBicepPath, mainBicepContent, "utf-8");
756
+ session.writeFile(mainBicepPath, mainBicepContent, { ownership: "extension-point", generator: SCAFFOLD_GENERATOR });
762
757
  console.log(`✅ Added container module to main.bicep`);
763
758
  }
764
759
  /**
765
760
  * Generate UI components (list, detail, form, create, edit pages)
766
761
  */
767
- async function generateUIComponents(modelInfo, sharedPackageName, authOptions) {
762
+ async function generateUIComponents(modelInfo, sharedPackageName, authOptions, session = new file_session_1.FileOperationSession("commit")) {
768
763
  console.log("\n🎨 Generating UI components...");
769
764
  const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
770
765
  const modelName = modelInfo.name;
@@ -775,12 +770,6 @@ async function generateUIComponents(modelInfo, sharedPackageName, authOptions) {
775
770
  const newDir = path.join(modelDir, "new");
776
771
  const idDir = path.join(modelDir, "[id]");
777
772
  const editDir = path.join(idDir, "edit");
778
- // Create directories
779
- [componentsDir, newDir, editDir].forEach(dir => {
780
- if (!fs.existsSync(dir)) {
781
- fs.mkdirSync(dir, { recursive: true });
782
- }
783
- });
784
773
  // Generate and write files
785
774
  const listPage = (0, ui_generator_1.generateListPage)(modelInfo, sharedPackageName, authOptions);
786
775
  const detailPage = (0, ui_generator_1.generateDetailPage)(modelInfo, sharedPackageName, authOptions);
@@ -788,13 +777,14 @@ async function generateUIComponents(modelInfo, sharedPackageName, authOptions) {
788
777
  const newPage = (0, ui_generator_1.generateNewPage)(modelInfo, authOptions);
789
778
  const editPage = (0, ui_generator_1.generateEditPage)(modelInfo, sharedPackageName, authOptions);
790
779
  const authLayout = (0, ui_generator_1.generateAuthLayout)(modelInfo, authOptions);
791
- fs.writeFileSync(path.join(modelDir, "page.tsx"), listPage, "utf-8");
792
- fs.writeFileSync(path.join(idDir, "page.tsx"), detailPage, "utf-8");
793
- fs.writeFileSync(path.join(componentsDir, `${modelName}Form.tsx`), formComponent, "utf-8");
794
- fs.writeFileSync(path.join(newDir, "page.tsx"), newPage, "utf-8");
795
- fs.writeFileSync(path.join(editDir, "page.tsx"), editPage, "utf-8");
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);
796
786
  if (authLayout) {
797
- fs.writeFileSync(path.join(modelDir, "layout.tsx"), authLayout, "utf-8");
787
+ session.writeFile(path.join(modelDir, "layout.tsx"), authLayout, meta);
798
788
  }
799
789
  console.log(`✅ Created: ${path.join(modelDir, "page.tsx")}`);
800
790
  console.log(`✅ Created: ${path.join(idDir, "page.tsx")}`);
@@ -808,18 +798,18 @@ async function generateUIComponents(modelInfo, sharedPackageName, authOptions) {
808
798
  /**
809
799
  * Update navigation menu on the homepage
810
800
  */
811
- async function updateNavigationMenu(modelInfo) {
801
+ async function updateNavigationMenu(modelInfo, session) {
812
802
  console.log("\n📋 Updating navigation menu...");
813
803
  const modelKebab = (0, model_parser_1.toKebabCase)(modelInfo.name);
814
804
  const cwd = process.cwd();
815
805
  // Update scaffold config
816
806
  const configPath = path.join(cwd, "lib", "scaffold-config.ts");
817
- if (!fs.existsSync(configPath)) {
807
+ if (!session.fileExists(configPath)) {
818
808
  console.log("⚠️ scaffold-config.ts not found. Skipping navigation menu update.");
819
809
  return;
820
810
  }
821
811
  // Read existing config
822
- const configContent = fs.readFileSync(configPath, "utf-8");
812
+ const configContent = session.readFile(configPath);
823
813
  // Parse models array - extract each model entry
824
814
  const models = [];
825
815
  const modelsMatch = configContent.match(/models:\s*\[([\s\S]*?)\]\s*as\s*ScaffoldModel\[\]/);
@@ -860,7 +850,7 @@ ${models.map(m => ` { name: '${m.name}', path: '${m.path}', label: '${m.label
860
850
  ] as ScaffoldModel[]
861
851
  };
862
852
  `;
863
- fs.writeFileSync(configPath, newConfigContent, "utf-8");
853
+ session.writeFile(configPath, newConfigContent, { ownership: "extension-point", generator: SCAFFOLD_GENERATOR });
864
854
  console.log(`✅ Added ${modelInfo.name} to navigation menu`);
865
855
  }
866
856
  //# sourceMappingURL=scaffold.js.map