catalyst-core 1.1.0 → 1.2.0-canary.2
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/bin/catalyst.js +54 -6
- package/changelog.md +27 -1
- package/dist/errors/registry.js +306 -18
- package/dist/errors-index.json +273 -70
- package/dist/native/androidProject/app/build.gradle.kts +53 -79
- package/dist/native/androidProject/app/src/main/AndroidManifest.xml +1 -1
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CatalystConstants.kt +3 -1
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +32 -4
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/KeyboardUtil.kt +5 -1
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/ConfigMappingTest.kt +25 -0
- package/dist/native/androidSetup.js +76 -170
- package/dist/native/androidToolchain.js +642 -0
- package/dist/native/appIcon.js +100 -0
- package/dist/native/buildAndroid/assets.js +29 -5
- package/dist/native/buildAndroid/build.js +110 -223
- package/dist/native/buildAndroid/config.js +57 -65
- package/dist/native/buildAndroid/devices.js +176 -0
- package/dist/native/buildAndroid/index.js +271 -46
- package/dist/native/buildAndroid/output.js +43 -0
- package/dist/native/buildAndroid/signing.js +456 -0
- package/dist/native/buildAppAndroid.js +27 -7
- package/dist/native/buildAppIos.js +139 -20
- package/dist/native/buildIos/assets.js +76 -4
- package/dist/native/buildIos/build.js +31 -181
- package/dist/native/buildIos/config.js +126 -11
- package/dist/native/buildIos/index.js +354 -34
- package/dist/native/buildIos/localhostCert.js +87 -0
- package/dist/native/buildIos/output.js +28 -0
- package/dist/native/buildIos/signing.js +596 -0
- package/dist/native/credentials.js +309 -0
- package/dist/native/exportCredentials.js +51 -0
- package/dist/native/iosToolchain.js +583 -0
- package/dist/native/iosnativeWebView/Configurations/Shared.xcconfig +1 -3
- package/dist/native/iosnativeWebView/iosnativeWebView/Info-Release.plist +2 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/PrivacyInfo.xcprivacy +40 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/iosnativeWebView.entitlements +0 -2
- package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.pbxproj +4 -4
- package/dist/native/localState.js +148 -0
- package/dist/native/nativeConfig.js +621 -0
- package/dist/native/releaseOutput.js +53 -0
- package/dist/native/renameAndroidProject.js +154 -369
- package/dist/native/setupEmulatorIos.js +58 -275
- package/dist/native/setupServer.js +520 -124
- package/dist/native/utils.js +418 -115
- package/dist/vite/resolveDevServerConfig.js +21 -0
- package/mcp_v2/knowledge-base.json +13 -11
- package/mcp_v2/test/config.test.ts +40 -0
- package/mcp_v2/tools/build.js +80 -104
- package/mcp_v2/tools/config.js +181 -174
- package/package.json +1 -1
- package/test/cli/nativeFlags.test.ts +97 -0
- package/test/native/androidDevices.test.ts +230 -0
- package/test/native/androidElfAlignment.test.ts +148 -0
- package/test/native/androidSigning.test.ts +646 -0
- package/test/native/androidToolchain.test.ts +766 -0
- package/test/native/appIcon.test.ts +124 -0
- package/test/native/credentials.test.ts +241 -0
- package/test/native/interaction.test.ts +179 -0
- package/test/native/iosConfigPhase.test.ts +611 -0
- package/test/native/iosSigning.test.ts +1060 -0
- package/test/native/iosToolchain.test.ts +821 -0
- package/test/native/localState.test.ts +80 -0
- package/test/native/nativeConfig.test.ts +602 -0
- package/test/native/setupServer.test.ts +529 -0
- package/test/native/utils.test.ts +102 -127
- package/test/vite/resolveDevServerConfig.test.js +25 -0
- package/dist/native/androidProject/keystore/Untitled +0 -0
- package/dist/native/androidProject/keystore/release-key.jks +0 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/localhost.p12 +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execSync, spawn } from "child_process"
|
|
1
|
+
import { execFileSync, execSync, spawn } from "child_process"
|
|
2
2
|
import fs from "fs"
|
|
3
3
|
import os from "os"
|
|
4
4
|
import path from "path"
|
|
@@ -8,7 +8,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
|
|
|
8
8
|
// utils.js is ESM, so it is imported here the same way redactSecrets.test.ts
|
|
9
9
|
// imports it: one module copy for v8 coverage to merge.
|
|
10
10
|
//
|
|
11
|
-
// execSync and spawn are wrapped so each test can fake a tool's answer, while
|
|
11
|
+
// execSync, execFileSync and spawn are wrapped so each test can fake a tool's answer, while
|
|
12
12
|
// anything a test does not stub still runs for real (node itself, never adb
|
|
13
13
|
// or an emulator).
|
|
14
14
|
vi.mock("child_process", async (importOriginal) => {
|
|
@@ -17,6 +17,7 @@ vi.mock("child_process", async (importOriginal) => {
|
|
|
17
17
|
...actual,
|
|
18
18
|
default: actual,
|
|
19
19
|
execSync: vi.fn(actual.execSync),
|
|
20
|
+
execFileSync: vi.fn(actual.execFileSync),
|
|
20
21
|
spawn: vi.fn(actual.spawn),
|
|
21
22
|
}
|
|
22
23
|
})
|
|
@@ -31,12 +32,13 @@ import {
|
|
|
31
32
|
redactError,
|
|
32
33
|
redactSecrets,
|
|
33
34
|
runCommand,
|
|
35
|
+
runFile,
|
|
36
|
+
runInheritedCommand,
|
|
34
37
|
runInteractiveCommand,
|
|
35
|
-
runSdkManagerCommand,
|
|
36
|
-
validateAndCompleteConfig,
|
|
37
38
|
} from "../../src/native/utils.js"
|
|
38
39
|
|
|
39
40
|
const mockedExec = vi.mocked(execSync)
|
|
41
|
+
const mockedExecFile = vi.mocked(execFileSync)
|
|
40
42
|
const mockedSpawn = vi.mocked(spawn)
|
|
41
43
|
const ANSI = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*[A-Za-z]`, "g")
|
|
42
44
|
const plain = (text: unknown) => String(text).replace(ANSI, "")
|
|
@@ -71,6 +73,7 @@ afterEach(() => {
|
|
|
71
73
|
vi.restoreAllMocks()
|
|
72
74
|
// mockReset puts back the real implementation vi.fn was given.
|
|
73
75
|
mockedExec.mockReset()
|
|
76
|
+
mockedExecFile.mockReset()
|
|
74
77
|
mockedSpawn.mockReset()
|
|
75
78
|
setTTY(originalTTY)
|
|
76
79
|
for (const dir of temps.splice(0)) fs.rmSync(dir, { recursive: true, force: true })
|
|
@@ -147,6 +150,89 @@ describe("runCommand", () => {
|
|
|
147
150
|
})
|
|
148
151
|
})
|
|
149
152
|
|
|
153
|
+
describe("runFile", () => {
|
|
154
|
+
it("runs the program with an argv array and no shell", () => {
|
|
155
|
+
mockedExecFile.mockImplementationOnce(() => "List of devices attached\n" as any)
|
|
156
|
+
const adb = "/sdk dir/$(touch x)/adb"
|
|
157
|
+
expect(runFile(adb, ["devices"], { timeout: 15000 })).toBe("List of devices attached\n")
|
|
158
|
+
expect(mockedExecFile).toHaveBeenCalledWith(adb, ["devices"], {
|
|
159
|
+
encoding: "utf8",
|
|
160
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
161
|
+
timeout: 15000,
|
|
162
|
+
})
|
|
163
|
+
expect(mockedExec).not.toHaveBeenCalled()
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it("refuses an empty path or one with a NUL or line break", () => {
|
|
167
|
+
for (const bad of ["", " ", "/sdk/adb\nrm -rf /", "/sdk/adb\u0000", undefined]) {
|
|
168
|
+
expect(() => runFile(bad as any, ["version"])).toThrow(/Not a valid tool path/)
|
|
169
|
+
}
|
|
170
|
+
expect(mockedExecFile).not.toHaveBeenCalled()
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it("names the tool on a timeout, even when its folder has spaces", () => {
|
|
174
|
+
mockedExecFile.mockImplementationOnce(() => {
|
|
175
|
+
throw Object.assign(new Error("spawnSync ETIMEDOUT"), { code: "ETIMEDOUT" })
|
|
176
|
+
})
|
|
177
|
+
let error: any
|
|
178
|
+
try {
|
|
179
|
+
runFile("/Android Sdk/platform-tools/adb", ["devices"], { timeout: 10000 })
|
|
180
|
+
} catch (caught) {
|
|
181
|
+
error = caught
|
|
182
|
+
}
|
|
183
|
+
expect(error.message).toBe("adb did not respond within 10s")
|
|
184
|
+
expect(error.hint).toContain("adb kill-server")
|
|
185
|
+
expect(console.error).not.toHaveBeenCalled()
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it("logs the failure with passwords redacted and rethrows", () => {
|
|
189
|
+
const failure = Object.assign(new Error("Command failed: keytool -storepass hunter2"), { status: 1 })
|
|
190
|
+
mockedExecFile.mockImplementationOnce(() => {
|
|
191
|
+
throw failure
|
|
192
|
+
})
|
|
193
|
+
expect(() => runFile("keytool", ["-storepass", "hunter2"])).toThrow(failure)
|
|
194
|
+
expect(failure.message).toBe("Command failed: keytool -storepass ***")
|
|
195
|
+
const logged = vi.mocked(console.error).mock.calls.flat().join("\n")
|
|
196
|
+
expect(logged).toContain("Error executing command: keytool -storepass ***")
|
|
197
|
+
expect(logged).not.toContain("hunter2")
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
it("runs a real program", () => {
|
|
201
|
+
expect(runFile(process.execPath, ["-e", "process.stdout.write('ok')"])).toBe("ok")
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
describe("runInheritedCommand", () => {
|
|
206
|
+
it("resolves on exit code 0 and passes env and cwd through", async () => {
|
|
207
|
+
const cwd = tempDir()
|
|
208
|
+
await runInheritedCommand(process.execPath, ["-e", "process.exit(0)"], {
|
|
209
|
+
env: { ...process.env },
|
|
210
|
+
cwd,
|
|
211
|
+
})
|
|
212
|
+
expect(mockedSpawn).toHaveBeenLastCalledWith(process.execPath, ["-e", "process.exit(0)"], {
|
|
213
|
+
stdio: "inherit",
|
|
214
|
+
env: expect.any(Object),
|
|
215
|
+
cwd,
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
it("rejects with the exit code", async () => {
|
|
220
|
+
const error: any = await runInheritedCommand(process.execPath, ["-e", "process.exit(3)"]).catch(
|
|
221
|
+
(caught) => caught
|
|
222
|
+
)
|
|
223
|
+
expect(error.message).toBe(`${process.execPath} exited with code 3`)
|
|
224
|
+
expect(error.code).toBe(3)
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
it("names a command that is not installed", async () => {
|
|
228
|
+
const error: any = await runInheritedCommand("catalyst-no-such-tool-xyz", []).catch(
|
|
229
|
+
(caught) => caught
|
|
230
|
+
)
|
|
231
|
+
expect(error.message).toBe("Command not found: catalyst-no-such-tool-xyz")
|
|
232
|
+
expect(error.cause.code).toBe("ENOENT")
|
|
233
|
+
})
|
|
234
|
+
})
|
|
235
|
+
|
|
150
236
|
describe("redactSecrets and redactError", () => {
|
|
151
237
|
it("redacts every password form and passes non-strings through", () => {
|
|
152
238
|
expect(
|
|
@@ -237,23 +323,30 @@ describe("listAvds", () => {
|
|
|
237
323
|
it("asks the SDK's emulator, expanding ~ and dropping INFO noise", () => {
|
|
238
324
|
const home = tempDir()
|
|
239
325
|
vi.spyOn(os, "homedir").mockReturnValue(home)
|
|
240
|
-
|
|
326
|
+
mockedExecFile.mockImplementationOnce(
|
|
327
|
+
() => "INFO | Storing crashdata\nPixel_7_API_34\n\n Tablet \n" as any
|
|
328
|
+
)
|
|
241
329
|
|
|
242
330
|
expect(listAvds("~/sdk")).toEqual(["Pixel_7_API_34", "Tablet"])
|
|
243
|
-
|
|
244
|
-
|
|
331
|
+
// The path is one argv entry, never a shell string.
|
|
332
|
+
expect(mockedExecFile).toHaveBeenLastCalledWith(
|
|
333
|
+
path.join(home, "sdk", "emulator", "emulator"),
|
|
334
|
+
["-list-avds"],
|
|
245
335
|
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 15000 }
|
|
246
336
|
)
|
|
247
337
|
})
|
|
248
338
|
|
|
249
339
|
it("returns [] without an SDK or when the emulator cannot be run", () => {
|
|
250
340
|
expect(listAvds(null)).toEqual([])
|
|
251
|
-
expect(
|
|
341
|
+
expect(mockedExecFile).not.toHaveBeenCalled()
|
|
252
342
|
|
|
253
|
-
|
|
343
|
+
mockedExecFile.mockImplementationOnce(() => {
|
|
254
344
|
throw new Error("ENOENT")
|
|
255
345
|
})
|
|
256
346
|
expect(listAvds("/no/sdk")).toEqual([])
|
|
347
|
+
// A path with a line break is refused before anything runs.
|
|
348
|
+
expect(listAvds("/sdk\nrm")).toEqual([])
|
|
349
|
+
expect(mockedExecFile).toHaveBeenCalledTimes(1)
|
|
257
350
|
})
|
|
258
351
|
})
|
|
259
352
|
|
|
@@ -411,122 +504,4 @@ describe("runInteractiveCommand", () => {
|
|
|
411
504
|
expect(output.endsWith("END\n")).toBe(true)
|
|
412
505
|
expect(output).not.toContain("got:")
|
|
413
506
|
})
|
|
414
|
-
|
|
415
|
-
it("feeds sdkmanager's licence prompts a yes", async () => {
|
|
416
|
-
const script = `
|
|
417
|
-
process.stdout.write("Accept? (y/N): ")
|
|
418
|
-
process.stdin.once("data", (answer) => { console.log("answer=" + String(answer).trim()); process.stdin.destroy() })
|
|
419
|
-
`
|
|
420
|
-
await expect(runSdkManagerCommand(node, ["-e", script])).resolves.toContain("answer=y")
|
|
421
|
-
})
|
|
422
|
-
})
|
|
423
|
-
|
|
424
|
-
describe("validateAndCompleteConfig", () => {
|
|
425
|
-
function writeConfig(config: unknown) {
|
|
426
|
-
const file = path.join(tempDir(), "config.json")
|
|
427
|
-
fs.writeFileSync(file, typeof config === "string" ? config : JSON.stringify(config))
|
|
428
|
-
return file
|
|
429
|
-
}
|
|
430
|
-
const read = (file: string) => JSON.parse(fs.readFileSync(file, "utf8"))
|
|
431
|
-
|
|
432
|
-
it("returns a complete config untouched", async () => {
|
|
433
|
-
const android = { buildType: "release", sdkPath: "/sdk", emulatorName: "Pixel" }
|
|
434
|
-
const file = writeConfig({ WEBVIEW_CONFIG: { port: "3005", android } })
|
|
435
|
-
mockedExec.mockImplementation(() => "Pixel\n")
|
|
436
|
-
const before = fs.readFileSync(file, "utf8")
|
|
437
|
-
|
|
438
|
-
await expect(validateAndCompleteConfig("android", file)).resolves.toEqual({ port: "3005", android })
|
|
439
|
-
expect(fs.readFileSync(file, "utf8")).toBe(before)
|
|
440
|
-
})
|
|
441
|
-
|
|
442
|
-
it("fills what it can work out for Android and saves it without asking", async () => {
|
|
443
|
-
const sdk = fakeSdk()
|
|
444
|
-
const file = writeConfig({ NODE_SERVER_PORT: 3005, WEBVIEW_CONFIG: { android: { sdkPath: sdk } } })
|
|
445
|
-
mockedExec.mockImplementation(() => "Pixel_7_API_34\n")
|
|
446
|
-
setTTY(false)
|
|
447
|
-
|
|
448
|
-
const result = await validateAndCompleteConfig("android", file)
|
|
449
|
-
|
|
450
|
-
expect(result).toEqual({
|
|
451
|
-
port: "3005",
|
|
452
|
-
android: { sdkPath: sdk, buildType: "debug", emulatorName: "Pixel_7_API_34" },
|
|
453
|
-
})
|
|
454
|
-
expect(read(file).WEBVIEW_CONFIG).toEqual(result)
|
|
455
|
-
const saved = plain(vi.mocked(console.log).mock.calls.flat().join("\n"))
|
|
456
|
-
expect(saved).toContain("Saved port, buildType, emulatorName to config/config.json")
|
|
457
|
-
})
|
|
458
|
-
|
|
459
|
-
it("detects the SDK when none is configured", async () => {
|
|
460
|
-
const sdk = fakeSdk()
|
|
461
|
-
vi.stubEnv("ANDROID_HOME", sdk)
|
|
462
|
-
mockedExec.mockImplementation(() => {
|
|
463
|
-
throw new Error("no emulator")
|
|
464
|
-
})
|
|
465
|
-
const file = writeConfig({
|
|
466
|
-
WEBVIEW_CONFIG: { port: "3005", android: { buildType: "debug", emulatorName: "Pixel" } },
|
|
467
|
-
})
|
|
468
|
-
setTTY(false)
|
|
469
|
-
try {
|
|
470
|
-
const result = await validateAndCompleteConfig("android", file)
|
|
471
|
-
expect(result.android.sdkPath).toBe(sdk)
|
|
472
|
-
} finally {
|
|
473
|
-
vi.unstubAllEnvs()
|
|
474
|
-
}
|
|
475
|
-
})
|
|
476
|
-
|
|
477
|
-
it("fails with ENOTTY for an iOS field it cannot guess in CI", async () => {
|
|
478
|
-
const file = writeConfig({ WEBVIEW_CONFIG: { port: "3005", ios: { buildType: "debug" } } })
|
|
479
|
-
setTTY(false)
|
|
480
|
-
const error: any = await validateAndCompleteConfig("ios", file).catch((caught) => caught)
|
|
481
|
-
expect(error.code).toBe("ENOTTY")
|
|
482
|
-
expect(error.message).toContain("iOS bundle ID")
|
|
483
|
-
})
|
|
484
|
-
|
|
485
|
-
it("asks on the terminal for what it cannot guess", async () => {
|
|
486
|
-
const file = writeConfig({ WEBVIEW_CONFIG: { port: "3005", ios: { buildType: "debug" } } })
|
|
487
|
-
setTTY(true)
|
|
488
|
-
const answers = ["com.example.app", "iPhone 16 Pro"]
|
|
489
|
-
vi.spyOn(readline, "createInterface").mockImplementation(
|
|
490
|
-
() =>
|
|
491
|
-
({
|
|
492
|
-
question: (_text: string, callback: (answer: string) => void) =>
|
|
493
|
-
callback(answers.shift()!),
|
|
494
|
-
close: () => {},
|
|
495
|
-
}) as any
|
|
496
|
-
)
|
|
497
|
-
vi.spyOn(process.stdout, "write").mockImplementation(() => true)
|
|
498
|
-
|
|
499
|
-
await expect(validateAndCompleteConfig("ios", file)).resolves.toEqual({
|
|
500
|
-
port: "3005",
|
|
501
|
-
ios: { buildType: "debug", appBundleId: "com.example.app", simulatorName: "iPhone 16 Pro" },
|
|
502
|
-
})
|
|
503
|
-
expect(read(file).WEBVIEW_CONFIG.ios.simulatorName).toBe("iPhone 16 Pro")
|
|
504
|
-
})
|
|
505
|
-
|
|
506
|
-
it("starts from an empty config when the file is unreadable, then rejects an unknown platform", async () => {
|
|
507
|
-
const file = writeConfig("{ not json")
|
|
508
|
-
await expect(validateAndCompleteConfig("windows", file)).rejects.toThrow(
|
|
509
|
-
'Invalid platform. Must be either "android" or "ios"'
|
|
510
|
-
)
|
|
511
|
-
expect(plain(vi.mocked(console.error).mock.calls.flat().join("\n"))).toContain(
|
|
512
|
-
"Error reading config file"
|
|
513
|
-
)
|
|
514
|
-
})
|
|
515
|
-
|
|
516
|
-
it("surfaces a config file it cannot save", async () => {
|
|
517
|
-
const file = writeConfig({
|
|
518
|
-
WEBVIEW_CONFIG: {
|
|
519
|
-
port: "3005",
|
|
520
|
-
ios: { buildType: "", appBundleId: "a.b", simulatorName: "iPhone" },
|
|
521
|
-
},
|
|
522
|
-
})
|
|
523
|
-
vi.spyOn(fs, "writeFileSync").mockImplementation(() => {
|
|
524
|
-
throw new Error("EROFS: read-only file system")
|
|
525
|
-
})
|
|
526
|
-
|
|
527
|
-
await expect(validateAndCompleteConfig("ios", file)).rejects.toThrow("EROFS")
|
|
528
|
-
expect(plain(vi.mocked(console.error).mock.calls.flat().join("\n"))).toContain(
|
|
529
|
-
"Error saving config file: EROFS"
|
|
530
|
-
)
|
|
531
|
-
})
|
|
532
507
|
})
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
toMountPathPrefix,
|
|
7
7
|
resolveDevFsAllow,
|
|
8
8
|
buildDevServer,
|
|
9
|
+
withExtraAllowedHosts,
|
|
9
10
|
} from "../../src/vite/resolveDevServerConfig.js"
|
|
10
11
|
|
|
11
12
|
// ─── resolveDevBase ───────────────────────────────────────────────────────────
|
|
@@ -114,3 +115,27 @@ test("buildDevServer: 'base' is not leaked into the server block", () => {
|
|
|
114
115
|
const server = buildDevServer({ base: "/o-mweb" }, { frameworkPaths: FRAMEWORK, isProduction: false })
|
|
115
116
|
assert.equal(server.base, undefined)
|
|
116
117
|
})
|
|
118
|
+
|
|
119
|
+
// ─── CATALYST_DEV_ALLOWED_HOSTS (buildApp:ios on a physical iPhone) ───────────
|
|
120
|
+
|
|
121
|
+
test("withExtraAllowedHosts: adds the env hosts to the app's list", () => {
|
|
122
|
+
assert.deepEqual(withExtraAllowedHosts(undefined, "my-mac.local"), ["my-mac.local"])
|
|
123
|
+
assert.deepEqual(withExtraAllowedHosts(["a.test"], "my-mac.local, a.test"), ["a.test", "my-mac.local"])
|
|
124
|
+
assert.equal(withExtraAllowedHosts(true, "my-mac.local"), true)
|
|
125
|
+
assert.equal(withExtraAllowedHosts(undefined, ""), undefined)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test("buildDevServer: CATALYST_DEV_ALLOWED_HOSTS reaches server.allowedHosts", () => {
|
|
129
|
+
const before = process.env.CATALYST_DEV_ALLOWED_HOSTS
|
|
130
|
+
process.env.CATALYST_DEV_ALLOWED_HOSTS = "my-mac.local"
|
|
131
|
+
try {
|
|
132
|
+
const server = buildDevServer(
|
|
133
|
+
{ allowedHosts: ["local.example.com"] },
|
|
134
|
+
{ frameworkPaths: FRAMEWORK, isProduction: false }
|
|
135
|
+
)
|
|
136
|
+
assert.deepEqual(server.allowedHosts, ["local.example.com", "my-mac.local"])
|
|
137
|
+
} finally {
|
|
138
|
+
if (before === undefined) delete process.env.CATALYST_DEV_ALLOWED_HOSTS
|
|
139
|
+
else process.env.CATALYST_DEV_ALLOWED_HOSTS = before
|
|
140
|
+
}
|
|
141
|
+
})
|
|
Binary file
|
|
Binary file
|
|
Binary file
|