@leeguoo/wrangler-accounts 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 +21 -0
- package/README.md +75 -0
- package/bin/wrangler-accounts.js +353 -0
- package/package.json +32 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 leeguoo
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# wrangler-accounts
|
|
2
|
+
|
|
3
|
+
Local CLI to manage multiple Cloudflare Wrangler login profiles by saving and swapping the Wrangler config file.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
- Save the current Wrangler config as a named profile
|
|
8
|
+
- Switch between profiles by copying a saved config into place
|
|
9
|
+
- List or inspect status (active profile and matching profile)
|
|
10
|
+
- Optional automatic backups when switching
|
|
11
|
+
|
|
12
|
+
## Install (npm)
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm i -g @leeguoo/wrangler-accounts
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Install (local)
|
|
19
|
+
|
|
20
|
+
From this repo:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm link
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or run directly:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
node bin/wrangler-accounts.js <command>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
wrangler-accounts list
|
|
36
|
+
wrangler-accounts status
|
|
37
|
+
wrangler-accounts save work
|
|
38
|
+
wrangler-accounts use personal
|
|
39
|
+
wrangler-accounts remove old
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Options
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
-c, --config <path> Wrangler config path
|
|
46
|
+
-p, --profiles <path> Profiles directory
|
|
47
|
+
--json JSON output for list/status
|
|
48
|
+
-f, --force Overwrite existing profile on save
|
|
49
|
+
--backup Backup current config on use (default)
|
|
50
|
+
--no-backup Disable backup on use
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Environment variables
|
|
54
|
+
|
|
55
|
+
- WRANGLER_CONFIG_PATH
|
|
56
|
+
- WRANGLER_ACCOUNTS_DIR
|
|
57
|
+
- XDG_CONFIG_HOME
|
|
58
|
+
|
|
59
|
+
## Defaults
|
|
60
|
+
|
|
61
|
+
If you do not specify a config path, the CLI checks for these and uses the first existing path:
|
|
62
|
+
|
|
63
|
+
- ~/.wrangler/config/default.toml
|
|
64
|
+
- ~/.config/.wrangler/config/default.toml
|
|
65
|
+
- ~/.config/wrangler/config/default.toml
|
|
66
|
+
|
|
67
|
+
The profiles directory defaults to:
|
|
68
|
+
|
|
69
|
+
- $XDG_CONFIG_HOME/wrangler-accounts (if set)
|
|
70
|
+
- ~/.config/wrangler-accounts
|
|
71
|
+
|
|
72
|
+
## Notes
|
|
73
|
+
|
|
74
|
+
- Profile names accept only letters, numbers, dot, underscore, and dash.
|
|
75
|
+
- On `use`, the current config is backed up into `__backup-YYYYMMDD-HHMMSS` unless you pass `--no-backup`.
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const os = require("os");
|
|
7
|
+
const crypto = require("crypto");
|
|
8
|
+
|
|
9
|
+
function die(message) {
|
|
10
|
+
console.error(`Error: ${message}`);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function printHelp(exitCode = 0) {
|
|
15
|
+
const text = `wrangler-accounts - manage multiple Wrangler login profiles
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
wrangler-accounts <command> [options]
|
|
19
|
+
|
|
20
|
+
Commands:
|
|
21
|
+
list
|
|
22
|
+
status
|
|
23
|
+
save <name>
|
|
24
|
+
use <name>
|
|
25
|
+
remove <name>
|
|
26
|
+
|
|
27
|
+
Options:
|
|
28
|
+
-c, --config <path> Wrangler config path
|
|
29
|
+
-p, --profiles <path> Profiles directory
|
|
30
|
+
--json JSON output for list/status
|
|
31
|
+
-f, --force Overwrite existing profile on save
|
|
32
|
+
--backup Backup current config on use (default)
|
|
33
|
+
--no-backup Disable backup on use
|
|
34
|
+
-h, --help Show help
|
|
35
|
+
|
|
36
|
+
Env:
|
|
37
|
+
WRANGLER_CONFIG_PATH
|
|
38
|
+
WRANGLER_ACCOUNTS_DIR
|
|
39
|
+
XDG_CONFIG_HOME
|
|
40
|
+
|
|
41
|
+
Examples:
|
|
42
|
+
wrangler-accounts save work
|
|
43
|
+
wrangler-accounts use personal
|
|
44
|
+
`;
|
|
45
|
+
console.log(text);
|
|
46
|
+
process.exit(exitCode);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function expandHome(p) {
|
|
50
|
+
if (!p) return p;
|
|
51
|
+
if (p === "~") return os.homedir();
|
|
52
|
+
if (p.startsWith("~/")) return path.join(os.homedir(), p.slice(2));
|
|
53
|
+
return p;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function resolvePath(p) {
|
|
57
|
+
if (!p) return p;
|
|
58
|
+
return path.resolve(expandHome(p));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function parseArgs(argv) {
|
|
62
|
+
const opts = {
|
|
63
|
+
json: false,
|
|
64
|
+
force: false,
|
|
65
|
+
backup: true,
|
|
66
|
+
};
|
|
67
|
+
const rest = [];
|
|
68
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
69
|
+
const arg = argv[i];
|
|
70
|
+
if (arg === "--help" || arg === "-h") {
|
|
71
|
+
opts.help = true;
|
|
72
|
+
} else if (arg === "--json") {
|
|
73
|
+
opts.json = true;
|
|
74
|
+
} else if (arg === "--force" || arg === "-f") {
|
|
75
|
+
opts.force = true;
|
|
76
|
+
} else if (arg === "--backup") {
|
|
77
|
+
opts.backup = true;
|
|
78
|
+
} else if (arg === "--no-backup") {
|
|
79
|
+
opts.backup = false;
|
|
80
|
+
} else if (arg === "--config" || arg === "-c") {
|
|
81
|
+
opts.config = argv[i + 1];
|
|
82
|
+
if (!opts.config) die("Missing value for --config");
|
|
83
|
+
i += 1;
|
|
84
|
+
} else if (arg === "--profiles" || arg === "-p") {
|
|
85
|
+
opts.profiles = argv[i + 1];
|
|
86
|
+
if (!opts.profiles) die("Missing value for --profiles");
|
|
87
|
+
i += 1;
|
|
88
|
+
} else {
|
|
89
|
+
rest.push(arg);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return { opts, rest };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function detectConfigPath(cliPath) {
|
|
96
|
+
if (cliPath) return resolvePath(cliPath);
|
|
97
|
+
if (process.env.WRANGLER_CONFIG_PATH) {
|
|
98
|
+
return resolvePath(process.env.WRANGLER_CONFIG_PATH);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const home = os.homedir();
|
|
102
|
+
const candidates = [
|
|
103
|
+
path.join(home, ".wrangler", "config", "default.toml"),
|
|
104
|
+
path.join(home, ".config", ".wrangler", "config", "default.toml"),
|
|
105
|
+
path.join(home, ".config", "wrangler", "config", "default.toml"),
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
for (const candidate of candidates) {
|
|
109
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return candidates[0];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function detectProfilesDir(cliPath) {
|
|
116
|
+
if (cliPath) return resolvePath(cliPath);
|
|
117
|
+
if (process.env.WRANGLER_ACCOUNTS_DIR) {
|
|
118
|
+
return resolvePath(process.env.WRANGLER_ACCOUNTS_DIR);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
122
|
+
if (xdg) return path.join(resolvePath(xdg), "wrangler-accounts");
|
|
123
|
+
|
|
124
|
+
return path.join(os.homedir(), ".config", "wrangler-accounts");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function ensureDir(dirPath) {
|
|
128
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function isValidName(name) {
|
|
132
|
+
return /^[A-Za-z0-9._-]+$/.test(name);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function listProfiles(profilesDir) {
|
|
136
|
+
if (!fs.existsSync(profilesDir)) return [];
|
|
137
|
+
const entries = fs.readdirSync(profilesDir, { withFileTypes: true });
|
|
138
|
+
return entries
|
|
139
|
+
.filter((entry) => entry.isDirectory())
|
|
140
|
+
.map((entry) => entry.name)
|
|
141
|
+
.filter((name) => fs.existsSync(path.join(profilesDir, name, "config.toml")))
|
|
142
|
+
.sort();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function fileHash(filePath) {
|
|
146
|
+
const data = fs.readFileSync(filePath);
|
|
147
|
+
return crypto.createHash("sha256").update(data).digest("hex");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function filesEqual(pathA, pathB) {
|
|
151
|
+
if (!fs.existsSync(pathA) || !fs.existsSync(pathB)) return false;
|
|
152
|
+
const statA = fs.statSync(pathA);
|
|
153
|
+
const statB = fs.statSync(pathB);
|
|
154
|
+
if (statA.size !== statB.size) return false;
|
|
155
|
+
return fileHash(pathA) === fileHash(pathB);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function writeMeta(profileDir, name, sourcePath) {
|
|
159
|
+
const configPath = path.join(profileDir, "config.toml");
|
|
160
|
+
const stat = fs.statSync(configPath);
|
|
161
|
+
const meta = {
|
|
162
|
+
name,
|
|
163
|
+
savedAt: new Date().toISOString(),
|
|
164
|
+
sourcePath,
|
|
165
|
+
bytes: stat.size,
|
|
166
|
+
sha256: fileHash(configPath),
|
|
167
|
+
};
|
|
168
|
+
fs.writeFileSync(path.join(profileDir, "meta.json"), JSON.stringify(meta, null, 2));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function setActiveProfile(profilesDir, name) {
|
|
172
|
+
ensureDir(profilesDir);
|
|
173
|
+
fs.writeFileSync(path.join(profilesDir, "active"), `${name}\n`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function getActiveProfile(profilesDir) {
|
|
177
|
+
const activePath = path.join(profilesDir, "active");
|
|
178
|
+
if (!fs.existsSync(activePath)) return null;
|
|
179
|
+
const value = fs.readFileSync(activePath, "utf8").trim();
|
|
180
|
+
return value.length ? value : null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function timestampForFile() {
|
|
184
|
+
const now = new Date();
|
|
185
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
186
|
+
return [
|
|
187
|
+
now.getFullYear(),
|
|
188
|
+
pad(now.getMonth() + 1),
|
|
189
|
+
pad(now.getDate()),
|
|
190
|
+
"-",
|
|
191
|
+
pad(now.getHours()),
|
|
192
|
+
pad(now.getMinutes()),
|
|
193
|
+
pad(now.getSeconds()),
|
|
194
|
+
].join("");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function backupCurrentConfig(configPath, profilesDir) {
|
|
198
|
+
const backupName = `__backup-${timestampForFile()}`;
|
|
199
|
+
const backupDir = path.join(profilesDir, backupName);
|
|
200
|
+
ensureDir(backupDir);
|
|
201
|
+
fs.copyFileSync(configPath, path.join(backupDir, "config.toml"));
|
|
202
|
+
writeMeta(backupDir, backupName, configPath);
|
|
203
|
+
return backupName;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function findMatchingProfile(profilesDir, configPath) {
|
|
207
|
+
if (!fs.existsSync(configPath)) return null;
|
|
208
|
+
const configHash = fileHash(configPath);
|
|
209
|
+
const profiles = listProfiles(profilesDir);
|
|
210
|
+
for (const name of profiles) {
|
|
211
|
+
const profileConfig = path.join(profilesDir, name, "config.toml");
|
|
212
|
+
if (fileHash(profileConfig) === configHash) return name;
|
|
213
|
+
}
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function saveProfile(name, configPath, profilesDir, force) {
|
|
218
|
+
if (!isValidName(name)) {
|
|
219
|
+
die(`Invalid profile name: ${name}`);
|
|
220
|
+
}
|
|
221
|
+
if (!fs.existsSync(configPath)) {
|
|
222
|
+
die(`Config file not found: ${configPath}`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const profileDir = path.join(profilesDir, name);
|
|
226
|
+
if (fs.existsSync(profileDir) && !force) {
|
|
227
|
+
die(`Profile exists: ${name} (use --force to overwrite)`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
ensureDir(profileDir);
|
|
231
|
+
fs.copyFileSync(configPath, path.join(profileDir, "config.toml"));
|
|
232
|
+
writeMeta(profileDir, name, configPath);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function useProfile(name, configPath, profilesDir, backup) {
|
|
236
|
+
if (!isValidName(name)) {
|
|
237
|
+
die(`Invalid profile name: ${name}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const profileDir = path.join(profilesDir, name);
|
|
241
|
+
const profileConfig = path.join(profileDir, "config.toml");
|
|
242
|
+
if (!fs.existsSync(profileConfig)) {
|
|
243
|
+
die(`Profile not found: ${name}`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let backupName = null;
|
|
247
|
+
if (backup && fs.existsSync(configPath) && !filesEqual(configPath, profileConfig)) {
|
|
248
|
+
backupName = backupCurrentConfig(configPath, profilesDir);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
ensureDir(path.dirname(configPath));
|
|
252
|
+
fs.copyFileSync(profileConfig, configPath);
|
|
253
|
+
setActiveProfile(profilesDir, name);
|
|
254
|
+
|
|
255
|
+
return backupName;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function removeProfile(name, profilesDir) {
|
|
259
|
+
if (!isValidName(name)) {
|
|
260
|
+
die(`Invalid profile name: ${name}`);
|
|
261
|
+
}
|
|
262
|
+
const profileDir = path.join(profilesDir, name);
|
|
263
|
+
if (!fs.existsSync(profileDir)) {
|
|
264
|
+
die(`Profile not found: ${name}`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
fs.rmSync(profileDir, { recursive: true, force: true });
|
|
268
|
+
|
|
269
|
+
const active = getActiveProfile(profilesDir);
|
|
270
|
+
if (active === name) {
|
|
271
|
+
const activePath = path.join(profilesDir, "active");
|
|
272
|
+
if (fs.existsSync(activePath)) fs.unlinkSync(activePath);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function main() {
|
|
277
|
+
const { opts, rest } = parseArgs(process.argv.slice(2));
|
|
278
|
+
if (opts.help) printHelp(0);
|
|
279
|
+
|
|
280
|
+
const command = rest[0];
|
|
281
|
+
if (!command) printHelp(1);
|
|
282
|
+
|
|
283
|
+
const configPath = detectConfigPath(opts.config);
|
|
284
|
+
const profilesDir = detectProfilesDir(opts.profiles);
|
|
285
|
+
|
|
286
|
+
if (command === "list") {
|
|
287
|
+
const profiles = listProfiles(profilesDir);
|
|
288
|
+
if (opts.json) {
|
|
289
|
+
console.log(JSON.stringify(profiles, null, 2));
|
|
290
|
+
} else if (profiles.length === 0) {
|
|
291
|
+
console.log("No profiles found.");
|
|
292
|
+
} else {
|
|
293
|
+
console.log(profiles.join("\n"));
|
|
294
|
+
}
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (command === "status") {
|
|
299
|
+
const profiles = listProfiles(profilesDir);
|
|
300
|
+
const active = getActiveProfile(profilesDir);
|
|
301
|
+
const match = findMatchingProfile(profilesDir, configPath);
|
|
302
|
+
const payload = {
|
|
303
|
+
configPath,
|
|
304
|
+
configExists: fs.existsSync(configPath),
|
|
305
|
+
profilesDir,
|
|
306
|
+
profileCount: profiles.length,
|
|
307
|
+
profiles,
|
|
308
|
+
activeProfile: active,
|
|
309
|
+
matchingProfile: match,
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
if (opts.json) {
|
|
313
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
314
|
+
} else {
|
|
315
|
+
console.log(`Config: ${payload.configPath} (${payload.configExists ? "exists" : "missing"})`);
|
|
316
|
+
console.log(`Profiles: ${payload.profilesDir} (${payload.profileCount})`);
|
|
317
|
+
console.log(`Active: ${payload.activeProfile || "-"}`);
|
|
318
|
+
console.log(`Match: ${payload.matchingProfile || "-"}`);
|
|
319
|
+
}
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (command === "save") {
|
|
324
|
+
const name = rest[1];
|
|
325
|
+
if (!name) die("Missing profile name for save");
|
|
326
|
+
ensureDir(profilesDir);
|
|
327
|
+
saveProfile(name, configPath, profilesDir, opts.force);
|
|
328
|
+
console.log(`Saved profile '${name}' from ${configPath}`);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (command === "use") {
|
|
333
|
+
const name = rest[1];
|
|
334
|
+
if (!name) die("Missing profile name for use");
|
|
335
|
+
ensureDir(profilesDir);
|
|
336
|
+
const backupName = useProfile(name, configPath, profilesDir, opts.backup);
|
|
337
|
+
const backupNote = backupName ? ` (backup: ${backupName})` : "";
|
|
338
|
+
console.log(`Switched to profile '${name}'${backupNote}`);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (command === "remove") {
|
|
343
|
+
const name = rest[1];
|
|
344
|
+
if (!name) die("Missing profile name for remove");
|
|
345
|
+
removeProfile(name, profilesDir);
|
|
346
|
+
console.log(`Removed profile '${name}'`);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
die(`Unknown command: ${command}`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@leeguoo/wrangler-accounts",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Local CLI to manage multiple Cloudflare Wrangler login profiles.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"bin": {
|
|
7
|
+
"wrangler-accounts": "bin/wrangler-accounts.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin"
|
|
11
|
+
],
|
|
12
|
+
"keywords": [
|
|
13
|
+
"cloudflare",
|
|
14
|
+
"wrangler",
|
|
15
|
+
"accounts",
|
|
16
|
+
"cli"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/leeguooooo/wrangler-accounts.git"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/leeguooooo/wrangler-accounts/issues"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/leeguooooo/wrangler-accounts#readme",
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=16"
|
|
31
|
+
}
|
|
32
|
+
}
|