@cardstack/boxel-cli 0.0.1 → 0.1.1
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 +124 -0
- package/api.ts +3 -0
- package/bin/boxel.js +15 -0
- package/dist/index.js +107 -66
- package/package.json +35 -26
- package/src/build-program.ts +91 -0
- package/src/commands/file/delete.ts +110 -0
- package/src/commands/file/index.ts +20 -0
- package/src/commands/file/lint.ts +235 -0
- package/src/commands/file/list.ts +121 -0
- package/src/commands/file/read.ts +113 -0
- package/src/commands/file/touch.ts +222 -0
- package/src/commands/file/write.ts +152 -0
- package/src/commands/profile.ts +199 -106
- package/src/commands/read-transpiled.ts +120 -0
- package/src/commands/realm/cancel-indexing.ts +113 -0
- package/src/commands/realm/create.ts +1 -4
- package/src/commands/realm/history.ts +388 -0
- package/src/commands/realm/index.ts +12 -0
- package/src/commands/realm/list.ts +156 -0
- package/src/commands/realm/pull.ts +51 -17
- package/src/commands/realm/push.ts +79 -27
- package/src/commands/realm/remove.ts +281 -0
- package/src/commands/realm/sync.ts +160 -60
- package/src/commands/realm/wait-for-ready.ts +120 -0
- package/src/commands/realm/watch.ts +626 -0
- package/src/commands/run-command.ts +4 -3
- package/src/commands/search.ts +160 -0
- package/src/index.ts +16 -38
- package/src/lib/auth-resolver.ts +58 -0
- package/src/lib/auth.ts +56 -12
- package/src/lib/boxel-cli-client.ts +146 -279
- package/src/lib/cli-log.ts +132 -0
- package/src/lib/colors.ts +14 -9
- package/src/lib/find-checkpoint.ts +65 -0
- package/src/lib/profile-manager.ts +49 -4
- package/src/lib/prompt.ts +133 -0
- package/src/lib/realm-authenticator.ts +12 -0
- package/src/lib/realm-sync-base.ts +122 -16
- package/src/lib/seed-auth.ts +214 -0
- package/src/lib/watch-lock.ts +81 -0
- package/LICENSE +0 -21
|
@@ -8,10 +8,10 @@ import {
|
|
|
8
8
|
CheckpointManager,
|
|
9
9
|
type CheckpointChange,
|
|
10
10
|
} from '../../lib/checkpoint-manager';
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from '../../lib/
|
|
11
|
+
import type { ProfileManager } from '../../lib/profile-manager';
|
|
12
|
+
import type { RealmAuthenticator } from '../../lib/realm-authenticator';
|
|
13
|
+
import { resolveRealmAuthenticator } from '../../lib/auth-resolver';
|
|
14
|
+
import { resolveRealmSecretSeed } from '../../lib/prompt';
|
|
15
15
|
import {
|
|
16
16
|
type SyncManifest,
|
|
17
17
|
computeFileHash,
|
|
@@ -25,14 +25,19 @@ interface PushOptions extends SyncOptions {
|
|
|
25
25
|
force?: boolean;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
// Fresh realms always include these server-managed cards even when the local
|
|
29
|
+
// workspace has never pulled them. Treat them as realm artifacts, not user
|
|
30
|
+
// drift, so `push --delete` only removes genuine remote-only user files.
|
|
31
|
+
const REMOTE_DELETE_EXCLUSIONS = new Set(['index.json', 'realm.json']);
|
|
32
|
+
|
|
28
33
|
class RealmPusher extends RealmSyncBase {
|
|
29
34
|
hasError = false;
|
|
30
35
|
|
|
31
36
|
constructor(
|
|
32
37
|
private pushOptions: PushOptions,
|
|
33
|
-
|
|
38
|
+
authenticator: RealmAuthenticator,
|
|
34
39
|
) {
|
|
35
|
-
super(pushOptions,
|
|
40
|
+
super(pushOptions, authenticator);
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
async sync(): Promise<void> {
|
|
@@ -225,10 +230,16 @@ class RealmPusher extends RealmSyncBase {
|
|
|
225
230
|
|
|
226
231
|
if (this.pushOptions.deleteRemote) {
|
|
227
232
|
const filesToDelete = new Set(initialRemoteFiles.keys());
|
|
233
|
+
const skippedDeleteArtifacts: string[] = [];
|
|
228
234
|
|
|
229
235
|
for (const relativePath of filesToDelete) {
|
|
230
236
|
if (isProtectedFile(relativePath)) {
|
|
231
237
|
filesToDelete.delete(relativePath);
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (REMOTE_DELETE_EXCLUSIONS.has(relativePath)) {
|
|
241
|
+
filesToDelete.delete(relativePath);
|
|
242
|
+
skippedDeleteArtifacts.push(relativePath);
|
|
232
243
|
}
|
|
233
244
|
}
|
|
234
245
|
|
|
@@ -236,21 +247,26 @@ class RealmPusher extends RealmSyncBase {
|
|
|
236
247
|
filesToDelete.delete(relativePath);
|
|
237
248
|
}
|
|
238
249
|
|
|
239
|
-
if (
|
|
250
|
+
if (skippedDeleteArtifacts.length > 0) {
|
|
240
251
|
console.log(
|
|
241
|
-
`
|
|
252
|
+
`Skipping ${skippedDeleteArtifacts.length} realm-managed remote artifact(s): ${skippedDeleteArtifacts.join(', ')}`,
|
|
242
253
|
);
|
|
254
|
+
}
|
|
243
255
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
} catch (error) {
|
|
249
|
-
this.hasError = true;
|
|
250
|
-
console.error(`Error deleting ${relativePath}:`, error);
|
|
251
|
-
}
|
|
252
|
-
}),
|
|
256
|
+
if (filesToDelete.size > 0) {
|
|
257
|
+
const deletePlan = Array.from(filesToDelete).sort();
|
|
258
|
+
console.log(
|
|
259
|
+
`Deleting ${deletePlan.length} remote files that don't exist locally: ${deletePlan.join(', ')}`,
|
|
253
260
|
);
|
|
261
|
+
|
|
262
|
+
for (const relativePath of deletePlan) {
|
|
263
|
+
try {
|
|
264
|
+
await this.deleteFile(relativePath);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
this.hasError = true;
|
|
267
|
+
console.error(`Error deleting ${relativePath}:`, error);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
254
270
|
}
|
|
255
271
|
}
|
|
256
272
|
|
|
@@ -308,6 +324,18 @@ export interface PushCommandOptions {
|
|
|
308
324
|
dryRun?: boolean;
|
|
309
325
|
force?: boolean;
|
|
310
326
|
profileManager?: ProfileManager;
|
|
327
|
+
/**
|
|
328
|
+
* Pre-resolved realm secret seed for administrative access. When set, the
|
|
329
|
+
* CLI mints a JWT locally and skips Matrix login + /_server-session +
|
|
330
|
+
* /_realm-auth. The `--realm-secret-seed` CLI flag is resolved via
|
|
331
|
+
* `resolveRealmSecretSeed` (env var or interactive prompt) before being
|
|
332
|
+
* passed here.
|
|
333
|
+
*/
|
|
334
|
+
realmSecretSeed?: string;
|
|
335
|
+
/**
|
|
336
|
+
* @internal Test hook: supply an already-constructed authenticator.
|
|
337
|
+
*/
|
|
338
|
+
authenticator?: RealmAuthenticator;
|
|
311
339
|
}
|
|
312
340
|
|
|
313
341
|
export function registerPushCommand(realm: Command): void {
|
|
@@ -322,13 +350,30 @@ export function registerPushCommand(realm: Command): void {
|
|
|
322
350
|
.option('--delete', 'Delete remote files that do not exist locally')
|
|
323
351
|
.option('--dry-run', 'Show what would be done without making changes')
|
|
324
352
|
.option('--force', 'Upload all files, even if unchanged')
|
|
353
|
+
.option(
|
|
354
|
+
'--realm-secret-seed',
|
|
355
|
+
'Administrative auth: prompt for a realm secret seed and mint a JWT locally instead of using a Matrix profile (env: BOXEL_REALM_SECRET_SEED)',
|
|
356
|
+
)
|
|
325
357
|
.action(
|
|
326
358
|
async (
|
|
327
359
|
localDir: string,
|
|
328
360
|
realmUrl: string,
|
|
329
|
-
options: {
|
|
361
|
+
options: {
|
|
362
|
+
delete?: boolean;
|
|
363
|
+
dryRun?: boolean;
|
|
364
|
+
force?: boolean;
|
|
365
|
+
realmSecretSeed?: boolean;
|
|
366
|
+
},
|
|
330
367
|
) => {
|
|
331
|
-
await
|
|
368
|
+
const realmSecretSeed = await resolveRealmSecretSeed(
|
|
369
|
+
options.realmSecretSeed === true,
|
|
370
|
+
);
|
|
371
|
+
await pushCommand(localDir, realmUrl, {
|
|
372
|
+
delete: options.delete,
|
|
373
|
+
dryRun: options.dryRun,
|
|
374
|
+
force: options.force,
|
|
375
|
+
realmSecretSeed,
|
|
376
|
+
});
|
|
332
377
|
},
|
|
333
378
|
);
|
|
334
379
|
}
|
|
@@ -338,13 +383,20 @@ export async function pushCommand(
|
|
|
338
383
|
realmUrl: string,
|
|
339
384
|
options: PushCommandOptions,
|
|
340
385
|
): Promise<void> {
|
|
341
|
-
let
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
386
|
+
let authenticator: RealmAuthenticator;
|
|
387
|
+
if (options.authenticator) {
|
|
388
|
+
authenticator = options.authenticator;
|
|
389
|
+
} else {
|
|
390
|
+
const resolution = resolveRealmAuthenticator({
|
|
391
|
+
realmUrl,
|
|
392
|
+
realmSecretSeed: options.realmSecretSeed,
|
|
393
|
+
profileManager: options.profileManager,
|
|
394
|
+
});
|
|
395
|
+
if (!resolution.ok) {
|
|
396
|
+
console.error(`Error: ${resolution.error}`);
|
|
397
|
+
process.exit(1);
|
|
398
|
+
}
|
|
399
|
+
authenticator = resolution.authenticator;
|
|
348
400
|
}
|
|
349
401
|
|
|
350
402
|
if (!(await pathExists(localDir))) {
|
|
@@ -361,7 +413,7 @@ export async function pushCommand(
|
|
|
361
413
|
dryRun: options.dryRun,
|
|
362
414
|
force: options.force,
|
|
363
415
|
},
|
|
364
|
-
|
|
416
|
+
authenticator,
|
|
365
417
|
);
|
|
366
418
|
|
|
367
419
|
await pusher.sync();
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import { ensureTrailingSlash } from '@cardstack/runtime-common/paths';
|
|
3
|
+
import {
|
|
4
|
+
getProfileManager,
|
|
5
|
+
NO_ACTIVE_PROFILE_ERROR,
|
|
6
|
+
type ProfileManager,
|
|
7
|
+
} from '../../lib/profile-manager';
|
|
8
|
+
import { prompt } from '../../lib/prompt';
|
|
9
|
+
import { DIM, FG_CYAN, FG_GREEN, FG_RED, RESET } from '../../lib/colors';
|
|
10
|
+
|
|
11
|
+
export interface RemoveRealmOptions {
|
|
12
|
+
realmUrl: string;
|
|
13
|
+
dryRun?: boolean;
|
|
14
|
+
profileManager?: ProfileManager;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface RemoveRealmResult {
|
|
18
|
+
/** Normalized URL the operation targeted (always trailing-slashed). */
|
|
19
|
+
realmUrl: string;
|
|
20
|
+
/** True only when both server delete and Matrix unlink completed. */
|
|
21
|
+
removed: boolean;
|
|
22
|
+
/** True when DELETE /_delete-realm returned 204. */
|
|
23
|
+
serverDeleted: boolean;
|
|
24
|
+
/** True when Matrix `app.boxel.realms` was rewritten without the URL. */
|
|
25
|
+
unlinked: boolean;
|
|
26
|
+
/** Number of entries before the change. */
|
|
27
|
+
previousCount: number;
|
|
28
|
+
/** Number of entries the next list would contain (computed even on dry-run). */
|
|
29
|
+
nextCount: number;
|
|
30
|
+
/**
|
|
31
|
+
* True when the URL was not present in `app.boxel.realms`. Mutually
|
|
32
|
+
* exclusive with a successful real removal.
|
|
33
|
+
*/
|
|
34
|
+
notInList?: boolean;
|
|
35
|
+
error?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Remove a realm: delete server-side files / index / registry via
|
|
40
|
+
* `DELETE /_delete-realm`, then unlink the URL from the active profile's
|
|
41
|
+
* `app.boxel.realms` Matrix account_data list. Mirrors the host UI's
|
|
42
|
+
* workspace delete flow and inverts `boxel realm create`.
|
|
43
|
+
*
|
|
44
|
+
* Programmatic API. Returns a result object on every code path; never
|
|
45
|
+
* prompts and never calls `process.exit`. The CLI wraps this with a TTY
|
|
46
|
+
* confirmation step (see `registerRemoveCommand`).
|
|
47
|
+
*/
|
|
48
|
+
export async function removeRealm(
|
|
49
|
+
options: RemoveRealmOptions,
|
|
50
|
+
): Promise<RemoveRealmResult> {
|
|
51
|
+
let realmUrl = ensureTrailingSlash(options.realmUrl.trim());
|
|
52
|
+
let pm = options.profileManager ?? getProfileManager();
|
|
53
|
+
let active = pm.getActiveProfile();
|
|
54
|
+
if (!active) {
|
|
55
|
+
return {
|
|
56
|
+
realmUrl,
|
|
57
|
+
removed: false,
|
|
58
|
+
serverDeleted: false,
|
|
59
|
+
unlinked: false,
|
|
60
|
+
previousCount: 0,
|
|
61
|
+
nextCount: 0,
|
|
62
|
+
error: NO_ACTIVE_PROFILE_ERROR,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let existing: string[];
|
|
67
|
+
try {
|
|
68
|
+
existing = await pm.getUserRealms();
|
|
69
|
+
} catch (err) {
|
|
70
|
+
return {
|
|
71
|
+
realmUrl,
|
|
72
|
+
removed: false,
|
|
73
|
+
serverDeleted: false,
|
|
74
|
+
unlinked: false,
|
|
75
|
+
previousCount: 0,
|
|
76
|
+
nextCount: 0,
|
|
77
|
+
error: `Failed to load realm list: ${
|
|
78
|
+
err instanceof Error ? err.message : String(err)
|
|
79
|
+
}`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
let normalized = existing.map(ensureTrailingSlash);
|
|
83
|
+
let previousCount = normalized.length;
|
|
84
|
+
let matchCount = normalized.filter((u) => u === realmUrl).length;
|
|
85
|
+
|
|
86
|
+
if (matchCount === 0) {
|
|
87
|
+
return {
|
|
88
|
+
realmUrl,
|
|
89
|
+
removed: false,
|
|
90
|
+
serverDeleted: false,
|
|
91
|
+
unlinked: false,
|
|
92
|
+
previousCount,
|
|
93
|
+
nextCount: previousCount,
|
|
94
|
+
notInList: true,
|
|
95
|
+
error: 'Realm is not in app.boxel.realms. Nothing to remove.',
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let nextCount = previousCount - matchCount;
|
|
100
|
+
|
|
101
|
+
if (options.dryRun) {
|
|
102
|
+
return {
|
|
103
|
+
realmUrl,
|
|
104
|
+
removed: false,
|
|
105
|
+
serverDeleted: false,
|
|
106
|
+
unlinked: false,
|
|
107
|
+
previousCount,
|
|
108
|
+
nextCount,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let realmServerUrl = active.profile.realmServerUrl.replace(/\/$/, '');
|
|
113
|
+
let response: Response;
|
|
114
|
+
try {
|
|
115
|
+
response = await pm.authedRealmServerFetch(
|
|
116
|
+
`${realmServerUrl}/_delete-realm`,
|
|
117
|
+
{
|
|
118
|
+
method: 'DELETE',
|
|
119
|
+
headers: { 'Content-Type': 'application/vnd.api+json' },
|
|
120
|
+
body: JSON.stringify({
|
|
121
|
+
data: { type: 'realm', id: realmUrl },
|
|
122
|
+
}),
|
|
123
|
+
},
|
|
124
|
+
);
|
|
125
|
+
} catch (err) {
|
|
126
|
+
return {
|
|
127
|
+
realmUrl,
|
|
128
|
+
removed: false,
|
|
129
|
+
serverDeleted: false,
|
|
130
|
+
unlinked: false,
|
|
131
|
+
previousCount,
|
|
132
|
+
nextCount: previousCount,
|
|
133
|
+
error: `Failed to reach realm server: ${
|
|
134
|
+
err instanceof Error ? err.message : String(err)
|
|
135
|
+
}`,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
let body = await safeReadResponseText(response);
|
|
141
|
+
let error =
|
|
142
|
+
response.status === 403
|
|
143
|
+
? `You do not own this realm and cannot delete it on the server. Server returned 403: ${body}`
|
|
144
|
+
: `Realm server returned ${response.status}: ${body}`;
|
|
145
|
+
return {
|
|
146
|
+
realmUrl,
|
|
147
|
+
removed: false,
|
|
148
|
+
serverDeleted: false,
|
|
149
|
+
unlinked: false,
|
|
150
|
+
previousCount,
|
|
151
|
+
nextCount: previousCount,
|
|
152
|
+
error,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let unlinked: boolean;
|
|
157
|
+
try {
|
|
158
|
+
unlinked = await pm.removeFromUserRealms(realmUrl);
|
|
159
|
+
} catch (err) {
|
|
160
|
+
return {
|
|
161
|
+
realmUrl,
|
|
162
|
+
removed: false,
|
|
163
|
+
serverDeleted: true,
|
|
164
|
+
unlinked: false,
|
|
165
|
+
previousCount,
|
|
166
|
+
nextCount: previousCount,
|
|
167
|
+
error: `Server delete succeeded, but Matrix unlink failed: ${
|
|
168
|
+
err instanceof Error ? err.message : String(err)
|
|
169
|
+
}`,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!unlinked) {
|
|
174
|
+
return {
|
|
175
|
+
realmUrl,
|
|
176
|
+
removed: false,
|
|
177
|
+
serverDeleted: true,
|
|
178
|
+
unlinked: false,
|
|
179
|
+
previousCount,
|
|
180
|
+
nextCount: previousCount,
|
|
181
|
+
error:
|
|
182
|
+
'Server delete succeeded, but Matrix account_data did not contain the URL by the time we PUT (concurrent edit?). Server-side files are gone; please refresh and check your realm list.',
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
realmUrl,
|
|
188
|
+
removed: true,
|
|
189
|
+
serverDeleted: true,
|
|
190
|
+
unlinked,
|
|
191
|
+
previousCount,
|
|
192
|
+
nextCount,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function safeReadResponseText(response: Response): Promise<string> {
|
|
197
|
+
try {
|
|
198
|
+
return await response.text();
|
|
199
|
+
} catch {
|
|
200
|
+
return '<no response body>';
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
interface RemoveCliOptions {
|
|
205
|
+
yes?: boolean;
|
|
206
|
+
dryRun?: boolean;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function registerRemoveCommand(realm: Command): void {
|
|
210
|
+
realm
|
|
211
|
+
.command('remove')
|
|
212
|
+
.description(
|
|
213
|
+
'Remove a realm — deletes server-side files and unlinks it from your realm list',
|
|
214
|
+
)
|
|
215
|
+
.argument('<realm-url>', 'realm URL to remove')
|
|
216
|
+
.option('-y, --yes', 'Skip the interactive confirmation prompt')
|
|
217
|
+
.option('--dry-run', 'Preview the change without writing to Matrix')
|
|
218
|
+
.action(async (realmUrlInput: string, opts: RemoveCliOptions) => {
|
|
219
|
+
let normalized = ensureTrailingSlash(realmUrlInput.trim());
|
|
220
|
+
|
|
221
|
+
let preview = await removeRealm({
|
|
222
|
+
realmUrl: normalized,
|
|
223
|
+
dryRun: true,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
if (preview.error && !preview.notInList) {
|
|
227
|
+
console.error(`${FG_RED}Error:${RESET} ${preview.error}`);
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (preview.notInList) {
|
|
232
|
+
console.error(`${FG_RED}Error:${RESET} ${preview.error}`);
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
console.log(`Remove target: ${FG_CYAN}${preview.realmUrl}${RESET}`);
|
|
237
|
+
console.log(
|
|
238
|
+
`${DIM}app.boxel.realms: ${preview.previousCount} -> ${preview.nextCount}${RESET}`,
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
if (opts.dryRun) {
|
|
242
|
+
console.log(
|
|
243
|
+
`${DIM}[DRY RUN] No server delete or Matrix changes sent.${RESET}`,
|
|
244
|
+
);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (!opts.yes) {
|
|
249
|
+
if (!process.stdin.isTTY) {
|
|
250
|
+
console.error(
|
|
251
|
+
`${FG_RED}Error:${RESET} stdin is not a TTY. Pass --yes to confirm in non-interactive mode.`,
|
|
252
|
+
);
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
let answer = await prompt(
|
|
256
|
+
'This will permanently delete the realm files, indexer state, and registry entry on the server. Proceed? (y/N) ',
|
|
257
|
+
);
|
|
258
|
+
if (!/^y/i.test(answer)) {
|
|
259
|
+
console.log(`${DIM}Cancelled.${RESET}`);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
let result = await removeRealm({ realmUrl: normalized });
|
|
265
|
+
if (result.error || !result.removed) {
|
|
266
|
+
console.error(
|
|
267
|
+
`${FG_RED}Error:${RESET} ${result.error ?? 'Removal did not complete.'}`,
|
|
268
|
+
);
|
|
269
|
+
if (result.serverDeleted && !result.unlinked) {
|
|
270
|
+
console.error(
|
|
271
|
+
`${DIM}The realm is gone, but your account_data still references ${result.realmUrl}.${RESET}`,
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
console.log(
|
|
278
|
+
`${FG_GREEN}Removed:${RESET} ${FG_CYAN}${result.realmUrl}${RESET}`,
|
|
279
|
+
);
|
|
280
|
+
});
|
|
281
|
+
}
|