gdk-version-archiver 1.0.0-alpha

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 ConMaster
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,41 @@
1
+ # GDK Version Archiver
2
+
3
+ **NOTICE: THIS TOOL DOES NOT HELP YOU TO PIRATE THE GAME. YOU MUST OWN A LEGITIMATE COPY OF MINECRAFT TO USE THIS TOOL.**
4
+
5
+ A CLI tool for archiving and managing Minecraft GDK installations on Windows. It captures current installations as mirrors and manages them using tags for easy version switching.
6
+
7
+ - **Archive Versions**: Capture installed Appx packages to local mirrors.
8
+ - **Tagging**: Use tags like `current` or `beta` to manage different versions. Default is `current` tag and is also hardcoded to always be used.
9
+ - **Protected File Handling**: Automatically copies encrypted files via internal instructions.
10
+ - **Concurrent Copying**: Fast archiving using a task concurrency channel.
11
+
12
+ ## Setup and Usage
13
+
14
+ **Prerequisites**: Windows OS, [Bun](https://bun.sh/) runtime, and Powershell.
15
+
16
+ ```bash
17
+ pnpm install
18
+ npm run build
19
+ ```
20
+
21
+ Binary name: `gdk-archive-manager`
22
+
23
+ - **Archive**: `bunx gdk-archive-manager archive [package-pattern]`
24
+ - `--tag <name>`: Target tag (default: `current`).
25
+ - `--force`: Overwrite existing mirrors.
26
+ - `--concurrency <n>`: Parallel file copy limit. Default is `10`, recommended is to follow default value.
27
+ - **List**: `bunx gdk-archive-manager list` - Show stored mirrors and tags.
28
+ - **Run**: `bunx gdk-archive-manager run --tag <name>` - Launch a tagged version. Default is `current` tag.
29
+
30
+ Global flags: `--verbose`, `--tag <name>`.
31
+
32
+ ## Storage and Mechanics
33
+
34
+ Data is stored in `%APPDATA%\ConMaster.BedrockArchiver\clients\`.
35
+
36
+ - `mirrors/`: Full version file structures.
37
+ - `tags/`: Junctions pointing to specific mirrors.
38
+
39
+ The tool bypasses GDK file protections by invoking a copy instruction within the package context. It then uses symlink junctions to allow external tools to reference a static path while the underlying version is updated.
40
+
41
+ MIT License
package/bin/cli ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ import "../dist/cli.js";
package/dist/cli.js ADDED
@@ -0,0 +1,1102 @@
1
+ import { copyFile, mkdir, readFile, realpath, rm, rmdir, stat, symlink } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { exit } from "node:process";
4
+ import { existsSync } from "node:fs";
5
+ import { pathToFileURL } from "node:url";
6
+
7
+ //#region node_modules/.pnpm/con-utils@0.0.2/node_modules/con-utils/dist/format-helper-CE23_M3S.js
8
+ var FormatCode = class FormatCode$1 {
9
+ prefixes;
10
+ suffixes;
11
+ constructor(prefix, suffix) {
12
+ this.prefixes = prefix;
13
+ this.suffixes = suffix;
14
+ }
15
+ static create(format, reset) {
16
+ return new FormatCode$1([format], reset === void 0 ? [] : [reset]);
17
+ }
18
+ static createForegroundRGB(red, green, blue) {
19
+ return new FormatCode$1([
20
+ 38,
21
+ 2,
22
+ red,
23
+ green,
24
+ blue
25
+ ], [39]);
26
+ }
27
+ static createBackgroundRGB(red, green, blue) {
28
+ return new FormatCode$1([
29
+ 48,
30
+ 2,
31
+ red,
32
+ green,
33
+ blue
34
+ ], [49]);
35
+ }
36
+ get prefix() {
37
+ return this.compile(this.prefixes);
38
+ }
39
+ get suffix() {
40
+ return this.compile(this.suffixes);
41
+ }
42
+ compile(format) {
43
+ return `\x1b[${format.join(";")}m`;
44
+ }
45
+ merge(format) {
46
+ pushUnique([...this.prefixes], ...format.prefixes);
47
+ pushUnique([...this.suffixes], ...this.suffixes);
48
+ return new FormatCode$1(this.prefixes, this.suffixes);
49
+ }
50
+ get wrap() {
51
+ return (raw) => `${this.prefix}${raw}${this.suffix}`;
52
+ }
53
+ get format() {
54
+ return (template, ...args) => this.wrap(template.reduce((acc, part, i) => acc + this.wrap(part) + (args[i] ?? ""), ""));
55
+ }
56
+ };
57
+ const DIM$1 = FormatCode.create(2, 22);
58
+ const ITALIC = FormatCode.create(3, 23);
59
+ const UNDERLINE = FormatCode.create(4, 24);
60
+ const INVERSE = FormatCode.create(7, 27);
61
+ const CENSOR = FormatCode.create(8, 28);
62
+ const CANCELED = FormatCode.create(9, 29);
63
+ const SOFT_RESET = [
64
+ 21,
65
+ 22,
66
+ 23,
67
+ 24,
68
+ 25,
69
+ 27,
70
+ 28,
71
+ 29,
72
+ 39,
73
+ 49,
74
+ 54,
75
+ 55,
76
+ 65
77
+ ].join(";");
78
+ /**
79
+ * Pushes value into array if it's not already present.
80
+ * Returns true if inserted, false if already existed.
81
+ */
82
+ function pushUnique(arr, ...values) {
83
+ for (const value of values) {
84
+ if (arr.includes(value)) continue;
85
+ arr.push(value);
86
+ }
87
+ }
88
+ var Format = class {
89
+ /**
90
+ * Wraps words in a string to fit within a specified print length.
91
+ * @param text The input string to wrap.
92
+ * @param printLength The maximum length of each line.
93
+ * @returns A generator yielding wrapped lines.
94
+ */
95
+ static *wrapWords(text, printLength) {
96
+ const words = text.split(/\s+/);
97
+ const currentLineWords = [];
98
+ let currentLength = 0;
99
+ for (const word of words) {
100
+ const wordLength = word.length;
101
+ if (currentLength + wordLength + currentLineWords.length > printLength && currentLineWords.length > 0) {
102
+ yield currentLineWords.join(" ");
103
+ currentLineWords.length = 0;
104
+ currentLength = 0;
105
+ }
106
+ currentLineWords.push(word);
107
+ currentLength += wordLength;
108
+ }
109
+ if (currentLineWords.length > 0) yield currentLineWords.join(" ");
110
+ }
111
+ };
112
+
113
+ //#endregion
114
+ //#region node_modules/.pnpm/con-utils@0.0.2/node_modules/con-utils/dist/con-cli.js
115
+ var ValueTypeValidator = class {
116
+ toString() {
117
+ return this.name;
118
+ }
119
+ };
120
+ const VALID_BOOLEAN_TYPES = new Set([
121
+ "false",
122
+ "true",
123
+ "0",
124
+ "1"
125
+ ]);
126
+ var BooleanTypeValidator = class extends ValueTypeValidator {
127
+ name = "bool";
128
+ description = "Boolean Type (true/false)";
129
+ isValid(input) {
130
+ return VALID_BOOLEAN_TYPES.has(input.toLowerCase());
131
+ }
132
+ coerce(input) {
133
+ input = input.toLowerCase();
134
+ return input === "false" || input === "0" ? false : Boolean(input);
135
+ }
136
+ };
137
+ var NumberTypeValidator = class extends ValueTypeValidator {
138
+ name = "number";
139
+ description = "Floating point number type";
140
+ isValid(input) {
141
+ return !isNaN(Number(input));
142
+ }
143
+ coerce(input) {
144
+ return Number(input);
145
+ }
146
+ };
147
+ var StringTypeValidator = class extends ValueTypeValidator {
148
+ name = "string";
149
+ description = "Raw string type";
150
+ isValid(input) {
151
+ return typeof input === "string";
152
+ }
153
+ coerce(input) {
154
+ return input;
155
+ }
156
+ };
157
+ /**
158
+ * Represents a command-line argument with validation and type coercion.
159
+ * @template T - The type of the argument value after validation
160
+ */
161
+ var ArgumentLike = class {
162
+ name;
163
+ validator;
164
+ description;
165
+ defaultValue;
166
+ constructor(name, opts) {
167
+ this.name = name.toLowerCase();
168
+ this.validator = opts.validator;
169
+ this.description = opts?.description ?? void 0;
170
+ this.defaultValue = opts.defaultValue;
171
+ }
172
+ /**
173
+ * Determines if this argument is required (has no default value).
174
+ * @returns True if the argument must be provided
175
+ */
176
+ isRequired() {
177
+ return this.defaultValue === null;
178
+ }
179
+ /**
180
+ * Validates and coerces a string value to the argument's type.
181
+ * @param value - The string value to enforce
182
+ * @returns The coerced value of type T
183
+ * @throws {Error} If the value is invalid or required argument is missing
184
+ */
185
+ enforce(value) {
186
+ if (value === void 0 || value === null) if (this.defaultValue === null) throw new Error("Following argument is required and can't be omitted! " + this.name);
187
+ else return this.defaultValue;
188
+ if (!this.validator.isValid(value)) throw new Error("Following argument has not valid value! " + this.name + " -> " + value);
189
+ return this.validator.coerce(value);
190
+ }
191
+ /**
192
+ * Returns a string representation of this argument for help text.
193
+ * @returns Formatted string like `<name:type>` or `[name:type]`
194
+ */
195
+ toString() {
196
+ const $ = `${this.name}:${this.validator.toString()}`;
197
+ return this.isRequired() ? `<${$}>` : `[${$}]`;
198
+ }
199
+ };
200
+ /**
201
+ * Represents a named flag that accepts a value.
202
+ * @template T - The type of the flag value
203
+ */
204
+ var ValueFlag = class extends ArgumentLike {
205
+ long;
206
+ short;
207
+ isValueFlag;
208
+ constructor(name, opts) {
209
+ super(name, opts);
210
+ this.long = opts.long?.toLowerCase();
211
+ this.short = opts.short?.toLowerCase();
212
+ this.isValueFlag = true;
213
+ }
214
+ /**
215
+ * Determines if this flag accepts a value.
216
+ * @returns True if this flag is value-based
217
+ */
218
+ isValueBased() {
219
+ return this.isValueFlag;
220
+ }
221
+ /**
222
+ * Returns a string representation of this flag for help text.
223
+ * @returns Formatted string like `--long, -s`
224
+ */
225
+ toString() {
226
+ return [this.long && `--${this.long}`, this.short && `-${this.short}`].filter(Boolean).join(", ");
227
+ }
228
+ };
229
+ /**
230
+ * Represents a boolean flag (presence indicator).
231
+ */
232
+ var Flag = class extends ValueFlag {
233
+ constructor(name, opts) {
234
+ const { description: description$1, long, short } = opts ?? {};
235
+ super(name, {
236
+ validator: new BooleanTypeValidator(),
237
+ defaultValue: false,
238
+ description: description$1,
239
+ long,
240
+ short
241
+ });
242
+ this.isValueFlag = false;
243
+ }
244
+ };
245
+ /**
246
+ * A collection of flags with lookup capabilities by name, long form, or short form.
247
+ */
248
+ var FlagsGroup = class {
249
+ flags = /* @__PURE__ */ new Set();
250
+ map = /* @__PURE__ */ new Map();
251
+ base = null;
252
+ constructor(base) {
253
+ this.base = base ?? null;
254
+ }
255
+ /**
256
+ * Checks if a flag exists in this group or inherited from base groups.
257
+ * @param valueFlag - The flag to check for
258
+ * @returns True if the flag is registered
259
+ */
260
+ hasFlag(valueFlag) {
261
+ return this.hasOwnFlag(valueFlag) || (this.base?.hasFlag(valueFlag) ?? false);
262
+ }
263
+ /**
264
+ * Checks if a flag exists directly in this group (not inherited).
265
+ * @param valueFlag - The flag to check for
266
+ * @returns True if the flag is registered in this group
267
+ */
268
+ hasOwnFlag(valueFlag) {
269
+ return this.flags.has(valueFlag);
270
+ }
271
+ /**
272
+ * Adds flags and registers their searchable keys.
273
+ * @param flags - The flags to add
274
+ * @returns This group for chaining
275
+ */
276
+ add(...flags) {
277
+ for (const flag of flags) {
278
+ this.flags.add(flag);
279
+ this.setGeneralInternal(flag.name, flag);
280
+ if (flag.long) this.setLongInternal(flag.long, flag);
281
+ if (flag.short) this.setShortInternal(flag.short, flag);
282
+ }
283
+ return this;
284
+ }
285
+ /**
286
+ * Adds a short alias for an existing flag.
287
+ * @param flag - The flag to add an alias for
288
+ * @param short - The short form alias
289
+ * @returns This group for chaining
290
+ */
291
+ addShortAlias(flag, short) {
292
+ this.flags.add(flag);
293
+ this.setGeneralInternal(flag.name, flag);
294
+ this.setShortInternal(short, flag);
295
+ return this;
296
+ }
297
+ /**
298
+ * Adds a long alias for an existing flag.
299
+ * @param flag - The flag to add an alias for
300
+ * @param long - The long form alias
301
+ * @returns This group for chaining
302
+ */
303
+ addLongAlias(flag, long) {
304
+ this.flags.add(flag);
305
+ this.setGeneralInternal(flag.name, flag);
306
+ this.setLongInternal(long, flag);
307
+ return this;
308
+ }
309
+ setLongInternal(key, flag) {
310
+ this.map.set(`long:${key}`, flag);
311
+ }
312
+ setShortInternal(key, flag) {
313
+ this.map.set(`short:${key}`, flag);
314
+ }
315
+ setGeneralInternal(key, flag) {
316
+ this.map.set(`flag:${key}`, flag);
317
+ }
318
+ getLong(key) {
319
+ return this.map.get(`long:${key}`) ?? this.base?.getLong(key) ?? null;
320
+ }
321
+ /**
322
+ * Retrieves a flag by its short form name.
323
+ * @param key - The short form name
324
+ * @returns The flag or null if not found
325
+ */
326
+ getShort(key) {
327
+ return this.map.get(`short:${key}`) ?? this.base?.getShort(key) ?? null;
328
+ }
329
+ /**
330
+ * Retrieves a flag by its primary name.
331
+ * @param name - The flag name
332
+ * @returns The flag or null if not found
333
+ */
334
+ getByName(name) {
335
+ return this.map.get(`flag:${name}`) ?? this.base?.getByName(name) ?? null;
336
+ }
337
+ /**
338
+ * Gets all flags directly registered in this group.
339
+ * @returns A read-only set of all flags
340
+ */
341
+ getAll() {
342
+ return this.flags;
343
+ }
344
+ };
345
+ const dim$1 = DIM$1.wrap;
346
+ var CommandHelpStringBuilder = class {
347
+ static *buildHelp(command, preferredWidth$1) {
348
+ yield dim$1(` Usage of ${command.syntax()}`);
349
+ yield "";
350
+ yield dim$1(" " + "-".repeat(preferredWidth$1));
351
+ yield* Format.wrapWords(command.description ?? "", preferredWidth$1).map((e) => dim$1(" ") + e);
352
+ yield dim$1(" " + "-".repeat(preferredWidth$1));
353
+ yield "";
354
+ yield* this.buildFlags(command);
355
+ yield "";
356
+ }
357
+ static *buildFlags(command) {
358
+ yield dim$1("Flags:");
359
+ yield* command.flags.getAll().values().map((e) => " " + e.toString().padEnd(20) + " " + dim$1(e.description ?? ""));
360
+ }
361
+ };
362
+ const dim = DIM$1.wrap;
363
+ var Command = class {
364
+ parent;
365
+ uniqueHelpFlag;
366
+ name;
367
+ flags;
368
+ description;
369
+ onFlags;
370
+ constructor(name, description$1, parent = null) {
371
+ this.name = name.toLowerCase();
372
+ this.description = description$1 ?? "";
373
+ this.flags = new FlagsGroup(parent?.flags ?? null);
374
+ this.parent = parent;
375
+ this.uniqueHelpFlag = new Flag("help", {
376
+ description: "Show's help message",
377
+ short: "?",
378
+ long: "help"
379
+ });
380
+ this.flags.add(this.uniqueHelpFlag);
381
+ this.flags.addShortAlias(this.uniqueHelpFlag, "h");
382
+ }
383
+ getParentCommand() {
384
+ return this.parent ?? null;
385
+ }
386
+ *getHelp() {
387
+ yield* CommandHelpStringBuilder.buildHelp(this, preferredWidth);
388
+ }
389
+ *getFlags() {
390
+ yield dim("Flags:");
391
+ yield* this.flags.getAll().values().map((e) => " " + e.toString().padEnd(20) + " " + dim(e.description ?? ""));
392
+ }
393
+ getFullPathName() {
394
+ return `${this.parent ? this.parent.getFullPathName() + " " : ""}${this.name}`;
395
+ }
396
+ };
397
+ var CommandAction = class CommandAction$1 extends Command {
398
+ argumentsTypes;
399
+ action = null;
400
+ constructor(name, opts) {
401
+ super(name, opts.description, opts.parent);
402
+ this.argumentsTypes = opts.argTypes;
403
+ }
404
+ *getHelp() {
405
+ yield* super.getHelp();
406
+ if (!this.argumentsTypes.length) return;
407
+ yield dim(" Arguments:");
408
+ yield* this.argumentsTypes.map((e) => ` ${e.toString().padEnd(20)} ${dim(e.description ?? "")}`);
409
+ yield "";
410
+ }
411
+ static create(name, description$1, parent, args) {
412
+ return new CommandAction$1(name, {
413
+ argTypes: args,
414
+ description: description$1,
415
+ parent
416
+ });
417
+ }
418
+ syntax() {
419
+ return `${this.getFullPathName()} ${this.argumentsTypes.map((e) => e.isRequired() ? `<${e.name}:${e.validator.name}>` : `[${e.name}:${e.validator.name}]`).join(" ")}`;
420
+ }
421
+ };
422
+ var GroupCommand = class GroupCommand$1 extends Command {
423
+ action = null;
424
+ subcommands = /* @__PURE__ */ new Map();
425
+ static create(name, description$1, parent) {
426
+ return new GroupCommand$1(name, description$1, parent);
427
+ }
428
+ createGroup(name, description$1) {
429
+ const cmd = GroupCommand$1.create(name, description$1, this);
430
+ this.subcommands.set(name, cmd);
431
+ return cmd;
432
+ }
433
+ createAction(name, description$1, args) {
434
+ const cmd = CommandAction.create(name, description$1, this, args);
435
+ this.subcommands.set(name, cmd);
436
+ return cmd;
437
+ }
438
+ syntax() {
439
+ return `${this.getFullPathName()} <${this.subcommands.keys().toArray().join("|")}>`;
440
+ }
441
+ *getHelp() {
442
+ yield* super.getHelp();
443
+ yield dim(" SubCommands:");
444
+ let p = preferredWidth - 25;
445
+ yield* this.subcommands.values().map((e) => ` ${e.name.padEnd(20)} ${dim((e.description?.length ?? 0) > p ? (e.description?.substring(0, p) ?? "") + "..." : e.description ?? "")}`);
446
+ yield "";
447
+ }
448
+ };
449
+ let preferredWidth = 75;
450
+ /**
451
+ * Represents the result of parsing a command-line input.
452
+ * Contains the matched command, parsed arguments, and resolved flags.
453
+ */
454
+ var ParseResult = class {
455
+ constructor(command, args, flags) {
456
+ this.command = command;
457
+ this.args = args;
458
+ this.flags = flags;
459
+ }
460
+ /**
461
+ * Gets the value of a flag, falling back to its default if not provided.
462
+ * @template T - The type of the flag value
463
+ * @param flag - The flag to retrieve
464
+ * @returns The flag value or its default value
465
+ */
466
+ getValue(flag) {
467
+ return this.flags.get(flag) ?? flag.defaultValue;
468
+ }
469
+ /**
470
+ * Gets the executable function for this command if it's an action command.
471
+ * @returns The bound action function or null if this is a group command
472
+ */
473
+ getExecutable() {
474
+ if ("action" in this.command && typeof this.command["action"] === "function") return this.command.action.bind(null, this, ...this.args);
475
+ return null;
476
+ }
477
+ };
478
+ /**
479
+ * Error thrown during command-line parsing.
480
+ * Contains context about where and why parsing failed.
481
+ */
482
+ var ParserError = class extends Error {
483
+ command;
484
+ argv;
485
+ index;
486
+ flags;
487
+ constructor(command, message, argv, index, flags) {
488
+ super(message);
489
+ this.command = command;
490
+ this.name = new.target.name;
491
+ this.argv = argv;
492
+ this.index = index;
493
+ this.flags = flags ?? null;
494
+ }
495
+ };
496
+ /**
497
+ * Parses command-line arguments into commands, flags, and positional arguments.
498
+ */
499
+ var CommandsParser = class CommandsParser$1 {
500
+ argv;
501
+ index;
502
+ lastError = null;
503
+ constructor(argv, index) {
504
+ this.argv = argv;
505
+ this.index = index;
506
+ }
507
+ /**
508
+ * Parses the provided command against the command-line arguments.
509
+ * @param command - The command to parse
510
+ * @returns The parse result containing the matched command and parsed values
511
+ * @throws {ParserError} If parsing fails
512
+ */
513
+ parse(command) {
514
+ if (command instanceof GroupCommand) return this.parseGroupCommand(command);
515
+ else if (command instanceof CommandAction) return this.parseActionCommand(command);
516
+ else throw new ParserError(command, "Unsupported command type", this.argv, this.index);
517
+ }
518
+ parseGroupCommand(command) {
519
+ const RESULT = new ParseResult(command, [], /* @__PURE__ */ new Map());
520
+ let argument = this.getArgument();
521
+ if (!argument) {
522
+ if (!command.action) throw new ParserError(command, `Expected subcommand name, received none: ${command.getFullPathName()}`, this.argv, this.index);
523
+ return new ParseResult(command, [], /* @__PURE__ */ new Map());
524
+ }
525
+ if (argument.startsWith("-")) {
526
+ while (argument = this.getArgument()) {
527
+ const flag = CommandsParser$1.parseFlag(argument);
528
+ if (!flag) break;
529
+ const result = this.resolveFlag(command.flags, flag, this.getArgument(1));
530
+ if (!result) if (this.getLastError()) throw new ParserError(command, this.getLastError(), this.argv, this.index, new Set(RESULT.flags.keys()));
531
+ else throw new ParserError(command, "Failed to resolve tag with name: " + flag.name, this.argv, this.index, new Set(RESULT.flags.keys()));
532
+ const { flag: FLAG_TYPE, nextValueUsed, value } = result;
533
+ this.index++;
534
+ if (nextValueUsed) this.index++;
535
+ RESULT.flags.set(FLAG_TYPE, value);
536
+ }
537
+ if (!argument) return RESULT;
538
+ }
539
+ const sub = argument.toLowerCase();
540
+ const cmd = command.subcommands.get(sub);
541
+ if (!cmd) throw new ParserError(command, `Unknown subcommand: ${command.getFullPathName()} >>${sub}<<`, this.argv, this.index, new Set(RESULT.flags.keys()));
542
+ this.index++;
543
+ try {
544
+ const HRESULT = this.parse(cmd);
545
+ for (const flag of RESULT.flags.keys()) if (!HRESULT.flags.has(flag)) HRESULT.flags.set(flag, RESULT.flags.get(flag));
546
+ return HRESULT;
547
+ } catch (error) {
548
+ if (error instanceof ParserError) {
549
+ const flags = error.flags;
550
+ if (flags) RESULT.flags.keys().forEach((_) => flags.add(_));
551
+ else error.flags = new Set(RESULT.flags.keys());
552
+ }
553
+ throw error;
554
+ }
555
+ }
556
+ parseActionCommand(command) {
557
+ const positionals = [];
558
+ let argument;
559
+ const FLAGS_MAP = /* @__PURE__ */ new Map();
560
+ while (argument = this.getArgument()) {
561
+ const flag = CommandsParser$1.parseFlag(argument);
562
+ this.index++;
563
+ if (!flag) {
564
+ positionals.push(argument);
565
+ continue;
566
+ }
567
+ const result = this.resolveFlag(command.flags, flag, this.getArgument());
568
+ if (!result) if (this.getLastError()) throw new ParserError(command, this.getLastError(), this.argv, this.index, new Set(FLAGS_MAP.keys()));
569
+ else {
570
+ positionals.push(argument);
571
+ continue;
572
+ }
573
+ const { flag: FLAG_TYPE, nextValueUsed, value } = result;
574
+ if (nextValueUsed) this.index++;
575
+ FLAGS_MAP.set(FLAG_TYPE, value);
576
+ }
577
+ const finalArgs = [];
578
+ for (let idx = 0; idx < command.argumentsTypes.length; idx++) {
579
+ const def = command.argumentsTypes[idx];
580
+ const posVal = positionals[idx];
581
+ if (typeof posVal === "undefined") {
582
+ if (def.isRequired()) throw new ParserError(command, `Missing required argument: ${command.getFullPathName()} ... ${def.toString()}`, this.argv, this.index, new Set(FLAGS_MAP.keys()));
583
+ finalArgs.push(def.defaultValue);
584
+ } else {
585
+ if (!def.validator.isValid(posVal)) throw new ParserError(command, `Invalid value: ${posVal}`, this.argv, this.index, new Set(FLAGS_MAP.keys()));
586
+ finalArgs.push(def.enforce(posVal));
587
+ }
588
+ }
589
+ for (let j = command.argumentsTypes.length; j < positionals.length; j++) finalArgs.push(positionals[j]);
590
+ return new ParseResult(command, finalArgs, FLAGS_MAP);
591
+ }
592
+ resolveFlag(group, flag, next) {
593
+ const FLAG_TYPE = flag.isLong ? group.getLong(flag.name) : group.getShort(flag.name);
594
+ if (!FLAG_TYPE) return this.setLastError(null);
595
+ let nextValueUsed = false;
596
+ if (FLAG_TYPE.isValueBased()) {
597
+ let { value } = flag;
598
+ if (value === null) {
599
+ nextValueUsed = true;
600
+ value = next;
601
+ }
602
+ if (value === null) {
603
+ if (FLAG_TYPE.defaultValue) return {
604
+ flag: FLAG_TYPE,
605
+ value: null,
606
+ nextValueUsed
607
+ };
608
+ return this.setLastError("Flag with no default value, requires value to be set explicitly, flag: " + flag.name);
609
+ }
610
+ if (!FLAG_TYPE.validator.isValid(value)) return this.setLastError("Incorrect type passed as value for flag: " + flag.name + " value: " + value);
611
+ return {
612
+ flag: FLAG_TYPE,
613
+ value: FLAG_TYPE.validator.coerce(value),
614
+ nextValueUsed
615
+ };
616
+ } else if (flag.value) return this.setLastError("Syntax error, this flag doesn't supports values: " + flag.name + " value: " + flag.value);
617
+ return {
618
+ flag: FLAG_TYPE,
619
+ value: true,
620
+ nextValueUsed
621
+ };
622
+ }
623
+ getLastError() {
624
+ return this.lastError;
625
+ }
626
+ setLastError(error) {
627
+ this.lastError = error;
628
+ return null;
629
+ }
630
+ getArgument(relative = 0) {
631
+ return this.argv[this.index + (relative ?? 0)] ?? null;
632
+ }
633
+ /**
634
+ * Parses a flag string into its components (name, type, and optional value).
635
+ * @param argument - The argument string to parse (e.g., "-f", "--flag=value")
636
+ * @returns The parsed flag result or null if the argument is not a flag
637
+ */
638
+ static parseFlag(argument) {
639
+ if (argument.length < 2 || argument[0] !== "-") return null;
640
+ const isLongBit = argument[1] === "-", hasValueIndex = argument.indexOf("="), hasValueBit = hasValueIndex !== -1;
641
+ let name, value = hasValueBit ? argument.substring(hasValueIndex + 1) : null;
642
+ if (isLongBit) name = argument.substring(2, hasValueBit ? hasValueIndex : void 0);
643
+ else {
644
+ name = argument[1];
645
+ if (!value && argument.length > 2) value = argument.substring(2);
646
+ }
647
+ return {
648
+ name: name.toLowerCase(),
649
+ isLong: isLongBit,
650
+ value
651
+ };
652
+ }
653
+ };
654
+ /**
655
+ * Main entry point for parsing and executing command-line arguments.
656
+ * Handles parsing, flag resolution, and command execution with error handling.
657
+ */
658
+ var CommandLine = class {
659
+ constructor() {}
660
+ /**
661
+ * Parses command-line arguments and executes the appropriate command.
662
+ * @param argv - The command-line arguments to parse (typically process.argv)
663
+ * @param command - The root command to parse against
664
+ * @throws {ParserError} If parsing fails or validation errors occur
665
+ */
666
+ static async run(argv, command) {
667
+ const parser = new CommandsParser(argv, 2);
668
+ try {
669
+ const result = parser.parse(command);
670
+ if (result.flags.has(result.command.uniqueHelpFlag)) return void console.info(Array.from(result.command.getHelp()).join("\r\n"));
671
+ for (const flag of result.flags.keys()) for (let command$1 = result.command; command$1; command$1 = command$1.getParentCommand()) if (command$1.flags.hasOwnFlag(flag)) command$1.onFlags?.(result);
672
+ result.getExecutable()?.();
673
+ return;
674
+ } catch (err) {
675
+ if (err instanceof ParserError) {
676
+ if (!err.flags?.has(err.command.uniqueHelpFlag)) console.error(FormatCode.create(31, 39).wrap("->" + err.message));
677
+ console.error("\n", Array.from(err.command.getHelp()).join("\r\n"));
678
+ } else throw err;
679
+ }
680
+ }
681
+ /**
682
+ * Creates a new group command for organizing subcommands.
683
+ * @param name - The name of the command group
684
+ * @param description - A description of what this command group does
685
+ * @returns A new GroupCommand instance
686
+ */
687
+ static createGroup(name, description$1) {
688
+ return GroupCommand.create(name, description$1, null);
689
+ }
690
+ /**
691
+ * Creates a new action command that executes a handler function.
692
+ * @template T - The tuple type of arguments the action accepts
693
+ * @param name - The name of the command
694
+ * @param description - A description of what this command does
695
+ * @param args - The argument definitions for this command
696
+ * @returns A new CommandAction instance
697
+ */
698
+ static createAction(name, description$1, args) {
699
+ return CommandAction.create(name, description$1, null, args);
700
+ }
701
+ };
702
+
703
+ //#endregion
704
+ //#region package.json
705
+ var description = "";
706
+
707
+ //#endregion
708
+ //#region node_modules/.pnpm/con-utils@0.0.2/node_modules/con-utils/dist/general.js
709
+ /**
710
+ * Manages concurrent execution of tasks with a maximum concurrency limit.
711
+ */
712
+ var TaskConcurrencyChannel = class {
713
+ /**
714
+ * Set of promises currently being tracked.
715
+ */
716
+ stack = /* @__PURE__ */ new Set();
717
+ /**
718
+ * Maximum number of concurrent tasks allowed.
719
+ */
720
+ concurrency;
721
+ /**
722
+ * Create a new TaskConcurrencyChannel.
723
+ * @param maxConcurrency The maximum number of tasks to run concurrently.
724
+ */
725
+ constructor(maxConcurrency) {
726
+ this.concurrency = maxConcurrency;
727
+ }
728
+ /**
729
+ * Add a task to the queue. Waits if concurrency limit is reached.
730
+ * @param task The task to execute.
731
+ */
732
+ async push(task) {
733
+ const promise = Promise.resolve(task).then((_) => void this.stack.delete(promise), (_) => void 0);
734
+ this.stack.add(promise);
735
+ if (this.stack.size >= this.concurrency) await Promise.any(this.stack);
736
+ }
737
+ /**
738
+ * Get a promise that resolves when all tasks complete.
739
+ * @returns A promise that resolves when the stack is empty.
740
+ */
741
+ getAwaiter() {
742
+ return Promise.all(this.stack).then((_) => void 0);
743
+ }
744
+ };
745
+
746
+ //#endregion
747
+ //#region src/logger.ts
748
+ let LOG_ENABLED = false;
749
+ const SET_VERBOSE = (value) => LOG_ENABLED = value;
750
+ const rgb = FormatCode.createForegroundRGB;
751
+ const INFO_CODE = rgb(80, 175, 199);
752
+ const WARN_CODE = rgb(199, 133, 80);
753
+ const E = rgb(199, 80, 96).wrap, I = INFO_CODE.wrap, W = WARN_CODE.wrap;
754
+ const ACCENT = FormatCode.create(92, 39).wrap;
755
+ const DIM = DIM$1.wrap;
756
+ const OUTPUT = (raw) => process.stderr.write(raw);
757
+ const LOG = (string) => void (LOG_ENABLED ? OUTPUT(DIM(`| ` + string)) : null);
758
+ const INFO = (string) => void OUTPUT(I(`| ` + string));
759
+ const WARN = (string) => void OUTPUT(W(`| ` + string));
760
+ const ERROR = (string) => void OUTPUT(E(`| ` + string));
761
+
762
+ //#endregion
763
+ //#region src/constants.ts
764
+ const { APPDATA, SAVE_DATA_PATH } = import.meta.env;
765
+ if (!(SAVE_DATA_PATH || APPDATA)) {
766
+ console.error("Couldn't find path to store the mirrors");
767
+ exit(-1);
768
+ }
769
+ const DEFAULT_APPDATA_FOLDER_NAME = "ConMaster.BedrockArchiver";
770
+ const PREFERRED_PATH = resolve(SAVE_DATA_PATH ?? join(APPDATA ?? ".", DEFAULT_APPDATA_FOLDER_NAME, "clients"));
771
+ const MIRRORS_FOLDER_PATH = join(PREFERRED_PATH, "mirrors");
772
+ const TAGS_FOLDER_PATH = join(PREFERRED_PATH, "tags");
773
+
774
+ //#endregion
775
+ //#region src/cli/root.ts
776
+ const ROOT = CommandLine.createGroup(">.", description);
777
+ const VERBOSE = new Flag("verbose", {
778
+ description: "Shows LOG messages",
779
+ long: "verbose"
780
+ });
781
+ const TAG = new ValueFlag("tag", {
782
+ defaultValue: "current",
783
+ description: "Environment Tag under this instance operates, archiving performs tag redirection of this name",
784
+ validator: new StringTypeValidator(),
785
+ long: "tag"
786
+ });
787
+ ROOT.flags.add(VERBOSE, TAG);
788
+ ROOT.onFlags = (options) => {
789
+ if (options.getValue(VERBOSE)) SET_VERBOSE(true);
790
+ };
791
+ const ENVIRONMENT_INFO = (options) => {
792
+ LOG(`-----------------------\n`);
793
+ LOG(`DATA: ${ACCENT(PREFERRED_PATH)}\n`);
794
+ LOG(`TAG: ${ACCENT(options.getValue(TAG))} ${options.getValue(TAG) === TAG.defaultValue ? "(default)" : ""}\n`);
795
+ LOG(`-----------------------\n`);
796
+ };
797
+
798
+ //#endregion
799
+ //#region src/cli/instruction.ts
800
+ const INSTRUCTION = ROOT.createAction("instruction", "Internal use", [new ArgumentLike("data", {
801
+ defaultValue: null,
802
+ validator: new StringTypeValidator(),
803
+ description: "BASE64 Encoded data"
804
+ })]);
805
+ INSTRUCTION.action = async (_, base64) => {
806
+ let data;
807
+ try {
808
+ LOG(`Trying to transform BASE64 to string\n`);
809
+ const RAW = atob(base64);
810
+ LOG(`Parsing as JSON\n`);
811
+ data = JSON.parse(RAW);
812
+ LOG(`Checking data validity\n`);
813
+ if (!Array.isArray(data)) return void ERROR("INVALID INSTRUCTION FORMAT");
814
+ if (typeof data[0] !== "number" || !(data[0] in InstructionType)) return void ERROR("INVALID INSTRUCTION FORMAT");
815
+ } catch {
816
+ ERROR("INVALID INSTRUCTION FORMAT");
817
+ return;
818
+ }
819
+ switch (data[0]) {
820
+ case InstructionType.Copy:
821
+ if (!await copyFile(data[1], data[2]).then((_$1) => true, (_$1) => false)) {
822
+ setTimeout(() => null, 5e3);
823
+ ERROR("FAILED TO COPY THIS ITEM: " + data[1]);
824
+ return;
825
+ }
826
+ break;
827
+ default:
828
+ ERROR("Unknown instruction");
829
+ return;
830
+ }
831
+ };
832
+ let InstructionType = /* @__PURE__ */ function(InstructionType$1) {
833
+ InstructionType$1[InstructionType$1["Copy"] = 1] = "Copy";
834
+ return InstructionType$1;
835
+ }({});
836
+ function createCommand(...args) {
837
+ const [EXECUTABLE, SELF_CLI] = Bun.argv;
838
+ return [
839
+ EXECUTABLE,
840
+ SELF_CLI,
841
+ INSTRUCTION.name,
842
+ btoa(JSON.stringify(args))
843
+ ];
844
+ }
845
+
846
+ //#endregion
847
+ //#region src/pwsh-helper.ts
848
+ var PowershellHelper = class PowershellHelper {
849
+ static POWERSHELL_EXECUTABLE = "powershell";
850
+ static async GetAppxPackage(packageNameLike) {
851
+ LOG(`Requesting Packages\n`);
852
+ let { output, exitCode } = await PowershellHelper.runRawCommand(`((Get-AppxPackage -Name ${packageNameLike}) | ForEach-Object { "$($_.PackageFamilyName);$($_.Version);$($_.InstallLocation)" })`);
853
+ if (exitCode != 0) return null;
854
+ LOG(`Parsing and Resoling Installation Paths\n`);
855
+ return output?.trim().split("\n").map((e) => {
856
+ const [PackageFamilyName, Version, InstallationPath] = e.trim().split(";");
857
+ return {
858
+ PackageFamilyName,
859
+ Version,
860
+ InstallationPath
861
+ };
862
+ }).filter((e) => e?.PackageFamilyName) ?? null;
863
+ }
864
+ static async runRawCommand(command) {
865
+ LOG(`Spawning PWSH command\n`);
866
+ const process$1 = Bun.spawn({
867
+ cmd: [
868
+ this.POWERSHELL_EXECUTABLE,
869
+ "-NoProfile",
870
+ "-NonInteractive",
871
+ "-Command",
872
+ command
873
+ ],
874
+ stdout: "pipe",
875
+ stderr: "pipe",
876
+ stdin: "ignore"
877
+ });
878
+ LOG(`Waiting for exit\n`);
879
+ const stdout = await new Response(process$1.stdout).text();
880
+ const stderr = await new Response(process$1.stderr).text();
881
+ const code = await process$1.exited;
882
+ LOG(`PWSH Process exited with code: ${code}\n`);
883
+ return {
884
+ exitCode: code,
885
+ output: code === 0 ? stdout : stderr.length > 0 ? stderr : null
886
+ };
887
+ }
888
+ static async InvokeCommandInDesktopPackage(packageFamilyName, appId, executable, args) {
889
+ let cmd = `Invoke-CommandInDesktopPackage -PackageFamilyName ${packageFamilyName} -AppId ${appId} -Command ${JSON.stringify(executable)}`;
890
+ if (args.length > 0) cmd += ` -Args '${args.map((_) => `"${_}"`).join(" ")}'`;
891
+ await this.runRawCommand(cmd);
892
+ }
893
+ };
894
+
895
+ //#endregion
896
+ //#region src/cli/archive.ts
897
+ const RECURSIVE_GLOB = new Bun.Glob("**/*");
898
+ const ARCHIVE = ROOT.createAction("archive", "Archive current version", [new ArgumentLike("package-name", {
899
+ defaultValue: "*minecraftuwp*",
900
+ validator: new StringTypeValidator(),
901
+ description: "Minecraft GDK Installation Package Name Pattern (*minecraftuwp*)"
902
+ })]);
903
+ const FORCE_TAG = new Flag("force", {
904
+ long: "force",
905
+ description: "Forces to overwrite archive folder if expected version is already archived."
906
+ });
907
+ const CONCURRENCY_TAG = new ValueFlag("concurrency", {
908
+ short: "c",
909
+ defaultValue: 10,
910
+ validator: new NumberTypeValidator(),
911
+ long: "concurrency",
912
+ description: "Forces to overwrite archive folder if expected version is already archived."
913
+ });
914
+ ARCHIVE.flags.add(FORCE_TAG, CONCURRENCY_TAG);
915
+ ARCHIVE.action = async (_, packageName) => {
916
+ const isForced = _.getValue(FORCE_TAG);
917
+ INFO(`TARGET: ${ACCENT(packageName)}\n`);
918
+ INFO(`----------------------------\n`);
919
+ const data = await PowershellHelper.GetAppxPackage(packageName);
920
+ LOG(`Found ${data?.length ?? 0} matches. . .\n`);
921
+ if (!data || !data.length) return void ERROR("Failed to obtain installation\n");
922
+ const packageInfo = data[0];
923
+ if (!packageInfo) {
924
+ ERROR("FAILED TO RESOLVE PACKAGE\n");
925
+ ERROR(`Minecraft Package Not Found: Looking for preview? Run with *minecraftwindowsbeta*\n`);
926
+ return;
927
+ }
928
+ const { InstallationPath, PackageFamilyName, Version } = packageInfo;
929
+ if (!PackageFamilyName.toLowerCase().includes("minecraft")) return void ERROR(`Minecraft Package Not Found: Looking for preview? Run with *minecraftwindowsbeta*\n`);
930
+ LOG(`RESOLVING REAL PATH FOR: ${JSON.stringify(InstallationPath)}\n`);
931
+ LOG(`----------------------------\n`);
932
+ let path = await realpath(InstallationPath).catch((_$1) => null);
933
+ if (!path) {
934
+ ERROR("FAILED TO RESOLVE INSTALLATION PATH\n");
935
+ return;
936
+ }
937
+ INFO(`NAME: ${ACCENT(PackageFamilyName)}\n`);
938
+ INFO(`VERSION: ${ACCENT(realVersion(Version))}\n`);
939
+ INFO(`PATH: ${ACCENT(toURL(path))}\n`);
940
+ const REAL_VERSION = realVersion(Version);
941
+ const appId = await getPackageApplicationId(path);
942
+ if (!appId) return void ERROR("FAILED TO GET APPLICATION ID");
943
+ INFO(`APPID: ${ACCENT(appId)}\n`);
944
+ const isPreview = PackageFamilyName.toLowerCase().match(/beta|preview|candidate/)?.[0];
945
+ const name = `minecraft${isPreview ? "-" + isPreview : ""}_${REAL_VERSION}`;
946
+ ENVIRONMENT_INFO(_);
947
+ const ARCHIVE_FOLDER = join(MIRRORS_FOLDER_PATH, name);
948
+ let OLD_FILES;
949
+ const KNOWN_FOLDERS = /* @__PURE__ */ new Set();
950
+ const KNOWN_FILES = /* @__PURE__ */ new Set();
951
+ if (existsSync(ARCHIVE_FOLDER)) if (!isForced) return void ERROR("This version is already archived, if you are sure you can use --force tag to rewrite this version.\n");
952
+ else OLD_FILES = new Set(Array.from(RECURSIVE_GLOB.scanSync({
953
+ cwd: ARCHIVE_FOLDER,
954
+ dot: true,
955
+ absolute: false,
956
+ onlyFiles: true,
957
+ followSymlinks: false
958
+ })).map((e) => e.toLowerCase()));
959
+ else {
960
+ LOG(`Creating archive folder for this version, ${name}\n`);
961
+ if (!await mkdir(ARCHIVE_FOLDER, { recursive: true }).then((_$1) => true, (_$1) => false)) return void ERROR("Something went wrong when creating folder\n");
962
+ OLD_FILES = /* @__PURE__ */ new Set();
963
+ }
964
+ INFO(`Invoking external process to decrypt the main executable and move it to archive\n`);
965
+ const EXE_PATH = "Minecraft.Windows.exe";
966
+ const SRC_EXE_PATH = join(path, EXE_PATH);
967
+ const FINAL_EXE_PATH = join(ARCHIVE_FOLDER, EXE_PATH);
968
+ const [runtime, ...args] = createCommand(InstructionType.Copy, SRC_EXE_PATH, FINAL_EXE_PATH);
969
+ await PowershellHelper.InvokeCommandInDesktopPackage(PackageFamilyName, appId, runtime, args);
970
+ const startTime = performance.now();
971
+ await new Promise((res) => setTimeout(res, 1e3));
972
+ LOG(`ORIGINAL STATS\n`);
973
+ let { size } = await stat(SRC_EXE_PATH);
974
+ while (performance.now() - startTime > 15 * 1e3) {
975
+ LOG(`WAITING FOR NEW STATS TO MATCH ORIGINAL\n`);
976
+ await new Promise((res) => setTimeout(res, 3e3));
977
+ if ((await stat(FINAL_EXE_PATH).catch((_$1) => null))?.size === size) break;
978
+ }
979
+ INFO(`Successfully copied encrypted file: ${EXE_PATH}\n`);
980
+ KNOWN_FILES.add(EXE_PATH.toLowerCase());
981
+ const concurrency = _.getValue(CONCURRENCY_TAG);
982
+ if (concurrency < 1) WARN(`Concurrency can not be less than 1!!!\n`);
983
+ const channel = new TaskConcurrencyChannel(concurrency);
984
+ let failedFiles = [];
985
+ const FILTER = /(gamelaunchhelper.exe|layout.xml|layout_[^.\\\/]+.xml|minecraft.windows.exe)$/;
986
+ let files = 0;
987
+ for await (const file of RECURSIVE_GLOB.scan({
988
+ cwd: path,
989
+ absolute: false,
990
+ dot: true,
991
+ followSymlinks: false,
992
+ onlyFiles: true
993
+ })) {
994
+ files++;
995
+ if (FILTER.test(file.toLowerCase())) continue;
996
+ let lio = file.lastIndexOf("\\");
997
+ const folder = file.substring(0, lio);
998
+ if (lio > 0 && !KNOWN_FOLDERS.has(folder)) {
999
+ const ABSOLUTE_FOLDER = join(ARCHIVE_FOLDER, folder);
1000
+ INFO(`\x1b[36m${folder}\n`);
1001
+ if (!existsSync(ABSOLUTE_FOLDER) && !await mkdir(join(ARCHIVE_FOLDER, folder), { recursive: true }).then((_$1) => true, (_$1) => false)) return void ERROR(`FAILED TO CREATE FOLDER IN ARCHIVE PATHS, MAYBE ITS TOO LONG? ${ARCHIVE_FOLDER.length + folder.length + 1}, ${folder}\n`);
1002
+ KNOWN_FOLDERS.add(folder);
1003
+ }
1004
+ await channel.push((async () => {
1005
+ if (!await copyFile(join(path, file), join(ARCHIVE_FOLDER, file)).then((_$1) => true, (_$1) => false)) failedFiles.push(file);
1006
+ else KNOWN_FILES.add(file.toLowerCase());
1007
+ })());
1008
+ }
1009
+ await channel.getAwaiter();
1010
+ if (failedFiles.length) {
1011
+ ERROR(`Something went wrong when coping these specific files: \n${failedFiles.join("\n")}\n`);
1012
+ return;
1013
+ }
1014
+ if (isForced) WARN(`FILES TO REMOVE: ${OLD_FILES.difference(KNOWN_FILES).size}\n`);
1015
+ for (const file of OLD_FILES.difference(KNOWN_FILES)) {
1016
+ WARN(`Removing ${ACCENT(file)}\n`);
1017
+ await channel.push(rm(join(ARCHIVE_FOLDER, file)));
1018
+ }
1019
+ await channel.getAwaiter();
1020
+ INFO(`TOTAL FILES ARCHIVED: ${ACCENT(String(files))}\n`);
1021
+ const tags = [_.getValue(TAG).toLowerCase()];
1022
+ if (tags[0] !== TAG.defaultValue) tags.push(TAG.defaultValue?.toLowerCase() ?? "current");
1023
+ for (let tag of tags) {
1024
+ tag = join(TAGS_FOLDER_PATH, tag);
1025
+ LOG(`REMOVING OLD TAG: ${tag}\n`);
1026
+ await rmdir(tag).catch((_$1) => null);
1027
+ LOG(`CREATING NEW TAG: ${_.getValue(TAG).toLowerCase()}\n`);
1028
+ await symlink(ARCHIVE_FOLDER, tag, "junction").catch((_$1) => null);
1029
+ }
1030
+ INFO(`OUTPUT: ${ACCENT(pathToFileURL(join(TAGS_FOLDER_PATH, tags[0])).href)}\n`);
1031
+ };
1032
+ function realVersion(version) {
1033
+ const [MA = 1, MI = 0, B = 0, P = 0] = version.split(".").map(Number);
1034
+ return [
1035
+ MA,
1036
+ MI,
1037
+ Math.floor(B / 100),
1038
+ P + B % 100
1039
+ ].join(".");
1040
+ }
1041
+ function toURL(path) {
1042
+ return `file:///${encodeURI(path.replaceAll("\\", "/"))}`;
1043
+ }
1044
+ async function getPackageApplicationId(installationPath) {
1045
+ LOG(`Trying to read file: /AppxManifest.xml\n`);
1046
+ return await readFile(join(installationPath, "AppxManifest.xml")).then((_) => {
1047
+ LOG(`Trying to search for application id\n`);
1048
+ const id = _.toString().match(/<Application(?:\s+[^>]*)??\s+Id\s*=\s*["']([^"']+)["']/is)?.[1];
1049
+ if (!id) {
1050
+ LOG(`FAILED TO SEARCH APPLICATION ID`);
1051
+ return null;
1052
+ }
1053
+ return id;
1054
+ }).catch((_) => (LOG(`FAILED TO READ FILE OR FAILED TO SEARCH FOR APPLICATION ID`), null));
1055
+ }
1056
+
1057
+ //#endregion
1058
+ //#region src/cli/list.ts
1059
+ const LIST = ROOT.createAction("list", "List your installations", []);
1060
+ LIST.action = async (_) => {
1061
+ ENVIRONMENT_INFO(_);
1062
+ INFO(`----------------------------\n`);
1063
+ INFO(`MIRRORS:\n`);
1064
+ for await (const folder of new Bun.Glob("*").scan({
1065
+ cwd: PREFERRED_PATH + "\\mirrors",
1066
+ onlyFiles: false
1067
+ })) INFO(` ${ACCENT(folder)}\n`);
1068
+ INFO(`\n`);
1069
+ INFO(`TAGS:\n`);
1070
+ for await (const folder of new Bun.Glob("*").scan({
1071
+ cwd: PREFERRED_PATH + "\\tags",
1072
+ onlyFiles: false
1073
+ })) INFO(` ${ACCENT(folder)}\n`);
1074
+ INFO(`\n`);
1075
+ INFO(`----------------------------\n`);
1076
+ };
1077
+
1078
+ //#endregion
1079
+ //#region src/cli/run.ts
1080
+ const RUN = ROOT.createAction("run", "Start new minecraft instance from tag or mirror version id", [new ArgumentLike("package-name", {
1081
+ defaultValue: "",
1082
+ validator: new StringTypeValidator(),
1083
+ description: "Minecraft GDK Installation Mirror Id if empty then tag is used"
1084
+ })]);
1085
+ RUN.action = async (_, packageName) => {
1086
+ ENVIRONMENT_INFO(_);
1087
+ Bun.spawn({
1088
+ cmd: [TAGS_FOLDER_PATH + "\\" + _.getValue(TAG) + "\\Minecraft.Windows.exe"],
1089
+ detached: true,
1090
+ stdio: [
1091
+ "ignore",
1092
+ "ignore",
1093
+ "ignore"
1094
+ ]
1095
+ }).unref();
1096
+ };
1097
+
1098
+ //#endregion
1099
+ //#region src/cli/main.ts
1100
+ CommandLine.run(Bun.argv, ROOT);
1101
+
1102
+ //#endregion
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "gdk-version-archiver",
3
+ "version": "1.0.0-alpha",
4
+ "description": "",
5
+ "keywords": [],
6
+ "license": "MIT",
7
+ "author": "conmaster2112",
8
+ "bin": {
9
+ "gdkva": "./bin/cli"
10
+ },
11
+ "files": [
12
+ "bin/cli",
13
+ "dist/*",
14
+ "package.json",
15
+ "README.md",
16
+ "LICENSE"
17
+ ],
18
+ "type": "module",
19
+ "scripts": {
20
+ "build": "rolldown -c",
21
+ "gdk-archive-manager": "rolldown -c && bun ./dist/cli.js"
22
+ },
23
+ "devDependencies": {
24
+ "@types/bun": "^1.3.5",
25
+ "@typescript/native-preview": "7.0.0-dev.20260108.1",
26
+ "con-utils": "^0.0.2",
27
+ "oxfmt": "^0.23.0",
28
+ "oxlint": "^1.38.0",
29
+ "rolldown": "1.0.0-beta.59",
30
+ "rolldown-plugin-dts": "^0.20.0",
31
+ "typescript": "6.0.0-dev.20260108"
32
+ },
33
+ "engines": {
34
+ "bun": ">=1.0.0",
35
+ "node": "please-use-bun"
36
+ },
37
+ "packageManager": "pnpm@10.25.0"
38
+ }