@project-ajax/sdk 0.0.49 → 0.0.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/capabilities/sync.d.ts +5 -0
  2. package/dist/capabilities/sync.d.ts.map +1 -1
  3. package/dist/cli/api/client.d.ts +0 -8
  4. package/dist/cli/api/client.d.ts.map +1 -1
  5. package/dist/cli/api/client.js +4 -31
  6. package/dist/cli/commands/auth.d.ts.map +1 -1
  7. package/dist/cli/commands/auth.impl.d.ts +4 -9
  8. package/dist/cli/commands/auth.impl.d.ts.map +1 -1
  9. package/dist/cli/commands/auth.impl.js +4 -10
  10. package/dist/cli/commands/auth.impl.test.d.ts +2 -0
  11. package/dist/cli/commands/auth.impl.test.d.ts.map +1 -0
  12. package/dist/cli/commands/auth.js +1 -15
  13. package/dist/cli/commands/bundle.impl.test.d.ts +2 -0
  14. package/dist/cli/commands/bundle.impl.test.d.ts.map +1 -0
  15. package/dist/cli/commands/deploy.impl.test.d.ts +2 -0
  16. package/dist/cli/commands/deploy.impl.test.d.ts.map +1 -0
  17. package/dist/cli/commands/exec.impl.d.ts.map +1 -1
  18. package/dist/cli/commands/exec.impl.js +3 -0
  19. package/dist/cli/commands/utils/testing.d.ts +25 -0
  20. package/dist/cli/commands/utils/testing.d.ts.map +1 -0
  21. package/dist/cli/commands/utils/testing.js +51 -0
  22. package/dist/cli/config.d.ts +8 -8
  23. package/dist/cli/config.d.ts.map +1 -1
  24. package/dist/cli/config.js +55 -19
  25. package/dist/cli/config.test.d.ts +2 -0
  26. package/dist/cli/config.test.d.ts.map +1 -0
  27. package/dist/cli/flags.d.ts +5 -0
  28. package/dist/cli/flags.d.ts.map +1 -1
  29. package/dist/cli/flags.js +25 -0
  30. package/dist/cli/handler.d.ts.map +1 -1
  31. package/dist/cli/handler.js +3 -5
  32. package/package.json +2 -2
  33. package/src/capabilities/sync.ts +5 -0
  34. package/src/capabilities/tool.test.ts +51 -1
  35. package/src/cli/api/client.ts +3 -44
  36. package/src/cli/commands/.cursor/rules/testing-commands.mdc +212 -0
  37. package/src/cli/commands/auth.impl.test.ts +206 -0
  38. package/src/cli/commands/auth.impl.ts +5 -17
  39. package/src/cli/commands/auth.ts +2 -15
  40. package/src/cli/commands/bundle.impl.test.ts +137 -0
  41. package/src/cli/commands/deploy.impl.test.ts +239 -0
  42. package/src/cli/commands/exec.impl.ts +4 -0
  43. package/src/cli/commands/utils/testing.ts +60 -0
  44. package/src/cli/config.test.ts +114 -0
  45. package/src/cli/config.ts +68 -28
  46. package/src/cli/flags.ts +29 -0
  47. package/src/cli/handler.ts +2 -5
@@ -0,0 +1,137 @@
1
+ import {
2
+ afterEach,
3
+ beforeEach,
4
+ describe,
5
+ expect,
6
+ it,
7
+ type Mock,
8
+ vi,
9
+ } from "vitest";
10
+ import { ApiClient } from "../api/client.js";
11
+ import { Result } from "../api/result.js";
12
+ import { Config } from "../config.js";
13
+ import { downloadBundle } from "./bundle.impl.js";
14
+ import {
15
+ baseFlags,
16
+ cleanupTmpDirectories,
17
+ createBaseContext,
18
+ createTestConfig,
19
+ } from "./utils/testing.js";
20
+
21
+ afterEach(cleanupTmpDirectories);
22
+
23
+ describe("downloadBundle", () => {
24
+ let stderrSpy: Mock<typeof process.stderr.write>;
25
+ let stdoutSpy: Mock<typeof process.stdout.write>;
26
+
27
+ beforeEach(() => {
28
+ stderrSpy = vi
29
+ .spyOn(process.stderr, "write")
30
+ .mockImplementation(() => true);
31
+ stdoutSpy = vi
32
+ .spyOn(process.stdout, "write")
33
+ .mockImplementation(() => true);
34
+ });
35
+
36
+ it("downloads and pipes bundle to stdout when workerId is set", async () => {
37
+ const mockConfig = await createTestConfig({
38
+ token:
39
+ "1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig",
40
+ workerId: "worker-123",
41
+ environment: "local",
42
+ baseUrl: "http://localhost:3000",
43
+ });
44
+
45
+ vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
46
+
47
+ // Create a mock web stream
48
+ const mockWebStream = new ReadableStream({
49
+ start(controller) {
50
+ controller.enqueue(new TextEncoder().encode("bundle content"));
51
+ controller.close();
52
+ },
53
+ });
54
+
55
+ // This allows us to wait for the pipe to happen.
56
+ let resolveWrite: () => void;
57
+ const writePromise = new Promise<void>((resolve) => {
58
+ resolveWrite = resolve;
59
+ });
60
+ stdoutSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => {
61
+ resolveWrite();
62
+ return true;
63
+ });
64
+
65
+ const mockDownloadWorkerBundle = vi
66
+ .spyOn(ApiClient.prototype, "downloadWorkerBundle")
67
+ .mockResolvedValue(Result.success(mockWebStream));
68
+
69
+ const context = createBaseContext();
70
+
71
+ await downloadBundle.call(context, baseFlags);
72
+
73
+ expect(mockDownloadWorkerBundle).toHaveBeenCalledWith("worker-123");
74
+ await writePromise;
75
+ expect(stdoutSpy).toHaveBeenCalledWith(Buffer.from("bundle content"));
76
+ });
77
+
78
+ it("throws error when workerId is not set", async () => {
79
+ const mockConfig = await createTestConfig({
80
+ token:
81
+ "1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig",
82
+ workerId: null,
83
+ environment: "local",
84
+ baseUrl: "http://localhost:3000",
85
+ });
86
+
87
+ vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
88
+
89
+ const mockDownloadWorkerBundle = vi.spyOn(
90
+ ApiClient.prototype,
91
+ "downloadWorkerBundle",
92
+ );
93
+
94
+ const context = createBaseContext();
95
+
96
+ await expect(downloadBundle.call(context, baseFlags)).rejects.toThrow(
97
+ "No worker configured. Run 'workers deploy' first to create a worker.",
98
+ );
99
+
100
+ expect(mockDownloadWorkerBundle).not.toHaveBeenCalled();
101
+ });
102
+
103
+ it("handles download failure gracefully", async () => {
104
+ const mockConfig = await createTestConfig({
105
+ token:
106
+ "1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig",
107
+ workerId: "worker-123",
108
+ environment: "local",
109
+ baseUrl: "http://localhost:3000",
110
+ });
111
+
112
+ vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
113
+
114
+ const mockDownloadWorkerBundle = vi
115
+ .spyOn(ApiClient.prototype, "downloadWorkerBundle")
116
+ .mockResolvedValue(
117
+ Result.fail({
118
+ status: 404,
119
+ statusText: "Not Found",
120
+ message: "Worker bundle not found",
121
+ }),
122
+ );
123
+
124
+ const context = createBaseContext();
125
+
126
+ await downloadBundle.call(context, baseFlags);
127
+
128
+ expect(mockDownloadWorkerBundle).toHaveBeenCalledWith("worker-123");
129
+
130
+ const allCalls = stderrSpy.mock.calls.map((call) => call[0]).join("");
131
+ expect(allCalls).toContain("✗ Failed to download bundle");
132
+ expect(allCalls).toContain("✗ Worker bundle not found");
133
+
134
+ // Should not write anything to stdout on error
135
+ expect(stdoutSpy).not.toHaveBeenCalled();
136
+ });
137
+ });
@@ -0,0 +1,239 @@
1
+ import {
2
+ afterEach,
3
+ beforeEach,
4
+ describe,
5
+ expect,
6
+ it,
7
+ type Mock,
8
+ vi,
9
+ } from "vitest";
10
+ import { Result } from "../api/result.js";
11
+ import { Config } from "../config.js";
12
+ import {
13
+ baseFlags,
14
+ cleanupTmpDirectories,
15
+ createBaseContext,
16
+ createTestConfig,
17
+ } from "./utils/testing.js";
18
+
19
+ // Mock external dependencies
20
+ vi.mock("../deploy.js", () => ({
21
+ deployWorker: vi.fn(),
22
+ }));
23
+
24
+ vi.mock("prompts", () => ({
25
+ default: vi.fn(),
26
+ }));
27
+
28
+ import prompts from "prompts";
29
+ import { deployWorker } from "../deploy.js";
30
+ import { deploy } from "./deploy.impl.js";
31
+
32
+ afterEach(cleanupTmpDirectories);
33
+
34
+ describe("deploy", () => {
35
+ let stderrSpy: Mock<typeof process.stderr.write>;
36
+
37
+ beforeEach(() => {
38
+ stderrSpy = vi
39
+ .spyOn(process.stderr, "write")
40
+ .mockImplementation(() => true);
41
+ });
42
+
43
+ it("deploys existing worker when workerId is set", async () => {
44
+ const mockConfig = await createTestConfig({
45
+ token:
46
+ "1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig",
47
+ workerId: "worker-123",
48
+ environment: "local",
49
+ baseUrl: "http://localhost:3000",
50
+ });
51
+
52
+ vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
53
+ vi.mocked(deployWorker).mockResolvedValue(
54
+ Result.success({ workerId: "worker-123" }),
55
+ );
56
+
57
+ const setWorkerIdSpy = vi.spyOn(mockConfig, "setWorkerId");
58
+ const context = createBaseContext();
59
+
60
+ await deploy.call(context, baseFlags);
61
+
62
+ expect(deployWorker).toHaveBeenCalledWith(expect.anything(), {
63
+ workerPath: process.cwd(),
64
+ workerId: "worker-123",
65
+ token:
66
+ "1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig",
67
+ environment: "local",
68
+ });
69
+
70
+ expect(setWorkerIdSpy).toHaveBeenCalledWith("worker-123");
71
+
72
+ const allCalls = stderrSpy.mock.calls.map((call) => call[0]).join("");
73
+ expect(allCalls).toContain("Deploying worker...");
74
+ expect(allCalls).toContain("✓ Successfully deployed worker");
75
+ });
76
+
77
+ it("deploys new worker with name flag", async () => {
78
+ const mockConfig = await createTestConfig({
79
+ token:
80
+ "1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig",
81
+ workerId: null,
82
+ environment: "local",
83
+ baseUrl: "http://localhost:3000",
84
+ });
85
+
86
+ vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
87
+ vi.mocked(deployWorker).mockResolvedValue(
88
+ Result.success({ workerId: "worker-456" }),
89
+ );
90
+
91
+ const setWorkerIdSpy = vi.spyOn(mockConfig, "setWorkerId");
92
+ const context = createBaseContext();
93
+
94
+ await deploy.call(context, { ...baseFlags, name: "my-worker" });
95
+
96
+ expect(deployWorker).toHaveBeenCalledWith(expect.anything(), {
97
+ name: "my-worker",
98
+ workerPath: process.cwd(),
99
+ token:
100
+ "1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig",
101
+ environment: "local",
102
+ });
103
+
104
+ expect(setWorkerIdSpy).toHaveBeenCalledWith("worker-456");
105
+ expect(prompts).not.toHaveBeenCalled();
106
+
107
+ const allCalls = stderrSpy.mock.calls.map((call) => call[0]).join("");
108
+ expect(allCalls).toContain("✓ Successfully deployed worker");
109
+ });
110
+
111
+ it("prompts for name when deploying new worker without name flag", async () => {
112
+ const mockConfig = await createTestConfig({
113
+ token:
114
+ "1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig",
115
+ workerId: null,
116
+ environment: "local",
117
+ baseUrl: "http://localhost:3000",
118
+ });
119
+
120
+ vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
121
+ vi.mocked(prompts).mockResolvedValue({ name: "prompted-worker" });
122
+ vi.mocked(deployWorker).mockResolvedValue(
123
+ Result.success({ workerId: "worker-789" }),
124
+ );
125
+
126
+ const setWorkerIdSpy = vi.spyOn(mockConfig, "setWorkerId");
127
+ const context = createBaseContext();
128
+
129
+ await deploy.call(context, baseFlags);
130
+
131
+ expect(prompts).toHaveBeenCalledWith({
132
+ type: "text",
133
+ name: "name",
134
+ message: "Enter a name for the worker",
135
+ validate: expect.any(Function),
136
+ });
137
+
138
+ expect(deployWorker).toHaveBeenCalledWith(expect.anything(), {
139
+ name: "prompted-worker",
140
+ workerPath: process.cwd(),
141
+ token:
142
+ "1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig",
143
+ environment: "local",
144
+ });
145
+
146
+ expect(setWorkerIdSpy).toHaveBeenCalledWith("worker-789");
147
+ });
148
+
149
+ it("throws error when prompt is cancelled", async () => {
150
+ const mockConfig = await createTestConfig({
151
+ token:
152
+ "1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig",
153
+ workerId: null,
154
+ environment: "local",
155
+ baseUrl: "http://localhost:3000",
156
+ });
157
+
158
+ vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
159
+ vi.mocked(prompts).mockResolvedValue({ name: undefined });
160
+
161
+ const context = createBaseContext();
162
+
163
+ await expect(deploy.call(context, baseFlags)).rejects.toThrow(
164
+ "Name is required",
165
+ );
166
+ });
167
+
168
+ it("throws error when both workerId and name are provided", async () => {
169
+ const mockConfig = await createTestConfig({
170
+ token:
171
+ "1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig",
172
+ workerId: "worker-123",
173
+ environment: "local",
174
+ baseUrl: "http://localhost:3000",
175
+ });
176
+
177
+ vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
178
+
179
+ const context = createBaseContext();
180
+
181
+ await expect(
182
+ deploy.call(context, { ...baseFlags, name: "my-worker" }),
183
+ ).rejects.toThrow("Cannot specify a name when updating an existing worker");
184
+ });
185
+
186
+ it("handles deployment failure", async () => {
187
+ const mockConfig = await createTestConfig({
188
+ token:
189
+ "1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig",
190
+ workerId: "worker-123",
191
+ environment: "local",
192
+ baseUrl: "http://localhost:3000",
193
+ });
194
+
195
+ vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
196
+ vi.mocked(deployWorker).mockResolvedValue(
197
+ Result.fail({
198
+ status: 500,
199
+ statusText: "Internal Server Error",
200
+ message: "Deployment failed",
201
+ }),
202
+ );
203
+
204
+ const context = createBaseContext();
205
+
206
+ await deploy.call(context, baseFlags);
207
+
208
+ const allCalls = stderrSpy.mock.calls.map((call) => call[0]).join("");
209
+ expect(allCalls).toContain("✗ Failed to deploy worker");
210
+ expect(allCalls).toContain("Deployment failed");
211
+ });
212
+
213
+ it("trims whitespace from prompted name", async () => {
214
+ const mockConfig = await createTestConfig({
215
+ token:
216
+ "1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig",
217
+ workerId: null,
218
+ environment: "local",
219
+ baseUrl: "http://localhost:3000",
220
+ });
221
+
222
+ vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
223
+ vi.mocked(prompts).mockResolvedValue({ name: " worker-name " });
224
+ vi.mocked(deployWorker).mockResolvedValue(
225
+ Result.success({ workerId: "worker-999" }),
226
+ );
227
+
228
+ const context = createBaseContext();
229
+
230
+ await deploy.call(context, baseFlags);
231
+
232
+ expect(deployWorker).toHaveBeenCalledWith(
233
+ expect.anything(),
234
+ expect.objectContaining({
235
+ name: "worker-name",
236
+ }),
237
+ );
238
+ });
239
+ });
@@ -82,6 +82,10 @@ export const exec = buildAuthedHandler(async function (
82
82
 
83
83
  switch (parsedLine._tag) {
84
84
  case "log":
85
+ if (!flags.debug) {
86
+ break;
87
+ }
88
+
85
89
  if (parsedLine.event.stream === "stdout") {
86
90
  allOutput.push(parsedLine.event.data);
87
91
  }
@@ -0,0 +1,60 @@
1
+ import * as fs from "node:fs";
2
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
3
+ import * as os from "node:os";
4
+ import { tmpdir } from "node:os";
5
+ import * as path from "node:path";
6
+ import { Config, type Environment } from "../../config.js";
7
+ import type { GlobalFlags } from "../../flags.js";
8
+ import { Writer } from "../../writer.js";
9
+
10
+ export type ConfigFileContents = {
11
+ token: string | null;
12
+ workerId: string | null;
13
+ environment: Environment;
14
+ baseUrl: string;
15
+ };
16
+
17
+ export const tmpDirectories: string[] = [];
18
+
19
+ export const baseFlags: GlobalFlags = {
20
+ debug: false,
21
+ };
22
+
23
+ export async function createConfigFile(contents: ConfigFileContents) {
24
+ const dir = await mkdtemp(path.join(tmpdir(), "cmd-test-"));
25
+ tmpDirectories.push(dir);
26
+ const configFilePath = path.join(dir, "config.json");
27
+
28
+ await writeFile(configFilePath, JSON.stringify(contents, null, 2), "utf-8");
29
+ return configFilePath;
30
+ }
31
+
32
+ export async function createTestConfig(
33
+ configFileContents: ConfigFileContents,
34
+ ): Promise<Config> {
35
+ const configFilePath = await createConfigFile(configFileContents);
36
+ return Config.load({
37
+ configFilePath,
38
+ processEnv: {} as NodeJS.ProcessEnv,
39
+ flags: { debug: false },
40
+ });
41
+ }
42
+
43
+ export function createBaseContext() {
44
+ return {
45
+ writer: new Writer({ debugEnabled: false }),
46
+ process,
47
+ fs,
48
+ os,
49
+ path,
50
+ };
51
+ }
52
+
53
+ export async function cleanupTmpDirectories() {
54
+ while (tmpDirectories.length > 0) {
55
+ const dir = tmpDirectories.pop();
56
+ if (dir) {
57
+ await rm(dir, { recursive: true, force: true });
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,114 @@
1
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import * as path from "node:path";
4
+ import { afterEach, describe, expect, it } from "vitest";
5
+ import { Config, type Environment } from "./config.js";
6
+ import type { GlobalFlags } from "./flags.js";
7
+
8
+ type ConfigFileContents = {
9
+ token: string | null;
10
+ workerId: string | null;
11
+ environment: Environment;
12
+ baseUrl: string;
13
+ };
14
+
15
+ const tmpDirectories: string[] = [];
16
+
17
+ async function createConfigFile(contents: ConfigFileContents) {
18
+ const dir = await mkdtemp(path.join(tmpdir(), "config-test-"));
19
+ tmpDirectories.push(dir);
20
+ const configFilePath = path.join(dir, "config.json");
21
+
22
+ await writeFile(configFilePath, JSON.stringify(contents, null, 2), "utf-8");
23
+ return configFilePath;
24
+ }
25
+
26
+ afterEach(async () => {
27
+ while (tmpDirectories.length > 0) {
28
+ const dir = tmpDirectories.pop();
29
+ if (dir) {
30
+ await rm(dir, { recursive: true, force: true });
31
+ }
32
+ }
33
+ });
34
+
35
+ async function loadTestConfig({
36
+ configFile,
37
+ env,
38
+ flags,
39
+ }: {
40
+ configFile: ConfigFileContents;
41
+ env?: Partial<NodeJS.ProcessEnv>;
42
+ flags?: Partial<GlobalFlags>;
43
+ }) {
44
+ const configFilePath = await createConfigFile(configFile);
45
+ return Config.load({
46
+ configFilePath,
47
+ processEnv: { ...(env ?? {}) } as NodeJS.ProcessEnv,
48
+ flags: {
49
+ debug: false,
50
+ ...(flags ?? {}),
51
+ },
52
+ });
53
+ }
54
+
55
+ const baseConfigFile: ConfigFileContents = {
56
+ token: "file-token",
57
+ workerId: "file-worker",
58
+ environment: "local",
59
+ baseUrl: "https://config.example.com",
60
+ };
61
+
62
+ describe("Config.load precedence", () => {
63
+ it("uses values from the config file when no overrides are present", async () => {
64
+ const config = await loadTestConfig({
65
+ configFile: baseConfigFile,
66
+ });
67
+
68
+ expect(config.token).toBe(baseConfigFile.token);
69
+ expect(config.workerId).toBe(baseConfigFile.workerId);
70
+ expect(config.environment).toBe(baseConfigFile.environment);
71
+ expect(config.baseUrl).toBe(baseConfigFile.baseUrl);
72
+ });
73
+
74
+ it("prefers environment variables over the config file", async () => {
75
+ const envVars = {
76
+ WORKERS_TOKEN: "env-token",
77
+ WORKERS_WORKER_ID: "env-worker",
78
+ WORKERS_ENVIRONMENT: "staging",
79
+ };
80
+ const config = await loadTestConfig({
81
+ configFile: baseConfigFile,
82
+ env: envVars,
83
+ });
84
+
85
+ expect(config.token).toBe(envVars.WORKERS_TOKEN);
86
+ expect(config.workerId).toBe(envVars.WORKERS_WORKER_ID);
87
+ expect(config.environment).toBe(envVars.WORKERS_ENVIRONMENT);
88
+ expect(config.baseUrl).toBe(baseConfigFile.baseUrl);
89
+ });
90
+
91
+ it("prefers flags over environment variables and config file values", async () => {
92
+ const envVars = {
93
+ WORKERS_WORKER_ID: "env-worker",
94
+ WORKERS_ENVIRONMENT: "staging",
95
+ WORKERS_BASE_URL: "https://env.example.com",
96
+ };
97
+
98
+ const flags = {
99
+ env: "dev" as const,
100
+ "base-url": "https://flag.example.com",
101
+ };
102
+
103
+ const config = await loadTestConfig({
104
+ configFile: baseConfigFile,
105
+ env: envVars,
106
+ flags,
107
+ });
108
+
109
+ expect(config.token).toBe(baseConfigFile.token);
110
+ expect(config.workerId).toBe(envVars.WORKERS_WORKER_ID);
111
+ expect(config.environment).toBe(flags.env);
112
+ expect(config.baseUrl).toBe(flags["base-url"]);
113
+ });
114
+ });