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
@@ -1,7 +1,9 @@
1
1
  import * as fs from "fs";
2
+ import * as os from "os";
2
3
  import * as path from "path";
3
4
  import { spawn, spawnSync } from "child_process";
4
5
  import { BackendLanguage } from "../../types";
6
+ import { FileOperationSession } from "../operations/file-session";
5
7
  import { ModelInfo, toKebabCase } from "./model-parser";
6
8
  import { generateOpenApiDocument } from "./openapi-generator";
7
9
  import {
@@ -621,23 +623,47 @@ function mergePythonInitSource(existingSource: string | undefined, generatedSour
621
623
  return `${normalizedExisting}${missingLines.join("\n")}\n`;
622
624
  }
623
625
 
624
- function writeMergedPythonInitFile(filePath: string, generatedSource: string): void {
625
- const existingSource = fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf-8") : undefined;
626
- fs.writeFileSync(filePath, mergePythonInitSource(existingSource, generatedSource), "utf-8");
626
+ function writeMergedPythonInitFile(
627
+ filePath: string,
628
+ generatedSource: string,
629
+ session: FileOperationSession,
630
+ sourceModel: string
631
+ ): void {
632
+ const existingSource = session.fileExists(filePath) ? session.readFile(filePath) : undefined;
633
+ session.writeFile(filePath, mergePythonInitSource(existingSource, generatedSource), {
634
+ ownership: "extension-point",
635
+ generator: "native-schema",
636
+ sourceModel,
637
+ });
627
638
  }
628
639
 
629
- function ensureCSharpCodegenProjectFiles(functionsRoot: string): void {
640
+ function ensureCSharpCodegenProjectFiles(
641
+ functionsRoot: string,
642
+ session: FileOperationSession,
643
+ sourceModel: string
644
+ ): void {
630
645
  const toolManifestPath = path.join(functionsRoot, ".config", "dotnet-tools.json");
631
- fs.mkdirSync(path.dirname(toolManifestPath), { recursive: true });
632
- if (!fs.existsSync(toolManifestPath)) {
633
- fs.writeFileSync(toolManifestPath, buildCSharpCodegenToolManifestSource(), "utf-8");
646
+ if (!session.fileExists(toolManifestPath)) {
647
+ session.writeFile(toolManifestPath, buildCSharpCodegenToolManifestSource(), {
648
+ ownership: "generated-once",
649
+ generator: "native-schema",
650
+ sourceModel,
651
+ });
634
652
  }
635
653
  }
636
654
 
637
- function ensurePythonCodegenProjectFiles(functionsRoot: string): string {
655
+ function ensurePythonCodegenProjectFiles(
656
+ functionsRoot: string,
657
+ session: FileOperationSession,
658
+ sourceModel: string
659
+ ): string {
638
660
  const requirementsPath = path.join(functionsRoot, "requirements.codegen.txt");
639
- if (!fs.existsSync(requirementsPath)) {
640
- fs.writeFileSync(requirementsPath, buildPythonCodegenRequirementsSource(), "utf-8");
661
+ if (!session.fileExists(requirementsPath)) {
662
+ session.writeFile(requirementsPath, buildPythonCodegenRequirementsSource(), {
663
+ ownership: "generated-once",
664
+ generator: "native-schema",
665
+ sourceModel,
666
+ });
641
667
  }
642
668
  return requirementsPath;
643
669
  }
@@ -676,8 +702,7 @@ async function ensureProjectLocalUvCommand(projectRoot: string): Promise<{ comma
676
702
  return { command: localUvExecutable, env: uvEnv };
677
703
  }
678
704
 
679
- async function ensurePythonCodegenEnvironment(functionsRoot: string): Promise<string> {
680
- const requirementsPath = ensurePythonCodegenProjectFiles(functionsRoot);
705
+ async function ensurePythonCodegenEnvironment(functionsRoot: string, requirementsPath: string): Promise<string> {
681
706
  const projectRoot = getPythonProjectRoot(functionsRoot);
682
707
  const { command: uvCommand, env: uvEnv } = await ensureProjectLocalUvCommand(projectRoot);
683
708
  const venvDir = path.join(functionsRoot, ".codegen-venv");
@@ -711,79 +736,125 @@ async function ensurePythonCodegenEnvironment(functionsRoot: string): Promise<st
711
736
  return venvPython;
712
737
  }
713
738
 
714
- async function generateCSharpSchemaArtifacts(
739
+ /**
740
+ * 外部 codegen toolchain の事前検証。プロジェクトには一切書き込まず、
741
+ * OS 一時ディレクトリ上で NSwag / datamodel-code-generator を実行して失敗を早期検出する。
742
+ */
743
+ export async function validateNativeSchemaToolchain(
715
744
  models: ModelInfo[],
716
- specPath: string,
717
- outputDir: string,
745
+ rootModel: ModelInfo,
746
+ backendLanguage: Exclude<BackendLanguage, "typescript">,
718
747
  functionsRoot: string
719
748
  ): Promise<void> {
720
- ensureCSharpCodegenProjectFiles(functionsRoot);
749
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "swallowkit-native-codegen-"));
750
+ try {
751
+ const specPath = path.join(tempDir, `${toKebabCase(rootModel.name)}.openapi.json`);
752
+ fs.writeFileSync(specPath, generateOpenApiDocument(models, rootModel), "utf-8");
753
+
754
+ if (backendLanguage === "csharp") {
755
+ if (!canRun("dotnet", ["--version"], tempDir)) {
756
+ throw new Error(
757
+ "The .NET SDK is required to generate C# backend schema assets.\n" +
758
+ "Install the .NET 8 SDK and retry."
759
+ );
760
+ }
721
761
 
722
- if (!canRun("dotnet", ["--version"], functionsRoot)) {
723
- throw new Error(
724
- "The .NET SDK is required to generate C# backend schema assets.\n" +
725
- "Install the .NET 8 SDK and retry."
726
- );
727
- }
762
+ // プロジェクトの tool manifest(バージョン固定)があればそれを尊重し、なければ一時 manifest で検証する
763
+ const projectManifestPath = path.join(functionsRoot, ".config", "dotnet-tools.json");
764
+ let toolCwd = functionsRoot;
765
+ if (!fs.existsSync(projectManifestPath)) {
766
+ fs.mkdirSync(path.join(tempDir, ".config"), { recursive: true });
767
+ fs.writeFileSync(path.join(tempDir, ".config", "dotnet-tools.json"), buildCSharpCodegenToolManifestSource(), "utf-8");
768
+ toolCwd = tempDir;
769
+ }
728
770
 
729
- const tempContractsPath = path.join(outputDir, ".native-temp", "Contracts.cs");
730
- fs.mkdirSync(path.dirname(tempContractsPath), { recursive: true });
771
+ await runCommand("dotnet", ["tool", "restore"], toolCwd, "Failed to restore the NSwag dotnet tool.");
772
+ await runCommand(
773
+ "dotnet",
774
+ getCSharpNativeGeneratorArgs(specPath, path.join(tempDir, "Contracts.cs")),
775
+ toolCwd,
776
+ "NSwag failed to generate C# backend schema assets."
777
+ );
778
+ } else {
779
+ const projectRequirementsPath = path.join(functionsRoot, "requirements.codegen.txt");
780
+ let requirementsPath = projectRequirementsPath;
781
+ if (!fs.existsSync(projectRequirementsPath)) {
782
+ requirementsPath = path.join(tempDir, "requirements.codegen.txt");
783
+ fs.writeFileSync(requirementsPath, buildPythonCodegenRequirementsSource(), "utf-8");
784
+ }
731
785
 
732
- await runCommand(
733
- "dotnet",
734
- ["tool", "restore"],
735
- functionsRoot,
736
- "Failed to restore the NSwag dotnet tool."
737
- );
738
- await runCommand(
739
- "dotnet",
740
- getCSharpNativeGeneratorArgs(specPath, tempContractsPath),
741
- functionsRoot,
742
- "NSwag failed to generate C# backend schema assets."
743
- );
786
+ // .codegen-venv は environment cache であり managed artifact ではない(既存挙動どおり functionsRoot 配下)
787
+ fs.mkdirSync(functionsRoot, { recursive: true });
788
+ const pythonExecutable = await ensurePythonCodegenEnvironment(functionsRoot, requirementsPath);
789
+ await runCommand(
790
+ pythonExecutable,
791
+ getPythonNativeGeneratorArgs(specPath, path.join(tempDir, "models.py")),
792
+ functionsRoot,
793
+ "datamodel-code-generator failed to generate Python backend schema assets."
794
+ );
795
+ }
796
+ } finally {
797
+ fs.rmSync(tempDir, { recursive: true, force: true });
798
+ }
799
+ }
744
800
 
745
- fs.rmSync(path.dirname(tempContractsPath), { recursive: true, force: true });
801
+ async function generateCSharpSchemaArtifacts(
802
+ models: ModelInfo[],
803
+ outputDir: string,
804
+ functionsRoot: string,
805
+ session: FileOperationSession,
806
+ sourceModel: string
807
+ ): Promise<void> {
808
+ ensureCSharpCodegenProjectFiles(functionsRoot, session, sourceModel);
746
809
 
747
810
  const optionPath = getCSharpSchemaOptionPath(outputDir);
748
- fs.mkdirSync(path.dirname(optionPath), { recursive: true });
749
- fs.writeFileSync(optionPath, generateLegacyCompatibleOptionSource(), "utf-8");
811
+ session.writeFile(optionPath, generateLegacyCompatibleOptionSource(), {
812
+ ownership: "managed",
813
+ generator: "native-schema",
814
+ sourceModel,
815
+ });
750
816
 
751
817
  for (const model of models) {
752
818
  const modelPath = getCSharpSchemaModelPath(outputDir, model.name);
753
- fs.mkdirSync(path.dirname(modelPath), { recursive: true });
754
- fs.writeFileSync(modelPath, generateLegacyCompatibleCSharpModelSource(model), "utf-8");
819
+ session.writeFile(modelPath, generateLegacyCompatibleCSharpModelSource(model), {
820
+ ownership: "managed",
821
+ generator: "native-schema",
822
+ sourceModel,
823
+ });
755
824
  }
756
825
  }
757
826
 
758
827
  async function generatePythonSchemaArtifacts(
759
828
  models: ModelInfo[],
760
- specPath: string,
761
829
  outputDir: string,
762
- functionsRoot: string
830
+ functionsRoot: string,
831
+ session: FileOperationSession,
832
+ sourceModel: string
763
833
  ): Promise<void> {
764
- const pythonExecutable = await ensurePythonCodegenEnvironment(functionsRoot);
765
- const tempModelsPath = path.join(outputDir, ".native-temp", "models.py");
766
- fs.mkdirSync(path.dirname(tempModelsPath), { recursive: true });
767
-
768
- await runCommand(
769
- pythonExecutable,
770
- getPythonNativeGeneratorArgs(specPath, tempModelsPath),
771
- functionsRoot,
772
- "datamodel-code-generator failed to generate Python backend schema assets."
773
- );
774
-
775
- fs.rmSync(path.dirname(tempModelsPath), { recursive: true, force: true });
834
+ ensurePythonCodegenProjectFiles(functionsRoot, session, sourceModel);
776
835
 
777
836
  const packageRoot = path.join(outputDir, "backend_models");
778
837
  const modelsRoot = path.join(packageRoot, "models");
779
- fs.mkdirSync(modelsRoot, { recursive: true });
780
- writeMergedPythonInitFile(path.join(packageRoot, "__init__.py"), buildGeneratedPythonPackageInitSource(models));
781
- writeMergedPythonInitFile(path.join(modelsRoot, "__init__.py"), buildGeneratedPythonModelsInitSource(models));
838
+ writeMergedPythonInitFile(
839
+ path.join(packageRoot, "__init__.py"),
840
+ buildGeneratedPythonPackageInitSource(models),
841
+ session,
842
+ sourceModel
843
+ );
844
+ writeMergedPythonInitFile(
845
+ path.join(modelsRoot, "__init__.py"),
846
+ buildGeneratedPythonModelsInitSource(models),
847
+ session,
848
+ sourceModel
849
+ );
782
850
 
783
851
  for (const model of models) {
784
852
  const modelPath = getPythonSchemaModelPath(outputDir, model.name);
785
- fs.mkdirSync(path.dirname(modelPath), { recursive: true });
786
- fs.writeFileSync(modelPath, generateLegacyCompatiblePythonModelSource(model), "utf-8");
853
+ session.writeFile(modelPath, generateLegacyCompatiblePythonModelSource(model), {
854
+ ownership: "managed",
855
+ generator: "native-schema",
856
+ sourceModel,
857
+ });
787
858
  }
788
859
  }
789
860
 
@@ -791,18 +862,27 @@ export async function generateLanguageSchemaArtifacts(
791
862
  models: ModelInfo[],
792
863
  rootModel: ModelInfo,
793
864
  functionsDir: string,
794
- backendLanguage: Exclude<BackendLanguage, "typescript">
865
+ backendLanguage: Exclude<BackendLanguage, "typescript">,
866
+ session: FileOperationSession = new FileOperationSession("commit")
795
867
  ): Promise<void> {
796
- console.log("\n🧬 Generating OpenAPI export and native schema assets...");
868
+ const planning = session.mode === "collect";
869
+ console.log(
870
+ planning
871
+ ? "\n\ud83e\uddec Planning OpenAPI export and native schema assets..."
872
+ : "\n\ud83e\uddec Generating OpenAPI export and native schema assets..."
873
+ );
797
874
 
798
875
  const projectRoot = process.cwd();
799
876
  const functionsRoot = path.join(projectRoot, functionsDir);
800
877
  const openApiDir = path.join(functionsRoot, "openapi");
801
- fs.mkdirSync(openApiDir, { recursive: true });
802
878
 
803
879
  const specPath = path.join(openApiDir, `${toKebabCase(rootModel.name)}.openapi.json`);
804
- fs.writeFileSync(specPath, generateOpenApiDocument(models, rootModel), "utf-8");
805
- console.log(`✅ Created: ${specPath}`);
880
+ session.writeFile(specPath, generateOpenApiDocument(models, rootModel), {
881
+ ownership: "managed",
882
+ generator: "native-schema",
883
+ sourceModel: rootModel.name,
884
+ });
885
+ console.log(planning ? `\ud83d\udccb Will create: ${specPath}` : `\u2705 Created: ${specPath}`);
806
886
 
807
887
  const outputDir = path.join(
808
888
  functionsRoot,
@@ -810,13 +890,15 @@ export async function generateLanguageSchemaArtifacts(
810
890
  backendLanguage === "csharp" ? "csharp-models" : "python-models"
811
891
  );
812
892
 
813
- fs.mkdirSync(outputDir, { recursive: true });
814
-
815
893
  if (backendLanguage === "csharp") {
816
- await generateCSharpSchemaArtifacts(models, specPath, outputDir, functionsRoot);
894
+ await generateCSharpSchemaArtifacts(models, outputDir, functionsRoot, session, rootModel.name);
817
895
  } else {
818
- await generatePythonSchemaArtifacts(models, specPath, outputDir, functionsRoot);
896
+ await generatePythonSchemaArtifacts(models, outputDir, functionsRoot, session, rootModel.name);
819
897
  }
820
898
 
821
- console.log(`✅ Generated ${backendLanguage} schema assets: ${outputDir}`);
899
+ console.log(
900
+ planning
901
+ ? `\ud83d\udccb Will generate ${backendLanguage} schema assets: ${outputDir}`
902
+ : `\u2705 Generated ${backendLanguage} schema assets: ${outputDir}`
903
+ );
822
904
  }
@@ -10,7 +10,7 @@ import { ModelInfo, toCamelCase } from "./model-parser";
10
10
  * BFF callFunction ヘルパー (lib/api/call-function.ts) のコードを生成
11
11
  */
12
12
  export function generateBFFCallFunction(): string {
13
- return `import { NextRequest, NextResponse } from 'next/server';
13
+ return `import { NextResponse } from 'next/server';
14
14
  import { z } from 'zod/v4';
15
15
 
16
16
  /**
@@ -43,13 +43,13 @@ function getFunctionsKeyHeaders(functionsBaseUrl: string): Record<string, string
43
43
  throw new Error('BACKEND_FUNCTIONS_KEY is required for non-local Functions calls');
44
44
  }
45
45
 
46
- interface CallFunctionConfig<TInput = any, TOutput = any> {
46
+ interface CallFunctionConfig<TInput = unknown, TOutput = unknown> {
47
47
  /** HTTP メソッド */
48
48
  method: 'GET' | 'POST' | 'PUT' | 'DELETE';
49
49
  /** Azure Functions のパス (例: '/api/todo', '/api/todo/123') */
50
50
  path: string;
51
51
  /** リクエストボディ (POST/PUT 用) */
52
- body?: any;
52
+ body?: unknown;
53
53
  /** 入力バリデーション用 Zod スキーマ (省略時はバリデーションなし) */
54
54
  inputSchema?: z.ZodSchema<TInput>;
55
55
  /** 出力バリデーション用 Zod スキーマ (省略時はそのまま返す) */
@@ -58,7 +58,7 @@ interface CallFunctionConfig<TInput = any, TOutput = any> {
58
58
  successStatus?: number;
59
59
  }
60
60
 
61
- export async function callFunction<TInput = any, TOutput = any>(
61
+ export async function callFunction<TInput = unknown, TOutput = unknown>(
62
62
  config: CallFunctionConfig<TInput, TOutput>
63
63
  ): Promise<NextResponse> {
64
64
  const { method, path, body, inputSchema, responseSchema, successStatus = 200 } = config;
@@ -121,10 +121,10 @@ export async function callFunction<TInput = any, TOutput = any>(
121
121
  }
122
122
 
123
123
  return NextResponse.json(data, { status: successStatus });
124
- } catch (error: any) {
124
+ } catch (error) {
125
125
  console.error(\`[BFF] Error:\`, error);
126
126
  return NextResponse.json(
127
- { error: error.message || 'Internal server error' },
127
+ { error: error instanceof Error ? error.message : 'Internal server error' },
128
128
  { status: 500 }
129
129
  );
130
130
  }
@@ -71,7 +71,7 @@ export function generateListPage(model: ModelInfo, sharedPackageName: string, au
71
71
  const refModelCamel = toCamelCase(refModel);
72
72
  return ` fetch('/api/${refModelCamel}')
73
73
  .then(res => res.json())
74
- .then((data: any[]) => {
74
+ .then((data: Array<{ id: string; name?: string; title?: string }>) => {
75
75
  const map: Record<string, string> = {};
76
76
  data.forEach(item => {
77
77
  // name または title フィールドを表示用文字列として使用
@@ -138,8 +138,8 @@ ${hasForeignKeys ? foreignKeyFetches : ''}
138
138
  if (!res.ok) throw new Error('Failed to delete ${modelCamel}');
139
139
 
140
140
  set${modelName}s(${modelCamel}s.filter((item) => item.id !== id));
141
- } catch (err: any) {
142
- alert(\`Error: \${err.message}\`);
141
+ } catch (err) {
142
+ alert(\`Error: \${err instanceof Error ? err.message : String(err)}\`);
143
143
  }
144
144
  };
145
145
 
@@ -216,7 +216,7 @@ ${displayFields.map(f => {
216
216
  const displayField = f.nestedDisplayField || 'name';
217
217
  if (f.isArray) {
218
218
  return ` <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
219
- {Array.isArray(item.${f.name}) ? item.${f.name}.map((ref: any) => ref?.${displayField} || '-').join(', ') : '-'}
219
+ {Array.isArray(item.${f.name}) ? item.${f.name}.map((ref: unknown) => (ref as { ${displayField}?: string } | null)?.${displayField} || '-').join(', ') : '-'}
220
220
  </td>`;
221
221
  } else {
222
222
  return ` <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
@@ -317,7 +317,7 @@ export function generateDetailPage(model: ModelInfo, sharedPackageName: string,
317
317
  const refModelCamel = toCamelCase(refModel);
318
318
  return ` fetch('/api/${refModelCamel}')
319
319
  .then(res => res.json())
320
- .then((data: any[]) => {
320
+ .then((data: Array<{ id: string; name?: string; title?: string }>) => {
321
321
  const map: Record<string, string> = {};
322
322
  data.forEach(item => {
323
323
  map[item.id] = item.name || item.title || item.id;
@@ -389,8 +389,8 @@ ${hasForeignKeys ? foreignKeyFetches : ''}
389
389
  if (!res.ok) throw new Error('Failed to delete ${modelCamel}');
390
390
 
391
391
  router.push('/${modelKebab}');
392
- } catch (err: any) {
393
- alert(\`Error: \${err.message}\`);
392
+ } catch (err) {
393
+ alert(\`Error: \${err instanceof Error ? err.message : String(err)}\`);
394
394
  }
395
395
  };
396
396
 
@@ -455,7 +455,7 @@ ${model.fields.map(f => {
455
455
  if (f.isArray) {
456
456
  return ` <div>
457
457
  <dt className="text-sm font-medium text-gray-500 dark:text-gray-400">${label}</dt>
458
- <dd className="mt-1 text-sm text-gray-900 dark:text-gray-100">{Array.isArray(${modelCamel}.${f.name}) ? ${modelCamel}.${f.name}.map((ref: any) => ref?.${displayField} || '-').join(', ') : '-'}</dd>
458
+ <dd className="mt-1 text-sm text-gray-900 dark:text-gray-100">{Array.isArray(${modelCamel}.${f.name}) ? ${modelCamel}.${f.name}.map((ref: unknown) => (ref as { ${displayField}?: string } | null)?.${displayField} || '-').join(', ') : '-'}</dd>
459
459
  </div>`;
460
460
  } else {
461
461
  return ` <div>
@@ -547,7 +547,7 @@ export function generateFormComponent(model: ModelInfo, sharedPackageName: strin
547
547
  return ` // ${refModelPascal} の一覧を取得
548
548
  fetch('/api/${refModelCamel}')
549
549
  .then(res => res.json())
550
- .then(data => set${refModelPascal}Options(data.map((item: any) => ({ id: item.id, name: item.${displayField} || item.name || item.title || item.id }))))
550
+ .then(data => set${refModelPascal}Options(data.map((item: Record<string, string>) => ({ id: item.id, name: item.${displayField} || item.name || item.title || item.id }))))
551
551
  .catch(err => console.error('Failed to load ${refModelPascal} options:', err));`;
552
552
  }).join('\n');
553
553
 
@@ -581,7 +581,7 @@ ${formFields.map(f => {
581
581
  // ネストスキーマの場合は参照ID(単一: string, 配列: string[])を管理
582
582
  if (f.isNestedSchema) {
583
583
  if (f.isArray) {
584
- return ` ${f.name}Ids: initialData?.${f.name} ? (Array.isArray(initialData.${f.name}) ? initialData.${f.name}.map((item: any) => item.id) : []) : [] as string[],`;
584
+ return ` ${f.name}Ids: initialData?.${f.name} ? (Array.isArray(initialData.${f.name}) ? initialData.${f.name}.map((item: { id: string }) => item.id) : []) : [] as string[],`;
585
585
  } else {
586
586
  return ` ${f.name}Id: initialData?.${f.name}?.id ?? '',`;
587
587
  }
@@ -615,7 +615,7 @@ ${needsUseEffect ? `
615
615
  ${hasForignKeys ? foreignKeyFields.map(f => ` // ${f.referencedModel} の一覧を取得
616
616
  fetch('/api/${toKebabCase(f.referencedModel!)}')
617
617
  .then(res => res.json())
618
- .then(data => set${f.referencedModel}Options(data.map((item: any) => ({ id: item.id, name: item.name || item.title || item.id }))))
618
+ .then(data => set${f.referencedModel}Options(data.map((item: Record<string, string>) => ({ id: item.id, name: item.name || item.title || item.id }))))
619
619
  .catch(err => console.error('Failed to load ${f.referencedModel} options:', err));`).join('\n') : ''}
620
620
  ${hasNestedSchemas ? nestedSchemaFetches : ''}
621
621
  }, []);
@@ -627,7 +627,7 @@ ${hasNestedSchemas ? nestedSchemaFetches : ''}
627
627
 
628
628
  try {
629
629
  // Array フィールドをカンマ区切りから配列に変換
630
- const submitData: any = { ...formData };
630
+ const submitData: Record<string, unknown> = { ...formData };
631
631
  ${formFields.filter(f => f.isArray && !f.isNestedSchema).map(f => ` if (typeof submitData.${f.name} === 'string') {
632
632
  submitData.${f.name} = submitData.${f.name}.split(',').map((s: string) => s.trim()).filter((s: string) => s.length > 0);
633
633
  }`).join('\n')}
@@ -651,7 +651,7 @@ ${nestedSchemaFields.map(f => {
651
651
  if (f.isArray) {
652
652
  return ` // ${refModelPascal} 配列参照の変換
653
653
  if (submitData.${f.name}Ids) {
654
- submitData.${f.name} = submitData.${f.name}Ids
654
+ submitData.${f.name} = (submitData.${f.name}Ids as string[])
655
655
  .map((refId: string) => ${refModelCamel}Options.find(opt => opt.id === refId))
656
656
  .filter(Boolean);
657
657
  delete submitData.${f.name}Ids;
@@ -688,17 +688,17 @@ ${nestedSchemaFields.map(f => {
688
688
  }
689
689
 
690
690
  router.push('/${toKebabCase(modelName)}');
691
- } catch (err: any) {
692
- if (err.issues) {
691
+ } catch (err) {
692
+ if (err instanceof z.ZodError) {
693
693
  // Zod validation errors
694
694
  const fieldErrors: Record<string, string> = {};
695
- err.issues.forEach((error: any) => {
696
- const field = error.path[0];
697
- fieldErrors[field] = error.message;
695
+ err.issues.forEach((issue) => {
696
+ const field = String(issue.path[0]);
697
+ fieldErrors[field] = issue.message;
698
698
  });
699
699
  setErrors(fieldErrors);
700
700
  } else {
701
- alert(\`Error: \${err.message}\`);
701
+ alert(\`Error: \${err instanceof Error ? err.message : String(err)}\`);
702
702
  }
703
703
  setLoading(false);
704
704
  }