swallowkit 1.0.0-beta.18 ā 1.0.0-beta.19
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/dist/cli/commands/add-auth.d.ts.map +1 -1
- package/dist/cli/commands/add-auth.js +1 -4
- package/dist/cli/commands/add-auth.js.map +1 -1
- package/dist/cli/commands/add-connector.d.ts +1 -1
- package/dist/cli/commands/add-connector.d.ts.map +1 -1
- package/dist/cli/commands/add-connector.js +7 -7
- package/dist/cli/commands/add-connector.js.map +1 -1
- package/dist/cli/commands/dev.d.ts +11 -0
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +32 -22
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/init.js +1 -2
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +3 -6
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/index.d.ts +4 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +153 -79
- package/dist/cli/index.js.map +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +11 -3
- package/dist/core/config.js.map +1 -1
- package/dist/core/mock/connector-mock-server.d.ts.map +1 -1
- package/dist/core/mock/connector-mock-server.js +20 -17
- package/dist/core/mock/connector-mock-server.js.map +1 -1
- package/dist/core/scaffold/auth-generator.d.ts +1 -1
- package/dist/core/scaffold/auth-generator.d.ts.map +1 -1
- package/dist/core/scaffold/auth-generator.js +2 -1
- package/dist/core/scaffold/auth-generator.js.map +1 -1
- package/dist/core/scaffold/connector-functions-generator.d.ts.map +1 -1
- package/dist/core/scaffold/connector-functions-generator.js +0 -1
- package/dist/core/scaffold/connector-functions-generator.js.map +1 -1
- package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
- package/dist/core/scaffold/functions-generator.js +0 -8
- package/dist/core/scaffold/functions-generator.js.map +1 -1
- package/dist/core/scaffold/model-parser.d.ts.map +1 -1
- package/dist/core/scaffold/model-parser.js +8 -10
- package/dist/core/scaffold/model-parser.js.map +1 -1
- package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
- package/dist/core/scaffold/nextjs-generator.js +0 -1
- package/dist/core/scaffold/nextjs-generator.js.map +1 -1
- package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
- package/dist/core/scaffold/ui-generator.js +0 -3
- package/dist/core/scaffold/ui-generator.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/dev.test.ts +106 -0
- package/src/cli/commands/add-auth.ts +1 -5
- package/src/cli/commands/add-connector.ts +10 -11
- package/src/cli/commands/dev.ts +44 -25
- package/src/cli/commands/init.ts +1 -3
- package/src/cli/commands/scaffold.ts +0 -4
- package/src/cli/index.ts +167 -83
- package/src/core/config.ts +15 -3
- package/src/core/mock/connector-mock-server.ts +12 -12
- package/src/core/scaffold/auth-generator.ts +3 -2
- package/src/core/scaffold/connector-functions-generator.ts +0 -2
- package/src/core/scaffold/functions-generator.ts +0 -9
- package/src/core/scaffold/model-parser.ts +5 -9
- package/src/core/scaffold/nextjs-generator.ts +1 -2
- package/src/core/scaffold/ui-generator.ts +1 -5
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import * as fs from "fs";
|
|
7
7
|
import * as path from "path";
|
|
8
8
|
import { ensureSwallowKitProject } from "../../core/config";
|
|
9
|
-
import { ConnectorDefinition } from "../../types";
|
|
9
|
+
import { ApiConnectorConfig, ConnectorDefinition } from "../../types";
|
|
10
10
|
|
|
11
11
|
interface AddConnectorOptions {
|
|
12
12
|
name: string;
|
|
@@ -32,7 +32,7 @@ export async function addConnectorCommand(options: AddConnectorOptions) {
|
|
|
32
32
|
const connectorDef = buildConnectorDefinition(options);
|
|
33
33
|
|
|
34
34
|
// Update config file
|
|
35
|
-
updateConfigWithConnector(configPath, options.name, connectorDef
|
|
35
|
+
updateConfigWithConnector(configPath, options.name, connectorDef);
|
|
36
36
|
|
|
37
37
|
console.log(`\nā
Connector '${options.name}' added successfully!`);
|
|
38
38
|
console.log("\nš Next steps:");
|
|
@@ -81,8 +81,7 @@ function buildConnectorDefinition(options: AddConnectorOptions): ConnectorDefini
|
|
|
81
81
|
export function updateConfigWithConnector(
|
|
82
82
|
configPath: string,
|
|
83
83
|
connectorName: string,
|
|
84
|
-
connectorDef: ConnectorDefinition
|
|
85
|
-
options: AddConnectorOptions
|
|
84
|
+
connectorDef: ConnectorDefinition
|
|
86
85
|
): void {
|
|
87
86
|
const content = fs.readFileSync(configPath, "utf-8");
|
|
88
87
|
|
|
@@ -101,7 +100,7 @@ export function updateConfigWithConnector(
|
|
|
101
100
|
// JS config ā append connectors section
|
|
102
101
|
if (content.includes("connectors:") || content.includes("connectors :")) {
|
|
103
102
|
console.log(`ā ļø 'connectors' section already exists in ${configPath}. Please add the connector manually:`);
|
|
104
|
-
console.log(formatConnectorSnippet(connectorName, connectorDef
|
|
103
|
+
console.log(formatConnectorSnippet(connectorName, connectorDef));
|
|
105
104
|
return;
|
|
106
105
|
}
|
|
107
106
|
|
|
@@ -109,7 +108,7 @@ export function updateConfigWithConnector(
|
|
|
109
108
|
const closingBraceIdx = content.lastIndexOf("}");
|
|
110
109
|
if (closingBraceIdx === -1) {
|
|
111
110
|
console.error("ā Could not parse config file structure. Please add the connector manually:");
|
|
112
|
-
console.log(formatConnectorSnippet(connectorName, connectorDef
|
|
111
|
+
console.log(formatConnectorSnippet(connectorName, connectorDef));
|
|
113
112
|
return;
|
|
114
113
|
}
|
|
115
114
|
|
|
@@ -117,7 +116,7 @@ export function updateConfigWithConnector(
|
|
|
117
116
|
const beforeClosing = content.substring(0, closingBraceIdx).trimEnd();
|
|
118
117
|
const needsComma = !beforeClosing.endsWith(",") && !beforeClosing.endsWith("{");
|
|
119
118
|
|
|
120
|
-
const connectorBlock = generateConnectorJSBlock(connectorName, connectorDef
|
|
119
|
+
const connectorBlock = generateConnectorJSBlock(connectorName, connectorDef);
|
|
121
120
|
const insertion = `${needsComma ? "," : ""}\n // ć³ććÆćæå®ē¾©\n connectors: {\n${connectorBlock}\n },\n`;
|
|
122
121
|
|
|
123
122
|
const newContent = content.substring(0, closingBraceIdx) + insertion + content.substring(closingBraceIdx);
|
|
@@ -125,16 +124,16 @@ export function updateConfigWithConnector(
|
|
|
125
124
|
console.log(`ā
Updated: ${configPath}`);
|
|
126
125
|
}
|
|
127
126
|
|
|
128
|
-
function generateConnectorJSBlock(name: string, def: ConnectorDefinition
|
|
127
|
+
function generateConnectorJSBlock(name: string, def: ConnectorDefinition): string {
|
|
129
128
|
if (def.type === "rdb") {
|
|
130
129
|
return ` ${name}: {
|
|
131
130
|
type: 'rdb',
|
|
132
|
-
provider: '${
|
|
131
|
+
provider: '${def.provider}',
|
|
133
132
|
connectionEnvVar: '${def.connectionEnvVar}',
|
|
134
133
|
},`;
|
|
135
134
|
}
|
|
136
135
|
|
|
137
|
-
const apiDef = def
|
|
136
|
+
const apiDef: ApiConnectorConfig = def;
|
|
138
137
|
let authBlock = "";
|
|
139
138
|
if (apiDef.auth) {
|
|
140
139
|
authBlock = `
|
|
@@ -152,6 +151,6 @@ function generateConnectorJSBlock(name: string, def: ConnectorDefinition, option
|
|
|
152
151
|
},`;
|
|
153
152
|
}
|
|
154
153
|
|
|
155
|
-
function formatConnectorSnippet(name: string, def: ConnectorDefinition
|
|
154
|
+
function formatConnectorSnippet(name: string, def: ConnectorDefinition): string {
|
|
156
155
|
return `\n ${name}: ${JSON.stringify(def, null, 4)}\n`;
|
|
157
156
|
}
|
package/src/cli/commands/dev.ts
CHANGED
|
@@ -3,15 +3,16 @@ import { spawn, ChildProcess } from 'child_process';
|
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import * as fs from 'fs';
|
|
5
5
|
import * as os from 'os';
|
|
6
|
+
import * as net from 'net';
|
|
6
7
|
import { CosmosClient, PartitionKeyKind } from '@azure/cosmos';
|
|
7
|
-
import { ensureSwallowKitProject, getBackendLanguage, getAuthConfig
|
|
8
|
+
import { ensureSwallowKitProject, getBackendLanguage, getAuthConfig } from '../../core/config';
|
|
8
9
|
import { ModelInfo } from '../../core/scaffold/model-parser';
|
|
9
10
|
import { applyDevSeedEnvironment, getContainerNameForModel, loadProjectModels } from './dev-seeds';
|
|
10
11
|
import { BackendLanguage } from '../../types';
|
|
11
12
|
import { detectFromProject, getCommands } from '../../utils/package-manager';
|
|
12
13
|
import { ConnectorMockServer } from '../../core/mock/connector-mock-server';
|
|
13
14
|
|
|
14
|
-
interface DevOptions {
|
|
15
|
+
export interface DevOptions {
|
|
15
16
|
port?: string;
|
|
16
17
|
functionsPort?: string;
|
|
17
18
|
host?: string;
|
|
@@ -22,6 +23,17 @@ interface DevOptions {
|
|
|
22
23
|
mockConnectors?: boolean;
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
type ParsedDevActionOptions = DevOptions & {
|
|
27
|
+
functions?: boolean;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function normalizeParsedDevOptions(options: ParsedDevActionOptions): DevOptions {
|
|
31
|
+
return {
|
|
32
|
+
...options,
|
|
33
|
+
noFunctions: options.noFunctions ?? options.functions === false,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
25
37
|
export function buildFunctionsStartArgs(functionsPort: string): string[] {
|
|
26
38
|
return ['start', '--port', functionsPort];
|
|
27
39
|
}
|
|
@@ -292,7 +304,6 @@ async function preparePythonFunctionsEnvironment(functionsDir: string): Promise<
|
|
|
292
304
|
*/
|
|
293
305
|
async function checkCosmosDBEmulator(): Promise<boolean> {
|
|
294
306
|
return new Promise((resolve) => {
|
|
295
|
-
const net = require('net');
|
|
296
307
|
const socket = new net.Socket();
|
|
297
308
|
|
|
298
309
|
const timeout = setTimeout(() => {
|
|
@@ -315,28 +326,37 @@ async function checkCosmosDBEmulator(): Promise<boolean> {
|
|
|
315
326
|
});
|
|
316
327
|
}
|
|
317
328
|
|
|
318
|
-
export
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
329
|
+
export function buildDevCommand(
|
|
330
|
+
runDevEnvironment: (options: DevOptions) => Promise<void> = startDevEnvironment,
|
|
331
|
+
verifyProject: (commandName: string, projectRoot?: string) => void = ensureSwallowKitProject
|
|
332
|
+
): Command {
|
|
333
|
+
return new Command()
|
|
334
|
+
.name('dev')
|
|
335
|
+
.description('Start SwallowKit development server (Cosmos DB + Next.js + Azure Functions)')
|
|
336
|
+
.option('-p, --port <port>', 'Next.js port', '3000')
|
|
337
|
+
.option('-f, --functions-port <port>', 'Azure Functions port', '7071')
|
|
338
|
+
.option('--host <host>', 'Host name', 'localhost')
|
|
339
|
+
.option('--open', 'Open browser automatically', false)
|
|
340
|
+
.option('--verbose', 'Show verbose logs', false)
|
|
341
|
+
.option('--no-functions', 'Skip Azure Functions startup', false)
|
|
342
|
+
.option('--seed-env <environment>', 'Replace Cosmos DB Emulator data from dev-seeds/<environment> before startup')
|
|
343
|
+
.option('--mock-connectors', 'Start mock server for connector models (serves Zod-generated data)', false)
|
|
344
|
+
.action(async (options: ParsedDevActionOptions) => {
|
|
345
|
+
const normalizedOptions = normalizeParsedDevOptions(options);
|
|
346
|
+
|
|
347
|
+
// SwallowKit ćććøć§ćÆććć£ć¬ćÆććŖćć©ćććę¤čؼ
|
|
348
|
+
verifyProject("dev");
|
|
349
|
+
|
|
350
|
+
console.log('š Starting SwallowKit development environment...');
|
|
351
|
+
if (normalizedOptions.verbose) {
|
|
352
|
+
console.log('āļø Options:', normalizedOptions);
|
|
353
|
+
}
|
|
337
354
|
|
|
338
|
-
|
|
339
|
-
|
|
355
|
+
await runDevEnvironment(normalizedOptions);
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export const devCommand = buildDevCommand();
|
|
340
360
|
|
|
341
361
|
interface CosmosInitializationResult {
|
|
342
362
|
endpoint: string;
|
|
@@ -749,7 +769,6 @@ async function startDevEnvironment(options: DevOptions) {
|
|
|
749
769
|
const authConfig = getAuthConfig();
|
|
750
770
|
let mockAuthConfig: { jwtSecret: string; tokenExpiry?: string; customJwt?: { userTable: string; loginIdColumn: string; passwordHashColumn: string; rolesColumn: string }; defaultPolicy?: "authenticated" | "anonymous" } | undefined;
|
|
751
771
|
if (authConfig?.provider === 'custom-jwt' && authConfig.customJwt) {
|
|
752
|
-
const fullConfig = getFullConfig();
|
|
753
772
|
// Read JWT_SECRET from functions/local.settings.json if available
|
|
754
773
|
let jwtSecret = 'dev-jwt-secret-change-in-production-min-32-chars!!';
|
|
755
774
|
try {
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -1806,7 +1806,7 @@ npx swallowkit scaffold shared/models/<name>.ts
|
|
|
1806
1806
|
\`\`\`
|
|
1807
1807
|
|
|
1808
1808
|
Generates:
|
|
1809
|
-
- Azure Functions handlers (${backendLanguage === 'typescript' ? '
|
|
1809
|
+
- Azure Functions handlers (${backendLanguage === 'typescript' ? '`functions/src/<name>.ts`' : '`functions/` language-specific CRUD files + `functions/generated/` schema assets'})
|
|
1810
1810
|
- BFF API routes (\`app/api/<name>/route.ts\`, \`app/api/<name>/[id]/route.ts\`)
|
|
1811
1811
|
- UI pages (\`app/<name>/page.tsx\`, detail, create, edit pages)
|
|
1812
1812
|
- Cosmos DB Bicep container config (\`infra/containers/<name>-container.bicep\`)
|
|
@@ -3223,8 +3223,6 @@ async function createGitHubActionsWorkflows(
|
|
|
3223
3223
|
backendLanguage: BackendLanguage
|
|
3224
3224
|
) {
|
|
3225
3225
|
console.log('š¦ Creating GitHub Actions workflows...\n');
|
|
3226
|
-
|
|
3227
|
-
const pmCmd = getCommands(pm);
|
|
3228
3226
|
const workflowsDir = path.join(projectDir, '.github', 'workflows');
|
|
3229
3227
|
fs.mkdirSync(workflowsDir, { recursive: true });
|
|
3230
3228
|
|
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
generateApiConnectorFunctionCSharp,
|
|
21
21
|
generateRdbConnectorFunctionPython,
|
|
22
22
|
generateApiConnectorFunctionPython,
|
|
23
|
-
isReadOnlyConnector,
|
|
24
23
|
} from "../../core/scaffold/connector-functions-generator";
|
|
25
24
|
import { generateCompactBFFRoutes, generateBFFCallFunction, generateConnectorBFFRoutes } from "../../core/scaffold/nextjs-generator";
|
|
26
25
|
import { generateOpenApiDocument } from "../../core/scaffold/openapi-generator";
|
|
@@ -508,7 +507,6 @@ async function installConnectorDriverDependencies(
|
|
|
508
507
|
const pm = detectFromProject(functionsPath);
|
|
509
508
|
const cmds = getCommands(pm);
|
|
510
509
|
|
|
511
|
-
const { spawnSync } = require("child_process");
|
|
512
510
|
if (entry.deps.length > 0) {
|
|
513
511
|
spawnSync(cmds.name, [pm === "pnpm" ? "add" : "install", ...entry.deps], {
|
|
514
512
|
cwd: functionsPath, stdio: "inherit", shell: true,
|
|
@@ -532,7 +530,6 @@ async function installConnectorDriverDependencies(
|
|
|
532
530
|
const pkg = nugetMap[rdbDef.provider];
|
|
533
531
|
if (!pkg) return;
|
|
534
532
|
console.log(`\nš¦ Installing ${rdbDef.provider} NuGet package...`);
|
|
535
|
-
const { spawnSync } = require("child_process");
|
|
536
533
|
spawnSync("dotnet", ["add", path.join(functionsPath, "functions.csproj"), "package", pkg], {
|
|
537
534
|
cwd: functionsPath, stdio: "inherit", shell: true,
|
|
538
535
|
});
|
|
@@ -867,7 +864,6 @@ async function generateBFFRoutes(
|
|
|
867
864
|
): Promise<void> {
|
|
868
865
|
console.log("\nšØ Generating Next.js BFF API routes...");
|
|
869
866
|
|
|
870
|
-
const modelKebab = toKebabCase(modelInfo.name);
|
|
871
867
|
const modelCamel = modelInfo.name.charAt(0).toLowerCase() + modelInfo.name.slice(1);
|
|
872
868
|
|
|
873
869
|
// List route: app/api/[model]/route.ts
|
package/src/cli/index.ts
CHANGED
|
@@ -1,101 +1,185 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
if (process.platform === 'win32') {
|
|
5
|
-
try {
|
|
6
|
-
require('child_process').execSync('chcp 65001', { stdio: 'ignore' });
|
|
7
|
-
} catch { /* ignore ā non-critical */ }
|
|
8
|
-
}
|
|
9
|
-
|
|
3
|
+
import { execSync } from "child_process";
|
|
10
4
|
import { Command } from "commander";
|
|
11
5
|
import { initCommand, devCommand, devSeedsCommand, scaffoldCommand, createModelCommand } from "./commands";
|
|
12
6
|
import { provisionCommand } from "./commands/provision";
|
|
13
7
|
import { addConnectorCommand } from "./commands/add-connector";
|
|
14
8
|
import { addAuthCommand } from "./commands/add-auth";
|
|
15
9
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
10
|
+
const DEV_OPTION_ARITY = new Map<string, 0 | 1>([
|
|
11
|
+
["-p", 1],
|
|
12
|
+
["--port", 1],
|
|
13
|
+
["-f", 1],
|
|
14
|
+
["--functions-port", 1],
|
|
15
|
+
["--host", 1],
|
|
16
|
+
["--seed-env", 1],
|
|
17
|
+
["-o", 0],
|
|
18
|
+
["--open", 0],
|
|
19
|
+
["-v", 0],
|
|
20
|
+
["--verbose", 0],
|
|
21
|
+
["--no-functions", 0],
|
|
22
|
+
["--mock-connectors", 0],
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
function ensureUtf8ConsoleOnWindows(): void {
|
|
26
|
+
if (process.platform === 'win32') {
|
|
27
|
+
try {
|
|
28
|
+
execSync('chcp 65001', { stdio: 'ignore' });
|
|
29
|
+
} catch {
|
|
30
|
+
// ignore ā non-critical
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isDevOptionSequence(tokens: string[]): boolean {
|
|
36
|
+
if (tokens.length === 0) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
41
|
+
const token = tokens[index];
|
|
42
|
+
const arity = DEV_OPTION_ARITY.get(token);
|
|
43
|
+
|
|
44
|
+
if (arity === undefined) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (arity === 1) {
|
|
49
|
+
const value = tokens[index + 1];
|
|
50
|
+
if (value === undefined || value === "dev") {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
index += 1;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function normalizeDevCommandArgv(argv: string[]): string[] {
|
|
61
|
+
if (argv.length <= 3) {
|
|
62
|
+
return argv;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const prefix = argv.slice(0, 2);
|
|
66
|
+
const args = argv.slice(2);
|
|
67
|
+
const devIndex = args.indexOf("dev");
|
|
68
|
+
|
|
69
|
+
if (devIndex <= 0) {
|
|
70
|
+
return argv;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const leadingTokens = args.slice(0, devIndex);
|
|
74
|
+
if (!isDevOptionSequence(leadingTokens)) {
|
|
75
|
+
return argv;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return [
|
|
79
|
+
...prefix,
|
|
80
|
+
"dev",
|
|
81
|
+
...leadingTokens,
|
|
82
|
+
...args.slice(devIndex + 1),
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function createProgram(devCommandOverride: Command = devCommand): Command {
|
|
87
|
+
const program = new Command();
|
|
88
|
+
|
|
89
|
+
program
|
|
90
|
+
.name("swallowkit")
|
|
91
|
+
.description("Next.js framework optimized for Azure deployment - Automatically splits SSR into individual Azure Functions")
|
|
92
|
+
.version("1.0.0-beta.9");
|
|
93
|
+
|
|
94
|
+
// Register commands
|
|
95
|
+
program
|
|
96
|
+
.command("init [project-name]")
|
|
97
|
+
.description("Initialize a new SwallowKit project")
|
|
98
|
+
.option("--template <template>", "Template to use", "default")
|
|
99
|
+
.option("--next-version <version>", "Next.js version to install (e.g., 16.0.7, latest)", "latest")
|
|
100
|
+
.option("--cicd <provider>", "CI/CD provider: github | azure | skip")
|
|
101
|
+
.option("--backend-language <language>", "Azure Functions backend language: typescript | csharp | python")
|
|
102
|
+
.option("--cosmos-db-mode <mode>", "Cosmos DB mode: freetier | serverless")
|
|
103
|
+
.option("--vnet <option>", "Network security: outbound | none")
|
|
104
|
+
.action((projectName, options) => {
|
|
105
|
+
initCommand({
|
|
106
|
+
name: projectName || "swallowkit-app",
|
|
107
|
+
template: options.template,
|
|
108
|
+
nextVersion: options.nextVersion,
|
|
109
|
+
cicd: options.cicd,
|
|
110
|
+
backendLanguage: options.backendLanguage,
|
|
111
|
+
cosmosDbMode: options.cosmosDbMode,
|
|
112
|
+
vnet: options.vnet,
|
|
113
|
+
});
|
|
42
114
|
});
|
|
43
|
-
});
|
|
44
115
|
|
|
45
|
-
program.addCommand(
|
|
46
|
-
program.addCommand(devSeedsCommand);
|
|
47
|
-
|
|
48
|
-
program.addCommand(provisionCommand);
|
|
49
|
-
|
|
50
|
-
program
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
116
|
+
program.addCommand(devCommandOverride);
|
|
117
|
+
program.addCommand(devSeedsCommand);
|
|
118
|
+
|
|
119
|
+
program.addCommand(provisionCommand);
|
|
120
|
+
|
|
121
|
+
program
|
|
122
|
+
.command("create-model <names...>")
|
|
123
|
+
.description("Create model template files with id, createdAt, and updatedAt fields")
|
|
124
|
+
.option("--models-dir <dir>", "Models directory", "shared/models")
|
|
125
|
+
.option("--connector <name>", "Associate model with a connector defined in swallowkit.config.js")
|
|
126
|
+
.action((names, options) => {
|
|
127
|
+
createModelCommand({
|
|
128
|
+
names,
|
|
129
|
+
modelsDir: options.modelsDir,
|
|
130
|
+
connector: options.connector,
|
|
131
|
+
});
|
|
60
132
|
});
|
|
61
|
-
});
|
|
62
133
|
|
|
63
|
-
program
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
134
|
+
program
|
|
135
|
+
.command("scaffold <model>")
|
|
136
|
+
.description("Generate CRUD code for Azure Functions and Next.js BFF from Zod models")
|
|
137
|
+
.option("--functions-dir <dir>", "Azure Functions directory", "functions")
|
|
138
|
+
.option("--api-dir <dir>", "Next.js API routes directory", "app/api")
|
|
139
|
+
.option("--api-only", "Generate API only, skip UI components", false)
|
|
140
|
+
.action((model, options) => {
|
|
141
|
+
scaffoldCommand({
|
|
142
|
+
model,
|
|
143
|
+
functionsDir: options.functionsDir,
|
|
144
|
+
apiDir: options.apiDir,
|
|
145
|
+
apiOnly: options.apiOnly,
|
|
146
|
+
});
|
|
75
147
|
});
|
|
76
|
-
});
|
|
77
148
|
|
|
78
|
-
program
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
149
|
+
program
|
|
150
|
+
.command("add-connector <name>")
|
|
151
|
+
.description("Add an external data source connector configuration")
|
|
152
|
+
.requiredOption("--type <type>", "Connector type: rdb | api")
|
|
153
|
+
.option("--provider <provider>", "RDB provider: mysql | postgres | sqlserver", "mysql")
|
|
154
|
+
.action((name, options) => {
|
|
155
|
+
addConnectorCommand({
|
|
156
|
+
name,
|
|
157
|
+
type: options.type,
|
|
158
|
+
provider: options.provider,
|
|
159
|
+
});
|
|
88
160
|
});
|
|
89
|
-
});
|
|
90
161
|
|
|
91
|
-
program
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
162
|
+
program
|
|
163
|
+
.command("add-auth")
|
|
164
|
+
.description("Add authentication and authorization to the project")
|
|
165
|
+
.option("--provider <provider>", "Auth provider: custom-jwt | swa | swa-custom | none", "custom-jwt")
|
|
166
|
+
.action((options) => {
|
|
167
|
+
addAuthCommand({
|
|
168
|
+
provider: options.provider,
|
|
169
|
+
});
|
|
98
170
|
});
|
|
99
|
-
});
|
|
100
171
|
|
|
101
|
-
program
|
|
172
|
+
return program;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function runCli(argv: string[] = process.argv): Promise<void> {
|
|
176
|
+
ensureUtf8ConsoleOnWindows();
|
|
177
|
+
await createProgram().parseAsync(normalizeDevCommandArgv(argv));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (require.main === module) {
|
|
181
|
+
void runCli().catch((error) => {
|
|
182
|
+
console.error(error);
|
|
183
|
+
process.exitCode = 1;
|
|
184
|
+
});
|
|
185
|
+
}
|
package/src/core/config.ts
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
|
+
import { createRequire } from "module";
|
|
2
3
|
import * as path from "path";
|
|
3
4
|
import { AuthConfig, BackendLanguage, ConnectorDefinition, SwallowKitConfig } from "../types";
|
|
4
5
|
import { detectFromProject, getCommands } from "../utils/package-manager";
|
|
5
6
|
|
|
7
|
+
const requireFromHere = createRequire(__filename);
|
|
8
|
+
|
|
9
|
+
function unwrapConfigModule(
|
|
10
|
+
loadedConfig: Partial<SwallowKitConfig> | { default?: Partial<SwallowKitConfig> }
|
|
11
|
+
): Partial<SwallowKitConfig> {
|
|
12
|
+
if (typeof loadedConfig === "object" && loadedConfig !== null && "default" in loadedConfig) {
|
|
13
|
+
return loadedConfig.default ?? {};
|
|
14
|
+
}
|
|
15
|
+
return loadedConfig as Partial<SwallowKitConfig>;
|
|
16
|
+
}
|
|
17
|
+
|
|
6
18
|
const VALID_BACKEND_LANGUAGES: BackendLanguage[] = ["typescript", "csharp", "python"];
|
|
7
19
|
|
|
8
20
|
/**
|
|
@@ -46,9 +58,9 @@ export function loadConfig(configPath?: string): SwallowKitConfig {
|
|
|
46
58
|
const userConfig = JSON.parse(configData);
|
|
47
59
|
return mergeConfig(DEFAULT_CONFIG, userConfig);
|
|
48
60
|
} else if (filePath.endsWith(".js")) {
|
|
49
|
-
delete
|
|
50
|
-
const
|
|
51
|
-
return mergeConfig(DEFAULT_CONFIG,
|
|
61
|
+
delete requireFromHere.cache[fullPath];
|
|
62
|
+
const loadedConfig = requireFromHere(fullPath) as Partial<SwallowKitConfig> | { default?: Partial<SwallowKitConfig> };
|
|
63
|
+
return mergeConfig(DEFAULT_CONFIG, unwrapConfigModule(loadedConfig));
|
|
52
64
|
}
|
|
53
65
|
} catch (error) {
|
|
54
66
|
console.warn(`ā ļø čØå®ćć”ć¤ć«ć®čŖćæč¾¼ćæć«å¤±ę: ${filePath}`, error);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import * as http from "http";
|
|
8
|
+
import jwt, { SignOptions } from "jsonwebtoken";
|
|
8
9
|
import { ModelInfo, toCamelCase } from "../scaffold/model-parser";
|
|
9
10
|
import { generateMockDocuments } from "./zod-mock-generator";
|
|
10
11
|
import { loadDevSeedFiles } from "../../cli/commands/dev-seeds";
|
|
@@ -240,15 +241,17 @@ export class ConnectorMockServer {
|
|
|
240
241
|
return;
|
|
241
242
|
|
|
242
243
|
case "DELETE":
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
244
|
+
{
|
|
245
|
+
req.resume();
|
|
246
|
+
if (!ops.includes("delete")) {
|
|
247
|
+
return this.sendJson(res, 405, { error: "delete not supported" });
|
|
248
|
+
}
|
|
249
|
+
if (!id) return this.sendJson(res, 400, { error: "id required" });
|
|
250
|
+
const deleteIdx = store.findIndex((doc) => doc.id === id);
|
|
251
|
+
if (deleteIdx === -1) return this.sendJson(res, 404, { error: "Item not found" });
|
|
252
|
+
store.splice(deleteIdx, 1);
|
|
253
|
+
return this.sendJson(res, 204, null);
|
|
246
254
|
}
|
|
247
|
-
if (!id) return this.sendJson(res, 400, { error: "id required" });
|
|
248
|
-
const deleteIdx = store.findIndex((doc) => doc.id === id);
|
|
249
|
-
if (deleteIdx === -1) return this.sendJson(res, 404, { error: "Item not found" });
|
|
250
|
-
store.splice(deleteIdx, 1);
|
|
251
|
-
return this.sendJson(res, 204, null);
|
|
252
255
|
|
|
253
256
|
default:
|
|
254
257
|
req.resume();
|
|
@@ -376,9 +379,8 @@ export class ConnectorMockServer {
|
|
|
376
379
|
};
|
|
377
380
|
|
|
378
381
|
// Generate JWT
|
|
379
|
-
const jwt = require("jsonwebtoken");
|
|
380
382
|
const secret = this.options.authConfig!.jwtSecret;
|
|
381
|
-
const expiry = this.options.authConfig!.tokenExpiry || "24h";
|
|
383
|
+
const expiry = (this.options.authConfig!.tokenExpiry || "24h") as SignOptions["expiresIn"];
|
|
382
384
|
|
|
383
385
|
const token = jwt.sign(
|
|
384
386
|
{
|
|
@@ -408,7 +410,6 @@ export class ConnectorMockServer {
|
|
|
408
410
|
|
|
409
411
|
const token = authHeader.slice(7);
|
|
410
412
|
try {
|
|
411
|
-
const jwt = require("jsonwebtoken");
|
|
412
413
|
const secret = this.options.authConfig!.jwtSecret;
|
|
413
414
|
const payload = jwt.verify(token, secret) as Record<string, unknown>;
|
|
414
415
|
this.sendJson(res, 200, {
|
|
@@ -470,7 +471,6 @@ export class ConnectorMockServer {
|
|
|
470
471
|
|
|
471
472
|
const token = authHeader.slice(7);
|
|
472
473
|
try {
|
|
473
|
-
const jwt = require("jsonwebtoken");
|
|
474
474
|
const payload = jwt.verify(token, authCfg.jwtSecret) as Record<string, unknown>;
|
|
475
475
|
|
|
476
476
|
// ćć¼ć«ćć§ććÆ
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* add-auth ć³ćć³ćććć³ scaffold ćć¼ć«ć¬ć¼ćęæå
„ć§ä½æēØćććć³ćć¬ć¼ć群
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { CustomJwtConfig, ModelAuthPolicy,
|
|
6
|
+
import { CustomJwtConfig, ModelAuthPolicy, RdbConnectorConfig } from "../../types";
|
|
7
7
|
|
|
8
8
|
type RdbProvider = RdbConnectorConfig["provider"]; // "mysql" | "postgres" | "sqlserver"
|
|
9
9
|
|
|
@@ -816,7 +816,8 @@ export async function POST() {
|
|
|
816
816
|
`;
|
|
817
817
|
}
|
|
818
818
|
|
|
819
|
-
export function generateBFFAuthMeRoute(
|
|
819
|
+
export function generateBFFAuthMeRoute(_sharedPackageName?: string): string {
|
|
820
|
+
void _sharedPackageName;
|
|
820
821
|
return `import { NextResponse } from 'next/server';
|
|
821
822
|
import { headers } from 'next/headers';
|
|
822
823
|
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
import { ModelInfo, toCamelCase, toKebabCase } from "./model-parser";
|
|
7
7
|
import {
|
|
8
|
-
ConnectorDefinition,
|
|
9
8
|
RdbConnectorConfig,
|
|
10
9
|
ApiConnectorConfig,
|
|
11
10
|
RdbModelConnectorConfig,
|
|
@@ -125,7 +124,6 @@ export function generateRdbConnectorFunctionTS(
|
|
|
125
124
|
const hasAuth = !!authPolicy;
|
|
126
125
|
const authImport = hasAuth ? `\n${generateAuthImportTS()}\n` : '';
|
|
127
126
|
const readGuard = hasAuth ? `\n${generateAuthGuardTS(authPolicy!, 'read')}\n` : '';
|
|
128
|
-
const writeGuard = hasAuth ? `\n${generateAuthGuardTS(authPolicy!, 'write')}\n` : '';
|
|
129
127
|
const authCatchBlock = hasAuth
|
|
130
128
|
? ` const authErr = handleAuthError(error);
|
|
131
129
|
if (authErr) return authErr;\n `
|