@project-ajax/cli 0.0.17 → 0.0.19

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/commands/auth.d.ts.map +1 -1
  2. package/dist/commands/auth.impl.d.ts +12 -2
  3. package/dist/commands/auth.impl.d.ts.map +1 -1
  4. package/dist/commands/auth.impl.js +73 -6
  5. package/dist/commands/auth.js +39 -2
  6. package/dist/commands/capabilities.impl.d.ts +1 -1
  7. package/dist/commands/capabilities.impl.d.ts.map +1 -1
  8. package/dist/commands/capabilities.impl.js +7 -1
  9. package/dist/commands/delete.d.ts +3 -0
  10. package/dist/commands/delete.d.ts.map +1 -0
  11. package/dist/commands/delete.impl.d.ts +7 -0
  12. package/dist/commands/delete.impl.d.ts.map +1 -0
  13. package/dist/commands/delete.impl.js +26 -0
  14. package/dist/commands/delete.impl.test.d.ts +2 -0
  15. package/dist/commands/delete.impl.test.d.ts.map +1 -0
  16. package/dist/commands/delete.js +31 -0
  17. package/dist/commands/deploy.js +1 -1
  18. package/dist/commands/list.d.ts +3 -0
  19. package/dist/commands/list.d.ts.map +1 -0
  20. package/dist/commands/list.impl.d.ts +3 -0
  21. package/dist/commands/list.impl.d.ts.map +1 -0
  22. package/dist/commands/list.impl.js +41 -0
  23. package/dist/commands/list.impl.test.d.ts +2 -0
  24. package/dist/commands/list.impl.test.d.ts.map +1 -0
  25. package/dist/commands/list.js +17 -0
  26. package/dist/commands/oauth.impl.d.ts +1 -1
  27. package/dist/commands/oauth.impl.d.ts.map +1 -1
  28. package/dist/commands/oauth.impl.js +11 -3
  29. package/dist/commands/runs.impl.d.ts +1 -1
  30. package/dist/commands/runs.impl.d.ts.map +1 -1
  31. package/dist/commands/runs.impl.js +7 -1
  32. package/dist/commands/secrets.impl.d.ts +1 -1
  33. package/dist/commands/secrets.impl.d.ts.map +1 -1
  34. package/dist/commands/secrets.impl.js +7 -1
  35. package/dist/commands/utils/testing.d.ts +4 -2
  36. package/dist/commands/utils/testing.d.ts.map +1 -1
  37. package/dist/commands/utils/testing.js +23 -10
  38. package/dist/config.d.ts +126 -7
  39. package/dist/config.d.ts.map +1 -1
  40. package/dist/config.js +206 -37
  41. package/dist/flags.d.ts +5 -0
  42. package/dist/flags.d.ts.map +1 -1
  43. package/dist/flags.js +34 -1
  44. package/dist/handler.d.ts.map +1 -1
  45. package/dist/handler.js +9 -3
  46. package/dist/routes.d.ts.map +1 -1
  47. package/dist/routes.js +39 -5
  48. package/dist/token.d.ts +7 -0
  49. package/dist/token.d.ts.map +1 -1
  50. package/dist/token.js +10 -1
  51. package/package.json +2 -2
@@ -5,29 +5,36 @@ import { tmpdir } from "node:os";
5
5
  import * as path from "node:path";
6
6
  import { getPassword } from "cross-keychain";
7
7
  import { vi } from "vitest";
8
- import { Config } from "../../config.js";
8
+ import {
9
+ Config
10
+ } from "../../config.js";
9
11
  import { IO } from "../../io.js";
10
12
  const baseFlags = {
11
13
  debug: false
12
14
  };
13
15
  function preventConfigResolution() {
16
+ vi.spyOn(Config, "resolveGlobalConfigPath").mockImplementation(() => "");
14
17
  vi.spyOn(Config, "resolveSpaceCachePath").mockImplementation(() => "");
15
18
  vi.spyOn(Config, "resolveLocalConfigPath").mockImplementation(() => "");
16
19
  }
17
20
  async function createAndLoadConfig(args = {}) {
18
- const globalConfig = args.globalConfig ?? Config.emptySpaceCache;
21
+ const userConfig = args.userConfig ?? Config.emptyGlobalConfig;
22
+ const spaceCache = args.spaceCache ?? Config.emptySpaceCache;
19
23
  const localConfig = args.localConfig ?? Config.emptyLocalConfig;
20
- const { globalPath, localPath } = await createConfigFiles({
21
- globalConfig,
24
+ const { globalConfigPath, spaceCachePath, localPath } = await createConfigFiles({
25
+ userConfig,
26
+ spaceCache,
22
27
  localConfig
23
28
  });
24
29
  const config = Config.load({
25
- spaceCachePath: globalPath,
30
+ globalConfigPath,
31
+ spaceCachePath,
26
32
  localPath,
27
33
  env: args.env ?? {},
28
34
  flags: {
29
35
  ...baseFlags,
30
- ...args.flags ?? {}
36
+ ...args.flags ?? {},
37
+ ...args.spaceOverride ? { space: args.spaceOverride } : {}
31
38
  }
32
39
  });
33
40
  return config;
@@ -55,11 +62,17 @@ async function cleanupTmpDirectories() {
55
62
  async function createConfigFiles(args) {
56
63
  const dir = await fsp.mkdtemp(path.join(tmpdir(), "cmd-test-"));
57
64
  tmpDirectories.push(dir);
58
- const globalPath = path.join(dir, "global.json");
65
+ const globalConfigPath = path.join(dir, "config.json");
66
+ const spaceCachePath = path.join(dir, "spaces.json");
59
67
  const localPath = path.join(dir, "local.json");
60
68
  await fsp.writeFile(
61
- globalPath,
62
- JSON.stringify(args.globalConfig, null, 2),
69
+ globalConfigPath,
70
+ JSON.stringify(args.userConfig, null, 2),
71
+ "utf-8"
72
+ );
73
+ await fsp.writeFile(
74
+ spaceCachePath,
75
+ JSON.stringify(args.spaceCache, null, 2),
63
76
  "utf-8"
64
77
  );
65
78
  await fsp.writeFile(
@@ -67,7 +80,7 @@ async function createConfigFiles(args) {
67
80
  JSON.stringify(args.localConfig, null, 2),
68
81
  "utf-8"
69
82
  );
70
- return { globalPath, localPath };
83
+ return { globalConfigPath, spaceCachePath, localPath };
71
84
  }
72
85
  function generateV1Token(args = {}, opts = { mock: false }) {
73
86
  const payload = {
package/dist/config.d.ts CHANGED
@@ -20,22 +20,81 @@ declare const LocalConfig: z.ZodObject<{
20
20
  workerId: z.ZodNullable<z.ZodString>;
21
21
  }, z.z.core.$strip>;
22
22
  export type LocalConfig = z.infer<typeof LocalConfig>;
23
+ declare const GlobalConfig: z.ZodObject<{
24
+ version: z.ZodLiteral<"1">;
25
+ defaultSpaceIds: z.ZodObject<{
26
+ local: z.ZodOptional<z.ZodString>;
27
+ dev: z.ZodOptional<z.ZodString>;
28
+ stg: z.ZodOptional<z.ZodString>;
29
+ prod: z.ZodOptional<z.ZodString>;
30
+ }, z.z.core.$strip>;
31
+ }, z.z.core.$strip>;
32
+ export type GlobalConfig = z.infer<typeof GlobalConfig>;
23
33
  export declare class NoSuitableConfigFileError extends Error {
24
34
  constructor();
25
35
  }
26
36
  /**
27
- * Manages configuration for the command line interface.
37
+ * Manages configuration for the Workers CLI.
38
+ *
39
+ * ## Configuration Sources
40
+ *
41
+ * The CLI uses three configuration files:
42
+ *
43
+ * 1. **Global user config** (`config.json`) - Stores user preferences like
44
+ * `defaultSpaceId`. Located in `$NOTION_HOME/`, `$XDG_CONFIG_HOME/notion/`,
45
+ * `$HOME/.config/notion/`, or `$HOME/.notion/`. This file is always created
46
+ * when the CLI runs.
47
+ *
48
+ * 2. **Global space cache** (`spaces.json`) - Stores authenticated spaces and
49
+ * their metadata. Located in the same directory as `config.json`.
50
+ *
51
+ * 3. **Local project config** (`workers.json`) - Stores project-specific settings
52
+ * like which space and worker the project is associated with. Located in the
53
+ * current working directory.
54
+ *
55
+ * ## Configuration Precedence
56
+ *
57
+ * Values are resolved in this order (highest to lowest priority):
58
+ * - Environment variables (WORKERS_ENVIRONMENT, WORKERS_BASE_URL, WORKERS_TOKEN)
59
+ * - Command-line flags (--env, --base-url, --token, --space)
60
+ * - Local config file (workers.json)
61
+ * - Global user config (config.json) for `defaultSpaceId`
62
+ * - Defaults
63
+ *
64
+ * Space ID specifically follows: --space > WORKERS_SPACE_ID > workers.json > config.json
28
65
  *
29
- * Order of precedence: Command-line flags, environment variables, local config
30
- * files, where appropriate.
66
+ * ## Lazy workers.json Creation
67
+ *
68
+ * The `workers.json` file is only written when the CLI detects it's running in
69
+ * a worker project directory. A directory is considered a worker project if it
70
+ * contains a `package.json` with any `@project-ajax/*` package in dependencies
71
+ * or devDependencies.
72
+ *
73
+ * This design allows users to run commands like `workers auth login` from their
74
+ * home directory without creating an unwanted `workers.json` file there. The
75
+ * authentication still succeeds—the token is stored in the system keychain and
76
+ * the space is cached in `spaces.json`—but no local config file is created.
77
+ *
78
+ * When running in a worker project directory, operations like `login()`,
79
+ * `switchToSpace()`, and `setWorker()` will create or update `workers.json`
80
+ * to persist the project's space and worker associations.
81
+ *
82
+ * ## Token Storage
83
+ *
84
+ * Tokens are stored in the system keychain (via the `cross-keychain` package),
85
+ * keyed by space ID. This keeps sensitive credentials out of config files and
86
+ * allows multiple spaces to be authenticated simultaneously.
31
87
  */
32
88
  export declare class Config {
33
89
  #private;
34
90
  constructor(opts: {
91
+ globalConfigPath: string;
92
+ globalConfig: GlobalConfig;
35
93
  spaceCachePath: string;
36
94
  spaceCache: SpaceCache;
37
95
  localConfigPath: string;
38
96
  localConfig: LocalConfig;
97
+ isInWorkerDirectory: boolean;
39
98
  environment: Environment;
40
99
  baseURL: string;
41
100
  spaceId: string | null;
@@ -46,6 +105,24 @@ export declare class Config {
46
105
  get baseURL(): string;
47
106
  get spaceId(): string | null;
48
107
  get workerId(): string | null;
108
+ /**
109
+ * Get the default space ID from global user config.
110
+ *
111
+ * This is the space that will be used when no local config exists and
112
+ * no --space flag is provided.
113
+ *
114
+ * @returns The default space ID, or null if not set.
115
+ */
116
+ get defaultSpaceId(): string | null;
117
+ /**
118
+ * Set the default space ID in global user config.
119
+ *
120
+ * This updates the `config.json` file to persist the default space
121
+ * preference across CLI invocations.
122
+ *
123
+ * @param spaceId The space ID to set as default, or null to clear.
124
+ */
125
+ setDefaultSpaceId(spaceId: string | null): void;
49
126
  /**
50
127
  * Get the list of cached spaces.
51
128
  *
@@ -104,31 +181,66 @@ export declare class Config {
104
181
  * @throws Error if the space is not in the cache.
105
182
  */
106
183
  switchToSpace(spaceId: string): void;
184
+ /**
185
+ * Resolve the global config directory path.
186
+ *
187
+ * This is a shared helper used by both `resolveGlobalConfigPath` and
188
+ * `resolveSpaceCachePath` to determine the directory for global config files.
189
+ *
190
+ * Priority order:
191
+ * 1. `$NOTION_HOME` (if set and is a directory)
192
+ * 2. `$XDG_CONFIG_HOME/notion` (if XDG_CONFIG_HOME is set)
193
+ * 3. `$HOME/.config/notion` (if $HOME/.config exists)
194
+ * 4. `$HOME/.notion` (fallback)
195
+ *
196
+ * @param env The environment variables.
197
+ * @returns The directory path, or undefined if no suitable directory found.
198
+ */
199
+ static resolveGlobalDir(env: NodeJS.ProcessEnv): string | undefined;
200
+ /**
201
+ * Resolve the global config path.
202
+ *
203
+ * The global config file (`config.json`) stores user preferences like
204
+ * `defaultSpaceId`. It uses the same directory resolution as `spaces.json`.
205
+ *
206
+ * @param opts Options for resolving the global config path.
207
+ * @param opts.env The environment variables.
208
+ * @param opts.create Whether to create the file if it doesn't exist.
209
+ * @returns The path to the global config file.
210
+ */
211
+ static resolveGlobalConfigPath(opts: {
212
+ env: NodeJS.ProcessEnv;
213
+ create?: boolean;
214
+ }): string;
107
215
  /**
108
216
  * Resolve the space cache path.
109
217
  *
110
218
  * @param opts Options for resolving the space cache path.
111
219
  * @param opts.filePath The path to the space cache file.
112
220
  * @param opts.env The environment variables.
221
+ * @param opts.create Whether to create the file if it doesn't exist.
113
222
  * @returns The path to the space cache file.
114
223
  */
115
224
  static resolveSpaceCachePath(opts: {
116
225
  filePath?: string;
117
226
  env: NodeJS.ProcessEnv;
227
+ create?: boolean;
118
228
  }): string;
119
229
  /**
120
230
  * Resolve the local config path.
121
231
  *
122
232
  * @param opts Options for resolving the local config path.
123
233
  * @param opts.filePath The path to the local config file.
124
- * @returns
234
+ * @param opts.create Whether to create the file if it doesn't exist.
235
+ * @returns The path to the local config file.
125
236
  */
126
237
  static resolveLocalConfigPath(opts?: {
127
238
  filePath?: string;
239
+ create?: boolean;
128
240
  }): string;
129
241
  /**
130
- * Load config from space cache and local config files, then resolve them to
131
- * a final config for this execution of the command line interface.
242
+ * Load config from global config, space cache, and local config files, then
243
+ * resolve them to a final config for this execution of the command line interface.
132
244
  *
133
245
  * If the local config is v0, it will be upgraded to v1.
134
246
  *
@@ -137,16 +249,19 @@ export declare class Config {
137
249
  * - Environment: WORKERS_ENVIRONMENT or --env flag
138
250
  * - Token: WORKERS_TOKEN or --token flag
139
251
  * - Base URL: WORKERS_BASE_URL or --base-url flag
252
+ * - Space: --space flag > WORKERS_SPACE_ID > workers.json > config.json defaultSpaceId
140
253
  *
141
254
  * @param opts The options to load the config from.
255
+ * @param opts.globalConfigPath The path to the global config file.
142
256
  * @param opts.spaceCachePath The path to the space cache file.
143
257
  * @param opts.localPath The path to the local config file.
144
258
  * @param opts.env The process environment.
145
- * @param opts.processEnv The process environment.
146
259
  * @param opts.flags The global flags.
260
+ * @param opts.spaceOverride Optional space ID override (from --space flag).
147
261
  * @returns The resolved config.
148
262
  */
149
263
  static load(opts: {
264
+ globalConfigPath: string;
150
265
  spaceCachePath: string;
151
266
  localPath: string;
152
267
  env: NodeJS.ProcessEnv;
@@ -160,6 +275,10 @@ export declare class Config {
160
275
  * The empty space cache.
161
276
  */
162
277
  static get emptySpaceCache(): SpaceCache;
278
+ /**
279
+ * The empty global config.
280
+ */
281
+ static get emptyGlobalConfig(): GlobalConfig;
163
282
  }
164
283
  export {};
165
284
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/config.js CHANGED
@@ -30,6 +30,15 @@ const LocalConfigV0 = z.object({
30
30
  token: z.string().nullable(),
31
31
  workerId: z.string().nullable()
32
32
  }).partial();
33
+ const GlobalConfig = z.object({
34
+ version: z.literal("1"),
35
+ defaultSpaceIds: z.object({
36
+ local: z.string(),
37
+ dev: z.string(),
38
+ stg: z.string(),
39
+ prod: z.string()
40
+ }).partial()
41
+ });
33
42
  class NoSuitableConfigFileError extends Error {
34
43
  constructor() {
35
44
  super("No suitable config file found");
@@ -37,20 +46,26 @@ class NoSuitableConfigFileError extends Error {
37
46
  }
38
47
  }
39
48
  class Config {
49
+ #globalConfigPath;
50
+ #globalConfig;
40
51
  #spaceCachePath;
41
52
  #spaceCache;
42
53
  #localConfigPath;
43
54
  #localConfig;
55
+ #isInWorkerDirectory;
44
56
  #baseURL;
45
57
  #environment;
46
58
  #spaceId;
47
59
  #workerId;
48
60
  #token = null;
49
61
  constructor(opts) {
62
+ this.#globalConfigPath = opts.globalConfigPath;
63
+ this.#globalConfig = opts.globalConfig;
50
64
  this.#spaceCachePath = opts.spaceCachePath;
51
65
  this.#spaceCache = opts.spaceCache;
52
66
  this.#localConfigPath = opts.localConfigPath;
53
67
  this.#localConfig = opts.localConfig;
68
+ this.#isInWorkerDirectory = opts.isInWorkerDirectory;
54
69
  this.#baseURL = opts.baseURL;
55
70
  this.#spaceId = opts.spaceId;
56
71
  this.#workerId = opts.workerId;
@@ -69,6 +84,33 @@ class Config {
69
84
  get workerId() {
70
85
  return this.#workerId;
71
86
  }
87
+ /**
88
+ * Get the default space ID from global user config.
89
+ *
90
+ * This is the space that will be used when no local config exists and
91
+ * no --space flag is provided.
92
+ *
93
+ * @returns The default space ID, or null if not set.
94
+ */
95
+ get defaultSpaceId() {
96
+ return this.#globalConfig.defaultSpaceIds[this.#environment] ?? null;
97
+ }
98
+ /**
99
+ * Set the default space ID in global user config.
100
+ *
101
+ * This updates the `config.json` file to persist the default space
102
+ * preference across CLI invocations.
103
+ *
104
+ * @param spaceId The space ID to set as default, or null to clear.
105
+ */
106
+ setDefaultSpaceId(spaceId) {
107
+ if (spaceId) {
108
+ this.#globalConfig.defaultSpaceIds[this.#environment] = spaceId;
109
+ } else {
110
+ delete this.#globalConfig.defaultSpaceIds[this.#environment];
111
+ }
112
+ this.#writeGlobalConfig();
113
+ }
72
114
  /**
73
115
  * Get the list of cached spaces.
74
116
  *
@@ -163,31 +205,123 @@ class Config {
163
205
  this.#localConfig.spaceId = spaceId;
164
206
  this.#writeLocalConfig();
165
207
  }
208
+ /**
209
+ * Write the space cache to disk.
210
+ *
211
+ * The space cache is always written, regardless of the current directory,
212
+ * since it's a global user-level file.
213
+ */
166
214
  #writeSpaceCache() {
167
215
  fs.writeFileSync(
168
216
  this.#spaceCachePath,
169
217
  JSON.stringify(this.#spaceCache, null, 2)
170
218
  );
171
219
  }
220
+ /**
221
+ * Write the local config to disk.
222
+ *
223
+ * This is a no-op if the current directory is not a worker project (i.e.,
224
+ * does not contain a package.json with @project-ajax/* dependencies). This
225
+ * prevents creating workers.json files in unintended locations like the
226
+ * user's home directory when running `workers auth login`.
227
+ *
228
+ * @see isWorkerDirectory for the detection logic
229
+ */
172
230
  #writeLocalConfig() {
231
+ if (!this.#isInWorkerDirectory) {
232
+ return;
233
+ }
173
234
  fs.writeFileSync(
174
235
  this.#localConfigPath,
175
236
  JSON.stringify(this.#localConfig, null, 2)
176
237
  );
177
238
  }
239
+ /**
240
+ * Write the global config to disk.
241
+ *
242
+ * The global config is always written since it's a user-level file.
243
+ */
244
+ #writeGlobalConfig() {
245
+ fs.writeFileSync(
246
+ this.#globalConfigPath,
247
+ JSON.stringify(this.#globalConfig, null, 2)
248
+ );
249
+ }
250
+ /**
251
+ * Resolve the global config directory path.
252
+ *
253
+ * This is a shared helper used by both `resolveGlobalConfigPath` and
254
+ * `resolveSpaceCachePath` to determine the directory for global config files.
255
+ *
256
+ * Priority order:
257
+ * 1. `$NOTION_HOME` (if set and is a directory)
258
+ * 2. `$XDG_CONFIG_HOME/notion` (if XDG_CONFIG_HOME is set)
259
+ * 3. `$HOME/.config/notion` (if $HOME/.config exists)
260
+ * 4. `$HOME/.notion` (fallback)
261
+ *
262
+ * @param env The environment variables.
263
+ * @returns The directory path, or undefined if no suitable directory found.
264
+ */
265
+ static resolveGlobalDir(env) {
266
+ const home = env.HOME;
267
+ const xdgConfigHome = env.XDG_CONFIG_HOME;
268
+ const notionHome = env.NOTION_HOME;
269
+ if (notionHome && isDir(notionHome)) {
270
+ return notionHome;
271
+ } else if (xdgConfigHome && isDir(xdgConfigHome)) {
272
+ return path.join(xdgConfigHome, "notion");
273
+ } else if (home && isDir(path.join(home, ".config"))) {
274
+ return path.join(home, ".config", "notion");
275
+ } else if (home && isDir(home)) {
276
+ return path.join(home, ".notion");
277
+ }
278
+ return void 0;
279
+ }
280
+ /**
281
+ * Resolve the global config path.
282
+ *
283
+ * The global config file (`config.json`) stores user preferences like
284
+ * `defaultSpaceId`. It uses the same directory resolution as `spaces.json`.
285
+ *
286
+ * @param opts Options for resolving the global config path.
287
+ * @param opts.env The environment variables.
288
+ * @param opts.create Whether to create the file if it doesn't exist.
289
+ * @returns The path to the global config file.
290
+ */
291
+ static resolveGlobalConfigPath(opts) {
292
+ const configDir = Config.resolveGlobalDir(opts.env);
293
+ if (!configDir) {
294
+ throw new NoSuitableConfigFileError();
295
+ }
296
+ const configFilePath = path.join(configDir, "config.json");
297
+ debug("%o", { configDir, configFilePath });
298
+ if (opts.create && !fs.existsSync(configDir)) {
299
+ fs.mkdirSync(configDir, { recursive: true });
300
+ }
301
+ if (fs.existsSync(configDir) && !isDir(configDir)) {
302
+ throw new NoSuitableConfigFileError();
303
+ }
304
+ if (opts.create && !fs.existsSync(configFilePath)) {
305
+ fs.writeFileSync(
306
+ configFilePath,
307
+ JSON.stringify(Config.emptyGlobalConfig, null, 2)
308
+ );
309
+ }
310
+ if (fs.existsSync(configFilePath) && !isFile(configFilePath)) {
311
+ throw new NoSuitableConfigFileError();
312
+ }
313
+ return configFilePath;
314
+ }
178
315
  /**
179
316
  * Resolve the space cache path.
180
317
  *
181
318
  * @param opts Options for resolving the space cache path.
182
319
  * @param opts.filePath The path to the space cache file.
183
320
  * @param opts.env The environment variables.
321
+ * @param opts.create Whether to create the file if it doesn't exist.
184
322
  * @returns The path to the space cache file.
185
323
  */
186
324
  static resolveSpaceCachePath(opts) {
187
- const env = opts.env;
188
- const home = env.HOME;
189
- const xdgConfigHome = env.XDG_CONFIG_HOME;
190
- const notionHome = env.NOTION_HOME;
191
325
  let configFileDir;
192
326
  let configFilePath;
193
327
  if (opts.filePath) {
@@ -195,37 +329,29 @@ class Config {
195
329
  configFileDir = path.dirname(absPath);
196
330
  configFilePath = absPath;
197
331
  } else {
198
- if (notionHome && isDir(notionHome)) {
199
- configFileDir = notionHome;
200
- configFilePath = path.join(configFileDir, "spaces.json");
201
- } else if (xdgConfigHome && isDir(xdgConfigHome)) {
202
- configFileDir = path.join(xdgConfigHome, "notion");
203
- configFilePath = path.join(configFileDir, "spaces.json");
204
- } else if (home && isDir(path.join(home, ".config"))) {
205
- configFileDir = path.join(home, ".config", "notion");
206
- configFilePath = path.join(configFileDir, "spaces.json");
207
- } else if (home && isDir(home)) {
208
- configFileDir = path.join(home, ".notion");
209
- configFilePath = path.join(configFileDir, "spaces.json");
332
+ const configDir = Config.resolveGlobalDir(opts.env);
333
+ if (configDir) {
334
+ configFileDir = configDir;
335
+ configFilePath = path.join(configDir, "spaces.json");
210
336
  }
211
337
  }
212
338
  debug("%o", { configFileDir, configFilePath });
213
339
  if (!(configFileDir && configFilePath)) {
214
340
  throw new NoSuitableConfigFileError();
215
341
  }
216
- if (!fs.existsSync(configFileDir)) {
342
+ if (opts.create && !fs.existsSync(configFileDir)) {
217
343
  fs.mkdirSync(configFileDir, { recursive: true });
218
344
  }
219
- if (!isDir(configFileDir)) {
345
+ if (fs.existsSync(configFileDir) && !isDir(configFileDir)) {
220
346
  throw new NoSuitableConfigFileError();
221
347
  }
222
- if (!fs.existsSync(configFilePath)) {
348
+ if (opts.create && !fs.existsSync(configFilePath)) {
223
349
  fs.writeFileSync(
224
350
  configFilePath,
225
351
  JSON.stringify(Config.emptySpaceCache, null, 2)
226
352
  );
227
353
  }
228
- if (!isFile(configFilePath)) {
354
+ if (fs.existsSync(configFilePath) && !isFile(configFilePath)) {
229
355
  throw new NoSuitableConfigFileError();
230
356
  }
231
357
  return configFilePath;
@@ -235,27 +361,28 @@ class Config {
235
361
  *
236
362
  * @param opts Options for resolving the local config path.
237
363
  * @param opts.filePath The path to the local config file.
238
- * @returns
364
+ * @param opts.create Whether to create the file if it doesn't exist.
365
+ * @returns The path to the local config file.
239
366
  */
240
367
  static resolveLocalConfigPath(opts = {}) {
241
368
  const absPath = path.resolve(
242
369
  process.cwd(),
243
370
  opts.filePath ?? "./workers.json"
244
371
  );
245
- if (!fs.existsSync(absPath)) {
372
+ if (opts.create && !fs.existsSync(absPath)) {
246
373
  fs.writeFileSync(
247
374
  absPath,
248
375
  JSON.stringify(Config.emptyLocalConfig, null, 2)
249
376
  );
250
377
  }
251
- if (!isFile(absPath)) {
378
+ if (fs.existsSync(absPath) && !isFile(absPath)) {
252
379
  throw new NoSuitableConfigFileError();
253
380
  }
254
381
  return absPath;
255
382
  }
256
383
  /**
257
- * Load config from space cache and local config files, then resolve them to
258
- * a final config for this execution of the command line interface.
384
+ * Load config from global config, space cache, and local config files, then
385
+ * resolve them to a final config for this execution of the command line interface.
259
386
  *
260
387
  * If the local config is v0, it will be upgraded to v1.
261
388
  *
@@ -264,43 +391,60 @@ class Config {
264
391
  * - Environment: WORKERS_ENVIRONMENT or --env flag
265
392
  * - Token: WORKERS_TOKEN or --token flag
266
393
  * - Base URL: WORKERS_BASE_URL or --base-url flag
394
+ * - Space: --space flag > WORKERS_SPACE_ID > workers.json > config.json defaultSpaceId
267
395
  *
268
396
  * @param opts The options to load the config from.
397
+ * @param opts.globalConfigPath The path to the global config file.
269
398
  * @param opts.spaceCachePath The path to the space cache file.
270
399
  * @param opts.localPath The path to the local config file.
271
400
  * @param opts.env The process environment.
272
- * @param opts.processEnv The process environment.
273
401
  * @param opts.flags The global flags.
402
+ * @param opts.spaceOverride Optional space ID override (from --space flag).
274
403
  * @returns The resolved config.
275
404
  */
276
405
  static load(opts) {
277
- const { spaceCachePath, localPath, env, flags } = opts;
406
+ const { globalConfigPath, spaceCachePath, localPath, env, flags } = opts;
407
+ let globalConfig;
408
+ if (fs.existsSync(globalConfigPath)) {
409
+ const globalJson = fs.readFileSync(globalConfigPath, "utf-8");
410
+ const globalRaw = JSON.parse(globalJson);
411
+ globalConfig = GlobalConfig.parse(globalRaw);
412
+ } else {
413
+ globalConfig = Config.emptyGlobalConfig;
414
+ }
278
415
  const cacheJson = fs.readFileSync(spaceCachePath, "utf-8");
279
416
  const cacheRaw = JSON.parse(cacheJson);
280
417
  const spaceCache = SpaceCache.parse(cacheRaw);
281
- const localJson = fs.readFileSync(localPath, "utf-8");
282
- const localRaw = JSON.parse(localJson);
283
418
  let localConfig;
284
- const v1Result = LocalConfig.safeParse(localRaw);
285
- if (v1Result.success) {
286
- localConfig = v1Result.data;
419
+ if (fs.existsSync(localPath)) {
420
+ const localJson = fs.readFileSync(localPath, "utf-8");
421
+ const localRaw = JSON.parse(localJson);
422
+ const v1Result = LocalConfig.safeParse(localRaw);
423
+ if (v1Result.success) {
424
+ localConfig = v1Result.data;
425
+ } else {
426
+ const v0Config = LocalConfigV0.parse(localRaw);
427
+ localConfig = upgradeV0ToV1(v0Config, localPath);
428
+ }
287
429
  } else {
288
- const v0Config = LocalConfigV0.parse(localRaw);
289
- localConfig = upgradeV0ToV1(v0Config, localPath);
430
+ localConfig = Config.emptyLocalConfig;
290
431
  }
291
432
  const environment = Environment.parse(
292
433
  env.WORKERS_ENVIRONMENT ?? flags.env ?? localConfig.environment
293
434
  );
294
435
  const baseURLOverride = env.WORKERS_BASE_URL ?? flags["base-url"] ?? localConfig.baseURL;
295
436
  const baseURL = baseURLOverride ?? baseUrlForEnvironment(environment);
296
- const spaceId = localConfig.spaceId;
437
+ const spaceId = flags.space ?? env.WORKERS_SPACE_ID ?? localConfig.spaceId ?? globalConfig.defaultSpaceIds[environment] ?? null;
297
438
  const workerId = localConfig.workerId;
298
439
  const token = env.WORKERS_TOKEN ?? flags.token ?? null;
299
440
  return new Config({
441
+ globalConfigPath,
442
+ globalConfig,
300
443
  spaceCachePath,
301
444
  spaceCache,
302
445
  localConfigPath: localPath,
303
446
  localConfig,
447
+ isInWorkerDirectory: isWorkerDirectory(path.dirname(localPath)),
304
448
  environment,
305
449
  baseURL,
306
450
  spaceId,
@@ -320,6 +464,15 @@ class Config {
320
464
  static get emptySpaceCache() {
321
465
  return { version: "1", spaces: {} };
322
466
  }
467
+ /**
468
+ * The empty global config.
469
+ */
470
+ static get emptyGlobalConfig() {
471
+ return {
472
+ version: "1",
473
+ defaultSpaceIds: {}
474
+ };
475
+ }
323
476
  }
324
477
  function isDir(path2) {
325
478
  try {
@@ -328,9 +481,25 @@ function isDir(path2) {
328
481
  return false;
329
482
  }
330
483
  }
331
- function isFile(path2) {
484
+ function isFile(filePath) {
332
485
  try {
333
- return fs.statSync(path2).isFile();
486
+ return fs.statSync(filePath).isFile();
487
+ } catch {
488
+ return false;
489
+ }
490
+ }
491
+ function isWorkerDirectory(dir) {
492
+ const packageJsonPath = path.join(dir, "package.json");
493
+ if (!fs.existsSync(packageJsonPath)) {
494
+ return false;
495
+ }
496
+ try {
497
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
498
+ const allDeps = {
499
+ ...pkg.dependencies,
500
+ ...pkg.devDependencies
501
+ };
502
+ return Object.keys(allDeps).some((dep) => dep.startsWith("@project-ajax/"));
334
503
  } catch {
335
504
  return false;
336
505
  }