@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.
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.impl.d.ts +12 -2
- package/dist/commands/auth.impl.d.ts.map +1 -1
- package/dist/commands/auth.impl.js +73 -6
- package/dist/commands/auth.js +39 -2
- package/dist/commands/capabilities.impl.d.ts +1 -1
- package/dist/commands/capabilities.impl.d.ts.map +1 -1
- package/dist/commands/capabilities.impl.js +7 -1
- package/dist/commands/delete.d.ts +3 -0
- package/dist/commands/delete.d.ts.map +1 -0
- package/dist/commands/delete.impl.d.ts +7 -0
- package/dist/commands/delete.impl.d.ts.map +1 -0
- package/dist/commands/delete.impl.js +26 -0
- package/dist/commands/delete.impl.test.d.ts +2 -0
- package/dist/commands/delete.impl.test.d.ts.map +1 -0
- package/dist/commands/delete.js +31 -0
- package/dist/commands/deploy.js +1 -1
- package/dist/commands/list.d.ts +3 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.impl.d.ts +3 -0
- package/dist/commands/list.impl.d.ts.map +1 -0
- package/dist/commands/list.impl.js +41 -0
- package/dist/commands/list.impl.test.d.ts +2 -0
- package/dist/commands/list.impl.test.d.ts.map +1 -0
- package/dist/commands/list.js +17 -0
- package/dist/commands/oauth.impl.d.ts +1 -1
- package/dist/commands/oauth.impl.d.ts.map +1 -1
- package/dist/commands/oauth.impl.js +11 -3
- package/dist/commands/runs.impl.d.ts +1 -1
- package/dist/commands/runs.impl.d.ts.map +1 -1
- package/dist/commands/runs.impl.js +7 -1
- package/dist/commands/secrets.impl.d.ts +1 -1
- package/dist/commands/secrets.impl.d.ts.map +1 -1
- package/dist/commands/secrets.impl.js +7 -1
- package/dist/commands/utils/testing.d.ts +4 -2
- package/dist/commands/utils/testing.d.ts.map +1 -1
- package/dist/commands/utils/testing.js +23 -10
- package/dist/config.d.ts +126 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +206 -37
- package/dist/flags.d.ts +5 -0
- package/dist/flags.d.ts.map +1 -1
- package/dist/flags.js +34 -1
- package/dist/handler.d.ts.map +1 -1
- package/dist/handler.js +9 -3
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +39 -5
- package/dist/token.d.ts +7 -0
- package/dist/token.d.ts.map +1 -1
- package/dist/token.js +10 -1
- 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 {
|
|
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
|
|
21
|
+
const userConfig = args.userConfig ?? Config.emptyGlobalConfig;
|
|
22
|
+
const spaceCache = args.spaceCache ?? Config.emptySpaceCache;
|
|
19
23
|
const localConfig = args.localConfig ?? Config.emptyLocalConfig;
|
|
20
|
-
const {
|
|
21
|
-
|
|
24
|
+
const { globalConfigPath, spaceCachePath, localPath } = await createConfigFiles({
|
|
25
|
+
userConfig,
|
|
26
|
+
spaceCache,
|
|
22
27
|
localConfig
|
|
23
28
|
});
|
|
24
29
|
const config = Config.load({
|
|
25
|
-
|
|
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
|
|
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
|
-
|
|
62
|
-
JSON.stringify(args.
|
|
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 {
|
|
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
|
|
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
|
-
*
|
|
30
|
-
*
|
|
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
|
-
* @
|
|
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
|
|
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
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,OAAO,CAAC,MAAM,KAAK,CAAC;AACpB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAIN,KAAK,SAAS,EAEd,MAAM,YAAY,CAAC;AAIpB,eAAO,MAAM,WAAW,gDAA6C,CAAC;AACtE,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,QAAA,MAAM,UAAU;;;;;;;mBAMd,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,QAAA,MAAM,WAAW;;;;;;mBAMf,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAatD,qBAAa,yBAA0B,SAAQ,KAAK;;CAKnD;AAED
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,OAAO,CAAC,MAAM,KAAK,CAAC;AACpB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAIN,KAAK,SAAS,EAEd,MAAM,YAAY,CAAC;AAIpB,eAAO,MAAM,WAAW,gDAA6C,CAAC;AACtE,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,QAAA,MAAM,UAAU;;;;;;;mBAMd,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,QAAA,MAAM,WAAW;;;;;;mBAMf,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAatD,QAAA,MAAM,YAAY;;;;;;;;mBAUhB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD,qBAAa,yBAA0B,SAAQ,KAAK;;CAKnD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,qBAAa,MAAM;;gBAeN,IAAI,EAAE;QACjB,gBAAgB,EAAE,MAAM,CAAC;QACzB,YAAY,EAAE,YAAY,CAAC;QAC3B,cAAc,EAAE,MAAM,CAAC;QACvB,UAAU,EAAE,UAAU,CAAC;QACvB,eAAe,EAAE,MAAM,CAAC;QACxB,WAAW,EAAE,WAAW,CAAC;QACzB,mBAAmB,EAAE,OAAO,CAAC;QAC7B,WAAW,EAAE,WAAW,CAAC;QACzB,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;KACrB;IAeD,IAAI,WAAW,IAAI,WAAW,CAE7B;IAED,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,OAAO,IAAI,MAAM,GAAG,IAAI,CAE3B;IAED,IAAI,QAAQ,IAAI,MAAM,GAAG,IAAI,CAE5B;IAED;;;;;;;OAOG;IACH,IAAI,cAAc,IAAI,MAAM,GAAG,IAAI,CAElC;IAED;;;;;;;OAOG;IACH,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAS/C;;;;OAIG;IACH,eAAe,IAAI,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAItE;;;;OAIG;IACG,QAAQ,IAAI,OAAO,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC;IAmBvE;;;;;;OAMG;IACG,YAAY,IAAI,OAAO,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;IASpE;;;;;;;;;;OAUG;IACG,KAAK,CAAC,IAAI,EAAE;QACjB,WAAW,EAAE,WAAW,CAAC;QACzB,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;KACf;IAqBD;;;;OAIG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM;IAM1B;;;;;;;;OAQG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAwDpC;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,GAAG,SAAS;IAiBnE;;;;;;;;;;OAUG;IACH,MAAM,CAAC,uBAAuB,CAAC,IAAI,EAAE;QACpC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;QACvB,MAAM,CAAC,EAAE,OAAO,CAAC;KACjB,GAAG,MAAM;IAiCV;;;;;;;;OAQG;IACH,MAAM,CAAC,qBAAqB,CAAC,IAAI,EAAE;QAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;QACvB,MAAM,CAAC,EAAE,OAAO,CAAC;KACjB,GAAG,MAAM;IA4CV;;;;;;;OAOG;IACH,MAAM,CAAC,sBAAsB,CAC5B,IAAI,GAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAO,GAChD,MAAM;IAoBT;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE;QACjB,gBAAgB,EAAE,MAAM,CAAC;QACzB,cAAc,EAAE,MAAM,CAAC;QACvB,SAAS,EAAE,MAAM,CAAC;QAClB,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;QACvB,KAAK,EAAE,WAAW,CAAC;KACnB,GAAG,MAAM;IAqEV;;OAEG;IACH,MAAM,KAAK,gBAAgB,IAAI,WAAW,CAEzC;IAED;;OAEG;IACH,MAAM,KAAK,eAAe,IAAI,UAAU,CAEvC;IAED;;OAEG;IACH,MAAM,KAAK,iBAAiB,IAAI,YAAY,CAK3C;CACD"}
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
* @
|
|
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
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
|
|
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(
|
|
484
|
+
function isFile(filePath) {
|
|
332
485
|
try {
|
|
333
|
-
return fs.statSync(
|
|
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
|
}
|