swallowkit 1.0.0-beta.9 → 1.0.0
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/README.ja.md +87 -181
- package/README.md +87 -238
- package/dist/__tests__/fixtures.d.ts +12 -0
- package/dist/__tests__/fixtures.d.ts.map +1 -1
- package/dist/__tests__/fixtures.js +101 -0
- package/dist/__tests__/fixtures.js.map +1 -1
- package/dist/cli/commands/add-auth.d.ts +15 -0
- package/dist/cli/commands/add-auth.d.ts.map +1 -0
- package/dist/cli/commands/add-auth.js +790 -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 +164 -0
- package/dist/cli/commands/add-connector.js.map +1 -0
- package/dist/cli/commands/cosmos-worktree-isolation.d.ts +25 -0
- package/dist/cli/commands/cosmos-worktree-isolation.d.ts.map +1 -0
- package/dist/cli/commands/cosmos-worktree-isolation.js +124 -0
- package/dist/cli/commands/cosmos-worktree-isolation.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 +20 -82
- package/dist/cli/commands/create-model.js.map +1 -1
- package/dist/cli/commands/dev-seeds.d.ts +27 -0
- package/dist/cli/commands/dev-seeds.d.ts.map +1 -1
- package/dist/cli/commands/dev-seeds.js +188 -6
- package/dist/cli/commands/dev-seeds.js.map +1 -1
- package/dist/cli/commands/dev.d.ts +30 -0
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +495 -129
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/init.d.ts +69 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +788 -766
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts +24 -3
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +408 -189
- 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 +335 -58
- package/dist/cli/index.js.map +1 -1
- package/dist/core/config.d.ts +13 -3
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +151 -8
- 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/auth-operations.d.ts +61 -0
- package/dist/core/operations/auth-operations.d.ts.map +1 -0
- package/dist/core/operations/auth-operations.js +272 -0
- package/dist/core/operations/auth-operations.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 +185 -0
- package/dist/core/operations/create-model.js.map +1 -0
- package/dist/core/operations/file-session.d.ts +56 -0
- package/dist/core/operations/file-session.d.ts.map +1 -0
- package/dist/core/operations/file-session.js +203 -0
- package/dist/core/operations/file-session.js.map +1 -0
- package/dist/core/operations/provision-operations.d.ts +52 -0
- package/dist/core/operations/provision-operations.d.ts.map +1 -0
- package/dist/core/operations/provision-operations.js +211 -0
- package/dist/core/operations/provision-operations.js.map +1 -0
- package/dist/core/operations/runtime.d.ts +37 -0
- package/dist/core/operations/runtime.d.ts.map +1 -0
- package/dist/core/operations/runtime.js +248 -0
- package/dist/core/operations/runtime.js.map +1 -0
- package/dist/core/operations/scaffold-apply.d.ts +21 -0
- package/dist/core/operations/scaffold-apply.d.ts.map +1 -0
- package/dist/core/operations/scaffold-apply.js +57 -0
- package/dist/core/operations/scaffold-apply.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 +51 -0
- package/dist/core/operations/scaffold-machine.js.map +1 -0
- package/dist/core/operations/scaffold-plan.d.ts +43 -0
- package/dist/core/operations/scaffold-plan.d.ts.map +1 -0
- package/dist/core/operations/scaffold-plan.js +187 -0
- package/dist/core/operations/scaffold-plan.js.map +1 -0
- package/dist/core/project/agent-instructions.d.ts +29 -0
- package/dist/core/project/agent-instructions.d.ts.map +1 -0
- package/dist/core/project/agent-instructions.js +160 -0
- package/dist/core/project/agent-instructions.js.map +1 -0
- package/dist/core/project/artifacts.d.ts +58 -0
- package/dist/core/project/artifacts.d.ts.map +1 -0
- package/dist/core/project/artifacts.js +179 -0
- package/dist/core/project/artifacts.js.map +1 -0
- package/dist/core/project/boundaries.d.ts +50 -0
- package/dist/core/project/boundaries.d.ts.map +1 -0
- package/dist/core/project/boundaries.js +117 -0
- package/dist/core/project/boundaries.js.map +1 -0
- package/dist/core/project/capabilities.d.ts +58 -0
- package/dist/core/project/capabilities.d.ts.map +1 -0
- package/dist/core/project/capabilities.js +135 -0
- package/dist/core/project/capabilities.js.map +1 -0
- package/dist/core/project/drift.d.ts +29 -0
- package/dist/core/project/drift.d.ts.map +1 -0
- package/dist/core/project/drift.js +188 -0
- package/dist/core/project/drift.js.map +1 -0
- package/dist/core/project/infra.d.ts +49 -0
- package/dist/core/project/infra.d.ts.map +1 -0
- package/dist/core/project/infra.js +167 -0
- package/dist/core/project/infra.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/state.d.ts +15 -0
- package/dist/core/project/state.d.ts.map +1 -0
- package/dist/core/project/state.js +106 -0
- package/dist/core/project/state.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 +274 -0
- package/dist/core/project/validation.js.map +1 -0
- package/dist/core/project/workflows.d.ts +43 -0
- package/dist/core/project/workflows.d.ts.map +1 -0
- package/dist/core/project/workflows.js +299 -0
- package/dist/core/project/workflows.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 +1774 -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 +4 -3
- package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
- package/dist/core/scaffold/functions-generator.js +405 -134
- package/dist/core/scaffold/functions-generator.js.map +1 -1
- package/dist/core/scaffold/model-parser.d.ts +20 -0
- package/dist/core/scaffold/model-parser.d.ts.map +1 -1
- package/dist/core/scaffold/model-parser.js +229 -78
- package/dist/core/scaffold/model-parser.js.map +1 -1
- package/dist/core/scaffold/native-schema-generator.d.ts +19 -0
- package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -0
- package/dist/core/scaffold/native-schema-generator.js +753 -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 +152 -8
- package/dist/core/scaffold/nextjs-generator.js.map +1 -1
- package/dist/core/scaffold/ui-generator.d.ts +17 -4
- package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
- package/dist/core/scaffold/ui-generator.js +174 -34
- package/dist/core/scaffold/ui-generator.js.map +1 -1
- package/dist/core/verify/index.d.ts +69 -0
- package/dist/core/verify/index.d.ts.map +1 -0
- package/dist/core/verify/index.js +412 -0
- package/dist/core/verify/index.js.map +1 -0
- package/dist/machine/contracts.d.ts +24 -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 +14 -0
- package/dist/machine/errors.d.ts.map +1 -0
- package/dist/machine/errors.js +42 -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 +566 -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 +425 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/types/index.d.ts +105 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/package-manager.d.ts +7 -11
- package/dist/utils/package-manager.d.ts.map +1 -1
- package/dist/utils/package-manager.js +42 -17
- 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 +15 -17
- package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +716 -22
- package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +19 -7
- package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +20 -12
- package/src/__tests__/agent-instructions.test.ts +237 -0
- package/src/__tests__/agent-loop-phase34.test.ts +513 -0
- package/src/__tests__/auth.test.ts +770 -0
- package/src/__tests__/bff-swa-auth.test.ts +275 -0
- package/src/__tests__/config.test.ts +176 -3
- package/src/__tests__/connector-functions-generator.test.ts +288 -0
- package/src/__tests__/connector-mock-server.test.ts +459 -0
- package/src/__tests__/connector-model-bff.test.ts +162 -0
- package/src/__tests__/cosmos-worktree-isolation.test.ts +149 -0
- package/src/__tests__/dev-seeds.test.ts +124 -0
- package/src/__tests__/dev.test.ts +254 -0
- package/src/__tests__/file-session.test.ts +98 -0
- package/src/__tests__/fixtures.ts +101 -0
- package/src/__tests__/functions-generator.test.ts +137 -1
- package/src/__tests__/init.test.ts +366 -2
- package/src/__tests__/jest-globals.d.ts +8 -0
- package/src/__tests__/machine.test.ts +371 -0
- package/src/__tests__/mcp.test.ts +253 -0
- package/src/__tests__/model-parser.test.ts +142 -0
- package/src/__tests__/multi-auth.test.ts +160 -0
- package/src/__tests__/nextjs-generator.test.ts +8 -0
- package/src/__tests__/package-manager.test.ts +59 -4
- package/src/__tests__/plan-apply.test.ts +499 -0
- package/src/__tests__/python-uv.test.ts +48 -0
- package/src/__tests__/scaffold.test.ts +287 -24
- package/src/__tests__/tsconfig.json +15 -0
- package/src/__tests__/ui-generator.test.ts +104 -0
- package/src/__tests__/zod-mock-generator.test.ts +132 -0
- package/src/cli/commands/add-auth.ts +853 -0
- package/src/cli/commands/add-connector.ts +159 -0
- package/src/cli/commands/cosmos-worktree-isolation.ts +130 -0
- package/src/cli/commands/create-model.ts +21 -100
- package/src/cli/commands/dev-seeds.ts +278 -7
- package/src/cli/commands/dev.ts +607 -160
- package/src/cli/commands/init.ts +851 -786
- package/src/cli/commands/scaffold.ts +531 -236
- package/src/cli/index.ts +338 -60
- package/src/core/config.ts +145 -9
- package/src/core/mock/connector-mock-server.ts +555 -0
- package/src/core/mock/zod-mock-generator.ts +205 -0
- package/src/core/operations/auth-operations.ts +360 -0
- package/src/core/operations/create-model.ts +191 -0
- package/src/core/operations/file-session.ts +222 -0
- package/src/core/operations/provision-operations.ts +297 -0
- package/src/core/operations/runtime.ts +258 -0
- package/src/core/operations/scaffold-apply.ts +106 -0
- package/src/core/operations/scaffold-machine.ts +77 -0
- package/src/core/operations/scaffold-plan.ts +215 -0
- package/src/core/project/agent-instructions.ts +167 -0
- package/src/core/project/agent-templates/AGENTS.md +504 -0
- package/src/core/project/agent-templates/CLAUDE.md +31 -0
- package/src/core/project/agent-templates/azure-functions.instructions.md +22 -0
- package/src/core/project/agent-templates/bff-routes.instructions.md +18 -0
- package/src/core/project/agent-templates/copilot-instructions.md +27 -0
- package/src/core/project/agent-templates/shared-models.instructions.md +72 -0
- package/src/core/project/artifacts.ts +205 -0
- package/src/core/project/boundaries.ts +170 -0
- package/src/core/project/capabilities.ts +193 -0
- package/src/core/project/drift.ts +202 -0
- package/src/core/project/infra.ts +196 -0
- package/src/core/project/manifest.ts +402 -0
- package/src/core/project/state.ts +77 -0
- package/src/core/project/validation.ts +295 -0
- package/src/core/project/workflows.ts +295 -0
- package/src/core/scaffold/auth-generator.ts +1824 -0
- package/src/core/scaffold/connector-functions-generator.ts +1133 -0
- package/src/core/scaffold/functions-generator.ts +432 -134
- package/src/core/scaffold/model-parser.ts +244 -90
- package/src/core/scaffold/native-schema-generator.ts +904 -0
- package/src/core/scaffold/nextjs-generator.ts +174 -9
- package/src/core/scaffold/ui-generator.ts +191 -36
- package/src/core/verify/index.ts +468 -0
- package/src/machine/contracts.ts +33 -0
- package/src/machine/errors.ts +45 -0
- package/src/machine/index.ts +690 -0
- package/src/mcp/index.ts +423 -0
- package/src/types/index.ts +137 -0
- package/src/utils/package-manager.ts +41 -19
- package/src/utils/python-uv.ts +96 -0
- package/src/version.ts +13 -0
package/src/cli/commands/dev.ts
CHANGED
|
@@ -1,16 +1,35 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { spawn, ChildProcess } from 'child_process';
|
|
3
|
+
import * as http from 'http';
|
|
4
|
+
import * as https from 'https';
|
|
3
5
|
import * as path from 'path';
|
|
4
6
|
import * as fs from 'fs';
|
|
5
7
|
import * as os from 'os';
|
|
8
|
+
import * as net from 'net';
|
|
6
9
|
import { CosmosClient, PartitionKeyKind } from '@azure/cosmos';
|
|
7
|
-
import { ensureSwallowKitProject, getBackendLanguage } from '../../core/config';
|
|
10
|
+
import { ensureSwallowKitProject, getBackendLanguage, getAuthConfig, normalizeAuthConfig } from '../../core/config';
|
|
8
11
|
import { ModelInfo } from '../../core/scaffold/model-parser';
|
|
9
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
applyDevSeedEnvironment,
|
|
14
|
+
getContainerNameForModel,
|
|
15
|
+
getDefaultCosmosDatabaseName,
|
|
16
|
+
loadProjectModels,
|
|
17
|
+
resolveLocalCosmosConnectionInfo,
|
|
18
|
+
} from './dev-seeds';
|
|
10
19
|
import { BackendLanguage } from '../../types';
|
|
11
20
|
import { detectFromProject, getCommands } from '../../utils/package-manager';
|
|
12
|
-
|
|
13
|
-
|
|
21
|
+
import {
|
|
22
|
+
buildProjectLocalUvEnv,
|
|
23
|
+
buildProjectLocalUvInstallerEnv,
|
|
24
|
+
buildUvPipInstallArgs,
|
|
25
|
+
buildUvVenvArgs,
|
|
26
|
+
getProjectLocalUvInstallerCommand,
|
|
27
|
+
getProjectLocalUvPaths,
|
|
28
|
+
getPythonProjectRoot,
|
|
29
|
+
} from '../../utils/python-uv';
|
|
30
|
+
import { ConnectorMockServer } from '../../core/mock/connector-mock-server';
|
|
31
|
+
|
|
32
|
+
export interface DevOptions {
|
|
14
33
|
port?: string;
|
|
15
34
|
functionsPort?: string;
|
|
16
35
|
host?: string;
|
|
@@ -18,17 +37,137 @@ interface DevOptions {
|
|
|
18
37
|
verbose?: boolean;
|
|
19
38
|
noFunctions?: boolean;
|
|
20
39
|
seedEnv?: string;
|
|
40
|
+
mockConnectors?: boolean;
|
|
41
|
+
swaPort?: string;
|
|
42
|
+
noSwa?: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type ParsedDevActionOptions = DevOptions & {
|
|
46
|
+
functions?: boolean;
|
|
47
|
+
swa?: boolean;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
interface FunctionsCoreToolsCommand {
|
|
51
|
+
command: string;
|
|
52
|
+
argsPrefix: string[];
|
|
53
|
+
label: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const MINIMUM_CSHARP_CORE_TOOLS_VERSION = '4.6.0';
|
|
57
|
+
const NPM_CORE_TOOLS_PACKAGE = 'azure-functions-core-tools@4';
|
|
58
|
+
|
|
59
|
+
function normalizeParsedDevOptions(options: ParsedDevActionOptions): DevOptions {
|
|
60
|
+
return {
|
|
61
|
+
...options,
|
|
62
|
+
noFunctions: options.noFunctions ?? options.functions === false,
|
|
63
|
+
noSwa: options.noSwa ?? options.swa === false,
|
|
64
|
+
};
|
|
21
65
|
}
|
|
22
66
|
|
|
23
67
|
export function buildFunctionsStartArgs(functionsPort: string): string[] {
|
|
24
68
|
return ['start', '--port', functionsPort];
|
|
25
69
|
}
|
|
26
70
|
|
|
71
|
+
export function parseCoreToolsVersion(output: string): string | null {
|
|
72
|
+
const match = output.match(/\d+\.\d+\.\d+/);
|
|
73
|
+
return match ? match[0] : null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function compareVersionNumbers(left: string, right: string): number {
|
|
77
|
+
const leftParts = left.split('.').map((value) => Number.parseInt(value, 10));
|
|
78
|
+
const rightParts = right.split('.').map((value) => Number.parseInt(value, 10));
|
|
79
|
+
const length = Math.max(leftParts.length, rightParts.length);
|
|
80
|
+
|
|
81
|
+
for (let index = 0; index < length; index += 1) {
|
|
82
|
+
const leftPart = leftParts[index] ?? 0;
|
|
83
|
+
const rightPart = rightParts[index] ?? 0;
|
|
84
|
+
|
|
85
|
+
if (leftPart !== rightPart) {
|
|
86
|
+
return leftPart - rightPart;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function buildFunctionsCoreToolsCommand(
|
|
94
|
+
backendLanguage: BackendLanguage,
|
|
95
|
+
installedVersion: string | null
|
|
96
|
+
): FunctionsCoreToolsCommand {
|
|
97
|
+
if (
|
|
98
|
+
backendLanguage === 'csharp' &&
|
|
99
|
+
(!installedVersion || compareVersionNumbers(installedVersion, MINIMUM_CSHARP_CORE_TOOLS_VERSION) < 0)
|
|
100
|
+
) {
|
|
101
|
+
const reason = installedVersion
|
|
102
|
+
? `installed func ${installedVersion} is too old for C# isolated`
|
|
103
|
+
: 'func is not installed';
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
command: 'npm',
|
|
107
|
+
argsPrefix: ['exec', '--yes', NPM_CORE_TOOLS_PACKAGE, '--'],
|
|
108
|
+
label: `npm exec ${NPM_CORE_TOOLS_PACKAGE} (${reason})`,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
command: 'func',
|
|
114
|
+
argsPrefix: [],
|
|
115
|
+
label: installedVersion ? `func ${installedVersion}` : 'func',
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
27
119
|
export function buildNextDevArgs(pm: string, port: string): string[] {
|
|
28
120
|
const baseArgs = ['next', 'dev', '--port', port, '--webpack'];
|
|
29
121
|
return pm === 'pnpm' ? ['exec', ...baseArgs] : baseArgs;
|
|
30
122
|
}
|
|
31
123
|
|
|
124
|
+
export function buildFunctionsBaseUrl(host: string | undefined, functionsPort: string): string {
|
|
125
|
+
return `http://${host || 'localhost'}:${functionsPort}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function buildSwaStartArgs(host: string | undefined, nextPort: string, swaPort: string): string[] {
|
|
129
|
+
return [
|
|
130
|
+
'start',
|
|
131
|
+
`http://${host || 'localhost'}:${nextPort}`,
|
|
132
|
+
'--swa-config-location',
|
|
133
|
+
'.',
|
|
134
|
+
'--port',
|
|
135
|
+
swaPort,
|
|
136
|
+
];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function getSwaCliInstallCommand(pm: string): string {
|
|
140
|
+
return pm === 'pnpm'
|
|
141
|
+
? 'pnpm add -Dw @azure/static-web-apps-cli'
|
|
142
|
+
: 'npm install -D @azure/static-web-apps-cli';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function getFunctionsReadinessTimeoutMs(backendLanguage: BackendLanguage): number {
|
|
146
|
+
return backendLanguage === 'csharp' ? 90_000 : 30_000;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function waitForHttpServerReady(
|
|
150
|
+
url: string,
|
|
151
|
+
timeoutMs = 30_000,
|
|
152
|
+
intervalMs = 500
|
|
153
|
+
): Promise<boolean> {
|
|
154
|
+
const deadline = Date.now() + timeoutMs;
|
|
155
|
+
|
|
156
|
+
while (Date.now() <= deadline) {
|
|
157
|
+
if (await probeHttpServer(url)) {
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (Date.now() + intervalMs > deadline) {
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return probeHttpServer(url);
|
|
169
|
+
}
|
|
170
|
+
|
|
32
171
|
export function getPythonVirtualEnvPaths(functionsDir: string): {
|
|
33
172
|
venvDir: string;
|
|
34
173
|
binDir: string;
|
|
@@ -45,6 +184,13 @@ export function getPythonVirtualEnvPaths(functionsDir: string): {
|
|
|
45
184
|
return { venvDir, binDir, pythonExecutable };
|
|
46
185
|
}
|
|
47
186
|
|
|
187
|
+
export function getCSharpFunctionsBuildArtifactPaths(functionsDir: string): string[] {
|
|
188
|
+
return [
|
|
189
|
+
path.join(functionsDir, 'bin'),
|
|
190
|
+
path.join(functionsDir, 'obj'),
|
|
191
|
+
];
|
|
192
|
+
}
|
|
193
|
+
|
|
48
194
|
export function buildPythonFunctionsEnv(baseEnv: NodeJS.ProcessEnv, functionsDir: string): NodeJS.ProcessEnv {
|
|
49
195
|
const { venvDir, binDir, pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
|
|
50
196
|
const pathKey = getPathEnvKey(baseEnv);
|
|
@@ -58,6 +204,16 @@ export function buildPythonFunctionsEnv(baseEnv: NodeJS.ProcessEnv, functionsDir
|
|
|
58
204
|
};
|
|
59
205
|
}
|
|
60
206
|
|
|
207
|
+
export function buildFunctionsCosmosEnvironment(
|
|
208
|
+
baseEnv: NodeJS.ProcessEnv,
|
|
209
|
+
databaseName: string
|
|
210
|
+
): NodeJS.ProcessEnv {
|
|
211
|
+
return {
|
|
212
|
+
...baseEnv,
|
|
213
|
+
COSMOS_DB_DATABASE_NAME: databaseName,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
61
217
|
function getPathEnvKey(env: NodeJS.ProcessEnv): string {
|
|
62
218
|
return Object.keys(env).find((key) => key.toUpperCase() === 'PATH') || 'PATH';
|
|
63
219
|
}
|
|
@@ -79,10 +235,20 @@ async function checkCoreTools(): Promise<boolean> {
|
|
|
79
235
|
return checkCommand('func', ['--version']);
|
|
80
236
|
}
|
|
81
237
|
|
|
82
|
-
async function checkCommand(
|
|
238
|
+
async function checkCommand(
|
|
239
|
+
command: string,
|
|
240
|
+
args: string[] = ['--version'],
|
|
241
|
+
options?: {
|
|
242
|
+
cwd?: string;
|
|
243
|
+
env?: NodeJS.ProcessEnv;
|
|
244
|
+
shell?: boolean;
|
|
245
|
+
}
|
|
246
|
+
): Promise<boolean> {
|
|
83
247
|
return new Promise((resolve) => {
|
|
84
248
|
const checkProcess = spawn(command, args, {
|
|
85
|
-
|
|
249
|
+
cwd: options?.cwd,
|
|
250
|
+
env: options?.env ?? process.env,
|
|
251
|
+
shell: options?.shell ?? true,
|
|
86
252
|
stdio: 'pipe',
|
|
87
253
|
});
|
|
88
254
|
|
|
@@ -96,26 +262,34 @@ async function checkCommand(command: string, args: string[] = ['--version']): Pr
|
|
|
96
262
|
});
|
|
97
263
|
}
|
|
98
264
|
|
|
99
|
-
async function
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
for (const candidate of candidates) {
|
|
111
|
-
if (await checkCommand(candidate.command, [...candidate.argsPrefix, '--version'])) {
|
|
112
|
-
return candidate;
|
|
113
|
-
}
|
|
265
|
+
async function resolveProjectLocalUvCommand(projectRoot: string): Promise<{ command: string; env: NodeJS.ProcessEnv }> {
|
|
266
|
+
const uvEnv = buildProjectLocalUvEnv(process.env, projectRoot);
|
|
267
|
+
const { localUvExecutable } = getProjectLocalUvPaths(projectRoot);
|
|
268
|
+
|
|
269
|
+
if (await checkCommand('uv', ['--version'], { shell: false })) {
|
|
270
|
+
return { command: 'uv', env: uvEnv };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (fs.existsSync(localUvExecutable) && await checkCommand(localUvExecutable, ['--version'], { shell: false })) {
|
|
274
|
+
return { command: localUvExecutable, env: uvEnv };
|
|
114
275
|
}
|
|
115
276
|
|
|
116
|
-
|
|
117
|
-
|
|
277
|
+
console.log('📦 Installing project-local uv...');
|
|
278
|
+
const installer = getProjectLocalUvInstallerCommand();
|
|
279
|
+
await runCommand(
|
|
280
|
+
installer.command,
|
|
281
|
+
installer.args,
|
|
282
|
+
projectRoot,
|
|
283
|
+
'uv installation',
|
|
284
|
+
buildProjectLocalUvInstallerEnv(process.env, projectRoot),
|
|
285
|
+
false
|
|
118
286
|
);
|
|
287
|
+
|
|
288
|
+
if (!(fs.existsSync(localUvExecutable) && await checkCommand(localUvExecutable, ['--version'], { shell: false }))) {
|
|
289
|
+
throw new Error('Failed to install project-local uv.');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return { command: localUvExecutable, env: uvEnv };
|
|
119
293
|
}
|
|
120
294
|
|
|
121
295
|
async function getCommandPath(command: string): Promise<string | null> {
|
|
@@ -129,6 +303,18 @@ async function getCommandPath(command: string): Promise<string | null> {
|
|
|
129
303
|
return firstLine || null;
|
|
130
304
|
}
|
|
131
305
|
|
|
306
|
+
async function resolveInstalledCoreToolsVersion(): Promise<string | null> {
|
|
307
|
+
if (!(await checkCoreTools())) {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
return parseCoreToolsVersion(await captureCommandOutput('func', ['--version']));
|
|
313
|
+
} catch {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
132
318
|
async function captureCommandOutput(
|
|
133
319
|
command: string,
|
|
134
320
|
args: string[],
|
|
@@ -166,6 +352,41 @@ async function captureCommandOutput(
|
|
|
166
352
|
});
|
|
167
353
|
}
|
|
168
354
|
|
|
355
|
+
async function probeHttpServer(url: string): Promise<boolean> {
|
|
356
|
+
return new Promise((resolve) => {
|
|
357
|
+
const target = new URL(url);
|
|
358
|
+
const requestFactory = target.protocol === 'https:' ? https.request : http.request;
|
|
359
|
+
let settled = false;
|
|
360
|
+
const finish = (value: boolean) => {
|
|
361
|
+
if (!settled) {
|
|
362
|
+
settled = true;
|
|
363
|
+
resolve(value);
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const request = requestFactory(
|
|
368
|
+
{
|
|
369
|
+
hostname: target.hostname,
|
|
370
|
+
port: target.port,
|
|
371
|
+
path: target.pathname || '/',
|
|
372
|
+
method: 'GET',
|
|
373
|
+
timeout: 1000,
|
|
374
|
+
},
|
|
375
|
+
(response) => {
|
|
376
|
+
response.resume();
|
|
377
|
+
finish(true);
|
|
378
|
+
}
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
request.on('timeout', () => {
|
|
382
|
+
request.destroy();
|
|
383
|
+
finish(false);
|
|
384
|
+
});
|
|
385
|
+
request.on('error', () => finish(false));
|
|
386
|
+
request.end();
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
169
390
|
async function resolvePythonRuntimeDetails(
|
|
170
391
|
functionsDir: string,
|
|
171
392
|
env: NodeJS.ProcessEnv
|
|
@@ -241,47 +462,36 @@ async function bridgePythonCoreToolsForWindowsArm64(
|
|
|
241
462
|
}
|
|
242
463
|
|
|
243
464
|
async function preparePythonFunctionsEnvironment(functionsDir: string): Promise<NodeJS.ProcessEnv> {
|
|
244
|
-
const
|
|
245
|
-
const
|
|
465
|
+
const projectRoot = getPythonProjectRoot(functionsDir);
|
|
466
|
+
const { command: uvCommand, env: uvEnv } = await resolveProjectLocalUvCommand(projectRoot);
|
|
467
|
+
const { venvDir, pythonExecutable } = getPythonVirtualEnvPaths(functionsDir);
|
|
468
|
+
const hasUsableVirtualEnv = fs.existsSync(pythonExecutable) && await checkCommand(pythonExecutable, ['--version'], {
|
|
469
|
+
cwd: functionsDir,
|
|
470
|
+
env: uvEnv,
|
|
471
|
+
shell: false,
|
|
472
|
+
});
|
|
246
473
|
|
|
247
|
-
if (!
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
} else {
|
|
252
|
-
const bootstrap = await resolvePythonBootstrapCommand();
|
|
253
|
-
console.log(`📦 Creating Python virtual environment with ${bootstrap.label}...`);
|
|
254
|
-
await runCommand(
|
|
255
|
-
bootstrap.command,
|
|
256
|
-
[...bootstrap.argsPrefix, '-m', 'venv', '.venv'],
|
|
257
|
-
functionsDir,
|
|
258
|
-
'python virtual environment setup'
|
|
259
|
-
);
|
|
474
|
+
if (!hasUsableVirtualEnv) {
|
|
475
|
+
const venvArgs = buildUvVenvArgs('.venv');
|
|
476
|
+
if (fs.existsSync(venvDir)) {
|
|
477
|
+
venvArgs.push('--clear');
|
|
260
478
|
}
|
|
261
|
-
}
|
|
262
479
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
if (hasUv) {
|
|
267
|
-
await runCommand(
|
|
268
|
-
'uv',
|
|
269
|
-
['pip', 'install', '--python', pythonExecutable, '-r', 'requirements.txt'],
|
|
270
|
-
functionsDir,
|
|
271
|
-
'python dependency installation',
|
|
272
|
-
pythonEnv
|
|
273
|
-
);
|
|
274
|
-
} else {
|
|
275
|
-
await runCommand('python', ['-m', 'pip', 'install', '--upgrade', 'pip'], functionsDir, 'python pip upgrade', pythonEnv);
|
|
276
|
-
await runCommand(
|
|
277
|
-
'python',
|
|
278
|
-
['-m', 'pip', 'install', '-r', 'requirements.txt'],
|
|
279
|
-
functionsDir,
|
|
280
|
-
'python dependency installation',
|
|
281
|
-
pythonEnv
|
|
282
|
-
);
|
|
480
|
+
console.log('📦 Creating Python virtual environment with uv...');
|
|
481
|
+
await runCommand(uvCommand, venvArgs, functionsDir, 'python virtual environment setup', uvEnv, false);
|
|
283
482
|
}
|
|
284
483
|
|
|
484
|
+
console.log('📦 Installing Python Azure Functions dependencies with uv...');
|
|
485
|
+
await runCommand(
|
|
486
|
+
uvCommand,
|
|
487
|
+
buildUvPipInstallArgs(pythonExecutable, 'requirements.txt'),
|
|
488
|
+
functionsDir,
|
|
489
|
+
'python dependency installation',
|
|
490
|
+
uvEnv,
|
|
491
|
+
false
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
const pythonEnv = buildPythonFunctionsEnv(uvEnv, functionsDir);
|
|
285
495
|
return bridgePythonCoreToolsForWindowsArm64(functionsDir, pythonEnv);
|
|
286
496
|
}
|
|
287
497
|
|
|
@@ -290,7 +500,6 @@ async function preparePythonFunctionsEnvironment(functionsDir: string): Promise<
|
|
|
290
500
|
*/
|
|
291
501
|
async function checkCosmosDBEmulator(): Promise<boolean> {
|
|
292
502
|
return new Promise((resolve) => {
|
|
293
|
-
const net = require('net');
|
|
294
503
|
const socket = new net.Socket();
|
|
295
504
|
|
|
296
505
|
const timeout = setTimeout(() => {
|
|
@@ -313,27 +522,39 @@ async function checkCosmosDBEmulator(): Promise<boolean> {
|
|
|
313
522
|
});
|
|
314
523
|
}
|
|
315
524
|
|
|
316
|
-
export
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
525
|
+
export function buildDevCommand(
|
|
526
|
+
runDevEnvironment: (options: DevOptions) => Promise<void> = startDevEnvironment,
|
|
527
|
+
verifyProject: (commandName: string, projectRoot?: string) => void = ensureSwallowKitProject
|
|
528
|
+
): Command {
|
|
529
|
+
return new Command()
|
|
530
|
+
.name('dev')
|
|
531
|
+
.description('Start SwallowKit development server (Cosmos DB + Next.js + Azure Functions)')
|
|
532
|
+
.option('-p, --port <port>', 'Next.js port', '3000')
|
|
533
|
+
.option('-f, --functions-port <port>', 'Azure Functions port', '7071')
|
|
534
|
+
.option('--host <host>', 'Host name', 'localhost')
|
|
535
|
+
.option('--open', 'Open browser automatically', false)
|
|
536
|
+
.option('--verbose', 'Show verbose logs', false)
|
|
537
|
+
.option('--no-functions', 'Skip Azure Functions startup')
|
|
538
|
+
.option('--seed-env <environment>', 'Replace Cosmos DB Emulator data from dev-seeds/<environment> before startup')
|
|
539
|
+
.option('--mock-connectors', 'Start mock server for connector models (serves Zod-generated data)', false)
|
|
540
|
+
.option('--swa-port <port>', 'SWA authentication emulator port', '4280')
|
|
541
|
+
.option('--no-swa', 'Skip the SWA authentication emulator')
|
|
542
|
+
.action(async (options: ParsedDevActionOptions) => {
|
|
543
|
+
const normalizedOptions = normalizeParsedDevOptions(options);
|
|
544
|
+
|
|
545
|
+
// SwallowKit プロジェクトディレクトリかどうかを検証
|
|
546
|
+
verifyProject("dev");
|
|
547
|
+
|
|
548
|
+
console.log('🚀 Starting SwallowKit development environment...');
|
|
549
|
+
if (normalizedOptions.verbose) {
|
|
550
|
+
console.log('⚙️ Options:', normalizedOptions);
|
|
551
|
+
}
|
|
334
552
|
|
|
335
|
-
|
|
336
|
-
|
|
553
|
+
await runDevEnvironment(normalizedOptions);
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
export const devCommand = buildDevCommand();
|
|
337
558
|
|
|
338
559
|
interface CosmosInitializationResult {
|
|
339
560
|
endpoint: string;
|
|
@@ -344,40 +565,29 @@ interface CosmosInitializationResult {
|
|
|
344
565
|
|
|
345
566
|
async function initializeCosmosDB(databaseName: string): Promise<CosmosInitializationResult | null> {
|
|
346
567
|
try {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
568
|
+
const connectionInfoResult = resolveLocalCosmosConnectionInfo(databaseName);
|
|
569
|
+
if (!connectionInfoResult.ok) {
|
|
570
|
+
if (connectionInfoResult.reason === 'missing-local-settings') {
|
|
571
|
+
console.log('⚠️ local.settings.json not found. Skipping Cosmos DB initialization.');
|
|
572
|
+
} else if (connectionInfoResult.reason === 'missing-connection-string') {
|
|
573
|
+
console.log('⚠️ CosmosDBConnection not found in local.settings.json. Skipping Cosmos DB initialization.');
|
|
574
|
+
} else {
|
|
575
|
+
console.log('⚠️ Invalid CosmosDB connection string format.');
|
|
576
|
+
}
|
|
355
577
|
|
|
356
|
-
const localSettings = JSON.parse(fs.readFileSync(localSettingsPath, 'utf-8'));
|
|
357
|
-
const connectionString = localSettings.Values?.CosmosDBConnection;
|
|
358
|
-
const dbName = localSettings.Values?.COSMOS_DB_DATABASE_NAME || databaseName;
|
|
359
|
-
|
|
360
|
-
if (!connectionString) {
|
|
361
|
-
console.log('⚠️ CosmosDBConnection not found in local.settings.json. Skipping Cosmos DB initialization.');
|
|
362
578
|
return null;
|
|
363
579
|
}
|
|
364
580
|
|
|
365
581
|
console.log('🗄️ Initializing Cosmos DB...');
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
if (!endpointMatch || !keyMatch) {
|
|
372
|
-
console.log('⚠️ Invalid CosmosDB connection string format.');
|
|
373
|
-
return null;
|
|
582
|
+
const { endpoint, key, databaseName: dbName, worktreeIsolationEnabled } = connectionInfoResult.value;
|
|
583
|
+
console.log(`📦 Cosmos DB database: ${dbName}`);
|
|
584
|
+
if (worktreeIsolationEnabled) {
|
|
585
|
+
console.log('🌳 Worktree isolation: enabled');
|
|
374
586
|
}
|
|
375
587
|
|
|
376
|
-
const endpoint = endpointMatch[1];
|
|
377
|
-
|
|
378
588
|
const client = new CosmosClient({
|
|
379
589
|
endpoint: endpoint,
|
|
380
|
-
key
|
|
590
|
+
key,
|
|
381
591
|
});
|
|
382
592
|
|
|
383
593
|
// Create database if not exists
|
|
@@ -392,11 +602,11 @@ async function initializeCosmosDB(databaseName: string): Promise<CosmosInitializ
|
|
|
392
602
|
let containerCreated = false;
|
|
393
603
|
|
|
394
604
|
try {
|
|
395
|
-
console.log(`🔧 Creating container "${containerName}" with partition key
|
|
605
|
+
console.log(`🔧 Creating container "${containerName}" with partition key ${model.partitionKey}...`);
|
|
396
606
|
const containerResponse = await database.containers.createIfNotExists({
|
|
397
607
|
id: containerName,
|
|
398
608
|
partitionKey: {
|
|
399
|
-
paths: [
|
|
609
|
+
paths: [model.partitionKey],
|
|
400
610
|
kind: PartitionKeyKind.Hash,
|
|
401
611
|
version: 2
|
|
402
612
|
}
|
|
@@ -415,7 +625,7 @@ async function initializeCosmosDB(databaseName: string): Promise<CosmosInitializ
|
|
|
415
625
|
const containerResponse = await database.containers.createIfNotExists({
|
|
416
626
|
id: containerName,
|
|
417
627
|
partitionKey: {
|
|
418
|
-
paths: [
|
|
628
|
+
paths: [model.partitionKey]
|
|
419
629
|
}
|
|
420
630
|
});
|
|
421
631
|
console.log(`✅ Container "${containerName}" ready (status: ${containerResponse.statusCode})`);
|
|
@@ -431,7 +641,7 @@ async function initializeCosmosDB(databaseName: string): Promise<CosmosInitializ
|
|
|
431
641
|
}
|
|
432
642
|
|
|
433
643
|
console.log('✅ Cosmos DB initialization complete\n');
|
|
434
|
-
return { endpoint, key
|
|
644
|
+
return { endpoint, key, databaseName: dbName, models };
|
|
435
645
|
} catch (error: any) {
|
|
436
646
|
console.error('⚠️ Cosmos DB initialization failed:', error.message);
|
|
437
647
|
if (error.stack) {
|
|
@@ -445,19 +655,58 @@ async function initializeCosmosDB(databaseName: string): Promise<CosmosInitializ
|
|
|
445
655
|
async function startDevEnvironment(options: DevOptions) {
|
|
446
656
|
const port = options.port || '3000';
|
|
447
657
|
const functionsPort = options.functionsPort || '7071';
|
|
658
|
+
const swaPort = options.swaPort || '4280';
|
|
448
659
|
|
|
449
660
|
// Detect package manager from project lockfile
|
|
450
661
|
const pm = detectFromProject();
|
|
451
662
|
const pmCmd = getCommands(pm);
|
|
452
663
|
const backendLanguage = getBackendLanguage();
|
|
664
|
+
const authConfig = getAuthConfig();
|
|
665
|
+
const normalizedAuth = normalizeAuthConfig(authConfig);
|
|
666
|
+
const useSwaEmulator = Boolean(normalizedAuth && Object.values(normalizedAuth.schemes).some(scheme => scheme.provider === 'swa')) && !options.noSwa;
|
|
667
|
+
const swaCommand = useSwaEmulator ? await resolveSwaCliCommand(process.cwd()) : null;
|
|
668
|
+
|
|
669
|
+
if (useSwaEmulator && !swaCommand) {
|
|
670
|
+
const installCommand = getSwaCliInstallCommand(pm);
|
|
671
|
+
console.error('❌ SWA authentication requires Azure Static Web Apps CLI for local development.');
|
|
672
|
+
console.error(' Install it in this project and retry:');
|
|
673
|
+
console.error(`\n ${installCommand}\n`);
|
|
674
|
+
process.exitCode = 1;
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
453
677
|
|
|
454
678
|
// プロセスを管理する配列
|
|
455
679
|
const processes: ChildProcess[] = [];
|
|
456
680
|
let functionsEnv: NodeJS.ProcessEnv = process.env;
|
|
681
|
+
let mockServer: ConnectorMockServer | null = null;
|
|
682
|
+
let envLocalPath = '';
|
|
683
|
+
let envLocalDefaultUrl = ''; // default Functions URL to restore on shutdown
|
|
684
|
+
const functionsBaseUrl = buildFunctionsBaseUrl(options.host, functionsPort);
|
|
685
|
+
let functionsReadinessPromise: Promise<boolean> | null = null;
|
|
686
|
+
let functionsReady = !!options.noFunctions;
|
|
457
687
|
|
|
458
688
|
// Cleanup processes on Ctrl+C
|
|
459
|
-
process.on('SIGINT', () => {
|
|
689
|
+
process.on('SIGINT', async () => {
|
|
460
690
|
console.log('\n🛑 Stopping development servers...');
|
|
691
|
+
// Restore .env.local to default Functions port on shutdown
|
|
692
|
+
if (envLocalPath && envLocalDefaultUrl) {
|
|
693
|
+
try {
|
|
694
|
+
if (fs.existsSync(envLocalPath)) {
|
|
695
|
+
const content = fs.readFileSync(envLocalPath, 'utf-8');
|
|
696
|
+
if (content.includes('BACKEND_FUNCTIONS_BASE_URL=') &&
|
|
697
|
+
!content.includes(`BACKEND_FUNCTIONS_BASE_URL=${envLocalDefaultUrl}`)) {
|
|
698
|
+
const restored = content.replace(
|
|
699
|
+
/^BACKEND_FUNCTIONS_BASE_URL=.*/m,
|
|
700
|
+
`BACKEND_FUNCTIONS_BASE_URL=${envLocalDefaultUrl}`
|
|
701
|
+
);
|
|
702
|
+
fs.writeFileSync(envLocalPath, restored, 'utf-8');
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
} catch { /* ignore */ }
|
|
706
|
+
}
|
|
707
|
+
if (mockServer) {
|
|
708
|
+
await mockServer.stop();
|
|
709
|
+
}
|
|
461
710
|
processes.forEach((proc) => {
|
|
462
711
|
if (proc && !proc.killed) {
|
|
463
712
|
proc.kill();
|
|
@@ -487,11 +736,14 @@ async function startDevEnvironment(options: DevOptions) {
|
|
|
487
736
|
const functionsDir = path.join(process.cwd(), 'functions');
|
|
488
737
|
const hasFunctions = fs.existsSync(functionsDir) && hasFunctionsProject(functionsDir, backendLanguage);
|
|
489
738
|
|
|
739
|
+
let functionsCoreToolsCommand: FunctionsCoreToolsCommand | null = null;
|
|
740
|
+
let installedCoreToolsVersion: string | null = null;
|
|
741
|
+
|
|
490
742
|
if (hasFunctions && !options.noFunctions) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
if (!
|
|
743
|
+
installedCoreToolsVersion = await resolveInstalledCoreToolsVersion();
|
|
744
|
+
functionsCoreToolsCommand = buildFunctionsCoreToolsCommand(backendLanguage, installedCoreToolsVersion);
|
|
745
|
+
|
|
746
|
+
if (functionsCoreToolsCommand.command === 'func' && !installedCoreToolsVersion) {
|
|
495
747
|
console.log('');
|
|
496
748
|
console.log('⚠️ Azure Functions Core Tools not found.');
|
|
497
749
|
console.log('');
|
|
@@ -540,6 +792,8 @@ async function startDevEnvironment(options: DevOptions) {
|
|
|
540
792
|
// Skip Azure Functions startup
|
|
541
793
|
options.noFunctions = true;
|
|
542
794
|
}
|
|
795
|
+
} else if (functionsCoreToolsCommand.command !== 'func') {
|
|
796
|
+
console.log(`ℹ️ Using ${functionsCoreToolsCommand.label}.`);
|
|
543
797
|
}
|
|
544
798
|
|
|
545
799
|
if (!options.noFunctions) {
|
|
@@ -559,10 +813,7 @@ async function startDevEnvironment(options: DevOptions) {
|
|
|
559
813
|
}
|
|
560
814
|
|
|
561
815
|
// Initialize Cosmos DB before starting Functions
|
|
562
|
-
const
|
|
563
|
-
const appName = packageJson.name || 'App';
|
|
564
|
-
const databaseName = `${appName.charAt(0).toUpperCase() + appName.slice(1)}Database`;
|
|
565
|
-
|
|
816
|
+
const databaseName = getDefaultCosmosDatabaseName();
|
|
566
817
|
const cosmosInitialization = await initializeCosmosDB(databaseName);
|
|
567
818
|
if (options.seedEnv && cosmosInitialization) {
|
|
568
819
|
await applyDevSeedEnvironment({
|
|
@@ -586,43 +837,65 @@ async function startDevEnvironment(options: DevOptions) {
|
|
|
586
837
|
await runCommand(pm, ['install'], functionsDir, `${pm} install`);
|
|
587
838
|
}
|
|
588
839
|
} else if (backendLanguage === 'csharp') {
|
|
589
|
-
console.log('
|
|
590
|
-
await runCommand('dotnet', ['build'], functionsDir, 'dotnet build');
|
|
840
|
+
console.log('ℹ️ C# Azure Functions can take longer on cold start while the worker builds.');
|
|
591
841
|
} else {
|
|
592
842
|
functionsEnv = await preparePythonFunctionsEnvironment(functionsDir);
|
|
593
843
|
}
|
|
844
|
+
|
|
845
|
+
if (cosmosInitialization) {
|
|
846
|
+
functionsEnv = buildFunctionsCosmosEnvironment(functionsEnv, cosmosInitialization.databaseName);
|
|
847
|
+
}
|
|
594
848
|
}
|
|
595
849
|
}
|
|
596
850
|
|
|
597
851
|
if (hasFunctions && !options.noFunctions) {
|
|
598
852
|
// Build shared package before starting Functions
|
|
599
853
|
const sharedDir = path.join(process.cwd(), 'shared');
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
854
|
+
const sharedPkgPath = path.join(sharedDir, 'package.json');
|
|
855
|
+
if (fs.existsSync(sharedDir) && fs.existsSync(sharedPkgPath)) {
|
|
856
|
+
const sharedPkg = JSON.parse(fs.readFileSync(sharedPkgPath, 'utf-8'));
|
|
857
|
+
if (sharedPkg.scripts?.build) {
|
|
858
|
+
console.log('📦 Building shared package...');
|
|
859
|
+
const filterArgs = pm === 'pnpm'
|
|
860
|
+
? ['run', '--filter', 'shared', 'build']
|
|
861
|
+
: ['run', '--workspace=shared', 'build'];
|
|
862
|
+
const sharedBuild = spawn(pm, filterArgs, {
|
|
863
|
+
cwd: process.cwd(),
|
|
864
|
+
shell: true,
|
|
865
|
+
stdio: 'inherit',
|
|
866
|
+
});
|
|
610
867
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
868
|
+
await new Promise<void>((resolve, reject) => {
|
|
869
|
+
sharedBuild.on('close', (code) => {
|
|
870
|
+
if (code === 0) {
|
|
871
|
+
console.log('✅ Shared package built successfully');
|
|
872
|
+
resolve();
|
|
873
|
+
} else {
|
|
874
|
+
reject(new Error(`Shared package build failed with code ${code}`));
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
sharedBuild.on('error', reject);
|
|
619
878
|
});
|
|
620
|
-
|
|
621
|
-
|
|
879
|
+
} else {
|
|
880
|
+
console.log('⚠️ Shared package has no build script — skipping build. Run "swallowkit add-auth" to fix.');
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// Build TypeScript functions after shared package (functions import from shared)
|
|
885
|
+
if (backendLanguage === 'typescript') {
|
|
886
|
+
const functionsPkgPath = path.join(functionsDir, 'package.json');
|
|
887
|
+
if (fs.existsSync(functionsPkgPath)) {
|
|
888
|
+
const functionsPkg = JSON.parse(fs.readFileSync(functionsPkgPath, 'utf-8'));
|
|
889
|
+
if (functionsPkg.scripts?.build) {
|
|
890
|
+
console.log('📦 Building TypeScript Azure Functions...');
|
|
891
|
+
await runCommand(pm, ['run', 'build'], functionsDir, `${pm} run build`);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
622
894
|
}
|
|
623
895
|
|
|
624
896
|
// Azure Functions を起動
|
|
625
|
-
const
|
|
897
|
+
const functionsCommand = functionsCoreToolsCommand ?? buildFunctionsCoreToolsCommand(backendLanguage, installedCoreToolsVersion);
|
|
898
|
+
const funcProcess = spawn(functionsCommand.command, [...functionsCommand.argsPrefix, ...buildFunctionsStartArgs(functionsPort)], {
|
|
626
899
|
cwd: functionsDir,
|
|
627
900
|
shell: true,
|
|
628
901
|
stdio: 'pipe', // Always pipe to capture output
|
|
@@ -683,7 +956,11 @@ async function startDevEnvironment(options: DevOptions) {
|
|
|
683
956
|
}
|
|
684
957
|
});
|
|
685
958
|
|
|
686
|
-
console.log(
|
|
959
|
+
console.log(`⏳ Waiting for Azure Functions to accept requests at ${functionsBaseUrl}...`);
|
|
960
|
+
functionsReadinessPromise = waitForHttpServerReady(
|
|
961
|
+
functionsBaseUrl,
|
|
962
|
+
getFunctionsReadinessTimeoutMs(backendLanguage)
|
|
963
|
+
);
|
|
687
964
|
} else if (!hasFunctions) {
|
|
688
965
|
console.log('');
|
|
689
966
|
console.log('ℹ️ functions/ directory not found. Starting Next.js only.');
|
|
@@ -695,25 +972,99 @@ async function startDevEnvironment(options: DevOptions) {
|
|
|
695
972
|
console.log('');
|
|
696
973
|
console.log('🚀 Starting Next.js development server...');
|
|
697
974
|
|
|
975
|
+
// Mock connector server — start proxy if --mock-connectors is enabled
|
|
976
|
+
let bffTargetPort = functionsPort;
|
|
977
|
+
|
|
978
|
+
if (options.mockConnectors) {
|
|
979
|
+
const allModels = await loadProjectModels();
|
|
980
|
+
const connectorModels = allModels.filter((m) => m.connectorConfig);
|
|
981
|
+
|
|
982
|
+
// Resolve auth config — auth functions use RDB connectors, mocked alongside other models
|
|
983
|
+
let mockAuthConfig: { jwtSecret: string; tokenExpiry?: string; customJwt?: { userTable: string; loginIdColumn: string; passwordHashColumn: string; rolesColumn: string }; defaultPolicy?: "authenticated" | "anonymous" } | undefined;
|
|
984
|
+
if (authConfig?.provider === 'custom-jwt' && authConfig.customJwt) {
|
|
985
|
+
// Read JWT_SECRET from functions/local.settings.json if available
|
|
986
|
+
let jwtSecret = 'dev-jwt-secret-change-in-production-min-32-chars!!';
|
|
987
|
+
try {
|
|
988
|
+
const localSettingsPath = path.join(process.cwd(), 'functions', 'local.settings.json');
|
|
989
|
+
if (fs.existsSync(localSettingsPath)) {
|
|
990
|
+
const localSettings = JSON.parse(fs.readFileSync(localSettingsPath, 'utf-8'));
|
|
991
|
+
jwtSecret = localSettings.Values?.[authConfig.customJwt.jwtSecretEnv || 'JWT_SECRET'] || jwtSecret;
|
|
992
|
+
}
|
|
993
|
+
} catch { /* ignore */ }
|
|
994
|
+
mockAuthConfig = {
|
|
995
|
+
jwtSecret,
|
|
996
|
+
tokenExpiry: authConfig.customJwt.tokenExpiry,
|
|
997
|
+
customJwt: {
|
|
998
|
+
userTable: authConfig.customJwt.userTable,
|
|
999
|
+
loginIdColumn: authConfig.customJwt.loginIdColumn,
|
|
1000
|
+
passwordHashColumn: authConfig.customJwt.passwordHashColumn,
|
|
1001
|
+
rolesColumn: authConfig.customJwt.rolesColumn,
|
|
1002
|
+
},
|
|
1003
|
+
defaultPolicy: authConfig.authorization?.defaultPolicy === "public" ? "anonymous" :
|
|
1004
|
+
authConfig.authorization?.defaultPolicy === "authenticated" || authConfig.authorization?.defaultPolicy === "anonymous"
|
|
1005
|
+
? authConfig.authorization.defaultPolicy : undefined,
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
if (connectorModels.length > 0 || mockAuthConfig) {
|
|
1010
|
+
const mockPort = parseInt(functionsPort, 10) + 1;
|
|
1011
|
+
mockServer = new ConnectorMockServer({
|
|
1012
|
+
port: mockPort,
|
|
1013
|
+
functionsTarget: `${options.host || 'localhost'}:${functionsPort}`,
|
|
1014
|
+
connectorModels,
|
|
1015
|
+
allModels,
|
|
1016
|
+
seedEnv: options.seedEnv,
|
|
1017
|
+
host: options.host || 'localhost',
|
|
1018
|
+
authConfig: mockAuthConfig,
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
await mockServer.start();
|
|
1022
|
+
bffTargetPort = String(mockPort);
|
|
1023
|
+
|
|
1024
|
+
const modelCount = connectorModels.length + (mockAuthConfig ? 1 : 0);
|
|
1025
|
+
console.log('');
|
|
1026
|
+
console.log(`🔌 Mock server started (port: ${mockPort}) — ${modelCount} model(s) mocked via Zod/seed data`);
|
|
1027
|
+
for (const m of connectorModels) {
|
|
1028
|
+
const ops = m.connectorConfig!.operations.join(', ');
|
|
1029
|
+
console.log(` - ${m.name} [${ops}]`);
|
|
1030
|
+
}
|
|
1031
|
+
if (mockAuthConfig) {
|
|
1032
|
+
console.log(` - auth [login, me, logout]`);
|
|
1033
|
+
}
|
|
1034
|
+
console.log(` Other routes → proxied to Azure Functions (port: ${functionsPort})`);
|
|
1035
|
+
} else {
|
|
1036
|
+
console.log('');
|
|
1037
|
+
console.log('ℹ️ --mock-connectors specified but no connector models found. Skipping mock server.');
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
698
1041
|
// 5. Start Next.js development server
|
|
699
1042
|
const nextArgs = buildNextDevArgs(pm, port);
|
|
700
1043
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
1044
|
+
// Ensure .env.local points to bffTargetPort so Next.js reads the correct backend URL.
|
|
1045
|
+
// When --mock-connectors is active, bffTargetPort = mock port (7072); otherwise = Functions port (7071).
|
|
1046
|
+
// Next.js may load .env.local values that override spawn env vars, so we must keep them in sync.
|
|
1047
|
+
envLocalPath = path.join(process.cwd(), '.env.local');
|
|
1048
|
+
envLocalDefaultUrl = functionsBaseUrl;
|
|
1049
|
+
const bffTargetUrl = buildFunctionsBaseUrl(options.host, bffTargetPort);
|
|
1050
|
+
try {
|
|
1051
|
+
if (fs.existsSync(envLocalPath)) {
|
|
1052
|
+
const envContent = fs.readFileSync(envLocalPath, 'utf-8');
|
|
1053
|
+
if (envContent.includes('BACKEND_FUNCTIONS_BASE_URL=') &&
|
|
1054
|
+
!envContent.includes(`BACKEND_FUNCTIONS_BASE_URL=${bffTargetUrl}`)) {
|
|
1055
|
+
const updated = envContent.replace(
|
|
1056
|
+
/^BACKEND_FUNCTIONS_BASE_URL=.*/m,
|
|
1057
|
+
`BACKEND_FUNCTIONS_BASE_URL=${bffTargetUrl}`
|
|
1058
|
+
);
|
|
1059
|
+
fs.writeFileSync(envLocalPath, updated, 'utf-8');
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
} catch { /* ignore */ }
|
|
712
1063
|
|
|
713
1064
|
const nextEnv: NodeJS.ProcessEnv = {
|
|
714
1065
|
...process.env,
|
|
715
|
-
BACKEND_FUNCTIONS_BASE_URL:
|
|
716
|
-
FUNCTIONS_BASE_URL:
|
|
1066
|
+
BACKEND_FUNCTIONS_BASE_URL: bffTargetUrl,
|
|
1067
|
+
FUNCTIONS_BASE_URL: bffTargetUrl,
|
|
717
1068
|
};
|
|
718
1069
|
|
|
719
1070
|
const nextProcess = spawn(pm === 'pnpm' ? 'pnpm' : 'npx', nextArgs, {
|
|
@@ -735,6 +1086,9 @@ async function startDevEnvironment(options: DevOptions) {
|
|
|
735
1086
|
console.log(`\n⏹️ Next.js exited (exit code: ${code})`);
|
|
736
1087
|
}
|
|
737
1088
|
// Exit all processes when Next.js exits
|
|
1089
|
+
if (mockServer) {
|
|
1090
|
+
mockServer.stop().catch(() => {});
|
|
1091
|
+
}
|
|
738
1092
|
processes.forEach((proc) => {
|
|
739
1093
|
if (proc && !proc.killed) {
|
|
740
1094
|
proc.kill();
|
|
@@ -743,18 +1097,93 @@ async function startDevEnvironment(options: DevOptions) {
|
|
|
743
1097
|
process.exit(code || 0);
|
|
744
1098
|
});
|
|
745
1099
|
|
|
1100
|
+
if (functionsReadinessPromise) {
|
|
1101
|
+
functionsReady = await functionsReadinessPromise;
|
|
1102
|
+
console.log('');
|
|
1103
|
+
if (functionsReady) {
|
|
1104
|
+
console.log(`✅ Azure Functions ready (port: ${functionsPort})`);
|
|
1105
|
+
} else {
|
|
1106
|
+
console.log(`⚠️ Azure Functions is still starting: ${functionsBaseUrl}`);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
let swaReady = false;
|
|
1111
|
+
if (useSwaEmulator && swaCommand) {
|
|
1112
|
+
const nextUrl = `http://${options.host || 'localhost'}:${port}`;
|
|
1113
|
+
console.log(`⏳ Waiting for Next.js before starting the SWA emulator at ${nextUrl}...`);
|
|
1114
|
+
const nextReady = await waitForHttpServerReady(nextUrl);
|
|
1115
|
+
if (!nextReady) {
|
|
1116
|
+
throw new Error(`Next.js did not become ready at ${nextUrl}`);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
console.log(`🔐 Starting SWA authentication emulator (port: ${swaPort})...`);
|
|
1120
|
+
const swaProcess = spawn(swaCommand, buildSwaStartArgs(options.host, port, swaPort), {
|
|
1121
|
+
cwd: process.cwd(),
|
|
1122
|
+
shell: true,
|
|
1123
|
+
stdio: 'inherit',
|
|
1124
|
+
env: process.env,
|
|
1125
|
+
});
|
|
1126
|
+
processes.push(swaProcess);
|
|
1127
|
+
swaProcess.on('error', (error) => {
|
|
1128
|
+
console.error('❌ SWA CLI startup error:', error.message);
|
|
1129
|
+
});
|
|
1130
|
+
|
|
1131
|
+
const swaUrl = `http://${options.host || 'localhost'}:${swaPort}`;
|
|
1132
|
+
swaReady = await waitForHttpServerReady(swaUrl);
|
|
1133
|
+
if (!swaReady) {
|
|
1134
|
+
throw new Error(`SWA authentication emulator did not become ready at ${swaUrl}`);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
746
1138
|
console.log('');
|
|
747
1139
|
console.log('✅ SwallowKit development environment is running!');
|
|
748
1140
|
console.log('');
|
|
749
|
-
|
|
1141
|
+
if (useSwaEmulator) {
|
|
1142
|
+
const swaUrl = `http://${options.host || 'localhost'}:${swaPort}`;
|
|
1143
|
+
console.log('🔐 SWA authenticated app:');
|
|
1144
|
+
console.log(` ${swaUrl}`);
|
|
1145
|
+
console.log('');
|
|
1146
|
+
console.log('🔑 Local sign-in:');
|
|
1147
|
+
console.log(` ${swaUrl}/.auth/login/aad`);
|
|
1148
|
+
console.log(' Enter any username and select Login.');
|
|
1149
|
+
console.log(' The "authenticated" role is added automatically.');
|
|
1150
|
+
console.log('');
|
|
1151
|
+
console.log('📱 Next.js direct access (authentication is not emulated):');
|
|
1152
|
+
console.log(` http://${options.host || 'localhost'}:${port}`);
|
|
1153
|
+
} else {
|
|
1154
|
+
console.log(`📱 Next.js: http://${options.host || 'localhost'}:${port}`);
|
|
1155
|
+
}
|
|
750
1156
|
if (hasFunctions && !options.noFunctions) {
|
|
751
|
-
console.log(
|
|
1157
|
+
console.log(`${functionsReady ? '⚡ Azure Functions' : '⏳ Azure Functions (starting)'}: ${functionsBaseUrl}`);
|
|
1158
|
+
}
|
|
1159
|
+
if (mockServer) {
|
|
1160
|
+
console.log(`🔌 Mock Proxy: ${bffTargetUrl} (BFF → here)`);
|
|
752
1161
|
}
|
|
753
1162
|
console.log('');
|
|
754
|
-
if (hasFunctions && !options.noFunctions) {
|
|
1163
|
+
if (hasFunctions && !options.noFunctions && functionsReady) {
|
|
755
1164
|
console.log('💡 Azure Functions and Next.js BFF are connected');
|
|
1165
|
+
} else if (hasFunctions && !options.noFunctions) {
|
|
1166
|
+
console.log('💡 Azure Functions is still warming up; BFF routes can fail until the backend responds.');
|
|
1167
|
+
}
|
|
1168
|
+
if (mockServer) {
|
|
1169
|
+
console.log('💡 Connector models served from mock server (Zod-generated data)');
|
|
1170
|
+
}
|
|
1171
|
+
if (swaReady) {
|
|
1172
|
+
console.log('💡 Open the app through the SWA port when testing authenticated pages and CRUD APIs.');
|
|
756
1173
|
}
|
|
757
1174
|
console.log('');
|
|
1175
|
+
|
|
1176
|
+
if (options.open) {
|
|
1177
|
+
const url = useSwaEmulator
|
|
1178
|
+
? `http://${options.host || 'localhost'}:${swaPort}`
|
|
1179
|
+
: `http://${options.host || 'localhost'}:${port}`;
|
|
1180
|
+
console.log(`🌐 Opening browser: ${url}`);
|
|
1181
|
+
const start = process.platform === 'darwin' ? 'open' :
|
|
1182
|
+
process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
1183
|
+
spawn(start, [url], { shell: true });
|
|
1184
|
+
console.log('');
|
|
1185
|
+
}
|
|
1186
|
+
|
|
758
1187
|
console.log('🛑 Press Ctrl+C to stop');
|
|
759
1188
|
console.log('');
|
|
760
1189
|
|
|
@@ -769,17 +1198,35 @@ async function startDevEnvironment(options: DevOptions) {
|
|
|
769
1198
|
}
|
|
770
1199
|
}
|
|
771
1200
|
|
|
1201
|
+
async function resolveSwaCliCommand(projectRoot: string): Promise<string | null> {
|
|
1202
|
+
const binaryName = process.platform === 'win32' ? 'swa.cmd' : 'swa';
|
|
1203
|
+
const localCommand = path.join(projectRoot, 'node_modules', '.bin', binaryName);
|
|
1204
|
+
if (fs.existsSync(localCommand) && await checkCommand(localCommand, ['--version'], {
|
|
1205
|
+
cwd: projectRoot,
|
|
1206
|
+
shell: process.platform === 'win32',
|
|
1207
|
+
})) {
|
|
1208
|
+
return localCommand;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
if (await checkCommand('swa', ['--version'], { cwd: projectRoot })) {
|
|
1212
|
+
return 'swa';
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
return null;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
772
1218
|
async function runCommand(
|
|
773
1219
|
command: string,
|
|
774
1220
|
args: string[],
|
|
775
1221
|
cwd: string,
|
|
776
1222
|
label: string,
|
|
777
|
-
env?: NodeJS.ProcessEnv
|
|
1223
|
+
env?: NodeJS.ProcessEnv,
|
|
1224
|
+
shell = true
|
|
778
1225
|
): Promise<void> {
|
|
779
1226
|
await new Promise<void>((resolve, reject) => {
|
|
780
1227
|
const child = spawn(command, args, {
|
|
781
1228
|
cwd,
|
|
782
|
-
shell
|
|
1229
|
+
shell,
|
|
783
1230
|
stdio: 'inherit',
|
|
784
1231
|
env,
|
|
785
1232
|
});
|