swallowkit 1.1.2 → 2.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +61 -7
- package/README.md +61 -7
- package/dist/cli/commands/add-auth.d.ts.map +1 -1
- package/dist/cli/commands/add-auth.js +66 -21
- package/dist/cli/commands/add-auth.js.map +1 -1
- package/dist/cli/commands/dev.d.ts +4 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +108 -44
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/init.d.ts +6 -5
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +83 -39
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts +1 -1
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +38 -15
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +6 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/core/config.d.ts +2 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +19 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/project/agent-instructions.d.ts +2 -1
- package/dist/core/project/agent-instructions.d.ts.map +1 -1
- package/dist/core/project/agent-instructions.js +12 -0
- package/dist/core/project/agent-instructions.js.map +1 -1
- package/dist/core/project/capabilities.d.ts +7 -0
- package/dist/core/project/capabilities.d.ts.map +1 -1
- package/dist/core/project/capabilities.js +7 -0
- package/dist/core/project/capabilities.js.map +1 -1
- package/dist/core/project/manifest.d.ts +3 -1
- package/dist/core/project/manifest.d.ts.map +1 -1
- package/dist/core/project/manifest.js +47 -25
- package/dist/core/project/manifest.js.map +1 -1
- package/dist/core/project/react-project.d.ts +26 -0
- package/dist/core/project/react-project.d.ts.map +1 -0
- package/dist/core/project/react-project.js +422 -0
- package/dist/core/project/react-project.js.map +1 -0
- package/dist/core/project/validation.d.ts.map +1 -1
- package/dist/core/project/validation.js +32 -10
- package/dist/core/project/validation.js.map +1 -1
- package/dist/core/scaffold/react-bff-generator.d.ts +10 -0
- package/dist/core/scaffold/react-bff-generator.d.ts.map +1 -0
- package/dist/core/scaffold/react-bff-generator.js +252 -0
- package/dist/core/scaffold/react-bff-generator.js.map +1 -0
- package/dist/core/scaffold/react-ui-generator.d.ts +6 -0
- package/dist/core/scaffold/react-ui-generator.d.ts.map +1 -0
- package/dist/core/scaffold/react-ui-generator.js +41 -0
- package/dist/core/scaffold/react-ui-generator.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/machine/index.js +2 -2
- package/dist/machine/index.js.map +1 -1
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/package-manager.d.ts +2 -2
- package/dist/utils/package-manager.d.ts.map +1 -1
- package/dist/utils/package-manager.js +5 -2
- package/dist/utils/package-manager.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/agent-instructions.test.ts +1 -1
- package/src/__tests__/init.test.ts +29 -5
- package/src/__tests__/package-manager.test.ts +15 -0
- package/src/__tests__/react.test.ts +819 -0
- package/src/__tests__/release-metadata.test.ts +121 -0
- package/src/cli/commands/add-auth.ts +67 -23
- package/src/cli/commands/dev.ts +109 -46
- package/src/cli/commands/init.ts +75 -35
- package/src/cli/commands/scaffold.ts +40 -19
- package/src/cli/index.ts +6 -3
- package/src/core/config.ts +21 -1
- package/src/core/project/agent-instructions.ts +14 -1
- package/src/core/project/agent-templates/AGENTS.md +24 -9
- package/src/core/project/agent-templates/bff-routes.instructions.md +5 -3
- package/src/core/project/capabilities.ts +14 -0
- package/src/core/project/manifest.ts +52 -27
- package/src/core/project/react-project.ts +507 -0
- package/src/core/project/validation.ts +32 -10
- package/src/core/scaffold/react-bff-generator.ts +292 -0
- package/src/core/scaffold/react-ui-generator.ts +51 -0
- package/src/index.ts +3 -1
- package/src/machine/index.ts +2 -2
- package/src/types/index.ts +4 -0
- package/src/utils/package-manager.ts +6 -3
package/src/cli/commands/init.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import { spawn, execSync } from "child_process";
|
|
4
4
|
import prompts from "prompts";
|
|
5
|
-
import { BackendLanguage } from "../../types";
|
|
5
|
+
import { BackendLanguage, FrontendFramework } from "../../types";
|
|
6
|
+
import { buildReactReadme, buildReactSwaConfig, createReactBffProject, createReactProject, buildReactGitHubSwaWorkflow, buildReactAzureSwaPipeline } from "../../core/project/react-project";
|
|
6
7
|
import {
|
|
7
8
|
type PackageManager,
|
|
8
9
|
detectFromUserAgent,
|
|
@@ -29,6 +30,7 @@ import { getProjectLocalUvPaths } from "../../utils/python-uv";
|
|
|
29
30
|
interface InitOptions {
|
|
30
31
|
name: string;
|
|
31
32
|
template: string;
|
|
33
|
+
frontend?: FrontendFramework;
|
|
32
34
|
nextVersion?: string;
|
|
33
35
|
cicd?: CiCdProvider;
|
|
34
36
|
backendLanguage?: BackendLanguage;
|
|
@@ -257,6 +259,9 @@ const VALID_VNET: VNetOption[] = ['none', 'outbound'];
|
|
|
257
259
|
const VALID_SWA_PLAN: StaticWebAppPlan[] = ['free', 'standard'];
|
|
258
260
|
|
|
259
261
|
function validateInitFlags(options: InitOptions): void {
|
|
262
|
+
if (options.frontend && !["nextjs", "react"].includes(options.frontend)) {
|
|
263
|
+
throw new Error("Invalid --frontend: expected nextjs or react");
|
|
264
|
+
}
|
|
260
265
|
if (options.packageManager && !['npm', 'pnpm'].includes(options.packageManager)) {
|
|
261
266
|
throw new Error('Invalid --package-manager: expected npm or pnpm');
|
|
262
267
|
}
|
|
@@ -322,17 +327,28 @@ export async function initCommand(options: InitOptions) {
|
|
|
322
327
|
}
|
|
323
328
|
}
|
|
324
329
|
|
|
330
|
+
// Preserve existing non-interactive invocations while offering a frontend choice in the wizard.
|
|
331
|
+
const frontend: FrontendFramework = options.frontend ?? (interactive
|
|
332
|
+
? (await prompts({
|
|
333
|
+
type: "select", name: "frontend", message: "Frontend framework:",
|
|
334
|
+
choices: [{ title: "Next.js (App Router)", value: "nextjs" }, { title: "React (Vite SPA + SWA managed Functions BFF)", value: "react" }],
|
|
335
|
+
initial: 0,
|
|
336
|
+
})).frontend || "nextjs"
|
|
337
|
+
: "nextjs");
|
|
338
|
+
options = { ...options, frontend };
|
|
339
|
+
|
|
325
340
|
// Use flag values if provided, otherwise prompt interactively
|
|
326
341
|
const cicdProvider: CiCdProvider = options.cicd || await promptCiCd();
|
|
327
342
|
const backendLanguage: BackendLanguage = options.backendLanguage || await promptBackendLanguage();
|
|
328
343
|
|
|
329
344
|
const azureConfig = await promptAzureConfig(options);
|
|
330
345
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
346
|
+
if (frontend === "react") {
|
|
347
|
+
createReactProject(projectDir, options.name);
|
|
348
|
+
} else {
|
|
349
|
+
await createNextJsProject(options.name, pm);
|
|
350
|
+
await upgradeNextJs(projectDir, options.nextVersion || 'latest', pm);
|
|
351
|
+
}
|
|
336
352
|
|
|
337
353
|
// Add SwallowKit specific files
|
|
338
354
|
await addSwallowKitFiles(projectDir, options, cicdProvider, azureConfig, pm, backendLanguage);
|
|
@@ -342,9 +358,9 @@ export async function initCommand(options: InitOptions) {
|
|
|
342
358
|
|
|
343
359
|
// Create CI/CD files based on choice
|
|
344
360
|
if (cicdProvider === 'github') {
|
|
345
|
-
await createGitHubActionsWorkflows(projectDir, azureConfig, pm, backendLanguage);
|
|
361
|
+
await createGitHubActionsWorkflows(projectDir, azureConfig, pm, backendLanguage, frontend);
|
|
346
362
|
} else if (cicdProvider === 'azure') {
|
|
347
|
-
await createAzurePipelines(projectDir, pm, backendLanguage);
|
|
363
|
+
await createAzurePipelines(projectDir, pm, backendLanguage, frontend);
|
|
348
364
|
}
|
|
349
365
|
|
|
350
366
|
await syncProjectManifest(projectDir);
|
|
@@ -651,9 +667,13 @@ export function parseIgnoredBuilds(output: string): string[] {
|
|
|
651
667
|
}
|
|
652
668
|
|
|
653
669
|
export function injectSwallowKitNextConfig(nextConfigContent: string, projectName: string): string {
|
|
654
|
-
|
|
670
|
+
const compatibleConfig = nextConfigContent.replace(/(\b(?:cacheComponents|partialPrefetching)\s*:\s*)true\b/g, '$1false');
|
|
671
|
+
const cacheComponentsSetting = /\bcacheComponents\s*:/.test(compatibleConfig)
|
|
672
|
+
? ''
|
|
673
|
+
: ' cacheComponents: false,\n';
|
|
674
|
+
return compatibleConfig.replace(
|
|
655
675
|
/(const\s+nextConfig[:\s]*(?::\s*NextConfig\s*)?=\s*\{)(\s*\/\*[^*]*\*\/)?/,
|
|
656
|
-
`$1\n output: 'standalone',\n agentRules: false, // SwallowKit owns the canonical root AGENTS.md contract\n transpilePackages: ['@${projectName}/shared'],\n serverExternalPackages: ['applicationinsights', 'diagnostic-channel-publishers'],$2`
|
|
676
|
+
`$1\n output: 'standalone',\n${cacheComponentsSetting} agentRules: false, // SwallowKit owns the canonical root AGENTS.md contract\n transpilePackages: ['@${projectName}/shared'],\n serverExternalPackages: ['applicationinsights', 'diagnostic-channel-publishers'],$2`
|
|
657
677
|
);
|
|
658
678
|
}
|
|
659
679
|
|
|
@@ -704,8 +724,11 @@ export function buildCSharpFunctionsProjectSource(): string {
|
|
|
704
724
|
`;
|
|
705
725
|
}
|
|
706
726
|
|
|
707
|
-
export function buildSwallowKitConfigSource(backendLanguage: BackendLanguage): string {
|
|
727
|
+
export function buildSwallowKitConfigSource(backendLanguage: BackendLanguage, frontend: FrontendFramework = "nextjs"): string {
|
|
708
728
|
return `module.exports = {
|
|
729
|
+
frontend: {
|
|
730
|
+
framework: '${frontend}',
|
|
731
|
+
},
|
|
709
732
|
backend: {
|
|
710
733
|
language: '${backendLanguage}',
|
|
711
734
|
},
|
|
@@ -723,10 +746,10 @@ export function buildSwallowKitConfigSource(backendLanguage: BackendLanguage): s
|
|
|
723
746
|
export function buildGeneratedProjectDependencies(
|
|
724
747
|
projectName: string,
|
|
725
748
|
pm: PackageManager = 'pnpm',
|
|
749
|
+
frontend: FrontendFramework = "nextjs",
|
|
726
750
|
): Record<string, string> {
|
|
727
751
|
return {
|
|
728
|
-
'@azure/cosmos': '^4.0.0',
|
|
729
|
-
'applicationinsights': '^3.3.0',
|
|
752
|
+
...(frontend === "nextjs" ? { '@azure/cosmos': '^4.0.0', 'applicationinsights': '^3.3.0' } : {}),
|
|
730
753
|
'zod': '^4.0.0',
|
|
731
754
|
[`@${projectName}/shared`]: pm === 'pnpm' ? 'workspace:*' : 'file:shared',
|
|
732
755
|
};
|
|
@@ -795,6 +818,7 @@ async function addSwallowKitFiles(
|
|
|
795
818
|
console.log('📦 Adding SwallowKit files...\n');
|
|
796
819
|
|
|
797
820
|
const projectName = options.name;
|
|
821
|
+
const frontend = options.frontend ?? "nextjs";
|
|
798
822
|
|
|
799
823
|
// 1. Update package.json to add runtime dependencies for generated projects
|
|
800
824
|
const packageJsonPath = path.join(projectDir, 'package.json');
|
|
@@ -803,7 +827,7 @@ async function addSwallowKitFiles(
|
|
|
803
827
|
// Root BFF/auth code imports Zod directly; declare it as well as the shared package.
|
|
804
828
|
packageJson.dependencies = {
|
|
805
829
|
...packageJson.dependencies,
|
|
806
|
-
...buildGeneratedProjectDependencies(projectName, pm),
|
|
830
|
+
...buildGeneratedProjectDependencies(projectName, pm, frontend),
|
|
807
831
|
};
|
|
808
832
|
packageJson.devDependencies = {
|
|
809
833
|
...packageJson.devDependencies,
|
|
@@ -812,8 +836,12 @@ async function addSwallowKitFiles(
|
|
|
812
836
|
|
|
813
837
|
packageJson.scripts = {
|
|
814
838
|
...packageJson.scripts,
|
|
815
|
-
'build': getBuildScript(pm),
|
|
816
|
-
'start': 'next start',
|
|
839
|
+
'build': getBuildScript(pm, frontend),
|
|
840
|
+
'start': frontend === "react" ? "vite preview" : 'next start',
|
|
841
|
+
...(frontend === "react" ? {
|
|
842
|
+
typecheck: `tsc --noEmit && ${getCommands(pm).exec} tsc --noEmit -p bff`,
|
|
843
|
+
"bff:start": `${getCommands(pm).runFilter("bff")} start`,
|
|
844
|
+
} : {}),
|
|
817
845
|
'functions:start': getFunctionsStartScript(pm, backendLanguage),
|
|
818
846
|
};
|
|
819
847
|
|
|
@@ -827,7 +855,7 @@ async function addSwallowKitFiles(
|
|
|
827
855
|
delete packageJson.packageManager;
|
|
828
856
|
|
|
829
857
|
// Keep package.json workspaces for npm-based CI/CD even when local development uses pnpm.
|
|
830
|
-
const workspacePackages = usesNodeFunctionsProject(backendLanguage) ? ['shared', 'functions'] : ['shared'];
|
|
858
|
+
const workspacePackages = [...(usesNodeFunctionsProject(backendLanguage) ? ['shared', 'functions'] : ['shared']), ...(frontend === "react" ? ["bff"] : [])];
|
|
831
859
|
const wsConfig = getWorkspaceConfig(pm, workspacePackages);
|
|
832
860
|
packageJson.workspaces = workspacePackages;
|
|
833
861
|
|
|
@@ -839,7 +867,7 @@ async function addSwallowKitFiles(
|
|
|
839
867
|
|
|
840
868
|
// Create pnpm-workspace.yaml for local pnpm development when needed.
|
|
841
869
|
if (wsConfig.type === 'file') {
|
|
842
|
-
fs.writeFileSync(path.join(projectDir, wsConfig.filename), wsConfig.content);
|
|
870
|
+
fs.writeFileSync(path.join(projectDir, wsConfig.filename), wsConfig.content + (frontend === "react" ? " esbuild: true\n" : ""));
|
|
843
871
|
}
|
|
844
872
|
|
|
845
873
|
// Don't install yet — wait until all workspace packages (shared, functions) are created
|
|
@@ -878,12 +906,13 @@ async function addSwallowKitFiles(
|
|
|
878
906
|
}
|
|
879
907
|
|
|
880
908
|
// 3. Create SwallowKit config
|
|
881
|
-
const swallowkitConfig = buildSwallowKitConfigSource(backendLanguage);
|
|
909
|
+
const swallowkitConfig = buildSwallowKitConfigSource(backendLanguage, frontend);
|
|
882
910
|
fs.writeFileSync(path.join(projectDir, 'swallowkit.config.js'), swallowkitConfig);
|
|
883
911
|
|
|
884
912
|
// 4. Create shared workspace package for Zod models (Single Source of Truth)
|
|
885
913
|
await createSharedPackage(projectDir, projectName);
|
|
886
914
|
|
|
915
|
+
if (frontend === "nextjs") {
|
|
887
916
|
// Create lib directory for Next.js-specific utilities
|
|
888
917
|
const libDir = path.join(projectDir, 'lib');
|
|
889
918
|
|
|
@@ -1003,6 +1032,7 @@ export const api = {
|
|
|
1003
1032
|
};
|
|
1004
1033
|
`;
|
|
1005
1034
|
fs.writeFileSync(path.join(apiLibDir, 'backend.ts'), backendUtilContent);
|
|
1035
|
+
}
|
|
1006
1036
|
|
|
1007
1037
|
// 5. Create components directory
|
|
1008
1038
|
const componentsDir = path.join(projectDir, 'components');
|
|
@@ -1091,7 +1121,7 @@ export async function register() {
|
|
|
1091
1121
|
}
|
|
1092
1122
|
}
|
|
1093
1123
|
`;
|
|
1094
|
-
fs.writeFileSync(path.join(projectDir, 'instrumentation.ts'), instrumentationContent);
|
|
1124
|
+
if (frontend === "nextjs") fs.writeFileSync(path.join(projectDir, 'instrumentation.ts'), instrumentationContent);
|
|
1095
1125
|
|
|
1096
1126
|
// 8. Create .env.local for local development
|
|
1097
1127
|
const envLocalContent = [
|
|
@@ -1124,17 +1154,19 @@ export async function register() {
|
|
|
1124
1154
|
};
|
|
1125
1155
|
fs.writeFileSync(
|
|
1126
1156
|
path.join(projectDir, 'staticwebapp.config.json'),
|
|
1127
|
-
JSON.stringify(swaConfig, null, 2)
|
|
1157
|
+
JSON.stringify(frontend === "react" ? buildReactSwaConfig() : swaConfig, null, 2)
|
|
1128
1158
|
);
|
|
1129
1159
|
|
|
1130
1160
|
// 14. Create Azure Functions project
|
|
1131
1161
|
await createAzureFunctionsProject(projectDir, pm, backendLanguage);
|
|
1132
1162
|
|
|
1133
1163
|
// 15. Create BFF API route to call Azure Functions
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1164
|
+
if (frontend === "react") {
|
|
1165
|
+
createReactBffProject(projectDir, projectName, pm);
|
|
1166
|
+
} else {
|
|
1167
|
+
await createBffApiRoute(projectDir);
|
|
1168
|
+
await createHomePage(projectDir, pm);
|
|
1169
|
+
}
|
|
1138
1170
|
|
|
1139
1171
|
// 17. Install all workspace dependencies (root + shared + functions)
|
|
1140
1172
|
console.log('📦 Installing workspace dependencies...\n');
|
|
@@ -1143,10 +1175,14 @@ export async function register() {
|
|
|
1143
1175
|
console.log('✅ Project structure created\n');
|
|
1144
1176
|
|
|
1145
1177
|
// 18. Create README.md
|
|
1146
|
-
|
|
1178
|
+
if (frontend === "react") {
|
|
1179
|
+
fs.writeFileSync(path.join(projectDir, "README.md"), buildReactReadme(projectName, pm, backendLanguage));
|
|
1180
|
+
} else {
|
|
1181
|
+
createReadme(projectDir, projectName, cicdChoice, azureConfig, pm, backendLanguage);
|
|
1182
|
+
}
|
|
1147
1183
|
|
|
1148
1184
|
// 19. Create AI agent instruction files (AGENTS.md, CLAUDE.md, .github/copilot-instructions.md, etc.)
|
|
1149
|
-
createAiAgentFiles(projectDir, projectName, backendLanguage, pm);
|
|
1185
|
+
createAiAgentFiles(projectDir, projectName, backendLanguage, pm, frontend);
|
|
1150
1186
|
}
|
|
1151
1187
|
|
|
1152
1188
|
async function createSharedPackage(projectDir: string, projectName: string) {
|
|
@@ -1978,7 +2014,8 @@ function createAiAgentFiles(
|
|
|
1978
2014
|
projectDir: string,
|
|
1979
2015
|
projectName: string,
|
|
1980
2016
|
backendLanguage: BackendLanguage,
|
|
1981
|
-
pm: PackageManager
|
|
2017
|
+
pm: PackageManager,
|
|
2018
|
+
frontend: FrontendFramework = "nextjs"
|
|
1982
2019
|
) {
|
|
1983
2020
|
console.log('🤖 Creating AI agent instruction files...\n');
|
|
1984
2021
|
const runCmd = getCommands(pm).exec;
|
|
@@ -1987,6 +2024,7 @@ function createAiAgentFiles(
|
|
|
1987
2024
|
projectName,
|
|
1988
2025
|
backendLanguage,
|
|
1989
2026
|
packageManager: pm,
|
|
2027
|
+
frontend,
|
|
1990
2028
|
});
|
|
1991
2029
|
console.log(' ✅ AGENTS.md (canonical project contract)');
|
|
1992
2030
|
console.log(' ✅ CLAUDE.md (Claude Code adapter)');
|
|
@@ -3133,7 +3171,8 @@ export function getSwaNodeVersionCommand(): string {
|
|
|
3133
3171
|
return `node -e "const fs=require('fs');const p=JSON.parse(fs.readFileSync('package.json','utf8'));p.engines={...p.engines,node:'>=22.14.0 <23'};fs.writeFileSync('package.json',JSON.stringify(p,null,2)+String.fromCharCode(10));"`;
|
|
3134
3172
|
}
|
|
3135
3173
|
|
|
3136
|
-
export function buildAzureSwaPipeline(pm: PackageManager, projectPm: PackageManager = pm): string {
|
|
3174
|
+
export function buildAzureSwaPipeline(pm: PackageManager, projectPm: PackageManager = pm, frontend: FrontendFramework = "nextjs"): string {
|
|
3175
|
+
if (frontend === "react") return buildReactAzureSwaPipeline(projectPm);
|
|
3137
3176
|
// SWA/Oryx also invokes npm internally; normalize only the disposable CI checkout.
|
|
3138
3177
|
pm = 'npm';
|
|
3139
3178
|
const pmCmd = getCommands(pm);
|
|
@@ -3321,7 +3360,8 @@ async function createGitHubActionsWorkflows(
|
|
|
3321
3360
|
projectDir: string,
|
|
3322
3361
|
azureConfig: AzureConfig,
|
|
3323
3362
|
pm: PackageManager,
|
|
3324
|
-
backendLanguage: BackendLanguage
|
|
3363
|
+
backendLanguage: BackendLanguage,
|
|
3364
|
+
frontend: FrontendFramework = "nextjs"
|
|
3325
3365
|
) {
|
|
3326
3366
|
console.log('📦 Creating GitHub Actions workflows...\n');
|
|
3327
3367
|
const workflowsDir = path.join(projectDir, '.github', 'workflows');
|
|
@@ -3329,7 +3369,7 @@ async function createGitHubActionsWorkflows(
|
|
|
3329
3369
|
|
|
3330
3370
|
const actionsPm: PackageManager = pm;
|
|
3331
3371
|
|
|
3332
|
-
const swaWorkflow = buildGitHubSwaWorkflow(actionsPm, pm);
|
|
3372
|
+
const swaWorkflow = buildGitHubSwaWorkflow(actionsPm, pm, frontend);
|
|
3333
3373
|
fs.writeFileSync(path.join(workflowsDir, 'deploy-swa.yml'), swaWorkflow);
|
|
3334
3374
|
|
|
3335
3375
|
// deploy-functions.yml
|
|
@@ -3339,7 +3379,8 @@ async function createGitHubActionsWorkflows(
|
|
|
3339
3379
|
console.log('✅ GitHub Actions workflows created\n');
|
|
3340
3380
|
}
|
|
3341
3381
|
|
|
3342
|
-
export function buildGitHubSwaWorkflow(pm: PackageManager, projectPm: PackageManager = pm): string {
|
|
3382
|
+
export function buildGitHubSwaWorkflow(pm: PackageManager, projectPm: PackageManager = pm, frontend: FrontendFramework = "nextjs"): string {
|
|
3383
|
+
if (frontend === "react") return buildReactGitHubSwaWorkflow(projectPm);
|
|
3343
3384
|
// SWA/Oryx also invokes npm internally; normalize only the disposable CI checkout.
|
|
3344
3385
|
pm = 'npm';
|
|
3345
3386
|
const npmWorkspaceNormalizationStep = getGitHubNpmWorkspaceNormalizationStep(pm, projectPm);
|
|
@@ -3417,7 +3458,7 @@ ${npmWorkspaceNormalizationStep ? `\n${npmWorkspaceNormalizationStep}` : ''}
|
|
|
3417
3458
|
`;
|
|
3418
3459
|
}
|
|
3419
3460
|
|
|
3420
|
-
async function createAzurePipelines(projectDir: string, pm: PackageManager, backendLanguage: BackendLanguage) {
|
|
3461
|
+
async function createAzurePipelines(projectDir: string, pm: PackageManager, backendLanguage: BackendLanguage, frontend: FrontendFramework = "nextjs") {
|
|
3421
3462
|
console.log('📦 Creating Azure Pipelines...\n');
|
|
3422
3463
|
|
|
3423
3464
|
const pipelinesPm: PackageManager = pm;
|
|
@@ -3425,7 +3466,7 @@ async function createAzurePipelines(projectDir: string, pm: PackageManager, back
|
|
|
3425
3466
|
fs.mkdirSync(pipelinesDir, { recursive: true });
|
|
3426
3467
|
|
|
3427
3468
|
// swa.yml
|
|
3428
|
-
const swaPipeline = buildAzureSwaPipeline(pipelinesPm, pm);
|
|
3469
|
+
const swaPipeline = buildAzureSwaPipeline(pipelinesPm, pm, frontend);
|
|
3429
3470
|
fs.writeFileSync(path.join(pipelinesDir, 'swa.yml'), swaPipeline);
|
|
3430
3471
|
|
|
3431
3472
|
// functions.yml
|
|
@@ -3434,4 +3475,3 @@ async function createAzurePipelines(projectDir: string, pm: PackageManager, back
|
|
|
3434
3475
|
|
|
3435
3476
|
console.log('✅ Azure Pipelines created\n');
|
|
3436
3477
|
}
|
|
3437
|
-
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SwallowKit Scaffold コマンド
|
|
3
|
-
* Zod モデルから Azure Functions
|
|
3
|
+
* Zod モデルから Azure Functions、BFF、選択したフロントエンドの CRUD を生成
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import * as fs from "fs";
|
|
7
7
|
import * as path from "path";
|
|
8
8
|
import { spawnSync } from "child_process";
|
|
9
|
-
import { getBackendLanguage, getConnectorDefinition, getAuthConfig, getFullConfig, getNormalizedAuthConfig, ensureSwallowKitProject, normalizeAuthConfig, validateConfig } from "../../core/config";
|
|
10
|
-
import {
|
|
9
|
+
import { getBackendLanguage, getFrontendFramework, getConnectorDefinition, getAuthConfig, getFullConfig, getNormalizedAuthConfig, ensureSwallowKitProject, normalizeAuthConfig, validateConfig } from "../../core/config";
|
|
10
|
+
import { generateManagedFunctionConfig, generateReactBffHelper, generateReactBffRoute } from "../../core/scaffold/react-bff-generator";
|
|
11
|
+
import { adaptReactPage, generateReactModelRoutes } from "../../core/scaffold/react-ui-generator";
|
|
12
|
+
import { ModelInfo, parseModelFile, toKebabCase, toCamelCase } from "../../core/scaffold/model-parser";
|
|
11
13
|
import { MachineCommandError } from "../../machine/errors";
|
|
12
14
|
import {
|
|
13
15
|
generateCSharpAzureFunctionsCRUD,
|
|
@@ -58,7 +60,7 @@ const SCAFFOLD_GENERATOR = "scaffold";
|
|
|
58
60
|
interface ScaffoldOptions {
|
|
59
61
|
model: string; // モデルファイルのパス(例: "lib/models/todo.ts" or "todo")
|
|
60
62
|
functionsDir?: string; // Azure Functions のディレクトリ(デフォルト: "functions")
|
|
61
|
-
apiDir?: string; // Next.js
|
|
63
|
+
apiDir?: string; // BFF directory (Next.js: app/api, React: bff)
|
|
62
64
|
apiOnly?: boolean; // true の場合、UI を生成しない(デフォルト: false)
|
|
63
65
|
expectedSchemaAnalysis?: SchemaAnalysisSnapshot;
|
|
64
66
|
}
|
|
@@ -131,6 +133,8 @@ export async function scaffoldCommand(options: ScaffoldOptions) {
|
|
|
131
133
|
console.log(`✅ Model parsed: ${modelInfo.name} (${modelInfo.schemaName})`);
|
|
132
134
|
|
|
133
135
|
const backendLanguage = getBackendLanguage();
|
|
136
|
+
const frontend = getFrontendFramework();
|
|
137
|
+
const apiDir = options.apiDir || (frontend === "react" ? "bff" : "app/api");
|
|
134
138
|
console.log(`🧠 Backend language: ${backendLanguage}`);
|
|
135
139
|
|
|
136
140
|
// コネクタモデルかどうかを判定
|
|
@@ -182,7 +186,7 @@ export async function scaffoldCommand(options: ScaffoldOptions) {
|
|
|
182
186
|
}
|
|
183
187
|
|
|
184
188
|
// 5. Generate BFF callFunction helper
|
|
185
|
-
await generateCallFunctionHelper(session);
|
|
189
|
+
await generateCallFunctionHelper(session, apiDir);
|
|
186
190
|
|
|
187
191
|
// 6. Generate Azure Functions code
|
|
188
192
|
if (isConnectorModel) {
|
|
@@ -197,9 +201,10 @@ export async function scaffoldCommand(options: ScaffoldOptions) {
|
|
|
197
201
|
}
|
|
198
202
|
}
|
|
199
203
|
|
|
200
|
-
// 7. Generate
|
|
201
|
-
|
|
202
|
-
|
|
204
|
+
// 7. Generate framework-specific BFF routes (the standalone backend is unchanged).
|
|
205
|
+
if (frontend === "react") {
|
|
206
|
+
generateManagedBffRoutes(modelInfo, apiDir, sharedPackageName, session);
|
|
207
|
+
} else if (isConnectorModel) {
|
|
203
208
|
await generateConnectorBFFRoutesFiles(modelInfo, apiDir, sharedPackageName, session);
|
|
204
209
|
} else {
|
|
205
210
|
await generateBFFRoutes(modelInfo, apiDir, sharedPackageName, session);
|
|
@@ -237,14 +242,14 @@ export async function scaffoldCommand(options: ScaffoldOptions) {
|
|
|
237
242
|
console.log("\n📝 Next steps:");
|
|
238
243
|
console.log(` 1. Review generated files in ${describeFunctionsOutputPath(functionsDir, backendLanguage)} and ${apiDir}/`);
|
|
239
244
|
if (!options.apiOnly) {
|
|
240
|
-
console.log(` 2. Check the generated UI pages in app/${toKebabCase(modelInfo.name)}/`);
|
|
245
|
+
console.log(` 2. Check the generated UI pages in ${frontend === "react" ? "src/pages" : "app"}/${toKebabCase(modelInfo.name)}/`);
|
|
241
246
|
console.log(" 3. Navigate to the model from the homepage menu");
|
|
242
247
|
}
|
|
243
248
|
if (backendLanguage !== "typescript") {
|
|
244
249
|
console.log(` ${options.apiOnly ? "2" : "4"}. Review generated OpenAPI export and native schema assets in ${functionsDir}/openapi/ and ${functionsDir}/generated/`);
|
|
245
250
|
}
|
|
246
251
|
console.log(
|
|
247
|
-
` ${options.apiOnly ? (backendLanguage === "typescript" ? "2" : "3") : (backendLanguage === "typescript" ? "4" : "5")}. Ensure BACKEND_FUNCTIONS_BASE_URL is set in your .env.local file`
|
|
252
|
+
` ${options.apiOnly ? (backendLanguage === "typescript" ? "2" : "3") : (backendLanguage === "typescript" ? "4" : "5")}. Ensure BACKEND_FUNCTIONS_BASE_URL is set in ${frontend === "react" ? `${apiDir}/local.settings.json` : "your .env.local file"}`
|
|
248
253
|
);
|
|
249
254
|
console.log(
|
|
250
255
|
` ${options.apiOnly ? (backendLanguage === "typescript" ? "3" : "4") : (backendLanguage === "typescript" ? "5" : "6")}. Configure CosmosDBConnection in functions/local.settings.json`
|
|
@@ -258,7 +263,7 @@ export async function scaffoldCommand(options: ScaffoldOptions) {
|
|
|
258
263
|
}
|
|
259
264
|
|
|
260
265
|
function applyExternalTokenFetchToGeneratedUi(modelInfo: ModelInfo, session: FileOperationSession, importPath = '@/lib/auth/authenticated-fetch'): void {
|
|
261
|
-
const uiPrefix =
|
|
266
|
+
const uiPrefix = `${getFrontendFramework() === "react" ? "src/pages" : "app"}/${toKebabCase(modelInfo.name)}/`;
|
|
262
267
|
for (const operation of session.operations) {
|
|
263
268
|
if (!operation.path.startsWith(uiPrefix) || !operation.path.endsWith('.tsx') || operation.action === 'delete') continue;
|
|
264
269
|
let content = session.readFile(operation.absolutePath);
|
|
@@ -384,10 +389,17 @@ function readSharedPackageName(): string {
|
|
|
384
389
|
/**
|
|
385
390
|
* Generate BFF callFunction helper (lib/api/call-function.ts)
|
|
386
391
|
*/
|
|
387
|
-
async function generateCallFunctionHelper(session: FileOperationSession): Promise<void> {
|
|
392
|
+
async function generateCallFunctionHelper(session: FileOperationSession, apiDir: string): Promise<void> {
|
|
388
393
|
console.log("\n📦 Generating BFF callFunction helper...");
|
|
389
394
|
|
|
390
395
|
const cwd = process.cwd();
|
|
396
|
+
if (getFrontendFramework() === "react") {
|
|
397
|
+
const projectName = JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf8")).name;
|
|
398
|
+
session.writeFile(path.join(cwd, apiDir, "src", "lib", "call-function.ts"), generateReactBffHelper(projectName, getAuthConfig()), {
|
|
399
|
+
ownership: "managed", generator: SCAFFOLD_GENERATOR,
|
|
400
|
+
});
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
391
403
|
|
|
392
404
|
const helperDir = path.join(cwd, "lib", "api");
|
|
393
405
|
const helperPath = path.join(helperDir, "call-function.ts");
|
|
@@ -800,6 +812,13 @@ function updatePythonFunctionRegistrations(functionAppPath: string, registration
|
|
|
800
812
|
/**
|
|
801
813
|
* Next.js BFF API Routes を生成
|
|
802
814
|
*/
|
|
815
|
+
function generateManagedBffRoutes(modelInfo: ModelInfo, apiDir: string, sharedPackageName: string, session: FileOperationSession): void {
|
|
816
|
+
const name = toCamelCase(modelInfo.name);
|
|
817
|
+
const meta = { ownership: "managed" as const, generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name };
|
|
818
|
+
session.writeFile(path.join(process.cwd(), apiDir, "src", name, "index.ts"), generateReactBffRoute(modelInfo, sharedPackageName), meta);
|
|
819
|
+
session.writeFile(path.join(process.cwd(), apiDir, name, "function.json"), generateManagedFunctionConfig(`${name}/{id?}`, ["GET", "POST", "PUT", "DELETE"], name), meta);
|
|
820
|
+
}
|
|
821
|
+
|
|
803
822
|
async function generateBFFRoutes(
|
|
804
823
|
modelInfo: any,
|
|
805
824
|
apiDir: string,
|
|
@@ -867,9 +886,10 @@ export async function generateUIComponents(modelInfo: ModelInfo, sharedPackageNa
|
|
|
867
886
|
const modelKebab = toKebabCase(modelInfo.name);
|
|
868
887
|
const modelName = modelInfo.name;
|
|
869
888
|
const cwd = process.cwd();
|
|
889
|
+
const react = getFrontendFramework() === "react";
|
|
870
890
|
|
|
871
891
|
// Create directory structure: app/[model]/
|
|
872
|
-
const modelDir = path.join(cwd, "app", modelKebab);
|
|
892
|
+
const modelDir = path.join(cwd, react ? path.join("src", "pages") : "app", modelKebab);
|
|
873
893
|
const componentsDir = path.join(modelDir, "_components");
|
|
874
894
|
const newDir = path.join(modelDir, "new");
|
|
875
895
|
const idDir = path.join(modelDir, "[id]");
|
|
@@ -884,14 +904,16 @@ export async function generateUIComponents(modelInfo: ModelInfo, sharedPackageNa
|
|
|
884
904
|
const authLayout = generateAuthLayout(modelInfo, authOptions);
|
|
885
905
|
|
|
886
906
|
const meta = { ownership: "managed" as const, generator: SCAFFOLD_GENERATOR, sourceModel: modelInfo.name };
|
|
887
|
-
|
|
888
|
-
session.writeFile(path.join(
|
|
889
|
-
session.writeFile(path.join(
|
|
890
|
-
session.writeFile(path.join(
|
|
891
|
-
session.writeFile(path.join(
|
|
907
|
+
const adapt = react ? adaptReactPage : (source: string) => source;
|
|
908
|
+
session.writeFile(path.join(modelDir, "page.tsx"), adapt(listPage), meta);
|
|
909
|
+
session.writeFile(path.join(idDir, "page.tsx"), adapt(detailPage), meta);
|
|
910
|
+
session.writeFile(path.join(componentsDir, `${modelName}Form.tsx`), adapt(formComponent), meta);
|
|
911
|
+
session.writeFile(path.join(newDir, "page.tsx"), adapt(newPage), meta);
|
|
912
|
+
session.writeFile(path.join(editDir, "page.tsx"), adapt(editPage), meta);
|
|
892
913
|
if (authLayout) {
|
|
893
914
|
session.writeFile(path.join(modelDir, "layout.tsx"), authLayout, meta);
|
|
894
915
|
}
|
|
916
|
+
if (react) session.writeFile(path.join(modelDir, "routes.tsx"), generateReactModelRoutes(modelInfo, authOptions), meta);
|
|
895
917
|
|
|
896
918
|
console.log(`✅ Created: ${path.join(modelDir, "page.tsx")}`);
|
|
897
919
|
console.log(`✅ Created: ${path.join(idDir, "page.tsx")}`);
|
|
@@ -971,4 +993,3 @@ ${models.map(m => ` { name: '${m.name}', path: '${m.path}', label: '${m.label
|
|
|
971
993
|
session.writeFile(configPath, newConfigContent, { ownership: "extension-point", generator: SCAFFOLD_GENERATOR });
|
|
972
994
|
console.log(`✅ Added ${modelInfo.name} to navigation menu`);
|
|
973
995
|
}
|
|
974
|
-
|
package/src/cli/index.ts
CHANGED
|
@@ -24,6 +24,7 @@ const DEV_OPTION_ARITY = new Map<string, 0 | 1>([
|
|
|
24
24
|
["--port", 1],
|
|
25
25
|
["-f", 1],
|
|
26
26
|
["--functions-port", 1],
|
|
27
|
+
["--bff-port", 1],
|
|
27
28
|
["--host", 1],
|
|
28
29
|
["--seed-env", 1],
|
|
29
30
|
["-o", 0],
|
|
@@ -102,7 +103,7 @@ export function createProgram(devCommandOverride: Command = devCommand): Command
|
|
|
102
103
|
|
|
103
104
|
program
|
|
104
105
|
.name("swallowkit")
|
|
105
|
-
.description("Next.js
|
|
106
|
+
.description("Schema-driven React and Next.js applications on Azure Static Web Apps and Functions")
|
|
106
107
|
.version(CLI_VERSION);
|
|
107
108
|
|
|
108
109
|
// Register commands
|
|
@@ -110,6 +111,7 @@ export function createProgram(devCommandOverride: Command = devCommand): Command
|
|
|
110
111
|
.command("init [project-name]")
|
|
111
112
|
.description("Initialize a new SwallowKit project")
|
|
112
113
|
.option("--template <template>", "Template to use", "default")
|
|
114
|
+
.option("--frontend <framework>", "Frontend framework: nextjs | react (defaults to nextjs in non-interactive mode)")
|
|
113
115
|
.option("--next-version <version>", "Next.js version to install (e.g., 16.0.7, latest)", "latest")
|
|
114
116
|
.option("--package-manager <manager>", "Package manager: npm | pnpm (defaults to invocation context)")
|
|
115
117
|
.option("--cicd <provider>", "CI/CD provider: github | azure | skip")
|
|
@@ -121,6 +123,7 @@ export function createProgram(devCommandOverride: Command = devCommand): Command
|
|
|
121
123
|
initCommand({
|
|
122
124
|
name: projectName || "swallowkit-app",
|
|
123
125
|
template: options.template,
|
|
126
|
+
frontend: options.frontend,
|
|
124
127
|
nextVersion: options.nextVersion,
|
|
125
128
|
packageManager: options.packageManager,
|
|
126
129
|
cicd: options.cicd,
|
|
@@ -153,9 +156,9 @@ export function createProgram(devCommandOverride: Command = devCommand): Command
|
|
|
153
156
|
|
|
154
157
|
program
|
|
155
158
|
.command("scaffold <model>")
|
|
156
|
-
.description("Generate CRUD code for Azure Functions and
|
|
159
|
+
.description("Generate CRUD code for Azure Functions, BFF, and the selected frontend from Zod models")
|
|
157
160
|
.option("--functions-dir <dir>", "Azure Functions directory", "functions")
|
|
158
|
-
.option("--api-dir <dir>", "
|
|
161
|
+
.option("--api-dir <dir>", "BFF routes directory (Next.js: app/api; React: bff)")
|
|
159
162
|
.option("--api-only", "Skip UI components; still update Functions, BFF routes, OpenAPI, and native schema assets", false)
|
|
160
163
|
.option("--dry-run", "Show planned file changes without writing anything", false)
|
|
161
164
|
.action(async (model, options) => {
|
package/src/core/config.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import { createRequire } from "module";
|
|
3
3
|
import * as path from "path";
|
|
4
|
-
import { AuthConfig, AuthProvider, BackendLanguage, ConnectorDefinition, NormalizedAuthConfig, SwallowKitConfig } from "../types";
|
|
4
|
+
import { AuthConfig, AuthProvider, BackendLanguage, ConnectorDefinition, FrontendFramework, NormalizedAuthConfig, SwallowKitConfig } from "../types";
|
|
5
5
|
import { detectFromProject, getCommands } from "../utils/package-manager";
|
|
6
6
|
|
|
7
7
|
const requireFromHere = createRequire(__filename);
|
|
@@ -21,6 +21,7 @@ const VALID_BACKEND_LANGUAGES: BackendLanguage[] = ["typescript", "csharp", "pyt
|
|
|
21
21
|
* デフォルト設定
|
|
22
22
|
*/
|
|
23
23
|
const DEFAULT_CONFIG: SwallowKitConfig = {
|
|
24
|
+
frontend: { framework: "nextjs" },
|
|
24
25
|
database: {},
|
|
25
26
|
backend: {
|
|
26
27
|
language: "typescript",
|
|
@@ -78,6 +79,10 @@ export function loadConfig(configPath?: string, throwOnError = false, quiet = fa
|
|
|
78
79
|
*/
|
|
79
80
|
function mergeConfig(defaultConfig: SwallowKitConfig, userConfig: Partial<SwallowKitConfig>): SwallowKitConfig {
|
|
80
81
|
return {
|
|
82
|
+
frontend: {
|
|
83
|
+
...defaultConfig.frontend,
|
|
84
|
+
...userConfig.frontend,
|
|
85
|
+
},
|
|
81
86
|
database: {
|
|
82
87
|
...defaultConfig.database,
|
|
83
88
|
...userConfig.database,
|
|
@@ -108,6 +113,9 @@ function mergeConfig(defaultConfig: SwallowKitConfig, userConfig: Partial<Swallo
|
|
|
108
113
|
export function generateConfig(outputPath: string = "swallowkit.config.json"): void {
|
|
109
114
|
const config = {
|
|
110
115
|
$schema: "https://swallowkit.dev/schema.json",
|
|
116
|
+
frontend: {
|
|
117
|
+
framework: "nextjs",
|
|
118
|
+
},
|
|
111
119
|
database: {
|
|
112
120
|
connectionString: "your-cosmos-connection-string",
|
|
113
121
|
databaseName: "SwallowKitDB",
|
|
@@ -176,6 +184,14 @@ export function getBackendLanguage(configPath?: string): BackendLanguage {
|
|
|
176
184
|
return "typescript";
|
|
177
185
|
}
|
|
178
186
|
|
|
187
|
+
export function getFrontendFramework(configPath?: string): FrontendFramework {
|
|
188
|
+
const framework = getFullConfig(configPath).frontend?.framework ?? "nextjs";
|
|
189
|
+
if (framework !== "nextjs" && framework !== "react") {
|
|
190
|
+
throw new Error(`Unsupported frontend framework '${framework}'; use nextjs or react`);
|
|
191
|
+
}
|
|
192
|
+
return framework;
|
|
193
|
+
}
|
|
194
|
+
|
|
179
195
|
/**
|
|
180
196
|
* 完全な設定を取得(ファイル + 環境変数)
|
|
181
197
|
*/
|
|
@@ -250,6 +266,10 @@ function hasConfiguredDatabase(config: SwallowKitConfig): boolean {
|
|
|
250
266
|
export function validateConfig(config: SwallowKitConfig): { valid: boolean; errors: string[] } {
|
|
251
267
|
const errors: string[] = [];
|
|
252
268
|
|
|
269
|
+
if (config.frontend?.framework && !["nextjs", "react"].includes(config.frontend.framework)) {
|
|
270
|
+
errors.push("Frontend framework must be one of: nextjs, react");
|
|
271
|
+
}
|
|
272
|
+
|
|
253
273
|
const auth = normalizeAuthConfig(config.auth);
|
|
254
274
|
if (auth) {
|
|
255
275
|
for (const [name, scheme] of Object.entries(auth.schemes)) {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
|
-
import { BackendLanguage } from "../../types";
|
|
3
|
+
import { BackendLanguage, FrontendFramework } from "../../types";
|
|
4
4
|
import { PackageManager, getCommands } from "../../utils/package-manager";
|
|
5
5
|
|
|
6
6
|
export interface AgentInstructionContext {
|
|
7
7
|
projectName: string;
|
|
8
8
|
backendLanguage: BackendLanguage;
|
|
9
9
|
packageManager: PackageManager;
|
|
10
|
+
frontend?: FrontendFramework;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export interface AgentInstructionDocuments {
|
|
@@ -75,11 +76,23 @@ function getBackendRules(backendLanguage: BackendLanguage): string {
|
|
|
75
76
|
|
|
76
77
|
function interpolateTemplate(template: string, context: AgentInstructionContext): string {
|
|
77
78
|
const runCmd = getCommands(context.packageManager).exec;
|
|
79
|
+
const react = context.frontend === "react";
|
|
78
80
|
const replacements: Record<string, string> = {
|
|
79
81
|
projectName: context.projectName,
|
|
80
82
|
backendLanguageLabel: BACKEND_LANGUAGE_LABELS[context.backendLanguage],
|
|
81
83
|
functionsStructureLine: getFunctionsStructureLine(context.backendLanguage),
|
|
82
84
|
backendRules: getBackendRules(context.backendLanguage),
|
|
85
|
+
frontendLabel: react ? "React + Vite SPA" : "Next.js App Router",
|
|
86
|
+
bffLabel: react ? "SWA managed Functions" : "Next.js API routes",
|
|
87
|
+
bffPath: react ? "bff/src" : "app/api",
|
|
88
|
+
bffGlob: react ? "bff/**" : "app/api/**",
|
|
89
|
+
helperImport: react ? "../lib/call-function" : "@/lib/api/call-function",
|
|
90
|
+
frontendStructure: react
|
|
91
|
+
? "├── src/ # React SPA (Vite + React Router)\n│ └── pages/ # generated/custom UI and route registrations\n├── bff/ # SWA managed HTTP-only Functions\n│ ├── src/ # BFF handlers and request-local helpers\n│ └── {function}/ # function.json registrations"
|
|
92
|
+
: "├── app/ # Next.js App Router\n│ ├── api/ # BFF routes\n│ └── {model}/ # generated/custom UI",
|
|
93
|
+
devGuidance: react
|
|
94
|
+
? "Use http://localhost:4280 for the app. `swallowkit dev` runs Vite (3000), the managed BFF (7073), standalone Functions (7071), and SWA CLI (4280). SWA forwards /api only to the managed BFF, never directly to standalone Functions. `--no-functions` leaves the BFF running; `--no-swa` removes same-origin routing/auth emulation. BFF local settings belong in bff/local.settings.json. Build bundles BFF dependencies and copies staticwebapp.config.json into dist/. Deploy dist/ + bff/ together, and functions/ separately."
|
|
95
|
+
: "Use http://localhost:3000 for the app, or the SWA emulator port (4280) when testing SWA authentication. `swallowkit dev` runs Next.js and standalone Functions (7071). The BFF remains Next.js API routes.",
|
|
83
96
|
pm: context.packageManager,
|
|
84
97
|
runCmd,
|
|
85
98
|
};
|