@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
@@ -1,7 +1,7 @@
1
1
  import type { GlobalFlags } from "./flags.js";
2
2
  export declare const Environments: readonly ["local", "staging", "dev", "prod"];
3
3
  export type Environment = (typeof Environments)[number];
4
- interface ConfigMap {
4
+ export interface ConfigMap {
5
5
  environment: Environment;
6
6
  baseUrl: string;
7
7
  token: string | null;
@@ -26,21 +26,29 @@ export declare class TokenNotSetError extends Error {
26
26
  export declare class Config {
27
27
  #private;
28
28
  constructor(opts: {
29
+ configMap: ConfigMap;
29
30
  configFilePath: string;
30
- configFile: ConfigMap;
31
31
  });
32
32
  get baseUrl(): string;
33
33
  get token(): string | null;
34
34
  get environment(): "local" | "staging" | "dev" | "prod";
35
35
  get workerId(): string | null;
36
- setEnvironment(environment: Environment): Promise<void>;
37
- setToken(token: string | null): Promise<void>;
38
- setWorkerId(workerId: string | null): Promise<void>;
39
36
  get tokenInfo(): {
40
37
  token: string;
41
38
  spaceId: string;
42
39
  cellId: string;
43
40
  };
41
+ /**
42
+ * Update the config with a partial config map.
43
+ *
44
+ * This will write only the updated keys in the config file on disk. Not all
45
+ * keys are written, since some current keys in the Config object may have
46
+ * come from e.g. environment variables, rather than the original config
47
+ * file.
48
+ *
49
+ * @param config The config update.
50
+ */
51
+ update(config: Partial<ConfigMap>): Promise<void>;
44
52
  static load(opts: {
45
53
  configFilePath: string;
46
54
  processEnv: NodeJS.ProcessEnv;
@@ -52,5 +60,4 @@ export declare function extractPayloadFromToken(token: string): {
52
60
  userId: string;
53
61
  cellId: string;
54
62
  };
55
- export {};
56
63
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/cli/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,eAAO,MAAM,YAAY,8CAA+C,CAAC;AACzE,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAExD,UAAU,SAAS;IAClB,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAM1D;AAED,qBAAa,gBAAiB,SAAQ,KAAK;gBAEzC,OAAO,GAAE,MAA6D;CAKvE;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,MAAM;;gBAIN,IAAI,EAAE;QACjB,cAAc,EAAE,MAAM,CAAC;QACvB,UAAU,EAAE,SAAS,CAAC;KACtB;IAOD,IAAI,OAAO,WAEV;IAED,IAAI,KAAK,kBAER;IAED,IAAI,WAAW,yCAEd;IAED,IAAI,QAAQ,kBAEX;IAIK,cAAc,CAAC,WAAW,EAAE,WAAW;IAMvC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK7B,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAazC,IAAI,SAAS,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAQlE;WAEY,IAAI,CAAC,IAAI,EAAE;QACvB,cAAc,EAAE,MAAM,CAAC;QACvB,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;QAC9B,KAAK,EAAE,WAAW,CAAC;KACnB;CAkED;AAQD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CACf,CAoCA"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/cli/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,eAAO,MAAM,YAAY,8CAA+C,CAAC;AACzE,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAExD,MAAM,WAAW,SAAS;IACzB,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAM1D;AAED,qBAAa,gBAAiB,SAAQ,KAAK;gBAEzC,OAAO,GAAE,MAA6D;CAKvE;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,MAAM;;gBAIN,IAAI,EAAE;QACjB,SAAS,EAAE,SAAS,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;KACvB;IAOD,IAAI,OAAO,WAEV;IAED,IAAI,KAAK,kBAER;IAED,IAAI,WAAW,yCAEd;IAED,IAAI,QAAQ,kBAEX;IAED,IAAI,SAAS,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAQlE;IAED;;;;;;;;;OASG;IACG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,SAAS,CAAC;WAc1B,IAAI,CAAC,IAAI,EAAE;QACvB,cAAc,EAAE,MAAM,CAAC;QACvB,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;QAC9B,KAAK,EAAE,WAAW,CAAC;KACnB;CA+FD;AAQD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CACf,CAoCA"}
@@ -17,8 +17,8 @@ class Config {
17
17
  #configMap;
18
18
  #configFilePath;
19
19
  constructor(opts) {
20
+ this.#configMap = opts.configMap;
20
21
  this.#configFilePath = opts.configFilePath;
21
- this.#configMap = opts.configFile;
22
22
  }
23
23
  // Getters read from the environment variables, and then the config file.
24
24
  get baseUrl() {
@@ -33,27 +33,6 @@ class Config {
33
33
  get workerId() {
34
34
  return this.#configMap.workerId;
35
35
  }
36
- // Setters update the config file, and then write it to disk.
37
- async setEnvironment(environment) {
38
- this.#configMap.environment = environment;
39
- this.#configMap.baseUrl = baseUrl(environment);
40
- await this.#writeConfigFile();
41
- }
42
- async setToken(token) {
43
- this.#configMap.token = token;
44
- await this.#writeConfigFile();
45
- }
46
- async setWorkerId(workerId) {
47
- this.#configMap.workerId = workerId;
48
- await this.#writeConfigFile();
49
- }
50
- async #writeConfigFile() {
51
- await fs.promises.writeFile(
52
- this.#configFilePath,
53
- JSON.stringify(this.#configMap, null, 2),
54
- "utf-8"
55
- );
56
- }
57
36
  get tokenInfo() {
58
37
  const token = this.token;
59
38
  if (!token) {
@@ -62,39 +41,85 @@ class Config {
62
41
  const { spaceId, cellId } = extractPayloadFromToken(token);
63
42
  return { token, spaceId, cellId };
64
43
  }
44
+ /**
45
+ * Update the config with a partial config map.
46
+ *
47
+ * This will write only the updated keys in the config file on disk. Not all
48
+ * keys are written, since some current keys in the Config object may have
49
+ * come from e.g. environment variables, rather than the original config
50
+ * file.
51
+ *
52
+ * @param config The config update.
53
+ */
54
+ async update(config) {
55
+ Object.assign(this.#configMap, config);
56
+ const currentConfigFile = await Config.#readConfigFile(
57
+ this.#configFilePath
58
+ );
59
+ Object.assign(currentConfigFile, config);
60
+ await fs.promises.writeFile(
61
+ this.#configFilePath,
62
+ JSON.stringify(currentConfigFile, null, 2),
63
+ "utf-8"
64
+ );
65
+ }
65
66
  static async load(opts) {
66
67
  const absConfigFilePath = path.resolve(process.cwd(), opts.configFilePath);
67
- const configFile = await Config.#readConfigFile(absConfigFilePath);
68
+ const partialConfig = await Config.#readConfigFile(absConfigFilePath);
68
69
  if (opts.processEnv.WORKERS_TOKEN) {
69
- configFile.token = opts.processEnv.WORKERS_TOKEN;
70
+ partialConfig.token = opts.processEnv.WORKERS_TOKEN;
70
71
  }
71
72
  if (opts.processEnv.WORKERS_ENVIRONMENT) {
72
- configFile.environment = parseEnvironment(
73
+ partialConfig.environment = parseEnvironment(
73
74
  opts.processEnv.WORKERS_ENVIRONMENT
74
75
  );
75
76
  }
76
77
  if (opts.processEnv.WORKERS_WORKER_ID) {
77
- configFile.workerId = opts.processEnv.WORKERS_WORKER_ID;
78
+ partialConfig.workerId = opts.processEnv.WORKERS_WORKER_ID;
78
79
  }
79
80
  if (opts.processEnv.WORKERS_BASE_URL) {
80
- configFile.baseUrl = opts.processEnv.WORKERS_BASE_URL;
81
+ partialConfig.baseUrl = opts.processEnv.WORKERS_BASE_URL;
81
82
  }
82
83
  if (opts.flags.token) {
83
- configFile.token = opts.flags.token;
84
+ partialConfig.token = opts.flags.token;
84
85
  }
85
86
  if (opts.flags.env) {
86
- configFile.environment = parseEnvironment(opts.flags.env);
87
+ partialConfig.environment = parseEnvironment(opts.flags.env);
87
88
  }
88
89
  if (opts.flags["base-url"]) {
89
- configFile.baseUrl = opts.flags["base-url"];
90
+ partialConfig.baseUrl = opts.flags["base-url"];
90
91
  }
91
92
  if (opts.flags["worker-id"]) {
92
- configFile.workerId = opts.flags["worker-id"];
93
+ partialConfig.workerId = opts.flags["worker-id"];
94
+ }
95
+ partialConfig.baseUrl ??= baseUrlForEnvironment(
96
+ partialConfig.environment ?? "prod"
97
+ );
98
+ const environment = partialConfig.environment;
99
+ if (!environment) {
100
+ throw new Error("Environment is required");
101
+ }
102
+ const baseUrl = partialConfig.baseUrl;
103
+ if (!baseUrl) {
104
+ throw new Error("Base URL is required");
105
+ }
106
+ const token = partialConfig.token;
107
+ if (token === void 0) {
108
+ throw new Error("Token is required");
93
109
  }
94
- configFile.baseUrl ??= baseUrl(configFile.environment ?? "prod");
110
+ const workerId = partialConfig.workerId;
111
+ if (workerId === void 0) {
112
+ throw new Error("Worker ID is required");
113
+ }
114
+ const configMap = {
115
+ environment,
116
+ baseUrl,
117
+ token,
118
+ workerId
119
+ };
95
120
  return new Config({
96
121
  configFilePath: absConfigFilePath,
97
- configFile
122
+ configMap
98
123
  });
99
124
  }
100
125
  static async #readConfigFile(configFilePath) {
@@ -111,7 +136,7 @@ class Config {
111
136
  token: null,
112
137
  workerId: null,
113
138
  environment: "prod",
114
- baseUrl: baseUrl("prod")
139
+ baseUrl: baseUrlForEnvironment("prod")
115
140
  };
116
141
  } else {
117
142
  throw error;
@@ -151,7 +176,7 @@ function extractPayloadFromToken(token) {
151
176
  throw new Error("Failed to parse token payload.");
152
177
  }
153
178
  }
154
- function baseUrl(environment) {
179
+ function baseUrlForEnvironment(environment) {
155
180
  switch (environment) {
156
181
  case "local":
157
182
  return "http://localhost:3000";
@@ -3,13 +3,13 @@ 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
  export interface LocalContext extends CommandContext, StricliAutoCompleteContext {
8
8
  readonly fs: typeof fs;
9
+ readonly io: IO;
9
10
  readonly os: typeof os;
10
11
  readonly path: typeof path;
11
12
  readonly process: NodeJS.Process;
12
- readonly writer: Writer;
13
13
  }
14
14
  export declare function buildContext(process: NodeJS.Process): LocalContext;
15
15
  //# sourceMappingURL=context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/cli/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,MAAM,WAAW,YAChB,SAAQ,cAAc,EACrB,0BAA0B;IAC3B,QAAQ,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,IAAI,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,GAAG,YAAY,CAQlE"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/cli/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAE7B,MAAM,WAAW,YAChB,SAAQ,cAAc,EACrB,0BAA0B;IAC3B,QAAQ,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,IAAI,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC;CACjC;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,GAAG,YAAY,CAQlE"}
@@ -1,14 +1,14 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
- import { Writer } from "./writer.js";
4
+ import { IO } from "./io.js";
5
5
  function buildContext(process) {
6
6
  return {
7
7
  fs,
8
+ io: new IO({ debugEnabled: false }),
8
9
  os,
9
10
  path,
10
- process,
11
- writer: new Writer({ debugEnabled: false })
11
+ process
12
12
  };
13
13
  }
14
14
  export {
@@ -6,13 +6,13 @@ import { Result } from "./api/result.js";
6
6
  async function deployWorker(context, options) {
7
7
  const { workerPath } = options;
8
8
  const absPath = path.resolve(process.cwd(), workerPath);
9
- context.writer.debug(`Deploying worker from: ${absPath}`);
9
+ context.io.debug(`Deploying worker from: ${absPath}`);
10
10
  const client = context.apiClient;
11
11
  let uploadUrl;
12
12
  let uploadFields;
13
13
  let workerId;
14
14
  if ("workerId" in options) {
15
- context.writer.writeErr(`Updating worker...`);
15
+ context.io.writeErr(`Updating worker...`);
16
16
  workerId = options.workerId;
17
17
  const updateResult = await client.updateWorkerBundle(workerId);
18
18
  if (Result.isSuccess(updateResult)) {
@@ -20,13 +20,13 @@ async function deployWorker(context, options) {
20
20
  uploadUrl = res.url;
21
21
  uploadFields = res.fields;
22
22
  } else {
23
- context.writer.writeErr(
23
+ context.io.writeErr(
24
24
  "Failed to generate pre-signed upload URL for worker bundle:"
25
25
  );
26
26
  throw new Error(updateResult.error.message);
27
27
  }
28
28
  } else {
29
- context.writer.writeErr(`Creating worker...`);
29
+ context.io.writeErr(`Creating worker...`);
30
30
  const createResult = await client.createWorker(options.name);
31
31
  if (Result.isSuccess(createResult)) {
32
32
  const res = Result.unwrap(createResult);
@@ -38,14 +38,14 @@ async function deployWorker(context, options) {
38
38
  throw new Error(createResult.error.message);
39
39
  }
40
40
  }
41
- context.writer.debug(`Generated upload URL: ${uploadUrl}`);
42
- context.writer.writeErr("Building worker bundle...");
41
+ context.io.debug(`Generated upload URL: ${uploadUrl}`);
42
+ context.io.writeErr("Building worker bundle...");
43
43
  const archivePath = await buildWorkerBundle(context, absPath);
44
- context.writer.debug(`Created bundle at: ${archivePath}`);
45
- context.writer.writeErr("Uploading bundle...");
44
+ context.io.debug(`Created bundle at: ${archivePath}`);
45
+ context.io.writeErr("Uploading bundle...");
46
46
  await uploadBundle(uploadUrl, uploadFields, archivePath);
47
- context.writer.debug("Upload complete");
48
- context.writer.writeErr("Fetching and saving worker capabilities...");
47
+ context.io.debug("Upload complete");
48
+ context.io.writeErr("Fetching and saving worker capabilities...");
49
49
  const capabilitiesResult = await client.fetchAndSaveCapabilities(workerId);
50
50
  if (Result.isSuccess(capabilitiesResult)) {
51
51
  return Result.success({ workerId });
@@ -66,7 +66,7 @@ async function buildWorkerBundle(context, workerPath) {
66
66
  outdir,
67
67
  platform: "node"
68
68
  });
69
- context.writer.debug(`Built bundle to: ${outdir}`);
69
+ context.io.debug(`Built bundle to: ${outdir}`);
70
70
  const archiveDir = fs.mkdtempSync("/tmp/workers-archive-");
71
71
  const archivePath = path.join(archiveDir, "archive.tar.gz");
72
72
  childProcess.execSync(`tar -czf ${archivePath} -C ${outdir} .`);
@@ -8,7 +8,7 @@ function buildHandler(handler) {
8
8
  processEnv: process.env,
9
9
  flags
10
10
  });
11
- this.writer.debugEnabled = flags.debug;
11
+ this.io.debugEnabled = flags.debug;
12
12
  await handler.call({ ...this, config }, flags, ...args);
13
13
  };
14
14
  }
@@ -21,7 +21,7 @@ function buildAuthedHandler(handler) {
21
21
  environment,
22
22
  baseUrl: this.config.baseUrl,
23
23
  cellId,
24
- writer: this.writer
24
+ writer: this.io
25
25
  });
26
26
  return handler.call({ ...this, apiClient: client }, flags, ...args);
27
27
  });
@@ -1,3 +1,4 @@
1
+ import * as prompts from "@inquirer/prompts";
1
2
  import { type TableCell } from "@visulima/tabular";
2
3
  export interface WriterOptions {
3
4
  debugEnabled: boolean;
@@ -7,10 +8,13 @@ export interface TableOptions {
7
8
  rows: TableCell[][];
8
9
  plain: boolean;
9
10
  }
11
+ type WithSafety<T> = T & {
12
+ noTTY: string;
13
+ };
10
14
  /**
11
- * A writer writes messages to standard out and standard error.
15
+ * IO manages safe, consistent, input and output patterns for the CLI.
12
16
  */
13
- export declare class Writer {
17
+ export declare class IO {
14
18
  #private;
15
19
  debugEnabled: boolean;
16
20
  constructor(options: WriterOptions);
@@ -44,5 +48,8 @@ export declare class Writer {
44
48
  * @param tableConfig The table configuration.
45
49
  */
46
50
  writeTableErr(tableConfig: TableOptions): void;
51
+ confirm(config: WithSafety<Parameters<typeof prompts.confirm>[0]>): Promise<boolean | void>;
52
+ input(config: WithSafety<Parameters<typeof prompts.input>[0]>): Promise<string | void>;
47
53
  }
48
- //# sourceMappingURL=writer.d.ts.map
54
+ export {};
55
+ //# sourceMappingURL=io.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"io.d.ts","sourceRoot":"","sources":["../../src/cli/io.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAe,KAAK,SAAS,EAAkB,MAAM,mBAAmB,CAAC;AAEhF,MAAM,WAAW,aAAa;IAC7B,YAAY,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,EAAE,CAAC;IACrC,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;CACf;AAED,KAAK,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;GAEG;AACH,qBAAa,EAAE;;IACd,YAAY,EAAE,OAAO,CAAC;gBAEV,OAAO,EAAE,aAAa;IAIlC;;;;OAIG;IACH,KAAK,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,GAAG,CAAC;IAW7C;;;;OAIG;IACH,QAAQ,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;IAIzD;;;;OAIG;IACH,QAAQ,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;IAIzD;;;;OAIG;IACH,aAAa,CAAC,WAAW,EAAE,YAAY;IAKvC;;;;OAIG;IACH,aAAa,CAAC,WAAW,EAAE,YAAY;IAKvC,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC,UAAU,CAAC,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IASjE,KAAK,CAAC,MAAM,EAAE,UAAU,CAAC,UAAU,CAAC,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;CAkC7D"}
@@ -1,5 +1,6 @@
1
+ import * as prompts from "@inquirer/prompts";
1
2
  import { createTable } from "@visulima/tabular";
2
- class Writer {
3
+ class IO {
3
4
  debugEnabled;
4
5
  constructor(options) {
5
6
  this.debugEnabled = options.debugEnabled;
@@ -52,6 +53,27 @@ class Writer {
52
53
  const content = this.#buildTable(tableConfig);
53
54
  this.writeErr(content);
54
55
  }
56
+ confirm(config) {
57
+ if (!process.stdin.isTTY) {
58
+ this.writeErr(config.noTTY);
59
+ process.exit(1);
60
+ }
61
+ return prompts.confirm(config).catch(this.#handlePromptExit.bind(this));
62
+ }
63
+ input(config) {
64
+ if (!process.stdin.isTTY) {
65
+ this.writeErr(config.noTTY);
66
+ process.exit(1);
67
+ }
68
+ return prompts.input(config).catch(this.#handlePromptExit.bind(this));
69
+ }
70
+ #handlePromptExit(err) {
71
+ if (err instanceof Error && err.name === "ExitPromptError") {
72
+ this.writeErr("\u{1F44B} Prompt cancelled. Goodbye!");
73
+ process.exit(1);
74
+ }
75
+ throw err;
76
+ }
55
77
  #buildTable(tableConfig) {
56
78
  if (tableConfig.plain) {
57
79
  return tableConfig.rows.map((row) => {
@@ -69,5 +91,5 @@ function isTableItem(cell) {
69
91
  return typeof cell === "object";
70
92
  }
71
93
  export {
72
- Writer
94
+ IO
73
95
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@project-ajax/sdk",
3
- "version": "0.0.54",
3
+ "version": "0.0.56",
4
4
  "description": "An SDK for building workers for the Project Ajax platform",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -70,11 +70,11 @@
70
70
  "vitest": "^4.0.8"
71
71
  },
72
72
  "dependencies": {
73
+ "@inquirer/prompts": "^8.0.1",
73
74
  "@stricli/auto-complete": "^1.2.4",
74
75
  "@stricli/core": "^1.2.4",
75
76
  "@visulima/tabular": "^3.1.1",
76
77
  "ajv": "^8.17.1",
77
- "esbuild": "^0.25.12",
78
- "prompts": "^2.4.2"
78
+ "esbuild": "^0.25.12"
79
79
  }
80
80
  }
@@ -2,7 +2,7 @@
2
2
  * API client for making authenticated requests to the Workers API
3
3
  */
4
4
 
5
- import type { Writer } from "../writer.js";
5
+ import type { IO } from "../io.js";
6
6
  import { Result } from "./result.js";
7
7
 
8
8
  export type Environment = "local" | "staging" | "dev" | "prod";
@@ -12,7 +12,7 @@ interface ApiClientConfig {
12
12
  environment: Environment;
13
13
  baseUrl?: string | undefined;
14
14
  cellId: string;
15
- writer: Writer;
15
+ writer: IO;
16
16
  }
17
17
 
18
18
  type Endpoint = `/${string}`;
@@ -36,7 +36,7 @@ export class ApiClient {
36
36
  readonly #token: string;
37
37
  readonly #baseUrl: string | undefined;
38
38
  readonly #cellId: string;
39
- readonly #writer: Writer;
39
+ readonly #writer: IO;
40
40
 
41
41
  constructor(config: ApiClientConfig) {
42
42
  this.#token = config.token;
@@ -11,8 +11,8 @@ import { Config } from "../config.js";
11
11
  import {
12
12
  baseFlags,
13
13
  cleanupTmpDirectories,
14
+ createAndLoadConfig,
14
15
  createBaseContext,
15
- createTestConfig,
16
16
  } from "./utils/testing.js";
17
17
 
18
18
  // Mock the openUrl module before importing the implementation
@@ -35,11 +35,13 @@ describe("login", () => {
35
35
  });
36
36
 
37
37
  it("opens browser and displays instructions when no token provided", async () => {
38
- const mockConfig = await createTestConfig({
39
- token: null,
40
- workerId: null,
41
- environment: "local",
42
- baseUrl: "http://localhost:3000",
38
+ const [mockConfig] = await createAndLoadConfig({
39
+ configFile: {
40
+ token: null,
41
+ workerId: null,
42
+ environment: "local",
43
+ baseUrl: "http://localhost:3000",
44
+ },
43
45
  });
44
46
 
45
47
  vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
@@ -63,11 +65,13 @@ describe("login", () => {
63
65
  });
64
66
 
65
67
  it("shows error message when browser fails to open", async () => {
66
- const mockConfig = await createTestConfig({
67
- token: null,
68
- workerId: null,
69
- environment: "local",
70
- baseUrl: "http://localhost:3000",
68
+ const [mockConfig] = await createAndLoadConfig({
69
+ configFile: {
70
+ token: null,
71
+ workerId: null,
72
+ environment: "local",
73
+ baseUrl: "http://localhost:3000",
74
+ },
71
75
  });
72
76
 
73
77
  vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
@@ -85,11 +89,13 @@ describe("login", () => {
85
89
  });
86
90
 
87
91
  it("saves token and clears workerId when token is provided", async () => {
88
- const mockConfig = await createTestConfig({
89
- token: null,
90
- workerId: null,
91
- environment: "local",
92
- baseUrl: "http://localhost:3000",
92
+ const [mockConfig] = await createAndLoadConfig({
93
+ configFile: {
94
+ token: null,
95
+ workerId: null,
96
+ environment: "local",
97
+ baseUrl: "http://localhost:3000",
98
+ },
93
99
  });
94
100
 
95
101
  // Spy on Config.load to return our mock config
@@ -98,28 +104,30 @@ describe("login", () => {
98
104
  const testToken =
99
105
  "1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig";
100
106
 
101
- const setEnvironmentSpy = vi.spyOn(mockConfig, "setEnvironment");
102
- const setTokenSpy = vi.spyOn(mockConfig, "setToken");
103
- const setWorkerIdSpy = vi.spyOn(mockConfig, "setWorkerId");
107
+ const updateSpy = vi.spyOn(mockConfig, "update");
104
108
 
105
109
  const context = createBaseContext();
106
110
 
107
111
  await login.call(context, baseFlags, testToken);
108
112
 
109
- expect(setEnvironmentSpy).toHaveBeenCalledWith("local");
110
- expect(setTokenSpy).toHaveBeenCalledWith(testToken);
111
- expect(setWorkerIdSpy).toHaveBeenCalledWith(null);
113
+ expect(updateSpy).toHaveBeenCalledWith({
114
+ environment: "local",
115
+ token: testToken,
116
+ workerId: null,
117
+ });
112
118
  expect(stderrSpy).toHaveBeenCalledWith(
113
119
  expect.stringContaining("Successfully logged in!"),
114
120
  );
115
121
  });
116
122
 
117
123
  it("saves environment from config when logging in", async () => {
118
- const mockConfig = await createTestConfig({
119
- token: null,
120
- workerId: null,
121
- environment: "prod",
122
- baseUrl: "https://www.notion.so",
124
+ const [mockConfig] = await createAndLoadConfig({
125
+ configFile: {
126
+ token: null,
127
+ workerId: null,
128
+ environment: "prod",
129
+ baseUrl: "https://www.notion.so",
130
+ },
123
131
  });
124
132
 
125
133
  vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
@@ -127,13 +135,17 @@ describe("login", () => {
127
135
  const testToken =
128
136
  "1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig";
129
137
 
130
- const setEnvironmentSpy = vi.spyOn(mockConfig, "setEnvironment");
138
+ const updateSpy = vi.spyOn(mockConfig, "update");
131
139
 
132
140
  const context = createBaseContext();
133
141
 
134
142
  await login.call(context, baseFlags, testToken);
135
143
 
136
- expect(setEnvironmentSpy).toHaveBeenCalledWith("prod");
144
+ expect(updateSpy).toHaveBeenCalledWith({
145
+ environment: "prod",
146
+ token: testToken,
147
+ workerId: null,
148
+ });
137
149
  });
138
150
  });
139
151
 
@@ -150,11 +162,13 @@ describe("show", () => {
150
162
  const testToken =
151
163
  "1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig";
152
164
 
153
- const mockConfig = await createTestConfig({
154
- token: testToken,
155
- workerId: "worker-1",
156
- environment: "local",
157
- baseUrl: "http://localhost:3000",
165
+ const [mockConfig] = await createAndLoadConfig({
166
+ configFile: {
167
+ token: testToken,
168
+ workerId: "worker-1",
169
+ environment: "local",
170
+ baseUrl: "http://localhost:3000",
171
+ },
158
172
  });
159
173
 
160
174
  vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
@@ -167,11 +181,13 @@ describe("show", () => {
167
181
  });
168
182
 
169
183
  it("writes empty string to stdout when no token exists", async () => {
170
- const mockConfig = await createTestConfig({
171
- token: null,
172
- workerId: null,
173
- environment: "local",
174
- baseUrl: "http://localhost:3000",
184
+ const [mockConfig] = await createAndLoadConfig({
185
+ configFile: {
186
+ token: null,
187
+ workerId: null,
188
+ environment: "local",
189
+ baseUrl: "http://localhost:3000",
190
+ },
175
191
  });
176
192
 
177
193
  vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
@@ -186,21 +202,25 @@ describe("show", () => {
186
202
 
187
203
  describe("logout", () => {
188
204
  it("calls setToken with null", async () => {
189
- const mockConfig = await createTestConfig({
190
- token: "existing-token",
191
- workerId: "worker-1",
192
- environment: "local",
193
- baseUrl: "http://localhost:3000",
205
+ const [mockConfig] = await createAndLoadConfig({
206
+ configFile: {
207
+ token: "existing-token",
208
+ workerId: "worker-1",
209
+ environment: "local",
210
+ baseUrl: "http://localhost:3000",
211
+ },
194
212
  });
195
213
 
196
214
  vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
197
215
 
198
- const setTokenSpy = vi.spyOn(mockConfig, "setToken");
216
+ const updateSpy = vi.spyOn(mockConfig, "update");
199
217
 
200
218
  const context = createBaseContext();
201
219
 
202
220
  await logout.call(context, baseFlags);
203
221
 
204
- expect(setTokenSpy).toHaveBeenCalledWith(null);
222
+ expect(updateSpy).toHaveBeenCalledWith({
223
+ token: null,
224
+ });
205
225
  });
206
226
  });