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