@gpc-cli/config 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 GPC Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,50 @@
1
+ interface GpcConfig {
2
+ app?: string;
3
+ output?: OutputFormat;
4
+ profile?: string;
5
+ auth?: AuthConfig;
6
+ developerId?: string;
7
+ plugins?: string[];
8
+ profiles?: Record<string, ProfileConfig>;
9
+ approvedPlugins?: string[];
10
+ }
11
+ interface AuthConfig {
12
+ serviceAccount?: string;
13
+ }
14
+ interface ProfileConfig {
15
+ auth?: AuthConfig;
16
+ app?: string;
17
+ developerId?: string;
18
+ }
19
+ type OutputFormat = "table" | "json" | "yaml" | "markdown";
20
+ interface ResolvedConfig extends Required<Pick<GpcConfig, "output">> {
21
+ app?: string;
22
+ profile?: string;
23
+ auth?: AuthConfig;
24
+ configPath?: string;
25
+ developerId?: string;
26
+ plugins?: string[];
27
+ profiles?: Record<string, ProfileConfig>;
28
+ approvedPlugins?: string[];
29
+ }
30
+
31
+ declare function loadEnvConfig(): Partial<GpcConfig>;
32
+ declare function findConfigFile(startDir?: string): string | undefined;
33
+ declare function loadConfig(overrides?: Partial<GpcConfig>): Promise<ResolvedConfig>;
34
+
35
+ declare function getConfigDir(): string;
36
+ declare function getUserConfigPath(): string;
37
+ declare function getDataDir(): string;
38
+ declare function getCacheDir(): string;
39
+
40
+ declare function setConfigValue(key: string, value: string): Promise<void>;
41
+ declare function setProfileConfig(profileName: string, config: ProfileConfig): Promise<void>;
42
+ declare function deleteProfile(profileName: string): Promise<boolean>;
43
+ declare function listProfiles(): Promise<string[]>;
44
+ declare function approvePlugin(pluginName: string): Promise<void>;
45
+ declare function revokePluginApproval(pluginName: string): Promise<boolean>;
46
+ declare function initConfig(config: GpcConfig): Promise<string>;
47
+
48
+ declare const DEFAULT_CONFIG: ResolvedConfig;
49
+
50
+ export { type AuthConfig, DEFAULT_CONFIG, type GpcConfig, type OutputFormat, type ProfileConfig, type ResolvedConfig, approvePlugin, deleteProfile, findConfigFile, getCacheDir, getConfigDir, getDataDir, getUserConfigPath, initConfig, listProfiles, loadConfig, loadEnvConfig, revokePluginApproval, setConfigValue, setProfileConfig };
package/dist/index.js ADDED
@@ -0,0 +1,281 @@
1
+ // src/loader.ts
2
+ import { statSync } from "fs";
3
+ import { readFile } from "fs/promises";
4
+ import { dirname, join as join2 } from "path";
5
+
6
+ // src/defaults.ts
7
+ var DEFAULT_CONFIG = {
8
+ output: "table"
9
+ };
10
+
11
+ // src/paths.ts
12
+ import { homedir } from "os";
13
+ import { join, isAbsolute } from "path";
14
+ function resolveXdg(envVar, fallback) {
15
+ const xdg = process.env[envVar];
16
+ if (xdg && isAbsolute(xdg)) return xdg;
17
+ return fallback;
18
+ }
19
+ function getConfigDir() {
20
+ const base = resolveXdg("XDG_CONFIG_HOME", join(homedir(), ".config"));
21
+ return join(base, "gpc");
22
+ }
23
+ function getUserConfigPath() {
24
+ return join(getConfigDir(), "config.json");
25
+ }
26
+ function getDataDir() {
27
+ const base = resolveXdg("XDG_DATA_HOME", join(homedir(), ".local", "share"));
28
+ return join(base, "gpc");
29
+ }
30
+ function getCacheDir() {
31
+ const base = resolveXdg("XDG_CACHE_HOME", join(homedir(), ".cache"));
32
+ return join(base, "gpc");
33
+ }
34
+
35
+ // src/loader.ts
36
+ var VALID_OUTPUT_FORMATS = /* @__PURE__ */ new Set([
37
+ "table",
38
+ "json",
39
+ "yaml",
40
+ "markdown"
41
+ ]);
42
+ function isValidOutputFormat(value) {
43
+ return VALID_OUTPUT_FORMATS.has(value);
44
+ }
45
+ function loadEnvConfig() {
46
+ const config = {};
47
+ const app = process.env["GPC_APP"];
48
+ if (app) config.app = app;
49
+ const output = process.env["GPC_OUTPUT"];
50
+ if (output) {
51
+ if (isValidOutputFormat(output)) {
52
+ config.output = output;
53
+ }
54
+ }
55
+ const profile = process.env["GPC_PROFILE"];
56
+ if (profile) config.profile = profile;
57
+ const serviceAccount = process.env["GPC_SERVICE_ACCOUNT"];
58
+ if (serviceAccount) {
59
+ config.auth = { serviceAccount };
60
+ }
61
+ const developerId = process.env["GPC_DEVELOPER_ID"];
62
+ if (developerId) config.developerId = developerId;
63
+ return config;
64
+ }
65
+ function findConfigFile(startDir) {
66
+ let dir = startDir || process.cwd();
67
+ while (true) {
68
+ const candidate = join2(dir, ".gpcrc.json");
69
+ try {
70
+ statSync(candidate);
71
+ return candidate;
72
+ } catch {
73
+ }
74
+ const parent = dirname(dir);
75
+ if (parent === dir) break;
76
+ dir = parent;
77
+ }
78
+ return void 0;
79
+ }
80
+ async function readConfigFile(filePath) {
81
+ const content = await readFile(filePath, "utf-8");
82
+ const parsed = JSON.parse(content);
83
+ sanitizeObject(parsed);
84
+ if (parsed.output !== void 0 && !isValidOutputFormat(parsed.output)) {
85
+ throw new Error(
86
+ `Invalid output format "${String(parsed.output)}" in ${filePath}. Must be one of: ${[...VALID_OUTPUT_FORMATS].join(", ")}`
87
+ );
88
+ }
89
+ return parsed;
90
+ }
91
+ var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
92
+ function sanitizeObject(obj) {
93
+ if (!obj || typeof obj !== "object") return;
94
+ if (Array.isArray(obj)) {
95
+ for (const item of obj) sanitizeObject(item);
96
+ return;
97
+ }
98
+ const record = obj;
99
+ for (const key of Object.keys(record)) {
100
+ if (DANGEROUS_KEYS.has(key)) {
101
+ delete record[key];
102
+ } else if (typeof record[key] === "object" && record[key] !== null) {
103
+ sanitizeObject(record[key]);
104
+ }
105
+ }
106
+ }
107
+ async function loadConfig(overrides) {
108
+ const result = { ...DEFAULT_CONFIG };
109
+ try {
110
+ const userConfig = await readConfigFile(getUserConfigPath());
111
+ Object.assign(result, stripUndefined(userConfig));
112
+ } catch {
113
+ }
114
+ const projectConfigPath = findConfigFile();
115
+ if (projectConfigPath) {
116
+ try {
117
+ const projectConfig = await readConfigFile(projectConfigPath);
118
+ Object.assign(result, stripUndefined(projectConfig));
119
+ result.configPath = projectConfigPath;
120
+ } catch {
121
+ }
122
+ }
123
+ const envConfig = loadEnvConfig();
124
+ Object.assign(result, stripUndefined(envConfig));
125
+ if (overrides) {
126
+ Object.assign(result, stripUndefined(overrides));
127
+ }
128
+ if (result.profile && result.profiles?.[result.profile]) {
129
+ const p = result.profiles[result.profile];
130
+ if (p.auth) result.auth = p.auth;
131
+ if (p.app) result.app = p.app;
132
+ if (p.developerId) result.developerId = p.developerId;
133
+ } else if (result.profile && result.profiles && !(result.profile in result.profiles)) {
134
+ const available = Object.keys(result.profiles).join(", ");
135
+ throw new Error(
136
+ `Profile "${result.profile}" not found. Available profiles: ${available || "(none)"}`
137
+ );
138
+ }
139
+ if (!isValidOutputFormat(result.output)) {
140
+ result.output = DEFAULT_CONFIG.output;
141
+ }
142
+ return result;
143
+ }
144
+ function stripUndefined(obj) {
145
+ const cleaned = {};
146
+ for (const [key, value] of Object.entries(obj)) {
147
+ if (value !== void 0) {
148
+ cleaned[key] = value;
149
+ }
150
+ }
151
+ return cleaned;
152
+ }
153
+
154
+ // src/writer.ts
155
+ import { chmod, mkdir, readFile as readFile2, writeFile } from "fs/promises";
156
+ import { dirname as dirname2, join as join3 } from "path";
157
+ async function writeSecureFile(filePath, content) {
158
+ await writeFile(filePath, content, { encoding: "utf-8", mode: 384 });
159
+ await chmod(filePath, 384).catch(() => {
160
+ });
161
+ }
162
+ async function setConfigValue(key, value) {
163
+ const configPath = join3(getConfigDir(), "config.json");
164
+ let existing = {};
165
+ try {
166
+ const content = await readFile2(configPath, "utf-8");
167
+ existing = JSON.parse(content);
168
+ } catch {
169
+ }
170
+ const keys = key.split(".");
171
+ let target = existing;
172
+ for (let i = 0; i < keys.length - 1; i++) {
173
+ const k = keys[i];
174
+ if (typeof target[k] !== "object" || target[k] === null) {
175
+ target[k] = {};
176
+ }
177
+ target = target[k];
178
+ }
179
+ target[keys[keys.length - 1]] = value;
180
+ await mkdir(dirname2(configPath), { recursive: true, mode: 448 });
181
+ await writeSecureFile(configPath, JSON.stringify(existing, null, 2) + "\n");
182
+ }
183
+ async function setProfileConfig(profileName, config) {
184
+ const configPath = join3(getConfigDir(), "config.json");
185
+ let existing = {};
186
+ try {
187
+ const content = await readFile2(configPath, "utf-8");
188
+ existing = JSON.parse(content);
189
+ } catch {
190
+ }
191
+ if (typeof existing["profiles"] !== "object" || existing["profiles"] === null) {
192
+ existing["profiles"] = {};
193
+ }
194
+ existing["profiles"][profileName] = config;
195
+ await mkdir(dirname2(configPath), { recursive: true, mode: 448 });
196
+ await writeSecureFile(configPath, JSON.stringify(existing, null, 2) + "\n");
197
+ }
198
+ async function deleteProfile(profileName) {
199
+ const configPath = join3(getConfigDir(), "config.json");
200
+ let existing = {};
201
+ try {
202
+ const content = await readFile2(configPath, "utf-8");
203
+ existing = JSON.parse(content);
204
+ } catch {
205
+ return false;
206
+ }
207
+ const profiles = existing["profiles"];
208
+ if (!profiles || !(profileName in profiles)) return false;
209
+ delete profiles[profileName];
210
+ await writeSecureFile(configPath, JSON.stringify(existing, null, 2) + "\n");
211
+ return true;
212
+ }
213
+ async function listProfiles() {
214
+ const configPath = join3(getConfigDir(), "config.json");
215
+ try {
216
+ const content = await readFile2(configPath, "utf-8");
217
+ const config = JSON.parse(content);
218
+ const profiles = config["profiles"];
219
+ return profiles ? Object.keys(profiles) : [];
220
+ } catch {
221
+ return [];
222
+ }
223
+ }
224
+ async function approvePlugin(pluginName) {
225
+ const configPath = join3(getConfigDir(), "config.json");
226
+ let existing = {};
227
+ try {
228
+ const content = await readFile2(configPath, "utf-8");
229
+ existing = JSON.parse(content);
230
+ } catch {
231
+ }
232
+ const approved = existing["approvedPlugins"] ?? [];
233
+ if (!approved.includes(pluginName)) {
234
+ approved.push(pluginName);
235
+ }
236
+ existing["approvedPlugins"] = approved;
237
+ await mkdir(dirname2(configPath), { recursive: true, mode: 448 });
238
+ await writeSecureFile(configPath, JSON.stringify(existing, null, 2) + "\n");
239
+ }
240
+ async function revokePluginApproval(pluginName) {
241
+ const configPath = join3(getConfigDir(), "config.json");
242
+ let existing = {};
243
+ try {
244
+ const content = await readFile2(configPath, "utf-8");
245
+ existing = JSON.parse(content);
246
+ } catch {
247
+ return false;
248
+ }
249
+ const approved = existing["approvedPlugins"] ?? [];
250
+ const index = approved.indexOf(pluginName);
251
+ if (index === -1) return false;
252
+ approved.splice(index, 1);
253
+ existing["approvedPlugins"] = approved;
254
+ await writeSecureFile(configPath, JSON.stringify(existing, null, 2) + "\n");
255
+ return true;
256
+ }
257
+ async function initConfig(config) {
258
+ const configDir = getConfigDir();
259
+ const configPath = join3(configDir, "config.json");
260
+ await mkdir(configDir, { recursive: true, mode: 448 });
261
+ await writeSecureFile(configPath, JSON.stringify(config, null, 2) + "\n");
262
+ return configPath;
263
+ }
264
+ export {
265
+ DEFAULT_CONFIG,
266
+ approvePlugin,
267
+ deleteProfile,
268
+ findConfigFile,
269
+ getCacheDir,
270
+ getConfigDir,
271
+ getDataDir,
272
+ getUserConfigPath,
273
+ initConfig,
274
+ listProfiles,
275
+ loadConfig,
276
+ loadEnvConfig,
277
+ revokePluginApproval,
278
+ setConfigValue,
279
+ setProfileConfig
280
+ };
281
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/loader.ts","../src/defaults.ts","../src/paths.ts","../src/writer.ts"],"sourcesContent":["import { statSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\nimport { DEFAULT_CONFIG } from \"./defaults.js\";\nimport { getUserConfigPath } from \"./paths.js\";\nimport type { GpcConfig, OutputFormat, ResolvedConfig } from \"./types.js\";\n\nconst VALID_OUTPUT_FORMATS: ReadonlySet<string> = new Set([\n \"table\",\n \"json\",\n \"yaml\",\n \"markdown\",\n]);\n\nfunction isValidOutputFormat(value: string): value is OutputFormat {\n return VALID_OUTPUT_FORMATS.has(value);\n}\n\nexport function loadEnvConfig(): Partial<GpcConfig> {\n const config: Partial<GpcConfig> = {};\n\n const app = process.env[\"GPC_APP\"];\n if (app) config.app = app;\n\n const output = process.env[\"GPC_OUTPUT\"];\n if (output) {\n if (isValidOutputFormat(output)) {\n config.output = output;\n }\n }\n\n const profile = process.env[\"GPC_PROFILE\"];\n if (profile) config.profile = profile;\n\n const serviceAccount = process.env[\"GPC_SERVICE_ACCOUNT\"];\n if (serviceAccount) {\n config.auth = { serviceAccount };\n }\n\n const developerId = process.env[\"GPC_DEVELOPER_ID\"];\n if (developerId) config.developerId = developerId;\n\n return config;\n}\n\nexport function findConfigFile(startDir?: string): string | undefined {\n let dir = startDir || process.cwd();\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const candidate = join(dir, \".gpcrc.json\");\n try {\n statSync(candidate);\n return candidate;\n } catch {\n // file does not exist, keep walking up\n }\n\n const parent = dirname(dir);\n if (parent === dir) break; // reached root\n dir = parent;\n }\n\n return undefined;\n}\n\nexport async function readConfigFile(filePath: string): Promise<GpcConfig> {\n const content = await readFile(filePath, \"utf-8\");\n const parsed = JSON.parse(content) as GpcConfig;\n\n // Guard against prototype pollution\n sanitizeObject(parsed);\n\n // Validate output format if present\n if (parsed.output !== undefined && !isValidOutputFormat(parsed.output)) {\n throw new Error(\n `Invalid output format \"${String(parsed.output)}\" in ${filePath}. Must be one of: ${[...VALID_OUTPUT_FORMATS].join(\", \")}`,\n );\n }\n\n return parsed;\n}\n\nconst DANGEROUS_KEYS = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n\n/** Remove prototype pollution vectors from parsed JSON objects recursively. */\nfunction sanitizeObject(obj: unknown): void {\n if (!obj || typeof obj !== \"object\") return;\n if (Array.isArray(obj)) {\n for (const item of obj) sanitizeObject(item);\n return;\n }\n const record = obj as Record<string, unknown>;\n for (const key of Object.keys(record)) {\n if (DANGEROUS_KEYS.has(key)) {\n delete record[key];\n } else if (typeof record[key] === \"object\" && record[key] !== null) {\n sanitizeObject(record[key]);\n }\n }\n}\n\nexport async function loadConfig(\n overrides?: Partial<GpcConfig>,\n): Promise<ResolvedConfig> {\n // Layer 5: defaults\n const result: ResolvedConfig = { ...DEFAULT_CONFIG };\n\n // Layer 4: user config file (~/.config/gpc/config.json)\n try {\n const userConfig = await readConfigFile(getUserConfigPath());\n Object.assign(result, stripUndefined(userConfig));\n } catch {\n // User config doesn't exist or is invalid — skip\n }\n\n // Layer 3: project config file (.gpcrc.json walking up)\n const projectConfigPath = findConfigFile();\n if (projectConfigPath) {\n try {\n const projectConfig = await readConfigFile(projectConfigPath);\n Object.assign(result, stripUndefined(projectConfig));\n result.configPath = projectConfigPath;\n } catch {\n // Project config is invalid — skip\n }\n }\n\n // Layer 2: environment variables\n const envConfig = loadEnvConfig();\n Object.assign(result, stripUndefined(envConfig));\n\n // Layer 1: CLI flag overrides\n if (overrides) {\n Object.assign(result, stripUndefined(overrides));\n }\n\n // Resolve profile overrides\n if (result.profile && result.profiles?.[result.profile]) {\n const p = result.profiles[result.profile]!;\n if (p.auth) result.auth = p.auth;\n if (p.app) result.app = p.app;\n if (p.developerId) result.developerId = p.developerId;\n } else if (result.profile && result.profiles && !(result.profile in result.profiles)) {\n const available = Object.keys(result.profiles).join(\", \");\n throw new Error(\n `Profile \"${result.profile}\" not found. Available profiles: ${available || \"(none)\"}`,\n );\n }\n\n // Validate final output format\n if (!isValidOutputFormat(result.output)) {\n result.output = DEFAULT_CONFIG.output;\n }\n\n return result;\n}\n\nfunction stripUndefined<T extends object>(obj: T): Partial<T> {\n const cleaned: Partial<T> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (value !== undefined) {\n (cleaned as Record<string, unknown>)[key] = value;\n }\n }\n return cleaned;\n}\n","import type { ResolvedConfig } from \"./types.js\";\n\nexport const DEFAULT_CONFIG: ResolvedConfig = {\n output: \"table\",\n};\n","import { homedir } from \"node:os\";\nimport { join, isAbsolute } from \"node:path\";\n\nfunction resolveXdg(envVar: string, fallback: string): string {\n const xdg = process.env[envVar];\n if (xdg && isAbsolute(xdg)) return xdg;\n return fallback;\n}\n\nexport function getConfigDir(): string {\n const base = resolveXdg(\"XDG_CONFIG_HOME\", join(homedir(), \".config\"));\n return join(base, \"gpc\");\n}\n\nexport function getUserConfigPath(): string {\n return join(getConfigDir(), \"config.json\");\n}\n\nexport function getDataDir(): string {\n const base = resolveXdg(\"XDG_DATA_HOME\", join(homedir(), \".local\", \"share\"));\n return join(base, \"gpc\");\n}\n\nexport function getCacheDir(): string {\n const base = resolveXdg(\"XDG_CACHE_HOME\", join(homedir(), \".cache\"));\n return join(base, \"gpc\");\n}\n","import { chmod, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\nimport { getConfigDir } from \"./paths.js\";\nimport type { GpcConfig, ProfileConfig } from \"./types.js\";\n\nasync function writeSecureFile(filePath: string, content: string): Promise<void> {\n await writeFile(filePath, content, { encoding: \"utf-8\", mode: 0o600 });\n await chmod(filePath, 0o600).catch(() => {});\n}\n\nexport async function setConfigValue(\n key: string,\n value: string,\n): Promise<void> {\n const configPath = join(getConfigDir(), \"config.json\");\n\n let existing: Record<string, unknown> = {};\n try {\n const content = await readFile(configPath, \"utf-8\");\n existing = JSON.parse(content) as Record<string, unknown>;\n } catch {\n // File doesn't exist yet — start fresh\n }\n\n // Support dotted keys like \"auth.serviceAccount\"\n const keys = key.split(\".\");\n let target = existing;\n for (let i = 0; i < keys.length - 1; i++) {\n const k = keys[i]!;\n if (typeof target[k] !== \"object\" || target[k] === null) {\n target[k] = {};\n }\n target = target[k] as Record<string, unknown>;\n }\n target[keys[keys.length - 1]!] = value;\n\n await mkdir(dirname(configPath), { recursive: true, mode: 0o700 });\n await writeSecureFile(configPath, JSON.stringify(existing, null, 2) + \"\\n\");\n}\n\nexport async function setProfileConfig(\n profileName: string,\n config: ProfileConfig,\n): Promise<void> {\n const configPath = join(getConfigDir(), \"config.json\");\n\n let existing: Record<string, unknown> = {};\n try {\n const content = await readFile(configPath, \"utf-8\");\n existing = JSON.parse(content) as Record<string, unknown>;\n } catch {\n // start fresh\n }\n\n if (typeof existing[\"profiles\"] !== \"object\" || existing[\"profiles\"] === null) {\n existing[\"profiles\"] = {};\n }\n (existing[\"profiles\"] as Record<string, unknown>)[profileName] = config;\n\n await mkdir(dirname(configPath), { recursive: true, mode: 0o700 });\n await writeSecureFile(configPath, JSON.stringify(existing, null, 2) + \"\\n\");\n}\n\nexport async function deleteProfile(profileName: string): Promise<boolean> {\n const configPath = join(getConfigDir(), \"config.json\");\n\n let existing: Record<string, unknown> = {};\n try {\n const content = await readFile(configPath, \"utf-8\");\n existing = JSON.parse(content) as Record<string, unknown>;\n } catch {\n return false;\n }\n\n const profiles = existing[\"profiles\"] as Record<string, unknown> | undefined;\n if (!profiles || !(profileName in profiles)) return false;\n\n delete profiles[profileName];\n await writeSecureFile(configPath, JSON.stringify(existing, null, 2) + \"\\n\");\n return true;\n}\n\nexport async function listProfiles(): Promise<string[]> {\n const configPath = join(getConfigDir(), \"config.json\");\n\n try {\n const content = await readFile(configPath, \"utf-8\");\n const config = JSON.parse(content) as Record<string, unknown>;\n const profiles = config[\"profiles\"] as Record<string, unknown> | undefined;\n return profiles ? Object.keys(profiles) : [];\n } catch {\n return [];\n }\n}\n\nexport async function approvePlugin(pluginName: string): Promise<void> {\n const configPath = join(getConfigDir(), \"config.json\");\n\n let existing: Record<string, unknown> = {};\n try {\n const content = await readFile(configPath, \"utf-8\");\n existing = JSON.parse(content) as Record<string, unknown>;\n } catch {\n // start fresh\n }\n\n const approved = (existing[\"approvedPlugins\"] as string[] | undefined) ?? [];\n if (!approved.includes(pluginName)) {\n approved.push(pluginName);\n }\n existing[\"approvedPlugins\"] = approved;\n\n await mkdir(dirname(configPath), { recursive: true, mode: 0o700 });\n await writeSecureFile(configPath, JSON.stringify(existing, null, 2) + \"\\n\");\n}\n\nexport async function revokePluginApproval(pluginName: string): Promise<boolean> {\n const configPath = join(getConfigDir(), \"config.json\");\n\n let existing: Record<string, unknown> = {};\n try {\n const content = await readFile(configPath, \"utf-8\");\n existing = JSON.parse(content) as Record<string, unknown>;\n } catch {\n return false;\n }\n\n const approved = (existing[\"approvedPlugins\"] as string[] | undefined) ?? [];\n const index = approved.indexOf(pluginName);\n if (index === -1) return false;\n\n approved.splice(index, 1);\n existing[\"approvedPlugins\"] = approved;\n await writeSecureFile(configPath, JSON.stringify(existing, null, 2) + \"\\n\");\n return true;\n}\n\nexport async function initConfig(config: GpcConfig): Promise<string> {\n const configDir = getConfigDir();\n const configPath = join(configDir, \"config.json\");\n\n await mkdir(configDir, { recursive: true, mode: 0o700 });\n await writeSecureFile(configPath, JSON.stringify(config, null, 2) + \"\\n\");\n\n return configPath;\n}\n"],"mappings":";AAAA,SAAS,gBAAgB;AACzB,SAAS,gBAAgB;AACzB,SAAS,SAAS,QAAAA,aAAY;;;ACAvB,IAAM,iBAAiC;AAAA,EAC5C,QAAQ;AACV;;;ACJA,SAAS,eAAe;AACxB,SAAS,MAAM,kBAAkB;AAEjC,SAAS,WAAW,QAAgB,UAA0B;AAC5D,QAAM,MAAM,QAAQ,IAAI,MAAM;AAC9B,MAAI,OAAO,WAAW,GAAG,EAAG,QAAO;AACnC,SAAO;AACT;AAEO,SAAS,eAAuB;AACrC,QAAM,OAAO,WAAW,mBAAmB,KAAK,QAAQ,GAAG,SAAS,CAAC;AACrE,SAAO,KAAK,MAAM,KAAK;AACzB;AAEO,SAAS,oBAA4B;AAC1C,SAAO,KAAK,aAAa,GAAG,aAAa;AAC3C;AAEO,SAAS,aAAqB;AACnC,QAAM,OAAO,WAAW,iBAAiB,KAAK,QAAQ,GAAG,UAAU,OAAO,CAAC;AAC3E,SAAO,KAAK,MAAM,KAAK;AACzB;AAEO,SAAS,cAAsB;AACpC,QAAM,OAAO,WAAW,kBAAkB,KAAK,QAAQ,GAAG,QAAQ,CAAC;AACnE,SAAO,KAAK,MAAM,KAAK;AACzB;;;AFlBA,IAAM,uBAA4C,oBAAI,IAAI;AAAA,EACxD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,oBAAoB,OAAsC;AACjE,SAAO,qBAAqB,IAAI,KAAK;AACvC;AAEO,SAAS,gBAAoC;AAClD,QAAM,SAA6B,CAAC;AAEpC,QAAM,MAAM,QAAQ,IAAI,SAAS;AACjC,MAAI,IAAK,QAAO,MAAM;AAEtB,QAAM,SAAS,QAAQ,IAAI,YAAY;AACvC,MAAI,QAAQ;AACV,QAAI,oBAAoB,MAAM,GAAG;AAC/B,aAAO,SAAS;AAAA,IAClB;AAAA,EACF;AAEA,QAAM,UAAU,QAAQ,IAAI,aAAa;AACzC,MAAI,QAAS,QAAO,UAAU;AAE9B,QAAM,iBAAiB,QAAQ,IAAI,qBAAqB;AACxD,MAAI,gBAAgB;AAClB,WAAO,OAAO,EAAE,eAAe;AAAA,EACjC;AAEA,QAAM,cAAc,QAAQ,IAAI,kBAAkB;AAClD,MAAI,YAAa,QAAO,cAAc;AAEtC,SAAO;AACT;AAEO,SAAS,eAAe,UAAuC;AACpE,MAAI,MAAM,YAAY,QAAQ,IAAI;AAGlC,SAAO,MAAM;AACX,UAAM,YAAYC,MAAK,KAAK,aAAa;AACzC,QAAI;AACF,eAAS,SAAS;AAClB,aAAO;AAAA,IACT,QAAQ;AAAA,IAER;AAEA,UAAM,SAAS,QAAQ,GAAG;AAC1B,QAAI,WAAW,IAAK;AACpB,UAAM;AAAA,EACR;AAEA,SAAO;AACT;AAEA,eAAsB,eAAe,UAAsC;AACzE,QAAM,UAAU,MAAM,SAAS,UAAU,OAAO;AAChD,QAAM,SAAS,KAAK,MAAM,OAAO;AAGjC,iBAAe,MAAM;AAGrB,MAAI,OAAO,WAAW,UAAa,CAAC,oBAAoB,OAAO,MAAM,GAAG;AACtE,UAAM,IAAI;AAAA,MACR,0BAA0B,OAAO,OAAO,MAAM,CAAC,QAAQ,QAAQ,qBAAqB,CAAC,GAAG,oBAAoB,EAAE,KAAK,IAAI,CAAC;AAAA,IAC1H;AAAA,EACF;AAEA,SAAO;AACT;AAEA,IAAM,iBAAiB,oBAAI,IAAI,CAAC,aAAa,eAAe,WAAW,CAAC;AAGxE,SAAS,eAAe,KAAoB;AAC1C,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU;AACrC,MAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,eAAW,QAAQ,IAAK,gBAAe,IAAI;AAC3C;AAAA,EACF;AACA,QAAM,SAAS;AACf,aAAW,OAAO,OAAO,KAAK,MAAM,GAAG;AACrC,QAAI,eAAe,IAAI,GAAG,GAAG;AAC3B,aAAO,OAAO,GAAG;AAAA,IACnB,WAAW,OAAO,OAAO,GAAG,MAAM,YAAY,OAAO,GAAG,MAAM,MAAM;AAClE,qBAAe,OAAO,GAAG,CAAC;AAAA,IAC5B;AAAA,EACF;AACF;AAEA,eAAsB,WACpB,WACyB;AAEzB,QAAM,SAAyB,EAAE,GAAG,eAAe;AAGnD,MAAI;AACF,UAAM,aAAa,MAAM,eAAe,kBAAkB,CAAC;AAC3D,WAAO,OAAO,QAAQ,eAAe,UAAU,CAAC;AAAA,EAClD,QAAQ;AAAA,EAER;AAGA,QAAM,oBAAoB,eAAe;AACzC,MAAI,mBAAmB;AACrB,QAAI;AACF,YAAM,gBAAgB,MAAM,eAAe,iBAAiB;AAC5D,aAAO,OAAO,QAAQ,eAAe,aAAa,CAAC;AACnD,aAAO,aAAa;AAAA,IACtB,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,QAAM,YAAY,cAAc;AAChC,SAAO,OAAO,QAAQ,eAAe,SAAS,CAAC;AAG/C,MAAI,WAAW;AACb,WAAO,OAAO,QAAQ,eAAe,SAAS,CAAC;AAAA,EACjD;AAGA,MAAI,OAAO,WAAW,OAAO,WAAW,OAAO,OAAO,GAAG;AACvD,UAAM,IAAI,OAAO,SAAS,OAAO,OAAO;AACxC,QAAI,EAAE,KAAM,QAAO,OAAO,EAAE;AAC5B,QAAI,EAAE,IAAK,QAAO,MAAM,EAAE;AAC1B,QAAI,EAAE,YAAa,QAAO,cAAc,EAAE;AAAA,EAC5C,WAAW,OAAO,WAAW,OAAO,YAAY,EAAE,OAAO,WAAW,OAAO,WAAW;AACpF,UAAM,YAAY,OAAO,KAAK,OAAO,QAAQ,EAAE,KAAK,IAAI;AACxD,UAAM,IAAI;AAAA,MACR,YAAY,OAAO,OAAO,oCAAoC,aAAa,QAAQ;AAAA,IACrF;AAAA,EACF;AAGA,MAAI,CAAC,oBAAoB,OAAO,MAAM,GAAG;AACvC,WAAO,SAAS,eAAe;AAAA,EACjC;AAEA,SAAO;AACT;AAEA,SAAS,eAAiC,KAAoB;AAC5D,QAAM,UAAsB,CAAC;AAC7B,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC9C,QAAI,UAAU,QAAW;AACvB,MAAC,QAAoC,GAAG,IAAI;AAAA,IAC9C;AAAA,EACF;AACA,SAAO;AACT;;;AGvKA,SAAS,OAAO,OAAO,YAAAC,WAAU,iBAAiB;AAClD,SAAS,WAAAC,UAAS,QAAAC,aAAY;AAK9B,eAAe,gBAAgB,UAAkB,SAAgC;AAC/E,QAAM,UAAU,UAAU,SAAS,EAAE,UAAU,SAAS,MAAM,IAAM,CAAC;AACrE,QAAM,MAAM,UAAU,GAAK,EAAE,MAAM,MAAM;AAAA,EAAC,CAAC;AAC7C;AAEA,eAAsB,eACpB,KACA,OACe;AACf,QAAM,aAAaC,MAAK,aAAa,GAAG,aAAa;AAErD,MAAI,WAAoC,CAAC;AACzC,MAAI;AACF,UAAM,UAAU,MAAMC,UAAS,YAAY,OAAO;AAClD,eAAW,KAAK,MAAM,OAAO;AAAA,EAC/B,QAAQ;AAAA,EAER;AAGA,QAAM,OAAO,IAAI,MAAM,GAAG;AAC1B,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,KAAK,SAAS,GAAG,KAAK;AACxC,UAAM,IAAI,KAAK,CAAC;AAChB,QAAI,OAAO,OAAO,CAAC,MAAM,YAAY,OAAO,CAAC,MAAM,MAAM;AACvD,aAAO,CAAC,IAAI,CAAC;AAAA,IACf;AACA,aAAS,OAAO,CAAC;AAAA,EACnB;AACA,SAAO,KAAK,KAAK,SAAS,CAAC,CAAE,IAAI;AAEjC,QAAM,MAAMC,SAAQ,UAAU,GAAG,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AACjE,QAAM,gBAAgB,YAAY,KAAK,UAAU,UAAU,MAAM,CAAC,IAAI,IAAI;AAC5E;AAEA,eAAsB,iBACpB,aACA,QACe;AACf,QAAM,aAAaF,MAAK,aAAa,GAAG,aAAa;AAErD,MAAI,WAAoC,CAAC;AACzC,MAAI;AACF,UAAM,UAAU,MAAMC,UAAS,YAAY,OAAO;AAClD,eAAW,KAAK,MAAM,OAAO;AAAA,EAC/B,QAAQ;AAAA,EAER;AAEA,MAAI,OAAO,SAAS,UAAU,MAAM,YAAY,SAAS,UAAU,MAAM,MAAM;AAC7E,aAAS,UAAU,IAAI,CAAC;AAAA,EAC1B;AACA,EAAC,SAAS,UAAU,EAA8B,WAAW,IAAI;AAEjE,QAAM,MAAMC,SAAQ,UAAU,GAAG,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AACjE,QAAM,gBAAgB,YAAY,KAAK,UAAU,UAAU,MAAM,CAAC,IAAI,IAAI;AAC5E;AAEA,eAAsB,cAAc,aAAuC;AACzE,QAAM,aAAaF,MAAK,aAAa,GAAG,aAAa;AAErD,MAAI,WAAoC,CAAC;AACzC,MAAI;AACF,UAAM,UAAU,MAAMC,UAAS,YAAY,OAAO;AAClD,eAAW,KAAK,MAAM,OAAO;AAAA,EAC/B,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,SAAS,UAAU;AACpC,MAAI,CAAC,YAAY,EAAE,eAAe,UAAW,QAAO;AAEpD,SAAO,SAAS,WAAW;AAC3B,QAAM,gBAAgB,YAAY,KAAK,UAAU,UAAU,MAAM,CAAC,IAAI,IAAI;AAC1E,SAAO;AACT;AAEA,eAAsB,eAAkC;AACtD,QAAM,aAAaD,MAAK,aAAa,GAAG,aAAa;AAErD,MAAI;AACF,UAAM,UAAU,MAAMC,UAAS,YAAY,OAAO;AAClD,UAAM,SAAS,KAAK,MAAM,OAAO;AACjC,UAAM,WAAW,OAAO,UAAU;AAClC,WAAO,WAAW,OAAO,KAAK,QAAQ,IAAI,CAAC;AAAA,EAC7C,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,eAAsB,cAAc,YAAmC;AACrE,QAAM,aAAaD,MAAK,aAAa,GAAG,aAAa;AAErD,MAAI,WAAoC,CAAC;AACzC,MAAI;AACF,UAAM,UAAU,MAAMC,UAAS,YAAY,OAAO;AAClD,eAAW,KAAK,MAAM,OAAO;AAAA,EAC/B,QAAQ;AAAA,EAER;AAEA,QAAM,WAAY,SAAS,iBAAiB,KAA8B,CAAC;AAC3E,MAAI,CAAC,SAAS,SAAS,UAAU,GAAG;AAClC,aAAS,KAAK,UAAU;AAAA,EAC1B;AACA,WAAS,iBAAiB,IAAI;AAE9B,QAAM,MAAMC,SAAQ,UAAU,GAAG,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AACjE,QAAM,gBAAgB,YAAY,KAAK,UAAU,UAAU,MAAM,CAAC,IAAI,IAAI;AAC5E;AAEA,eAAsB,qBAAqB,YAAsC;AAC/E,QAAM,aAAaF,MAAK,aAAa,GAAG,aAAa;AAErD,MAAI,WAAoC,CAAC;AACzC,MAAI;AACF,UAAM,UAAU,MAAMC,UAAS,YAAY,OAAO;AAClD,eAAW,KAAK,MAAM,OAAO;AAAA,EAC/B,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,QAAM,WAAY,SAAS,iBAAiB,KAA8B,CAAC;AAC3E,QAAM,QAAQ,SAAS,QAAQ,UAAU;AACzC,MAAI,UAAU,GAAI,QAAO;AAEzB,WAAS,OAAO,OAAO,CAAC;AACxB,WAAS,iBAAiB,IAAI;AAC9B,QAAM,gBAAgB,YAAY,KAAK,UAAU,UAAU,MAAM,CAAC,IAAI,IAAI;AAC1E,SAAO;AACT;AAEA,eAAsB,WAAW,QAAoC;AACnE,QAAM,YAAY,aAAa;AAC/B,QAAM,aAAaD,MAAK,WAAW,aAAa;AAEhD,QAAM,MAAM,WAAW,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AACvD,QAAM,gBAAgB,YAAY,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,IAAI;AAExE,SAAO;AACT;","names":["join","join","readFile","dirname","join","join","readFile","dirname"]}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@gpc-cli/config",
3
+ "version": "0.1.0",
4
+ "description": "Configuration loading and validation for GPC",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "keywords": [
18
+ "google-play",
19
+ "config"
20
+ ],
21
+ "license": "MIT",
22
+ "devDependencies": {
23
+ "@types/node": "^25.3.5"
24
+ },
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "dev": "tsup --watch",
28
+ "test": "vitest run",
29
+ "test:watch": "vitest",
30
+ "lint": "eslint src/",
31
+ "typecheck": "tsc --noEmit",
32
+ "clean": "rm -rf dist"
33
+ }
34
+ }