swallowkit 1.0.0-beta.2 ā 1.0.0-beta.21
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 +312 -215
- package/README.md +369 -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 +35 -0
- package/dist/cli/commands/dev-seeds.d.ts.map +1 -0
- package/dist/cli/commands/dev-seeds.js +292 -0
- package/dist/cli/commands/dev-seeds.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +19 -0
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +476 -117
- 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 +13 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +2627 -1708
- 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 +617 -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 +164 -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 +90 -4
- 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 +204 -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 +329 -135
- package/dist/core/scaffold/model-parser.js.map +1 -1
- 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/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 +263 -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 +112 -0
- package/src/__tests__/dev.test.ts +154 -0
- package/src/__tests__/fixtures.ts +144 -0
- package/src/__tests__/functions-generator.test.ts +237 -0
- package/src/__tests__/init.test.ts +80 -0
- package/src/__tests__/machine.test.ts +212 -0
- package/src/__tests__/mcp.test.ts +56 -0
- package/src/__tests__/model-parser.test.ts +72 -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/__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 +358 -0
- package/src/cli/commands/dev.ts +962 -0
- package/src/cli/commands/index.ts +9 -0
- package/src/cli/commands/init.ts +3371 -0
- package/src/cli/commands/provision.ts +193 -0
- package/src/cli/commands/scaffold.ts +1211 -0
- package/src/cli/index.ts +193 -0
- package/src/core/config.ts +308 -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 +221 -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/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/dist/cli/commands/dev.js
CHANGED
|
@@ -34,18 +34,76 @@ 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.buildNextDevArgs = buildNextDevArgs;
|
|
39
|
+
exports.getPythonVirtualEnvPaths = getPythonVirtualEnvPaths;
|
|
40
|
+
exports.buildPythonFunctionsEnv = buildPythonFunctionsEnv;
|
|
41
|
+
exports.buildDevCommand = buildDevCommand;
|
|
37
42
|
const commander_1 = require("commander");
|
|
38
43
|
const child_process_1 = require("child_process");
|
|
39
44
|
const path = __importStar(require("path"));
|
|
40
45
|
const fs = __importStar(require("fs"));
|
|
46
|
+
const os = __importStar(require("os"));
|
|
47
|
+
const net = __importStar(require("net"));
|
|
41
48
|
const cosmos_1 = require("@azure/cosmos");
|
|
42
49
|
const config_1 = require("../../core/config");
|
|
50
|
+
const dev_seeds_1 = require("./dev-seeds");
|
|
51
|
+
const package_manager_1 = require("../../utils/package-manager");
|
|
52
|
+
const connector_mock_server_1 = require("../../core/mock/connector-mock-server");
|
|
53
|
+
function normalizeParsedDevOptions(options) {
|
|
54
|
+
return {
|
|
55
|
+
...options,
|
|
56
|
+
noFunctions: options.noFunctions ?? options.functions === false,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function buildFunctionsStartArgs(functionsPort) {
|
|
60
|
+
return ['start', '--port', functionsPort];
|
|
61
|
+
}
|
|
62
|
+
function buildNextDevArgs(pm, port) {
|
|
63
|
+
const baseArgs = ['next', 'dev', '--port', port, '--webpack'];
|
|
64
|
+
return pm === 'pnpm' ? ['exec', ...baseArgs] : baseArgs;
|
|
65
|
+
}
|
|
66
|
+
function getPythonVirtualEnvPaths(functionsDir) {
|
|
67
|
+
const venvDir = path.join(functionsDir, '.venv');
|
|
68
|
+
const binDir = process.platform === 'win32'
|
|
69
|
+
? path.join(venvDir, 'Scripts')
|
|
70
|
+
: path.join(venvDir, 'bin');
|
|
71
|
+
const pythonExecutable = process.platform === 'win32'
|
|
72
|
+
? path.join(binDir, 'python.exe')
|
|
73
|
+
: path.join(binDir, 'python');
|
|
74
|
+
return { venvDir, binDir, pythonExecutable };
|
|
75
|
+
}
|
|
76
|
+
function buildPythonFunctionsEnv(baseEnv, functionsDir) {
|
|
77
|
+
const { venvDir, binDir, pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
|
|
78
|
+
const pathKey = getPathEnvKey(baseEnv);
|
|
79
|
+
const currentPath = baseEnv[pathKey] || '';
|
|
80
|
+
return {
|
|
81
|
+
...baseEnv,
|
|
82
|
+
[pathKey]: currentPath ? `${binDir}${path.delimiter}${currentPath}` : binDir,
|
|
83
|
+
VIRTUAL_ENV: venvDir,
|
|
84
|
+
languageWorkers__python__defaultExecutablePath: pythonExecutable,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function getPathEnvKey(env) {
|
|
88
|
+
return Object.keys(env).find((key) => key.toUpperCase() === 'PATH') || 'PATH';
|
|
89
|
+
}
|
|
90
|
+
function prependToPathEnv(env, entry) {
|
|
91
|
+
const pathKey = getPathEnvKey(env);
|
|
92
|
+
const currentPath = env[pathKey] || '';
|
|
93
|
+
return {
|
|
94
|
+
...env,
|
|
95
|
+
[pathKey]: currentPath ? `${entry}${path.delimiter}${currentPath}` : entry,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
43
98
|
/**
|
|
44
99
|
* Check if Azure Functions Core Tools is installed
|
|
45
100
|
*/
|
|
46
101
|
async function checkCoreTools() {
|
|
102
|
+
return checkCommand('func', ['--version']);
|
|
103
|
+
}
|
|
104
|
+
async function checkCommand(command, args = ['--version']) {
|
|
47
105
|
return new Promise((resolve) => {
|
|
48
|
-
const checkProcess = (0, child_process_1.spawn)(
|
|
106
|
+
const checkProcess = (0, child_process_1.spawn)(command, args, {
|
|
49
107
|
shell: true,
|
|
50
108
|
stdio: 'pipe',
|
|
51
109
|
});
|
|
@@ -57,12 +115,136 @@ async function checkCoreTools() {
|
|
|
57
115
|
});
|
|
58
116
|
});
|
|
59
117
|
}
|
|
118
|
+
async function resolvePythonBootstrapCommand() {
|
|
119
|
+
const candidates = process.platform === 'win32'
|
|
120
|
+
? [
|
|
121
|
+
{ command: 'py', argsPrefix: ['-3.11'], label: 'py -3.11' },
|
|
122
|
+
{ command: 'python', argsPrefix: [], label: 'python' },
|
|
123
|
+
]
|
|
124
|
+
: [
|
|
125
|
+
{ command: 'python3', argsPrefix: [], label: 'python3' },
|
|
126
|
+
{ command: 'python', argsPrefix: [], label: 'python' },
|
|
127
|
+
];
|
|
128
|
+
for (const candidate of candidates) {
|
|
129
|
+
if (await checkCommand(candidate.command, [...candidate.argsPrefix, '--version'])) {
|
|
130
|
+
return candidate;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
throw new Error('Python 3.11 was not found. Install Python 3.11 and make sure `python`, `python3`, or `py -3.11` is available.');
|
|
134
|
+
}
|
|
135
|
+
async function getCommandPath(command) {
|
|
136
|
+
const locator = process.platform === 'win32' ? 'where' : 'which';
|
|
137
|
+
const result = await captureCommandOutput(locator, [command]);
|
|
138
|
+
const firstLine = result
|
|
139
|
+
.split(/\r?\n/)
|
|
140
|
+
.map((line) => line.trim())
|
|
141
|
+
.find(Boolean);
|
|
142
|
+
return firstLine || null;
|
|
143
|
+
}
|
|
144
|
+
async function captureCommandOutput(command, args, cwd, env) {
|
|
145
|
+
return new Promise((resolve, reject) => {
|
|
146
|
+
const child = (0, child_process_1.spawn)(command, args, {
|
|
147
|
+
cwd,
|
|
148
|
+
env,
|
|
149
|
+
shell: false,
|
|
150
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
151
|
+
});
|
|
152
|
+
let stdout = '';
|
|
153
|
+
let stderr = '';
|
|
154
|
+
child.stdout?.on('data', (data) => {
|
|
155
|
+
stdout += data.toString();
|
|
156
|
+
});
|
|
157
|
+
child.stderr?.on('data', (data) => {
|
|
158
|
+
stderr += data.toString();
|
|
159
|
+
});
|
|
160
|
+
child.on('close', (code) => {
|
|
161
|
+
if (code === 0) {
|
|
162
|
+
resolve(stdout);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
reject(new Error(stderr || stdout || `${command} exited with code ${code}`));
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
child.on('error', reject);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
async function resolvePythonRuntimeDetails(functionsDir, env) {
|
|
172
|
+
const { pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
|
|
173
|
+
const output = await captureCommandOutput(pythonExecutable, [
|
|
174
|
+
'-c',
|
|
175
|
+
'import platform; import sys; print(str(sys.version_info.major) + "." + str(sys.version_info.minor)); print(platform.machine())',
|
|
176
|
+
], functionsDir, env);
|
|
177
|
+
const [version, architecture] = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
178
|
+
if (!version || !architecture) {
|
|
179
|
+
throw new Error('Failed to determine Python runtime details.');
|
|
180
|
+
}
|
|
181
|
+
return { version, architecture };
|
|
182
|
+
}
|
|
183
|
+
async function bridgePythonCoreToolsForWindowsArm64(functionsDir, env) {
|
|
184
|
+
if (process.platform !== 'win32' || process.arch !== 'arm64') {
|
|
185
|
+
return env;
|
|
186
|
+
}
|
|
187
|
+
const funcPath = await getCommandPath('func');
|
|
188
|
+
if (!funcPath) {
|
|
189
|
+
return env;
|
|
190
|
+
}
|
|
191
|
+
const { version, architecture } = await resolvePythonRuntimeDetails(functionsDir, env);
|
|
192
|
+
if (architecture.toUpperCase() !== 'AMD64') {
|
|
193
|
+
return env;
|
|
194
|
+
}
|
|
195
|
+
const coreToolsRoot = path.dirname(funcPath);
|
|
196
|
+
const armWorkerDir = path.join(coreToolsRoot, 'workers', 'python', version, 'WINDOWS', 'Arm64');
|
|
197
|
+
if (fs.existsSync(armWorkerDir)) {
|
|
198
|
+
return env;
|
|
199
|
+
}
|
|
200
|
+
const x64WorkerDir = path.join(coreToolsRoot, 'workers', 'python', version, 'WINDOWS', 'X64');
|
|
201
|
+
if (!fs.existsSync(x64WorkerDir)) {
|
|
202
|
+
return env;
|
|
203
|
+
}
|
|
204
|
+
const patchedRoot = path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'), 'SwallowKit', 'azure-functions-core-tools-python-bridge');
|
|
205
|
+
if (!fs.existsSync(path.join(patchedRoot, 'func.exe'))) {
|
|
206
|
+
console.log('𩹠Creating a local Azure Functions Core Tools bridge for Windows Arm64 Python...');
|
|
207
|
+
fs.mkdirSync(path.dirname(patchedRoot), { recursive: true });
|
|
208
|
+
fs.cpSync(coreToolsRoot, patchedRoot, { recursive: true });
|
|
209
|
+
}
|
|
210
|
+
const patchedArmWorkerDir = path.join(patchedRoot, 'workers', 'python', version, 'WINDOWS', 'Arm64');
|
|
211
|
+
const patchedX64WorkerDir = path.join(patchedRoot, 'workers', 'python', version, 'WINDOWS', 'X64');
|
|
212
|
+
if (!fs.existsSync(patchedArmWorkerDir) && fs.existsSync(patchedX64WorkerDir)) {
|
|
213
|
+
fs.cpSync(patchedX64WorkerDir, patchedArmWorkerDir, { recursive: true });
|
|
214
|
+
}
|
|
215
|
+
console.log(`𩹠Using bridged Azure Functions Core Tools from ${patchedRoot}`);
|
|
216
|
+
return prependToPathEnv(env, patchedRoot);
|
|
217
|
+
}
|
|
218
|
+
async function preparePythonFunctionsEnvironment(functionsDir) {
|
|
219
|
+
const { pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
|
|
220
|
+
const hasUv = await checkCommand('uv', ['--version']);
|
|
221
|
+
if (!fs.existsSync(pythonExecutable)) {
|
|
222
|
+
if (hasUv) {
|
|
223
|
+
console.log('š¦ Creating Python virtual environment with uv...');
|
|
224
|
+
await runCommand('uv', ['venv', '.venv', '--python', '3.11'], functionsDir, 'python virtual environment setup');
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
const bootstrap = await resolvePythonBootstrapCommand();
|
|
228
|
+
console.log(`š¦ Creating Python virtual environment with ${bootstrap.label}...`);
|
|
229
|
+
await runCommand(bootstrap.command, [...bootstrap.argsPrefix, '-m', 'venv', '.venv'], functionsDir, 'python virtual environment setup');
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const pythonEnv = buildPythonFunctionsEnv(process.env, functionsDir);
|
|
233
|
+
console.log(`š¦ Installing Python Azure Functions dependencies${hasUv ? ' with uv' : ''}...`);
|
|
234
|
+
if (hasUv) {
|
|
235
|
+
await runCommand('uv', ['pip', 'install', '--python', pythonExecutable, '-r', 'requirements.txt'], functionsDir, 'python dependency installation', pythonEnv);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
await runCommand('python', ['-m', 'pip', 'install', '--upgrade', 'pip'], functionsDir, 'python pip upgrade', pythonEnv);
|
|
239
|
+
await runCommand('python', ['-m', 'pip', 'install', '-r', 'requirements.txt'], functionsDir, 'python dependency installation', pythonEnv);
|
|
240
|
+
}
|
|
241
|
+
return bridgePythonCoreToolsForWindowsArm64(functionsDir, pythonEnv);
|
|
242
|
+
}
|
|
60
243
|
/**
|
|
61
244
|
* Check if Cosmos DB Emulator is running by checking if port 8081 is open
|
|
62
245
|
*/
|
|
63
246
|
async function checkCosmosDBEmulator() {
|
|
64
247
|
return new Promise((resolve) => {
|
|
65
|
-
const net = require('net');
|
|
66
248
|
const socket = new net.Socket();
|
|
67
249
|
const timeout = setTimeout(() => {
|
|
68
250
|
socket.destroy();
|
|
@@ -80,24 +262,30 @@ async function checkCosmosDBEmulator() {
|
|
|
80
262
|
socket.connect(8081, 'localhost');
|
|
81
263
|
});
|
|
82
264
|
}
|
|
83
|
-
|
|
84
|
-
.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
265
|
+
function buildDevCommand(runDevEnvironment = startDevEnvironment, verifyProject = config_1.ensureSwallowKitProject) {
|
|
266
|
+
return new commander_1.Command()
|
|
267
|
+
.name('dev')
|
|
268
|
+
.description('Start SwallowKit development server (Cosmos DB + Next.js + Azure Functions)')
|
|
269
|
+
.option('-p, --port <port>', 'Next.js port', '3000')
|
|
270
|
+
.option('-f, --functions-port <port>', 'Azure Functions port', '7071')
|
|
271
|
+
.option('--host <host>', 'Host name', 'localhost')
|
|
272
|
+
.option('--open', 'Open browser automatically', false)
|
|
273
|
+
.option('--verbose', 'Show verbose logs', false)
|
|
274
|
+
.option('--no-functions', 'Skip Azure Functions startup', false)
|
|
275
|
+
.option('--seed-env <environment>', 'Replace Cosmos DB Emulator data from dev-seeds/<environment> before startup')
|
|
276
|
+
.option('--mock-connectors', 'Start mock server for connector models (serves Zod-generated data)', false)
|
|
277
|
+
.action(async (options) => {
|
|
278
|
+
const normalizedOptions = normalizeParsedDevOptions(options);
|
|
279
|
+
// SwallowKit ćććøć§ćÆććć£ć¬ćÆććŖćć©ćććę¤čؼ
|
|
280
|
+
verifyProject("dev");
|
|
281
|
+
console.log('š Starting SwallowKit development environment...');
|
|
282
|
+
if (normalizedOptions.verbose) {
|
|
283
|
+
console.log('āļø Options:', normalizedOptions);
|
|
284
|
+
}
|
|
285
|
+
await runDevEnvironment(normalizedOptions);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
exports.devCommand = buildDevCommand();
|
|
101
289
|
async function initializeCosmosDB(databaseName) {
|
|
102
290
|
try {
|
|
103
291
|
// Read local.settings.json from functions directory
|
|
@@ -105,14 +293,14 @@ async function initializeCosmosDB(databaseName) {
|
|
|
105
293
|
const localSettingsPath = path.join(functionsDir, 'local.settings.json');
|
|
106
294
|
if (!fs.existsSync(localSettingsPath)) {
|
|
107
295
|
console.log('ā ļø local.settings.json not found. Skipping Cosmos DB initialization.');
|
|
108
|
-
return;
|
|
296
|
+
return null;
|
|
109
297
|
}
|
|
110
298
|
const localSettings = JSON.parse(fs.readFileSync(localSettingsPath, 'utf-8'));
|
|
111
299
|
const connectionString = localSettings.Values?.CosmosDBConnection;
|
|
112
300
|
const dbName = localSettings.Values?.COSMOS_DB_DATABASE_NAME || databaseName;
|
|
113
301
|
if (!connectionString) {
|
|
114
302
|
console.log('ā ļø CosmosDBConnection not found in local.settings.json. Skipping Cosmos DB initialization.');
|
|
115
|
-
return;
|
|
303
|
+
return null;
|
|
116
304
|
}
|
|
117
305
|
console.log('šļø Initializing Cosmos DB...');
|
|
118
306
|
// Parse connection string
|
|
@@ -120,7 +308,7 @@ async function initializeCosmosDB(databaseName) {
|
|
|
120
308
|
const keyMatch = connectionString.match(/AccountKey=([^;]+)/);
|
|
121
309
|
if (!endpointMatch || !keyMatch) {
|
|
122
310
|
console.log('ā ļø Invalid CosmosDB connection string format.');
|
|
123
|
-
return;
|
|
311
|
+
return null;
|
|
124
312
|
}
|
|
125
313
|
const endpoint = endpointMatch[1];
|
|
126
314
|
const client = new cosmos_1.CosmosClient({
|
|
@@ -130,62 +318,52 @@ async function initializeCosmosDB(databaseName) {
|
|
|
130
318
|
// Create database if not exists
|
|
131
319
|
const { database } = await client.databases.createIfNotExists({ id: dbName });
|
|
132
320
|
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...`);
|
|
321
|
+
const models = await (0, dev_seeds_1.loadProjectModels)();
|
|
322
|
+
for (const model of models) {
|
|
323
|
+
const containerName = (0, dev_seeds_1.getContainerNameForModel)(model);
|
|
324
|
+
// Try creating container with full partition key definition first
|
|
325
|
+
let containerCreated = false;
|
|
326
|
+
try {
|
|
327
|
+
console.log(`š§ Creating container "${containerName}" with partition key ${model.partitionKey}...`);
|
|
328
|
+
const containerResponse = await database.containers.createIfNotExists({
|
|
329
|
+
id: containerName,
|
|
330
|
+
partitionKey: {
|
|
331
|
+
paths: [model.partitionKey],
|
|
332
|
+
kind: cosmos_1.PartitionKeyKind.Hash,
|
|
333
|
+
version: 2
|
|
164
334
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
// Continue with other containers
|
|
335
|
+
});
|
|
336
|
+
console.log(`ā
Container "${containerName}" ready (status: ${containerResponse.statusCode})`);
|
|
337
|
+
containerCreated = true;
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
341
|
+
console.log(`ā ļø Failed with full partition key definition: ${message}`);
|
|
342
|
+
console.log(`š Retrying with simple partition key...`);
|
|
343
|
+
}
|
|
344
|
+
// If first attempt failed, try with simple partition key definition
|
|
345
|
+
if (!containerCreated) {
|
|
346
|
+
try {
|
|
347
|
+
const containerResponse = await database.containers.createIfNotExists({
|
|
348
|
+
id: containerName,
|
|
349
|
+
partitionKey: {
|
|
350
|
+
paths: [model.partitionKey]
|
|
183
351
|
}
|
|
352
|
+
});
|
|
353
|
+
console.log(`ā
Container "${containerName}" ready (status: ${containerResponse.statusCode})`);
|
|
354
|
+
}
|
|
355
|
+
catch (containerError) {
|
|
356
|
+
console.error(`ā Failed to create container "${containerName}":`, containerError.message);
|
|
357
|
+
console.error(`Error code: ${containerError.code}`);
|
|
358
|
+
if (containerError.body) {
|
|
359
|
+
console.error(`Response body:`, JSON.stringify(containerError.body, null, 2));
|
|
184
360
|
}
|
|
361
|
+
// Continue with other containers
|
|
185
362
|
}
|
|
186
363
|
}
|
|
187
364
|
}
|
|
188
365
|
console.log('ā
Cosmos DB initialization complete\n');
|
|
366
|
+
return { endpoint, key: keyMatch[1], databaseName: dbName, models };
|
|
189
367
|
}
|
|
190
368
|
catch (error) {
|
|
191
369
|
console.error('ā ļø Cosmos DB initialization failed:', error.message);
|
|
@@ -193,16 +371,42 @@ async function initializeCosmosDB(databaseName) {
|
|
|
193
371
|
console.error('Stack trace:', error.stack);
|
|
194
372
|
}
|
|
195
373
|
console.log('š” Make sure Cosmos DB Emulator is running');
|
|
374
|
+
return null;
|
|
196
375
|
}
|
|
197
376
|
}
|
|
198
377
|
async function startDevEnvironment(options) {
|
|
199
378
|
const port = options.port || '3000';
|
|
200
379
|
const functionsPort = options.functionsPort || '7071';
|
|
380
|
+
// Detect package manager from project lockfile
|
|
381
|
+
const pm = (0, package_manager_1.detectFromProject)();
|
|
382
|
+
const pmCmd = (0, package_manager_1.getCommands)(pm);
|
|
383
|
+
const backendLanguage = (0, config_1.getBackendLanguage)();
|
|
201
384
|
// ććć»ć¹ćē®”ēććé
å
|
|
202
385
|
const processes = [];
|
|
386
|
+
let functionsEnv = process.env;
|
|
387
|
+
let mockServer = null;
|
|
388
|
+
let envLocalPath = '';
|
|
389
|
+
let envLocalDefaultUrl = ''; // default Functions URL to restore on shutdown
|
|
203
390
|
// Cleanup processes on Ctrl+C
|
|
204
|
-
process.on('SIGINT', () => {
|
|
391
|
+
process.on('SIGINT', async () => {
|
|
205
392
|
console.log('\nš Stopping development servers...');
|
|
393
|
+
// Restore .env.local to default Functions port on shutdown
|
|
394
|
+
if (envLocalPath && envLocalDefaultUrl) {
|
|
395
|
+
try {
|
|
396
|
+
if (fs.existsSync(envLocalPath)) {
|
|
397
|
+
const content = fs.readFileSync(envLocalPath, 'utf-8');
|
|
398
|
+
if (content.includes('BACKEND_FUNCTIONS_BASE_URL=') &&
|
|
399
|
+
!content.includes(`BACKEND_FUNCTIONS_BASE_URL=${envLocalDefaultUrl}`)) {
|
|
400
|
+
const restored = content.replace(/^BACKEND_FUNCTIONS_BASE_URL=.*/m, `BACKEND_FUNCTIONS_BASE_URL=${envLocalDefaultUrl}`);
|
|
401
|
+
fs.writeFileSync(envLocalPath, restored, 'utf-8');
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
catch { /* ignore */ }
|
|
406
|
+
}
|
|
407
|
+
if (mockServer) {
|
|
408
|
+
await mockServer.stop();
|
|
409
|
+
}
|
|
206
410
|
processes.forEach((proc) => {
|
|
207
411
|
if (proc && !proc.killed) {
|
|
208
412
|
proc.kill();
|
|
@@ -226,8 +430,7 @@ async function startDevEnvironment(options) {
|
|
|
226
430
|
}
|
|
227
431
|
// 2. Check if Azure Functions exists
|
|
228
432
|
const functionsDir = path.join(process.cwd(), 'functions');
|
|
229
|
-
const hasFunctions = fs.existsSync(functionsDir) &&
|
|
230
|
-
fs.existsSync(path.join(functionsDir, 'package.json'));
|
|
433
|
+
const hasFunctions = fs.existsSync(functionsDir) && hasFunctionsProject(functionsDir, backendLanguage);
|
|
231
434
|
if (hasFunctions && !options.noFunctions) {
|
|
232
435
|
// Check if Azure Functions Core Tools is installed
|
|
233
436
|
const coreToolsInstalled = await checkCoreTools();
|
|
@@ -248,7 +451,7 @@ async function startDevEnvironment(options) {
|
|
|
248
451
|
if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
|
|
249
452
|
console.log('š¦ Installing Azure Functions Core Tools...');
|
|
250
453
|
console.log(' This may take a few minutes.');
|
|
251
|
-
const installProcess = (0, child_process_1.spawn)('
|
|
454
|
+
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
455
|
shell: true,
|
|
253
456
|
stdio: 'inherit',
|
|
254
457
|
});
|
|
@@ -261,7 +464,7 @@ async function startDevEnvironment(options) {
|
|
|
261
464
|
else {
|
|
262
465
|
console.error('ā Installation failed.');
|
|
263
466
|
console.log('š” Please install manually:');
|
|
264
|
-
console.log(
|
|
467
|
+
console.log(` ${pmCmd.addGlobal} azure-functions-core-tools@4`);
|
|
265
468
|
reject(new Error(`Installation failed with code ${code}`));
|
|
266
469
|
}
|
|
267
470
|
});
|
|
@@ -272,7 +475,7 @@ async function startDevEnvironment(options) {
|
|
|
272
475
|
console.log('');
|
|
273
476
|
console.log('ā¹ļø Skipping Azure Functions startup.');
|
|
274
477
|
console.log('š” To install later:');
|
|
275
|
-
console.log(
|
|
478
|
+
console.log(` ${pmCmd.addGlobal} azure-functions-core-tools@4`);
|
|
276
479
|
console.log('');
|
|
277
480
|
// Skip Azure Functions startup
|
|
278
481
|
options.noFunctions = true;
|
|
@@ -296,63 +499,88 @@ async function startDevEnvironment(options) {
|
|
|
296
499
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
297
500
|
const appName = packageJson.name || 'App';
|
|
298
501
|
const databaseName = `${appName.charAt(0).toUpperCase() + appName.slice(1)}Database`;
|
|
299
|
-
await initializeCosmosDB(databaseName);
|
|
502
|
+
const cosmosInitialization = await initializeCosmosDB(databaseName);
|
|
503
|
+
if (options.seedEnv && cosmosInitialization) {
|
|
504
|
+
await (0, dev_seeds_1.applyDevSeedEnvironment)({
|
|
505
|
+
client: new cosmos_1.CosmosClient({
|
|
506
|
+
endpoint: cosmosInitialization.endpoint,
|
|
507
|
+
key: cosmosInitialization.key,
|
|
508
|
+
}),
|
|
509
|
+
databaseName: cosmosInitialization.databaseName,
|
|
510
|
+
environment: options.seedEnv,
|
|
511
|
+
models: cosmosInitialization.models,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
300
514
|
console.log('');
|
|
301
515
|
console.log('š Starting Azure Functions...');
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
516
|
+
if (backendLanguage === 'typescript') {
|
|
517
|
+
const functionsNodeModules = path.join(functionsDir, 'node_modules');
|
|
518
|
+
if (!fs.existsSync(functionsNodeModules)) {
|
|
519
|
+
console.log('š¦ Installing Azure Functions dependencies...');
|
|
520
|
+
await runCommand(pm, ['install'], functionsDir, `${pm} install`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
else if (backendLanguage === 'csharp') {
|
|
524
|
+
console.log('š¦ Building C# Azure Functions project...');
|
|
525
|
+
await runCommand('dotnet', ['build'], functionsDir, 'dotnet build');
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
functionsEnv = await preparePythonFunctionsEnvironment(functionsDir);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (hasFunctions && !options.noFunctions) {
|
|
533
|
+
// Build shared package before starting Functions
|
|
534
|
+
const sharedDir = path.join(process.cwd(), 'shared');
|
|
535
|
+
const sharedPkgPath = path.join(sharedDir, 'package.json');
|
|
536
|
+
if (fs.existsSync(sharedDir) && fs.existsSync(sharedPkgPath)) {
|
|
537
|
+
const sharedPkg = JSON.parse(fs.readFileSync(sharedPkgPath, 'utf-8'));
|
|
538
|
+
if (sharedPkg.scripts?.build) {
|
|
539
|
+
console.log('š¦ Building shared package...');
|
|
540
|
+
const filterArgs = pm === 'pnpm'
|
|
541
|
+
? ['run', '--filter', 'shared', 'build']
|
|
542
|
+
: ['run', '--workspace=shared', 'build'];
|
|
543
|
+
const sharedBuild = (0, child_process_1.spawn)(pm, filterArgs, {
|
|
544
|
+
cwd: process.cwd(),
|
|
308
545
|
shell: true,
|
|
309
546
|
stdio: 'inherit',
|
|
310
547
|
});
|
|
311
548
|
await new Promise((resolve, reject) => {
|
|
312
|
-
|
|
549
|
+
sharedBuild.on('close', (code) => {
|
|
313
550
|
if (code === 0) {
|
|
551
|
+
console.log('ā
Shared package built successfully');
|
|
314
552
|
resolve();
|
|
315
553
|
}
|
|
316
554
|
else {
|
|
317
|
-
reject(new Error(`
|
|
555
|
+
reject(new Error(`Shared package build failed with code ${code}`));
|
|
318
556
|
}
|
|
319
557
|
});
|
|
320
|
-
|
|
558
|
+
sharedBuild.on('error', reject);
|
|
321
559
|
});
|
|
322
560
|
}
|
|
561
|
+
else {
|
|
562
|
+
console.log('ā ļø Shared package has no build script ā skipping build. Run "swallowkit add-auth" to fix.');
|
|
563
|
+
}
|
|
323
564
|
}
|
|
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
|
-
});
|
|
565
|
+
// Build TypeScript functions after shared package (functions import from shared)
|
|
566
|
+
if (backendLanguage === 'typescript') {
|
|
567
|
+
const functionsPkgPath = path.join(functionsDir, 'package.json');
|
|
568
|
+
if (fs.existsSync(functionsPkgPath)) {
|
|
569
|
+
const functionsPkg = JSON.parse(fs.readFileSync(functionsPkgPath, 'utf-8'));
|
|
570
|
+
if (functionsPkg.scripts?.build) {
|
|
571
|
+
console.log('š¦ Building TypeScript Azure Functions...');
|
|
572
|
+
await runCommand(pm, ['run', 'build'], functionsDir, `${pm} run build`);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
347
575
|
}
|
|
348
576
|
// Azure Functions ćčµ·å
|
|
349
|
-
const
|
|
350
|
-
const funcProcess = (0, child_process_1.spawn)('npm', ['start'], {
|
|
577
|
+
const funcProcess = (0, child_process_1.spawn)('func', buildFunctionsStartArgs(functionsPort), {
|
|
351
578
|
cwd: functionsDir,
|
|
352
579
|
shell: true,
|
|
353
580
|
stdio: 'pipe', // Always pipe to capture output
|
|
354
|
-
env:
|
|
581
|
+
env: functionsEnv
|
|
355
582
|
});
|
|
583
|
+
let pythonWorkerMissing = false;
|
|
356
584
|
// Functions ć®åŗåććć®ć¾ć¾č”Øē¤ŗļ¼ćć¬ćć£ććÆć¹ä»ćļ¼
|
|
357
585
|
if (funcProcess.stdout) {
|
|
358
586
|
funcProcess.stdout.on('data', (data) => {
|
|
@@ -360,6 +588,10 @@ async function startDevEnvironment(options) {
|
|
|
360
588
|
// åč”ć«ćć¬ćć£ććÆć¹ćä»ćć¦åŗå
|
|
361
589
|
const lines = output.split('\n').filter((line) => line.trim());
|
|
362
590
|
lines.forEach((line) => {
|
|
591
|
+
if (backendLanguage === 'python' &&
|
|
592
|
+
(line.includes('WorkerConfig for runtime: python not found') || line.includes('DefaultWorkerPath:'))) {
|
|
593
|
+
pythonWorkerMissing = true;
|
|
594
|
+
}
|
|
363
595
|
console.log(`[Functions] ${line}`);
|
|
364
596
|
});
|
|
365
597
|
});
|
|
@@ -369,6 +601,10 @@ async function startDevEnvironment(options) {
|
|
|
369
601
|
const output = data.toString();
|
|
370
602
|
const lines = output.split('\n').filter((line) => line.trim());
|
|
371
603
|
lines.forEach((line) => {
|
|
604
|
+
if (backendLanguage === 'python' &&
|
|
605
|
+
(line.includes('WorkerConfig for runtime: python not found') || line.includes('DefaultWorkerPath:'))) {
|
|
606
|
+
pythonWorkerMissing = true;
|
|
607
|
+
}
|
|
372
608
|
console.error(`[Functions Error] ${line}`);
|
|
373
609
|
});
|
|
374
610
|
});
|
|
@@ -377,11 +613,16 @@ async function startDevEnvironment(options) {
|
|
|
377
613
|
funcProcess.on('error', (error) => {
|
|
378
614
|
console.error('ā ļø Azure Functions startup error:', error.message);
|
|
379
615
|
console.log('š” Please ensure Azure Functions Core Tools is installed');
|
|
380
|
-
console.log(
|
|
616
|
+
console.log(` ${pmCmd.addGlobal} azure-functions-core-tools@4`);
|
|
381
617
|
});
|
|
382
618
|
funcProcess.on('close', (code) => {
|
|
383
619
|
if (code !== 0) {
|
|
384
620
|
console.log(`\nā¹ļø Azure Functions exited (exit code: ${code})`);
|
|
621
|
+
if (backendLanguage === 'python' && pythonWorkerMissing) {
|
|
622
|
+
console.log('š” Your Azure Functions Core Tools installation is missing the Python worker for this OS/architecture.');
|
|
623
|
+
console.log(' Reinstall a matching Core Tools v4 package (Windows users should prefer the official x64/x86 MSI for their machine).');
|
|
624
|
+
console.log(' SwallowKit local Python dev uses functions/.venv and requirements.txt, but Core Tools still needs its own bundled Python worker.');
|
|
625
|
+
}
|
|
385
626
|
}
|
|
386
627
|
});
|
|
387
628
|
console.log(`ā
Azure Functions started (port: ${functionsPort})`);
|
|
@@ -396,8 +637,69 @@ async function startDevEnvironment(options) {
|
|
|
396
637
|
}
|
|
397
638
|
console.log('');
|
|
398
639
|
console.log('š Starting Next.js development server...');
|
|
640
|
+
// Mock connector server ā start proxy if --mock-connectors is enabled
|
|
641
|
+
let bffTargetPort = functionsPort;
|
|
642
|
+
if (options.mockConnectors) {
|
|
643
|
+
const allModels = await (0, dev_seeds_1.loadProjectModels)();
|
|
644
|
+
const connectorModels = allModels.filter((m) => m.connectorConfig);
|
|
645
|
+
// Resolve auth config ā auth functions use RDB connectors, mocked alongside other models
|
|
646
|
+
const authConfig = (0, config_1.getAuthConfig)();
|
|
647
|
+
let mockAuthConfig;
|
|
648
|
+
if (authConfig?.provider === 'custom-jwt' && authConfig.customJwt) {
|
|
649
|
+
// Read JWT_SECRET from functions/local.settings.json if available
|
|
650
|
+
let jwtSecret = 'dev-jwt-secret-change-in-production-min-32-chars!!';
|
|
651
|
+
try {
|
|
652
|
+
const localSettingsPath = path.join(process.cwd(), 'functions', 'local.settings.json');
|
|
653
|
+
if (fs.existsSync(localSettingsPath)) {
|
|
654
|
+
const localSettings = JSON.parse(fs.readFileSync(localSettingsPath, 'utf-8'));
|
|
655
|
+
jwtSecret = localSettings.Values?.[authConfig.customJwt.jwtSecretEnv || 'JWT_SECRET'] || jwtSecret;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
catch { /* ignore */ }
|
|
659
|
+
mockAuthConfig = {
|
|
660
|
+
jwtSecret,
|
|
661
|
+
tokenExpiry: authConfig.customJwt.tokenExpiry,
|
|
662
|
+
customJwt: {
|
|
663
|
+
userTable: authConfig.customJwt.userTable,
|
|
664
|
+
loginIdColumn: authConfig.customJwt.loginIdColumn,
|
|
665
|
+
passwordHashColumn: authConfig.customJwt.passwordHashColumn,
|
|
666
|
+
rolesColumn: authConfig.customJwt.rolesColumn,
|
|
667
|
+
},
|
|
668
|
+
defaultPolicy: authConfig.authorization?.defaultPolicy,
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
if (connectorModels.length > 0 || mockAuthConfig) {
|
|
672
|
+
const mockPort = parseInt(functionsPort, 10) + 1;
|
|
673
|
+
mockServer = new connector_mock_server_1.ConnectorMockServer({
|
|
674
|
+
port: mockPort,
|
|
675
|
+
functionsTarget: `${options.host || 'localhost'}:${functionsPort}`,
|
|
676
|
+
connectorModels,
|
|
677
|
+
allModels,
|
|
678
|
+
seedEnv: options.seedEnv,
|
|
679
|
+
host: options.host || 'localhost',
|
|
680
|
+
authConfig: mockAuthConfig,
|
|
681
|
+
});
|
|
682
|
+
await mockServer.start();
|
|
683
|
+
bffTargetPort = String(mockPort);
|
|
684
|
+
const modelCount = connectorModels.length + (mockAuthConfig ? 1 : 0);
|
|
685
|
+
console.log('');
|
|
686
|
+
console.log(`š Mock server started (port: ${mockPort}) ā ${modelCount} model(s) mocked via Zod/seed data`);
|
|
687
|
+
for (const m of connectorModels) {
|
|
688
|
+
const ops = m.connectorConfig.operations.join(', ');
|
|
689
|
+
console.log(` - ${m.name} [${ops}]`);
|
|
690
|
+
}
|
|
691
|
+
if (mockAuthConfig) {
|
|
692
|
+
console.log(` - auth [login, me, logout]`);
|
|
693
|
+
}
|
|
694
|
+
console.log(` Other routes ā proxied to Azure Functions (port: ${functionsPort})`);
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
console.log('');
|
|
698
|
+
console.log('ā¹ļø --mock-connectors specified but no connector models found. Skipping mock server.');
|
|
699
|
+
}
|
|
700
|
+
}
|
|
399
701
|
// 5. Start Next.js development server
|
|
400
|
-
const nextArgs =
|
|
702
|
+
const nextArgs = buildNextDevArgs(pm, port);
|
|
401
703
|
if (options.open) {
|
|
402
704
|
// Next.js 14+ deprecated --open option, so we open browser manually
|
|
403
705
|
setTimeout(() => {
|
|
@@ -408,10 +710,33 @@ async function startDevEnvironment(options) {
|
|
|
408
710
|
(0, child_process_1.spawn)(start, [url], { shell: true });
|
|
409
711
|
}, 3000);
|
|
410
712
|
}
|
|
411
|
-
|
|
713
|
+
// Ensure .env.local points to bffTargetPort so Next.js reads the correct backend URL.
|
|
714
|
+
// When --mock-connectors is active, bffTargetPort = mock port (7072); otherwise = Functions port (7071).
|
|
715
|
+
// Next.js may load .env.local values that override spawn env vars, so we must keep them in sync.
|
|
716
|
+
envLocalPath = path.join(process.cwd(), '.env.local');
|
|
717
|
+
envLocalDefaultUrl = `http://${options.host || 'localhost'}:${functionsPort}`;
|
|
718
|
+
const bffTargetUrl = `http://${options.host || 'localhost'}:${bffTargetPort}`;
|
|
719
|
+
try {
|
|
720
|
+
if (fs.existsSync(envLocalPath)) {
|
|
721
|
+
const envContent = fs.readFileSync(envLocalPath, 'utf-8');
|
|
722
|
+
if (envContent.includes('BACKEND_FUNCTIONS_BASE_URL=') &&
|
|
723
|
+
!envContent.includes(`BACKEND_FUNCTIONS_BASE_URL=${bffTargetUrl}`)) {
|
|
724
|
+
const updated = envContent.replace(/^BACKEND_FUNCTIONS_BASE_URL=.*/m, `BACKEND_FUNCTIONS_BASE_URL=${bffTargetUrl}`);
|
|
725
|
+
fs.writeFileSync(envLocalPath, updated, 'utf-8');
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
catch { /* ignore */ }
|
|
730
|
+
const nextEnv = {
|
|
731
|
+
...process.env,
|
|
732
|
+
BACKEND_FUNCTIONS_BASE_URL: `http://${options.host || 'localhost'}:${bffTargetPort}`,
|
|
733
|
+
FUNCTIONS_BASE_URL: `http://${options.host || 'localhost'}:${bffTargetPort}`,
|
|
734
|
+
};
|
|
735
|
+
const nextProcess = (0, child_process_1.spawn)(pm === 'pnpm' ? 'pnpm' : 'npx', nextArgs, {
|
|
412
736
|
cwd: process.cwd(),
|
|
413
737
|
shell: true,
|
|
414
738
|
stdio: options.verbose ? 'inherit' : 'inherit',
|
|
739
|
+
env: nextEnv,
|
|
415
740
|
});
|
|
416
741
|
processes.push(nextProcess);
|
|
417
742
|
nextProcess.on('error', (error) => {
|
|
@@ -423,6 +748,9 @@ async function startDevEnvironment(options) {
|
|
|
423
748
|
console.log(`\nā¹ļø Next.js exited (exit code: ${code})`);
|
|
424
749
|
}
|
|
425
750
|
// Exit all processes when Next.js exits
|
|
751
|
+
if (mockServer) {
|
|
752
|
+
mockServer.stop().catch(() => { });
|
|
753
|
+
}
|
|
426
754
|
processes.forEach((proc) => {
|
|
427
755
|
if (proc && !proc.killed) {
|
|
428
756
|
proc.kill();
|
|
@@ -437,10 +765,16 @@ async function startDevEnvironment(options) {
|
|
|
437
765
|
if (hasFunctions && !options.noFunctions) {
|
|
438
766
|
console.log(`ā” Azure Functions: http://${options.host || 'localhost'}:${functionsPort}`);
|
|
439
767
|
}
|
|
768
|
+
if (mockServer) {
|
|
769
|
+
console.log(`š Mock Proxy: http://${options.host || 'localhost'}:${bffTargetPort} (BFF ā here)`);
|
|
770
|
+
}
|
|
440
771
|
console.log('');
|
|
441
772
|
if (hasFunctions && !options.noFunctions) {
|
|
442
773
|
console.log('š” Azure Functions and Next.js BFF are connected');
|
|
443
774
|
}
|
|
775
|
+
if (mockServer) {
|
|
776
|
+
console.log('š” Connector models served from mock server (Zod-generated data)');
|
|
777
|
+
}
|
|
444
778
|
console.log('');
|
|
445
779
|
console.log('š Press Ctrl+C to stop');
|
|
446
780
|
console.log('');
|
|
@@ -455,4 +789,29 @@ async function startDevEnvironment(options) {
|
|
|
455
789
|
process.exit(1);
|
|
456
790
|
}
|
|
457
791
|
}
|
|
792
|
+
async function runCommand(command, args, cwd, label, env) {
|
|
793
|
+
await new Promise((resolve, reject) => {
|
|
794
|
+
const child = (0, child_process_1.spawn)(command, args, {
|
|
795
|
+
cwd,
|
|
796
|
+
shell: true,
|
|
797
|
+
stdio: 'inherit',
|
|
798
|
+
env,
|
|
799
|
+
});
|
|
800
|
+
child.on('close', (code) => {
|
|
801
|
+
if (code === 0) {
|
|
802
|
+
resolve();
|
|
803
|
+
}
|
|
804
|
+
else {
|
|
805
|
+
reject(new Error(`${label} failed with code ${code}`));
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
child.on('error', reject);
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
function hasFunctionsProject(functionsDir, backendLanguage) {
|
|
812
|
+
if (backendLanguage === 'typescript') {
|
|
813
|
+
return fs.existsSync(path.join(functionsDir, 'package.json'));
|
|
814
|
+
}
|
|
815
|
+
return fs.existsSync(path.join(functionsDir, 'host.json'));
|
|
816
|
+
}
|
|
458
817
|
//# sourceMappingURL=dev.js.map
|