swallowkit 1.0.0-beta.5 → 1.0.0-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.ja.md +251 -242
- package/README.md +252 -243
- package/dist/__tests__/fixtures.d.ts +14 -0
- package/dist/__tests__/fixtures.d.ts.map +1 -0
- package/dist/__tests__/fixtures.js +85 -0
- package/dist/__tests__/fixtures.js.map +1 -0
- package/dist/cli/commands/create-model.js +14 -14
- package/dist/cli/commands/dev.d.ts +8 -0
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +238 -30
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/init.d.ts +5 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +2507 -1664
- 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 +281 -117
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/core/config.d.ts +2 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +28 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/scaffold/functions-generator.d.ts +5 -0
- package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
- package/dist/core/scaffold/functions-generator.js +649 -218
- package/dist/core/scaffold/functions-generator.js.map +1 -1
- package/dist/core/scaffold/model-parser.d.ts +1 -1
- package/dist/core/scaffold/model-parser.js +99 -99
- package/dist/core/scaffold/nextjs-generator.js +181 -181
- 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.js +656 -656
- package/dist/database/base-model.d.ts +3 -3
- package/dist/database/base-model.js +3 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/package-manager.d.ts +2 -1
- package/dist/utils/package-manager.d.ts.map +1 -1
- package/dist/utils/package-manager.js +14 -10
- package/dist/utils/package-manager.js.map +1 -1
- package/package.json +81 -74
- package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +445 -0
- package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +194 -0
- package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +524 -0
- package/src/__tests__/config.test.ts +122 -0
- package/src/__tests__/dev.test.ts +42 -0
- package/src/__tests__/fixtures.ts +83 -0
- package/src/__tests__/functions-generator.test.ts +101 -0
- package/src/__tests__/init.test.ts +59 -0
- package/src/__tests__/nextjs-generator.test.ts +97 -0
- package/src/__tests__/openapi-generator.test.ts +43 -0
- package/src/__tests__/package-manager.test.ts +189 -0
- package/src/__tests__/scaffold.test.ts +39 -0
- package/src/__tests__/string-utils.test.ts +75 -0
- package/src/__tests__/ui-generator.test.ts +144 -0
- package/src/cli/commands/create-model.ts +141 -0
- package/src/cli/commands/dev.ts +794 -0
- package/src/cli/commands/index.ts +8 -0
- package/src/cli/commands/init.ts +3363 -0
- package/src/cli/commands/provision.ts +193 -0
- package/src/cli/commands/scaffold.ts +786 -0
- package/src/cli/index.ts +73 -0
- package/src/core/config.ts +244 -0
- package/src/core/scaffold/functions-generator.ts +674 -0
- package/src/core/scaffold/model-parser.ts +627 -0
- package/src/core/scaffold/nextjs-generator.ts +217 -0
- package/src/core/scaffold/openapi-generator.ts +212 -0
- package/src/core/scaffold/ui-generator.ts +945 -0
- package/src/database/base-model.ts +184 -0
- package/src/database/client.ts +140 -0
- package/src/database/repository.ts +104 -0
- package/src/database/runtime-check.ts +25 -0
- package/src/index.ts +27 -0
- package/src/types/index.ts +45 -0
- package/src/utils/package-manager.ts +229 -0
- 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
|
@@ -0,0 +1,794 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { spawn, ChildProcess } from 'child_process';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import * as os from 'os';
|
|
6
|
+
import { CosmosClient, PartitionKeyKind } from '@azure/cosmos';
|
|
7
|
+
import { ensureSwallowKitProject, getBackendLanguage } from '../../core/config';
|
|
8
|
+
import { BackendLanguage } from '../../types';
|
|
9
|
+
import { detectFromProject, getCommands } from '../../utils/package-manager';
|
|
10
|
+
|
|
11
|
+
interface DevOptions {
|
|
12
|
+
port?: string;
|
|
13
|
+
functionsPort?: string;
|
|
14
|
+
host?: string;
|
|
15
|
+
open?: boolean;
|
|
16
|
+
verbose?: boolean;
|
|
17
|
+
noFunctions?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function buildFunctionsStartArgs(functionsPort: string): string[] {
|
|
21
|
+
return ['start', '--port', functionsPort];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function buildNextDevArgs(pm: string, port: string): string[] {
|
|
25
|
+
const baseArgs = ['next', 'dev', '--port', port, '--webpack'];
|
|
26
|
+
return pm === 'pnpm' ? ['exec', ...baseArgs] : baseArgs;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getPythonVirtualEnvPaths(functionsDir: string): {
|
|
30
|
+
venvDir: string;
|
|
31
|
+
binDir: string;
|
|
32
|
+
pythonExecutable: string;
|
|
33
|
+
} {
|
|
34
|
+
const venvDir = path.join(functionsDir, '.venv');
|
|
35
|
+
const binDir = process.platform === 'win32'
|
|
36
|
+
? path.join(venvDir, 'Scripts')
|
|
37
|
+
: path.join(venvDir, 'bin');
|
|
38
|
+
const pythonExecutable = process.platform === 'win32'
|
|
39
|
+
? path.join(binDir, 'python.exe')
|
|
40
|
+
: path.join(binDir, 'python');
|
|
41
|
+
|
|
42
|
+
return { venvDir, binDir, pythonExecutable };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function buildPythonFunctionsEnv(baseEnv: NodeJS.ProcessEnv, functionsDir: string): NodeJS.ProcessEnv {
|
|
46
|
+
const { venvDir, binDir, pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
|
|
47
|
+
const pathKey = getPathEnvKey(baseEnv);
|
|
48
|
+
const currentPath = baseEnv[pathKey] || '';
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
...baseEnv,
|
|
52
|
+
[pathKey]: currentPath ? `${binDir}${path.delimiter}${currentPath}` : binDir,
|
|
53
|
+
VIRTUAL_ENV: venvDir,
|
|
54
|
+
languageWorkers__python__defaultExecutablePath: pythonExecutable,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getPathEnvKey(env: NodeJS.ProcessEnv): string {
|
|
59
|
+
return Object.keys(env).find((key) => key.toUpperCase() === 'PATH') || 'PATH';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function prependToPathEnv(env: NodeJS.ProcessEnv, entry: string): NodeJS.ProcessEnv {
|
|
63
|
+
const pathKey = getPathEnvKey(env);
|
|
64
|
+
const currentPath = env[pathKey] || '';
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
...env,
|
|
68
|
+
[pathKey]: currentPath ? `${entry}${path.delimiter}${currentPath}` : entry,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Check if Azure Functions Core Tools is installed
|
|
74
|
+
*/
|
|
75
|
+
async function checkCoreTools(): Promise<boolean> {
|
|
76
|
+
return checkCommand('func', ['--version']);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function checkCommand(command: string, args: string[] = ['--version']): Promise<boolean> {
|
|
80
|
+
return new Promise((resolve) => {
|
|
81
|
+
const checkProcess = spawn(command, args, {
|
|
82
|
+
shell: false,
|
|
83
|
+
stdio: 'pipe',
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
checkProcess.on('close', (code) => {
|
|
87
|
+
resolve(code === 0);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
checkProcess.on('error', () => {
|
|
91
|
+
resolve(false);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function resolvePythonBootstrapCommand(): Promise<{ command: string; argsPrefix: string[]; label: string }> {
|
|
97
|
+
const candidates = process.platform === 'win32'
|
|
98
|
+
? [
|
|
99
|
+
{ command: 'py', argsPrefix: ['-3.11'], label: 'py -3.11' },
|
|
100
|
+
{ command: 'python', argsPrefix: [], label: 'python' },
|
|
101
|
+
]
|
|
102
|
+
: [
|
|
103
|
+
{ command: 'python3', argsPrefix: [], label: 'python3' },
|
|
104
|
+
{ command: 'python', argsPrefix: [], label: 'python' },
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
for (const candidate of candidates) {
|
|
108
|
+
if (await checkCommand(candidate.command, [...candidate.argsPrefix, '--version'])) {
|
|
109
|
+
return candidate;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
throw new Error(
|
|
114
|
+
'Python 3.11 was not found. Install Python 3.11 and make sure `python`, `python3`, or `py -3.11` is available.'
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function getCommandPath(command: string): Promise<string | null> {
|
|
119
|
+
const locator = process.platform === 'win32' ? 'where' : 'which';
|
|
120
|
+
const result = await captureCommandOutput(locator, [command]);
|
|
121
|
+
const firstLine = result
|
|
122
|
+
.split(/\r?\n/)
|
|
123
|
+
.map((line) => line.trim())
|
|
124
|
+
.find(Boolean);
|
|
125
|
+
|
|
126
|
+
return firstLine || null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function captureCommandOutput(
|
|
130
|
+
command: string,
|
|
131
|
+
args: string[],
|
|
132
|
+
cwd?: string,
|
|
133
|
+
env?: NodeJS.ProcessEnv
|
|
134
|
+
): Promise<string> {
|
|
135
|
+
return new Promise((resolve, reject) => {
|
|
136
|
+
const child = spawn(command, args, {
|
|
137
|
+
cwd,
|
|
138
|
+
env,
|
|
139
|
+
shell: false,
|
|
140
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
let stdout = '';
|
|
144
|
+
let stderr = '';
|
|
145
|
+
|
|
146
|
+
child.stdout?.on('data', (data) => {
|
|
147
|
+
stdout += data.toString();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
child.stderr?.on('data', (data) => {
|
|
151
|
+
stderr += data.toString();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
child.on('close', (code) => {
|
|
155
|
+
if (code === 0) {
|
|
156
|
+
resolve(stdout);
|
|
157
|
+
} else {
|
|
158
|
+
reject(new Error(stderr || stdout || `${command} exited with code ${code}`));
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
child.on('error', reject);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function resolvePythonRuntimeDetails(
|
|
167
|
+
functionsDir: string,
|
|
168
|
+
env: NodeJS.ProcessEnv
|
|
169
|
+
): Promise<{ version: string; architecture: string }> {
|
|
170
|
+
const { pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
|
|
171
|
+
const output = await captureCommandOutput(
|
|
172
|
+
pythonExecutable,
|
|
173
|
+
[
|
|
174
|
+
'-c',
|
|
175
|
+
'import platform; import sys; print(str(sys.version_info.major) + "." + str(sys.version_info.minor)); print(platform.machine())',
|
|
176
|
+
],
|
|
177
|
+
functionsDir,
|
|
178
|
+
env
|
|
179
|
+
);
|
|
180
|
+
const [version, architecture] = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
181
|
+
|
|
182
|
+
if (!version || !architecture) {
|
|
183
|
+
throw new Error('Failed to determine Python runtime details.');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return { version, architecture };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function bridgePythonCoreToolsForWindowsArm64(
|
|
190
|
+
functionsDir: string,
|
|
191
|
+
env: NodeJS.ProcessEnv
|
|
192
|
+
): Promise<NodeJS.ProcessEnv> {
|
|
193
|
+
if (process.platform !== 'win32' || process.arch !== 'arm64') {
|
|
194
|
+
return env;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const funcPath = await getCommandPath('func');
|
|
198
|
+
if (!funcPath) {
|
|
199
|
+
return env;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const { version, architecture } = await resolvePythonRuntimeDetails(functionsDir, env);
|
|
203
|
+
if (architecture.toUpperCase() !== 'AMD64') {
|
|
204
|
+
return env;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const coreToolsRoot = path.dirname(funcPath);
|
|
208
|
+
const armWorkerDir = path.join(coreToolsRoot, 'workers', 'python', version, 'WINDOWS', 'Arm64');
|
|
209
|
+
if (fs.existsSync(armWorkerDir)) {
|
|
210
|
+
return env;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const x64WorkerDir = path.join(coreToolsRoot, 'workers', 'python', version, 'WINDOWS', 'X64');
|
|
214
|
+
if (!fs.existsSync(x64WorkerDir)) {
|
|
215
|
+
return env;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const patchedRoot = path.join(
|
|
219
|
+
process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
|
|
220
|
+
'SwallowKit',
|
|
221
|
+
'azure-functions-core-tools-python-bridge'
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
if (!fs.existsSync(path.join(patchedRoot, 'func.exe'))) {
|
|
225
|
+
console.log('🩹 Creating a local Azure Functions Core Tools bridge for Windows Arm64 Python...');
|
|
226
|
+
fs.mkdirSync(path.dirname(patchedRoot), { recursive: true });
|
|
227
|
+
fs.cpSync(coreToolsRoot, patchedRoot, { recursive: true });
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const patchedArmWorkerDir = path.join(patchedRoot, 'workers', 'python', version, 'WINDOWS', 'Arm64');
|
|
231
|
+
const patchedX64WorkerDir = path.join(patchedRoot, 'workers', 'python', version, 'WINDOWS', 'X64');
|
|
232
|
+
if (!fs.existsSync(patchedArmWorkerDir) && fs.existsSync(patchedX64WorkerDir)) {
|
|
233
|
+
fs.cpSync(patchedX64WorkerDir, patchedArmWorkerDir, { recursive: true });
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
console.log(`🩹 Using bridged Azure Functions Core Tools from ${patchedRoot}`);
|
|
237
|
+
return prependToPathEnv(env, patchedRoot);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function preparePythonFunctionsEnvironment(functionsDir: string): Promise<NodeJS.ProcessEnv> {
|
|
241
|
+
const { pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
|
|
242
|
+
const hasUv = await checkCommand('uv', ['--version']);
|
|
243
|
+
|
|
244
|
+
if (!fs.existsSync(pythonExecutable)) {
|
|
245
|
+
if (hasUv) {
|
|
246
|
+
console.log('📦 Creating Python virtual environment with uv...');
|
|
247
|
+
await runCommand('uv', ['venv', '.venv', '--python', '3.11'], functionsDir, 'python virtual environment setup');
|
|
248
|
+
} else {
|
|
249
|
+
const bootstrap = await resolvePythonBootstrapCommand();
|
|
250
|
+
console.log(`📦 Creating Python virtual environment with ${bootstrap.label}...`);
|
|
251
|
+
await runCommand(
|
|
252
|
+
bootstrap.command,
|
|
253
|
+
[...bootstrap.argsPrefix, '-m', 'venv', '.venv'],
|
|
254
|
+
functionsDir,
|
|
255
|
+
'python virtual environment setup'
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const pythonEnv = buildPythonFunctionsEnv(process.env, functionsDir);
|
|
261
|
+
console.log(`📦 Installing Python Azure Functions dependencies${hasUv ? ' with uv' : ''}...`);
|
|
262
|
+
|
|
263
|
+
if (hasUv) {
|
|
264
|
+
await runCommand(
|
|
265
|
+
'uv',
|
|
266
|
+
['pip', 'install', '--python', pythonExecutable, '-r', 'requirements.txt'],
|
|
267
|
+
functionsDir,
|
|
268
|
+
'python dependency installation',
|
|
269
|
+
pythonEnv
|
|
270
|
+
);
|
|
271
|
+
} else {
|
|
272
|
+
await runCommand('python', ['-m', 'pip', 'install', '--upgrade', 'pip'], functionsDir, 'python pip upgrade', pythonEnv);
|
|
273
|
+
await runCommand(
|
|
274
|
+
'python',
|
|
275
|
+
['-m', 'pip', 'install', '-r', 'requirements.txt'],
|
|
276
|
+
functionsDir,
|
|
277
|
+
'python dependency installation',
|
|
278
|
+
pythonEnv
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return bridgePythonCoreToolsForWindowsArm64(functionsDir, pythonEnv);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Check if Cosmos DB Emulator is running by checking if port 8081 is open
|
|
287
|
+
*/
|
|
288
|
+
async function checkCosmosDBEmulator(): Promise<boolean> {
|
|
289
|
+
return new Promise((resolve) => {
|
|
290
|
+
const net = require('net');
|
|
291
|
+
const socket = new net.Socket();
|
|
292
|
+
|
|
293
|
+
const timeout = setTimeout(() => {
|
|
294
|
+
socket.destroy();
|
|
295
|
+
resolve(false);
|
|
296
|
+
}, 2000);
|
|
297
|
+
|
|
298
|
+
socket.on('connect', () => {
|
|
299
|
+
clearTimeout(timeout);
|
|
300
|
+
socket.destroy();
|
|
301
|
+
resolve(true);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
socket.on('error', () => {
|
|
305
|
+
clearTimeout(timeout);
|
|
306
|
+
resolve(false);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
socket.connect(8081, 'localhost');
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export const devCommand = new Command()
|
|
314
|
+
.name('dev')
|
|
315
|
+
.description('Start SwallowKit development server (Cosmos DB + Next.js + Azure Functions)')
|
|
316
|
+
.option('-p, --port <port>', 'Next.js port', '3000')
|
|
317
|
+
.option('-f, --functions-port <port>', 'Azure Functions port', '7071')
|
|
318
|
+
.option('--host <host>', 'Host name', 'localhost')
|
|
319
|
+
.option('--open', 'Open browser automatically', false)
|
|
320
|
+
.option('--verbose', 'Show verbose logs', false)
|
|
321
|
+
.option('--no-functions', 'Skip Azure Functions startup', false)
|
|
322
|
+
.action(async (options: DevOptions & { functionsPort?: string; noFunctions?: boolean }) => {
|
|
323
|
+
// SwallowKit プロジェクトディレクトリかどうかを検証
|
|
324
|
+
ensureSwallowKitProject("dev");
|
|
325
|
+
|
|
326
|
+
console.log('🚀 Starting SwallowKit development environment...');
|
|
327
|
+
if (options.verbose) {
|
|
328
|
+
console.log('⚙️ Options:', options);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
await startDevEnvironment(options);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
async function initializeCosmosDB(databaseName: string): Promise<void> {
|
|
335
|
+
try {
|
|
336
|
+
// Read local.settings.json from functions directory
|
|
337
|
+
const functionsDir = path.join(process.cwd(), 'functions');
|
|
338
|
+
const localSettingsPath = path.join(functionsDir, 'local.settings.json');
|
|
339
|
+
|
|
340
|
+
if (!fs.existsSync(localSettingsPath)) {
|
|
341
|
+
console.log('⚠️ local.settings.json not found. Skipping Cosmos DB initialization.');
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const localSettings = JSON.parse(fs.readFileSync(localSettingsPath, 'utf-8'));
|
|
346
|
+
const connectionString = localSettings.Values?.CosmosDBConnection;
|
|
347
|
+
const dbName = localSettings.Values?.COSMOS_DB_DATABASE_NAME || databaseName;
|
|
348
|
+
|
|
349
|
+
if (!connectionString) {
|
|
350
|
+
console.log('⚠️ CosmosDBConnection not found in local.settings.json. Skipping Cosmos DB initialization.');
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
console.log('🗄️ Initializing Cosmos DB...');
|
|
355
|
+
|
|
356
|
+
// Parse connection string
|
|
357
|
+
const endpointMatch = connectionString.match(/AccountEndpoint=([^;]+)/);
|
|
358
|
+
const keyMatch = connectionString.match(/AccountKey=([^;]+)/);
|
|
359
|
+
|
|
360
|
+
if (!endpointMatch || !keyMatch) {
|
|
361
|
+
console.log('⚠️ Invalid CosmosDB connection string format.');
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const endpoint = endpointMatch[1];
|
|
366
|
+
|
|
367
|
+
const client = new CosmosClient({
|
|
368
|
+
endpoint: endpoint,
|
|
369
|
+
key: keyMatch[1]
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// Create database if not exists
|
|
373
|
+
const { database } = await client.databases.createIfNotExists({ id: dbName });
|
|
374
|
+
console.log(`✅ Database "${dbName}" ready`);
|
|
375
|
+
|
|
376
|
+
// Read lib/scaffold-config.ts to get list of models
|
|
377
|
+
const scaffoldConfigPath = path.join(process.cwd(), 'lib', 'scaffold-config.ts');
|
|
378
|
+
if (fs.existsSync(scaffoldConfigPath)) {
|
|
379
|
+
const scaffoldConfigContent = fs.readFileSync(scaffoldConfigPath, 'utf-8');
|
|
380
|
+
|
|
381
|
+
// Parse TypeScript file to extract models array
|
|
382
|
+
const modelsMatch = scaffoldConfigContent.match(/models:\s*\[([\s\S]*?)\]\s*as\s*ScaffoldModel\[\]/);
|
|
383
|
+
if (modelsMatch) {
|
|
384
|
+
const modelsArrayContent = modelsMatch[1];
|
|
385
|
+
// Extract model names from objects like { name: 'Task', path: '/task', label: 'Task' }
|
|
386
|
+
const modelMatches = modelsArrayContent.matchAll(/\{\s*name:\s*['"](\w+)['"]/g);
|
|
387
|
+
const models = Array.from(modelMatches, m => m[1]);
|
|
388
|
+
|
|
389
|
+
for (const modelName of models) {
|
|
390
|
+
const containerName = `${modelName}s`; // Pluralize model name
|
|
391
|
+
|
|
392
|
+
// Try creating container with full partition key definition first
|
|
393
|
+
let containerCreated = false;
|
|
394
|
+
|
|
395
|
+
try {
|
|
396
|
+
console.log(`🔧 Creating container "${containerName}" with partition key /id...`);
|
|
397
|
+
const containerResponse = await database.containers.createIfNotExists({
|
|
398
|
+
id: containerName,
|
|
399
|
+
partitionKey: {
|
|
400
|
+
paths: ['/id'],
|
|
401
|
+
kind: PartitionKeyKind.Hash,
|
|
402
|
+
version: 2
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
console.log(`✅ Container "${containerName}" ready (status: ${containerResponse.statusCode})`);
|
|
406
|
+
containerCreated = true;
|
|
407
|
+
} catch (error: any) {
|
|
408
|
+
console.log(`⚠️ Failed with full partition key definition: ${error.message}`);
|
|
409
|
+
console.log(`🔄 Retrying with simple partition key...`);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// If first attempt failed, try with simple partition key definition
|
|
413
|
+
if (!containerCreated) {
|
|
414
|
+
try {
|
|
415
|
+
const containerResponse = await database.containers.createIfNotExists({
|
|
416
|
+
id: containerName,
|
|
417
|
+
partitionKey: {
|
|
418
|
+
paths: ['/id']
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
console.log(`✅ Container "${containerName}" ready (status: ${containerResponse.statusCode})`);
|
|
422
|
+
} catch (containerError: any) {
|
|
423
|
+
console.error(`❌ Failed to create container "${containerName}":`, containerError.message);
|
|
424
|
+
console.error(`Error code: ${containerError.code}`);
|
|
425
|
+
if (containerError.body) {
|
|
426
|
+
console.error(`Response body:`, JSON.stringify(containerError.body, null, 2));
|
|
427
|
+
}
|
|
428
|
+
// Continue with other containers
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
console.log('✅ Cosmos DB initialization complete\n');
|
|
436
|
+
} catch (error: any) {
|
|
437
|
+
console.error('⚠️ Cosmos DB initialization failed:', error.message);
|
|
438
|
+
if (error.stack) {
|
|
439
|
+
console.error('Stack trace:', error.stack);
|
|
440
|
+
}
|
|
441
|
+
console.log('💡 Make sure Cosmos DB Emulator is running');
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
async function startDevEnvironment(options: DevOptions) {
|
|
446
|
+
const port = options.port || '3000';
|
|
447
|
+
const functionsPort = options.functionsPort || '7071';
|
|
448
|
+
|
|
449
|
+
// Detect package manager from project lockfile
|
|
450
|
+
const pm = detectFromProject();
|
|
451
|
+
const pmCmd = getCommands(pm);
|
|
452
|
+
const backendLanguage = getBackendLanguage();
|
|
453
|
+
|
|
454
|
+
// プロセスを管理する配列
|
|
455
|
+
const processes: ChildProcess[] = [];
|
|
456
|
+
let functionsEnv: NodeJS.ProcessEnv = process.env;
|
|
457
|
+
|
|
458
|
+
// Cleanup processes on Ctrl+C
|
|
459
|
+
process.on('SIGINT', () => {
|
|
460
|
+
console.log('\n🛑 Stopping development servers...');
|
|
461
|
+
processes.forEach((proc) => {
|
|
462
|
+
if (proc && !proc.killed) {
|
|
463
|
+
proc.kill();
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
process.exit(0);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
try {
|
|
470
|
+
// 1. Check for Next.js project
|
|
471
|
+
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
472
|
+
const nextConfigPathJs = path.join(process.cwd(), 'next.config.js');
|
|
473
|
+
const nextConfigPathTs = path.join(process.cwd(), 'next.config.ts');
|
|
474
|
+
const nextConfigPathMjs = path.join(process.cwd(), 'next.config.mjs');
|
|
475
|
+
|
|
476
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
477
|
+
console.log('❌ package.json not found.');
|
|
478
|
+
console.log('💡 Please run this command in the root directory of a Next.js project.');
|
|
479
|
+
process.exit(1);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (!fs.existsSync(nextConfigPathJs) && !fs.existsSync(nextConfigPathTs) && !fs.existsSync(nextConfigPathMjs)) {
|
|
483
|
+
console.log('⚠️ next.config file not found. Is this a Next.js project?');
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// 2. Check if Azure Functions exists
|
|
487
|
+
const functionsDir = path.join(process.cwd(), 'functions');
|
|
488
|
+
const hasFunctions = fs.existsSync(functionsDir) && hasFunctionsProject(functionsDir, backendLanguage);
|
|
489
|
+
|
|
490
|
+
if (hasFunctions && !options.noFunctions) {
|
|
491
|
+
// Check if Azure Functions Core Tools is installed
|
|
492
|
+
const coreToolsInstalled = await checkCoreTools();
|
|
493
|
+
|
|
494
|
+
if (!coreToolsInstalled) {
|
|
495
|
+
console.log('');
|
|
496
|
+
console.log('⚠️ Azure Functions Core Tools not found.');
|
|
497
|
+
console.log('');
|
|
498
|
+
|
|
499
|
+
// Prompt user for installation
|
|
500
|
+
const readline = await import('readline');
|
|
501
|
+
const rl = readline.createInterface({
|
|
502
|
+
input: process.stdin,
|
|
503
|
+
output: process.stdout
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
const answer = await new Promise<string>((resolve) => {
|
|
507
|
+
rl.question('Would you like to install Azure Functions Core Tools? (y/n): ', resolve);
|
|
508
|
+
});
|
|
509
|
+
rl.close();
|
|
510
|
+
|
|
511
|
+
if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
|
|
512
|
+
console.log('📦 Installing Azure Functions Core Tools...');
|
|
513
|
+
console.log(' This may take a few minutes.');
|
|
514
|
+
|
|
515
|
+
const installProcess = spawn(pm, pm === 'pnpm' ? ['add', '-g', 'azure-functions-core-tools@4'] : ['install', '-g', 'azure-functions-core-tools@4'], {
|
|
516
|
+
shell: true,
|
|
517
|
+
stdio: 'inherit',
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
await new Promise<void>((resolve, reject) => {
|
|
521
|
+
installProcess.on('close', (code) => {
|
|
522
|
+
if (code === 0) {
|
|
523
|
+
console.log('✅ Azure Functions Core Tools installed successfully.');
|
|
524
|
+
resolve();
|
|
525
|
+
} else {
|
|
526
|
+
console.error('❌ Installation failed.');
|
|
527
|
+
console.log('💡 Please install manually:');
|
|
528
|
+
console.log(` ${pmCmd.addGlobal} azure-functions-core-tools@4`);
|
|
529
|
+
reject(new Error(`Installation failed with code ${code}`));
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
installProcess.on('error', reject);
|
|
533
|
+
});
|
|
534
|
+
} else {
|
|
535
|
+
console.log('');
|
|
536
|
+
console.log('ℹ️ Skipping Azure Functions startup.');
|
|
537
|
+
console.log('💡 To install later:');
|
|
538
|
+
console.log(` ${pmCmd.addGlobal} azure-functions-core-tools@4`);
|
|
539
|
+
console.log('');
|
|
540
|
+
// Skip Azure Functions startup
|
|
541
|
+
options.noFunctions = true;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (!options.noFunctions) {
|
|
546
|
+
// Check if Cosmos DB Emulator is running
|
|
547
|
+
const cosmosRunning = await checkCosmosDBEmulator();
|
|
548
|
+
|
|
549
|
+
if (!cosmosRunning) {
|
|
550
|
+
console.log('');
|
|
551
|
+
console.log('❌ Cosmos DB Emulator is not running.');
|
|
552
|
+
console.log('');
|
|
553
|
+
console.log('💡 Please start Cosmos DB Emulator manually:');
|
|
554
|
+
console.log(' C:\\Program Files\\Azure Cosmos DB Emulator\\CosmosDB.Emulator.exe');
|
|
555
|
+
console.log('');
|
|
556
|
+
console.log(' Or search for "Azure Cosmos DB Emulator" in the Start menu.');
|
|
557
|
+
console.log('');
|
|
558
|
+
process.exit(1);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Initialize Cosmos DB before starting Functions
|
|
562
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
563
|
+
const appName = packageJson.name || 'App';
|
|
564
|
+
const databaseName = `${appName.charAt(0).toUpperCase() + appName.slice(1)}Database`;
|
|
565
|
+
|
|
566
|
+
await initializeCosmosDB(databaseName);
|
|
567
|
+
|
|
568
|
+
console.log('');
|
|
569
|
+
console.log('🚀 Starting Azure Functions...');
|
|
570
|
+
|
|
571
|
+
if (backendLanguage === 'typescript') {
|
|
572
|
+
const functionsNodeModules = path.join(functionsDir, 'node_modules');
|
|
573
|
+
if (!fs.existsSync(functionsNodeModules)) {
|
|
574
|
+
console.log('📦 Installing Azure Functions dependencies...');
|
|
575
|
+
await runCommand(pm, ['install'], functionsDir, `${pm} install`);
|
|
576
|
+
}
|
|
577
|
+
} else if (backendLanguage === 'csharp') {
|
|
578
|
+
console.log('📦 Building C# Azure Functions project...');
|
|
579
|
+
await runCommand('dotnet', ['build'], functionsDir, 'dotnet build');
|
|
580
|
+
} else {
|
|
581
|
+
functionsEnv = await preparePythonFunctionsEnvironment(functionsDir);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (hasFunctions && !options.noFunctions) {
|
|
587
|
+
// Build shared package before starting Functions
|
|
588
|
+
const sharedDir = path.join(process.cwd(), 'shared');
|
|
589
|
+
if (fs.existsSync(sharedDir) && fs.existsSync(path.join(sharedDir, 'package.json'))) {
|
|
590
|
+
console.log('📦 Building shared package...');
|
|
591
|
+
const filterArgs = pm === 'pnpm'
|
|
592
|
+
? ['run', '--filter', 'shared', 'build']
|
|
593
|
+
: ['run', '--workspace=shared', 'build'];
|
|
594
|
+
const sharedBuild = spawn(pm, filterArgs, {
|
|
595
|
+
cwd: process.cwd(),
|
|
596
|
+
shell: true,
|
|
597
|
+
stdio: 'inherit',
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
await new Promise<void>((resolve, reject) => {
|
|
601
|
+
sharedBuild.on('close', (code) => {
|
|
602
|
+
if (code === 0) {
|
|
603
|
+
console.log('✅ Shared package built successfully');
|
|
604
|
+
resolve();
|
|
605
|
+
} else {
|
|
606
|
+
reject(new Error(`Shared package build failed with code ${code}`));
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
sharedBuild.on('error', reject);
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Azure Functions を起動
|
|
614
|
+
const funcProcess = spawn('func', buildFunctionsStartArgs(functionsPort), {
|
|
615
|
+
cwd: functionsDir,
|
|
616
|
+
shell: true,
|
|
617
|
+
stdio: 'pipe', // Always pipe to capture output
|
|
618
|
+
env: functionsEnv
|
|
619
|
+
});
|
|
620
|
+
let pythonWorkerMissing = false;
|
|
621
|
+
|
|
622
|
+
// Functions の出力をそのまま表示(プレフィックス付き)
|
|
623
|
+
if (funcProcess.stdout) {
|
|
624
|
+
funcProcess.stdout.on('data', (data) => {
|
|
625
|
+
const output = data.toString();
|
|
626
|
+
// 各行にプレフィックスを付けて出力
|
|
627
|
+
const lines = output.split('\n').filter((line: string) => line.trim());
|
|
628
|
+
lines.forEach((line: string) => {
|
|
629
|
+
if (
|
|
630
|
+
backendLanguage === 'python' &&
|
|
631
|
+
(line.includes('WorkerConfig for runtime: python not found') || line.includes('DefaultWorkerPath:'))
|
|
632
|
+
) {
|
|
633
|
+
pythonWorkerMissing = true;
|
|
634
|
+
}
|
|
635
|
+
console.log(`[Functions] ${line}`);
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (funcProcess.stderr) {
|
|
641
|
+
funcProcess.stderr.on('data', (data) => {
|
|
642
|
+
const output = data.toString();
|
|
643
|
+
const lines = output.split('\n').filter((line: string) => line.trim());
|
|
644
|
+
lines.forEach((line: string) => {
|
|
645
|
+
if (
|
|
646
|
+
backendLanguage === 'python' &&
|
|
647
|
+
(line.includes('WorkerConfig for runtime: python not found') || line.includes('DefaultWorkerPath:'))
|
|
648
|
+
) {
|
|
649
|
+
pythonWorkerMissing = true;
|
|
650
|
+
}
|
|
651
|
+
console.error(`[Functions Error] ${line}`);
|
|
652
|
+
});
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
processes.push(funcProcess);
|
|
657
|
+
|
|
658
|
+
funcProcess.on('error', (error) => {
|
|
659
|
+
console.error('⚠️ Azure Functions startup error:', error.message);
|
|
660
|
+
console.log('💡 Please ensure Azure Functions Core Tools is installed');
|
|
661
|
+
console.log(` ${pmCmd.addGlobal} azure-functions-core-tools@4`);
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
funcProcess.on('close', (code) => {
|
|
665
|
+
if (code !== 0) {
|
|
666
|
+
console.log(`\n⏹️ Azure Functions exited (exit code: ${code})`);
|
|
667
|
+
if (backendLanguage === 'python' && pythonWorkerMissing) {
|
|
668
|
+
console.log('💡 Your Azure Functions Core Tools installation is missing the Python worker for this OS/architecture.');
|
|
669
|
+
console.log(' Reinstall a matching Core Tools v4 package (Windows users should prefer the official x64/x86 MSI for their machine).');
|
|
670
|
+
console.log(' SwallowKit local Python dev uses functions/.venv and requirements.txt, but Core Tools still needs its own bundled Python worker.');
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
console.log(`✅ Azure Functions started (port: ${functionsPort})`);
|
|
676
|
+
} else if (!hasFunctions) {
|
|
677
|
+
console.log('');
|
|
678
|
+
console.log('ℹ️ functions/ directory not found. Starting Next.js only.');
|
|
679
|
+
} else if (options.noFunctions) {
|
|
680
|
+
console.log('');
|
|
681
|
+
console.log('ℹ️ --no-functions specified. Skipping Azure Functions.');
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
console.log('');
|
|
685
|
+
console.log('🚀 Starting Next.js development server...');
|
|
686
|
+
|
|
687
|
+
// 5. Start Next.js development server
|
|
688
|
+
const nextArgs = buildNextDevArgs(pm, port);
|
|
689
|
+
|
|
690
|
+
if (options.open) {
|
|
691
|
+
// Next.js 14+ deprecated --open option, so we open browser manually
|
|
692
|
+
setTimeout(() => {
|
|
693
|
+
const url = `http://${options.host || 'localhost'}:${port}`;
|
|
694
|
+
console.log(`\n🌐 Opening browser: ${url}`);
|
|
695
|
+
|
|
696
|
+
const start = process.platform === 'darwin' ? 'open' :
|
|
697
|
+
process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
698
|
+
spawn(start, [url], { shell: true });
|
|
699
|
+
}, 3000);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
const nextEnv: NodeJS.ProcessEnv = {
|
|
703
|
+
...process.env,
|
|
704
|
+
BACKEND_FUNCTIONS_BASE_URL: `http://${options.host || 'localhost'}:${functionsPort}`,
|
|
705
|
+
FUNCTIONS_BASE_URL: `http://${options.host || 'localhost'}:${functionsPort}`,
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
const nextProcess = spawn(pm === 'pnpm' ? 'pnpm' : 'npx', nextArgs, {
|
|
709
|
+
cwd: process.cwd(),
|
|
710
|
+
shell: true,
|
|
711
|
+
stdio: options.verbose ? 'inherit' : 'inherit',
|
|
712
|
+
env: nextEnv,
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
processes.push(nextProcess);
|
|
716
|
+
|
|
717
|
+
nextProcess.on('error', (error) => {
|
|
718
|
+
console.error('❌ Next.js startup error:', error.message);
|
|
719
|
+
process.exit(1);
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
nextProcess.on('close', (code) => {
|
|
723
|
+
if (code !== 0) {
|
|
724
|
+
console.log(`\n⏹️ Next.js exited (exit code: ${code})`);
|
|
725
|
+
}
|
|
726
|
+
// Exit all processes when Next.js exits
|
|
727
|
+
processes.forEach((proc) => {
|
|
728
|
+
if (proc && !proc.killed) {
|
|
729
|
+
proc.kill();
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
process.exit(code || 0);
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
console.log('');
|
|
736
|
+
console.log('✅ SwallowKit development environment is running!');
|
|
737
|
+
console.log('');
|
|
738
|
+
console.log(`📱 Next.js: http://${options.host || 'localhost'}:${port}`);
|
|
739
|
+
if (hasFunctions && !options.noFunctions) {
|
|
740
|
+
console.log(`⚡ Azure Functions: http://${options.host || 'localhost'}:${functionsPort}`);
|
|
741
|
+
}
|
|
742
|
+
console.log('');
|
|
743
|
+
if (hasFunctions && !options.noFunctions) {
|
|
744
|
+
console.log('💡 Azure Functions and Next.js BFF are connected');
|
|
745
|
+
}
|
|
746
|
+
console.log('');
|
|
747
|
+
console.log('🛑 Press Ctrl+C to stop');
|
|
748
|
+
console.log('');
|
|
749
|
+
|
|
750
|
+
} catch (error) {
|
|
751
|
+
console.error('❌ Failed to start development environment:', error instanceof Error ? error.message : error);
|
|
752
|
+
processes.forEach((proc) => {
|
|
753
|
+
if (proc && !proc.killed) {
|
|
754
|
+
proc.kill();
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
process.exit(1);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
async function runCommand(
|
|
762
|
+
command: string,
|
|
763
|
+
args: string[],
|
|
764
|
+
cwd: string,
|
|
765
|
+
label: string,
|
|
766
|
+
env?: NodeJS.ProcessEnv
|
|
767
|
+
): Promise<void> {
|
|
768
|
+
await new Promise<void>((resolve, reject) => {
|
|
769
|
+
const child = spawn(command, args, {
|
|
770
|
+
cwd,
|
|
771
|
+
shell: true,
|
|
772
|
+
stdio: 'inherit',
|
|
773
|
+
env,
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
child.on('close', (code) => {
|
|
777
|
+
if (code === 0) {
|
|
778
|
+
resolve();
|
|
779
|
+
} else {
|
|
780
|
+
reject(new Error(`${label} failed with code ${code}`));
|
|
781
|
+
}
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
child.on('error', reject);
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function hasFunctionsProject(functionsDir: string, backendLanguage: BackendLanguage): boolean {
|
|
789
|
+
if (backendLanguage === 'typescript') {
|
|
790
|
+
return fs.existsSync(path.join(functionsDir, 'package.json'));
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
return fs.existsSync(path.join(functionsDir, 'host.json'));
|
|
794
|
+
}
|