@project-ajax/sdk 0.0.54 → 0.0.56

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 (51) hide show
  1. package/dist/cli/api/client.d.ts +2 -2
  2. package/dist/cli/api/client.d.ts.map +1 -1
  3. package/dist/cli/commands/auth.impl.d.ts.map +1 -1
  4. package/dist/cli/commands/auth.impl.js +14 -10
  5. package/dist/cli/commands/bundle.impl.js +2 -2
  6. package/dist/cli/commands/capabilities.impl.js +4 -4
  7. package/dist/cli/commands/connect.impl.js +13 -13
  8. package/dist/cli/commands/deploy.impl.d.ts.map +1 -1
  9. package/dist/cli/commands/deploy.impl.js +11 -17
  10. package/dist/cli/commands/env.impl.d.ts.map +1 -1
  11. package/dist/cli/commands/env.impl.js +7 -10
  12. package/dist/cli/commands/exec.impl.d.ts.map +1 -1
  13. package/dist/cli/commands/exec.impl.js +14 -16
  14. package/dist/cli/commands/runs.impl.js +7 -7
  15. package/dist/cli/commands/secrets.impl.js +12 -12
  16. package/dist/cli/commands/utils/testing.d.ts +8 -20
  17. package/dist/cli/commands/utils/testing.d.ts.map +1 -1
  18. package/dist/cli/commands/utils/testing.js +25 -18
  19. package/dist/cli/config.d.ts +13 -6
  20. package/dist/cli/config.d.ts.map +1 -1
  21. package/dist/cli/config.js +60 -35
  22. package/dist/cli/context.d.ts +2 -2
  23. package/dist/cli/context.d.ts.map +1 -1
  24. package/dist/cli/context.js +3 -3
  25. package/dist/cli/deploy.js +11 -11
  26. package/dist/cli/handler.js +2 -2
  27. package/dist/cli/{writer.d.ts → io.d.ts} +10 -3
  28. package/dist/cli/io.d.ts.map +1 -0
  29. package/dist/cli/{writer.js → io.js} +24 -2
  30. package/package.json +3 -3
  31. package/src/cli/api/client.ts +3 -3
  32. package/src/cli/commands/auth.impl.test.ts +66 -46
  33. package/src/cli/commands/auth.impl.ts +14 -10
  34. package/src/cli/commands/bundle.impl.test.ts +25 -19
  35. package/src/cli/commands/bundle.impl.ts +2 -2
  36. package/src/cli/commands/capabilities.impl.ts +4 -4
  37. package/src/cli/commands/connect.impl.ts +13 -13
  38. package/src/cli/commands/deploy.impl.test.ts +78 -63
  39. package/src/cli/commands/deploy.impl.ts +12 -17
  40. package/src/cli/commands/env.impl.ts +7 -11
  41. package/src/cli/commands/exec.impl.ts +14 -16
  42. package/src/cli/commands/runs.impl.ts +7 -7
  43. package/src/cli/commands/secrets.impl.ts +12 -12
  44. package/src/cli/commands/utils/testing.ts +33 -27
  45. package/src/cli/config.test.ts +45 -95
  46. package/src/cli/config.ts +69 -42
  47. package/src/cli/context.ts +3 -3
  48. package/src/cli/deploy.ts +11 -11
  49. package/src/cli/handler.ts +2 -2
  50. package/src/cli/{writer.ts → io.ts} +34 -2
  51. package/dist/cli/writer.d.ts.map +0 -1
@@ -19,9 +19,9 @@ export const listRuns = buildAuthedHandler(async function (flags: FormatFlags) {
19
19
 
20
20
  const data = Result.unwrap(result);
21
21
  if (data.runs.length === 0) {
22
- this.writer.writeErr("No runs found for this worker.");
22
+ this.io.writeErr("No runs found for this worker.");
23
23
  } else {
24
- this.writer.writeTableOut({
24
+ this.io.writeTableOut({
25
25
  headers: ["Run ID", "Name", "Started At", "Ended At", "Exit Code"],
26
26
  rows: data.runs.map((run) => [
27
27
  run.runId,
@@ -38,10 +38,10 @@ export const listRuns = buildAuthedHandler(async function (flags: FormatFlags) {
38
38
 
39
39
  // If it's a validation error, show the clean debug message
40
40
  if (result.error.validationError) {
41
- this.writer.writeErr(`✗ ${result.error.validationError.debugMessage}`);
41
+ this.io.writeErr(`✗ ${result.error.validationError.debugMessage}`);
42
42
  throw new Error(result.error.validationError.debugMessage);
43
43
  } else {
44
- this.writer.writeErr(`✗ ${result.error.message}`);
44
+ this.io.writeErr(`✗ ${result.error.message}`);
45
45
  throw new Error(result.error.message);
46
46
  }
47
47
  }
@@ -71,16 +71,16 @@ export const getRunLogs = buildAuthedHandler(async function (
71
71
 
72
72
  const data = Result.unwrap(result);
73
73
  // Output logs directly to stdout (primary output)
74
- this.writer.writeOut(data.logs);
74
+ this.io.writeOut(data.logs);
75
75
  } else {
76
76
  this.process.stderr.write("ERROR\n\n");
77
77
 
78
78
  // If it's a validation error, show the clean debug message
79
79
  if (result.error.validationError) {
80
- this.writer.writeErr(`✗ ${result.error.validationError.debugMessage}`);
80
+ this.io.writeErr(`✗ ${result.error.validationError.debugMessage}`);
81
81
  throw new Error(result.error.validationError.debugMessage);
82
82
  } else {
83
- this.writer.writeErr(`✗ ${result.error.message}`);
83
+ this.io.writeErr(`✗ ${result.error.message}`);
84
84
  throw new Error(result.error.message);
85
85
  }
86
86
  }
@@ -17,17 +17,17 @@ export const setSecrets = buildAuthedHandler(async function (
17
17
  );
18
18
  }
19
19
 
20
- this.writer.writeErr(`Setting ${pluralize(secrets.length, "secret")}...`);
20
+ this.io.writeErr(`Setting ${pluralize(secrets.length, "secret")}...`);
21
21
 
22
22
  const result = await this.apiClient.upsertSecrets(workerId, secrets);
23
23
 
24
24
  if (Result.isSuccess(result)) {
25
25
  for (const secret of Result.unwrap(result).secrets) {
26
- this.writer.writeErr(`Set secret "${secret.key}"`);
26
+ this.io.writeErr(`Set secret "${secret.key}"`);
27
27
  }
28
28
  } else {
29
- this.writer.writeErr(`✗ Failed to set secrets`);
30
- this.writer.writeErr(`✗ ${result.error.message}`);
29
+ this.io.writeErr(`✗ Failed to set secrets`);
30
+ this.io.writeErr(`✗ ${result.error.message}`);
31
31
  throw new Error(result.error.message);
32
32
  }
33
33
  });
@@ -51,25 +51,25 @@ export const listSecrets = buildAuthedHandler(async function (
51
51
 
52
52
  const data = Result.unwrap(result);
53
53
  if (data.secrets.length === 0) {
54
- this.writer.writeErr("No secrets for this worker.");
55
- this.writer.writeErr(
54
+ this.io.writeErr("No secrets for this worker.");
55
+ this.io.writeErr(
56
56
  "To list OAuth connect secrets, use `npx workers connect list`",
57
57
  );
58
58
  } else {
59
- this.writer.writeTableOut({
59
+ this.io.writeTableOut({
60
60
  headers: ["Key", "Created At"],
61
61
  rows: data.secrets.map((secret) => [secret.key, secret.createdAt]),
62
62
  plain: flags.plain,
63
63
  });
64
- this.writer.writeErr(
64
+ this.io.writeErr(
65
65
  "To list OAuth connect secrets, use `npx workers connect list`",
66
66
  );
67
67
  }
68
68
  } else {
69
69
  this.process.stderr.write("ERROR\n\n");
70
70
 
71
- this.writer.writeErr(`✗ Failed to list secrets`);
72
- this.writer.writeErr(`✗ ${result.error.message}`);
71
+ this.io.writeErr(`✗ Failed to list secrets`);
72
+ this.io.writeErr(`✗ ${result.error.message}`);
73
73
  throw new Error(result.error.message);
74
74
  }
75
75
  });
@@ -94,8 +94,8 @@ export const removeSecret = buildAuthedHandler(async function (
94
94
  } else {
95
95
  this.process.stderr.write("ERROR\n\n");
96
96
 
97
- this.writer.writeErr(`✗ Failed to remove secret "${key}"`);
98
- this.writer.writeErr(`✗ ${result.error.message}`);
97
+ this.io.writeErr(`✗ Failed to remove secret "${key}"`);
98
+ this.io.writeErr(`✗ ${result.error.message}`);
99
99
  throw new Error(result.error.message);
100
100
  }
101
101
  });
@@ -3,16 +3,10 @@ import { mkdtemp, rm, writeFile } from "node:fs/promises";
3
3
  import * as os from "node:os";
4
4
  import { tmpdir } from "node:os";
5
5
  import * as path from "node:path";
6
- import { Config, type Environment } from "../../config.js";
6
+ import { Config, type ConfigMap } from "../../config.js";
7
+ import type { LocalContext } from "../../context.js";
7
8
  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
- };
9
+ import { IO } from "../../io.js";
16
10
 
17
11
  export const tmpDirectories: string[] = [];
18
12
 
@@ -20,33 +14,36 @@ export const baseFlags: GlobalFlags = {
20
14
  debug: false,
21
15
  };
22
16
 
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
- }
17
+ export async function createAndLoadConfig({
18
+ configFile,
19
+ env,
20
+ flags,
21
+ }: {
22
+ configFile: Partial<ConfigMap>;
23
+ env?: Partial<NodeJS.ProcessEnv>;
24
+ flags?: Partial<GlobalFlags>;
25
+ }): Promise<[config: Config, path: string]> {
26
+ const configFilePath = await createConfigFile(configFile);
31
27
 
32
- export async function createTestConfig(
33
- configFileContents: ConfigFileContents,
34
- ): Promise<Config> {
35
- const configFilePath = await createConfigFile(configFileContents);
36
- return Config.load({
28
+ const map = await Config.load({
37
29
  configFilePath,
38
- processEnv: {} as NodeJS.ProcessEnv,
39
- flags: { debug: false },
30
+ processEnv: env ?? {},
31
+ flags: {
32
+ ...baseFlags,
33
+ ...(flags ?? {}),
34
+ },
40
35
  });
36
+
37
+ return [map, configFilePath];
41
38
  }
42
39
 
43
- export function createBaseContext() {
40
+ export function createBaseContext(): LocalContext {
44
41
  return {
45
- writer: new Writer({ debugEnabled: false }),
46
- process,
47
42
  fs,
43
+ io: new IO({ debugEnabled: false }),
48
44
  os,
49
45
  path,
46
+ process,
50
47
  };
51
48
  }
52
49
 
@@ -58,3 +55,12 @@ export async function cleanupTmpDirectories() {
58
55
  }
59
56
  }
60
57
  }
58
+
59
+ async function createConfigFile(contents: Partial<ConfigMap>) {
60
+ const dir = await mkdtemp(path.join(tmpdir(), "cmd-test-"));
61
+ tmpDirectories.push(dir);
62
+ const configFilePath = path.join(dir, "config.json");
63
+
64
+ await writeFile(configFilePath, JSON.stringify(contents, null, 2), "utf-8");
65
+ return configFilePath;
66
+ }
@@ -1,74 +1,30 @@
1
- import { mkdtemp, rm, writeFile } from "node:fs/promises";
2
- import { tmpdir } from "node:os";
3
- import * as path from "node:path";
1
+ import { readFile } from "node:fs/promises";
4
2
  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");
3
+ import {
4
+ cleanupTmpDirectories,
5
+ createAndLoadConfig,
6
+ } from "./commands/utils/testing.js";
7
+ import { Config, type ConfigMap } from "./config.js";
21
8
 
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 = {
9
+ const baseConfigMap: ConfigMap = {
56
10
  token: "file-token",
57
11
  workerId: "file-worker",
58
12
  environment: "local",
59
13
  baseUrl: "https://config.example.com",
60
14
  };
61
15
 
16
+ afterEach(cleanupTmpDirectories);
17
+
62
18
  describe("Config.load precedence", () => {
63
19
  it("uses values from the config file when no overrides are present", async () => {
64
- const config = await loadTestConfig({
65
- configFile: baseConfigFile,
20
+ const [config] = await createAndLoadConfig({
21
+ configFile: baseConfigMap,
66
22
  });
67
23
 
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);
24
+ expect(config.token).toBe(baseConfigMap.token);
25
+ expect(config.workerId).toBe(baseConfigMap.workerId);
26
+ expect(config.environment).toBe(baseConfigMap.environment);
27
+ expect(config.baseUrl).toBe(baseConfigMap.baseUrl);
72
28
  });
73
29
 
74
30
  it("prefers environment variables over the config file", async () => {
@@ -77,15 +33,15 @@ describe("Config.load precedence", () => {
77
33
  WORKERS_WORKER_ID: "env-worker",
78
34
  WORKERS_ENVIRONMENT: "staging",
79
35
  };
80
- const config = await loadTestConfig({
81
- configFile: baseConfigFile,
36
+ const [config] = await createAndLoadConfig({
37
+ configFile: baseConfigMap,
82
38
  env: envVars,
83
39
  });
84
40
 
85
41
  expect(config.token).toBe(envVars.WORKERS_TOKEN);
86
42
  expect(config.workerId).toBe(envVars.WORKERS_WORKER_ID);
87
43
  expect(config.environment).toBe(envVars.WORKERS_ENVIRONMENT);
88
- expect(config.baseUrl).toBe(baseConfigFile.baseUrl);
44
+ expect(config.baseUrl).toBe(baseConfigMap.baseUrl);
89
45
  });
90
46
 
91
47
  it("prefers flags over environment variables and config file values", async () => {
@@ -100,59 +56,53 @@ describe("Config.load precedence", () => {
100
56
  "base-url": "https://flag.example.com",
101
57
  };
102
58
 
103
- const config = await loadTestConfig({
104
- configFile: baseConfigFile,
59
+ const [config] = await createAndLoadConfig({
60
+ configFile: baseConfigMap,
105
61
  env: envVars,
106
62
  flags,
107
63
  });
108
64
 
109
- expect(config.token).toBe(baseConfigFile.token);
65
+ expect(config.token).toBe(baseConfigMap.token);
110
66
  expect(config.workerId).toBe(envVars.WORKERS_WORKER_ID);
111
67
  expect(config.environment).toBe(flags.env);
112
68
  expect(config.baseUrl).toBe(flags["base-url"]);
113
69
  });
114
70
  });
115
71
 
116
- describe("Config.setEnvironment", () => {
117
- it("updates the baseUrl when environment is changed", async () => {
118
- const config = await loadTestConfig({
72
+ describe("Config.update", () => {
73
+ it("persists only provided keys to disk", async () => {
74
+ const [, configFilePath] = await createAndLoadConfig({
119
75
  configFile: {
120
- token: "test-token",
121
- workerId: null,
76
+ token: "file-token",
77
+ workerId: "file-worker",
122
78
  environment: "prod",
123
- baseUrl: "https://www.notion.so",
79
+ baseUrl: "https://config.example.com",
124
80
  },
125
81
  });
126
82
 
127
- expect(config.environment).toBe("prod");
128
- expect(config.baseUrl).toBe("https://www.notion.so");
129
-
130
- await config.setEnvironment("local");
83
+ const config = await Config.load({
84
+ configFilePath,
85
+ processEnv: {
86
+ WORKERS_BASE_URL: "https://env.example.com",
87
+ } as NodeJS.ProcessEnv,
88
+ flags: { debug: false },
89
+ });
131
90
 
132
- expect(config.environment).toBe("local");
133
- expect(config.baseUrl).toBe("http://localhost:3000");
134
- });
91
+ expect(config.baseUrl).toBe("https://env.example.com");
135
92
 
136
- it("updates baseUrl for all environments", async () => {
137
- const config = await loadTestConfig({
138
- configFile: {
139
- token: "test-token",
140
- workerId: null,
141
- environment: "prod",
142
- baseUrl: "https://www.notion.so",
143
- },
93
+ await config.update({
94
+ token: "updated-token",
144
95
  });
145
96
 
146
- await config.setEnvironment("staging");
147
- expect(config.baseUrl).toBe("https://staging.notion.so");
97
+ expect(config.token).toBe("updated-token");
148
98
 
149
- await config.setEnvironment("dev");
150
- expect(config.baseUrl).toBe("https://dev.notion.so");
99
+ const persisted = JSON.parse(await readFile(configFilePath, "utf-8"));
151
100
 
152
- await config.setEnvironment("prod");
153
- expect(config.baseUrl).toBe("https://www.notion.so");
154
-
155
- await config.setEnvironment("local");
156
- expect(config.baseUrl).toBe("http://localhost:3000");
101
+ expect(persisted).toEqual({
102
+ token: "updated-token",
103
+ workerId: "file-worker",
104
+ environment: "prod",
105
+ baseUrl: "https://config.example.com",
106
+ });
157
107
  });
158
108
  });
package/src/cli/config.ts CHANGED
@@ -5,7 +5,7 @@ import type { GlobalFlags } from "./flags.js";
5
5
  export const Environments = ["local", "staging", "dev", "prod"] as const;
6
6
  export type Environment = (typeof Environments)[number];
7
7
 
8
- interface ConfigMap {
8
+ export interface ConfigMap {
9
9
  environment: Environment;
10
10
  baseUrl: string;
11
11
  token: string | null;
@@ -46,11 +46,11 @@ export class Config {
46
46
  readonly #configFilePath: string;
47
47
 
48
48
  constructor(opts: {
49
+ configMap: ConfigMap;
49
50
  configFilePath: string;
50
- configFile: ConfigMap;
51
51
  }) {
52
+ this.#configMap = opts.configMap;
52
53
  this.#configFilePath = opts.configFilePath;
53
- this.#configMap = opts.configFile;
54
54
  }
55
55
 
56
56
  // Getters read from the environment variables, and then the config file.
@@ -71,32 +71,6 @@ export class Config {
71
71
  return this.#configMap.workerId;
72
72
  }
73
73
 
74
- // Setters update the config file, and then write it to disk.
75
-
76
- async setEnvironment(environment: Environment) {
77
- this.#configMap.environment = environment;
78
- this.#configMap.baseUrl = baseUrl(environment);
79
- await this.#writeConfigFile();
80
- }
81
-
82
- async setToken(token: string | null) {
83
- this.#configMap.token = token;
84
- await this.#writeConfigFile();
85
- }
86
-
87
- async setWorkerId(workerId: string | null) {
88
- this.#configMap.workerId = workerId;
89
- await this.#writeConfigFile();
90
- }
91
-
92
- async #writeConfigFile() {
93
- await fs.promises.writeFile(
94
- this.#configFilePath,
95
- JSON.stringify(this.#configMap, null, 2),
96
- "utf-8",
97
- );
98
- }
99
-
100
74
  get tokenInfo(): { token: string; spaceId: string; cellId: string } {
101
75
  const token = this.token;
102
76
  if (!token) {
@@ -107,47 +81,100 @@ export class Config {
107
81
  return { token, spaceId, cellId };
108
82
  }
109
83
 
84
+ /**
85
+ * Update the config with a partial config map.
86
+ *
87
+ * This will write only the updated keys in the config file on disk. Not all
88
+ * keys are written, since some current keys in the Config object may have
89
+ * come from e.g. environment variables, rather than the original config
90
+ * file.
91
+ *
92
+ * @param config The config update.
93
+ */
94
+ async update(config: Partial<ConfigMap>) {
95
+ Object.assign(this.#configMap, config);
96
+
97
+ const currentConfigFile = await Config.#readConfigFile(
98
+ this.#configFilePath,
99
+ );
100
+ Object.assign(currentConfigFile, config);
101
+ await fs.promises.writeFile(
102
+ this.#configFilePath,
103
+ JSON.stringify(currentConfigFile, null, 2),
104
+ "utf-8",
105
+ );
106
+ }
107
+
110
108
  static async load(opts: {
111
109
  configFilePath: string;
112
110
  processEnv: NodeJS.ProcessEnv;
113
111
  flags: GlobalFlags;
114
112
  }) {
115
113
  const absConfigFilePath = path.resolve(process.cwd(), opts.configFilePath);
116
- const configFile = await Config.#readConfigFile(absConfigFilePath);
114
+ const partialConfig = await Config.#readConfigFile(absConfigFilePath);
117
115
 
118
116
  if (opts.processEnv.WORKERS_TOKEN) {
119
- configFile.token = opts.processEnv.WORKERS_TOKEN;
117
+ partialConfig.token = opts.processEnv.WORKERS_TOKEN;
120
118
  }
121
119
  if (opts.processEnv.WORKERS_ENVIRONMENT) {
122
- configFile.environment = parseEnvironment(
120
+ partialConfig.environment = parseEnvironment(
123
121
  opts.processEnv.WORKERS_ENVIRONMENT,
124
122
  );
125
123
  }
126
124
  if (opts.processEnv.WORKERS_WORKER_ID) {
127
- configFile.workerId = opts.processEnv.WORKERS_WORKER_ID;
125
+ partialConfig.workerId = opts.processEnv.WORKERS_WORKER_ID;
128
126
  }
129
127
  if (opts.processEnv.WORKERS_BASE_URL) {
130
- configFile.baseUrl = opts.processEnv.WORKERS_BASE_URL;
128
+ partialConfig.baseUrl = opts.processEnv.WORKERS_BASE_URL;
131
129
  }
132
130
 
133
131
  if (opts.flags.token) {
134
- configFile.token = opts.flags.token;
132
+ partialConfig.token = opts.flags.token;
135
133
  }
136
134
  if (opts.flags.env) {
137
- configFile.environment = parseEnvironment(opts.flags.env);
135
+ partialConfig.environment = parseEnvironment(opts.flags.env);
138
136
  }
139
137
  if (opts.flags["base-url"]) {
140
- configFile.baseUrl = opts.flags["base-url"];
138
+ partialConfig.baseUrl = opts.flags["base-url"];
141
139
  }
142
140
  if (opts.flags["worker-id"]) {
143
- configFile.workerId = opts.flags["worker-id"];
141
+ partialConfig.workerId = opts.flags["worker-id"];
142
+ }
143
+
144
+ partialConfig.baseUrl ??= baseUrlForEnvironment(
145
+ partialConfig.environment ?? "prod",
146
+ );
147
+
148
+ const environment = partialConfig.environment;
149
+ if (!environment) {
150
+ throw new Error("Environment is required");
151
+ }
152
+
153
+ const baseUrl = partialConfig.baseUrl;
154
+ if (!baseUrl) {
155
+ throw new Error("Base URL is required");
156
+ }
157
+
158
+ const token = partialConfig.token;
159
+ if (token === undefined) {
160
+ throw new Error("Token is required");
161
+ }
162
+
163
+ const workerId = partialConfig.workerId;
164
+ if (workerId === undefined) {
165
+ throw new Error("Worker ID is required");
144
166
  }
145
167
 
146
- configFile.baseUrl ??= baseUrl(configFile.environment ?? "prod");
168
+ const configMap: ConfigMap = {
169
+ environment,
170
+ baseUrl,
171
+ token,
172
+ workerId,
173
+ };
147
174
 
148
175
  return new Config({
149
176
  configFilePath: absConfigFilePath,
150
- configFile: configFile as ConfigMap,
177
+ configMap,
151
178
  });
152
179
  }
153
180
 
@@ -168,7 +195,7 @@ export class Config {
168
195
  token: null,
169
196
  workerId: null,
170
197
  environment: "prod",
171
- baseUrl: baseUrl("prod"),
198
+ baseUrl: baseUrlForEnvironment("prod"),
172
199
  };
173
200
  } else {
174
201
  throw error;
@@ -227,7 +254,7 @@ export function extractPayloadFromToken(token: string): {
227
254
  }
228
255
  }
229
256
 
230
- function baseUrl(environment: Environment): string {
257
+ function baseUrlForEnvironment(environment: Environment): string {
231
258
  switch (environment) {
232
259
  case "local":
233
260
  return "http://localhost:3000";
@@ -3,24 +3,24 @@ import * as os from "node:os";
3
3
  import * as path from "node:path";
4
4
  import type { StricliAutoCompleteContext } from "@stricli/auto-complete";
5
5
  import type { CommandContext } from "@stricli/core";
6
- import { Writer } from "./writer.js";
6
+ import { IO } from "./io.js";
7
7
 
8
8
  export interface LocalContext
9
9
  extends CommandContext,
10
10
  StricliAutoCompleteContext {
11
11
  readonly fs: typeof fs;
12
+ readonly io: IO;
12
13
  readonly os: typeof os;
13
14
  readonly path: typeof path;
14
15
  readonly process: NodeJS.Process;
15
- readonly writer: Writer;
16
16
  }
17
17
 
18
18
  export function buildContext(process: NodeJS.Process): LocalContext {
19
19
  return {
20
20
  fs,
21
+ io: new IO({ debugEnabled: false }),
21
22
  os,
22
23
  path,
23
24
  process,
24
- writer: new Writer({ debugEnabled: false }),
25
25
  };
26
26
  }
package/src/cli/deploy.ts CHANGED
@@ -53,7 +53,7 @@ export async function deployWorker(
53
53
 
54
54
  // Resolve absolute path
55
55
  const absPath = path.resolve(process.cwd(), workerPath);
56
- context.writer.debug(`Deploying worker from: ${absPath}`);
56
+ context.io.debug(`Deploying worker from: ${absPath}`);
57
57
 
58
58
  // Create API client
59
59
  const client = context.apiClient;
@@ -63,7 +63,7 @@ export async function deployWorker(
63
63
  let workerId: string;
64
64
 
65
65
  if ("workerId" in options) {
66
- context.writer.writeErr(`Updating worker...`);
66
+ context.io.writeErr(`Updating worker...`);
67
67
 
68
68
  workerId = options.workerId;
69
69
 
@@ -73,13 +73,13 @@ export async function deployWorker(
73
73
  uploadUrl = res.url;
74
74
  uploadFields = res.fields;
75
75
  } else {
76
- context.writer.writeErr(
76
+ context.io.writeErr(
77
77
  "Failed to generate pre-signed upload URL for worker bundle:",
78
78
  );
79
79
  throw new Error(updateResult.error.message);
80
80
  }
81
81
  } else {
82
- context.writer.writeErr(`Creating worker...`);
82
+ context.io.writeErr(`Creating worker...`);
83
83
 
84
84
  const createResult = await client.createWorker(options.name);
85
85
  if (Result.isSuccess(createResult)) {
@@ -93,20 +93,20 @@ export async function deployWorker(
93
93
  }
94
94
  }
95
95
 
96
- context.writer.debug(`Generated upload URL: ${uploadUrl}`);
96
+ context.io.debug(`Generated upload URL: ${uploadUrl}`);
97
97
 
98
98
  // Build the worker bundle
99
- context.writer.writeErr("Building worker bundle...");
99
+ context.io.writeErr("Building worker bundle...");
100
100
  const archivePath = await buildWorkerBundle(context, absPath);
101
- context.writer.debug(`Created bundle at: ${archivePath}`);
101
+ context.io.debug(`Created bundle at: ${archivePath}`);
102
102
 
103
103
  // Upload the bundle
104
- context.writer.writeErr("Uploading bundle...");
104
+ context.io.writeErr("Uploading bundle...");
105
105
  await uploadBundle(uploadUrl, uploadFields, archivePath);
106
- context.writer.debug("Upload complete");
106
+ context.io.debug("Upload complete");
107
107
 
108
108
  // Fetch and save capabilities
109
- context.writer.writeErr("Fetching and saving worker capabilities...");
109
+ context.io.writeErr("Fetching and saving worker capabilities...");
110
110
  const capabilitiesResult = await client.fetchAndSaveCapabilities(workerId);
111
111
 
112
112
  if (Result.isSuccess(capabilitiesResult)) {
@@ -141,7 +141,7 @@ async function buildWorkerBundle(
141
141
  platform: "node",
142
142
  });
143
143
 
144
- context.writer.debug(`Built bundle to: ${outdir}`);
144
+ context.io.debug(`Built bundle to: ${outdir}`);
145
145
 
146
146
  // Create tar.gz archive
147
147
  const archiveDir = fs.mkdtempSync("/tmp/workers-archive-");