swallowkit 1.0.0-beta.5 → 1.0.0-beta.51
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 +153 -242
- package/README.md +153 -243
- 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 +13 -0
- package/dist/cli/commands/add-auth.d.ts.map +1 -0
- package/dist/cli/commands/add-auth.js +759 -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 +19 -81
- 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 +37 -0
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +720 -156
- 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 +73 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +3089 -1779
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts +13 -0
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +513 -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 +206 -55
- package/dist/cli/index.js.map +1 -1
- package/dist/core/config.d.ts +14 -3
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +172 -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 +226 -0
- package/dist/core/project/validation.js.map +1 -0
- package/dist/core/scaffold/auth-generator.d.ts +63 -0
- package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
- package/dist/core/scaffold/auth-generator.js +1714 -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 +1029 -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 +922 -220
- 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 +333 -139
- 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 +696 -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 +326 -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 +13 -4
- package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
- package/dist/core/scaffold/ui-generator.js +772 -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 +202 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/types/index.d.ts +95 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/package-manager.d.ts +5 -2
- package/dist/utils/package-manager.d.ts.map +1 -1
- package/dist/utils/package-manager.js +34 -13
- package/dist/utils/package-manager.js.map +1 -1
- 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/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +49 -0
- package/dist/version.js.map +1 -0
- package/package.json +84 -74
- package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +1139 -0
- package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +206 -0
- package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +532 -0
- package/src/__tests__/auth.test.ts +761 -0
- package/src/__tests__/config.test.ts +295 -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 +287 -0
- package/src/__tests__/fixtures.ts +144 -0
- package/src/__tests__/functions-generator.test.ts +237 -0
- package/src/__tests__/init.test.ts +356 -0
- package/src/__tests__/jest-globals.d.ts +8 -0
- package/src/__tests__/machine.test.ts +356 -0
- package/src/__tests__/mcp.test.ts +145 -0
- package/src/__tests__/model-parser.test.ts +52 -0
- package/src/__tests__/multi-auth.test.ts +133 -0
- package/src/__tests__/nextjs-generator.test.ts +105 -0
- package/src/__tests__/openapi-generator.test.ts +43 -0
- package/src/__tests__/package-manager.test.ts +227 -0
- package/src/__tests__/python-uv.test.ts +48 -0
- package/src/__tests__/scaffold.test.ts +191 -0
- package/src/__tests__/string-utils.test.ts +75 -0
- package/src/__tests__/tsconfig.json +15 -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 +815 -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 +1234 -0
- package/src/cli/commands/index.ts +9 -0
- package/src/cli/commands/init.ts +3902 -0
- package/src/cli/commands/provision.ts +193 -0
- package/src/cli/commands/scaffold.ts +1067 -0
- package/src/cli/index.ts +202 -0
- package/src/core/config.ts +379 -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 +245 -0
- package/src/core/scaffold/auth-generator.ts +1760 -0
- package/src/core/scaffold/connector-functions-generator.ts +1133 -0
- package/src/core/scaffold/functions-generator.ts +972 -0
- package/src/core/scaffold/model-parser.ts +847 -0
- package/src/core/scaffold/native-schema-generator.ts +822 -0
- package/src/core/scaffold/nextjs-generator.ts +382 -0
- package/src/core/scaffold/openapi-generator.ts +212 -0
- package/src/core/scaffold/ui-generator.ts +1068 -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 +209 -0
- package/src/types/index.ts +165 -0
- package/src/utils/package-manager.ts +246 -0
- package/src/utils/python-uv.ts +96 -0
- package/src/version.ts +13 -0
- package/dist/cli/commands/build.d.ts +0 -6
- package/dist/cli/commands/build.d.ts.map +0 -1
- package/dist/cli/commands/build.js +0 -177
- package/dist/cli/commands/build.js.map +0 -1
- package/dist/cli/commands/deploy.d.ts +0 -3
- package/dist/cli/commands/deploy.d.ts.map +0 -1
- package/dist/cli/commands/deploy.js +0 -147
- package/dist/cli/commands/deploy.js.map +0 -1
- package/dist/cli/commands/setup.d.ts +0 -6
- package/dist/cli/commands/setup.d.ts.map +0 -1
- package/dist/cli/commands/setup.js +0 -254
- package/dist/cli/commands/setup.js.map +0 -1
package/dist/cli/commands/dev.js
CHANGED
|
@@ -34,20 +34,169 @@ 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.buildSwaStartArgs = buildSwaStartArgs;
|
|
44
|
+
exports.getSwaCliInstallCommand = getSwaCliInstallCommand;
|
|
45
|
+
exports.getFunctionsReadinessTimeoutMs = getFunctionsReadinessTimeoutMs;
|
|
46
|
+
exports.waitForHttpServerReady = waitForHttpServerReady;
|
|
47
|
+
exports.getPythonVirtualEnvPaths = getPythonVirtualEnvPaths;
|
|
48
|
+
exports.getCSharpFunctionsBuildArtifactPaths = getCSharpFunctionsBuildArtifactPaths;
|
|
49
|
+
exports.buildPythonFunctionsEnv = buildPythonFunctionsEnv;
|
|
50
|
+
exports.buildDevCommand = buildDevCommand;
|
|
37
51
|
const commander_1 = require("commander");
|
|
38
52
|
const child_process_1 = require("child_process");
|
|
53
|
+
const http = __importStar(require("http"));
|
|
54
|
+
const https = __importStar(require("https"));
|
|
39
55
|
const path = __importStar(require("path"));
|
|
40
56
|
const fs = __importStar(require("fs"));
|
|
57
|
+
const os = __importStar(require("os"));
|
|
58
|
+
const net = __importStar(require("net"));
|
|
41
59
|
const cosmos_1 = require("@azure/cosmos");
|
|
42
60
|
const config_1 = require("../../core/config");
|
|
61
|
+
const dev_seeds_1 = require("./dev-seeds");
|
|
43
62
|
const package_manager_1 = require("../../utils/package-manager");
|
|
63
|
+
const python_uv_1 = require("../../utils/python-uv");
|
|
64
|
+
const connector_mock_server_1 = require("../../core/mock/connector-mock-server");
|
|
65
|
+
const MINIMUM_CSHARP_CORE_TOOLS_VERSION = '4.6.0';
|
|
66
|
+
const NPM_CORE_TOOLS_PACKAGE = 'azure-functions-core-tools@4';
|
|
67
|
+
function normalizeParsedDevOptions(options) {
|
|
68
|
+
return {
|
|
69
|
+
...options,
|
|
70
|
+
noFunctions: options.noFunctions ?? options.functions === false,
|
|
71
|
+
noSwa: options.noSwa ?? options.swa === false,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function buildFunctionsStartArgs(functionsPort) {
|
|
75
|
+
return ['start', '--port', functionsPort];
|
|
76
|
+
}
|
|
77
|
+
function parseCoreToolsVersion(output) {
|
|
78
|
+
const match = output.match(/\d+\.\d+\.\d+/);
|
|
79
|
+
return match ? match[0] : null;
|
|
80
|
+
}
|
|
81
|
+
function compareVersionNumbers(left, right) {
|
|
82
|
+
const leftParts = left.split('.').map((value) => Number.parseInt(value, 10));
|
|
83
|
+
const rightParts = right.split('.').map((value) => Number.parseInt(value, 10));
|
|
84
|
+
const length = Math.max(leftParts.length, rightParts.length);
|
|
85
|
+
for (let index = 0; index < length; index += 1) {
|
|
86
|
+
const leftPart = leftParts[index] ?? 0;
|
|
87
|
+
const rightPart = rightParts[index] ?? 0;
|
|
88
|
+
if (leftPart !== rightPart) {
|
|
89
|
+
return leftPart - rightPart;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
function buildFunctionsCoreToolsCommand(backendLanguage, installedVersion) {
|
|
95
|
+
if (backendLanguage === 'csharp' &&
|
|
96
|
+
(!installedVersion || compareVersionNumbers(installedVersion, MINIMUM_CSHARP_CORE_TOOLS_VERSION) < 0)) {
|
|
97
|
+
const reason = installedVersion
|
|
98
|
+
? `installed func ${installedVersion} is too old for C# isolated`
|
|
99
|
+
: 'func is not installed';
|
|
100
|
+
return {
|
|
101
|
+
command: 'npm',
|
|
102
|
+
argsPrefix: ['exec', '--yes', NPM_CORE_TOOLS_PACKAGE, '--'],
|
|
103
|
+
label: `npm exec ${NPM_CORE_TOOLS_PACKAGE} (${reason})`,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
command: 'func',
|
|
108
|
+
argsPrefix: [],
|
|
109
|
+
label: installedVersion ? `func ${installedVersion}` : 'func',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function buildNextDevArgs(pm, port) {
|
|
113
|
+
const baseArgs = ['next', 'dev', '--port', port, '--webpack'];
|
|
114
|
+
return pm === 'pnpm' ? ['exec', ...baseArgs] : baseArgs;
|
|
115
|
+
}
|
|
116
|
+
function buildFunctionsBaseUrl(host, functionsPort) {
|
|
117
|
+
return `http://${host || 'localhost'}:${functionsPort}`;
|
|
118
|
+
}
|
|
119
|
+
function buildSwaStartArgs(host, nextPort, swaPort) {
|
|
120
|
+
return [
|
|
121
|
+
'start',
|
|
122
|
+
`http://${host || 'localhost'}:${nextPort}`,
|
|
123
|
+
'--swa-config-location',
|
|
124
|
+
'.',
|
|
125
|
+
'--port',
|
|
126
|
+
swaPort,
|
|
127
|
+
];
|
|
128
|
+
}
|
|
129
|
+
function getSwaCliInstallCommand(pm) {
|
|
130
|
+
return pm === 'pnpm'
|
|
131
|
+
? 'pnpm add -Dw @azure/static-web-apps-cli'
|
|
132
|
+
: 'npm install -D @azure/static-web-apps-cli';
|
|
133
|
+
}
|
|
134
|
+
function getFunctionsReadinessTimeoutMs(backendLanguage) {
|
|
135
|
+
return backendLanguage === 'csharp' ? 90000 : 30000;
|
|
136
|
+
}
|
|
137
|
+
async function waitForHttpServerReady(url, timeoutMs = 30000, intervalMs = 500) {
|
|
138
|
+
const deadline = Date.now() + timeoutMs;
|
|
139
|
+
while (Date.now() <= deadline) {
|
|
140
|
+
if (await probeHttpServer(url)) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
if (Date.now() + intervalMs > deadline) {
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
147
|
+
}
|
|
148
|
+
return probeHttpServer(url);
|
|
149
|
+
}
|
|
150
|
+
function getPythonVirtualEnvPaths(functionsDir) {
|
|
151
|
+
const venvDir = path.join(functionsDir, '.venv');
|
|
152
|
+
const binDir = process.platform === 'win32'
|
|
153
|
+
? path.join(venvDir, 'Scripts')
|
|
154
|
+
: path.join(venvDir, 'bin');
|
|
155
|
+
const pythonExecutable = process.platform === 'win32'
|
|
156
|
+
? path.join(binDir, 'python.exe')
|
|
157
|
+
: path.join(binDir, 'python');
|
|
158
|
+
return { venvDir, binDir, pythonExecutable };
|
|
159
|
+
}
|
|
160
|
+
function getCSharpFunctionsBuildArtifactPaths(functionsDir) {
|
|
161
|
+
return [
|
|
162
|
+
path.join(functionsDir, 'bin'),
|
|
163
|
+
path.join(functionsDir, 'obj'),
|
|
164
|
+
];
|
|
165
|
+
}
|
|
166
|
+
function buildPythonFunctionsEnv(baseEnv, functionsDir) {
|
|
167
|
+
const { venvDir, binDir, pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
|
|
168
|
+
const pathKey = getPathEnvKey(baseEnv);
|
|
169
|
+
const currentPath = baseEnv[pathKey] || '';
|
|
170
|
+
return {
|
|
171
|
+
...baseEnv,
|
|
172
|
+
[pathKey]: currentPath ? `${binDir}${path.delimiter}${currentPath}` : binDir,
|
|
173
|
+
VIRTUAL_ENV: venvDir,
|
|
174
|
+
languageWorkers__python__defaultExecutablePath: pythonExecutable,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function getPathEnvKey(env) {
|
|
178
|
+
return Object.keys(env).find((key) => key.toUpperCase() === 'PATH') || 'PATH';
|
|
179
|
+
}
|
|
180
|
+
function prependToPathEnv(env, entry) {
|
|
181
|
+
const pathKey = getPathEnvKey(env);
|
|
182
|
+
const currentPath = env[pathKey] || '';
|
|
183
|
+
return {
|
|
184
|
+
...env,
|
|
185
|
+
[pathKey]: currentPath ? `${entry}${path.delimiter}${currentPath}` : entry,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
44
188
|
/**
|
|
45
189
|
* Check if Azure Functions Core Tools is installed
|
|
46
190
|
*/
|
|
47
191
|
async function checkCoreTools() {
|
|
192
|
+
return checkCommand('func', ['--version']);
|
|
193
|
+
}
|
|
194
|
+
async function checkCommand(command, args = ['--version'], options) {
|
|
48
195
|
return new Promise((resolve) => {
|
|
49
|
-
const checkProcess = (0, child_process_1.spawn)(
|
|
50
|
-
|
|
196
|
+
const checkProcess = (0, child_process_1.spawn)(command, args, {
|
|
197
|
+
cwd: options?.cwd,
|
|
198
|
+
env: options?.env ?? process.env,
|
|
199
|
+
shell: options?.shell ?? true,
|
|
51
200
|
stdio: 'pipe',
|
|
52
201
|
});
|
|
53
202
|
checkProcess.on('close', (code) => {
|
|
@@ -58,12 +207,173 @@ async function checkCoreTools() {
|
|
|
58
207
|
});
|
|
59
208
|
});
|
|
60
209
|
}
|
|
210
|
+
async function resolveProjectLocalUvCommand(projectRoot) {
|
|
211
|
+
const uvEnv = (0, python_uv_1.buildProjectLocalUvEnv)(process.env, projectRoot);
|
|
212
|
+
const { localUvExecutable } = (0, python_uv_1.getProjectLocalUvPaths)(projectRoot);
|
|
213
|
+
if (await checkCommand('uv', ['--version'], { shell: false })) {
|
|
214
|
+
return { command: 'uv', env: uvEnv };
|
|
215
|
+
}
|
|
216
|
+
if (fs.existsSync(localUvExecutable) && await checkCommand(localUvExecutable, ['--version'], { shell: false })) {
|
|
217
|
+
return { command: localUvExecutable, env: uvEnv };
|
|
218
|
+
}
|
|
219
|
+
console.log('📦 Installing project-local uv...');
|
|
220
|
+
const installer = (0, python_uv_1.getProjectLocalUvInstallerCommand)();
|
|
221
|
+
await runCommand(installer.command, installer.args, projectRoot, 'uv installation', (0, python_uv_1.buildProjectLocalUvInstallerEnv)(process.env, projectRoot), false);
|
|
222
|
+
if (!(fs.existsSync(localUvExecutable) && await checkCommand(localUvExecutable, ['--version'], { shell: false }))) {
|
|
223
|
+
throw new Error('Failed to install project-local uv.');
|
|
224
|
+
}
|
|
225
|
+
return { command: localUvExecutable, env: uvEnv };
|
|
226
|
+
}
|
|
227
|
+
async function getCommandPath(command) {
|
|
228
|
+
const locator = process.platform === 'win32' ? 'where' : 'which';
|
|
229
|
+
const result = await captureCommandOutput(locator, [command]);
|
|
230
|
+
const firstLine = result
|
|
231
|
+
.split(/\r?\n/)
|
|
232
|
+
.map((line) => line.trim())
|
|
233
|
+
.find(Boolean);
|
|
234
|
+
return firstLine || null;
|
|
235
|
+
}
|
|
236
|
+
async function resolveInstalledCoreToolsVersion() {
|
|
237
|
+
if (!(await checkCoreTools())) {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
return parseCoreToolsVersion(await captureCommandOutput('func', ['--version']));
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
async function captureCommandOutput(command, args, cwd, env) {
|
|
248
|
+
return new Promise((resolve, reject) => {
|
|
249
|
+
const child = (0, child_process_1.spawn)(command, args, {
|
|
250
|
+
cwd,
|
|
251
|
+
env,
|
|
252
|
+
shell: false,
|
|
253
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
254
|
+
});
|
|
255
|
+
let stdout = '';
|
|
256
|
+
let stderr = '';
|
|
257
|
+
child.stdout?.on('data', (data) => {
|
|
258
|
+
stdout += data.toString();
|
|
259
|
+
});
|
|
260
|
+
child.stderr?.on('data', (data) => {
|
|
261
|
+
stderr += data.toString();
|
|
262
|
+
});
|
|
263
|
+
child.on('close', (code) => {
|
|
264
|
+
if (code === 0) {
|
|
265
|
+
resolve(stdout);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
reject(new Error(stderr || stdout || `${command} exited with code ${code}`));
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
child.on('error', reject);
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
async function probeHttpServer(url) {
|
|
275
|
+
return new Promise((resolve) => {
|
|
276
|
+
const target = new URL(url);
|
|
277
|
+
const requestFactory = target.protocol === 'https:' ? https.request : http.request;
|
|
278
|
+
let settled = false;
|
|
279
|
+
const finish = (value) => {
|
|
280
|
+
if (!settled) {
|
|
281
|
+
settled = true;
|
|
282
|
+
resolve(value);
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
const request = requestFactory({
|
|
286
|
+
hostname: target.hostname,
|
|
287
|
+
port: target.port,
|
|
288
|
+
path: target.pathname || '/',
|
|
289
|
+
method: 'GET',
|
|
290
|
+
timeout: 1000,
|
|
291
|
+
}, (response) => {
|
|
292
|
+
response.resume();
|
|
293
|
+
finish(true);
|
|
294
|
+
});
|
|
295
|
+
request.on('timeout', () => {
|
|
296
|
+
request.destroy();
|
|
297
|
+
finish(false);
|
|
298
|
+
});
|
|
299
|
+
request.on('error', () => finish(false));
|
|
300
|
+
request.end();
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
async function resolvePythonRuntimeDetails(functionsDir, env) {
|
|
304
|
+
const { pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
|
|
305
|
+
const output = await captureCommandOutput(pythonExecutable, [
|
|
306
|
+
'-c',
|
|
307
|
+
'import platform; import sys; print(str(sys.version_info.major) + "." + str(sys.version_info.minor)); print(platform.machine())',
|
|
308
|
+
], functionsDir, env);
|
|
309
|
+
const [version, architecture] = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
310
|
+
if (!version || !architecture) {
|
|
311
|
+
throw new Error('Failed to determine Python runtime details.');
|
|
312
|
+
}
|
|
313
|
+
return { version, architecture };
|
|
314
|
+
}
|
|
315
|
+
async function bridgePythonCoreToolsForWindowsArm64(functionsDir, env) {
|
|
316
|
+
if (process.platform !== 'win32' || process.arch !== 'arm64') {
|
|
317
|
+
return env;
|
|
318
|
+
}
|
|
319
|
+
const funcPath = await getCommandPath('func');
|
|
320
|
+
if (!funcPath) {
|
|
321
|
+
return env;
|
|
322
|
+
}
|
|
323
|
+
const { version, architecture } = await resolvePythonRuntimeDetails(functionsDir, env);
|
|
324
|
+
if (architecture.toUpperCase() !== 'AMD64') {
|
|
325
|
+
return env;
|
|
326
|
+
}
|
|
327
|
+
const coreToolsRoot = path.dirname(funcPath);
|
|
328
|
+
const armWorkerDir = path.join(coreToolsRoot, 'workers', 'python', version, 'WINDOWS', 'Arm64');
|
|
329
|
+
if (fs.existsSync(armWorkerDir)) {
|
|
330
|
+
return env;
|
|
331
|
+
}
|
|
332
|
+
const x64WorkerDir = path.join(coreToolsRoot, 'workers', 'python', version, 'WINDOWS', 'X64');
|
|
333
|
+
if (!fs.existsSync(x64WorkerDir)) {
|
|
334
|
+
return env;
|
|
335
|
+
}
|
|
336
|
+
const patchedRoot = path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'), 'SwallowKit', 'azure-functions-core-tools-python-bridge');
|
|
337
|
+
if (!fs.existsSync(path.join(patchedRoot, 'func.exe'))) {
|
|
338
|
+
console.log('🩹 Creating a local Azure Functions Core Tools bridge for Windows Arm64 Python...');
|
|
339
|
+
fs.mkdirSync(path.dirname(patchedRoot), { recursive: true });
|
|
340
|
+
fs.cpSync(coreToolsRoot, patchedRoot, { recursive: true });
|
|
341
|
+
}
|
|
342
|
+
const patchedArmWorkerDir = path.join(patchedRoot, 'workers', 'python', version, 'WINDOWS', 'Arm64');
|
|
343
|
+
const patchedX64WorkerDir = path.join(patchedRoot, 'workers', 'python', version, 'WINDOWS', 'X64');
|
|
344
|
+
if (!fs.existsSync(patchedArmWorkerDir) && fs.existsSync(patchedX64WorkerDir)) {
|
|
345
|
+
fs.cpSync(patchedX64WorkerDir, patchedArmWorkerDir, { recursive: true });
|
|
346
|
+
}
|
|
347
|
+
console.log(`🩹 Using bridged Azure Functions Core Tools from ${patchedRoot}`);
|
|
348
|
+
return prependToPathEnv(env, patchedRoot);
|
|
349
|
+
}
|
|
350
|
+
async function preparePythonFunctionsEnvironment(functionsDir) {
|
|
351
|
+
const projectRoot = (0, python_uv_1.getPythonProjectRoot)(functionsDir);
|
|
352
|
+
const { command: uvCommand, env: uvEnv } = await resolveProjectLocalUvCommand(projectRoot);
|
|
353
|
+
const { venvDir, pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
|
|
354
|
+
const hasUsableVirtualEnv = fs.existsSync(pythonExecutable) && await checkCommand(pythonExecutable, ['--version'], {
|
|
355
|
+
cwd: functionsDir,
|
|
356
|
+
env: uvEnv,
|
|
357
|
+
shell: false,
|
|
358
|
+
});
|
|
359
|
+
if (!hasUsableVirtualEnv) {
|
|
360
|
+
const venvArgs = (0, python_uv_1.buildUvVenvArgs)('.venv');
|
|
361
|
+
if (fs.existsSync(venvDir)) {
|
|
362
|
+
venvArgs.push('--clear');
|
|
363
|
+
}
|
|
364
|
+
console.log('📦 Creating Python virtual environment with uv...');
|
|
365
|
+
await runCommand(uvCommand, venvArgs, functionsDir, 'python virtual environment setup', uvEnv, false);
|
|
366
|
+
}
|
|
367
|
+
console.log('📦 Installing Python Azure Functions dependencies with uv...');
|
|
368
|
+
await runCommand(uvCommand, (0, python_uv_1.buildUvPipInstallArgs)(pythonExecutable, 'requirements.txt'), functionsDir, 'python dependency installation', uvEnv, false);
|
|
369
|
+
const pythonEnv = buildPythonFunctionsEnv(uvEnv, functionsDir);
|
|
370
|
+
return bridgePythonCoreToolsForWindowsArm64(functionsDir, pythonEnv);
|
|
371
|
+
}
|
|
61
372
|
/**
|
|
62
373
|
* Check if Cosmos DB Emulator is running by checking if port 8081 is open
|
|
63
374
|
*/
|
|
64
375
|
async function checkCosmosDBEmulator() {
|
|
65
376
|
return new Promise((resolve) => {
|
|
66
|
-
const net = require('net');
|
|
67
377
|
const socket = new net.Socket();
|
|
68
378
|
const timeout = setTimeout(() => {
|
|
69
379
|
socket.destroy();
|
|
@@ -81,112 +391,102 @@ async function checkCosmosDBEmulator() {
|
|
|
81
391
|
socket.connect(8081, 'localhost');
|
|
82
392
|
});
|
|
83
393
|
}
|
|
84
|
-
|
|
85
|
-
.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
394
|
+
function buildDevCommand(runDevEnvironment = startDevEnvironment, verifyProject = config_1.ensureSwallowKitProject) {
|
|
395
|
+
return new commander_1.Command()
|
|
396
|
+
.name('dev')
|
|
397
|
+
.description('Start SwallowKit development server (Cosmos DB + Next.js + Azure Functions)')
|
|
398
|
+
.option('-p, --port <port>', 'Next.js port', '3000')
|
|
399
|
+
.option('-f, --functions-port <port>', 'Azure Functions port', '7071')
|
|
400
|
+
.option('--host <host>', 'Host name', 'localhost')
|
|
401
|
+
.option('--open', 'Open browser automatically', false)
|
|
402
|
+
.option('--verbose', 'Show verbose logs', false)
|
|
403
|
+
.option('--no-functions', 'Skip Azure Functions startup')
|
|
404
|
+
.option('--seed-env <environment>', 'Replace Cosmos DB Emulator data from dev-seeds/<environment> before startup')
|
|
405
|
+
.option('--mock-connectors', 'Start mock server for connector models (serves Zod-generated data)', false)
|
|
406
|
+
.option('--swa-port <port>', 'SWA authentication emulator port', '4280')
|
|
407
|
+
.option('--no-swa', 'Skip the SWA authentication emulator')
|
|
408
|
+
.action(async (options) => {
|
|
409
|
+
const normalizedOptions = normalizeParsedDevOptions(options);
|
|
410
|
+
// SwallowKit プロジェクトディレクトリかどうかを検証
|
|
411
|
+
verifyProject("dev");
|
|
412
|
+
console.log('🚀 Starting SwallowKit development environment...');
|
|
413
|
+
if (normalizedOptions.verbose) {
|
|
414
|
+
console.log('⚙️ Options:', normalizedOptions);
|
|
415
|
+
}
|
|
416
|
+
await runDevEnvironment(normalizedOptions);
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
exports.devCommand = buildDevCommand();
|
|
102
420
|
async function initializeCosmosDB(databaseName) {
|
|
103
421
|
try {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return;
|
|
422
|
+
const connectionInfoResult = (0, dev_seeds_1.resolveLocalCosmosConnectionInfo)(databaseName);
|
|
423
|
+
if (!connectionInfoResult.ok) {
|
|
424
|
+
if (connectionInfoResult.reason === 'missing-local-settings') {
|
|
425
|
+
console.log('⚠️ local.settings.json not found. Skipping Cosmos DB initialization.');
|
|
426
|
+
}
|
|
427
|
+
else if (connectionInfoResult.reason === 'missing-connection-string') {
|
|
428
|
+
console.log('⚠️ CosmosDBConnection not found in local.settings.json. Skipping Cosmos DB initialization.');
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
console.log('⚠️ Invalid CosmosDB connection string format.');
|
|
432
|
+
}
|
|
433
|
+
return null;
|
|
117
434
|
}
|
|
118
435
|
console.log('🗄️ Initializing Cosmos DB...');
|
|
119
|
-
|
|
120
|
-
const endpointMatch = connectionString.match(/AccountEndpoint=([^;]+)/);
|
|
121
|
-
const keyMatch = connectionString.match(/AccountKey=([^;]+)/);
|
|
122
|
-
if (!endpointMatch || !keyMatch) {
|
|
123
|
-
console.log('⚠️ Invalid CosmosDB connection string format.');
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
const endpoint = endpointMatch[1];
|
|
436
|
+
const { endpoint, key, databaseName: dbName } = connectionInfoResult.value;
|
|
127
437
|
const client = new cosmos_1.CosmosClient({
|
|
128
438
|
endpoint: endpoint,
|
|
129
|
-
key
|
|
439
|
+
key,
|
|
130
440
|
});
|
|
131
441
|
// Create database if not exists
|
|
132
442
|
const { database } = await client.databases.createIfNotExists({ id: dbName });
|
|
133
443
|
console.log(`✅ Database "${dbName}" ready`);
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
// Try creating container with full partition key definition first
|
|
148
|
-
let containerCreated = false;
|
|
149
|
-
try {
|
|
150
|
-
console.log(`🔧 Creating container "${containerName}" with partition key /id...`);
|
|
151
|
-
const containerResponse = await database.containers.createIfNotExists({
|
|
152
|
-
id: containerName,
|
|
153
|
-
partitionKey: {
|
|
154
|
-
paths: ['/id'],
|
|
155
|
-
kind: cosmos_1.PartitionKeyKind.Hash,
|
|
156
|
-
version: 2
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
console.log(`✅ Container "${containerName}" ready (status: ${containerResponse.statusCode})`);
|
|
160
|
-
containerCreated = true;
|
|
161
|
-
}
|
|
162
|
-
catch (error) {
|
|
163
|
-
console.log(`⚠️ Failed with full partition key definition: ${error.message}`);
|
|
164
|
-
console.log(`🔄 Retrying with simple partition key...`);
|
|
444
|
+
const models = await (0, dev_seeds_1.loadProjectModels)();
|
|
445
|
+
for (const model of models) {
|
|
446
|
+
const containerName = (0, dev_seeds_1.getContainerNameForModel)(model);
|
|
447
|
+
// Try creating container with full partition key definition first
|
|
448
|
+
let containerCreated = false;
|
|
449
|
+
try {
|
|
450
|
+
console.log(`🔧 Creating container "${containerName}" with partition key ${model.partitionKey}...`);
|
|
451
|
+
const containerResponse = await database.containers.createIfNotExists({
|
|
452
|
+
id: containerName,
|
|
453
|
+
partitionKey: {
|
|
454
|
+
paths: [model.partitionKey],
|
|
455
|
+
kind: cosmos_1.PartitionKeyKind.Hash,
|
|
456
|
+
version: 2
|
|
165
457
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
// Continue with other containers
|
|
458
|
+
});
|
|
459
|
+
console.log(`✅ Container "${containerName}" ready (status: ${containerResponse.statusCode})`);
|
|
460
|
+
containerCreated = true;
|
|
461
|
+
}
|
|
462
|
+
catch (error) {
|
|
463
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
464
|
+
console.log(`⚠️ Failed with full partition key definition: ${message}`);
|
|
465
|
+
console.log(`🔄 Retrying with simple partition key...`);
|
|
466
|
+
}
|
|
467
|
+
// If first attempt failed, try with simple partition key definition
|
|
468
|
+
if (!containerCreated) {
|
|
469
|
+
try {
|
|
470
|
+
const containerResponse = await database.containers.createIfNotExists({
|
|
471
|
+
id: containerName,
|
|
472
|
+
partitionKey: {
|
|
473
|
+
paths: [model.partitionKey]
|
|
184
474
|
}
|
|
475
|
+
});
|
|
476
|
+
console.log(`✅ Container "${containerName}" ready (status: ${containerResponse.statusCode})`);
|
|
477
|
+
}
|
|
478
|
+
catch (containerError) {
|
|
479
|
+
console.error(`❌ Failed to create container "${containerName}":`, containerError.message);
|
|
480
|
+
console.error(`Error code: ${containerError.code}`);
|
|
481
|
+
if (containerError.body) {
|
|
482
|
+
console.error(`Response body:`, JSON.stringify(containerError.body, null, 2));
|
|
185
483
|
}
|
|
484
|
+
// Continue with other containers
|
|
186
485
|
}
|
|
187
486
|
}
|
|
188
487
|
}
|
|
189
488
|
console.log('✅ Cosmos DB initialization complete\n');
|
|
489
|
+
return { endpoint, key, databaseName: dbName, models };
|
|
190
490
|
}
|
|
191
491
|
catch (error) {
|
|
192
492
|
console.error('⚠️ Cosmos DB initialization failed:', error.message);
|
|
@@ -194,19 +494,58 @@ async function initializeCosmosDB(databaseName) {
|
|
|
194
494
|
console.error('Stack trace:', error.stack);
|
|
195
495
|
}
|
|
196
496
|
console.log('💡 Make sure Cosmos DB Emulator is running');
|
|
497
|
+
return null;
|
|
197
498
|
}
|
|
198
499
|
}
|
|
199
500
|
async function startDevEnvironment(options) {
|
|
200
501
|
const port = options.port || '3000';
|
|
201
502
|
const functionsPort = options.functionsPort || '7071';
|
|
503
|
+
const swaPort = options.swaPort || '4280';
|
|
202
504
|
// Detect package manager from project lockfile
|
|
203
505
|
const pm = (0, package_manager_1.detectFromProject)();
|
|
204
506
|
const pmCmd = (0, package_manager_1.getCommands)(pm);
|
|
507
|
+
const backendLanguage = (0, config_1.getBackendLanguage)();
|
|
508
|
+
const authConfig = (0, config_1.getAuthConfig)();
|
|
509
|
+
const normalizedAuth = (0, config_1.normalizeAuthConfig)(authConfig);
|
|
510
|
+
const useSwaEmulator = Boolean(normalizedAuth && Object.values(normalizedAuth.schemes).some(scheme => scheme.provider === 'swa')) && !options.noSwa;
|
|
511
|
+
const swaCommand = useSwaEmulator ? await resolveSwaCliCommand(process.cwd()) : null;
|
|
512
|
+
if (useSwaEmulator && !swaCommand) {
|
|
513
|
+
const installCommand = getSwaCliInstallCommand(pm);
|
|
514
|
+
console.error('❌ SWA authentication requires Azure Static Web Apps CLI for local development.');
|
|
515
|
+
console.error(' Install it in this project and retry:');
|
|
516
|
+
console.error(`\n ${installCommand}\n`);
|
|
517
|
+
process.exitCode = 1;
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
205
520
|
// プロセスを管理する配列
|
|
206
521
|
const processes = [];
|
|
522
|
+
let functionsEnv = process.env;
|
|
523
|
+
let mockServer = null;
|
|
524
|
+
let envLocalPath = '';
|
|
525
|
+
let envLocalDefaultUrl = ''; // default Functions URL to restore on shutdown
|
|
526
|
+
const functionsBaseUrl = buildFunctionsBaseUrl(options.host, functionsPort);
|
|
527
|
+
let functionsReadinessPromise = null;
|
|
528
|
+
let functionsReady = !!options.noFunctions;
|
|
207
529
|
// Cleanup processes on Ctrl+C
|
|
208
|
-
process.on('SIGINT', () => {
|
|
530
|
+
process.on('SIGINT', async () => {
|
|
209
531
|
console.log('\n🛑 Stopping development servers...');
|
|
532
|
+
// Restore .env.local to default Functions port on shutdown
|
|
533
|
+
if (envLocalPath && envLocalDefaultUrl) {
|
|
534
|
+
try {
|
|
535
|
+
if (fs.existsSync(envLocalPath)) {
|
|
536
|
+
const content = fs.readFileSync(envLocalPath, 'utf-8');
|
|
537
|
+
if (content.includes('BACKEND_FUNCTIONS_BASE_URL=') &&
|
|
538
|
+
!content.includes(`BACKEND_FUNCTIONS_BASE_URL=${envLocalDefaultUrl}`)) {
|
|
539
|
+
const restored = content.replace(/^BACKEND_FUNCTIONS_BASE_URL=.*/m, `BACKEND_FUNCTIONS_BASE_URL=${envLocalDefaultUrl}`);
|
|
540
|
+
fs.writeFileSync(envLocalPath, restored, 'utf-8');
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
catch { /* ignore */ }
|
|
545
|
+
}
|
|
546
|
+
if (mockServer) {
|
|
547
|
+
await mockServer.stop();
|
|
548
|
+
}
|
|
210
549
|
processes.forEach((proc) => {
|
|
211
550
|
if (proc && !proc.killed) {
|
|
212
551
|
proc.kill();
|
|
@@ -230,12 +569,13 @@ async function startDevEnvironment(options) {
|
|
|
230
569
|
}
|
|
231
570
|
// 2. Check if Azure Functions exists
|
|
232
571
|
const functionsDir = path.join(process.cwd(), 'functions');
|
|
233
|
-
const hasFunctions = fs.existsSync(functionsDir) &&
|
|
234
|
-
|
|
572
|
+
const hasFunctions = fs.existsSync(functionsDir) && hasFunctionsProject(functionsDir, backendLanguage);
|
|
573
|
+
let functionsCoreToolsCommand = null;
|
|
574
|
+
let installedCoreToolsVersion = null;
|
|
235
575
|
if (hasFunctions && !options.noFunctions) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if (!
|
|
576
|
+
installedCoreToolsVersion = await resolveInstalledCoreToolsVersion();
|
|
577
|
+
functionsCoreToolsCommand = buildFunctionsCoreToolsCommand(backendLanguage, installedCoreToolsVersion);
|
|
578
|
+
if (functionsCoreToolsCommand.command === 'func' && !installedCoreToolsVersion) {
|
|
239
579
|
console.log('');
|
|
240
580
|
console.log('⚠️ Azure Functions Core Tools not found.');
|
|
241
581
|
console.log('');
|
|
@@ -282,6 +622,9 @@ async function startDevEnvironment(options) {
|
|
|
282
622
|
options.noFunctions = true;
|
|
283
623
|
}
|
|
284
624
|
}
|
|
625
|
+
else if (functionsCoreToolsCommand.command !== 'func') {
|
|
626
|
+
console.log(`ℹ️ Using ${functionsCoreToolsCommand.label}.`);
|
|
627
|
+
}
|
|
285
628
|
if (!options.noFunctions) {
|
|
286
629
|
// Check if Cosmos DB Emulator is running
|
|
287
630
|
const cosmosRunning = await checkCosmosDBEmulator();
|
|
@@ -297,69 +640,89 @@ async function startDevEnvironment(options) {
|
|
|
297
640
|
process.exit(1);
|
|
298
641
|
}
|
|
299
642
|
// Initialize Cosmos DB before starting Functions
|
|
300
|
-
const
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
643
|
+
const databaseName = (0, dev_seeds_1.getDefaultCosmosDatabaseName)();
|
|
644
|
+
const cosmosInitialization = await initializeCosmosDB(databaseName);
|
|
645
|
+
if (options.seedEnv && cosmosInitialization) {
|
|
646
|
+
await (0, dev_seeds_1.applyDevSeedEnvironment)({
|
|
647
|
+
client: new cosmos_1.CosmosClient({
|
|
648
|
+
endpoint: cosmosInitialization.endpoint,
|
|
649
|
+
key: cosmosInitialization.key,
|
|
650
|
+
}),
|
|
651
|
+
databaseName: cosmosInitialization.databaseName,
|
|
652
|
+
environment: options.seedEnv,
|
|
653
|
+
models: cosmosInitialization.models,
|
|
654
|
+
});
|
|
655
|
+
}
|
|
304
656
|
console.log('');
|
|
305
657
|
console.log('🚀 Starting Azure Functions...');
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
658
|
+
if (backendLanguage === 'typescript') {
|
|
659
|
+
const functionsNodeModules = path.join(functionsDir, 'node_modules');
|
|
660
|
+
if (!fs.existsSync(functionsNodeModules)) {
|
|
661
|
+
console.log('📦 Installing Azure Functions dependencies...');
|
|
662
|
+
await runCommand(pm, ['install'], functionsDir, `${pm} install`);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
else if (backendLanguage === 'csharp') {
|
|
666
|
+
console.log('ℹ️ C# Azure Functions can take longer on cold start while the worker builds.');
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
functionsEnv = await preparePythonFunctionsEnvironment(functionsDir);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (hasFunctions && !options.noFunctions) {
|
|
674
|
+
// Build shared package before starting Functions
|
|
675
|
+
const sharedDir = path.join(process.cwd(), 'shared');
|
|
676
|
+
const sharedPkgPath = path.join(sharedDir, 'package.json');
|
|
677
|
+
if (fs.existsSync(sharedDir) && fs.existsSync(sharedPkgPath)) {
|
|
678
|
+
const sharedPkg = JSON.parse(fs.readFileSync(sharedPkgPath, 'utf-8'));
|
|
679
|
+
if (sharedPkg.scripts?.build) {
|
|
680
|
+
console.log('📦 Building shared package...');
|
|
681
|
+
const filterArgs = pm === 'pnpm'
|
|
682
|
+
? ['run', '--filter', 'shared', 'build']
|
|
683
|
+
: ['run', '--workspace=shared', 'build'];
|
|
684
|
+
const sharedBuild = (0, child_process_1.spawn)(pm, filterArgs, {
|
|
685
|
+
cwd: process.cwd(),
|
|
312
686
|
shell: true,
|
|
313
687
|
stdio: 'inherit',
|
|
314
688
|
});
|
|
315
689
|
await new Promise((resolve, reject) => {
|
|
316
|
-
|
|
690
|
+
sharedBuild.on('close', (code) => {
|
|
317
691
|
if (code === 0) {
|
|
692
|
+
console.log('✅ Shared package built successfully');
|
|
318
693
|
resolve();
|
|
319
694
|
}
|
|
320
695
|
else {
|
|
321
|
-
reject(new Error(
|
|
696
|
+
reject(new Error(`Shared package build failed with code ${code}`));
|
|
322
697
|
}
|
|
323
698
|
});
|
|
324
|
-
|
|
699
|
+
sharedBuild.on('error', reject);
|
|
325
700
|
});
|
|
326
701
|
}
|
|
702
|
+
else {
|
|
703
|
+
console.log('⚠️ Shared package has no build script — skipping build. Run "swallowkit add-auth" to fix.');
|
|
704
|
+
}
|
|
327
705
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
cwd: process.cwd(),
|
|
339
|
-
shell: true,
|
|
340
|
-
stdio: 'inherit',
|
|
341
|
-
});
|
|
342
|
-
await new Promise((resolve, reject) => {
|
|
343
|
-
sharedBuild.on('close', (code) => {
|
|
344
|
-
if (code === 0) {
|
|
345
|
-
console.log('✅ Shared package built successfully');
|
|
346
|
-
resolve();
|
|
347
|
-
}
|
|
348
|
-
else {
|
|
349
|
-
reject(new Error(`Shared package build failed with code ${code}`));
|
|
350
|
-
}
|
|
351
|
-
});
|
|
352
|
-
sharedBuild.on('error', reject);
|
|
353
|
-
});
|
|
706
|
+
// Build TypeScript functions after shared package (functions import from shared)
|
|
707
|
+
if (backendLanguage === 'typescript') {
|
|
708
|
+
const functionsPkgPath = path.join(functionsDir, 'package.json');
|
|
709
|
+
if (fs.existsSync(functionsPkgPath)) {
|
|
710
|
+
const functionsPkg = JSON.parse(fs.readFileSync(functionsPkgPath, 'utf-8'));
|
|
711
|
+
if (functionsPkg.scripts?.build) {
|
|
712
|
+
console.log('📦 Building TypeScript Azure Functions...');
|
|
713
|
+
await runCommand(pm, ['run', 'build'], functionsDir, `${pm} run build`);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
354
716
|
}
|
|
355
717
|
// Azure Functions を起動
|
|
356
|
-
const
|
|
357
|
-
const funcProcess = (0, child_process_1.spawn)(
|
|
718
|
+
const functionsCommand = functionsCoreToolsCommand ?? buildFunctionsCoreToolsCommand(backendLanguage, installedCoreToolsVersion);
|
|
719
|
+
const funcProcess = (0, child_process_1.spawn)(functionsCommand.command, [...functionsCommand.argsPrefix, ...buildFunctionsStartArgs(functionsPort)], {
|
|
358
720
|
cwd: functionsDir,
|
|
359
721
|
shell: true,
|
|
360
722
|
stdio: 'pipe', // Always pipe to capture output
|
|
361
|
-
env:
|
|
723
|
+
env: functionsEnv
|
|
362
724
|
});
|
|
725
|
+
let pythonWorkerMissing = false;
|
|
363
726
|
// Functions の出力をそのまま表示(プレフィックス付き)
|
|
364
727
|
if (funcProcess.stdout) {
|
|
365
728
|
funcProcess.stdout.on('data', (data) => {
|
|
@@ -367,6 +730,10 @@ async function startDevEnvironment(options) {
|
|
|
367
730
|
// 各行にプレフィックスを付けて出力
|
|
368
731
|
const lines = output.split('\n').filter((line) => line.trim());
|
|
369
732
|
lines.forEach((line) => {
|
|
733
|
+
if (backendLanguage === 'python' &&
|
|
734
|
+
(line.includes('WorkerConfig for runtime: python not found') || line.includes('DefaultWorkerPath:'))) {
|
|
735
|
+
pythonWorkerMissing = true;
|
|
736
|
+
}
|
|
370
737
|
console.log(`[Functions] ${line}`);
|
|
371
738
|
});
|
|
372
739
|
});
|
|
@@ -376,6 +743,10 @@ async function startDevEnvironment(options) {
|
|
|
376
743
|
const output = data.toString();
|
|
377
744
|
const lines = output.split('\n').filter((line) => line.trim());
|
|
378
745
|
lines.forEach((line) => {
|
|
746
|
+
if (backendLanguage === 'python' &&
|
|
747
|
+
(line.includes('WorkerConfig for runtime: python not found') || line.includes('DefaultWorkerPath:'))) {
|
|
748
|
+
pythonWorkerMissing = true;
|
|
749
|
+
}
|
|
379
750
|
console.error(`[Functions Error] ${line}`);
|
|
380
751
|
});
|
|
381
752
|
});
|
|
@@ -389,9 +760,15 @@ async function startDevEnvironment(options) {
|
|
|
389
760
|
funcProcess.on('close', (code) => {
|
|
390
761
|
if (code !== 0) {
|
|
391
762
|
console.log(`\n⏹️ Azure Functions exited (exit code: ${code})`);
|
|
763
|
+
if (backendLanguage === 'python' && pythonWorkerMissing) {
|
|
764
|
+
console.log('💡 Your Azure Functions Core Tools installation is missing the Python worker for this OS/architecture.');
|
|
765
|
+
console.log(' Reinstall a matching Core Tools v4 package (Windows users should prefer the official x64/x86 MSI for their machine).');
|
|
766
|
+
console.log(' SwallowKit local Python dev uses functions/.venv and requirements.txt, but Core Tools still needs its own bundled Python worker.');
|
|
767
|
+
}
|
|
392
768
|
}
|
|
393
769
|
});
|
|
394
|
-
console.log(
|
|
770
|
+
console.log(`⏳ Waiting for Azure Functions to accept requests at ${functionsBaseUrl}...`);
|
|
771
|
+
functionsReadinessPromise = waitForHttpServerReady(functionsBaseUrl, getFunctionsReadinessTimeoutMs(backendLanguage));
|
|
395
772
|
}
|
|
396
773
|
else if (!hasFunctions) {
|
|
397
774
|
console.log('');
|
|
@@ -403,24 +780,97 @@ async function startDevEnvironment(options) {
|
|
|
403
780
|
}
|
|
404
781
|
console.log('');
|
|
405
782
|
console.log('🚀 Starting Next.js development server...');
|
|
783
|
+
// Mock connector server — start proxy if --mock-connectors is enabled
|
|
784
|
+
let bffTargetPort = functionsPort;
|
|
785
|
+
if (options.mockConnectors) {
|
|
786
|
+
const allModels = await (0, dev_seeds_1.loadProjectModels)();
|
|
787
|
+
const connectorModels = allModels.filter((m) => m.connectorConfig);
|
|
788
|
+
// Resolve auth config — auth functions use RDB connectors, mocked alongside other models
|
|
789
|
+
let mockAuthConfig;
|
|
790
|
+
if (authConfig?.provider === 'custom-jwt' && authConfig.customJwt) {
|
|
791
|
+
// Read JWT_SECRET from functions/local.settings.json if available
|
|
792
|
+
let jwtSecret = 'dev-jwt-secret-change-in-production-min-32-chars!!';
|
|
793
|
+
try {
|
|
794
|
+
const localSettingsPath = path.join(process.cwd(), 'functions', 'local.settings.json');
|
|
795
|
+
if (fs.existsSync(localSettingsPath)) {
|
|
796
|
+
const localSettings = JSON.parse(fs.readFileSync(localSettingsPath, 'utf-8'));
|
|
797
|
+
jwtSecret = localSettings.Values?.[authConfig.customJwt.jwtSecretEnv || 'JWT_SECRET'] || jwtSecret;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
catch { /* ignore */ }
|
|
801
|
+
mockAuthConfig = {
|
|
802
|
+
jwtSecret,
|
|
803
|
+
tokenExpiry: authConfig.customJwt.tokenExpiry,
|
|
804
|
+
customJwt: {
|
|
805
|
+
userTable: authConfig.customJwt.userTable,
|
|
806
|
+
loginIdColumn: authConfig.customJwt.loginIdColumn,
|
|
807
|
+
passwordHashColumn: authConfig.customJwt.passwordHashColumn,
|
|
808
|
+
rolesColumn: authConfig.customJwt.rolesColumn,
|
|
809
|
+
},
|
|
810
|
+
defaultPolicy: authConfig.authorization?.defaultPolicy === "public" ? "anonymous" :
|
|
811
|
+
authConfig.authorization?.defaultPolicy === "authenticated" || authConfig.authorization?.defaultPolicy === "anonymous"
|
|
812
|
+
? authConfig.authorization.defaultPolicy : undefined,
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
if (connectorModels.length > 0 || mockAuthConfig) {
|
|
816
|
+
const mockPort = parseInt(functionsPort, 10) + 1;
|
|
817
|
+
mockServer = new connector_mock_server_1.ConnectorMockServer({
|
|
818
|
+
port: mockPort,
|
|
819
|
+
functionsTarget: `${options.host || 'localhost'}:${functionsPort}`,
|
|
820
|
+
connectorModels,
|
|
821
|
+
allModels,
|
|
822
|
+
seedEnv: options.seedEnv,
|
|
823
|
+
host: options.host || 'localhost',
|
|
824
|
+
authConfig: mockAuthConfig,
|
|
825
|
+
});
|
|
826
|
+
await mockServer.start();
|
|
827
|
+
bffTargetPort = String(mockPort);
|
|
828
|
+
const modelCount = connectorModels.length + (mockAuthConfig ? 1 : 0);
|
|
829
|
+
console.log('');
|
|
830
|
+
console.log(`🔌 Mock server started (port: ${mockPort}) — ${modelCount} model(s) mocked via Zod/seed data`);
|
|
831
|
+
for (const m of connectorModels) {
|
|
832
|
+
const ops = m.connectorConfig.operations.join(', ');
|
|
833
|
+
console.log(` - ${m.name} [${ops}]`);
|
|
834
|
+
}
|
|
835
|
+
if (mockAuthConfig) {
|
|
836
|
+
console.log(` - auth [login, me, logout]`);
|
|
837
|
+
}
|
|
838
|
+
console.log(` Other routes → proxied to Azure Functions (port: ${functionsPort})`);
|
|
839
|
+
}
|
|
840
|
+
else {
|
|
841
|
+
console.log('');
|
|
842
|
+
console.log('ℹ️ --mock-connectors specified but no connector models found. Skipping mock server.');
|
|
843
|
+
}
|
|
844
|
+
}
|
|
406
845
|
// 5. Start Next.js development server
|
|
407
|
-
const nextArgs = pm
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
(
|
|
418
|
-
|
|
846
|
+
const nextArgs = buildNextDevArgs(pm, port);
|
|
847
|
+
// Ensure .env.local points to bffTargetPort so Next.js reads the correct backend URL.
|
|
848
|
+
// When --mock-connectors is active, bffTargetPort = mock port (7072); otherwise = Functions port (7071).
|
|
849
|
+
// Next.js may load .env.local values that override spawn env vars, so we must keep them in sync.
|
|
850
|
+
envLocalPath = path.join(process.cwd(), '.env.local');
|
|
851
|
+
envLocalDefaultUrl = functionsBaseUrl;
|
|
852
|
+
const bffTargetUrl = buildFunctionsBaseUrl(options.host, bffTargetPort);
|
|
853
|
+
try {
|
|
854
|
+
if (fs.existsSync(envLocalPath)) {
|
|
855
|
+
const envContent = fs.readFileSync(envLocalPath, 'utf-8');
|
|
856
|
+
if (envContent.includes('BACKEND_FUNCTIONS_BASE_URL=') &&
|
|
857
|
+
!envContent.includes(`BACKEND_FUNCTIONS_BASE_URL=${bffTargetUrl}`)) {
|
|
858
|
+
const updated = envContent.replace(/^BACKEND_FUNCTIONS_BASE_URL=.*/m, `BACKEND_FUNCTIONS_BASE_URL=${bffTargetUrl}`);
|
|
859
|
+
fs.writeFileSync(envLocalPath, updated, 'utf-8');
|
|
860
|
+
}
|
|
861
|
+
}
|
|
419
862
|
}
|
|
863
|
+
catch { /* ignore */ }
|
|
864
|
+
const nextEnv = {
|
|
865
|
+
...process.env,
|
|
866
|
+
BACKEND_FUNCTIONS_BASE_URL: bffTargetUrl,
|
|
867
|
+
FUNCTIONS_BASE_URL: bffTargetUrl,
|
|
868
|
+
};
|
|
420
869
|
const nextProcess = (0, child_process_1.spawn)(pm === 'pnpm' ? 'pnpm' : 'npx', nextArgs, {
|
|
421
870
|
cwd: process.cwd(),
|
|
422
871
|
shell: true,
|
|
423
872
|
stdio: options.verbose ? 'inherit' : 'inherit',
|
|
873
|
+
env: nextEnv,
|
|
424
874
|
});
|
|
425
875
|
processes.push(nextProcess);
|
|
426
876
|
nextProcess.on('error', (error) => {
|
|
@@ -432,6 +882,9 @@ async function startDevEnvironment(options) {
|
|
|
432
882
|
console.log(`\n⏹️ Next.js exited (exit code: ${code})`);
|
|
433
883
|
}
|
|
434
884
|
// Exit all processes when Next.js exits
|
|
885
|
+
if (mockServer) {
|
|
886
|
+
mockServer.stop().catch(() => { });
|
|
887
|
+
}
|
|
435
888
|
processes.forEach((proc) => {
|
|
436
889
|
if (proc && !proc.killed) {
|
|
437
890
|
proc.kill();
|
|
@@ -439,18 +892,90 @@ async function startDevEnvironment(options) {
|
|
|
439
892
|
});
|
|
440
893
|
process.exit(code || 0);
|
|
441
894
|
});
|
|
895
|
+
if (functionsReadinessPromise) {
|
|
896
|
+
functionsReady = await functionsReadinessPromise;
|
|
897
|
+
console.log('');
|
|
898
|
+
if (functionsReady) {
|
|
899
|
+
console.log(`✅ Azure Functions ready (port: ${functionsPort})`);
|
|
900
|
+
}
|
|
901
|
+
else {
|
|
902
|
+
console.log(`⚠️ Azure Functions is still starting: ${functionsBaseUrl}`);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
let swaReady = false;
|
|
906
|
+
if (useSwaEmulator && swaCommand) {
|
|
907
|
+
const nextUrl = `http://${options.host || 'localhost'}:${port}`;
|
|
908
|
+
console.log(`⏳ Waiting for Next.js before starting the SWA emulator at ${nextUrl}...`);
|
|
909
|
+
const nextReady = await waitForHttpServerReady(nextUrl);
|
|
910
|
+
if (!nextReady) {
|
|
911
|
+
throw new Error(`Next.js did not become ready at ${nextUrl}`);
|
|
912
|
+
}
|
|
913
|
+
console.log(`🔐 Starting SWA authentication emulator (port: ${swaPort})...`);
|
|
914
|
+
const swaProcess = (0, child_process_1.spawn)(swaCommand, buildSwaStartArgs(options.host, port, swaPort), {
|
|
915
|
+
cwd: process.cwd(),
|
|
916
|
+
shell: true,
|
|
917
|
+
stdio: 'inherit',
|
|
918
|
+
env: process.env,
|
|
919
|
+
});
|
|
920
|
+
processes.push(swaProcess);
|
|
921
|
+
swaProcess.on('error', (error) => {
|
|
922
|
+
console.error('❌ SWA CLI startup error:', error.message);
|
|
923
|
+
});
|
|
924
|
+
const swaUrl = `http://${options.host || 'localhost'}:${swaPort}`;
|
|
925
|
+
swaReady = await waitForHttpServerReady(swaUrl);
|
|
926
|
+
if (!swaReady) {
|
|
927
|
+
throw new Error(`SWA authentication emulator did not become ready at ${swaUrl}`);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
442
930
|
console.log('');
|
|
443
931
|
console.log('✅ SwallowKit development environment is running!');
|
|
444
932
|
console.log('');
|
|
445
|
-
|
|
933
|
+
if (useSwaEmulator) {
|
|
934
|
+
const swaUrl = `http://${options.host || 'localhost'}:${swaPort}`;
|
|
935
|
+
console.log('🔐 SWA authenticated app:');
|
|
936
|
+
console.log(` ${swaUrl}`);
|
|
937
|
+
console.log('');
|
|
938
|
+
console.log('🔑 Local sign-in:');
|
|
939
|
+
console.log(` ${swaUrl}/.auth/login/aad`);
|
|
940
|
+
console.log(' Enter any username and select Login.');
|
|
941
|
+
console.log(' The "authenticated" role is added automatically.');
|
|
942
|
+
console.log('');
|
|
943
|
+
console.log('📱 Next.js direct access (authentication is not emulated):');
|
|
944
|
+
console.log(` http://${options.host || 'localhost'}:${port}`);
|
|
945
|
+
}
|
|
946
|
+
else {
|
|
947
|
+
console.log(`📱 Next.js: http://${options.host || 'localhost'}:${port}`);
|
|
948
|
+
}
|
|
446
949
|
if (hasFunctions && !options.noFunctions) {
|
|
447
|
-
console.log(
|
|
950
|
+
console.log(`${functionsReady ? '⚡ Azure Functions' : '⏳ Azure Functions (starting)'}: ${functionsBaseUrl}`);
|
|
951
|
+
}
|
|
952
|
+
if (mockServer) {
|
|
953
|
+
console.log(`🔌 Mock Proxy: ${bffTargetUrl} (BFF → here)`);
|
|
448
954
|
}
|
|
449
955
|
console.log('');
|
|
450
|
-
if (hasFunctions && !options.noFunctions) {
|
|
956
|
+
if (hasFunctions && !options.noFunctions && functionsReady) {
|
|
451
957
|
console.log('💡 Azure Functions and Next.js BFF are connected');
|
|
452
958
|
}
|
|
959
|
+
else if (hasFunctions && !options.noFunctions) {
|
|
960
|
+
console.log('💡 Azure Functions is still warming up; BFF routes can fail until the backend responds.');
|
|
961
|
+
}
|
|
962
|
+
if (mockServer) {
|
|
963
|
+
console.log('💡 Connector models served from mock server (Zod-generated data)');
|
|
964
|
+
}
|
|
965
|
+
if (swaReady) {
|
|
966
|
+
console.log('💡 Open the app through the SWA port when testing authenticated pages and CRUD APIs.');
|
|
967
|
+
}
|
|
453
968
|
console.log('');
|
|
969
|
+
if (options.open) {
|
|
970
|
+
const url = useSwaEmulator
|
|
971
|
+
? `http://${options.host || 'localhost'}:${swaPort}`
|
|
972
|
+
: `http://${options.host || 'localhost'}:${port}`;
|
|
973
|
+
console.log(`🌐 Opening browser: ${url}`);
|
|
974
|
+
const start = process.platform === 'darwin' ? 'open' :
|
|
975
|
+
process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
976
|
+
(0, child_process_1.spawn)(start, [url], { shell: true });
|
|
977
|
+
console.log('');
|
|
978
|
+
}
|
|
454
979
|
console.log('🛑 Press Ctrl+C to stop');
|
|
455
980
|
console.log('');
|
|
456
981
|
}
|
|
@@ -464,4 +989,43 @@ async function startDevEnvironment(options) {
|
|
|
464
989
|
process.exit(1);
|
|
465
990
|
}
|
|
466
991
|
}
|
|
992
|
+
async function resolveSwaCliCommand(projectRoot) {
|
|
993
|
+
const binaryName = process.platform === 'win32' ? 'swa.cmd' : 'swa';
|
|
994
|
+
const localCommand = path.join(projectRoot, 'node_modules', '.bin', binaryName);
|
|
995
|
+
if (fs.existsSync(localCommand) && await checkCommand(localCommand, ['--version'], {
|
|
996
|
+
cwd: projectRoot,
|
|
997
|
+
shell: process.platform === 'win32',
|
|
998
|
+
})) {
|
|
999
|
+
return localCommand;
|
|
1000
|
+
}
|
|
1001
|
+
if (await checkCommand('swa', ['--version'], { cwd: projectRoot })) {
|
|
1002
|
+
return 'swa';
|
|
1003
|
+
}
|
|
1004
|
+
return null;
|
|
1005
|
+
}
|
|
1006
|
+
async function runCommand(command, args, cwd, label, env, shell = true) {
|
|
1007
|
+
await new Promise((resolve, reject) => {
|
|
1008
|
+
const child = (0, child_process_1.spawn)(command, args, {
|
|
1009
|
+
cwd,
|
|
1010
|
+
shell,
|
|
1011
|
+
stdio: 'inherit',
|
|
1012
|
+
env,
|
|
1013
|
+
});
|
|
1014
|
+
child.on('close', (code) => {
|
|
1015
|
+
if (code === 0) {
|
|
1016
|
+
resolve();
|
|
1017
|
+
}
|
|
1018
|
+
else {
|
|
1019
|
+
reject(new Error(`${label} failed with code ${code}`));
|
|
1020
|
+
}
|
|
1021
|
+
});
|
|
1022
|
+
child.on('error', reject);
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
function hasFunctionsProject(functionsDir, backendLanguage) {
|
|
1026
|
+
if (backendLanguage === 'typescript') {
|
|
1027
|
+
return fs.existsSync(path.join(functionsDir, 'package.json'));
|
|
1028
|
+
}
|
|
1029
|
+
return fs.existsSync(path.join(functionsDir, 'host.json'));
|
|
1030
|
+
}
|
|
467
1031
|
//# sourceMappingURL=dev.js.map
|