oc-chatgpt-multi-auth 5.4.8 → 5.4.9
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/README.md +6 -2
- package/config/README.md +2 -0
- package/package.json +1 -1
- package/scripts/install-opencode-codex-auth.js +249 -60
package/README.md
CHANGED
|
@@ -35,6 +35,10 @@ What the installer does:
|
|
|
35
35
|
- normalizes the plugin entry to `"oc-chatgpt-multi-auth"`
|
|
36
36
|
- clears the cached plugin copy so OpenCode reinstalls the latest package
|
|
37
37
|
|
|
38
|
+
By default, the installer now writes a full catalog config that includes both:
|
|
39
|
+
- modern base model entries such as `gpt-5.4` for `--variant` workflows
|
|
40
|
+
- explicit preset entries such as `gpt-5.4-high` so the shipped catalog is visible directly in model pickers
|
|
41
|
+
|
|
38
42
|
## Example Usage
|
|
39
43
|
|
|
40
44
|
```bash
|
|
@@ -96,10 +100,10 @@ See [Architecture](docs/development/ARCHITECTURE.md) for implementation details.
|
|
|
96
100
|
|
|
97
101
|
Use the quick-start path above for the fastest setup. For full setup, local development installs, legacy OpenCode support, and verification steps, see [Getting Started](docs/getting-started.md).
|
|
98
102
|
|
|
99
|
-
If you
|
|
103
|
+
If you prefer the compact variant-only config on OpenCode `v1.0.210+`, use:
|
|
100
104
|
|
|
101
105
|
```bash
|
|
102
|
-
npx -y oc-chatgpt-multi-auth@latest --
|
|
106
|
+
npx -y oc-chatgpt-multi-auth@latest --modern
|
|
103
107
|
```
|
|
104
108
|
|
|
105
109
|
## Configuration
|
package/config/README.md
CHANGED
|
@@ -9,6 +9,8 @@ This directory contains the official OpenCode config templates for the ChatGPT C
|
|
|
9
9
|
| [`opencode-modern.json`](./opencode-modern.json) | **v1.0.210+** | Variant-based config: 9 base models with 34 total presets |
|
|
10
10
|
| [`opencode-legacy.json`](./opencode-legacy.json) | **v1.0.209 and below** | Legacy explicit entries: 34 individual model definitions |
|
|
11
11
|
|
|
12
|
+
The installer currently uses a merged full-catalog mode by default so users get both the modern base entries and the explicit preset entries without having to hand-edit `opencode.json`.
|
|
13
|
+
|
|
12
14
|
## Quick pick
|
|
13
15
|
|
|
14
16
|
If your OpenCode version is v1.0.210 or newer:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oc-chatgpt-multi-auth",
|
|
3
|
-
"version": "5.4.
|
|
3
|
+
"version": "5.4.9",
|
|
4
4
|
"description": "OpenCode plugin for using ChatGPT Plus/Pro in GPT-5 and Codex workflows with OAuth login, multi-account rotation, and guided setup",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -1,55 +1,97 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
|
-
import {
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { copyFile, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
7
5
|
import { homedir } from "node:os";
|
|
6
|
+
import { dirname, join, resolve } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
8
|
|
|
9
9
|
const PLUGIN_NAME = "oc-chatgpt-multi-auth";
|
|
10
|
+
const WINDOWS_RENAME_RETRY_ATTEMPTS = 5;
|
|
11
|
+
const WINDOWS_RENAME_RETRY_BASE_DELAY_MS = 10;
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (args.has("--help") || args.has("-h")) {
|
|
13
|
+
function printHelp() {
|
|
14
14
|
console.log(`Usage: ${PLUGIN_NAME} [--modern|--legacy] [--dry-run] [--no-cache-clear]\n\n` +
|
|
15
15
|
"Default behavior:\n" +
|
|
16
16
|
" - Installs/updates global config at ~/.config/opencode/opencode.json\n" +
|
|
17
|
-
" - Uses
|
|
17
|
+
" - Uses full catalog config by default (9 base models + 34 explicit presets)\n" +
|
|
18
18
|
" - Ensures plugin is unpinned (latest)\n" +
|
|
19
19
|
" - Clears OpenCode plugin cache\n\n" +
|
|
20
20
|
"Options:\n" +
|
|
21
|
-
" --modern Force modern config (
|
|
22
|
-
" --legacy
|
|
21
|
+
" --modern Force compact modern config (9 base models + --variant presets)\n" +
|
|
22
|
+
" --legacy Force explicit legacy config (34 preset model entries)\n" +
|
|
23
23
|
" --dry-run Show actions without writing\n" +
|
|
24
24
|
" --no-cache-clear Skip clearing OpenCode cache\n"
|
|
25
25
|
);
|
|
26
|
-
process.exit(0);
|
|
27
26
|
}
|
|
28
27
|
|
|
29
|
-
const useLegacy = args.has("--legacy");
|
|
30
|
-
const useModern = args.has("--modern") || !useLegacy;
|
|
31
|
-
const dryRun = args.has("--dry-run");
|
|
32
|
-
const skipCacheClear = args.has("--no-cache-clear");
|
|
33
|
-
|
|
34
28
|
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
35
29
|
const repoRoot = resolve(scriptDir, "..");
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
"config",
|
|
39
|
-
useLegacy ? "opencode-legacy.json" : "opencode-modern.json"
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
const configDir = join(homedir(), ".config", "opencode");
|
|
43
|
-
const configPath = join(configDir, "opencode.json");
|
|
44
|
-
const cacheDir = join(homedir(), ".cache", "opencode");
|
|
45
|
-
const cacheNodeModules = join(cacheDir, "node_modules", PLUGIN_NAME);
|
|
46
|
-
const cacheBunLock = join(cacheDir, "bun.lock");
|
|
47
|
-
const cachePackageJson = join(cacheDir, "package.json");
|
|
30
|
+
const modernTemplatePath = join(repoRoot, "config", "opencode-modern.json");
|
|
31
|
+
const legacyTemplatePath = join(repoRoot, "config", "opencode-legacy.json");
|
|
48
32
|
|
|
49
33
|
function log(message) {
|
|
50
34
|
console.log(message);
|
|
51
35
|
}
|
|
52
36
|
|
|
37
|
+
function delay(ms) {
|
|
38
|
+
return new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isWindowsLockError(error) {
|
|
42
|
+
const code = error?.code;
|
|
43
|
+
return code === "EPERM" || code === "EBUSY";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function formatErrorForLog(error) {
|
|
47
|
+
if (error instanceof Error) {
|
|
48
|
+
return error.message;
|
|
49
|
+
}
|
|
50
|
+
return String(error);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function resolveHomeDirectory(env = process.env) {
|
|
54
|
+
return env.HOME || env.USERPROFILE || homedir();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function buildPaths(homeDir) {
|
|
58
|
+
const configDir = join(homeDir, ".config", "opencode");
|
|
59
|
+
const cacheDir = join(homeDir, ".cache", "opencode");
|
|
60
|
+
return {
|
|
61
|
+
configDir,
|
|
62
|
+
configPath: join(configDir, "opencode.json"),
|
|
63
|
+
cacheDir,
|
|
64
|
+
cacheNodeModules: join(cacheDir, "node_modules", PLUGIN_NAME),
|
|
65
|
+
cacheBunLock: join(cacheDir, "bun.lock"),
|
|
66
|
+
cachePackageJson: join(cacheDir, "package.json"),
|
|
67
|
+
modernTemplatePath,
|
|
68
|
+
legacyTemplatePath,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function parseCliArgs(argv = process.argv.slice(2)) {
|
|
73
|
+
const args = new Set(argv);
|
|
74
|
+
if (args.has("--help") || args.has("-h")) {
|
|
75
|
+
return {
|
|
76
|
+
wantsHelp: true,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const requestedModern = args.has("--modern");
|
|
81
|
+
const requestedLegacy = args.has("--legacy");
|
|
82
|
+
|
|
83
|
+
if (requestedModern && requestedLegacy) {
|
|
84
|
+
throw new Error("Choose only one of --modern or --legacy.");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
wantsHelp: false,
|
|
89
|
+
dryRun: args.has("--dry-run"),
|
|
90
|
+
skipCacheClear: args.has("--no-cache-clear"),
|
|
91
|
+
configMode: requestedModern ? "modern" : requestedLegacy ? "legacy" : "full",
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
53
95
|
function normalizePluginList(list) {
|
|
54
96
|
const entries = Array.isArray(list) ? list.filter(Boolean) : [];
|
|
55
97
|
const filtered = entries.filter((entry) => {
|
|
@@ -63,12 +105,112 @@ function formatJson(obj) {
|
|
|
63
105
|
return `${JSON.stringify(obj, null, 2)}\n`;
|
|
64
106
|
}
|
|
65
107
|
|
|
108
|
+
function mergeFullTemplate(modernTemplate, legacyTemplate) {
|
|
109
|
+
const modernModels = modernTemplate.provider?.openai?.models ?? {};
|
|
110
|
+
const legacyModels = legacyTemplate.provider?.openai?.models ?? {};
|
|
111
|
+
const overlappingKeys = Object.keys(modernModels).filter((key) => Object.hasOwn(legacyModels, key));
|
|
112
|
+
|
|
113
|
+
if (overlappingKeys.length > 0) {
|
|
114
|
+
throw new Error(`Full config template collision for model keys: ${overlappingKeys.join(", ")}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
...modernTemplate,
|
|
119
|
+
provider: {
|
|
120
|
+
...(modernTemplate.provider ?? {}),
|
|
121
|
+
openai: {
|
|
122
|
+
...(modernTemplate.provider?.openai ?? {}),
|
|
123
|
+
models: {
|
|
124
|
+
...modernModels,
|
|
125
|
+
...legacyModels,
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
66
132
|
async function readJson(filePath) {
|
|
67
133
|
const content = await readFile(filePath, "utf-8");
|
|
68
134
|
return JSON.parse(content);
|
|
69
135
|
}
|
|
70
136
|
|
|
71
|
-
async function
|
|
137
|
+
async function renameWithWindowsRetry(sourcePath, destinationPath) {
|
|
138
|
+
let lastError = null;
|
|
139
|
+
|
|
140
|
+
for (let attempt = 0; attempt < WINDOWS_RENAME_RETRY_ATTEMPTS; attempt += 1) {
|
|
141
|
+
try {
|
|
142
|
+
await rename(sourcePath, destinationPath);
|
|
143
|
+
return;
|
|
144
|
+
} catch (error) {
|
|
145
|
+
if (isWindowsLockError(error)) {
|
|
146
|
+
// Windows desktop installs often see brief AV/indexer locks on config
|
|
147
|
+
// files, so retry the atomic rename before surfacing a hard failure.
|
|
148
|
+
lastError = error;
|
|
149
|
+
await delay(WINDOWS_RENAME_RETRY_BASE_DELAY_MS * 2 ** attempt);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (lastError) {
|
|
157
|
+
throw lastError;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function writeFileAtomic(filePath, content) {
|
|
162
|
+
const uniqueSuffix = `${Date.now()}.${Math.random().toString(36).slice(2, 8)}`;
|
|
163
|
+
const tempPath = `${filePath}.${uniqueSuffix}.tmp`;
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
167
|
+
await writeFile(tempPath, content, { encoding: "utf-8", mode: 0o600 });
|
|
168
|
+
await renameWithWindowsRetry(tempPath, filePath);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
await rm(tempPath, { force: true }).catch(() => {});
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function loadTemplate(mode, paths) {
|
|
176
|
+
if (mode === "modern") {
|
|
177
|
+
return readJson(paths.modernTemplatePath);
|
|
178
|
+
}
|
|
179
|
+
if (mode === "legacy") {
|
|
180
|
+
return readJson(paths.legacyTemplatePath);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const [modernTemplate, legacyTemplate] = await Promise.all([
|
|
184
|
+
readJson(paths.modernTemplatePath),
|
|
185
|
+
readJson(paths.legacyTemplatePath),
|
|
186
|
+
]);
|
|
187
|
+
|
|
188
|
+
return mergeFullTemplate(modernTemplate, legacyTemplate);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function copyFileWithWindowsRetry(sourcePath, destinationPath) {
|
|
192
|
+
let lastError = null;
|
|
193
|
+
|
|
194
|
+
for (let attempt = 0; attempt < WINDOWS_RENAME_RETRY_ATTEMPTS; attempt += 1) {
|
|
195
|
+
try {
|
|
196
|
+
await copyFile(sourcePath, destinationPath);
|
|
197
|
+
return;
|
|
198
|
+
} catch (error) {
|
|
199
|
+
if (isWindowsLockError(error)) {
|
|
200
|
+
lastError = error;
|
|
201
|
+
await delay(WINDOWS_RENAME_RETRY_BASE_DELAY_MS * 2 ** attempt);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (lastError) {
|
|
209
|
+
throw lastError;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function backupConfig(sourcePath, dryRun) {
|
|
72
214
|
const timestamp = new Date()
|
|
73
215
|
.toISOString()
|
|
74
216
|
.replace(/[:.]/g, "-")
|
|
@@ -76,21 +218,21 @@ async function backupConfig(sourcePath) {
|
|
|
76
218
|
.replace("Z", "");
|
|
77
219
|
const backupPath = `${sourcePath}.bak-${timestamp}`;
|
|
78
220
|
if (!dryRun) {
|
|
79
|
-
await
|
|
221
|
+
await copyFileWithWindowsRetry(sourcePath, backupPath);
|
|
80
222
|
}
|
|
81
223
|
return backupPath;
|
|
82
224
|
}
|
|
83
225
|
|
|
84
|
-
async function removePluginFromCachePackage() {
|
|
85
|
-
if (!existsSync(cachePackageJson)) {
|
|
226
|
+
async function removePluginFromCachePackage(paths, dryRun) {
|
|
227
|
+
if (!existsSync(paths.cachePackageJson)) {
|
|
86
228
|
return;
|
|
87
229
|
}
|
|
88
230
|
|
|
89
231
|
let cacheData;
|
|
90
232
|
try {
|
|
91
|
-
cacheData = await readJson(cachePackageJson);
|
|
233
|
+
cacheData = await readJson(paths.cachePackageJson);
|
|
92
234
|
} catch (error) {
|
|
93
|
-
log(`Warning: Could not parse ${cachePackageJson} (${error}). Skipping.`);
|
|
235
|
+
log(`Warning: Could not parse ${paths.cachePackageJson} (${formatErrorForLog(error)}). Skipping.`);
|
|
94
236
|
return;
|
|
95
237
|
}
|
|
96
238
|
|
|
@@ -115,45 +257,63 @@ async function removePluginFromCachePackage() {
|
|
|
115
257
|
}
|
|
116
258
|
|
|
117
259
|
if (dryRun) {
|
|
118
|
-
log(`[dry-run] Would update ${cachePackageJson} to remove ${PLUGIN_NAME}`);
|
|
260
|
+
log(`[dry-run] Would update ${paths.cachePackageJson} to remove ${PLUGIN_NAME}`);
|
|
119
261
|
return;
|
|
120
262
|
}
|
|
121
263
|
|
|
122
|
-
await
|
|
264
|
+
await writeFileAtomic(paths.cachePackageJson, formatJson(cacheData));
|
|
123
265
|
}
|
|
124
266
|
|
|
125
|
-
async function clearCache() {
|
|
267
|
+
async function clearCache(paths, dryRun, skipCacheClear) {
|
|
126
268
|
if (skipCacheClear) {
|
|
127
269
|
log("Skipping cache clear (--no-cache-clear).");
|
|
270
|
+
await removePluginFromCachePackage(paths, dryRun);
|
|
128
271
|
return;
|
|
129
272
|
}
|
|
130
273
|
|
|
131
274
|
if (dryRun) {
|
|
132
|
-
log(`[dry-run] Would remove ${cacheNodeModules}`);
|
|
133
|
-
log(`[dry-run] Would remove ${cacheBunLock}`);
|
|
275
|
+
log(`[dry-run] Would remove ${paths.cacheNodeModules}`);
|
|
276
|
+
log(`[dry-run] Would remove ${paths.cacheBunLock}`);
|
|
134
277
|
} else {
|
|
135
|
-
await rm(cacheNodeModules, { recursive: true, force: true });
|
|
136
|
-
await rm(cacheBunLock, { force: true });
|
|
278
|
+
await rm(paths.cacheNodeModules, { recursive: true, force: true });
|
|
279
|
+
await rm(paths.cacheBunLock, { force: true });
|
|
137
280
|
}
|
|
138
281
|
|
|
139
|
-
await removePluginFromCachePackage();
|
|
282
|
+
await removePluginFromCachePackage(paths, dryRun);
|
|
140
283
|
}
|
|
141
284
|
|
|
142
|
-
async function
|
|
143
|
-
|
|
144
|
-
|
|
285
|
+
export async function runInstaller(argv = process.argv.slice(2), options = {}) {
|
|
286
|
+
const parsed = parseCliArgs(argv);
|
|
287
|
+
if (parsed.wantsHelp) {
|
|
288
|
+
printHelp();
|
|
289
|
+
return { exitCode: 0, action: "help" };
|
|
145
290
|
}
|
|
146
291
|
|
|
147
|
-
const
|
|
292
|
+
const { env = process.env } = options;
|
|
293
|
+
const { configMode, dryRun, skipCacheClear } = parsed;
|
|
294
|
+
const paths = buildPaths(resolveHomeDirectory(env));
|
|
295
|
+
const requiredTemplatePaths = configMode === "modern"
|
|
296
|
+
? [paths.modernTemplatePath]
|
|
297
|
+
: configMode === "legacy"
|
|
298
|
+
? [paths.legacyTemplatePath]
|
|
299
|
+
: [paths.modernTemplatePath, paths.legacyTemplatePath];
|
|
300
|
+
|
|
301
|
+
for (const templatePath of requiredTemplatePaths) {
|
|
302
|
+
if (!existsSync(templatePath)) {
|
|
303
|
+
throw new Error(`Config template not found at ${templatePath}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const template = await loadTemplate(configMode, paths);
|
|
148
308
|
template.plugin = [PLUGIN_NAME];
|
|
149
309
|
|
|
150
310
|
let nextConfig = template;
|
|
151
|
-
if (existsSync(configPath)) {
|
|
152
|
-
const backupPath = await backupConfig(configPath);
|
|
311
|
+
if (existsSync(paths.configPath)) {
|
|
312
|
+
const backupPath = await backupConfig(paths.configPath, dryRun);
|
|
153
313
|
log(`${dryRun ? "[dry-run] Would create backup" : "Backup created"}: ${backupPath}`);
|
|
154
314
|
|
|
155
315
|
try {
|
|
156
|
-
const existing = await readJson(configPath);
|
|
316
|
+
const existing = await readJson(paths.configPath);
|
|
157
317
|
const merged = { ...existing };
|
|
158
318
|
merged.plugin = normalizePluginList(existing.plugin);
|
|
159
319
|
const provider = (existing.provider && typeof existing.provider === "object")
|
|
@@ -163,7 +323,9 @@ async function main() {
|
|
|
163
323
|
merged.provider = provider;
|
|
164
324
|
nextConfig = merged;
|
|
165
325
|
} catch (error) {
|
|
166
|
-
log
|
|
326
|
+
// Only log the filesystem/parser message. Never echo config bodies because
|
|
327
|
+
// opencode.json may carry provider credentials or tokens.
|
|
328
|
+
log(`Warning: Could not parse existing config (${formatErrorForLog(error)}). Replacing with template.`);
|
|
167
329
|
nextConfig = template;
|
|
168
330
|
}
|
|
169
331
|
} else {
|
|
@@ -171,23 +333,50 @@ async function main() {
|
|
|
171
333
|
}
|
|
172
334
|
|
|
173
335
|
if (dryRun) {
|
|
174
|
-
log(`[dry-run] Would write ${configPath} using ${
|
|
336
|
+
log(`[dry-run] Would write ${paths.configPath} using ${configMode} config`);
|
|
175
337
|
} else {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
338
|
+
// Persist through a temp file plus rename so Windows AV/file locks do not
|
|
339
|
+
// leave a truncated opencode.json behind during installer updates.
|
|
340
|
+
await writeFileAtomic(paths.configPath, formatJson(nextConfig));
|
|
341
|
+
log(`Wrote ${paths.configPath} (${configMode} config)`);
|
|
179
342
|
}
|
|
180
343
|
|
|
181
|
-
await clearCache();
|
|
344
|
+
await clearCache(paths, dryRun, skipCacheClear);
|
|
182
345
|
|
|
183
346
|
log("\nDone. Restart OpenCode to (re)install the plugin.");
|
|
184
347
|
log("Example: opencode");
|
|
185
|
-
if (
|
|
186
|
-
log("Note:
|
|
348
|
+
if (configMode === "modern") {
|
|
349
|
+
log("Note: Modern config intentionally shows 9 base model entries; use --variant to access all 34 shipped presets.");
|
|
350
|
+
}
|
|
351
|
+
if (configMode === "legacy") {
|
|
352
|
+
log("Note: Legacy config writes 34 explicit preset entries and is also safe for older OpenCode versions.");
|
|
353
|
+
}
|
|
354
|
+
if (configMode === "full") {
|
|
355
|
+
log("Note: Full config installs both modern base models and explicit preset entries so the full shipped catalog is visible by default.");
|
|
187
356
|
}
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
exitCode: 0,
|
|
360
|
+
action: "install",
|
|
361
|
+
configMode,
|
|
362
|
+
configPath: paths.configPath,
|
|
363
|
+
};
|
|
188
364
|
}
|
|
189
365
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
366
|
+
export const __test = {
|
|
367
|
+
buildPaths,
|
|
368
|
+
backupConfig,
|
|
369
|
+
copyFileWithWindowsRetry,
|
|
370
|
+
mergeFullTemplate,
|
|
371
|
+
parseCliArgs,
|
|
372
|
+
writeFileAtomic,
|
|
373
|
+
renameWithWindowsRetry,
|
|
374
|
+
resolveHomeDirectory,
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
378
|
+
runInstaller().catch((error) => {
|
|
379
|
+
console.error(`Installer failed: ${formatErrorForLog(error)}`);
|
|
380
|
+
process.exit(1);
|
|
381
|
+
});
|
|
382
|
+
}
|