swallowkit 1.0.0-beta.6 → 1.0.0-beta.7
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 +12 -6
- package/README.md +12 -6
- package/dist/cli/commands/dev.d.ts +8 -0
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +238 -30
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/init.d.ts +5 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +723 -285
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts +3 -0
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +181 -17
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/index.js +2 -0
- 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 +28 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/scaffold/functions-generator.d.ts +5 -0
- package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
- package/dist/core/scaffold/functions-generator.js +431 -0
- package/dist/core/scaffold/functions-generator.js.map +1 -1
- package/dist/core/scaffold/model-parser.d.ts +1 -1
- package/dist/core/scaffold/model-parser.js +1 -1
- package/dist/core/scaffold/nextjs-generator.js +1 -1
- package/dist/core/scaffold/openapi-generator.d.ts +3 -0
- package/dist/core/scaffold/openapi-generator.d.ts.map +1 -0
- package/dist/core/scaffold/openapi-generator.js +190 -0
- package/dist/core/scaffold/openapi-generator.js.map +1 -0
- package/dist/database/base-model.d.ts +3 -3
- package/dist/database/base-model.js +3 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/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 -1
- package/dist/utils/package-manager.d.ts.map +1 -1
- package/dist/utils/package-manager.js +10 -6
- package/dist/utils/package-manager.js.map +1 -1
- package/package.json +2 -1
- package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +445 -0
- package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +194 -0
- package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +524 -0
- package/src/__tests__/config.test.ts +122 -0
- package/src/__tests__/dev.test.ts +42 -0
- package/src/__tests__/fixtures.ts +83 -0
- package/src/__tests__/functions-generator.test.ts +101 -0
- package/src/__tests__/init.test.ts +59 -0
- package/src/__tests__/nextjs-generator.test.ts +97 -0
- package/src/__tests__/openapi-generator.test.ts +43 -0
- package/src/__tests__/package-manager.test.ts +189 -0
- package/src/__tests__/scaffold.test.ts +39 -0
- package/src/__tests__/string-utils.test.ts +75 -0
- package/src/__tests__/ui-generator.test.ts +144 -0
- package/src/cli/commands/create-model.ts +141 -0
- package/src/cli/commands/dev.ts +794 -0
- package/src/cli/commands/index.ts +8 -0
- package/src/cli/commands/init.ts +3363 -0
- package/src/cli/commands/provision.ts +193 -0
- package/src/cli/commands/scaffold.ts +786 -0
- package/src/cli/index.ts +73 -0
- package/src/core/config.ts +244 -0
- package/src/core/scaffold/functions-generator.ts +674 -0
- package/src/core/scaffold/model-parser.ts +627 -0
- package/src/core/scaffold/nextjs-generator.ts +217 -0
- package/src/core/scaffold/openapi-generator.ts +212 -0
- package/src/core/scaffold/ui-generator.ts +945 -0
- package/src/database/base-model.ts +184 -0
- package/src/database/client.ts +140 -0
- package/src/database/repository.ts +104 -0
- package/src/database/runtime-check.ts +25 -0
- package/src/index.ts +27 -0
- package/src/types/index.ts +45 -0
- package/src/utils/package-manager.ts +229 -0
|
@@ -37,11 +37,53 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.initCommand = initCommand;
|
|
40
|
+
exports.injectSwallowKitNextConfig = injectSwallowKitNextConfig;
|
|
41
|
+
exports.buildCSharpFunctionsProgramSource = buildCSharpFunctionsProgramSource;
|
|
42
|
+
exports.buildCSharpFunctionsProjectSource = buildCSharpFunctionsProjectSource;
|
|
40
43
|
const fs = __importStar(require("fs"));
|
|
41
44
|
const path = __importStar(require("path"));
|
|
42
45
|
const child_process_1 = require("child_process");
|
|
43
46
|
const prompts_1 = __importDefault(require("prompts"));
|
|
44
47
|
const package_manager_1 = require("../../utils/package-manager");
|
|
48
|
+
const BACKEND_LANGUAGE_CHOICES = [
|
|
49
|
+
{ title: "TypeScript", value: "typescript" },
|
|
50
|
+
{ title: "C#", value: "csharp" },
|
|
51
|
+
{ title: "Python", value: "python" },
|
|
52
|
+
];
|
|
53
|
+
function usesNodeFunctionsProject(backendLanguage) {
|
|
54
|
+
return backendLanguage === "typescript";
|
|
55
|
+
}
|
|
56
|
+
function getBackendLanguageLabel(backendLanguage) {
|
|
57
|
+
return BACKEND_LANGUAGE_CHOICES.find((choice) => choice.value === backendLanguage)?.title || backendLanguage;
|
|
58
|
+
}
|
|
59
|
+
function getFunctionsWorkerRuntime(backendLanguage) {
|
|
60
|
+
if (backendLanguage === "csharp") {
|
|
61
|
+
return "dotnet-isolated";
|
|
62
|
+
}
|
|
63
|
+
if (backendLanguage === "python") {
|
|
64
|
+
return "python";
|
|
65
|
+
}
|
|
66
|
+
return "node";
|
|
67
|
+
}
|
|
68
|
+
function getFunctionsRuntimeConfig(backendLanguage) {
|
|
69
|
+
if (backendLanguage === "csharp") {
|
|
70
|
+
return { name: "dotnet-isolated", version: "8.0" };
|
|
71
|
+
}
|
|
72
|
+
if (backendLanguage === "python") {
|
|
73
|
+
return { name: "python", version: "3.11" };
|
|
74
|
+
}
|
|
75
|
+
return { name: "node", version: "22" };
|
|
76
|
+
}
|
|
77
|
+
async function promptBackendLanguage() {
|
|
78
|
+
const response = await (0, prompts_1.default)({
|
|
79
|
+
type: "select",
|
|
80
|
+
name: "backendLanguage",
|
|
81
|
+
message: "Azure Functions backend language:",
|
|
82
|
+
choices: BACKEND_LANGUAGE_CHOICES,
|
|
83
|
+
initial: 0,
|
|
84
|
+
});
|
|
85
|
+
return response.backendLanguage || "typescript";
|
|
86
|
+
}
|
|
45
87
|
async function promptCiCd() {
|
|
46
88
|
const response = await (0, prompts_1.default)({
|
|
47
89
|
type: 'select',
|
|
@@ -83,6 +125,7 @@ async function promptAzureConfig() {
|
|
|
83
125
|
};
|
|
84
126
|
}
|
|
85
127
|
const VALID_CICD = ['github', 'azure', 'skip'];
|
|
128
|
+
const VALID_BACKEND_LANGUAGE = ['typescript', 'csharp', 'python'];
|
|
86
129
|
const VALID_COSMOS_DB_MODE = ['freetier', 'serverless'];
|
|
87
130
|
const VALID_VNET = ['none', 'outbound'];
|
|
88
131
|
function validateInitFlags(options) {
|
|
@@ -90,6 +133,10 @@ function validateInitFlags(options) {
|
|
|
90
133
|
console.error(`❌ Invalid --cicd value: "${options.cicd}". Must be: ${VALID_CICD.join(', ')}`);
|
|
91
134
|
process.exit(1);
|
|
92
135
|
}
|
|
136
|
+
if (options.backendLanguage && !VALID_BACKEND_LANGUAGE.includes(options.backendLanguage)) {
|
|
137
|
+
console.error(`❌ Invalid --backend-language value: "${options.backendLanguage}". Must be: ${VALID_BACKEND_LANGUAGE.join(', ')}`);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
93
140
|
if (options.cosmosDbMode && !VALID_COSMOS_DB_MODE.includes(options.cosmosDbMode)) {
|
|
94
141
|
console.error(`❌ Invalid --cosmos-db-mode value: "${options.cosmosDbMode}". Must be: ${VALID_COSMOS_DB_MODE.join(', ')}`);
|
|
95
142
|
process.exit(1);
|
|
@@ -117,6 +164,7 @@ async function initCommand(options) {
|
|
|
117
164
|
}
|
|
118
165
|
// Use flag values if provided, otherwise prompt interactively
|
|
119
166
|
const cicdProvider = options.cicd || await promptCiCd();
|
|
167
|
+
const backendLanguage = options.backendLanguage || await promptBackendLanguage();
|
|
120
168
|
const azureConfig = (options.cosmosDbMode && options.vnet)
|
|
121
169
|
? { cosmosDbMode: options.cosmosDbMode, vnetOption: options.vnet }
|
|
122
170
|
: await promptAzureConfig();
|
|
@@ -125,15 +173,15 @@ async function initCommand(options) {
|
|
|
125
173
|
// Upgrade Next.js to specified version (or latest) to avoid cached old versions
|
|
126
174
|
await upgradeNextJs(projectDir, options.nextVersion || 'latest', pm);
|
|
127
175
|
// Add SwallowKit specific files
|
|
128
|
-
await addSwallowKitFiles(projectDir, options, cicdProvider, azureConfig, pm);
|
|
176
|
+
await addSwallowKitFiles(projectDir, options, cicdProvider, azureConfig, pm, backendLanguage);
|
|
129
177
|
// Create infrastructure files (Bicep)
|
|
130
|
-
await createInfrastructure(projectDir, options.name, azureConfig);
|
|
178
|
+
await createInfrastructure(projectDir, options.name, azureConfig, backendLanguage);
|
|
131
179
|
// Create CI/CD files based on choice
|
|
132
180
|
if (cicdProvider === 'github') {
|
|
133
|
-
await createGitHubActionsWorkflows(projectDir, azureConfig, pm);
|
|
181
|
+
await createGitHubActionsWorkflows(projectDir, azureConfig, pm, backendLanguage);
|
|
134
182
|
}
|
|
135
183
|
else if (cicdProvider === 'azure') {
|
|
136
|
-
await createAzurePipelines(projectDir, pm);
|
|
184
|
+
await createAzurePipelines(projectDir, pm, backendLanguage);
|
|
137
185
|
}
|
|
138
186
|
// Initialize Git repository and create initial commit
|
|
139
187
|
try {
|
|
@@ -179,7 +227,7 @@ async function initCommand(options) {
|
|
|
179
227
|
console.log("\n📝 Next steps:");
|
|
180
228
|
console.log(` cd ${options.name}`);
|
|
181
229
|
console.log(` ${pmCmd.dlx} swallowkit create-model <name> # Create your first model`);
|
|
182
|
-
console.log(` ${pmCmd.dlx} swallowkit scaffold
|
|
230
|
+
console.log(` ${pmCmd.dlx} swallowkit scaffold shared/models/<name>.ts # Generate CRUD code`);
|
|
183
231
|
console.log(` ${pmCmd.dlx} swallowkit dev # Start development servers`);
|
|
184
232
|
console.log("\n🚀 Deploy to Azure:");
|
|
185
233
|
console.log(` ${pmCmd.dlx} swallowkit provision --resource-group <name>`);
|
|
@@ -285,7 +333,52 @@ async function installDependencies(projectDir, pm = 'pnpm') {
|
|
|
285
333
|
});
|
|
286
334
|
});
|
|
287
335
|
}
|
|
288
|
-
|
|
336
|
+
function injectSwallowKitNextConfig(nextConfigContent, projectName) {
|
|
337
|
+
return nextConfigContent.replace(/(const\s+nextConfig[:\s]*(?::\s*NextConfig\s*)?=\s*\{)(\s*\/\*[^*]*\*\/)?/, `$1\n output: 'standalone',\n transpilePackages: ['@${projectName}/shared'],\n serverExternalPackages: ['applicationinsights', 'diagnostic-channel-publishers'],$2`);
|
|
338
|
+
}
|
|
339
|
+
function buildCSharpFunctionsProgramSource() {
|
|
340
|
+
return `using Microsoft.Extensions.DependencyInjection;
|
|
341
|
+
using Microsoft.Extensions.Hosting;
|
|
342
|
+
|
|
343
|
+
var host = new HostBuilder()
|
|
344
|
+
.ConfigureFunctionsWorkerDefaults()
|
|
345
|
+
.ConfigureServices(services =>
|
|
346
|
+
{
|
|
347
|
+
services.AddApplicationInsightsTelemetryWorkerService();
|
|
348
|
+
})
|
|
349
|
+
.Build();
|
|
350
|
+
|
|
351
|
+
host.Run();
|
|
352
|
+
`;
|
|
353
|
+
}
|
|
354
|
+
function buildCSharpFunctionsProjectSource() {
|
|
355
|
+
return `<Project Sdk="Microsoft.NET.Sdk">
|
|
356
|
+
<PropertyGroup>
|
|
357
|
+
<TargetFramework>net8.0</TargetFramework>
|
|
358
|
+
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
|
|
359
|
+
<OutputType>Exe</OutputType>
|
|
360
|
+
<ImplicitUsings>enable</ImplicitUsings>
|
|
361
|
+
<Nullable>enable</Nullable>
|
|
362
|
+
</PropertyGroup>
|
|
363
|
+
<ItemGroup>
|
|
364
|
+
<Compile Remove="generated\\**\\bin\\**\\*.cs;generated\\**\\obj\\**\\*.cs" />
|
|
365
|
+
<EmbeddedResource Remove="generated\\**\\bin\\**;generated\\**\\obj\\**" />
|
|
366
|
+
<None Remove="generated\\**\\bin\\**;generated\\**\\obj\\**" />
|
|
367
|
+
</ItemGroup>
|
|
368
|
+
<ItemGroup>
|
|
369
|
+
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.47.0" />
|
|
370
|
+
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
|
371
|
+
<PackageReference Include="Azure.Identity" Version="1.13.2" />
|
|
372
|
+
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.23.0" />
|
|
373
|
+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.2.0" />
|
|
374
|
+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.18.0" OutputItemType="Analyzer" />
|
|
375
|
+
<PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.22.0" />
|
|
376
|
+
<PackageReference Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="1.2.0" />
|
|
377
|
+
</ItemGroup>
|
|
378
|
+
</Project>
|
|
379
|
+
`;
|
|
380
|
+
}
|
|
381
|
+
async function addSwallowKitFiles(projectDir, options, cicdChoice, azureConfig, pm, backendLanguage) {
|
|
289
382
|
console.log('📦 Adding SwallowKit files...\n');
|
|
290
383
|
const projectName = options.name;
|
|
291
384
|
// 1. Update package.json to add swallowkit and @azure/cosmos dependencies
|
|
@@ -300,11 +393,17 @@ async function addSwallowKitFiles(projectDir, options, cicdChoice, azureConfig,
|
|
|
300
393
|
'applicationinsights': '^3.3.0',
|
|
301
394
|
[`@${projectName}/shared`]: '*',
|
|
302
395
|
};
|
|
396
|
+
if (backendLanguage !== "typescript") {
|
|
397
|
+
packageJson.devDependencies = {
|
|
398
|
+
...packageJson.devDependencies,
|
|
399
|
+
'@openapitools/openapi-generator-cli': '^2.21.0',
|
|
400
|
+
};
|
|
401
|
+
}
|
|
303
402
|
packageJson.scripts = {
|
|
304
403
|
...packageJson.scripts,
|
|
305
404
|
'build': (0, package_manager_1.getBuildScript)(pm),
|
|
306
405
|
'start': 'next start',
|
|
307
|
-
'functions:start': (0, package_manager_1.getFunctionsStartScript)(pm),
|
|
406
|
+
'functions:start': (0, package_manager_1.getFunctionsStartScript)(pm, backendLanguage),
|
|
308
407
|
};
|
|
309
408
|
if (pm === 'pnpm') {
|
|
310
409
|
packageJson.packageManager = 'pnpm@latest';
|
|
@@ -313,7 +412,8 @@ async function addSwallowKitFiles(projectDir, options, cicdChoice, azureConfig,
|
|
|
313
412
|
node: '20.x',
|
|
314
413
|
};
|
|
315
414
|
// Workspace configuration depends on package manager
|
|
316
|
-
const
|
|
415
|
+
const workspacePackages = usesNodeFunctionsProject(backendLanguage) ? ['shared', 'functions'] : ['shared'];
|
|
416
|
+
const wsConfig = (0, package_manager_1.getWorkspaceConfig)(pm, workspacePackages);
|
|
317
417
|
if (wsConfig.type === 'file') {
|
|
318
418
|
// pnpm: workspaces are defined in pnpm-workspace.yaml
|
|
319
419
|
delete packageJson.workspaces;
|
|
@@ -336,11 +436,9 @@ async function addSwallowKitFiles(projectDir, options, cicdChoice, azureConfig,
|
|
|
336
436
|
}
|
|
337
437
|
if (fs.existsSync(nextConfigPath)) {
|
|
338
438
|
let nextConfigContent = fs.readFileSync(nextConfigPath, 'utf-8');
|
|
339
|
-
// Add output
|
|
439
|
+
// Add output, transpiled workspace package, and server externals for standalone deployment
|
|
340
440
|
if (!nextConfigContent.includes("output:") && !nextConfigContent.includes('output =')) {
|
|
341
|
-
|
|
342
|
-
// Handle JavaScript config format: const nextConfig = {
|
|
343
|
-
nextConfigContent = nextConfigContent.replace(/(const\s+nextConfig[:\s]*(?::\s*NextConfig\s*)?=\s*\{)(\s*\/\*[^*]*\*\/)?/, `$1\n output: 'standalone',\n transpilePackages: ['@${projectName}/shared'],\n experimental: {\n turbopackUseSystemTlsCerts: true,\n },\n serverExternalPackages: ['applicationinsights', 'diagnostic-channel-publishers'],$2`);
|
|
441
|
+
nextConfigContent = injectSwallowKitNextConfig(nextConfigContent, projectName);
|
|
344
442
|
fs.writeFileSync(nextConfigPath, nextConfigContent);
|
|
345
443
|
}
|
|
346
444
|
}
|
|
@@ -362,8 +460,11 @@ async function addSwallowKitFiles(projectDir, options, cicdChoice, azureConfig,
|
|
|
362
460
|
// 3. Create SwallowKit config
|
|
363
461
|
const swallowkitConfig = `/** @type {import('swallowkit').SwallowKitConfig} */
|
|
364
462
|
module.exports = {
|
|
463
|
+
backend: {
|
|
464
|
+
language: '${backendLanguage}',
|
|
465
|
+
},
|
|
365
466
|
functions: {
|
|
366
|
-
baseUrl: process.env.FUNCTIONS_BASE_URL || 'http://localhost:7071',
|
|
467
|
+
baseUrl: process.env.BACKEND_FUNCTIONS_BASE_URL || process.env.FUNCTIONS_BASE_URL || 'http://localhost:7071',
|
|
367
468
|
},
|
|
368
469
|
deployment: {
|
|
369
470
|
resourceGroup: process.env.AZURE_RESOURCE_GROUP || '',
|
|
@@ -382,7 +483,7 @@ module.exports = {
|
|
|
382
483
|
// Create backend utility for calling Azure Functions
|
|
383
484
|
const backendUtilContent = `// Get Functions base URL at runtime (not at build time)
|
|
384
485
|
function getFunctionsBaseUrl(): string {
|
|
385
|
-
return process.env.BACKEND_FUNCTIONS_BASE_URL || 'http://localhost:7071';
|
|
486
|
+
return process.env.BACKEND_FUNCTIONS_BASE_URL || process.env.FUNCTIONS_BASE_URL || 'http://localhost:7071';
|
|
386
487
|
}
|
|
387
488
|
|
|
388
489
|
/**
|
|
@@ -487,7 +588,7 @@ export const api = {
|
|
|
487
588
|
fs.mkdirSync(componentsDir, { recursive: true });
|
|
488
589
|
// 6. Create .env.example
|
|
489
590
|
const envExample = `# Azure Functions Backend URL
|
|
490
|
-
|
|
591
|
+
BACKEND_FUNCTIONS_BASE_URL=http://localhost:7071
|
|
491
592
|
|
|
492
593
|
# Azure Configuration
|
|
493
594
|
AZURE_RESOURCE_GROUP=your-resource-group
|
|
@@ -570,7 +671,7 @@ export async function register() {
|
|
|
570
671
|
// 8. Create .env.local for local development
|
|
571
672
|
const envLocalContent = [
|
|
572
673
|
'# Azure Functions Backend URL (Local)',
|
|
573
|
-
'
|
|
674
|
+
'BACKEND_FUNCTIONS_BASE_URL=http://localhost:7071',
|
|
574
675
|
''
|
|
575
676
|
].join('\n');
|
|
576
677
|
fs.writeFileSync(path.join(projectDir, '.env.local'), envLocalContent);
|
|
@@ -596,7 +697,7 @@ export async function register() {
|
|
|
596
697
|
};
|
|
597
698
|
fs.writeFileSync(path.join(projectDir, 'staticwebapp.config.json'), JSON.stringify(swaConfig, null, 2));
|
|
598
699
|
// 14. Create Azure Functions project
|
|
599
|
-
await createAzureFunctionsProject(projectDir, pm);
|
|
700
|
+
await createAzureFunctionsProject(projectDir, pm, backendLanguage);
|
|
600
701
|
// 15. Create BFF API route to call Azure Functions
|
|
601
702
|
await createBffApiRoute(projectDir);
|
|
602
703
|
// 16. Create home page
|
|
@@ -606,9 +707,9 @@ export async function register() {
|
|
|
606
707
|
await installDependencies(projectDir, pm);
|
|
607
708
|
console.log('✅ Project structure created\n');
|
|
608
709
|
// 18. Create README.md
|
|
609
|
-
createReadme(projectDir, projectName, cicdChoice, azureConfig, pm);
|
|
710
|
+
createReadme(projectDir, projectName, cicdChoice, azureConfig, pm, backendLanguage);
|
|
610
711
|
// 19. Create AI agent instruction files (AGENTS.md, CLAUDE.md, .github/copilot-instructions.md, etc.)
|
|
611
|
-
createAiAgentFiles(projectDir, projectName);
|
|
712
|
+
createAiAgentFiles(projectDir, projectName, backendLanguage);
|
|
612
713
|
}
|
|
613
714
|
async function createSharedPackage(projectDir, projectName) {
|
|
614
715
|
console.log('📦 Creating shared workspace package for Zod models...\n');
|
|
@@ -661,11 +762,93 @@ async function createSharedPackage(projectDir, projectName) {
|
|
|
661
762
|
fs.writeFileSync(path.join(sharedDir, '.gitignore'), `node_modules\ndist\n`);
|
|
662
763
|
console.log('✅ Shared package created\n');
|
|
663
764
|
}
|
|
664
|
-
async function createAzureFunctionsProject(projectDir, pm = 'pnpm') {
|
|
665
|
-
console.log(
|
|
765
|
+
async function createAzureFunctionsProject(projectDir, pm = 'pnpm', backendLanguage = 'typescript') {
|
|
766
|
+
console.log(`📦 Creating Azure Functions project (${getBackendLanguageLabel(backendLanguage)})...\n`);
|
|
666
767
|
const functionsDir = path.join(projectDir, 'functions');
|
|
667
768
|
fs.mkdirSync(functionsDir, { recursive: true });
|
|
668
|
-
|
|
769
|
+
const projectName = path.basename(projectDir);
|
|
770
|
+
const databaseName = `${projectName.charAt(0).toUpperCase() + projectName.slice(1)}Database`;
|
|
771
|
+
createFunctionsHostFiles(functionsDir, databaseName, backendLanguage);
|
|
772
|
+
if (backendLanguage === 'typescript') {
|
|
773
|
+
createTypeScriptFunctionsProject(projectDir, functionsDir, pm);
|
|
774
|
+
}
|
|
775
|
+
else if (backendLanguage === 'csharp') {
|
|
776
|
+
createCSharpFunctionsProject(projectDir, functionsDir);
|
|
777
|
+
}
|
|
778
|
+
else {
|
|
779
|
+
createPythonFunctionsProject(projectDir, functionsDir);
|
|
780
|
+
}
|
|
781
|
+
console.log('✅ Azure Functions project created\n');
|
|
782
|
+
}
|
|
783
|
+
function createFunctionsHostFiles(functionsDir, databaseName, backendLanguage) {
|
|
784
|
+
const hostJson = {
|
|
785
|
+
version: '2.0',
|
|
786
|
+
logging: {
|
|
787
|
+
applicationInsights: {
|
|
788
|
+
samplingSettings: {
|
|
789
|
+
isEnabled: true,
|
|
790
|
+
maxTelemetryItemsPerSecond: 20,
|
|
791
|
+
},
|
|
792
|
+
},
|
|
793
|
+
},
|
|
794
|
+
extensionBundle: {
|
|
795
|
+
id: 'Microsoft.Azure.Functions.ExtensionBundle',
|
|
796
|
+
version: '[4.0.0, 4.10.0)',
|
|
797
|
+
},
|
|
798
|
+
};
|
|
799
|
+
fs.writeFileSync(path.join(functionsDir, 'host.json'), JSON.stringify(hostJson, null, 2));
|
|
800
|
+
const localSettings = {
|
|
801
|
+
IsEncrypted: false,
|
|
802
|
+
Values: {
|
|
803
|
+
AzureWebJobsStorage: '',
|
|
804
|
+
FUNCTIONS_WORKER_RUNTIME: getFunctionsWorkerRuntime(backendLanguage),
|
|
805
|
+
AzureWebJobsFeatureFlags: 'EnableWorkerIndexing',
|
|
806
|
+
CosmosDBConnection: 'AccountEndpoint=http://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==',
|
|
807
|
+
COSMOS_DB_DATABASE_NAME: databaseName,
|
|
808
|
+
NODE_TLS_REJECT_UNAUTHORIZED: '0',
|
|
809
|
+
},
|
|
810
|
+
};
|
|
811
|
+
fs.writeFileSync(path.join(functionsDir, 'local.settings.json'), JSON.stringify(localSettings, null, 2));
|
|
812
|
+
const gitignoreLines = [
|
|
813
|
+
'local.settings.json',
|
|
814
|
+
'*.log',
|
|
815
|
+
'.vscode',
|
|
816
|
+
'.DS_Store',
|
|
817
|
+
];
|
|
818
|
+
if (backendLanguage === 'typescript') {
|
|
819
|
+
gitignoreLines.unshift('node_modules', 'dist');
|
|
820
|
+
fs.writeFileSync(path.join(functionsDir, '.funcignore'), `node_modules
|
|
821
|
+
.git
|
|
822
|
+
.vscode
|
|
823
|
+
local.settings.json
|
|
824
|
+
test
|
|
825
|
+
tsconfig.json
|
|
826
|
+
*.ts
|
|
827
|
+
!dist/**/*.js
|
|
828
|
+
`);
|
|
829
|
+
}
|
|
830
|
+
else if (backendLanguage === 'python') {
|
|
831
|
+
gitignoreLines.unshift('.venv', '__pycache__', '.python_packages');
|
|
832
|
+
fs.writeFileSync(path.join(functionsDir, '.funcignore'), `.venv
|
|
833
|
+
__pycache__
|
|
834
|
+
.pytest_cache
|
|
835
|
+
.mypy_cache
|
|
836
|
+
.ruff_cache
|
|
837
|
+
local.settings.json
|
|
838
|
+
tests
|
|
839
|
+
`);
|
|
840
|
+
}
|
|
841
|
+
else {
|
|
842
|
+
gitignoreLines.unshift('bin', 'obj');
|
|
843
|
+
fs.writeFileSync(path.join(functionsDir, '.funcignore'), `bin
|
|
844
|
+
obj
|
|
845
|
+
local.settings.json
|
|
846
|
+
tests
|
|
847
|
+
`);
|
|
848
|
+
}
|
|
849
|
+
fs.writeFileSync(path.join(functionsDir, '.gitignore'), `${gitignoreLines.join('\n')}\n`);
|
|
850
|
+
}
|
|
851
|
+
function createTypeScriptFunctionsProject(projectDir, functionsDir, pm) {
|
|
669
852
|
const functionsPackageJson = {
|
|
670
853
|
name: 'functions',
|
|
671
854
|
version: '1.0.0',
|
|
@@ -674,22 +857,21 @@ async function createAzureFunctionsProject(projectDir, pm = 'pnpm') {
|
|
|
674
857
|
scripts: {
|
|
675
858
|
start: 'func start',
|
|
676
859
|
build: 'tsc',
|
|
677
|
-
prestart: (0, package_manager_1.getFunctionsPrestart)(pm)
|
|
860
|
+
prestart: (0, package_manager_1.getFunctionsPrestart)(pm),
|
|
678
861
|
},
|
|
679
862
|
dependencies: {
|
|
680
863
|
'@azure/functions': '~4.5.0',
|
|
681
864
|
'@azure/cosmos': '^4.0.0',
|
|
682
865
|
'@azure/identity': '^4.0.0',
|
|
683
|
-
|
|
866
|
+
zod: '>=3.25.0',
|
|
684
867
|
[`@${path.basename(projectDir)}/shared`]: '*',
|
|
685
868
|
},
|
|
686
869
|
devDependencies: {
|
|
687
870
|
'@types/node': '^20.0.0',
|
|
688
|
-
|
|
689
|
-
}
|
|
871
|
+
typescript: '^5.0.0',
|
|
872
|
+
},
|
|
690
873
|
};
|
|
691
874
|
fs.writeFileSync(path.join(functionsDir, 'package.json'), JSON.stringify(functionsPackageJson, null, 2));
|
|
692
|
-
// Create functions tsconfig.json
|
|
693
875
|
const sharedPkgName = `@${path.basename(projectDir)}/shared`;
|
|
694
876
|
const functionsTsConfig = {
|
|
695
877
|
compilerOptions: {
|
|
@@ -709,69 +891,14 @@ async function createAzureFunctionsProject(projectDir, pm = 'pnpm') {
|
|
|
709
891
|
},
|
|
710
892
|
},
|
|
711
893
|
include: ['src/**/*'],
|
|
712
|
-
exclude: ['node_modules', 'dist']
|
|
894
|
+
exclude: ['node_modules', 'dist'],
|
|
713
895
|
};
|
|
714
896
|
fs.writeFileSync(path.join(functionsDir, 'tsconfig.json'), JSON.stringify(functionsTsConfig, null, 2));
|
|
715
|
-
// Create host.json
|
|
716
|
-
const hostJson = {
|
|
717
|
-
version: '2.0',
|
|
718
|
-
logging: {
|
|
719
|
-
applicationInsights: {
|
|
720
|
-
samplingSettings: {
|
|
721
|
-
isEnabled: true,
|
|
722
|
-
maxTelemetryItemsPerSecond: 20
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
},
|
|
726
|
-
extensionBundle: {
|
|
727
|
-
id: 'Microsoft.Azure.Functions.ExtensionBundle',
|
|
728
|
-
version: '[4.0.0, 4.10.0)'
|
|
729
|
-
}
|
|
730
|
-
};
|
|
731
|
-
fs.writeFileSync(path.join(functionsDir, 'host.json'), JSON.stringify(hostJson, null, 2));
|
|
732
|
-
// Create .funcignore
|
|
733
|
-
const funcignore = `node_modules
|
|
734
|
-
.git
|
|
735
|
-
.vscode
|
|
736
|
-
local.settings.json
|
|
737
|
-
test
|
|
738
|
-
tsconfig.json
|
|
739
|
-
*.ts
|
|
740
|
-
!dist/**/*.js
|
|
741
|
-
`;
|
|
742
|
-
fs.writeFileSync(path.join(functionsDir, '.funcignore'), funcignore);
|
|
743
|
-
// Create .gitignore for functions directory
|
|
744
|
-
const functionsGitignore = `node_modules
|
|
745
|
-
dist
|
|
746
|
-
local.settings.json
|
|
747
|
-
*.log
|
|
748
|
-
.vscode
|
|
749
|
-
.DS_Store
|
|
750
|
-
`;
|
|
751
|
-
fs.writeFileSync(path.join(functionsDir, '.gitignore'), functionsGitignore);
|
|
752
|
-
// Create local.settings.json
|
|
753
|
-
const projectName = path.basename(projectDir);
|
|
754
|
-
const databaseName = `${projectName.charAt(0).toUpperCase() + projectName.slice(1)}Database`;
|
|
755
|
-
const localSettings = {
|
|
756
|
-
IsEncrypted: false,
|
|
757
|
-
Values: {
|
|
758
|
-
AzureWebJobsStorage: '',
|
|
759
|
-
FUNCTIONS_WORKER_RUNTIME: 'node',
|
|
760
|
-
AzureWebJobsFeatureFlags: 'EnableWorkerIndexing',
|
|
761
|
-
CosmosDBConnection: 'AccountEndpoint=http://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==',
|
|
762
|
-
COSMOS_DB_DATABASE_NAME: databaseName,
|
|
763
|
-
NODE_TLS_REJECT_UNAUTHORIZED: '0'
|
|
764
|
-
}
|
|
765
|
-
};
|
|
766
|
-
fs.writeFileSync(path.join(functionsDir, 'local.settings.json'), JSON.stringify(localSettings, null, 2));
|
|
767
|
-
// Create src directory
|
|
768
897
|
const srcDir = path.join(functionsDir, 'src');
|
|
769
898
|
fs.mkdirSync(srcDir, { recursive: true });
|
|
770
|
-
|
|
771
|
-
const greetFunction = `import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
|
|
899
|
+
fs.writeFileSync(path.join(srcDir, 'greet.ts'), `import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
|
|
772
900
|
import { z } from 'zod/v4';
|
|
773
901
|
|
|
774
|
-
// Zod schema for request validation
|
|
775
902
|
const greetRequestSchema = z.object({
|
|
776
903
|
name: z.string().min(1, 'Name is required').max(50, 'Name must be less than 50 characters'),
|
|
777
904
|
});
|
|
@@ -780,12 +907,9 @@ export async function greet(request: HttpRequest, context: InvocationContext): P
|
|
|
780
907
|
context.log('HTTP trigger function processed a request.');
|
|
781
908
|
|
|
782
909
|
try {
|
|
783
|
-
// Get name from query or body
|
|
784
910
|
const name = request.query.get('name') || (await request.text());
|
|
785
|
-
|
|
786
|
-
// Validate with Zod
|
|
787
911
|
const result = greetRequestSchema.safeParse({ name });
|
|
788
|
-
|
|
912
|
+
|
|
789
913
|
if (!result.success) {
|
|
790
914
|
return {
|
|
791
915
|
status: 400,
|
|
@@ -796,7 +920,7 @@ export async function greet(request: HttpRequest, context: InvocationContext): P
|
|
|
796
920
|
}
|
|
797
921
|
|
|
798
922
|
const greeting = \`Hello, \${result.data.name}! This message is from Azure Functions.\`;
|
|
799
|
-
|
|
923
|
+
|
|
800
924
|
return {
|
|
801
925
|
status: 200,
|
|
802
926
|
jsonBody: {
|
|
@@ -820,10 +944,89 @@ app.http('greet', {
|
|
|
820
944
|
authLevel: 'anonymous',
|
|
821
945
|
handler: greet
|
|
822
946
|
});
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
947
|
+
`);
|
|
948
|
+
}
|
|
949
|
+
function createCSharpFunctionsProject(projectDir, functionsDir) {
|
|
950
|
+
const projectBaseName = path.basename(projectDir);
|
|
951
|
+
const projectPascal = projectBaseName.charAt(0).toUpperCase() + projectBaseName.slice(1);
|
|
952
|
+
const csprojName = `${projectPascal}.Functions.csproj`;
|
|
953
|
+
fs.writeFileSync(path.join(functionsDir, csprojName), buildCSharpFunctionsProjectSource());
|
|
954
|
+
fs.writeFileSync(path.join(functionsDir, 'Program.cs'), buildCSharpFunctionsProgramSource());
|
|
955
|
+
const crudDir = path.join(functionsDir, 'Crud');
|
|
956
|
+
fs.mkdirSync(crudDir, { recursive: true });
|
|
957
|
+
fs.writeFileSync(path.join(crudDir, 'GreetFunction.cs'), `using System.Net;
|
|
958
|
+
using Microsoft.Azure.Functions.Worker;
|
|
959
|
+
using Microsoft.Azure.Functions.Worker.Http;
|
|
960
|
+
|
|
961
|
+
namespace SwallowKit.Functions;
|
|
962
|
+
|
|
963
|
+
public sealed class GreetFunction
|
|
964
|
+
{
|
|
965
|
+
[Function("greet")]
|
|
966
|
+
public async Task<HttpResponseData> Run(
|
|
967
|
+
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "greet")] HttpRequestData request)
|
|
968
|
+
{
|
|
969
|
+
var query = request.Url.Query.TrimStart('?').Split('&', StringSplitOptions.RemoveEmptyEntries);
|
|
970
|
+
var name = "SwallowKit";
|
|
971
|
+
foreach (var segment in query)
|
|
972
|
+
{
|
|
973
|
+
var parts = segment.Split('=', 2);
|
|
974
|
+
if (parts.Length == 2 && parts[0] == "name")
|
|
975
|
+
{
|
|
976
|
+
name = Uri.UnescapeDataString(parts[1]);
|
|
977
|
+
break;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
var response = request.CreateResponse(HttpStatusCode.OK);
|
|
981
|
+
await response.WriteAsJsonAsync(new
|
|
982
|
+
{
|
|
983
|
+
message = $"Hello, {name}! This message is from Azure Functions.",
|
|
984
|
+
timestamp = DateTimeOffset.UtcNow.ToString("O"),
|
|
985
|
+
});
|
|
986
|
+
return response;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
`);
|
|
990
|
+
}
|
|
991
|
+
function createPythonFunctionsProject(projectDir, functionsDir) {
|
|
992
|
+
fs.writeFileSync(path.join(projectDir, '.python-version'), '3.11\n');
|
|
993
|
+
fs.writeFileSync(path.join(functionsDir, 'requirements.txt'), `azure-functions>=1.20.0
|
|
994
|
+
azure-cosmos>=4.9.0
|
|
995
|
+
azure-identity>=1.19.0
|
|
996
|
+
`);
|
|
997
|
+
const blueprintsDir = path.join(functionsDir, 'blueprints');
|
|
998
|
+
fs.mkdirSync(blueprintsDir, { recursive: true });
|
|
999
|
+
fs.writeFileSync(path.join(blueprintsDir, '__init__.py'), '');
|
|
1000
|
+
fs.writeFileSync(path.join(blueprintsDir, 'greet.py'), `import json
|
|
1001
|
+
from datetime import datetime, timezone
|
|
1002
|
+
|
|
1003
|
+
import azure.functions as func
|
|
1004
|
+
|
|
1005
|
+
bp = func.Blueprint()
|
|
1006
|
+
|
|
1007
|
+
|
|
1008
|
+
@bp.route(route="greet", methods=["GET", "POST"])
|
|
1009
|
+
def greet(req: func.HttpRequest) -> func.HttpResponse:
|
|
1010
|
+
name = req.params.get("name") or "SwallowKit"
|
|
1011
|
+
payload = {
|
|
1012
|
+
"message": f"Hello, {name}! This message is from Azure Functions.",
|
|
1013
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
1014
|
+
}
|
|
1015
|
+
return func.HttpResponse(
|
|
1016
|
+
body=json.dumps(payload, ensure_ascii=False),
|
|
1017
|
+
status_code=200,
|
|
1018
|
+
mimetype="application/json",
|
|
1019
|
+
)
|
|
1020
|
+
`);
|
|
1021
|
+
fs.writeFileSync(path.join(functionsDir, 'function_app.py'), `import azure.functions as func
|
|
1022
|
+
|
|
1023
|
+
from blueprints.greet import bp as greet_bp
|
|
1024
|
+
|
|
1025
|
+
app = func.FunctionApp(http_auth_level=func.AuthLevel.ANONYMOUS)
|
|
1026
|
+
|
|
1027
|
+
app.register_blueprint(greet_bp)
|
|
1028
|
+
# SwallowKit scaffold registrations
|
|
1029
|
+
`);
|
|
827
1030
|
}
|
|
828
1031
|
async function createBffApiRoute(projectDir) {
|
|
829
1032
|
console.log('📦 Creating BFF API route...\n');
|
|
@@ -874,17 +1077,17 @@ export async function POST(request: NextRequest) {
|
|
|
874
1077
|
}
|
|
875
1078
|
`;
|
|
876
1079
|
fs.writeFileSync(path.join(apiDir, 'route.ts'), apiRoute);
|
|
877
|
-
// Update .env.example to include
|
|
1080
|
+
// Update .env.example to include BACKEND_FUNCTIONS_BASE_URL
|
|
878
1081
|
const envExamplePath = path.join(projectDir, '.env.example');
|
|
879
1082
|
let envExample = fs.readFileSync(envExamplePath, 'utf-8');
|
|
880
|
-
if (!envExample.includes('
|
|
1083
|
+
if (!envExample.includes('BACKEND_FUNCTIONS_BASE_URL')) {
|
|
881
1084
|
envExample += `\n# Azure Functions Backend URL\nBACKEND_FUNCTIONS_BASE_URL=http://localhost:7071\n`;
|
|
882
1085
|
fs.writeFileSync(envExamplePath, envExample);
|
|
883
1086
|
}
|
|
884
1087
|
// Update .env.local
|
|
885
1088
|
const envLocalPath = path.join(projectDir, '.env.local');
|
|
886
1089
|
let envLocal = fs.readFileSync(envLocalPath, 'utf-8');
|
|
887
|
-
if (!envLocal.includes('
|
|
1090
|
+
if (!envLocal.includes('BACKEND_FUNCTIONS_BASE_URL')) {
|
|
888
1091
|
envLocal += `\n# Azure Functions Backend URL (Local)\nBACKEND_FUNCTIONS_BASE_URL=http://localhost:7071\n`;
|
|
889
1092
|
fs.writeFileSync(envLocalPath, envLocal);
|
|
890
1093
|
}
|
|
@@ -985,7 +1188,7 @@ export default function Home() {
|
|
|
985
1188
|
Create your first model with Zod and generate CRUD operations automatically.
|
|
986
1189
|
</p>
|
|
987
1190
|
<code className="block bg-gray-100 dark:bg-gray-900 p-4 rounded text-left text-sm">
|
|
988
|
-
${pmCmd.dlx} swallowkit scaffold
|
|
1191
|
+
${pmCmd.dlx} swallowkit scaffold shared/models/your-model.ts
|
|
989
1192
|
</code>
|
|
990
1193
|
</div>
|
|
991
1194
|
</section>
|
|
@@ -1021,13 +1224,28 @@ export const scaffoldConfig = {
|
|
|
1021
1224
|
fs.writeFileSync(path.join(scaffoldConfigDir, 'scaffold-config.ts'), scaffoldConfigContent);
|
|
1022
1225
|
console.log('✅ Scaffold config created\n');
|
|
1023
1226
|
}
|
|
1024
|
-
function createReadme(projectDir, projectName, cicdChoice, azureConfig, pm) {
|
|
1227
|
+
function createReadme(projectDir, projectName, cicdChoice, azureConfig, pm, backendLanguage) {
|
|
1025
1228
|
console.log('📝 Creating README.md...\n');
|
|
1026
1229
|
const pmCmd = (0, package_manager_1.getCommands)(pm);
|
|
1027
1230
|
const cosmosDbModeLabel = azureConfig.cosmosDbMode === 'freetier' ? 'Free Tier (1000 RU/s)' : 'Serverless';
|
|
1028
1231
|
const cicdLabel = cicdChoice === 'github' ? 'GitHub Actions' : cicdChoice === 'azure' ? 'Azure Pipelines' : 'None';
|
|
1029
1232
|
const vnetLabel = azureConfig.vnetOption === 'none' ? 'None (public endpoints)' :
|
|
1030
1233
|
'Outbound VNet (Cosmos DB Private Endpoint)';
|
|
1234
|
+
const backendLanguageLabel = getBackendLanguageLabel(backendLanguage);
|
|
1235
|
+
const schemaBridgeDescription = backendLanguage === 'typescript'
|
|
1236
|
+
? 'Zod (shared between frontend and backend)'
|
|
1237
|
+
: `Zod + OpenAPI bridge (Zod in shared/, generated ${backendLanguageLabel} schemas in functions/generated/)`;
|
|
1238
|
+
const functionsTree = backendLanguage === 'typescript'
|
|
1239
|
+
? `│ └── src/\n│ └── greet.ts # Sample function`
|
|
1240
|
+
: backendLanguage === 'csharp'
|
|
1241
|
+
? `│ ├── Crud/\n│ │ └── GreetFunction.cs\n│ └── generated/ # OpenAPI-derived C# models`
|
|
1242
|
+
: `│ ├── blueprints/\n│ │ └── greet.py\n│ └── generated/ # OpenAPI-derived Python models`;
|
|
1243
|
+
const backendScaffoldNote = backendLanguage === 'typescript'
|
|
1244
|
+
? '- Azure Functions CRUD endpoints'
|
|
1245
|
+
: `- Azure Functions ${backendLanguageLabel} CRUD handlers\n- OpenAPI spec + generated ${backendLanguageLabel} schema assets`;
|
|
1246
|
+
const pythonLocalDevNote = backendLanguage === 'python'
|
|
1247
|
+
? `\n**Python local dev note**: SwallowKit uses \`functions/.venv\` for local Azure Functions development. If \`uv\` is installed, \`swallowkit dev\` uses it to create/manage that virtual environment; otherwise it falls back to the standard \`venv\` + \`pip\` workflow. Keep \`functions/requirements.txt\` as the dependency source of truth for Azure Functions compatibility.\n`
|
|
1248
|
+
: '';
|
|
1031
1249
|
const readme = `# ${projectName}
|
|
1032
1250
|
|
|
1033
1251
|
A full-stack application built with **SwallowKit** - Next.js on Azure Static Web Apps + Functions + Cosmos DB with Zod schema sharing.
|
|
@@ -1036,9 +1254,9 @@ A full-stack application built with **SwallowKit** - Next.js on Azure Static Web
|
|
|
1036
1254
|
|
|
1037
1255
|
- **Frontend**: Next.js 15 (App Router), React, TypeScript, Tailwind CSS
|
|
1038
1256
|
- **BFF (Backend for Frontend)**: Next.js API Routes
|
|
1039
|
-
- **Backend**: Azure Functions (
|
|
1257
|
+
- **Backend**: Azure Functions (${backendLanguageLabel})
|
|
1040
1258
|
- **Database**: Azure Cosmos DB
|
|
1041
|
-
- **Schema Validation**:
|
|
1259
|
+
- **Schema Validation**: ${schemaBridgeDescription}
|
|
1042
1260
|
- **Infrastructure**: Bicep (Infrastructure as Code)
|
|
1043
1261
|
- **CI/CD**: ${cicdLabel}
|
|
1044
1262
|
|
|
@@ -1073,11 +1291,8 @@ ${projectName}/
|
|
|
1073
1291
|
│ ├── api/ # BFF API routes (proxy to Functions)
|
|
1074
1292
|
│ └── page.tsx # Home page
|
|
1075
1293
|
├── functions/ # Azure Functions (backend)
|
|
1076
|
-
|
|
1077
|
-
│ ├── models/ # Data models (copied from lib/models)
|
|
1078
|
-
│ └── hello.ts # Sample function
|
|
1294
|
+
${functionsTree}
|
|
1079
1295
|
├── lib/
|
|
1080
|
-
│ ├── models/ # Shared Zod schemas
|
|
1081
1296
|
│ └── api/ # API client utilities
|
|
1082
1297
|
├── infra/ # Bicep infrastructure files
|
|
1083
1298
|
│ ├── main.bicep
|
|
@@ -1095,18 +1310,18 @@ Define your data model with Zod schema:
|
|
|
1095
1310
|
${pmCmd.dlx} swallowkit create-model <model-name>
|
|
1096
1311
|
\`\`\`
|
|
1097
1312
|
|
|
1098
|
-
This creates a model file in \`
|
|
1313
|
+
This creates a model file in \`shared/models/<model-name>.ts\`. Edit it to define your schema.
|
|
1099
1314
|
|
|
1100
1315
|
### 2. Generate CRUD Code
|
|
1101
1316
|
|
|
1102
1317
|
Generate complete CRUD operations (Functions, API routes, UI):
|
|
1103
1318
|
|
|
1104
1319
|
\`\`\`bash
|
|
1105
|
-
${pmCmd.dlx} swallowkit scaffold
|
|
1320
|
+
${pmCmd.dlx} swallowkit scaffold shared/models/<model-name>.ts
|
|
1106
1321
|
\`\`\`
|
|
1107
1322
|
|
|
1108
1323
|
This generates:
|
|
1109
|
-
|
|
1324
|
+
${backendScaffoldNote}
|
|
1110
1325
|
- Next.js BFF API routes
|
|
1111
1326
|
- React UI components (list, detail, create, edit)
|
|
1112
1327
|
- Navigation menu integration
|
|
@@ -1123,6 +1338,7 @@ This starts:
|
|
|
1123
1338
|
- Cosmos DB Emulator check (must be running separately)
|
|
1124
1339
|
|
|
1125
1340
|
**Note**: You need to start Cosmos DB Emulator manually before running \`swallowkit dev\`.
|
|
1341
|
+
${pythonLocalDevNote}
|
|
1126
1342
|
|
|
1127
1343
|
## ☁️ Deploy to Azure
|
|
1128
1344
|
|
|
@@ -1237,8 +1453,20 @@ This project was generated by SwallowKit. If you encounter any issues or have su
|
|
|
1237
1453
|
fs.writeFileSync(path.join(projectDir, 'README.md'), readme);
|
|
1238
1454
|
console.log('✅ README.md created\n');
|
|
1239
1455
|
}
|
|
1240
|
-
function createAiAgentFiles(projectDir, projectName) {
|
|
1456
|
+
function createAiAgentFiles(projectDir, projectName, backendLanguage) {
|
|
1241
1457
|
console.log('🤖 Creating AI agent instruction files...\n');
|
|
1458
|
+
const backendLanguageLabel = getBackendLanguageLabel(backendLanguage);
|
|
1459
|
+
const functionsStructureLine = backendLanguage === 'typescript'
|
|
1460
|
+
? `│ └── src/ # HTTP trigger handlers with Cosmos DB bindings`
|
|
1461
|
+
: backendLanguage === 'csharp'
|
|
1462
|
+
? `│ ├── Crud/ # C# HTTP trigger handlers\n│ └── generated/ # OpenAPI-derived C# schema assets`
|
|
1463
|
+
: `│ ├── blueprints/ # Python HTTP trigger handlers\n│ └── generated/ # OpenAPI-derived Python schema assets`;
|
|
1464
|
+
const backendSchemaNote = backendLanguage === 'typescript'
|
|
1465
|
+
? `- The shared package (\`@${projectName}/shared\`) is consumed by both Next.js and Azure Functions as a workspace dependency.`
|
|
1466
|
+
: `- The frontend/BFF source of truth stays in \`shared/models/\` as Zod schemas.\n- \`swallowkit scaffold\` exports OpenAPI into \`functions/openapi/\` and generates ${backendLanguageLabel} schema assets into \`functions/generated/\` for backend use.`;
|
|
1467
|
+
const backendRulesNote = backendLanguage === 'typescript'
|
|
1468
|
+
? `- All CRUD operations and business logic live in \`functions/src/\`.\n- Use Azure Functions Cosmos DB **input/output bindings** (\`extraInputs\`/\`extraOutputs\`) for reads and writes.\n- Use the Cosmos DB SDK client directly **only** for delete operations (bindings do not support delete).\n- Validate all data against Zod schemas before writing to Cosmos DB.\n- The backend auto-generates \`id\` (UUID), \`createdAt\`, and \`updatedAt\` — never trust client-sent values for these fields.`
|
|
1469
|
+
: `- All business logic lives in \`functions/\` and the generated handlers perform real Cosmos DB CRUD.\n- Keep Zod schemas in \`shared/models/\` as the source of truth.\n- Regenerate backend contracts with \`swallowkit scaffold shared/models/<name>.ts\` whenever a schema changes.\n- Use the generated OpenAPI-derived models in \`functions/generated/\` to keep backend contracts aligned.\n- The backend should still own \`id\`, \`createdAt\`, and \`updatedAt\`.`;
|
|
1242
1470
|
// ── 1. AGENTS.md (Codex / generic agents) ──────────────────────────
|
|
1243
1471
|
const agentsMd = `# AGENTS.md
|
|
1244
1472
|
|
|
@@ -1247,14 +1475,14 @@ All coding agents **must** follow the architecture and conventions described bel
|
|
|
1247
1475
|
|
|
1248
1476
|
## Architecture Overview
|
|
1249
1477
|
|
|
1250
|
-
This is a full-stack
|
|
1478
|
+
This is a full-stack application deployed on Azure with a TypeScript frontend/BFF and an Azure Functions backend in ${backendLanguageLabel}.
|
|
1251
1479
|
|
|
1252
1480
|
\`\`\`
|
|
1253
1481
|
Frontend (React / Next.js App Router)
|
|
1254
1482
|
↓ fetch('/api/{model}', ...)
|
|
1255
1483
|
BFF Layer (Next.js API Routes)
|
|
1256
1484
|
↓ HTTP → Azure Functions
|
|
1257
|
-
Backend (Azure Functions
|
|
1485
|
+
Backend (Azure Functions)
|
|
1258
1486
|
↓
|
|
1259
1487
|
Azure Cosmos DB (Document Database)
|
|
1260
1488
|
\`\`\`
|
|
@@ -1267,7 +1495,7 @@ ${projectName}/
|
|
|
1267
1495
|
│ ├── api/ # BFF API routes (proxy to Azure Functions)
|
|
1268
1496
|
│ └── {model}/ # UI pages per model (list, detail, create, edit)
|
|
1269
1497
|
├── functions/ # Azure Functions (backend)
|
|
1270
|
-
|
|
1498
|
+
${functionsStructureLine}
|
|
1271
1499
|
├── shared/ # Shared workspace package
|
|
1272
1500
|
│ ├── models/ # Zod schema definitions (single source of truth)
|
|
1273
1501
|
│ └── index.ts # Re-exports all models
|
|
@@ -1322,8 +1550,7 @@ export async function POST(request: NextRequest) {
|
|
|
1322
1550
|
|
|
1323
1551
|
- All data models are defined **once** as Zod schemas in \`shared/models/\`.
|
|
1324
1552
|
- TypeScript types are derived with \`z.infer<typeof Schema>\` — never define types separately.
|
|
1325
|
-
-
|
|
1326
|
-
- The shared package (\`@${projectName}/shared\`) is consumed by both Next.js and Azure Functions as a workspace dependency.
|
|
1553
|
+
- ${backendSchemaNote}
|
|
1327
1554
|
|
|
1328
1555
|
Model definition pattern:
|
|
1329
1556
|
|
|
@@ -1350,15 +1577,11 @@ Key rules:
|
|
|
1350
1577
|
|
|
1351
1578
|
### 3. Azure Functions Own All Business Logic and Data Access
|
|
1352
1579
|
|
|
1353
|
-
-
|
|
1354
|
-
- Use Azure Functions Cosmos DB **input/output bindings** (\`extraInputs\`/\`extraOutputs\`) for reads and writes.
|
|
1355
|
-
- Use the Cosmos DB SDK client directly **only** for delete operations (bindings do not support delete).
|
|
1356
|
-
- Validate all data against Zod schemas before writing to Cosmos DB.
|
|
1357
|
-
- The backend auto-generates \`id\` (UUID), \`createdAt\`, and \`updatedAt\` — never trust client-sent values for these fields.
|
|
1580
|
+
- ${backendRulesNote}
|
|
1358
1581
|
|
|
1359
|
-
Azure Functions handler pattern:
|
|
1582
|
+
${backendLanguage === 'typescript' ? 'Azure Functions handler pattern:' : `Generated ${backendLanguageLabel} handlers live under \`functions/\`. Re-run \`swallowkit scaffold shared/models/<name>.ts\` after schema changes to keep generated CRUD handlers and \`functions/generated/\` in sync.`}
|
|
1360
1583
|
|
|
1361
|
-
|
|
1584
|
+
${backendLanguage === 'typescript' ? `\`\`\`typescript
|
|
1362
1585
|
// functions/src/{model}.ts
|
|
1363
1586
|
import { app } from '@azure/functions';
|
|
1364
1587
|
import { ModelSchema } from '@${projectName}/shared';
|
|
@@ -1376,7 +1599,7 @@ app.http('{model}-get-all', {
|
|
|
1376
1599
|
return { status: 200, jsonBody: validated };
|
|
1377
1600
|
},
|
|
1378
1601
|
});
|
|
1379
|
-
|
|
1602
|
+
\`\`\`` : ''}
|
|
1380
1603
|
|
|
1381
1604
|
## Naming Conventions
|
|
1382
1605
|
|
|
@@ -1384,7 +1607,7 @@ app.http('{model}-get-all', {
|
|
|
1384
1607
|
|------|-----------|---------|
|
|
1385
1608
|
| Model schema file | \`shared/models/{kebab-case}.ts\` | \`shared/models/todo.ts\` |
|
|
1386
1609
|
| Schema/type name | PascalCase (same name for both) | \`export const Todo = z.object({...}); export type Todo = z.infer<typeof Todo>;\` |
|
|
1387
|
-
| Functions handler file | \`functions/src/
|
|
1610
|
+
| Functions handler file | backend-language specific under \`functions/\` | \`${backendLanguage === 'typescript' ? 'functions/src/todo.ts' : backendLanguage === 'csharp' ? 'functions/Crud/TodoFunctions.cs' : 'functions/blueprints/todo.py'}\` |
|
|
1388
1611
|
| Functions handler name | \`{camelCase}-{operation}\` | \`todo-get-all\`, \`todo-create\` |
|
|
1389
1612
|
| API route path | \`/api/{camelCase}\` | \`/api/todo\`, \`/api/todo/{id}\` |
|
|
1390
1613
|
| BFF route file | \`app/api/{kebab-case}/route.ts\` | \`app/api/todo/route.ts\` |
|
|
@@ -1417,7 +1640,7 @@ npx swallowkit scaffold shared/models/<name>.ts
|
|
|
1417
1640
|
\`\`\`
|
|
1418
1641
|
|
|
1419
1642
|
Generates:
|
|
1420
|
-
- Azure Functions handlers (\`functions/src/<name>.ts\`)
|
|
1643
|
+
- Azure Functions handlers (${backendLanguage === 'typescript' ? '\`functions/src/<name>.ts\`' : '\`functions/\` language-specific CRUD files + \`functions/generated/\` schema assets'})
|
|
1421
1644
|
- BFF API routes (\`app/api/<name>/route.ts\`, \`app/api/<name>/[id]/route.ts\`)
|
|
1422
1645
|
- UI pages (\`app/<name>/page.tsx\`, detail, create, edit pages)
|
|
1423
1646
|
- Cosmos DB Bicep container config (\`infra/containers/<name>-container.bicep\`)
|
|
@@ -1459,7 +1682,7 @@ Deploys Bicep infrastructure: Static Web Apps, Functions, Cosmos DB, Storage, Ma
|
|
|
1459
1682
|
|
|
1460
1683
|
- **Frontend**: Next.js (App Router), React, TypeScript, Tailwind CSS
|
|
1461
1684
|
- **BFF**: Next.js API Routes (proxy only)
|
|
1462
|
-
- **Backend**: Azure Functions (
|
|
1685
|
+
- **Backend**: Azure Functions (${backendLanguageLabel})
|
|
1463
1686
|
- **Database**: Azure Cosmos DB (NoSQL)
|
|
1464
1687
|
- **Schema**: Zod (shared across all layers via workspace package)
|
|
1465
1688
|
- **Infrastructure**: Bicep (IaC)
|
|
@@ -1479,7 +1702,8 @@ This file is for Claude Code. Read AGENTS.md in the project root for the full ar
|
|
|
1479
1702
|
- **Architecture**: Next.js (frontend) → BFF (API routes, proxy only) → Azure Functions (backend) → Cosmos DB
|
|
1480
1703
|
- **Schema**: Zod schemas in \`shared/models/\` are the single source of truth. Never define types separately.
|
|
1481
1704
|
- **BFF rule**: \`app/api/\` routes must ONLY proxy to Azure Functions via \`callFunction()\`. No business logic.
|
|
1482
|
-
- **Backend
|
|
1705
|
+
- **Backend language**: ${backendLanguageLabel}
|
|
1706
|
+
- **Backend rule**: Regenerate backend contracts with \`swallowkit scaffold\` after schema changes and keep \`functions/generated/\` in sync.
|
|
1483
1707
|
|
|
1484
1708
|
## SwallowKit CLI Commands
|
|
1485
1709
|
|
|
@@ -1516,13 +1740,13 @@ Frontend (Next.js App Router) → BFF (Next.js API Routes) → Backend (Azure Fu
|
|
|
1516
1740
|
|
|
1517
1741
|
1. **BFF is proxy only** — \`app/api/\` routes call Azure Functions via \`callFunction()\`. No business logic, no direct DB access.
|
|
1518
1742
|
2. **Zod = single source of truth** — Models live in \`shared/models/\`. Types are derived with \`z.infer<>\`. Never define types separately.
|
|
1519
|
-
3. **Backend owns data** — All CRUD
|
|
1743
|
+
3. **Backend owns data** — All CRUD and business logic stay in \`functions/\`, and generated contract assets under \`functions/generated/\` must stay aligned with \`shared/models/\`.
|
|
1520
1744
|
4. **Use the CLI** — Run \`npx swallowkit create-model <name>\` then \`npx swallowkit scaffold shared/models/<name>.ts\` to add models. Do not create boilerplate manually.
|
|
1521
1745
|
|
|
1522
1746
|
## Naming
|
|
1523
1747
|
|
|
1524
1748
|
- Schema/type: PascalCase, same name for both (\`export const Todo = z.object({...}); export type Todo = z.infer<typeof Todo>;\`)
|
|
1525
|
-
- Files: kebab-case (\`shared/models/todo.ts\`, \`functions
|
|
1749
|
+
- Files: kebab-case (\`shared/models/todo.ts\`, backend handlers under \`functions/\`)
|
|
1526
1750
|
- Cosmos DB containers: PascalCase + 's' (\`Todos\`), partition key always \`/id\`
|
|
1527
1751
|
|
|
1528
1752
|
## Managed Fields
|
|
@@ -1598,13 +1822,13 @@ applyTo: "functions/**"
|
|
|
1598
1822
|
|
|
1599
1823
|
# Azure Functions — Backend Rules
|
|
1600
1824
|
|
|
1601
|
-
Files in \`functions
|
|
1825
|
+
Files in \`functions/\` contain all business logic and data access for this application.
|
|
1602
1826
|
|
|
1603
1827
|
## Rules
|
|
1604
1828
|
|
|
1605
|
-
-
|
|
1606
|
-
-
|
|
1607
|
-
-
|
|
1829
|
+
- Keep backend contracts aligned with \`shared/models/\` by rerunning \`swallowkit scaffold\` after schema changes.
|
|
1830
|
+
- For TypeScript backends, use Cosmos DB **input/output bindings** (\`extraInputs\`/\`extraOutputs\`) for reads and writes.
|
|
1831
|
+
- For C#/Python backends, consume the generated OpenAPI-derived assets in \`functions/generated/\`.
|
|
1608
1832
|
- Auto-generate \`id\` (UUID), \`createdAt\`, and \`updatedAt\` on the backend. Never trust client-sent values.
|
|
1609
1833
|
- Container names are PascalCase + 's' (e.g., \`Todos\`). Partition key is always \`/id\`.
|
|
1610
1834
|
|
|
@@ -1640,12 +1864,13 @@ app.http('{model}-get-all', {
|
|
|
1640
1864
|
console.log(' - GitHub Copilot (edit) → .github/instructions/*.instructions.md');
|
|
1641
1865
|
console.log('');
|
|
1642
1866
|
}
|
|
1643
|
-
async function createInfrastructure(projectDir, projectName, azureConfig) {
|
|
1867
|
+
async function createInfrastructure(projectDir, projectName, azureConfig, backendLanguage) {
|
|
1644
1868
|
console.log('📦 Creating infrastructure files (Bicep)...\n');
|
|
1645
1869
|
const infraDir = path.join(projectDir, 'infra');
|
|
1646
1870
|
const modulesDir = path.join(infraDir, 'modules');
|
|
1647
1871
|
fs.mkdirSync(modulesDir, { recursive: true });
|
|
1648
1872
|
const enableVNet = azureConfig.vnetOption !== 'none';
|
|
1873
|
+
const functionsRuntime = getFunctionsRuntimeConfig(backendLanguage);
|
|
1649
1874
|
// main.bicep
|
|
1650
1875
|
const mainBicep = `targetScope = 'resourceGroup'
|
|
1651
1876
|
|
|
@@ -1756,16 +1981,18 @@ module cosmosPrivateEndpoint 'modules/private-endpoint-cosmos.bicep' = if (enabl
|
|
|
1756
1981
|
// Azure Functions (Flex Consumption) - Deploy AFTER Cosmos DB
|
|
1757
1982
|
module functionsFlex 'modules/functions-flex.bicep' = {
|
|
1758
1983
|
name: 'functionsApp'
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1984
|
+
params: {
|
|
1985
|
+
name: 'func-\${projectName}'
|
|
1986
|
+
location: location
|
|
1987
|
+
storageAccountName: 'stg\${uniqueString(resourceGroup().id, projectName)}'
|
|
1988
|
+
appInsightsConnectionString: appInsightsFunctions.outputs.connectionString
|
|
1989
|
+
swaDefaultHostname: staticWebApp.outputs.defaultHostname
|
|
1990
|
+
cosmosDbEndpoint: cosmosDbMode == 'freetier' ? cosmosDbFreeTier.outputs.endpoint : cosmosDbServerless.outputs.endpoint
|
|
1991
|
+
cosmosDbDatabaseName: cosmosDbMode == 'freetier' ? cosmosDbFreeTier.outputs.databaseName : cosmosDbServerless.outputs.databaseName
|
|
1992
|
+
functionsRuntimeName: '${functionsRuntime.name}'
|
|
1993
|
+
functionsRuntimeVersion: '${functionsRuntime.version}'
|
|
1994
|
+
enableVNet: enableVNet
|
|
1995
|
+
vnetSubnetId: enableVNet ? vnet.outputs.functionsSubnetId : ''
|
|
1769
1996
|
}
|
|
1770
1997
|
dependsOn: [
|
|
1771
1998
|
cosmosDbFreeTier
|
|
@@ -1996,6 +2223,12 @@ param enableVNet bool = false
|
|
|
1996
2223
|
@description('VNet subnet ID for Functions (required if enableVNet is true)')
|
|
1997
2224
|
param vnetSubnetId string = ''
|
|
1998
2225
|
|
|
2226
|
+
@description('Functions runtime name')
|
|
2227
|
+
param functionsRuntimeName string
|
|
2228
|
+
|
|
2229
|
+
@description('Functions runtime version')
|
|
2230
|
+
param functionsRuntimeVersion string
|
|
2231
|
+
|
|
1999
2232
|
// Storage Account for Functions
|
|
2000
2233
|
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
|
|
2001
2234
|
name: storageAccountName
|
|
@@ -2066,8 +2299,8 @@ resource functionApp 'Microsoft.Web/sites@2023-12-01' = {
|
|
|
2066
2299
|
instanceMemoryMB: 2048
|
|
2067
2300
|
}
|
|
2068
2301
|
runtime: {
|
|
2069
|
-
name:
|
|
2070
|
-
version:
|
|
2302
|
+
name: functionsRuntimeName
|
|
2303
|
+
version: functionsRuntimeVersion
|
|
2071
2304
|
}
|
|
2072
2305
|
}
|
|
2073
2306
|
siteConfig: {
|
|
@@ -2409,68 +2642,77 @@ output privateDnsZoneId string = privateDnsZone.id
|
|
|
2409
2642
|
}
|
|
2410
2643
|
console.log('✅ Infrastructure files created\n');
|
|
2411
2644
|
}
|
|
2412
|
-
|
|
2413
|
-
console.log('📦 Creating GitHub Actions workflows...\n');
|
|
2645
|
+
function getGitHubFunctionsWorkflow(pm, backendLanguage) {
|
|
2414
2646
|
const pmCmd = (0, package_manager_1.getCommands)(pm);
|
|
2415
2647
|
const pnpmSetupStep = (0, package_manager_1.getCiSetupStep)(pm);
|
|
2416
|
-
const
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2648
|
+
const commonSetup = ` - uses: actions/checkout@v4
|
|
2649
|
+
|
|
2650
|
+
- name: Setup Node.js
|
|
2651
|
+
uses: actions/setup-node@v4
|
|
2652
|
+
with:
|
|
2653
|
+
node-version: '22'
|
|
2654
|
+
${pnpmSetupStep ? `\n${pnpmSetupStep}\n` : ''}
|
|
2655
|
+
- name: Install dependencies
|
|
2656
|
+
run: |
|
|
2657
|
+
${pmCmd.ci}
|
|
2658
|
+
|
|
2659
|
+
- name: Build shared package
|
|
2660
|
+
run: |
|
|
2661
|
+
${pmCmd.runFilter('shared')} build
|
|
2662
|
+
`;
|
|
2663
|
+
if (backendLanguage === 'typescript') {
|
|
2664
|
+
return `name: Deploy Azure Functions
|
|
2420
2665
|
|
|
2421
2666
|
on:
|
|
2422
2667
|
push:
|
|
2423
2668
|
branches:
|
|
2424
2669
|
- main
|
|
2425
2670
|
paths:
|
|
2426
|
-
- '
|
|
2427
|
-
- 'components/**'
|
|
2428
|
-
- 'lib/**'
|
|
2671
|
+
- 'functions/**'
|
|
2429
2672
|
- 'shared/**'
|
|
2430
|
-
- 'public/**'
|
|
2431
|
-
- 'package.json'
|
|
2432
|
-
- 'next.config.js'
|
|
2433
|
-
- 'next.config.ts'
|
|
2434
|
-
workflow_dispatch:
|
|
2435
2673
|
pull_request:
|
|
2436
2674
|
branches:
|
|
2437
2675
|
- main
|
|
2438
2676
|
paths:
|
|
2439
|
-
- '
|
|
2440
|
-
- 'components/**'
|
|
2441
|
-
- 'lib/**'
|
|
2677
|
+
- 'functions/**'
|
|
2442
2678
|
- 'shared/**'
|
|
2443
|
-
|
|
2444
|
-
- 'package.json'
|
|
2445
|
-
- 'next.config.js'
|
|
2446
|
-
- 'next.config.ts'
|
|
2679
|
+
workflow_dispatch:
|
|
2447
2680
|
|
|
2448
2681
|
jobs:
|
|
2449
2682
|
build-and-deploy:
|
|
2450
2683
|
runs-on: ubuntu-latest
|
|
2451
|
-
name: Build and Deploy
|
|
2684
|
+
name: Build and Deploy Functions
|
|
2452
2685
|
|
|
2453
2686
|
steps:
|
|
2454
|
-
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2687
|
+
${commonSetup} - name: Build Functions
|
|
2688
|
+
run: |
|
|
2689
|
+
${pmCmd.runFilter('functions')} build
|
|
2457
2690
|
|
|
2458
|
-
- name:
|
|
2691
|
+
- name: Prepare functions for deployment
|
|
2692
|
+
run: |
|
|
2693
|
+
SHARED_PKG_NAME=$(node -p "require('./shared/package.json').name")
|
|
2694
|
+
mkdir -p /tmp/fn-deps
|
|
2695
|
+
node -e "const p=JSON.parse(require('fs').readFileSync('./functions/package.json','utf8'));Object.keys(p.dependencies).filter(k=>k.endsWith('/shared')).forEach(k=>delete p.dependencies[k]);require('fs').writeFileSync('/tmp/fn-deps/package.json',JSON.stringify(p,null,2));"
|
|
2696
|
+
cd /tmp/fn-deps && ${pmCmd.installProd} && cd -
|
|
2697
|
+
rm -rf ./functions/node_modules
|
|
2698
|
+
mv /tmp/fn-deps/node_modules ./functions/node_modules
|
|
2699
|
+
SHARED_DEST="./functions/node_modules/$SHARED_PKG_NAME"
|
|
2700
|
+
mkdir -p "$SHARED_DEST"
|
|
2701
|
+
cp -r ./shared/dist "$SHARED_DEST/dist"
|
|
2702
|
+
cp ./shared/package.json "$SHARED_DEST/package.json"
|
|
2703
|
+
|
|
2704
|
+
- name: Deploy to Azure Functions
|
|
2459
2705
|
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main'
|
|
2460
|
-
uses: Azure/
|
|
2706
|
+
uses: Azure/functions-action@v1
|
|
2461
2707
|
with:
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
api_location: ''
|
|
2467
|
-
output_location: ''
|
|
2468
|
-
env:
|
|
2469
|
-
NEXT_TURBOPACK_EXPERIMENTAL_USE_SYSTEM_TLS_CERTS: '1'
|
|
2708
|
+
app-name: \${{ secrets.AZURE_FUNCTIONAPP_NAME }}
|
|
2709
|
+
package: './functions'
|
|
2710
|
+
publish-profile: \${{ secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE }}
|
|
2711
|
+
sku: flexconsumption
|
|
2470
2712
|
`;
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2713
|
+
}
|
|
2714
|
+
if (backendLanguage === 'csharp') {
|
|
2715
|
+
return `name: Deploy Azure Functions
|
|
2474
2716
|
|
|
2475
2717
|
on:
|
|
2476
2718
|
push:
|
|
@@ -2493,119 +2735,86 @@ jobs:
|
|
|
2493
2735
|
name: Build and Deploy Functions
|
|
2494
2736
|
|
|
2495
2737
|
steps:
|
|
2496
|
-
-
|
|
2497
|
-
|
|
2498
|
-
- name: Setup Node.js
|
|
2499
|
-
uses: actions/setup-node@v4
|
|
2738
|
+
${commonSetup} - name: Setup .NET
|
|
2739
|
+
uses: actions/setup-dotnet@v4
|
|
2500
2740
|
with:
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
- name:
|
|
2504
|
-
run: |
|
|
2505
|
-
${pmCmd.ci}
|
|
2506
|
-
|
|
2507
|
-
- name: Build shared package
|
|
2508
|
-
run: |
|
|
2509
|
-
${pmCmd.runFilter('shared')} build
|
|
2510
|
-
|
|
2511
|
-
- name: Build Functions
|
|
2512
|
-
run: |
|
|
2513
|
-
${pmCmd.runFilter('functions')} build
|
|
2514
|
-
|
|
2515
|
-
- name: Prepare functions for deployment
|
|
2741
|
+
dotnet-version: '8.0.x'
|
|
2742
|
+
|
|
2743
|
+
- name: Publish Functions
|
|
2516
2744
|
run: |
|
|
2517
|
-
|
|
2518
|
-
mkdir -p /tmp/fn-deps
|
|
2519
|
-
node -e "const p=JSON.parse(require('fs').readFileSync('./functions/package.json','utf8'));Object.keys(p.dependencies).filter(k=>k.endsWith('/shared')).forEach(k=>delete p.dependencies[k]);require('fs').writeFileSync('/tmp/fn-deps/package.json',JSON.stringify(p,null,2));"
|
|
2520
|
-
cd /tmp/fn-deps && ${pmCmd.installProd} && cd -
|
|
2521
|
-
rm -rf ./functions/node_modules
|
|
2522
|
-
mv /tmp/fn-deps/node_modules ./functions/node_modules
|
|
2523
|
-
SHARED_DEST="./functions/node_modules/$SHARED_PKG_NAME"
|
|
2524
|
-
mkdir -p "$SHARED_DEST"
|
|
2525
|
-
cp -r ./shared/dist "$SHARED_DEST/dist"
|
|
2526
|
-
cp ./shared/package.json "$SHARED_DEST/package.json"
|
|
2745
|
+
dotnet publish ./functions -c Release -o ./functions/publish
|
|
2527
2746
|
|
|
2528
2747
|
- name: Deploy to Azure Functions
|
|
2529
2748
|
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main'
|
|
2530
2749
|
uses: Azure/functions-action@v1
|
|
2531
2750
|
with:
|
|
2532
2751
|
app-name: \${{ secrets.AZURE_FUNCTIONAPP_NAME }}
|
|
2533
|
-
package: './functions'
|
|
2752
|
+
package: './functions/publish'
|
|
2534
2753
|
publish-profile: \${{ secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE }}
|
|
2535
2754
|
sku: flexconsumption
|
|
2536
2755
|
`;
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
}
|
|
2540
|
-
async function createAzurePipelines(projectDir, pm) {
|
|
2541
|
-
console.log('📦 Creating Azure Pipelines...\n');
|
|
2542
|
-
const pmCmd = (0, package_manager_1.getCommands)(pm);
|
|
2543
|
-
const azPipelinesSetup = (0, package_manager_1.getAzurePipelinesSetup)(pm);
|
|
2544
|
-
const pipelinesDir = path.join(projectDir, 'pipelines');
|
|
2545
|
-
fs.mkdirSync(pipelinesDir, { recursive: true });
|
|
2546
|
-
// swa.yml
|
|
2547
|
-
const swaPipeline = `trigger:
|
|
2548
|
-
branches:
|
|
2549
|
-
include:
|
|
2550
|
-
- main
|
|
2551
|
-
paths:
|
|
2552
|
-
include:
|
|
2553
|
-
- app/**
|
|
2554
|
-
- components/**
|
|
2555
|
-
- lib/**
|
|
2556
|
-
- shared/**
|
|
2557
|
-
- public/**
|
|
2558
|
-
- package.json
|
|
2559
|
-
- next.config.js
|
|
2756
|
+
}
|
|
2757
|
+
return `name: Deploy Azure Functions
|
|
2560
2758
|
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2759
|
+
on:
|
|
2760
|
+
push:
|
|
2761
|
+
branches:
|
|
2564
2762
|
- main
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
-
|
|
2571
|
-
|
|
2572
|
-
-
|
|
2573
|
-
-
|
|
2574
|
-
|
|
2575
|
-
pool:
|
|
2576
|
-
vmImage: 'ubuntu-latest'
|
|
2763
|
+
paths:
|
|
2764
|
+
- 'functions/**'
|
|
2765
|
+
- 'shared/**'
|
|
2766
|
+
pull_request:
|
|
2767
|
+
branches:
|
|
2768
|
+
- main
|
|
2769
|
+
paths:
|
|
2770
|
+
- 'functions/**'
|
|
2771
|
+
- 'shared/**'
|
|
2772
|
+
workflow_dispatch:
|
|
2577
2773
|
|
|
2578
|
-
|
|
2579
|
-
-
|
|
2774
|
+
jobs:
|
|
2775
|
+
build-and-deploy:
|
|
2776
|
+
runs-on: ubuntu-latest
|
|
2777
|
+
name: Build and Deploy Functions
|
|
2778
|
+
|
|
2779
|
+
steps:
|
|
2780
|
+
${commonSetup} - name: Setup Python
|
|
2781
|
+
uses: actions/setup-python@v5
|
|
2782
|
+
with:
|
|
2783
|
+
python-version: '3.11'
|
|
2580
2784
|
|
|
2581
|
-
|
|
2582
|
-
|
|
2785
|
+
- name: Install Functions dependencies
|
|
2786
|
+
run: |
|
|
2787
|
+
python -m pip install --upgrade pip
|
|
2788
|
+
python -m pip install -r ./functions/requirements.txt --target "./functions/.python_packages/lib/site-packages"
|
|
2789
|
+
|
|
2790
|
+
- name: Deploy to Azure Functions
|
|
2791
|
+
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main'
|
|
2792
|
+
uses: Azure/functions-action@v1
|
|
2793
|
+
with:
|
|
2794
|
+
app-name: \${{ secrets.AZURE_FUNCTIONAPP_NAME }}
|
|
2795
|
+
package: './functions'
|
|
2796
|
+
publish-profile: \${{ secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE }}
|
|
2797
|
+
sku: flexconsumption
|
|
2798
|
+
`;
|
|
2799
|
+
}
|
|
2800
|
+
function getAzureFunctionsPipeline(pm, backendLanguage) {
|
|
2801
|
+
const pmCmd = (0, package_manager_1.getCommands)(pm);
|
|
2802
|
+
const azPipelinesSetup = (0, package_manager_1.getAzurePipelinesSetup)(pm);
|
|
2803
|
+
const commonSetup = ` - task: NodeTool@0
|
|
2583
2804
|
inputs:
|
|
2584
2805
|
versionSpec: '22.x'
|
|
2585
2806
|
displayName: 'Install Node.js'
|
|
2586
2807
|
${azPipelinesSetup ? `\n${azPipelinesSetup}\n` : ''}
|
|
2587
2808
|
- script: |
|
|
2588
2809
|
${pmCmd.ci}
|
|
2589
|
-
displayName: 'Install dependencies'
|
|
2810
|
+
displayName: 'Install workspace dependencies'
|
|
2590
2811
|
|
|
2591
2812
|
- script: |
|
|
2592
|
-
${pmCmd.
|
|
2593
|
-
|
|
2594
|
-
NODE_ENV: production
|
|
2595
|
-
displayName: 'Build Next.js app'
|
|
2596
|
-
|
|
2597
|
-
- task: AzureStaticWebApp@0
|
|
2598
|
-
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
|
|
2599
|
-
inputs:
|
|
2600
|
-
app_location: '.'
|
|
2601
|
-
output_location: '.next/standalone'
|
|
2602
|
-
skip_app_build: true
|
|
2603
|
-
azure_static_web_apps_api_token: $(AZURE_STATIC_WEB_APPS_API_TOKEN)
|
|
2604
|
-
displayName: 'Deploy to Azure Static Web Apps'
|
|
2813
|
+
${pmCmd.runFilter('shared')} build
|
|
2814
|
+
displayName: 'Build shared package'
|
|
2605
2815
|
`;
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
const functionsPipeline = `trigger:
|
|
2816
|
+
if (backendLanguage === 'typescript') {
|
|
2817
|
+
return `trigger:
|
|
2609
2818
|
branches:
|
|
2610
2819
|
include:
|
|
2611
2820
|
- main
|
|
@@ -2630,20 +2839,7 @@ variables:
|
|
|
2630
2839
|
- group: azure-deployment
|
|
2631
2840
|
|
|
2632
2841
|
steps:
|
|
2633
|
-
-
|
|
2634
|
-
inputs:
|
|
2635
|
-
versionSpec: '22.x'
|
|
2636
|
-
displayName: 'Install Node.js'
|
|
2637
|
-
${azPipelinesSetup ? `\n${azPipelinesSetup}\n` : ''}
|
|
2638
|
-
- script: |
|
|
2639
|
-
${pmCmd.ci}
|
|
2640
|
-
displayName: 'Install workspace dependencies'
|
|
2641
|
-
|
|
2642
|
-
- script: |
|
|
2643
|
-
${pmCmd.runFilter('shared')} build
|
|
2644
|
-
displayName: 'Build shared package'
|
|
2645
|
-
|
|
2646
|
-
- script: |
|
|
2842
|
+
${commonSetup} - script: |
|
|
2647
2843
|
${pmCmd.runFilter('functions')} build
|
|
2648
2844
|
displayName: 'Build Functions'
|
|
2649
2845
|
|
|
@@ -2685,6 +2881,248 @@ ${azPipelinesSetup ? `\n${azPipelinesSetup}\n` : ''}
|
|
|
2685
2881
|
package: '$(Build.ArtifactStagingDirectory)/functions.zip'
|
|
2686
2882
|
displayName: 'Deploy to Azure Functions'
|
|
2687
2883
|
`;
|
|
2884
|
+
}
|
|
2885
|
+
if (backendLanguage === 'csharp') {
|
|
2886
|
+
return `trigger:
|
|
2887
|
+
branches:
|
|
2888
|
+
include:
|
|
2889
|
+
- main
|
|
2890
|
+
paths:
|
|
2891
|
+
include:
|
|
2892
|
+
- functions/**
|
|
2893
|
+
- shared/**
|
|
2894
|
+
|
|
2895
|
+
pr:
|
|
2896
|
+
branches:
|
|
2897
|
+
include:
|
|
2898
|
+
- main
|
|
2899
|
+
paths:
|
|
2900
|
+
include:
|
|
2901
|
+
- functions/**
|
|
2902
|
+
- shared/**
|
|
2903
|
+
|
|
2904
|
+
pool:
|
|
2905
|
+
vmImage: 'ubuntu-latest'
|
|
2906
|
+
|
|
2907
|
+
variables:
|
|
2908
|
+
- group: azure-deployment
|
|
2909
|
+
|
|
2910
|
+
steps:
|
|
2911
|
+
${commonSetup} - task: UseDotNet@2
|
|
2912
|
+
inputs:
|
|
2913
|
+
version: '8.0.x'
|
|
2914
|
+
displayName: 'Install .NET SDK'
|
|
2915
|
+
|
|
2916
|
+
- script: |
|
|
2917
|
+
dotnet publish ./functions -c Release -o ./functions/publish
|
|
2918
|
+
displayName: 'Publish Functions'
|
|
2919
|
+
|
|
2920
|
+
- task: ArchiveFiles@2
|
|
2921
|
+
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
|
|
2922
|
+
inputs:
|
|
2923
|
+
rootFolderOrFile: '$(System.DefaultWorkingDirectory)/functions/publish'
|
|
2924
|
+
includeRootFolder: false
|
|
2925
|
+
archiveType: 'zip'
|
|
2926
|
+
archiveFile: '$(Build.ArtifactStagingDirectory)/functions.zip'
|
|
2927
|
+
displayName: 'Archive Functions'
|
|
2928
|
+
|
|
2929
|
+
- task: AzureFunctionApp@2
|
|
2930
|
+
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
|
|
2931
|
+
inputs:
|
|
2932
|
+
azureSubscription: '$(AZURE_SUBSCRIPTION)'
|
|
2933
|
+
appType: 'functionAppLinux'
|
|
2934
|
+
appName: '$(AZURE_FUNCTIONAPP_NAME)'
|
|
2935
|
+
package: '$(Build.ArtifactStagingDirectory)/functions.zip'
|
|
2936
|
+
displayName: 'Deploy to Azure Functions'
|
|
2937
|
+
`;
|
|
2938
|
+
}
|
|
2939
|
+
return `trigger:
|
|
2940
|
+
branches:
|
|
2941
|
+
include:
|
|
2942
|
+
- main
|
|
2943
|
+
paths:
|
|
2944
|
+
include:
|
|
2945
|
+
- functions/**
|
|
2946
|
+
- shared/**
|
|
2947
|
+
|
|
2948
|
+
pr:
|
|
2949
|
+
branches:
|
|
2950
|
+
include:
|
|
2951
|
+
- main
|
|
2952
|
+
paths:
|
|
2953
|
+
include:
|
|
2954
|
+
- functions/**
|
|
2955
|
+
- shared/**
|
|
2956
|
+
|
|
2957
|
+
pool:
|
|
2958
|
+
vmImage: 'ubuntu-latest'
|
|
2959
|
+
|
|
2960
|
+
variables:
|
|
2961
|
+
- group: azure-deployment
|
|
2962
|
+
|
|
2963
|
+
steps:
|
|
2964
|
+
${commonSetup} - task: UsePythonVersion@0
|
|
2965
|
+
inputs:
|
|
2966
|
+
versionSpec: '3.11'
|
|
2967
|
+
displayName: 'Install Python'
|
|
2968
|
+
|
|
2969
|
+
- script: |
|
|
2970
|
+
python -m pip install --upgrade pip
|
|
2971
|
+
python -m pip install -r ./functions/requirements.txt --target "./functions/.python_packages/lib/site-packages"
|
|
2972
|
+
displayName: 'Install Functions dependencies'
|
|
2973
|
+
|
|
2974
|
+
- task: ArchiveFiles@2
|
|
2975
|
+
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
|
|
2976
|
+
inputs:
|
|
2977
|
+
rootFolderOrFile: '$(System.DefaultWorkingDirectory)/functions'
|
|
2978
|
+
includeRootFolder: false
|
|
2979
|
+
archiveType: 'zip'
|
|
2980
|
+
archiveFile: '$(Build.ArtifactStagingDirectory)/functions.zip'
|
|
2981
|
+
displayName: 'Archive Functions'
|
|
2982
|
+
|
|
2983
|
+
- task: AzureFunctionApp@2
|
|
2984
|
+
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
|
|
2985
|
+
inputs:
|
|
2986
|
+
azureSubscription: '$(AZURE_SUBSCRIPTION)'
|
|
2987
|
+
appType: 'functionAppLinux'
|
|
2988
|
+
appName: '$(AZURE_FUNCTIONAPP_NAME)'
|
|
2989
|
+
package: '$(Build.ArtifactStagingDirectory)/functions.zip'
|
|
2990
|
+
displayName: 'Deploy to Azure Functions'
|
|
2991
|
+
`;
|
|
2992
|
+
}
|
|
2993
|
+
async function createGitHubActionsWorkflows(projectDir, azureConfig, pm, backendLanguage) {
|
|
2994
|
+
console.log('📦 Creating GitHub Actions workflows...\n');
|
|
2995
|
+
const pmCmd = (0, package_manager_1.getCommands)(pm);
|
|
2996
|
+
const workflowsDir = path.join(projectDir, '.github', 'workflows');
|
|
2997
|
+
fs.mkdirSync(workflowsDir, { recursive: true });
|
|
2998
|
+
// deploy-swa.yml
|
|
2999
|
+
const swaWorkflow = `name: Deploy Static Web App
|
|
3000
|
+
|
|
3001
|
+
on:
|
|
3002
|
+
push:
|
|
3003
|
+
branches:
|
|
3004
|
+
- main
|
|
3005
|
+
paths:
|
|
3006
|
+
- 'app/**'
|
|
3007
|
+
- 'components/**'
|
|
3008
|
+
- 'lib/**'
|
|
3009
|
+
- 'shared/**'
|
|
3010
|
+
- 'public/**'
|
|
3011
|
+
- 'package.json'
|
|
3012
|
+
- 'next.config.js'
|
|
3013
|
+
- 'next.config.ts'
|
|
3014
|
+
workflow_dispatch:
|
|
3015
|
+
pull_request:
|
|
3016
|
+
branches:
|
|
3017
|
+
- main
|
|
3018
|
+
paths:
|
|
3019
|
+
- 'app/**'
|
|
3020
|
+
- 'components/**'
|
|
3021
|
+
- 'lib/**'
|
|
3022
|
+
- 'shared/**'
|
|
3023
|
+
- 'public/**'
|
|
3024
|
+
- 'package.json'
|
|
3025
|
+
- 'next.config.js'
|
|
3026
|
+
- 'next.config.ts'
|
|
3027
|
+
|
|
3028
|
+
jobs:
|
|
3029
|
+
build-and-deploy:
|
|
3030
|
+
runs-on: ubuntu-latest
|
|
3031
|
+
name: Build and Deploy Static Web App
|
|
3032
|
+
|
|
3033
|
+
steps:
|
|
3034
|
+
- uses: actions/checkout@v4
|
|
3035
|
+
with:
|
|
3036
|
+
submodules: true
|
|
3037
|
+
|
|
3038
|
+
- name: Deploy to Azure Static Web Apps
|
|
3039
|
+
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main'
|
|
3040
|
+
uses: Azure/static-web-apps-deploy@v1
|
|
3041
|
+
with:
|
|
3042
|
+
azure_static_web_apps_api_token: \${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
|
|
3043
|
+
repo_token: \${{ secrets.GITHUB_TOKEN }}
|
|
3044
|
+
action: 'upload'
|
|
3045
|
+
app_location: '/'
|
|
3046
|
+
api_location: ''
|
|
3047
|
+
output_location: ''
|
|
3048
|
+
env:
|
|
3049
|
+
NEXT_TURBOPACK_EXPERIMENTAL_USE_SYSTEM_TLS_CERTS: '1'
|
|
3050
|
+
`;
|
|
3051
|
+
fs.writeFileSync(path.join(workflowsDir, 'deploy-swa.yml'), swaWorkflow);
|
|
3052
|
+
// deploy-functions.yml
|
|
3053
|
+
const functionsWorkflow = getGitHubFunctionsWorkflow(pm, backendLanguage);
|
|
3054
|
+
fs.writeFileSync(path.join(workflowsDir, 'deploy-functions.yml'), functionsWorkflow);
|
|
3055
|
+
console.log('✅ GitHub Actions workflows created\n');
|
|
3056
|
+
}
|
|
3057
|
+
async function createAzurePipelines(projectDir, pm, backendLanguage) {
|
|
3058
|
+
console.log('📦 Creating Azure Pipelines...\n');
|
|
3059
|
+
const pmCmd = (0, package_manager_1.getCommands)(pm);
|
|
3060
|
+
const azPipelinesSetup = (0, package_manager_1.getAzurePipelinesSetup)(pm);
|
|
3061
|
+
const pipelinesDir = path.join(projectDir, 'pipelines');
|
|
3062
|
+
fs.mkdirSync(pipelinesDir, { recursive: true });
|
|
3063
|
+
// swa.yml
|
|
3064
|
+
const swaPipeline = `trigger:
|
|
3065
|
+
branches:
|
|
3066
|
+
include:
|
|
3067
|
+
- main
|
|
3068
|
+
paths:
|
|
3069
|
+
include:
|
|
3070
|
+
- app/**
|
|
3071
|
+
- components/**
|
|
3072
|
+
- lib/**
|
|
3073
|
+
- shared/**
|
|
3074
|
+
- public/**
|
|
3075
|
+
- package.json
|
|
3076
|
+
- next.config.js
|
|
3077
|
+
|
|
3078
|
+
pr:
|
|
3079
|
+
branches:
|
|
3080
|
+
include:
|
|
3081
|
+
- main
|
|
3082
|
+
paths:
|
|
3083
|
+
include:
|
|
3084
|
+
- app/**
|
|
3085
|
+
- components/**
|
|
3086
|
+
- lib/**
|
|
3087
|
+
- shared/**
|
|
3088
|
+
- public/**
|
|
3089
|
+
- package.json
|
|
3090
|
+
- next.config.js
|
|
3091
|
+
|
|
3092
|
+
pool:
|
|
3093
|
+
vmImage: 'ubuntu-latest'
|
|
3094
|
+
|
|
3095
|
+
variables:
|
|
3096
|
+
- group: azure-deployment
|
|
3097
|
+
|
|
3098
|
+
steps:
|
|
3099
|
+
- task: NodeTool@0
|
|
3100
|
+
inputs:
|
|
3101
|
+
versionSpec: '22.x'
|
|
3102
|
+
displayName: 'Install Node.js'
|
|
3103
|
+
${azPipelinesSetup ? `\n${azPipelinesSetup}\n` : ''}
|
|
3104
|
+
- script: |
|
|
3105
|
+
${pmCmd.ci}
|
|
3106
|
+
displayName: 'Install dependencies'
|
|
3107
|
+
|
|
3108
|
+
- script: |
|
|
3109
|
+
${pmCmd.run} build
|
|
3110
|
+
env:
|
|
3111
|
+
NODE_ENV: production
|
|
3112
|
+
displayName: 'Build Next.js app'
|
|
3113
|
+
|
|
3114
|
+
- task: AzureStaticWebApp@0
|
|
3115
|
+
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
|
|
3116
|
+
inputs:
|
|
3117
|
+
app_location: '.'
|
|
3118
|
+
output_location: '.next/standalone'
|
|
3119
|
+
skip_app_build: true
|
|
3120
|
+
azure_static_web_apps_api_token: $(AZURE_STATIC_WEB_APPS_API_TOKEN)
|
|
3121
|
+
displayName: 'Deploy to Azure Static Web Apps'
|
|
3122
|
+
`;
|
|
3123
|
+
fs.writeFileSync(path.join(pipelinesDir, 'swa.yml'), swaPipeline);
|
|
3124
|
+
// functions.yml
|
|
3125
|
+
const functionsPipeline = getAzureFunctionsPipeline(pm, backendLanguage);
|
|
2688
3126
|
fs.writeFileSync(path.join(pipelinesDir, 'functions.yml'), functionsPipeline);
|
|
2689
3127
|
console.log('✅ Azure Pipelines created\n');
|
|
2690
3128
|
}
|