@project-ajax/sdk 0.0.53 → 0.0.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/auth.impl.d.ts.map +1 -1
- package/dist/cli/commands/auth.impl.js +8 -4
- package/dist/cli/commands/deploy.impl.js +1 -1
- package/dist/cli/commands/utils/testing.d.ts +6 -9
- package/dist/cli/commands/utils/testing.d.ts.map +1 -1
- package/dist/cli/commands/utils/testing.js +21 -14
- package/dist/cli/config.d.ts +13 -6
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +60 -34
- package/package.json +1 -1
- package/src/cli/commands/auth.impl.test.ts +66 -46
- package/src/cli/commands/auth.impl.ts +8 -4
- package/src/cli/commands/bundle.impl.test.ts +25 -19
- package/src/cli/commands/deploy.impl.test.ts +69 -49
- package/src/cli/commands/deploy.impl.ts +1 -1
- package/src/cli/commands/utils/testing.ts +28 -23
- package/src/cli/config.test.ts +59 -65
- package/src/cli/config.ts +69 -41
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.impl.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/auth.impl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,eAAO,MAAM,KAAK,+
|
|
1
|
+
{"version":3,"file":"auth.impl.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/auth.impl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,eAAO,MAAM,KAAK,+GAiChB,CAAC;AAEH,eAAO,MAAM,IAAI,mFAEf,CAAC;AAEH,eAAO,MAAM,MAAM,mFAIjB,CAAC"}
|
|
@@ -19,16 +19,20 @@ ${url}
|
|
|
19
19
|
}
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
|
-
await this.config.
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
await this.config.update({
|
|
23
|
+
environment,
|
|
24
|
+
token,
|
|
25
|
+
workerId: null
|
|
26
|
+
});
|
|
25
27
|
this.writer.writeErr("Successfully logged in!");
|
|
26
28
|
});
|
|
27
29
|
const show = buildHandler(function() {
|
|
28
30
|
this.writer.writeOut(`${this.config.token ?? ""}`);
|
|
29
31
|
});
|
|
30
32
|
const logout = buildHandler(async function() {
|
|
31
|
-
await this.config.
|
|
33
|
+
await this.config.update({
|
|
34
|
+
token: null
|
|
35
|
+
});
|
|
32
36
|
});
|
|
33
37
|
export {
|
|
34
38
|
login,
|
|
@@ -53,7 +53,7 @@ const deploy = buildAuthedHandler(async function(flags) {
|
|
|
53
53
|
}
|
|
54
54
|
if (Result.isSuccess(result)) {
|
|
55
55
|
const { workerId: workerId2 } = Result.unwrap(result);
|
|
56
|
-
await this.config.
|
|
56
|
+
await this.config.update({ workerId: workerId2 });
|
|
57
57
|
this.writer.writeErr("\u2713 Successfully deployed worker");
|
|
58
58
|
} else {
|
|
59
59
|
this.writer.writeErr("\u2717 Failed to deploy worker");
|
|
@@ -1,19 +1,16 @@
|
|
|
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 { Config, type
|
|
4
|
+
import { Config, type ConfigMap } from "../../config.js";
|
|
5
5
|
import type { GlobalFlags } from "../../flags.js";
|
|
6
6
|
import { Writer } from "../../writer.js";
|
|
7
|
-
export type ConfigFileContents = {
|
|
8
|
-
token: string | null;
|
|
9
|
-
workerId: string | null;
|
|
10
|
-
environment: Environment;
|
|
11
|
-
baseUrl: string;
|
|
12
|
-
};
|
|
13
7
|
export declare const tmpDirectories: string[];
|
|
14
8
|
export declare const baseFlags: GlobalFlags;
|
|
15
|
-
export declare function
|
|
16
|
-
|
|
9
|
+
export declare function createAndLoadConfig({ configFile, env, flags, }: {
|
|
10
|
+
configFile: Partial<ConfigMap>;
|
|
11
|
+
env?: Partial<NodeJS.ProcessEnv>;
|
|
12
|
+
flags?: Partial<GlobalFlags>;
|
|
13
|
+
}): Promise<[config: Config, path: string]>;
|
|
17
14
|
export declare function createBaseContext(): {
|
|
18
15
|
writer: Writer;
|
|
19
16
|
process: NodeJS.Process;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/utils/testing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAE9B,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAE9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,MAAM,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/utils/testing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAE9B,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAE9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,MAAM,EAAE,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC,eAAO,MAAM,cAAc,EAAE,MAAM,EAAO,CAAC;AAE3C,eAAO,MAAM,SAAS,EAAE,WAEvB,CAAC;AAEF,wBAAsB,mBAAmB,CAAC,EACzC,UAAU,EACV,GAAG,EACH,KAAK,GACL,EAAE;IACF,UAAU,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IAC/B,GAAG,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;CAC7B,GAAG,OAAO,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAa1C;AAED,wBAAgB,iBAAiB;;;;;;EAQhC;AAED,wBAAsB,qBAAqB,kBAO1C"}
|
|
@@ -9,20 +9,21 @@ const tmpDirectories = [];
|
|
|
9
9
|
const baseFlags = {
|
|
10
10
|
debug: false
|
|
11
11
|
};
|
|
12
|
-
async function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
async function createTestConfig(configFileContents) {
|
|
20
|
-
const configFilePath = await createConfigFile(configFileContents);
|
|
21
|
-
return Config.load({
|
|
12
|
+
async function createAndLoadConfig({
|
|
13
|
+
configFile,
|
|
14
|
+
env,
|
|
15
|
+
flags
|
|
16
|
+
}) {
|
|
17
|
+
const configFilePath = await createConfigFile(configFile);
|
|
18
|
+
const map = await Config.load({
|
|
22
19
|
configFilePath,
|
|
23
|
-
processEnv: {},
|
|
24
|
-
flags: {
|
|
20
|
+
processEnv: env ?? {},
|
|
21
|
+
flags: {
|
|
22
|
+
...baseFlags,
|
|
23
|
+
...flags ?? {}
|
|
24
|
+
}
|
|
25
25
|
});
|
|
26
|
+
return [map, configFilePath];
|
|
26
27
|
}
|
|
27
28
|
function createBaseContext() {
|
|
28
29
|
return {
|
|
@@ -41,11 +42,17 @@ async function cleanupTmpDirectories() {
|
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
}
|
|
45
|
+
async function createConfigFile(contents) {
|
|
46
|
+
const dir = await mkdtemp(path.join(tmpdir(), "cmd-test-"));
|
|
47
|
+
tmpDirectories.push(dir);
|
|
48
|
+
const configFilePath = path.join(dir, "config.json");
|
|
49
|
+
await writeFile(configFilePath, JSON.stringify(contents, null, 2), "utf-8");
|
|
50
|
+
return configFilePath;
|
|
51
|
+
}
|
|
44
52
|
export {
|
|
45
53
|
baseFlags,
|
|
46
54
|
cleanupTmpDirectories,
|
|
55
|
+
createAndLoadConfig,
|
|
47
56
|
createBaseContext,
|
|
48
|
-
createConfigFile,
|
|
49
|
-
createTestConfig,
|
|
50
57
|
tmpDirectories
|
|
51
58
|
};
|
package/dist/cli/config.d.ts
CHANGED
|
@@ -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
|
package/dist/cli/config.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/cli/config.js
CHANGED
|
@@ -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,26 +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
|
-
await this.#writeConfigFile();
|
|
40
|
-
}
|
|
41
|
-
async setToken(token) {
|
|
42
|
-
this.#configMap.token = token;
|
|
43
|
-
await this.#writeConfigFile();
|
|
44
|
-
}
|
|
45
|
-
async setWorkerId(workerId) {
|
|
46
|
-
this.#configMap.workerId = workerId;
|
|
47
|
-
await this.#writeConfigFile();
|
|
48
|
-
}
|
|
49
|
-
async #writeConfigFile() {
|
|
50
|
-
await fs.promises.writeFile(
|
|
51
|
-
this.#configFilePath,
|
|
52
|
-
JSON.stringify(this.#configMap, null, 2),
|
|
53
|
-
"utf-8"
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
36
|
get tokenInfo() {
|
|
57
37
|
const token = this.token;
|
|
58
38
|
if (!token) {
|
|
@@ -61,39 +41,85 @@ class Config {
|
|
|
61
41
|
const { spaceId, cellId } = extractPayloadFromToken(token);
|
|
62
42
|
return { token, spaceId, cellId };
|
|
63
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
|
+
}
|
|
64
66
|
static async load(opts) {
|
|
65
67
|
const absConfigFilePath = path.resolve(process.cwd(), opts.configFilePath);
|
|
66
|
-
const
|
|
68
|
+
const partialConfig = await Config.#readConfigFile(absConfigFilePath);
|
|
67
69
|
if (opts.processEnv.WORKERS_TOKEN) {
|
|
68
|
-
|
|
70
|
+
partialConfig.token = opts.processEnv.WORKERS_TOKEN;
|
|
69
71
|
}
|
|
70
72
|
if (opts.processEnv.WORKERS_ENVIRONMENT) {
|
|
71
|
-
|
|
73
|
+
partialConfig.environment = parseEnvironment(
|
|
72
74
|
opts.processEnv.WORKERS_ENVIRONMENT
|
|
73
75
|
);
|
|
74
76
|
}
|
|
75
77
|
if (opts.processEnv.WORKERS_WORKER_ID) {
|
|
76
|
-
|
|
78
|
+
partialConfig.workerId = opts.processEnv.WORKERS_WORKER_ID;
|
|
77
79
|
}
|
|
78
80
|
if (opts.processEnv.WORKERS_BASE_URL) {
|
|
79
|
-
|
|
81
|
+
partialConfig.baseUrl = opts.processEnv.WORKERS_BASE_URL;
|
|
80
82
|
}
|
|
81
83
|
if (opts.flags.token) {
|
|
82
|
-
|
|
84
|
+
partialConfig.token = opts.flags.token;
|
|
83
85
|
}
|
|
84
86
|
if (opts.flags.env) {
|
|
85
|
-
|
|
87
|
+
partialConfig.environment = parseEnvironment(opts.flags.env);
|
|
86
88
|
}
|
|
87
89
|
if (opts.flags["base-url"]) {
|
|
88
|
-
|
|
90
|
+
partialConfig.baseUrl = opts.flags["base-url"];
|
|
89
91
|
}
|
|
90
92
|
if (opts.flags["worker-id"]) {
|
|
91
|
-
|
|
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");
|
|
92
109
|
}
|
|
93
|
-
|
|
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
|
+
};
|
|
94
120
|
return new Config({
|
|
95
121
|
configFilePath: absConfigFilePath,
|
|
96
|
-
|
|
122
|
+
configMap
|
|
97
123
|
});
|
|
98
124
|
}
|
|
99
125
|
static async #readConfigFile(configFilePath) {
|
|
@@ -110,7 +136,7 @@ class Config {
|
|
|
110
136
|
token: null,
|
|
111
137
|
workerId: null,
|
|
112
138
|
environment: "prod",
|
|
113
|
-
baseUrl:
|
|
139
|
+
baseUrl: baseUrlForEnvironment("prod")
|
|
114
140
|
};
|
|
115
141
|
} else {
|
|
116
142
|
throw error;
|
|
@@ -150,7 +176,7 @@ function extractPayloadFromToken(token) {
|
|
|
150
176
|
throw new Error("Failed to parse token payload.");
|
|
151
177
|
}
|
|
152
178
|
}
|
|
153
|
-
function
|
|
179
|
+
function baseUrlForEnvironment(environment) {
|
|
154
180
|
switch (environment) {
|
|
155
181
|
case "local":
|
|
156
182
|
return "http://localhost:3000";
|
package/package.json
CHANGED
|
@@ -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
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
|
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(
|
|
110
|
-
|
|
111
|
-
|
|
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
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
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(
|
|
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
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
|
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(
|
|
222
|
+
expect(updateSpy).toHaveBeenCalledWith({
|
|
223
|
+
token: null,
|
|
224
|
+
});
|
|
205
225
|
});
|
|
206
226
|
});
|
|
@@ -28,9 +28,11 @@ export const login = buildHandler(async function (
|
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
await this.config.
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
await this.config.update({
|
|
32
|
+
environment,
|
|
33
|
+
token,
|
|
34
|
+
workerId: null,
|
|
35
|
+
});
|
|
34
36
|
|
|
35
37
|
this.writer.writeErr("Successfully logged in!");
|
|
36
38
|
});
|
|
@@ -40,5 +42,7 @@ export const show = buildHandler(function (this: HandlerContext) {
|
|
|
40
42
|
});
|
|
41
43
|
|
|
42
44
|
export const logout = buildHandler(async function (this: HandlerContext) {
|
|
43
|
-
await this.config.
|
|
45
|
+
await this.config.update({
|
|
46
|
+
token: null,
|
|
47
|
+
});
|
|
44
48
|
});
|
|
@@ -14,8 +14,8 @@ import { downloadBundle } from "./bundle.impl.js";
|
|
|
14
14
|
import {
|
|
15
15
|
baseFlags,
|
|
16
16
|
cleanupTmpDirectories,
|
|
17
|
+
createAndLoadConfig,
|
|
17
18
|
createBaseContext,
|
|
18
|
-
createTestConfig,
|
|
19
19
|
} from "./utils/testing.js";
|
|
20
20
|
|
|
21
21
|
afterEach(cleanupTmpDirectories);
|
|
@@ -34,12 +34,14 @@ describe("downloadBundle", () => {
|
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
it("downloads and pipes bundle to stdout when workerId is set", async () => {
|
|
37
|
-
const mockConfig = await
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
const [mockConfig] = await createAndLoadConfig({
|
|
38
|
+
configFile: {
|
|
39
|
+
token:
|
|
40
|
+
"1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig",
|
|
41
|
+
workerId: "worker-123",
|
|
42
|
+
environment: "local",
|
|
43
|
+
baseUrl: "http://localhost:3000",
|
|
44
|
+
},
|
|
43
45
|
});
|
|
44
46
|
|
|
45
47
|
vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
|
|
@@ -76,12 +78,14 @@ describe("downloadBundle", () => {
|
|
|
76
78
|
});
|
|
77
79
|
|
|
78
80
|
it("throws error when workerId is not set", async () => {
|
|
79
|
-
const mockConfig = await
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
const [mockConfig] = await createAndLoadConfig({
|
|
82
|
+
configFile: {
|
|
83
|
+
token:
|
|
84
|
+
"1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig",
|
|
85
|
+
workerId: null,
|
|
86
|
+
environment: "local",
|
|
87
|
+
baseUrl: "http://localhost:3000",
|
|
88
|
+
},
|
|
85
89
|
});
|
|
86
90
|
|
|
87
91
|
vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
|
|
@@ -101,12 +105,14 @@ describe("downloadBundle", () => {
|
|
|
101
105
|
});
|
|
102
106
|
|
|
103
107
|
it("handles download failure gracefully", async () => {
|
|
104
|
-
const mockConfig = await
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
const [mockConfig] = await createAndLoadConfig({
|
|
109
|
+
configFile: {
|
|
110
|
+
token:
|
|
111
|
+
"1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig",
|
|
112
|
+
workerId: "worker-123",
|
|
113
|
+
environment: "local",
|
|
114
|
+
baseUrl: "http://localhost:3000",
|
|
115
|
+
},
|
|
110
116
|
});
|
|
111
117
|
|
|
112
118
|
vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
|
|
@@ -12,8 +12,8 @@ import { Config } from "../config.js";
|
|
|
12
12
|
import {
|
|
13
13
|
baseFlags,
|
|
14
14
|
cleanupTmpDirectories,
|
|
15
|
+
createAndLoadConfig,
|
|
15
16
|
createBaseContext,
|
|
16
|
-
createTestConfig,
|
|
17
17
|
} from "./utils/testing.js";
|
|
18
18
|
|
|
19
19
|
// Mock external dependencies
|
|
@@ -41,12 +41,14 @@ describe("deploy", () => {
|
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
it("deploys existing worker when workerId is set", async () => {
|
|
44
|
-
const mockConfig = await
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
const [mockConfig] = await createAndLoadConfig({
|
|
45
|
+
configFile: {
|
|
46
|
+
token:
|
|
47
|
+
"1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig",
|
|
48
|
+
workerId: "worker-123",
|
|
49
|
+
environment: "local",
|
|
50
|
+
baseUrl: "http://localhost:3000",
|
|
51
|
+
},
|
|
50
52
|
});
|
|
51
53
|
|
|
52
54
|
vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
|
|
@@ -54,7 +56,7 @@ describe("deploy", () => {
|
|
|
54
56
|
Result.success({ workerId: "worker-123" }),
|
|
55
57
|
);
|
|
56
58
|
|
|
57
|
-
const
|
|
59
|
+
const updateSpy = vi.spyOn(mockConfig, "update");
|
|
58
60
|
const context = createBaseContext();
|
|
59
61
|
|
|
60
62
|
await deploy.call(context, baseFlags);
|
|
@@ -67,7 +69,9 @@ describe("deploy", () => {
|
|
|
67
69
|
environment: "local",
|
|
68
70
|
});
|
|
69
71
|
|
|
70
|
-
expect(
|
|
72
|
+
expect(updateSpy).toHaveBeenCalledWith({
|
|
73
|
+
workerId: "worker-123",
|
|
74
|
+
});
|
|
71
75
|
|
|
72
76
|
const allCalls = stderrSpy.mock.calls.map((call) => call[0]).join("");
|
|
73
77
|
expect(allCalls).toContain("Deploying worker...");
|
|
@@ -75,12 +79,14 @@ describe("deploy", () => {
|
|
|
75
79
|
});
|
|
76
80
|
|
|
77
81
|
it("deploys new worker with name flag", async () => {
|
|
78
|
-
const mockConfig = await
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
const [mockConfig] = await createAndLoadConfig({
|
|
83
|
+
configFile: {
|
|
84
|
+
token:
|
|
85
|
+
"1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig",
|
|
86
|
+
workerId: null,
|
|
87
|
+
environment: "local",
|
|
88
|
+
baseUrl: "http://localhost:3000",
|
|
89
|
+
},
|
|
84
90
|
});
|
|
85
91
|
|
|
86
92
|
vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
|
|
@@ -88,7 +94,7 @@ describe("deploy", () => {
|
|
|
88
94
|
Result.success({ workerId: "worker-456" }),
|
|
89
95
|
);
|
|
90
96
|
|
|
91
|
-
const
|
|
97
|
+
const updateSpy = vi.spyOn(mockConfig, "update");
|
|
92
98
|
const context = createBaseContext();
|
|
93
99
|
|
|
94
100
|
await deploy.call(context, { ...baseFlags, name: "my-worker" });
|
|
@@ -101,7 +107,9 @@ describe("deploy", () => {
|
|
|
101
107
|
environment: "local",
|
|
102
108
|
});
|
|
103
109
|
|
|
104
|
-
expect(
|
|
110
|
+
expect(updateSpy).toHaveBeenCalledWith({
|
|
111
|
+
workerId: "worker-456",
|
|
112
|
+
});
|
|
105
113
|
expect(prompts).not.toHaveBeenCalled();
|
|
106
114
|
|
|
107
115
|
const allCalls = stderrSpy.mock.calls.map((call) => call[0]).join("");
|
|
@@ -109,12 +117,14 @@ describe("deploy", () => {
|
|
|
109
117
|
});
|
|
110
118
|
|
|
111
119
|
it("prompts for name when deploying new worker without name flag", async () => {
|
|
112
|
-
const mockConfig = await
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
120
|
+
const [mockConfig] = await createAndLoadConfig({
|
|
121
|
+
configFile: {
|
|
122
|
+
token:
|
|
123
|
+
"1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig",
|
|
124
|
+
workerId: null,
|
|
125
|
+
environment: "local",
|
|
126
|
+
baseUrl: "http://localhost:3000",
|
|
127
|
+
},
|
|
118
128
|
});
|
|
119
129
|
|
|
120
130
|
vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
|
|
@@ -123,7 +133,7 @@ describe("deploy", () => {
|
|
|
123
133
|
Result.success({ workerId: "worker-789" }),
|
|
124
134
|
);
|
|
125
135
|
|
|
126
|
-
const
|
|
136
|
+
const updateSpy = vi.spyOn(mockConfig, "update");
|
|
127
137
|
const context = createBaseContext();
|
|
128
138
|
|
|
129
139
|
await deploy.call(context, baseFlags);
|
|
@@ -143,16 +153,20 @@ describe("deploy", () => {
|
|
|
143
153
|
environment: "local",
|
|
144
154
|
});
|
|
145
155
|
|
|
146
|
-
expect(
|
|
156
|
+
expect(updateSpy).toHaveBeenCalledWith({
|
|
157
|
+
workerId: "worker-789",
|
|
158
|
+
});
|
|
147
159
|
});
|
|
148
160
|
|
|
149
161
|
it("throws error when prompt is cancelled", async () => {
|
|
150
|
-
const mockConfig = await
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
162
|
+
const [mockConfig] = await createAndLoadConfig({
|
|
163
|
+
configFile: {
|
|
164
|
+
token:
|
|
165
|
+
"1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig",
|
|
166
|
+
workerId: null,
|
|
167
|
+
environment: "local",
|
|
168
|
+
baseUrl: "http://localhost:3000",
|
|
169
|
+
},
|
|
156
170
|
});
|
|
157
171
|
|
|
158
172
|
vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
|
|
@@ -166,12 +180,14 @@ describe("deploy", () => {
|
|
|
166
180
|
});
|
|
167
181
|
|
|
168
182
|
it("throws error when both workerId and name are provided", async () => {
|
|
169
|
-
const mockConfig = await
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
183
|
+
const [mockConfig] = await createAndLoadConfig({
|
|
184
|
+
configFile: {
|
|
185
|
+
token:
|
|
186
|
+
"1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig",
|
|
187
|
+
workerId: "worker-123",
|
|
188
|
+
environment: "local",
|
|
189
|
+
baseUrl: "http://localhost:3000",
|
|
190
|
+
},
|
|
175
191
|
});
|
|
176
192
|
|
|
177
193
|
vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
|
|
@@ -184,12 +200,14 @@ describe("deploy", () => {
|
|
|
184
200
|
});
|
|
185
201
|
|
|
186
202
|
it("handles deployment failure", async () => {
|
|
187
|
-
const mockConfig = await
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
203
|
+
const [mockConfig] = await createAndLoadConfig({
|
|
204
|
+
configFile: {
|
|
205
|
+
token:
|
|
206
|
+
"1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig",
|
|
207
|
+
workerId: "worker-123",
|
|
208
|
+
environment: "local",
|
|
209
|
+
baseUrl: "http://localhost:3000",
|
|
210
|
+
},
|
|
193
211
|
});
|
|
194
212
|
|
|
195
213
|
vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
|
|
@@ -211,12 +229,14 @@ describe("deploy", () => {
|
|
|
211
229
|
});
|
|
212
230
|
|
|
213
231
|
it("trims whitespace from prompted name", async () => {
|
|
214
|
-
const mockConfig = await
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
232
|
+
const [mockConfig] = await createAndLoadConfig({
|
|
233
|
+
configFile: {
|
|
234
|
+
token:
|
|
235
|
+
"1.2.eyJzcGFjZUlkIjoic3BhY2UxIiwidXNlcklkIjoidXNlcjEiLCJjZWxsSWQiOiJjZWxsMSJ9.sig",
|
|
236
|
+
workerId: null,
|
|
237
|
+
environment: "local",
|
|
238
|
+
baseUrl: "http://localhost:3000",
|
|
239
|
+
},
|
|
220
240
|
});
|
|
221
241
|
|
|
222
242
|
vi.spyOn(Config, "load").mockResolvedValue(mockConfig);
|
|
@@ -68,7 +68,7 @@ export const deploy = buildAuthedHandler(async function (flags: DeployFlags) {
|
|
|
68
68
|
|
|
69
69
|
if (Result.isSuccess(result)) {
|
|
70
70
|
const { workerId } = Result.unwrap(result);
|
|
71
|
-
await this.config.
|
|
71
|
+
await this.config.update({ workerId });
|
|
72
72
|
this.writer.writeErr("✓ Successfully deployed worker");
|
|
73
73
|
} else {
|
|
74
74
|
this.writer.writeErr("✗ Failed to deploy worker");
|
|
@@ -3,41 +3,37 @@ 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
|
|
6
|
+
import { Config, type ConfigMap } from "../../config.js";
|
|
7
7
|
import type { GlobalFlags } from "../../flags.js";
|
|
8
8
|
import { Writer } from "../../writer.js";
|
|
9
9
|
|
|
10
|
-
export type ConfigFileContents = {
|
|
11
|
-
token: string | null;
|
|
12
|
-
workerId: string | null;
|
|
13
|
-
environment: Environment;
|
|
14
|
-
baseUrl: string;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
10
|
export const tmpDirectories: string[] = [];
|
|
18
11
|
|
|
19
12
|
export const baseFlags: GlobalFlags = {
|
|
20
13
|
debug: false,
|
|
21
14
|
};
|
|
22
15
|
|
|
23
|
-
export async function
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
16
|
+
export async function createAndLoadConfig({
|
|
17
|
+
configFile,
|
|
18
|
+
env,
|
|
19
|
+
flags,
|
|
20
|
+
}: {
|
|
21
|
+
configFile: Partial<ConfigMap>;
|
|
22
|
+
env?: Partial<NodeJS.ProcessEnv>;
|
|
23
|
+
flags?: Partial<GlobalFlags>;
|
|
24
|
+
}): Promise<[config: Config, path: string]> {
|
|
25
|
+
const configFilePath = await createConfigFile(configFile);
|
|
31
26
|
|
|
32
|
-
|
|
33
|
-
configFileContents: ConfigFileContents,
|
|
34
|
-
): Promise<Config> {
|
|
35
|
-
const configFilePath = await createConfigFile(configFileContents);
|
|
36
|
-
return Config.load({
|
|
27
|
+
const map = await Config.load({
|
|
37
28
|
configFilePath,
|
|
38
|
-
processEnv: {}
|
|
39
|
-
flags: {
|
|
29
|
+
processEnv: env ?? {},
|
|
30
|
+
flags: {
|
|
31
|
+
...baseFlags,
|
|
32
|
+
...(flags ?? {}),
|
|
33
|
+
},
|
|
40
34
|
});
|
|
35
|
+
|
|
36
|
+
return [map, configFilePath];
|
|
41
37
|
}
|
|
42
38
|
|
|
43
39
|
export function createBaseContext() {
|
|
@@ -58,3 +54,12 @@ export async function cleanupTmpDirectories() {
|
|
|
58
54
|
}
|
|
59
55
|
}
|
|
60
56
|
}
|
|
57
|
+
|
|
58
|
+
async function createConfigFile(contents: Partial<ConfigMap>) {
|
|
59
|
+
const dir = await mkdtemp(path.join(tmpdir(), "cmd-test-"));
|
|
60
|
+
tmpDirectories.push(dir);
|
|
61
|
+
const configFilePath = path.join(dir, "config.json");
|
|
62
|
+
|
|
63
|
+
await writeFile(configFilePath, JSON.stringify(contents, null, 2), "utf-8");
|
|
64
|
+
return configFilePath;
|
|
65
|
+
}
|
package/src/cli/config.test.ts
CHANGED
|
@@ -1,74 +1,30 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
});
|
|
3
|
+
import {
|
|
4
|
+
cleanupTmpDirectories,
|
|
5
|
+
createAndLoadConfig,
|
|
6
|
+
} from "./commands/utils/testing.js";
|
|
7
|
+
import { Config, type ConfigMap } from "./config.js";
|
|
34
8
|
|
|
35
|
-
|
|
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
|
|
65
|
-
configFile:
|
|
20
|
+
const [config] = await createAndLoadConfig({
|
|
21
|
+
configFile: baseConfigMap,
|
|
66
22
|
});
|
|
67
23
|
|
|
68
|
-
expect(config.token).toBe(
|
|
69
|
-
expect(config.workerId).toBe(
|
|
70
|
-
expect(config.environment).toBe(
|
|
71
|
-
expect(config.baseUrl).toBe(
|
|
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
|
|
81
|
-
configFile:
|
|
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(
|
|
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,15 +56,53 @@ describe("Config.load precedence", () => {
|
|
|
100
56
|
"base-url": "https://flag.example.com",
|
|
101
57
|
};
|
|
102
58
|
|
|
103
|
-
const config = await
|
|
104
|
-
configFile:
|
|
59
|
+
const [config] = await createAndLoadConfig({
|
|
60
|
+
configFile: baseConfigMap,
|
|
105
61
|
env: envVars,
|
|
106
62
|
flags,
|
|
107
63
|
});
|
|
108
64
|
|
|
109
|
-
expect(config.token).toBe(
|
|
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
|
});
|
|
71
|
+
|
|
72
|
+
describe("Config.update", () => {
|
|
73
|
+
it("persists only provided keys to disk", async () => {
|
|
74
|
+
const [, configFilePath] = await createAndLoadConfig({
|
|
75
|
+
configFile: {
|
|
76
|
+
token: "file-token",
|
|
77
|
+
workerId: "file-worker",
|
|
78
|
+
environment: "prod",
|
|
79
|
+
baseUrl: "https://config.example.com",
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
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
|
+
});
|
|
90
|
+
|
|
91
|
+
expect(config.baseUrl).toBe("https://env.example.com");
|
|
92
|
+
|
|
93
|
+
await config.update({
|
|
94
|
+
token: "updated-token",
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(config.token).toBe("updated-token");
|
|
98
|
+
|
|
99
|
+
const persisted = JSON.parse(await readFile(configFilePath, "utf-8"));
|
|
100
|
+
|
|
101
|
+
expect(persisted).toEqual({
|
|
102
|
+
token: "updated-token",
|
|
103
|
+
workerId: "file-worker",
|
|
104
|
+
environment: "prod",
|
|
105
|
+
baseUrl: "https://config.example.com",
|
|
106
|
+
});
|
|
107
|
+
});
|
|
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,31 +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
|
-
await this.#writeConfigFile();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async setToken(token: string | null) {
|
|
82
|
-
this.#configMap.token = token;
|
|
83
|
-
await this.#writeConfigFile();
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async setWorkerId(workerId: string | null) {
|
|
87
|
-
this.#configMap.workerId = workerId;
|
|
88
|
-
await this.#writeConfigFile();
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async #writeConfigFile() {
|
|
92
|
-
await fs.promises.writeFile(
|
|
93
|
-
this.#configFilePath,
|
|
94
|
-
JSON.stringify(this.#configMap, null, 2),
|
|
95
|
-
"utf-8",
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
74
|
get tokenInfo(): { token: string; spaceId: string; cellId: string } {
|
|
100
75
|
const token = this.token;
|
|
101
76
|
if (!token) {
|
|
@@ -106,47 +81,100 @@ export class Config {
|
|
|
106
81
|
return { token, spaceId, cellId };
|
|
107
82
|
}
|
|
108
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
|
+
|
|
109
108
|
static async load(opts: {
|
|
110
109
|
configFilePath: string;
|
|
111
110
|
processEnv: NodeJS.ProcessEnv;
|
|
112
111
|
flags: GlobalFlags;
|
|
113
112
|
}) {
|
|
114
113
|
const absConfigFilePath = path.resolve(process.cwd(), opts.configFilePath);
|
|
115
|
-
const
|
|
114
|
+
const partialConfig = await Config.#readConfigFile(absConfigFilePath);
|
|
116
115
|
|
|
117
116
|
if (opts.processEnv.WORKERS_TOKEN) {
|
|
118
|
-
|
|
117
|
+
partialConfig.token = opts.processEnv.WORKERS_TOKEN;
|
|
119
118
|
}
|
|
120
119
|
if (opts.processEnv.WORKERS_ENVIRONMENT) {
|
|
121
|
-
|
|
120
|
+
partialConfig.environment = parseEnvironment(
|
|
122
121
|
opts.processEnv.WORKERS_ENVIRONMENT,
|
|
123
122
|
);
|
|
124
123
|
}
|
|
125
124
|
if (opts.processEnv.WORKERS_WORKER_ID) {
|
|
126
|
-
|
|
125
|
+
partialConfig.workerId = opts.processEnv.WORKERS_WORKER_ID;
|
|
127
126
|
}
|
|
128
127
|
if (opts.processEnv.WORKERS_BASE_URL) {
|
|
129
|
-
|
|
128
|
+
partialConfig.baseUrl = opts.processEnv.WORKERS_BASE_URL;
|
|
130
129
|
}
|
|
131
130
|
|
|
132
131
|
if (opts.flags.token) {
|
|
133
|
-
|
|
132
|
+
partialConfig.token = opts.flags.token;
|
|
134
133
|
}
|
|
135
134
|
if (opts.flags.env) {
|
|
136
|
-
|
|
135
|
+
partialConfig.environment = parseEnvironment(opts.flags.env);
|
|
137
136
|
}
|
|
138
137
|
if (opts.flags["base-url"]) {
|
|
139
|
-
|
|
138
|
+
partialConfig.baseUrl = opts.flags["base-url"];
|
|
140
139
|
}
|
|
141
140
|
if (opts.flags["worker-id"]) {
|
|
142
|
-
|
|
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");
|
|
143
166
|
}
|
|
144
167
|
|
|
145
|
-
|
|
168
|
+
const configMap: ConfigMap = {
|
|
169
|
+
environment,
|
|
170
|
+
baseUrl,
|
|
171
|
+
token,
|
|
172
|
+
workerId,
|
|
173
|
+
};
|
|
146
174
|
|
|
147
175
|
return new Config({
|
|
148
176
|
configFilePath: absConfigFilePath,
|
|
149
|
-
|
|
177
|
+
configMap,
|
|
150
178
|
});
|
|
151
179
|
}
|
|
152
180
|
|
|
@@ -167,7 +195,7 @@ export class Config {
|
|
|
167
195
|
token: null,
|
|
168
196
|
workerId: null,
|
|
169
197
|
environment: "prod",
|
|
170
|
-
baseUrl:
|
|
198
|
+
baseUrl: baseUrlForEnvironment("prod"),
|
|
171
199
|
};
|
|
172
200
|
} else {
|
|
173
201
|
throw error;
|
|
@@ -226,7 +254,7 @@ export function extractPayloadFromToken(token: string): {
|
|
|
226
254
|
}
|
|
227
255
|
}
|
|
228
256
|
|
|
229
|
-
function
|
|
257
|
+
function baseUrlForEnvironment(environment: Environment): string {
|
|
230
258
|
switch (environment) {
|
|
231
259
|
case "local":
|
|
232
260
|
return "http://localhost:3000";
|