swallowkit 1.0.0-beta.3 ā 1.0.0-beta.31
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/LICENSE +21 -21
- package/README.ja.md +353 -215
- package/README.md +406 -216
- package/dist/__tests__/fixtures.d.ts +22 -0
- package/dist/__tests__/fixtures.d.ts.map +1 -0
- package/dist/__tests__/fixtures.js +146 -0
- package/dist/__tests__/fixtures.js.map +1 -0
- package/dist/cli/commands/add-auth.d.ts +10 -0
- package/dist/cli/commands/add-auth.d.ts.map +1 -0
- package/dist/cli/commands/add-auth.js +444 -0
- package/dist/cli/commands/add-auth.js.map +1 -0
- package/dist/cli/commands/add-connector.d.ts +20 -0
- package/dist/cli/commands/add-connector.d.ts.map +1 -0
- package/dist/cli/commands/add-connector.js +163 -0
- package/dist/cli/commands/add-connector.js.map +1 -0
- package/dist/cli/commands/create-model.d.ts +1 -4
- package/dist/cli/commands/create-model.d.ts.map +1 -1
- package/dist/cli/commands/create-model.js +21 -82
- package/dist/cli/commands/create-model.js.map +1 -1
- package/dist/cli/commands/dev-seeds.d.ts +57 -0
- package/dist/cli/commands/dev-seeds.d.ts.map +1 -0
- package/dist/cli/commands/dev-seeds.js +470 -0
- package/dist/cli/commands/dev-seeds.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +33 -0
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +628 -146
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +3 -1
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/init.d.ts +15 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +2696 -1706
- 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 +448 -129
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/index.d.ts +5 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +200 -42
- package/dist/cli/index.js.map +1 -1
- package/dist/core/config.d.ts +8 -2
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +94 -5
- package/dist/core/config.js.map +1 -1
- package/dist/core/mock/connector-mock-server.d.ts +101 -0
- package/dist/core/mock/connector-mock-server.d.ts.map +1 -0
- package/dist/core/mock/connector-mock-server.js +480 -0
- package/dist/core/mock/connector-mock-server.js.map +1 -0
- package/dist/core/mock/zod-mock-generator.d.ts +14 -0
- package/dist/core/mock/zod-mock-generator.d.ts.map +1 -0
- package/dist/core/mock/zod-mock-generator.js +163 -0
- package/dist/core/mock/zod-mock-generator.js.map +1 -0
- package/dist/core/operations/create-model.d.ts +15 -0
- package/dist/core/operations/create-model.d.ts.map +1 -0
- package/dist/core/operations/create-model.js +171 -0
- package/dist/core/operations/create-model.js.map +1 -0
- package/dist/core/operations/runtime.d.ts +32 -0
- package/dist/core/operations/runtime.d.ts.map +1 -0
- package/dist/core/operations/runtime.js +225 -0
- package/dist/core/operations/runtime.js.map +1 -0
- package/dist/core/operations/scaffold-machine.d.ts +16 -0
- package/dist/core/operations/scaffold-machine.d.ts.map +1 -0
- package/dist/core/operations/scaffold-machine.js +63 -0
- package/dist/core/operations/scaffold-machine.js.map +1 -0
- package/dist/core/project/manifest.d.ts +92 -0
- package/dist/core/project/manifest.d.ts.map +1 -0
- package/dist/core/project/manifest.js +321 -0
- package/dist/core/project/manifest.js.map +1 -0
- package/dist/core/project/validation.d.ts +20 -0
- package/dist/core/project/validation.d.ts.map +1 -0
- package/dist/core/project/validation.js +209 -0
- package/dist/core/project/validation.js.map +1 -0
- package/dist/core/scaffold/auth-generator.d.ts +38 -0
- package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
- package/dist/core/scaffold/auth-generator.js +1244 -0
- package/dist/core/scaffold/auth-generator.js.map +1 -0
- package/dist/core/scaffold/connector-functions-generator.d.ts +41 -0
- package/dist/core/scaffold/connector-functions-generator.d.ts.map +1 -0
- package/dist/core/scaffold/connector-functions-generator.js +1027 -0
- package/dist/core/scaffold/connector-functions-generator.js.map +1 -0
- package/dist/core/scaffold/functions-generator.d.ts +7 -1
- package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
- package/dist/core/scaffold/functions-generator.js +920 -213
- package/dist/core/scaffold/functions-generator.js.map +1 -1
- package/dist/core/scaffold/model-parser.d.ts +20 -1
- package/dist/core/scaffold/model-parser.d.ts.map +1 -1
- package/dist/core/scaffold/model-parser.js +328 -135
- package/dist/core/scaffold/model-parser.js.map +1 -1
- package/dist/core/scaffold/native-schema-generator.d.ts +13 -0
- package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -0
- package/dist/core/scaffold/native-schema-generator.js +677 -0
- package/dist/core/scaffold/native-schema-generator.js.map +1 -0
- package/dist/core/scaffold/nextjs-generator.d.ts +8 -0
- package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
- package/dist/core/scaffold/nextjs-generator.js +314 -182
- package/dist/core/scaffold/nextjs-generator.js.map +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/core/scaffold/ui-generator.d.ts +10 -4
- package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
- package/dist/core/scaffold/ui-generator.js +768 -663
- package/dist/core/scaffold/ui-generator.js.map +1 -1
- 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/machine/contracts.d.ts +16 -0
- package/dist/machine/contracts.d.ts.map +1 -0
- package/dist/machine/contracts.js +3 -0
- package/dist/machine/contracts.js.map +1 -0
- package/dist/machine/errors.d.ts +11 -0
- package/dist/machine/errors.d.ts.map +1 -0
- package/dist/machine/errors.js +34 -0
- package/dist/machine/errors.js.map +1 -0
- package/dist/machine/index.d.ts +3 -0
- package/dist/machine/index.d.ts.map +1 -0
- package/dist/machine/index.js +156 -0
- package/dist/machine/index.js.map +1 -0
- package/dist/mcp/index.d.ts +25 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +184 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/types/index.d.ts +65 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/package-manager.d.ts +109 -0
- package/dist/utils/package-manager.d.ts.map +1 -0
- package/dist/utils/package-manager.js +215 -0
- package/dist/utils/package-manager.js.map +1 -0
- package/dist/utils/python-uv.d.ts +21 -0
- package/dist/utils/python-uv.d.ts.map +1 -0
- package/dist/utils/python-uv.js +111 -0
- package/dist/utils/python-uv.js.map +1 -0
- package/package.json +85 -73
- package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +1139 -0
- package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +194 -0
- package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +532 -0
- package/src/__tests__/auth.test.ts +654 -0
- package/src/__tests__/config.test.ts +274 -0
- package/src/__tests__/connector-functions-generator.test.ts +288 -0
- package/src/__tests__/connector-mock-server.test.ts +439 -0
- package/src/__tests__/connector-model-bff.test.ts +162 -0
- package/src/__tests__/dev-seeds.test.ts +173 -0
- package/src/__tests__/dev.test.ts +252 -0
- package/src/__tests__/fixtures.ts +144 -0
- package/src/__tests__/functions-generator.test.ts +237 -0
- package/src/__tests__/init.test.ts +115 -0
- package/src/__tests__/machine.test.ts +251 -0
- package/src/__tests__/mcp.test.ts +117 -0
- package/src/__tests__/model-parser.test.ts +52 -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__/python-uv.test.ts +48 -0
- package/src/__tests__/scaffold.test.ts +67 -0
- package/src/__tests__/string-utils.test.ts +75 -0
- package/src/__tests__/ui-generator.test.ts +144 -0
- package/src/__tests__/zod-mock-generator.test.ts +132 -0
- package/src/cli/commands/add-auth.ts +500 -0
- package/src/cli/commands/add-connector.ts +158 -0
- package/src/cli/commands/create-model.ts +62 -0
- package/src/cli/commands/dev-seeds.ts +614 -0
- package/src/cli/commands/dev.ts +1134 -0
- package/src/cli/commands/index.ts +9 -0
- package/src/cli/commands/init.ts +3480 -0
- package/src/cli/commands/provision.ts +193 -0
- package/src/cli/commands/scaffold.ts +1001 -0
- package/src/cli/index.ts +196 -0
- package/src/core/config.ts +312 -0
- package/src/core/mock/connector-mock-server.ts +555 -0
- package/src/core/mock/zod-mock-generator.ts +205 -0
- package/src/core/operations/create-model.ts +174 -0
- package/src/core/operations/runtime.ts +235 -0
- package/src/core/operations/scaffold-machine.ts +91 -0
- package/src/core/project/manifest.ts +402 -0
- package/src/core/project/validation.ts +229 -0
- package/src/core/scaffold/auth-generator.ts +1284 -0
- package/src/core/scaffold/connector-functions-generator.ts +1128 -0
- package/src/core/scaffold/functions-generator.ts +970 -0
- package/src/core/scaffold/model-parser.ts +841 -0
- package/src/core/scaffold/native-schema-generator.ts +798 -0
- package/src/core/scaffold/nextjs-generator.ts +370 -0
- package/src/core/scaffold/openapi-generator.ts +212 -0
- package/src/core/scaffold/ui-generator.ts +1061 -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/machine/contracts.ts +17 -0
- package/src/machine/errors.ts +34 -0
- package/src/machine/index.ts +173 -0
- package/src/mcp/index.ts +185 -0
- package/src/types/index.ts +134 -0
- package/src/utils/package-manager.ts +229 -0
- package/src/utils/python-uv.ts +96 -0
package/dist/cli/commands/dev.js
CHANGED
|
@@ -34,19 +34,151 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.devCommand = void 0;
|
|
37
|
+
exports.buildFunctionsStartArgs = buildFunctionsStartArgs;
|
|
38
|
+
exports.parseCoreToolsVersion = parseCoreToolsVersion;
|
|
39
|
+
exports.compareVersionNumbers = compareVersionNumbers;
|
|
40
|
+
exports.buildFunctionsCoreToolsCommand = buildFunctionsCoreToolsCommand;
|
|
41
|
+
exports.buildNextDevArgs = buildNextDevArgs;
|
|
42
|
+
exports.buildFunctionsBaseUrl = buildFunctionsBaseUrl;
|
|
43
|
+
exports.getFunctionsReadinessTimeoutMs = getFunctionsReadinessTimeoutMs;
|
|
44
|
+
exports.waitForHttpServerReady = waitForHttpServerReady;
|
|
45
|
+
exports.getPythonVirtualEnvPaths = getPythonVirtualEnvPaths;
|
|
46
|
+
exports.getCSharpFunctionsBuildArtifactPaths = getCSharpFunctionsBuildArtifactPaths;
|
|
47
|
+
exports.buildPythonFunctionsEnv = buildPythonFunctionsEnv;
|
|
48
|
+
exports.buildDevCommand = buildDevCommand;
|
|
37
49
|
const commander_1 = require("commander");
|
|
38
50
|
const child_process_1 = require("child_process");
|
|
51
|
+
const http = __importStar(require("http"));
|
|
52
|
+
const https = __importStar(require("https"));
|
|
39
53
|
const path = __importStar(require("path"));
|
|
40
54
|
const fs = __importStar(require("fs"));
|
|
55
|
+
const os = __importStar(require("os"));
|
|
56
|
+
const net = __importStar(require("net"));
|
|
41
57
|
const cosmos_1 = require("@azure/cosmos");
|
|
42
58
|
const config_1 = require("../../core/config");
|
|
59
|
+
const dev_seeds_1 = require("./dev-seeds");
|
|
60
|
+
const package_manager_1 = require("../../utils/package-manager");
|
|
61
|
+
const python_uv_1 = require("../../utils/python-uv");
|
|
62
|
+
const connector_mock_server_1 = require("../../core/mock/connector-mock-server");
|
|
63
|
+
const MINIMUM_CSHARP_CORE_TOOLS_VERSION = '4.6.0';
|
|
64
|
+
const NPM_CORE_TOOLS_PACKAGE = 'azure-functions-core-tools@4';
|
|
65
|
+
function normalizeParsedDevOptions(options) {
|
|
66
|
+
return {
|
|
67
|
+
...options,
|
|
68
|
+
noFunctions: options.noFunctions ?? options.functions === false,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function buildFunctionsStartArgs(functionsPort) {
|
|
72
|
+
return ['start', '--port', functionsPort];
|
|
73
|
+
}
|
|
74
|
+
function parseCoreToolsVersion(output) {
|
|
75
|
+
const match = output.match(/\d+\.\d+\.\d+/);
|
|
76
|
+
return match ? match[0] : null;
|
|
77
|
+
}
|
|
78
|
+
function compareVersionNumbers(left, right) {
|
|
79
|
+
const leftParts = left.split('.').map((value) => Number.parseInt(value, 10));
|
|
80
|
+
const rightParts = right.split('.').map((value) => Number.parseInt(value, 10));
|
|
81
|
+
const length = Math.max(leftParts.length, rightParts.length);
|
|
82
|
+
for (let index = 0; index < length; index += 1) {
|
|
83
|
+
const leftPart = leftParts[index] ?? 0;
|
|
84
|
+
const rightPart = rightParts[index] ?? 0;
|
|
85
|
+
if (leftPart !== rightPart) {
|
|
86
|
+
return leftPart - rightPart;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
function buildFunctionsCoreToolsCommand(backendLanguage, installedVersion) {
|
|
92
|
+
if (backendLanguage === 'csharp' &&
|
|
93
|
+
(!installedVersion || compareVersionNumbers(installedVersion, MINIMUM_CSHARP_CORE_TOOLS_VERSION) < 0)) {
|
|
94
|
+
const reason = installedVersion
|
|
95
|
+
? `installed func ${installedVersion} is too old for C# isolated`
|
|
96
|
+
: 'func is not installed';
|
|
97
|
+
return {
|
|
98
|
+
command: 'npm',
|
|
99
|
+
argsPrefix: ['exec', '--yes', NPM_CORE_TOOLS_PACKAGE, '--'],
|
|
100
|
+
label: `npm exec ${NPM_CORE_TOOLS_PACKAGE} (${reason})`,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
command: 'func',
|
|
105
|
+
argsPrefix: [],
|
|
106
|
+
label: installedVersion ? `func ${installedVersion}` : 'func',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function buildNextDevArgs(pm, port) {
|
|
110
|
+
const baseArgs = ['next', 'dev', '--port', port, '--webpack'];
|
|
111
|
+
return pm === 'pnpm' ? ['exec', ...baseArgs] : baseArgs;
|
|
112
|
+
}
|
|
113
|
+
function buildFunctionsBaseUrl(host, functionsPort) {
|
|
114
|
+
return `http://${host || 'localhost'}:${functionsPort}`;
|
|
115
|
+
}
|
|
116
|
+
function getFunctionsReadinessTimeoutMs(backendLanguage) {
|
|
117
|
+
return backendLanguage === 'csharp' ? 90000 : 30000;
|
|
118
|
+
}
|
|
119
|
+
async function waitForHttpServerReady(url, timeoutMs = 30000, intervalMs = 500) {
|
|
120
|
+
const deadline = Date.now() + timeoutMs;
|
|
121
|
+
while (Date.now() <= deadline) {
|
|
122
|
+
if (await probeHttpServer(url)) {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
if (Date.now() + intervalMs > deadline) {
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
129
|
+
}
|
|
130
|
+
return probeHttpServer(url);
|
|
131
|
+
}
|
|
132
|
+
function getPythonVirtualEnvPaths(functionsDir) {
|
|
133
|
+
const venvDir = path.join(functionsDir, '.venv');
|
|
134
|
+
const binDir = process.platform === 'win32'
|
|
135
|
+
? path.join(venvDir, 'Scripts')
|
|
136
|
+
: path.join(venvDir, 'bin');
|
|
137
|
+
const pythonExecutable = process.platform === 'win32'
|
|
138
|
+
? path.join(binDir, 'python.exe')
|
|
139
|
+
: path.join(binDir, 'python');
|
|
140
|
+
return { venvDir, binDir, pythonExecutable };
|
|
141
|
+
}
|
|
142
|
+
function getCSharpFunctionsBuildArtifactPaths(functionsDir) {
|
|
143
|
+
return [
|
|
144
|
+
path.join(functionsDir, 'bin'),
|
|
145
|
+
path.join(functionsDir, 'obj'),
|
|
146
|
+
];
|
|
147
|
+
}
|
|
148
|
+
function buildPythonFunctionsEnv(baseEnv, functionsDir) {
|
|
149
|
+
const { venvDir, binDir, pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
|
|
150
|
+
const pathKey = getPathEnvKey(baseEnv);
|
|
151
|
+
const currentPath = baseEnv[pathKey] || '';
|
|
152
|
+
return {
|
|
153
|
+
...baseEnv,
|
|
154
|
+
[pathKey]: currentPath ? `${binDir}${path.delimiter}${currentPath}` : binDir,
|
|
155
|
+
VIRTUAL_ENV: venvDir,
|
|
156
|
+
languageWorkers__python__defaultExecutablePath: pythonExecutable,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function getPathEnvKey(env) {
|
|
160
|
+
return Object.keys(env).find((key) => key.toUpperCase() === 'PATH') || 'PATH';
|
|
161
|
+
}
|
|
162
|
+
function prependToPathEnv(env, entry) {
|
|
163
|
+
const pathKey = getPathEnvKey(env);
|
|
164
|
+
const currentPath = env[pathKey] || '';
|
|
165
|
+
return {
|
|
166
|
+
...env,
|
|
167
|
+
[pathKey]: currentPath ? `${entry}${path.delimiter}${currentPath}` : entry,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
43
170
|
/**
|
|
44
171
|
* Check if Azure Functions Core Tools is installed
|
|
45
172
|
*/
|
|
46
173
|
async function checkCoreTools() {
|
|
174
|
+
return checkCommand('func', ['--version']);
|
|
175
|
+
}
|
|
176
|
+
async function checkCommand(command, args = ['--version'], options) {
|
|
47
177
|
return new Promise((resolve) => {
|
|
48
|
-
const checkProcess = (0, child_process_1.spawn)(
|
|
49
|
-
|
|
178
|
+
const checkProcess = (0, child_process_1.spawn)(command, args, {
|
|
179
|
+
cwd: options?.cwd,
|
|
180
|
+
env: options?.env ?? process.env,
|
|
181
|
+
shell: options?.shell ?? true,
|
|
50
182
|
stdio: 'pipe',
|
|
51
183
|
});
|
|
52
184
|
checkProcess.on('close', (code) => {
|
|
@@ -57,12 +189,173 @@ async function checkCoreTools() {
|
|
|
57
189
|
});
|
|
58
190
|
});
|
|
59
191
|
}
|
|
192
|
+
async function resolveProjectLocalUvCommand(projectRoot) {
|
|
193
|
+
const uvEnv = (0, python_uv_1.buildProjectLocalUvEnv)(process.env, projectRoot);
|
|
194
|
+
const { localUvExecutable } = (0, python_uv_1.getProjectLocalUvPaths)(projectRoot);
|
|
195
|
+
if (await checkCommand('uv', ['--version'], { shell: false })) {
|
|
196
|
+
return { command: 'uv', env: uvEnv };
|
|
197
|
+
}
|
|
198
|
+
if (fs.existsSync(localUvExecutable) && await checkCommand(localUvExecutable, ['--version'], { shell: false })) {
|
|
199
|
+
return { command: localUvExecutable, env: uvEnv };
|
|
200
|
+
}
|
|
201
|
+
console.log('š¦ Installing project-local uv...');
|
|
202
|
+
const installer = (0, python_uv_1.getProjectLocalUvInstallerCommand)();
|
|
203
|
+
await runCommand(installer.command, installer.args, projectRoot, 'uv installation', (0, python_uv_1.buildProjectLocalUvInstallerEnv)(process.env, projectRoot), false);
|
|
204
|
+
if (!(fs.existsSync(localUvExecutable) && await checkCommand(localUvExecutable, ['--version'], { shell: false }))) {
|
|
205
|
+
throw new Error('Failed to install project-local uv.');
|
|
206
|
+
}
|
|
207
|
+
return { command: localUvExecutable, env: uvEnv };
|
|
208
|
+
}
|
|
209
|
+
async function getCommandPath(command) {
|
|
210
|
+
const locator = process.platform === 'win32' ? 'where' : 'which';
|
|
211
|
+
const result = await captureCommandOutput(locator, [command]);
|
|
212
|
+
const firstLine = result
|
|
213
|
+
.split(/\r?\n/)
|
|
214
|
+
.map((line) => line.trim())
|
|
215
|
+
.find(Boolean);
|
|
216
|
+
return firstLine || null;
|
|
217
|
+
}
|
|
218
|
+
async function resolveInstalledCoreToolsVersion() {
|
|
219
|
+
if (!(await checkCoreTools())) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
return parseCoreToolsVersion(await captureCommandOutput('func', ['--version']));
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
async function captureCommandOutput(command, args, cwd, env) {
|
|
230
|
+
return new Promise((resolve, reject) => {
|
|
231
|
+
const child = (0, child_process_1.spawn)(command, args, {
|
|
232
|
+
cwd,
|
|
233
|
+
env,
|
|
234
|
+
shell: false,
|
|
235
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
236
|
+
});
|
|
237
|
+
let stdout = '';
|
|
238
|
+
let stderr = '';
|
|
239
|
+
child.stdout?.on('data', (data) => {
|
|
240
|
+
stdout += data.toString();
|
|
241
|
+
});
|
|
242
|
+
child.stderr?.on('data', (data) => {
|
|
243
|
+
stderr += data.toString();
|
|
244
|
+
});
|
|
245
|
+
child.on('close', (code) => {
|
|
246
|
+
if (code === 0) {
|
|
247
|
+
resolve(stdout);
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
reject(new Error(stderr || stdout || `${command} exited with code ${code}`));
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
child.on('error', reject);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
async function probeHttpServer(url) {
|
|
257
|
+
return new Promise((resolve) => {
|
|
258
|
+
const target = new URL(url);
|
|
259
|
+
const requestFactory = target.protocol === 'https:' ? https.request : http.request;
|
|
260
|
+
let settled = false;
|
|
261
|
+
const finish = (value) => {
|
|
262
|
+
if (!settled) {
|
|
263
|
+
settled = true;
|
|
264
|
+
resolve(value);
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
const request = requestFactory({
|
|
268
|
+
hostname: target.hostname,
|
|
269
|
+
port: target.port,
|
|
270
|
+
path: target.pathname || '/',
|
|
271
|
+
method: 'GET',
|
|
272
|
+
timeout: 1000,
|
|
273
|
+
}, (response) => {
|
|
274
|
+
response.resume();
|
|
275
|
+
finish(true);
|
|
276
|
+
});
|
|
277
|
+
request.on('timeout', () => {
|
|
278
|
+
request.destroy();
|
|
279
|
+
finish(false);
|
|
280
|
+
});
|
|
281
|
+
request.on('error', () => finish(false));
|
|
282
|
+
request.end();
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
async function resolvePythonRuntimeDetails(functionsDir, env) {
|
|
286
|
+
const { pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
|
|
287
|
+
const output = await captureCommandOutput(pythonExecutable, [
|
|
288
|
+
'-c',
|
|
289
|
+
'import platform; import sys; print(str(sys.version_info.major) + "." + str(sys.version_info.minor)); print(platform.machine())',
|
|
290
|
+
], functionsDir, env);
|
|
291
|
+
const [version, architecture] = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
292
|
+
if (!version || !architecture) {
|
|
293
|
+
throw new Error('Failed to determine Python runtime details.');
|
|
294
|
+
}
|
|
295
|
+
return { version, architecture };
|
|
296
|
+
}
|
|
297
|
+
async function bridgePythonCoreToolsForWindowsArm64(functionsDir, env) {
|
|
298
|
+
if (process.platform !== 'win32' || process.arch !== 'arm64') {
|
|
299
|
+
return env;
|
|
300
|
+
}
|
|
301
|
+
const funcPath = await getCommandPath('func');
|
|
302
|
+
if (!funcPath) {
|
|
303
|
+
return env;
|
|
304
|
+
}
|
|
305
|
+
const { version, architecture } = await resolvePythonRuntimeDetails(functionsDir, env);
|
|
306
|
+
if (architecture.toUpperCase() !== 'AMD64') {
|
|
307
|
+
return env;
|
|
308
|
+
}
|
|
309
|
+
const coreToolsRoot = path.dirname(funcPath);
|
|
310
|
+
const armWorkerDir = path.join(coreToolsRoot, 'workers', 'python', version, 'WINDOWS', 'Arm64');
|
|
311
|
+
if (fs.existsSync(armWorkerDir)) {
|
|
312
|
+
return env;
|
|
313
|
+
}
|
|
314
|
+
const x64WorkerDir = path.join(coreToolsRoot, 'workers', 'python', version, 'WINDOWS', 'X64');
|
|
315
|
+
if (!fs.existsSync(x64WorkerDir)) {
|
|
316
|
+
return env;
|
|
317
|
+
}
|
|
318
|
+
const patchedRoot = path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'), 'SwallowKit', 'azure-functions-core-tools-python-bridge');
|
|
319
|
+
if (!fs.existsSync(path.join(patchedRoot, 'func.exe'))) {
|
|
320
|
+
console.log('𩹠Creating a local Azure Functions Core Tools bridge for Windows Arm64 Python...');
|
|
321
|
+
fs.mkdirSync(path.dirname(patchedRoot), { recursive: true });
|
|
322
|
+
fs.cpSync(coreToolsRoot, patchedRoot, { recursive: true });
|
|
323
|
+
}
|
|
324
|
+
const patchedArmWorkerDir = path.join(patchedRoot, 'workers', 'python', version, 'WINDOWS', 'Arm64');
|
|
325
|
+
const patchedX64WorkerDir = path.join(patchedRoot, 'workers', 'python', version, 'WINDOWS', 'X64');
|
|
326
|
+
if (!fs.existsSync(patchedArmWorkerDir) && fs.existsSync(patchedX64WorkerDir)) {
|
|
327
|
+
fs.cpSync(patchedX64WorkerDir, patchedArmWorkerDir, { recursive: true });
|
|
328
|
+
}
|
|
329
|
+
console.log(`𩹠Using bridged Azure Functions Core Tools from ${patchedRoot}`);
|
|
330
|
+
return prependToPathEnv(env, patchedRoot);
|
|
331
|
+
}
|
|
332
|
+
async function preparePythonFunctionsEnvironment(functionsDir) {
|
|
333
|
+
const projectRoot = (0, python_uv_1.getPythonProjectRoot)(functionsDir);
|
|
334
|
+
const { command: uvCommand, env: uvEnv } = await resolveProjectLocalUvCommand(projectRoot);
|
|
335
|
+
const { venvDir, pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
|
|
336
|
+
const hasUsableVirtualEnv = fs.existsSync(pythonExecutable) && await checkCommand(pythonExecutable, ['--version'], {
|
|
337
|
+
cwd: functionsDir,
|
|
338
|
+
env: uvEnv,
|
|
339
|
+
shell: false,
|
|
340
|
+
});
|
|
341
|
+
if (!hasUsableVirtualEnv) {
|
|
342
|
+
const venvArgs = (0, python_uv_1.buildUvVenvArgs)('.venv');
|
|
343
|
+
if (fs.existsSync(venvDir)) {
|
|
344
|
+
venvArgs.push('--clear');
|
|
345
|
+
}
|
|
346
|
+
console.log('š¦ Creating Python virtual environment with uv...');
|
|
347
|
+
await runCommand(uvCommand, venvArgs, functionsDir, 'python virtual environment setup', uvEnv, false);
|
|
348
|
+
}
|
|
349
|
+
console.log('š¦ Installing Python Azure Functions dependencies with uv...');
|
|
350
|
+
await runCommand(uvCommand, (0, python_uv_1.buildUvPipInstallArgs)(pythonExecutable, 'requirements.txt'), functionsDir, 'python dependency installation', uvEnv, false);
|
|
351
|
+
const pythonEnv = buildPythonFunctionsEnv(uvEnv, functionsDir);
|
|
352
|
+
return bridgePythonCoreToolsForWindowsArm64(functionsDir, pythonEnv);
|
|
353
|
+
}
|
|
60
354
|
/**
|
|
61
355
|
* Check if Cosmos DB Emulator is running by checking if port 8081 is open
|
|
62
356
|
*/
|
|
63
357
|
async function checkCosmosDBEmulator() {
|
|
64
358
|
return new Promise((resolve) => {
|
|
65
|
-
const net = require('net');
|
|
66
359
|
const socket = new net.Socket();
|
|
67
360
|
const timeout = setTimeout(() => {
|
|
68
361
|
socket.destroy();
|
|
@@ -80,112 +373,100 @@ async function checkCosmosDBEmulator() {
|
|
|
80
373
|
socket.connect(8081, 'localhost');
|
|
81
374
|
});
|
|
82
375
|
}
|
|
83
|
-
|
|
84
|
-
.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
376
|
+
function buildDevCommand(runDevEnvironment = startDevEnvironment, verifyProject = config_1.ensureSwallowKitProject) {
|
|
377
|
+
return new commander_1.Command()
|
|
378
|
+
.name('dev')
|
|
379
|
+
.description('Start SwallowKit development server (Cosmos DB + Next.js + Azure Functions)')
|
|
380
|
+
.option('-p, --port <port>', 'Next.js port', '3000')
|
|
381
|
+
.option('-f, --functions-port <port>', 'Azure Functions port', '7071')
|
|
382
|
+
.option('--host <host>', 'Host name', 'localhost')
|
|
383
|
+
.option('--open', 'Open browser automatically', false)
|
|
384
|
+
.option('--verbose', 'Show verbose logs', false)
|
|
385
|
+
.option('--no-functions', 'Skip Azure Functions startup')
|
|
386
|
+
.option('--seed-env <environment>', 'Replace Cosmos DB Emulator data from dev-seeds/<environment> before startup')
|
|
387
|
+
.option('--mock-connectors', 'Start mock server for connector models (serves Zod-generated data)', false)
|
|
388
|
+
.action(async (options) => {
|
|
389
|
+
const normalizedOptions = normalizeParsedDevOptions(options);
|
|
390
|
+
// SwallowKit ćććøć§ćÆććć£ć¬ćÆććŖćć©ćććę¤čؼ
|
|
391
|
+
verifyProject("dev");
|
|
392
|
+
console.log('š Starting SwallowKit development environment...');
|
|
393
|
+
if (normalizedOptions.verbose) {
|
|
394
|
+
console.log('āļø Options:', normalizedOptions);
|
|
395
|
+
}
|
|
396
|
+
await runDevEnvironment(normalizedOptions);
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
exports.devCommand = buildDevCommand();
|
|
101
400
|
async function initializeCosmosDB(databaseName) {
|
|
102
401
|
try {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return;
|
|
402
|
+
const connectionInfoResult = (0, dev_seeds_1.resolveLocalCosmosConnectionInfo)(databaseName);
|
|
403
|
+
if (!connectionInfoResult.ok) {
|
|
404
|
+
if (connectionInfoResult.reason === 'missing-local-settings') {
|
|
405
|
+
console.log('ā ļø local.settings.json not found. Skipping Cosmos DB initialization.');
|
|
406
|
+
}
|
|
407
|
+
else if (connectionInfoResult.reason === 'missing-connection-string') {
|
|
408
|
+
console.log('ā ļø CosmosDBConnection not found in local.settings.json. Skipping Cosmos DB initialization.');
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
console.log('ā ļø Invalid CosmosDB connection string format.');
|
|
412
|
+
}
|
|
413
|
+
return null;
|
|
116
414
|
}
|
|
117
415
|
console.log('šļø Initializing Cosmos DB...');
|
|
118
|
-
|
|
119
|
-
const endpointMatch = connectionString.match(/AccountEndpoint=([^;]+)/);
|
|
120
|
-
const keyMatch = connectionString.match(/AccountKey=([^;]+)/);
|
|
121
|
-
if (!endpointMatch || !keyMatch) {
|
|
122
|
-
console.log('ā ļø Invalid CosmosDB connection string format.');
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
const endpoint = endpointMatch[1];
|
|
416
|
+
const { endpoint, key, databaseName: dbName } = connectionInfoResult.value;
|
|
126
417
|
const client = new cosmos_1.CosmosClient({
|
|
127
418
|
endpoint: endpoint,
|
|
128
|
-
key
|
|
419
|
+
key,
|
|
129
420
|
});
|
|
130
421
|
// Create database if not exists
|
|
131
422
|
const { database } = await client.databases.createIfNotExists({ id: dbName });
|
|
132
423
|
console.log(`ā
Database "${dbName}" ready`);
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
// Try creating container with full partition key definition first
|
|
147
|
-
let containerCreated = false;
|
|
148
|
-
try {
|
|
149
|
-
console.log(`š§ Creating container "${containerName}" with partition key /id...`);
|
|
150
|
-
const containerResponse = await database.containers.createIfNotExists({
|
|
151
|
-
id: containerName,
|
|
152
|
-
partitionKey: {
|
|
153
|
-
paths: ['/id'],
|
|
154
|
-
kind: cosmos_1.PartitionKeyKind.Hash,
|
|
155
|
-
version: 2
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
console.log(`ā
Container "${containerName}" ready (status: ${containerResponse.statusCode})`);
|
|
159
|
-
containerCreated = true;
|
|
160
|
-
}
|
|
161
|
-
catch (error) {
|
|
162
|
-
console.log(`ā ļø Failed with full partition key definition: ${error.message}`);
|
|
163
|
-
console.log(`š Retrying with simple partition key...`);
|
|
424
|
+
const models = await (0, dev_seeds_1.loadProjectModels)();
|
|
425
|
+
for (const model of models) {
|
|
426
|
+
const containerName = (0, dev_seeds_1.getContainerNameForModel)(model);
|
|
427
|
+
// Try creating container with full partition key definition first
|
|
428
|
+
let containerCreated = false;
|
|
429
|
+
try {
|
|
430
|
+
console.log(`š§ Creating container "${containerName}" with partition key ${model.partitionKey}...`);
|
|
431
|
+
const containerResponse = await database.containers.createIfNotExists({
|
|
432
|
+
id: containerName,
|
|
433
|
+
partitionKey: {
|
|
434
|
+
paths: [model.partitionKey],
|
|
435
|
+
kind: cosmos_1.PartitionKeyKind.Hash,
|
|
436
|
+
version: 2
|
|
164
437
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
// Continue with other containers
|
|
438
|
+
});
|
|
439
|
+
console.log(`ā
Container "${containerName}" ready (status: ${containerResponse.statusCode})`);
|
|
440
|
+
containerCreated = true;
|
|
441
|
+
}
|
|
442
|
+
catch (error) {
|
|
443
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
444
|
+
console.log(`ā ļø Failed with full partition key definition: ${message}`);
|
|
445
|
+
console.log(`š Retrying with simple partition key...`);
|
|
446
|
+
}
|
|
447
|
+
// If first attempt failed, try with simple partition key definition
|
|
448
|
+
if (!containerCreated) {
|
|
449
|
+
try {
|
|
450
|
+
const containerResponse = await database.containers.createIfNotExists({
|
|
451
|
+
id: containerName,
|
|
452
|
+
partitionKey: {
|
|
453
|
+
paths: [model.partitionKey]
|
|
183
454
|
}
|
|
455
|
+
});
|
|
456
|
+
console.log(`ā
Container "${containerName}" ready (status: ${containerResponse.statusCode})`);
|
|
457
|
+
}
|
|
458
|
+
catch (containerError) {
|
|
459
|
+
console.error(`ā Failed to create container "${containerName}":`, containerError.message);
|
|
460
|
+
console.error(`Error code: ${containerError.code}`);
|
|
461
|
+
if (containerError.body) {
|
|
462
|
+
console.error(`Response body:`, JSON.stringify(containerError.body, null, 2));
|
|
184
463
|
}
|
|
464
|
+
// Continue with other containers
|
|
185
465
|
}
|
|
186
466
|
}
|
|
187
467
|
}
|
|
188
468
|
console.log('ā
Cosmos DB initialization complete\n');
|
|
469
|
+
return { endpoint, key, databaseName: dbName, models };
|
|
189
470
|
}
|
|
190
471
|
catch (error) {
|
|
191
472
|
console.error('ā ļø Cosmos DB initialization failed:', error.message);
|
|
@@ -193,16 +474,45 @@ async function initializeCosmosDB(databaseName) {
|
|
|
193
474
|
console.error('Stack trace:', error.stack);
|
|
194
475
|
}
|
|
195
476
|
console.log('š” Make sure Cosmos DB Emulator is running');
|
|
477
|
+
return null;
|
|
196
478
|
}
|
|
197
479
|
}
|
|
198
480
|
async function startDevEnvironment(options) {
|
|
199
481
|
const port = options.port || '3000';
|
|
200
482
|
const functionsPort = options.functionsPort || '7071';
|
|
483
|
+
// Detect package manager from project lockfile
|
|
484
|
+
const pm = (0, package_manager_1.detectFromProject)();
|
|
485
|
+
const pmCmd = (0, package_manager_1.getCommands)(pm);
|
|
486
|
+
const backendLanguage = (0, config_1.getBackendLanguage)();
|
|
201
487
|
// ććć»ć¹ćē®”ēććé
å
|
|
202
488
|
const processes = [];
|
|
489
|
+
let functionsEnv = process.env;
|
|
490
|
+
let mockServer = null;
|
|
491
|
+
let envLocalPath = '';
|
|
492
|
+
let envLocalDefaultUrl = ''; // default Functions URL to restore on shutdown
|
|
493
|
+
const functionsBaseUrl = buildFunctionsBaseUrl(options.host, functionsPort);
|
|
494
|
+
let functionsReadinessPromise = null;
|
|
495
|
+
let functionsReady = !!options.noFunctions;
|
|
203
496
|
// Cleanup processes on Ctrl+C
|
|
204
|
-
process.on('SIGINT', () => {
|
|
497
|
+
process.on('SIGINT', async () => {
|
|
205
498
|
console.log('\nš Stopping development servers...');
|
|
499
|
+
// Restore .env.local to default Functions port on shutdown
|
|
500
|
+
if (envLocalPath && envLocalDefaultUrl) {
|
|
501
|
+
try {
|
|
502
|
+
if (fs.existsSync(envLocalPath)) {
|
|
503
|
+
const content = fs.readFileSync(envLocalPath, 'utf-8');
|
|
504
|
+
if (content.includes('BACKEND_FUNCTIONS_BASE_URL=') &&
|
|
505
|
+
!content.includes(`BACKEND_FUNCTIONS_BASE_URL=${envLocalDefaultUrl}`)) {
|
|
506
|
+
const restored = content.replace(/^BACKEND_FUNCTIONS_BASE_URL=.*/m, `BACKEND_FUNCTIONS_BASE_URL=${envLocalDefaultUrl}`);
|
|
507
|
+
fs.writeFileSync(envLocalPath, restored, 'utf-8');
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
catch { /* ignore */ }
|
|
512
|
+
}
|
|
513
|
+
if (mockServer) {
|
|
514
|
+
await mockServer.stop();
|
|
515
|
+
}
|
|
206
516
|
processes.forEach((proc) => {
|
|
207
517
|
if (proc && !proc.killed) {
|
|
208
518
|
proc.kill();
|
|
@@ -226,12 +536,13 @@ async function startDevEnvironment(options) {
|
|
|
226
536
|
}
|
|
227
537
|
// 2. Check if Azure Functions exists
|
|
228
538
|
const functionsDir = path.join(process.cwd(), 'functions');
|
|
229
|
-
const hasFunctions = fs.existsSync(functionsDir) &&
|
|
230
|
-
|
|
539
|
+
const hasFunctions = fs.existsSync(functionsDir) && hasFunctionsProject(functionsDir, backendLanguage);
|
|
540
|
+
let functionsCoreToolsCommand = null;
|
|
541
|
+
let installedCoreToolsVersion = null;
|
|
231
542
|
if (hasFunctions && !options.noFunctions) {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (!
|
|
543
|
+
installedCoreToolsVersion = await resolveInstalledCoreToolsVersion();
|
|
544
|
+
functionsCoreToolsCommand = buildFunctionsCoreToolsCommand(backendLanguage, installedCoreToolsVersion);
|
|
545
|
+
if (functionsCoreToolsCommand.command === 'func' && !installedCoreToolsVersion) {
|
|
235
546
|
console.log('');
|
|
236
547
|
console.log('ā ļø Azure Functions Core Tools not found.');
|
|
237
548
|
console.log('');
|
|
@@ -248,7 +559,7 @@ async function startDevEnvironment(options) {
|
|
|
248
559
|
if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
|
|
249
560
|
console.log('š¦ Installing Azure Functions Core Tools...');
|
|
250
561
|
console.log(' This may take a few minutes.');
|
|
251
|
-
const installProcess = (0, child_process_1.spawn)('
|
|
562
|
+
const installProcess = (0, child_process_1.spawn)(pm, pm === 'pnpm' ? ['add', '-g', 'azure-functions-core-tools@4'] : ['install', '-g', 'azure-functions-core-tools@4'], {
|
|
252
563
|
shell: true,
|
|
253
564
|
stdio: 'inherit',
|
|
254
565
|
});
|
|
@@ -261,7 +572,7 @@ async function startDevEnvironment(options) {
|
|
|
261
572
|
else {
|
|
262
573
|
console.error('ā Installation failed.');
|
|
263
574
|
console.log('š” Please install manually:');
|
|
264
|
-
console.log(
|
|
575
|
+
console.log(` ${pmCmd.addGlobal} azure-functions-core-tools@4`);
|
|
265
576
|
reject(new Error(`Installation failed with code ${code}`));
|
|
266
577
|
}
|
|
267
578
|
});
|
|
@@ -272,12 +583,15 @@ async function startDevEnvironment(options) {
|
|
|
272
583
|
console.log('');
|
|
273
584
|
console.log('ā¹ļø Skipping Azure Functions startup.');
|
|
274
585
|
console.log('š” To install later:');
|
|
275
|
-
console.log(
|
|
586
|
+
console.log(` ${pmCmd.addGlobal} azure-functions-core-tools@4`);
|
|
276
587
|
console.log('');
|
|
277
588
|
// Skip Azure Functions startup
|
|
278
589
|
options.noFunctions = true;
|
|
279
590
|
}
|
|
280
591
|
}
|
|
592
|
+
else if (functionsCoreToolsCommand.command !== 'func') {
|
|
593
|
+
console.log(`ā¹ļø Using ${functionsCoreToolsCommand.label}.`);
|
|
594
|
+
}
|
|
281
595
|
if (!options.noFunctions) {
|
|
282
596
|
// Check if Cosmos DB Emulator is running
|
|
283
597
|
const cosmosRunning = await checkCosmosDBEmulator();
|
|
@@ -293,66 +607,89 @@ async function startDevEnvironment(options) {
|
|
|
293
607
|
process.exit(1);
|
|
294
608
|
}
|
|
295
609
|
// Initialize Cosmos DB before starting Functions
|
|
296
|
-
const
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
610
|
+
const databaseName = (0, dev_seeds_1.getDefaultCosmosDatabaseName)();
|
|
611
|
+
const cosmosInitialization = await initializeCosmosDB(databaseName);
|
|
612
|
+
if (options.seedEnv && cosmosInitialization) {
|
|
613
|
+
await (0, dev_seeds_1.applyDevSeedEnvironment)({
|
|
614
|
+
client: new cosmos_1.CosmosClient({
|
|
615
|
+
endpoint: cosmosInitialization.endpoint,
|
|
616
|
+
key: cosmosInitialization.key,
|
|
617
|
+
}),
|
|
618
|
+
databaseName: cosmosInitialization.databaseName,
|
|
619
|
+
environment: options.seedEnv,
|
|
620
|
+
models: cosmosInitialization.models,
|
|
621
|
+
});
|
|
622
|
+
}
|
|
300
623
|
console.log('');
|
|
301
624
|
console.log('š Starting Azure Functions...');
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
625
|
+
if (backendLanguage === 'typescript') {
|
|
626
|
+
const functionsNodeModules = path.join(functionsDir, 'node_modules');
|
|
627
|
+
if (!fs.existsSync(functionsNodeModules)) {
|
|
628
|
+
console.log('š¦ Installing Azure Functions dependencies...');
|
|
629
|
+
await runCommand(pm, ['install'], functionsDir, `${pm} install`);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
else if (backendLanguage === 'csharp') {
|
|
633
|
+
console.log('ā¹ļø C# Azure Functions can take longer on cold start while the worker builds.');
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
functionsEnv = await preparePythonFunctionsEnvironment(functionsDir);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
if (hasFunctions && !options.noFunctions) {
|
|
641
|
+
// Build shared package before starting Functions
|
|
642
|
+
const sharedDir = path.join(process.cwd(), 'shared');
|
|
643
|
+
const sharedPkgPath = path.join(sharedDir, 'package.json');
|
|
644
|
+
if (fs.existsSync(sharedDir) && fs.existsSync(sharedPkgPath)) {
|
|
645
|
+
const sharedPkg = JSON.parse(fs.readFileSync(sharedPkgPath, 'utf-8'));
|
|
646
|
+
if (sharedPkg.scripts?.build) {
|
|
647
|
+
console.log('š¦ Building shared package...');
|
|
648
|
+
const filterArgs = pm === 'pnpm'
|
|
649
|
+
? ['run', '--filter', 'shared', 'build']
|
|
650
|
+
: ['run', '--workspace=shared', 'build'];
|
|
651
|
+
const sharedBuild = (0, child_process_1.spawn)(pm, filterArgs, {
|
|
652
|
+
cwd: process.cwd(),
|
|
308
653
|
shell: true,
|
|
309
654
|
stdio: 'inherit',
|
|
310
655
|
});
|
|
311
656
|
await new Promise((resolve, reject) => {
|
|
312
|
-
|
|
657
|
+
sharedBuild.on('close', (code) => {
|
|
313
658
|
if (code === 0) {
|
|
659
|
+
console.log('ā
Shared package built successfully');
|
|
314
660
|
resolve();
|
|
315
661
|
}
|
|
316
662
|
else {
|
|
317
|
-
reject(new Error(`
|
|
663
|
+
reject(new Error(`Shared package build failed with code ${code}`));
|
|
318
664
|
}
|
|
319
665
|
});
|
|
320
|
-
|
|
666
|
+
sharedBuild.on('error', reject);
|
|
321
667
|
});
|
|
322
668
|
}
|
|
669
|
+
else {
|
|
670
|
+
console.log('ā ļø Shared package has no build script ā skipping build. Run "swallowkit add-auth" to fix.');
|
|
671
|
+
}
|
|
323
672
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
});
|
|
335
|
-
await new Promise((resolve, reject) => {
|
|
336
|
-
sharedBuild.on('close', (code) => {
|
|
337
|
-
if (code === 0) {
|
|
338
|
-
console.log('ā
Shared package built successfully');
|
|
339
|
-
resolve();
|
|
340
|
-
}
|
|
341
|
-
else {
|
|
342
|
-
reject(new Error(`Shared package build failed with code ${code}`));
|
|
343
|
-
}
|
|
344
|
-
});
|
|
345
|
-
sharedBuild.on('error', reject);
|
|
346
|
-
});
|
|
673
|
+
// Build TypeScript functions after shared package (functions import from shared)
|
|
674
|
+
if (backendLanguage === 'typescript') {
|
|
675
|
+
const functionsPkgPath = path.join(functionsDir, 'package.json');
|
|
676
|
+
if (fs.existsSync(functionsPkgPath)) {
|
|
677
|
+
const functionsPkg = JSON.parse(fs.readFileSync(functionsPkgPath, 'utf-8'));
|
|
678
|
+
if (functionsPkg.scripts?.build) {
|
|
679
|
+
console.log('š¦ Building TypeScript Azure Functions...');
|
|
680
|
+
await runCommand(pm, ['run', 'build'], functionsDir, `${pm} run build`);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
347
683
|
}
|
|
348
684
|
// Azure Functions ćčµ·å
|
|
349
|
-
const
|
|
350
|
-
const funcProcess = (0, child_process_1.spawn)(
|
|
685
|
+
const functionsCommand = functionsCoreToolsCommand ?? buildFunctionsCoreToolsCommand(backendLanguage, installedCoreToolsVersion);
|
|
686
|
+
const funcProcess = (0, child_process_1.spawn)(functionsCommand.command, [...functionsCommand.argsPrefix, ...buildFunctionsStartArgs(functionsPort)], {
|
|
351
687
|
cwd: functionsDir,
|
|
352
688
|
shell: true,
|
|
353
689
|
stdio: 'pipe', // Always pipe to capture output
|
|
354
|
-
env:
|
|
690
|
+
env: functionsEnv
|
|
355
691
|
});
|
|
692
|
+
let pythonWorkerMissing = false;
|
|
356
693
|
// Functions ć®åŗåććć®ć¾ć¾č”Øē¤ŗļ¼ćć¬ćć£ććÆć¹ä»ćļ¼
|
|
357
694
|
if (funcProcess.stdout) {
|
|
358
695
|
funcProcess.stdout.on('data', (data) => {
|
|
@@ -360,6 +697,10 @@ async function startDevEnvironment(options) {
|
|
|
360
697
|
// åč”ć«ćć¬ćć£ććÆć¹ćä»ćć¦åŗå
|
|
361
698
|
const lines = output.split('\n').filter((line) => line.trim());
|
|
362
699
|
lines.forEach((line) => {
|
|
700
|
+
if (backendLanguage === 'python' &&
|
|
701
|
+
(line.includes('WorkerConfig for runtime: python not found') || line.includes('DefaultWorkerPath:'))) {
|
|
702
|
+
pythonWorkerMissing = true;
|
|
703
|
+
}
|
|
363
704
|
console.log(`[Functions] ${line}`);
|
|
364
705
|
});
|
|
365
706
|
});
|
|
@@ -369,6 +710,10 @@ async function startDevEnvironment(options) {
|
|
|
369
710
|
const output = data.toString();
|
|
370
711
|
const lines = output.split('\n').filter((line) => line.trim());
|
|
371
712
|
lines.forEach((line) => {
|
|
713
|
+
if (backendLanguage === 'python' &&
|
|
714
|
+
(line.includes('WorkerConfig for runtime: python not found') || line.includes('DefaultWorkerPath:'))) {
|
|
715
|
+
pythonWorkerMissing = true;
|
|
716
|
+
}
|
|
372
717
|
console.error(`[Functions Error] ${line}`);
|
|
373
718
|
});
|
|
374
719
|
});
|
|
@@ -377,14 +722,20 @@ async function startDevEnvironment(options) {
|
|
|
377
722
|
funcProcess.on('error', (error) => {
|
|
378
723
|
console.error('ā ļø Azure Functions startup error:', error.message);
|
|
379
724
|
console.log('š” Please ensure Azure Functions Core Tools is installed');
|
|
380
|
-
console.log(
|
|
725
|
+
console.log(` ${pmCmd.addGlobal} azure-functions-core-tools@4`);
|
|
381
726
|
});
|
|
382
727
|
funcProcess.on('close', (code) => {
|
|
383
728
|
if (code !== 0) {
|
|
384
729
|
console.log(`\nā¹ļø Azure Functions exited (exit code: ${code})`);
|
|
730
|
+
if (backendLanguage === 'python' && pythonWorkerMissing) {
|
|
731
|
+
console.log('š” Your Azure Functions Core Tools installation is missing the Python worker for this OS/architecture.');
|
|
732
|
+
console.log(' Reinstall a matching Core Tools v4 package (Windows users should prefer the official x64/x86 MSI for their machine).');
|
|
733
|
+
console.log(' SwallowKit local Python dev uses functions/.venv and requirements.txt, but Core Tools still needs its own bundled Python worker.');
|
|
734
|
+
}
|
|
385
735
|
}
|
|
386
736
|
});
|
|
387
|
-
console.log(
|
|
737
|
+
console.log(`ā³ Waiting for Azure Functions to accept requests at ${functionsBaseUrl}...`);
|
|
738
|
+
functionsReadinessPromise = waitForHttpServerReady(functionsBaseUrl, getFunctionsReadinessTimeoutMs(backendLanguage));
|
|
388
739
|
}
|
|
389
740
|
else if (!hasFunctions) {
|
|
390
741
|
console.log('');
|
|
@@ -396,8 +747,69 @@ async function startDevEnvironment(options) {
|
|
|
396
747
|
}
|
|
397
748
|
console.log('');
|
|
398
749
|
console.log('š Starting Next.js development server...');
|
|
750
|
+
// Mock connector server ā start proxy if --mock-connectors is enabled
|
|
751
|
+
let bffTargetPort = functionsPort;
|
|
752
|
+
if (options.mockConnectors) {
|
|
753
|
+
const allModels = await (0, dev_seeds_1.loadProjectModels)();
|
|
754
|
+
const connectorModels = allModels.filter((m) => m.connectorConfig);
|
|
755
|
+
// Resolve auth config ā auth functions use RDB connectors, mocked alongside other models
|
|
756
|
+
const authConfig = (0, config_1.getAuthConfig)();
|
|
757
|
+
let mockAuthConfig;
|
|
758
|
+
if (authConfig?.provider === 'custom-jwt' && authConfig.customJwt) {
|
|
759
|
+
// Read JWT_SECRET from functions/local.settings.json if available
|
|
760
|
+
let jwtSecret = 'dev-jwt-secret-change-in-production-min-32-chars!!';
|
|
761
|
+
try {
|
|
762
|
+
const localSettingsPath = path.join(process.cwd(), 'functions', 'local.settings.json');
|
|
763
|
+
if (fs.existsSync(localSettingsPath)) {
|
|
764
|
+
const localSettings = JSON.parse(fs.readFileSync(localSettingsPath, 'utf-8'));
|
|
765
|
+
jwtSecret = localSettings.Values?.[authConfig.customJwt.jwtSecretEnv || 'JWT_SECRET'] || jwtSecret;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
catch { /* ignore */ }
|
|
769
|
+
mockAuthConfig = {
|
|
770
|
+
jwtSecret,
|
|
771
|
+
tokenExpiry: authConfig.customJwt.tokenExpiry,
|
|
772
|
+
customJwt: {
|
|
773
|
+
userTable: authConfig.customJwt.userTable,
|
|
774
|
+
loginIdColumn: authConfig.customJwt.loginIdColumn,
|
|
775
|
+
passwordHashColumn: authConfig.customJwt.passwordHashColumn,
|
|
776
|
+
rolesColumn: authConfig.customJwt.rolesColumn,
|
|
777
|
+
},
|
|
778
|
+
defaultPolicy: authConfig.authorization?.defaultPolicy,
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
if (connectorModels.length > 0 || mockAuthConfig) {
|
|
782
|
+
const mockPort = parseInt(functionsPort, 10) + 1;
|
|
783
|
+
mockServer = new connector_mock_server_1.ConnectorMockServer({
|
|
784
|
+
port: mockPort,
|
|
785
|
+
functionsTarget: `${options.host || 'localhost'}:${functionsPort}`,
|
|
786
|
+
connectorModels,
|
|
787
|
+
allModels,
|
|
788
|
+
seedEnv: options.seedEnv,
|
|
789
|
+
host: options.host || 'localhost',
|
|
790
|
+
authConfig: mockAuthConfig,
|
|
791
|
+
});
|
|
792
|
+
await mockServer.start();
|
|
793
|
+
bffTargetPort = String(mockPort);
|
|
794
|
+
const modelCount = connectorModels.length + (mockAuthConfig ? 1 : 0);
|
|
795
|
+
console.log('');
|
|
796
|
+
console.log(`š Mock server started (port: ${mockPort}) ā ${modelCount} model(s) mocked via Zod/seed data`);
|
|
797
|
+
for (const m of connectorModels) {
|
|
798
|
+
const ops = m.connectorConfig.operations.join(', ');
|
|
799
|
+
console.log(` - ${m.name} [${ops}]`);
|
|
800
|
+
}
|
|
801
|
+
if (mockAuthConfig) {
|
|
802
|
+
console.log(` - auth [login, me, logout]`);
|
|
803
|
+
}
|
|
804
|
+
console.log(` Other routes ā proxied to Azure Functions (port: ${functionsPort})`);
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
console.log('');
|
|
808
|
+
console.log('ā¹ļø --mock-connectors specified but no connector models found. Skipping mock server.');
|
|
809
|
+
}
|
|
810
|
+
}
|
|
399
811
|
// 5. Start Next.js development server
|
|
400
|
-
const nextArgs =
|
|
812
|
+
const nextArgs = buildNextDevArgs(pm, port);
|
|
401
813
|
if (options.open) {
|
|
402
814
|
// Next.js 14+ deprecated --open option, so we open browser manually
|
|
403
815
|
setTimeout(() => {
|
|
@@ -408,10 +820,33 @@ async function startDevEnvironment(options) {
|
|
|
408
820
|
(0, child_process_1.spawn)(start, [url], { shell: true });
|
|
409
821
|
}, 3000);
|
|
410
822
|
}
|
|
411
|
-
|
|
823
|
+
// Ensure .env.local points to bffTargetPort so Next.js reads the correct backend URL.
|
|
824
|
+
// When --mock-connectors is active, bffTargetPort = mock port (7072); otherwise = Functions port (7071).
|
|
825
|
+
// Next.js may load .env.local values that override spawn env vars, so we must keep them in sync.
|
|
826
|
+
envLocalPath = path.join(process.cwd(), '.env.local');
|
|
827
|
+
envLocalDefaultUrl = functionsBaseUrl;
|
|
828
|
+
const bffTargetUrl = buildFunctionsBaseUrl(options.host, bffTargetPort);
|
|
829
|
+
try {
|
|
830
|
+
if (fs.existsSync(envLocalPath)) {
|
|
831
|
+
const envContent = fs.readFileSync(envLocalPath, 'utf-8');
|
|
832
|
+
if (envContent.includes('BACKEND_FUNCTIONS_BASE_URL=') &&
|
|
833
|
+
!envContent.includes(`BACKEND_FUNCTIONS_BASE_URL=${bffTargetUrl}`)) {
|
|
834
|
+
const updated = envContent.replace(/^BACKEND_FUNCTIONS_BASE_URL=.*/m, `BACKEND_FUNCTIONS_BASE_URL=${bffTargetUrl}`);
|
|
835
|
+
fs.writeFileSync(envLocalPath, updated, 'utf-8');
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
catch { /* ignore */ }
|
|
840
|
+
const nextEnv = {
|
|
841
|
+
...process.env,
|
|
842
|
+
BACKEND_FUNCTIONS_BASE_URL: bffTargetUrl,
|
|
843
|
+
FUNCTIONS_BASE_URL: bffTargetUrl,
|
|
844
|
+
};
|
|
845
|
+
const nextProcess = (0, child_process_1.spawn)(pm === 'pnpm' ? 'pnpm' : 'npx', nextArgs, {
|
|
412
846
|
cwd: process.cwd(),
|
|
413
847
|
shell: true,
|
|
414
848
|
stdio: options.verbose ? 'inherit' : 'inherit',
|
|
849
|
+
env: nextEnv,
|
|
415
850
|
});
|
|
416
851
|
processes.push(nextProcess);
|
|
417
852
|
nextProcess.on('error', (error) => {
|
|
@@ -423,6 +858,9 @@ async function startDevEnvironment(options) {
|
|
|
423
858
|
console.log(`\nā¹ļø Next.js exited (exit code: ${code})`);
|
|
424
859
|
}
|
|
425
860
|
// Exit all processes when Next.js exits
|
|
861
|
+
if (mockServer) {
|
|
862
|
+
mockServer.stop().catch(() => { });
|
|
863
|
+
}
|
|
426
864
|
processes.forEach((proc) => {
|
|
427
865
|
if (proc && !proc.killed) {
|
|
428
866
|
proc.kill();
|
|
@@ -430,17 +868,36 @@ async function startDevEnvironment(options) {
|
|
|
430
868
|
});
|
|
431
869
|
process.exit(code || 0);
|
|
432
870
|
});
|
|
871
|
+
if (functionsReadinessPromise) {
|
|
872
|
+
functionsReady = await functionsReadinessPromise;
|
|
873
|
+
console.log('');
|
|
874
|
+
if (functionsReady) {
|
|
875
|
+
console.log(`ā
Azure Functions ready (port: ${functionsPort})`);
|
|
876
|
+
}
|
|
877
|
+
else {
|
|
878
|
+
console.log(`ā ļø Azure Functions is still starting: ${functionsBaseUrl}`);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
433
881
|
console.log('');
|
|
434
882
|
console.log('ā
SwallowKit development environment is running!');
|
|
435
883
|
console.log('');
|
|
436
884
|
console.log(`š± Next.js: http://${options.host || 'localhost'}:${port}`);
|
|
437
885
|
if (hasFunctions && !options.noFunctions) {
|
|
438
|
-
console.log(
|
|
886
|
+
console.log(`${functionsReady ? 'ā” Azure Functions' : 'ā³ Azure Functions (starting)'}: ${functionsBaseUrl}`);
|
|
887
|
+
}
|
|
888
|
+
if (mockServer) {
|
|
889
|
+
console.log(`š Mock Proxy: ${bffTargetUrl} (BFF ā here)`);
|
|
439
890
|
}
|
|
440
891
|
console.log('');
|
|
441
|
-
if (hasFunctions && !options.noFunctions) {
|
|
892
|
+
if (hasFunctions && !options.noFunctions && functionsReady) {
|
|
442
893
|
console.log('š” Azure Functions and Next.js BFF are connected');
|
|
443
894
|
}
|
|
895
|
+
else if (hasFunctions && !options.noFunctions) {
|
|
896
|
+
console.log('š” Azure Functions is still warming up; BFF routes can fail until the backend responds.');
|
|
897
|
+
}
|
|
898
|
+
if (mockServer) {
|
|
899
|
+
console.log('š” Connector models served from mock server (Zod-generated data)');
|
|
900
|
+
}
|
|
444
901
|
console.log('');
|
|
445
902
|
console.log('š Press Ctrl+C to stop');
|
|
446
903
|
console.log('');
|
|
@@ -455,4 +912,29 @@ async function startDevEnvironment(options) {
|
|
|
455
912
|
process.exit(1);
|
|
456
913
|
}
|
|
457
914
|
}
|
|
915
|
+
async function runCommand(command, args, cwd, label, env, shell = true) {
|
|
916
|
+
await new Promise((resolve, reject) => {
|
|
917
|
+
const child = (0, child_process_1.spawn)(command, args, {
|
|
918
|
+
cwd,
|
|
919
|
+
shell,
|
|
920
|
+
stdio: 'inherit',
|
|
921
|
+
env,
|
|
922
|
+
});
|
|
923
|
+
child.on('close', (code) => {
|
|
924
|
+
if (code === 0) {
|
|
925
|
+
resolve();
|
|
926
|
+
}
|
|
927
|
+
else {
|
|
928
|
+
reject(new Error(`${label} failed with code ${code}`));
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
child.on('error', reject);
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
function hasFunctionsProject(functionsDir, backendLanguage) {
|
|
935
|
+
if (backendLanguage === 'typescript') {
|
|
936
|
+
return fs.existsSync(path.join(functionsDir, 'package.json'));
|
|
937
|
+
}
|
|
938
|
+
return fs.existsSync(path.join(functionsDir, 'host.json'));
|
|
939
|
+
}
|
|
458
940
|
//# sourceMappingURL=dev.js.map
|