hackmud-script-manager 0.19.0-7c69a3b → 0.19.0-b5e2c0b

Sign up to get free protection for your applications and to get access to all the features.
package/bin/hsm.js CHANGED
@@ -1,683 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import { DynamicMap } from '@samual/lib/DynamicMap';
3
- import { assert } from '@samual/lib/assert';
4
- import { countHackmudCharacters } from '@samual/lib/countHackmudCharacters';
5
- import { writeFilePersistent } from '@samual/lib/writeFilePersistent';
6
- import { readFile, writeFile, mkdir, rmdir } from 'fs/promises';
7
- import { homedir } from 'os';
8
- import { supportedExtensions } from '../constants.js';
9
- import { generateTypeDeclaration } from '../generateTypeDeclaration.js';
10
- import { pull } from '../pull.js';
11
- import { syncMacros } from '../syncMacros.js';
12
- import { resolve, extname, basename, dirname, relative } from 'path';
13
- import '@samual/lib/copyFilePersistent';
14
-
15
- const version = "0.19.0-7c69a3b";
16
-
17
- /* | ArgValue[]*/
18
-
19
- const configDirectoryPath = resolve(homedir(), `.config`);
20
- const configFilePath = resolve(configDirectoryPath, `hsm.json`);
21
- const options = new Map();
22
- const commands = [];
23
- const userColours = new DynamicMap(user => {
24
- let hash = 0;
25
- for (const char of user) hash += (hash >> 1) + hash + `xi1_8ratvsw9hlbgm02y5zpdcn7uekof463qj`.indexOf(char) + 1;
26
- return [colourJ, colourK, colourM, colourW, colourL, colourB][hash % 6](user);
27
- });
28
- const logNeedHackmudPathMessage = () => console.error(colourS(`\
29
- ${colourD(`You need to set hackmudPath in config before you can use this command`)}
30
-
31
- ${colourA(`To fix this:`)}
32
- Open hackmud and run "${colourC(`#dir`)}"
33
- This will open a file browser and print your hackmud user's script directory
34
- Go up 2 directories and then copy the path
35
- Then in a terminal run "${colourC(`hsm`)} ${colourL(`config set`)} ${colourV(`hackmudPath`)} ${colourB(`<the path you copied>`)}"`));
36
- const logHelp = () => {
37
- const pushCommandDescription = `Push scripts from a directory to hackmud user's scripts directories`;
38
- const watchCommandDescription = `Watch a directory and push a script when modified`;
39
- const minifyCommandDescription = `Minify a script file on the spot`;
40
- const generateTypeDeclarationCommandDescription = `Generate a type declaration file for a directory of scripts`;
41
- const syncMacrosCommandDescription = `Sync macros across all hackmud users`;
42
- const configCommandDescription = `Modify and view the config file`;
43
- const configGetCommandDescription = `Retrieve a value from the config file`;
44
- const configSetCommandDescription = `Assign a value to the config file`;
45
- const configDeleteCommandDescription = `Remove a key and value from the config file`;
46
- const pullCommandDescription = `Pull a script a from a hackmud user's script directory`;
47
- const skipMinifyOptionDescription = `Skip minification to produce a readable script`;
48
- const mangleNamesOptionDescription = `Reduce character count further but lose function names in error call stacks`;
49
- const forceQuineCheatsOptionDescription = `Force quine cheats even if the character count is higher`;
50
- console.log(colourN(`Version`) + colourS(`: `) + colourV(version));
51
- switch (commands[0]) {
52
- case `config`:
53
- {
54
- switch (commands[1]) {
55
- case `get`:
56
- {
57
- console.log(`
58
- ${colourJ(configGetCommandDescription)}
59
-
60
- ${colourA(`Usage:`)}
61
- ${colourC(`hsm`)} ${colourL(`${commands[0]} ${commands[1]}`)} ${colourB(`<key>`)}`);
62
- }
63
- break;
64
- case `set`:
65
- {
66
- console.log(`
67
- ${colourJ(configSetCommandDescription)}
68
-
69
- ${colourA(`Usage:`)}
70
- ${colourC(`hsm`)} ${colourL(`${commands[0]} ${commands[1]}`)} ${colourB(`<key> <value>`)}`);
71
- }
72
- break;
73
- case `delete`:
74
- {
75
- console.log(`
76
- ${colourJ(configDeleteCommandDescription)}
77
-
78
- ${colourA(`Usage:`)}
79
- ${colourC(`hsm`)} ${colourL(`${commands[0]} ${commands[1]}`)} ${colourB(`<key>`)}`);
80
- }
81
- break;
82
- default:
83
- {
84
- console.log(colourS(`\
85
- ${colourN(`Config path`)}: ${colourV(configFilePath)}
86
-
87
- ${colourJ(`Modify the config file`)}
88
-
89
- ${colourA(`Usage:`)}
90
- ${colourC(`hsm`)} ${colourL(`${commands[0]} get`)} ${colourB(`<key>`)}
91
- ${configGetCommandDescription}
92
- ${colourC(`hsm`)} ${colourL(`${commands[0]} set`)} ${colourB(`<key> <value>`)}
93
- ${configSetCommandDescription}
94
- ${colourC(`hsm`)} ${colourL(`${commands[0]} delete`)} ${colourB(`<key>`)}
95
- ${configDeleteCommandDescription}`));
96
- }
97
- }
98
- }
99
- break;
100
- case `push`:
101
- {
102
- console.log(colourS(`
103
- ${colourJ(pushCommandDescription)}
104
-
105
- ${colourA(`Usage:`)}
106
- ${colourC(`hsm`)} ${colourL(commands[0])} ${colourB(`<directory> [<script user>.<script name>...]`)}
107
-
108
- ${colourA(`Options:`)}
109
- ${colourN(`--skip-minify`)}
110
- ${skipMinifyOptionDescription}
111
- ${colourN(`--mangle-names`)}
112
- ${mangleNamesOptionDescription}
113
- ${colourN(`--force-quine-cheats`)}
114
- ${forceQuineCheatsOptionDescription}`));
115
- }
116
- break;
117
- case `dev`:
118
- case `watch`:
119
- {
120
- console.log(colourS(`\
121
- ${colourN(`Aliases`)}: ${colourV(`watch, dev`)}
122
-
123
- ${colourJ(watchCommandDescription)}
124
-
125
- ${colourA(`Usage:`)}
126
- ${colourC(`hsm`)} ${colourL(commands[0])} ${colourB(`<directory> [<script user>.<script name>...]`)}
127
-
128
- ${colourA(`Options:`)}
129
- ${colourN(`--skip-minify`)}
130
- ${skipMinifyOptionDescription}
131
- ${colourN(`--mangle-names`)}
132
- ${mangleNamesOptionDescription}
133
- ${colourN(`--type-declaration-path`)}=${colourB(`<path>`)}
134
- Path to generate a type declaration file for the scripts
135
- ${colourN(`--force-quine-cheats`)}
136
- ${forceQuineCheatsOptionDescription}`));
137
- }
138
- break;
139
- case `pull`:
140
- {
141
- console.log(colourS(`
142
- ${colourJ(pullCommandDescription)}
143
-
144
- ${colourA(`Usage:`)}
145
- ${colourC(`hsm`)} ${colourL(commands[0])} ${colourB(`<script user>`)}${colourV(`.`)}${colourB(`<script name>`)}`));
146
- }
147
- break;
148
- case `minify`:
149
- case `golf`:
150
- {
151
- console.log(colourS(`\
152
- ${colourN(`Aliases`)}: ${colourV(`minify, golf`)}
153
-
154
- ${colourJ(minifyCommandDescription)}
155
-
156
- ${colourA(`Usage:`)}
157
- ${colourC(`hsm`)} ${colourL(commands[0])} ${colourB(`<target> [output path]`)}
158
-
159
- ${colourA(`Options:`)}
160
- ${colourN(`--skip-minify`)}
161
- ${skipMinifyOptionDescription}
162
- ${colourN(`--mangle-names`)}
163
- ${mangleNamesOptionDescription}
164
- ${colourN(`--force-quine-cheats`)}
165
- ${forceQuineCheatsOptionDescription}
166
- ${colourN(`--watch`)}
167
- Watch for changes`));
168
- }
169
- break;
170
- case `generate-type-declaration`:
171
- case `gen-type-declaration`:
172
- case `gen-dts`:
173
- case `gen-types`:
174
- {
175
- console.log(colourS(`\
176
- ${colourN(`Aliases`)}: ${colourV(`generate-type-declaration, gen-type-declaration, gen-types, gen-dts`)}
177
-
178
- ${colourJ(generateTypeDeclarationCommandDescription)}
179
-
180
- ${colourA(`Usage:`)}
181
- ${colourC(`hsm`)} ${colourL(commands[0])} ${colourB(`<directory> [output path]`)}`));
182
- }
183
- break;
184
- case `sync-macros`:
185
- {
186
- console.log(`\n${colourJ(syncMacrosCommandDescription)}`);
187
- }
188
- break;
189
- default:
190
- {
191
- console.log(colourS(`
192
- ${colourJ(`Hackmud Script Manager`)}
193
-
194
- ${colourA(`Commands:`)}
195
- ${colourL(`push`)}
196
- ${pushCommandDescription}
197
- ${colourL(`watch`)}, ${colourL(`dev`)}
198
- ${watchCommandDescription}
199
- ${colourL(`minify`)}, ${colourL(`golf`)}
200
- ${minifyCommandDescription}
201
- ${colourL(`generate-type-declaration`)}, ${colourL(`gen-type-declaration`)}, ${colourL(`gen-types`)}, ${colourL(`gen-dts`)}
202
- ${generateTypeDeclarationCommandDescription}
203
- ${colourL(`sync-macros`)}
204
- ${syncMacrosCommandDescription}
205
- ${colourL(`config`)}
206
- ${configCommandDescription}
207
- ${colourL(`pull`)}
208
- ${pullCommandDescription}`));
209
- }
210
- }
211
- };
212
- const exploreObject = (object, keys, createPath = false) => {
213
- for (const key of keys) {
214
- if (createPath) object = typeof object[key] == `object` ? object[key] : object[key] = {};else object = object?.[key];
215
- }
216
- return object;
217
- };
218
- const updateConfig = async config => {
219
- const json = JSON.stringify(config, undefined, `\t`);
220
- if (configDidNotExist) log(`Creating config file at ${configFilePath}`);
221
- await writeFile(configFilePath, json).catch(async error => {
222
- switch (error.code) {
223
- case `EISDIR`:
224
- {
225
- await rmdir(configFilePath);
226
- }
227
- break;
228
- case `ENOENT`:
229
- {
230
- await mkdir(configDirectoryPath);
231
- }
232
- break;
233
- default:
234
- throw error;
235
- }
236
- await writeFile(configFilePath, json);
237
- });
238
- };
239
- const logInfo = ({
240
- file,
241
- users,
242
- minLength,
243
- error
244
- }, hackmudPath) => {
245
- if (error) {
246
- logError(`error "${chalk.bold(error.message)}" in ${chalk.bold(file)}`);
247
- return;
248
- }
249
- console.log(`pushed ${chalk.bold(file)} to ${users.map(user => chalk.bold(userColours.get(user))).join(`, `)} | ${chalk.bold(String(minLength))} chars | ${chalk.bold(`${resolve(hackmudPath, users[0], `scripts`, basename(file, extname(file)))}.js`)}`);
250
- };
251
- const log = message => {
252
- console.log(colourS(message));
253
- };
254
- const logError = message => {
255
- console.error(colourD(message));
256
- process.exitCode = 1;
257
- };
258
- for (const argument of process.argv.slice(2)) {
259
- if (argument[0] == `-`) {
260
- const [key, valueRaw] = argument.split(`=`);
261
- let value = valueRaw;
262
- if (value) {
263
- if (value == `true`) value = true;else if (value == `false`) value = false;else {
264
- const number = Number(value);
265
- if (isFinite(number)) value = number;
266
- }
267
- } else value = true;
268
- if (argument[1] == `-`) options.set(key.slice(2), value);else {
269
- for (const option of key.slice(1)) options.set(option, value);
270
- }
271
- } else commands.push(argument);
272
- }
273
- if (commands[0] == `v` || commands[0] == `version` || options.get(`version`) || options.get(`v`)) {
274
- console.log(version);
275
- process.exit();
276
- }
277
- let configDidNotExist = false;
278
- const configPromise = readFile(configFilePath, {
279
- encoding: `utf-8`
280
- }).then(configFile => {
281
- let temporaryConfig;
282
- try {
283
- temporaryConfig = JSON.parse(configFile);
284
- } catch {
285
- // TODO log to error log file
286
- log(`Config file was corrupted, resetting`);
287
- return {};
288
- }
289
- if (!temporaryConfig || typeof temporaryConfig != `object`) {
290
- log(`Config file was corrupted, resetting`);
291
- return {};
292
- }
293
- if (`hackmudPath` in temporaryConfig && typeof temporaryConfig.hackmudPath != `string`) {
294
- log(`Property "hackmudPath" of config file was corrupted, removing`);
295
- delete temporaryConfig.hackmudPath;
296
- }
297
- return temporaryConfig;
298
- }, () => {
299
- configDidNotExist = true;
300
- return {};
301
- });
302
- const pushModule = import('../push.js');
303
- const processScriptModule = import('../processScript/index.js');
304
- const watchModule = import('../watch.js');
305
- const chokidarModule = import('chokidar');
306
- const {
307
- default: chalk
308
- } = await import('chalk');
309
- const colourA = chalk.rgb(0xFF, 0xFF, 0xFF);
310
- const colourB = chalk.rgb(0xCA, 0xCA, 0xCA);
311
- const colourC = chalk.rgb(0x9B, 0x9B, 0x9B);
312
- const colourD = chalk.rgb(0xFF, 0x00, 0x00);
313
- const colourJ = chalk.rgb(0xFF, 0xF4, 0x04);
314
- const colourK = chalk.rgb(0xF3, 0xF9, 0x98);
315
- const colourL = chalk.rgb(0x1E, 0xFF, 0x00);
316
- const colourM = chalk.rgb(0xB3, 0xFF, 0x9B);
317
- const colourN = chalk.rgb(0x00, 0xFF, 0xFF);
318
- const colourS = chalk.rgb(0x7A, 0xB2, 0xF4);
319
- const colourV = chalk.rgb(0xFF, 0x00, 0xEC);
320
- const colourW = chalk.rgb(0xFF, 0x96, 0xE0);
321
- if (options.get(`help`) || options.get(`h`)) {
322
- logHelp();
323
- process.exit();
324
- }
325
- let autoExit = true;
326
- switch (commands[0]) {
327
- case `push`:
328
- {
329
- const {
330
- hackmudPath
331
- } = await configPromise;
332
- if (!hackmudPath) {
333
- logNeedHackmudPathMessage();
334
- break;
335
- }
336
- const sourcePath = commands[1];
337
- if (!sourcePath) {
338
- logError(`Must provide the directory to push from\n`);
339
- logHelp();
340
- break;
341
- }
342
- const scripts = commands.slice(2);
343
- if (scripts.length) {
344
- const invalidScript = scripts.find(script => !/^(?:[a-z_][a-z\d_]{0,24}|\*)\.(?:[a-z_][a-z\d_]{0,24}|\*)$/.test(script));
345
- if (invalidScript) {
346
- logError(`Invalid script name: ${JSON.stringify(invalidScript)}\n`);
347
- logHelp();
348
- break;
349
- }
350
- } else scripts.push(`*.*`);
351
- if (options.has(`skip-minify`) && options.has(`mangle-names`)) {
352
- logError(`Option ${colourN(`--mangle-names`)} is not compatible with ${colourN(`--skip-minify`)}\n`);
353
- logHelp();
354
- break;
355
- }
356
- const shouldSkipMinify = options.get(`skip-minify`);
357
- let shouldMinify;
358
- if (shouldSkipMinify != undefined) {
359
- if (typeof shouldSkipMinify != `boolean`) {
360
- logError(`The value for ${colourN(`--skip-minify`)} must be ${colourV(`true`)} or ${colourV(`false`)}\n`);
361
- logHelp();
362
- break;
363
- }
364
- shouldMinify = !shouldSkipMinify;
365
- }
366
- const shouldMangleNames = options.get(`mangle-names`);
367
- if (shouldMangleNames != undefined && typeof shouldMangleNames != `boolean`) {
368
- logError(`The value for ${colourN(`--mangle-names`)} must be ${colourV(`true`)} or ${colourV(`false`)}\n`);
369
- logHelp();
370
- break;
371
- }
372
- const shouldforceQuineCheats = options.get(`force-quine-cheats`);
373
- if (shouldforceQuineCheats != undefined && typeof shouldforceQuineCheats != `boolean`) {
374
- logError(`The value for ${colourN(`--force-quine-cheats`)} must be ${colourV(`true`)} or ${colourV(`false`)}\n`);
375
- logHelp();
376
- break;
377
- }
378
- const {
379
- push
380
- } = await pushModule;
381
- const infos = await push(sourcePath, hackmudPath, {
382
- scripts,
383
- onPush: info => logInfo(info, hackmudPath),
384
- minify: shouldMinify,
385
- mangleNames: shouldMangleNames,
386
- forceQuineCheats: shouldforceQuineCheats
387
- });
388
- if (!infos.length) logError(`Could not find any scripts to push`);
389
- }
390
- break;
391
- case `dev`:
392
- case `watch`:
393
- {
394
- const {
395
- hackmudPath
396
- } = await configPromise;
397
- if (!hackmudPath) {
398
- logNeedHackmudPathMessage();
399
- break;
400
- }
401
- const sourcePath = commands[1];
402
- if (!sourcePath) {
403
- logError(`Must provide the directory to watch\n`);
404
- logHelp();
405
- break;
406
- }
407
- const scripts = commands.slice(2);
408
- if (scripts.length) {
409
- const invalidScript = scripts.find(script => !/^(?:[a-z_][a-z\d_]{0,24}|\*)\.(?:[a-z_][a-z\d_]{0,24}|\*)$/.test(script));
410
- if (invalidScript) {
411
- logError(`Invalid script name: ${JSON.stringify(invalidScript)}\n`);
412
- logHelp();
413
- break;
414
- }
415
- } else scripts.push(`*.*`);
416
- if (options.has(`skip-minify`) && options.has(`mangle-names`)) {
417
- logError(`Option ${colourN(`--mangle-names`)} is not compatible with ${colourN(`--skip-minify`)}\n`);
418
- logHelp();
419
- break;
420
- }
421
- const shouldSkipMinify = options.get(`skip-minify`);
422
- let shouldMinify;
423
- if (shouldSkipMinify != undefined) {
424
- if (typeof shouldSkipMinify != `boolean`) {
425
- logError(`The value for ${colourN(`--skip-minify`)} must be ${colourV(`true`)} or ${colourV(`false`)}\n`);
426
- logHelp();
427
- break;
428
- }
429
- shouldMinify = !shouldSkipMinify;
430
- }
431
- const shouldMangleNames = options.get(`mangle-names`);
432
- if (shouldMangleNames != undefined && typeof shouldMangleNames != `boolean`) {
433
- logError(`The value for ${colourN(`--mangle-names`)} must be ${colourV(`true`)} or ${colourV(`false`)}\n`);
434
- logHelp();
435
- break;
436
- }
437
- const shouldforceQuineCheats = options.get(`force-quine-cheats`);
438
- if (shouldforceQuineCheats != undefined && typeof shouldforceQuineCheats != `boolean`) {
439
- logError(`The value for ${colourN(`--force-quine-cheats`)} must be ${colourV(`true`)} or ${colourV(`false`)}\n`);
440
- logHelp();
441
- break;
442
- }
443
- const {
444
- watch
445
- } = await watchModule;
446
- watch(sourcePath, hackmudPath, {
447
- scripts,
448
- onPush: info => logInfo(info, hackmudPath),
449
- typeDeclarationPath: (options.get(`type-declaration-path`) || options.get(`type-declaration`) || options.get(`dts`) || options.get(`gen-types`))?.toString(),
450
- minify: shouldMinify,
451
- mangleNames: shouldMangleNames,
452
- onReady: () => log(`Watching`),
453
- forceQuineCheats: shouldforceQuineCheats
454
- });
455
- autoExit = false;
456
- }
457
- break;
458
- case `pull`:
459
- {
460
- const {
461
- hackmudPath
462
- } = await configPromise;
463
- if (!hackmudPath) {
464
- logNeedHackmudPathMessage();
465
- break;
466
- }
467
- const script = commands[1];
468
- if (!script) {
469
- logError(`Must provide the script to pull\n`);
470
- logHelp();
471
- break;
472
- }
473
- const sourcePath = commands[2] || `.`;
474
- try {
475
- await pull(sourcePath, hackmudPath, script);
476
- } catch (error) {
477
- console.error(error);
478
- logError(`Something went wrong, did you forget to ${colourC(`#down`)} the script?`);
479
- }
480
- }
481
- break;
482
- case `sync-macros`:
483
- {
484
- const {
485
- hackmudPath
486
- } = await configPromise;
487
- if (!hackmudPath) {
488
- logNeedHackmudPathMessage();
489
- break;
490
- }
491
- const {
492
- macrosSynced,
493
- usersSynced
494
- } = await syncMacros(hackmudPath);
495
- log(`Synced ${macrosSynced} macros to ${usersSynced} users`);
496
- }
497
- break;
498
- case `generate-type-declaration`:
499
- case `gen-type-declaration`:
500
- case `gen-dts`:
501
- case `gen-types`:
502
- {
503
- const target = commands[1];
504
- if (!target) {
505
- logError(`Must provide target directory\n`);
506
- logHelp();
507
- break;
508
- }
509
- const sourcePath = resolve(target);
510
- const outputPath = commands[2] || `./player.d.ts`;
511
- const typeDeclaration = await generateTypeDeclaration(sourcePath, (await configPromise).hackmudPath);
512
- let typeDeclarationPath = resolve(outputPath);
513
- try {
514
- await writeFile(typeDeclarationPath, typeDeclaration);
515
- } catch (error) {
516
- assert(error instanceof Error);
517
- if (!(error.code == `EISDIR`)) throw error;
518
- typeDeclarationPath = resolve(typeDeclarationPath, `player.d.ts`);
519
- await writeFile(typeDeclarationPath, typeDeclaration);
520
- }
521
- log(`Wrote type declaration to ${chalk.bold(typeDeclarationPath)}`);
522
- }
523
- break;
524
- case `config`:
525
- {
526
- switch (commands[1]) {
527
- case `get`:
528
- {
529
- const key = commands[2];
530
- if (key) log(exploreObject(await configPromise, key.split(`.`)));else console.log(await configPromise);
531
- }
532
- break;
533
- case `delete`:
534
- {
535
- const key = commands[2];
536
- if (!key) {
537
- logError(`Must provide a key to delete\n`);
538
- logHelp();
539
- break;
540
- }
541
- const keyParts = key.split(`.`);
542
- const pathName = keyParts.map(name => /^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name)).join(`.`);
543
- const lastKey = keyParts.pop();
544
- const config = await configPromise;
545
- delete exploreObject(config, keyParts)?.[lastKey];
546
- log(`Removed ${colourV(pathName)} from config file`);
547
- }
548
- break;
549
- case `set`:
550
- {
551
- const key = commands[2];
552
- const value = commands[3];
553
- if (!key) {
554
- logError(`Must provide a key and value\n`);
555
- logHelp();
556
- break;
557
- }
558
- const keys = key.split(`.`);
559
- const pathName = keys.map(name => /^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name)).join(`.`);
560
- if (!value) {
561
- logError(`Must provide a value for the key ${pathName}\n`);
562
- logHelp();
563
- break;
564
- }
565
- const lastKey = keys.pop();
566
- const config = await configPromise;
567
- if (!keys.length && lastKey == `hackmudPath`) config.hackmudPath = resolve(value.startsWith(`~/`) ? homedir() + value.slice(1) : value);else {
568
- let object = config;
569
- for (const key of keys) {
570
- if (typeof object[key] == `object`) object = object[key];else {
571
- object[key] = {};
572
- object = object[key];
573
- }
574
- }
575
- object[lastKey] = value;
576
- }
577
- console.log(config);
578
- await updateConfig(config);
579
- }
580
- break;
581
- default:
582
- {
583
- if (commands[1]) logError(`Unknown command: ${JSON.stringify(commands[1])}\n`);
584
- logHelp();
585
- }
586
- }
587
- }
588
- break;
589
- case `help`:
590
- case `h`:
591
- {
592
- logHelp();
593
- }
594
- break;
595
- case `golf`:
596
- case `minify`:
597
- {
598
- const target = commands[1];
599
- if (!target) {
600
- logError(`Must provide target\n`);
601
- logHelp();
602
- break;
603
- }
604
- const fileExtension = extname(target);
605
- if (!supportedExtensions.includes(fileExtension)) {
606
- logError(`Unsupported file extension "${chalk.bold(fileExtension)}"\nSupported extensions are "${supportedExtensions.map(extension => chalk.bold(extension)).join(`", "`)}"`);
607
- break;
608
- }
609
- const {
610
- processScript
611
- } = await processScriptModule;
612
- const fileBaseName = basename(target, fileExtension);
613
- // eslint-disable-next-line unicorn/prevent-abbreviations -- the file extension is `src` not `source`
614
- const fileBaseNameEndsWithDotSrc = fileBaseName.endsWith(`.src`);
615
- const scriptName = fileBaseNameEndsWithDotSrc ? fileBaseName.slice(0, -4) : fileBaseName;
616
- const scriptUser = basename(resolve(target, `..`)) == `scripts` && basename(resolve(target, `../../..`)) == `hackmud` ? basename(resolve(target, `../..`)) : `UNKNOWN`;
617
- const minify = !options.get(`skip-minify`);
618
- if (options.has(`skip-minify`) && options.has(`mangle-names`)) {
619
- logError(`Option ${colourN(`--mangle-names`)} would have no effect if minification is skipped\n`);
620
- logHelp();
621
- break;
622
- }
623
- const mangleNames_ = options.get(`mangle-names`);
624
- if (mangleNames_ != undefined && typeof mangleNames_ != `boolean`) {
625
- logError(`The value for ${colourN(`--mangle-names`)} must be ${colourV(`true`)} or ${colourV(`false`)}\n`);
626
- logHelp();
627
- break;
628
- }
629
- const mangleNames = mangleNames_;
630
- const forceQuineCheats_ = options.get(`force-quine-cheats`);
631
- if (forceQuineCheats_ != undefined && typeof forceQuineCheats_ != `boolean`) {
632
- logError(`the value for ${colourN(`--force-quine-cheats`)} must be ${colourV(`true`)} or ${colourV(`false`)}\n`);
633
- logHelp();
634
- break;
635
- }
636
- const forceQuineCheats = forceQuineCheats_;
637
- let outputPath = commands[2] || resolve(dirname(target), fileBaseNameEndsWithDotSrc ? `${scriptName}.js` : fileExtension == `.js` ? `${fileBaseName}.min.js` : `${fileBaseName}.js`);
638
- const golfFile = () => readFile(target, {
639
- encoding: `utf-8`
640
- }).then(async source => {
641
- const timeStart = performance.now();
642
- const {
643
- script,
644
- warnings
645
- } = await processScript(source, {
646
- minify,
647
- scriptUser,
648
- scriptName,
649
- filePath: target,
650
- mangleNames,
651
- forceQuineCheats
652
- });
653
- const timeTook = performance.now() - timeStart;
654
- for (const {
655
- message,
656
- line
657
- } of warnings) log(`Warning "${chalk.bold(message)}" on line ${chalk.bold(String(line))}`);
658
- await writeFilePersistent(outputPath, script).catch(async error => {
659
- if (!commands[2] || error.code != `EISDIR`) throw error;
660
- outputPath = resolve(outputPath, `${basename(target, fileExtension)}.js`);
661
- await writeFilePersistent(outputPath, script);
662
- }).then(() => log(`Wrote ${chalk.bold(countHackmudCharacters(script))} chars to ${chalk.bold(relative(`.`, outputPath))} | took ${Math.round(timeTook * 100) / 100}ms`), error => logError(error.message));
663
- }, error => logError(error.message));
664
- if (options.get(`watch`)) {
665
- const {
666
- watch: watchFile
667
- } = await chokidarModule;
668
- watchFile(target, {
669
- awaitWriteFinish: {
670
- stabilityThreshold: 100
671
- }
672
- }).on(`ready`, () => log(`Watching ${target}`)).on(`change`, golfFile);
673
- autoExit = false;
674
- } else await golfFile();
675
- }
676
- break;
677
- default:
678
- {
679
- if (commands[0]) logError(`Unknown command: ${JSON.stringify(commands[0])}\n`);
680
- logHelp();
681
- }
682
- }
683
- if (autoExit) process.exit();
2
+ import{DynamicMap as e}from"@samual/lib/DynamicMap";import{assert as t}from"@samual/lib/assert";import{countHackmudCharacters as n}from"@samual/lib/countHackmudCharacters";import{writeFilePersistent as a}from"@samual/lib/writeFilePersistent";import{readFile as s,writeFile as o,mkdir as i,rmdir as r}from"fs/promises";import{homedir as c}from"os";import{supportedExtensions as l}from"../constants.js";import{generateTypeDeclaration as f}from"../generateTypeDeclaration.js";import{pull as $}from"../pull.js";import{syncMacros as p}from"../syncMacros.js";import{resolve as m,extname as h,basename as u,dirname as g,relative as d}from"path";import"@samual/lib/copyFilePersistent";const y="0.19.0-b5e2c0b",b=m(c(),".config"),k=m(b,"hsm.json"),w=new Map,v=[],S=new e((e=>{let t=0;for(const n of e)t+=(t>>1)+t+"xi1_8ratvsw9hlbgm02y5zpdcn7uekof463qj".indexOf(n)+1;return[_,x,J,F,I,z][t%6](e)})),logNeedHackmudPathMessage=()=>console.error(D(`${W("You need to set hackmudPath in config before you can use this command")}\n\n${q("To fix this:")}\nOpen hackmud and run "${C("#dir")}"\nThis will open a file browser and print your hackmud user's script directory\nGo up 2 directories and then copy the path\nThen in a terminal run "${C("hsm")} ${I("config set")} ${E("hackmudPath")} ${z("<the path you copied>")}"`)),logHelp=()=>{const e="Push scripts from a directory to hackmud user's scripts directories",t="Watch a directory and push a script when modified",n="Minify a script file on the spot",a="Generate a type declaration file for a directory of scripts",s="Sync macros across all hackmud users",o="Retrieve a value from the config file",i="Assign a value to the config file",r="Remove a key and value from the config file",c="Pull a script a from a hackmud user's script directory",l="Skip minification to produce a readable script",f="Reduce character count further but lose function names in error call stacks",$="Force quine cheats even if the character count is higher";switch(console.log(R("Version")+D(": ")+E(y)),v[0]){case"config":switch(v[1]){case"get":console.log(`\n${_(o)}\n\n${q("Usage:")}\n${C("hsm")} ${I(`${v[0]} ${v[1]}`)} ${z("<key>")}`);break;case"set":console.log(`\n${_(i)}\n\n${q("Usage:")}\n${C("hsm")} ${I(`${v[0]} ${v[1]}`)} ${z("<key> <value>")}`);break;case"delete":console.log(`\n${_(r)}\n\n${q("Usage:")}\n${C("hsm")} ${I(`${v[0]} ${v[1]}`)} ${z("<key>")}`);break;default:console.log(D(`${R("Config path")}: ${E(k)}\n\n${_("Modify the config file")}\n\n${q("Usage:")}\n${C("hsm")} ${I(`${v[0]} get`)} ${z("<key>")}\n ${o}\n${C("hsm")} ${I(`${v[0]} set`)} ${z("<key> <value>")}\n ${i}\n${C("hsm")} ${I(`${v[0]} delete`)} ${z("<key>")}\n ${r}`))}break;case"push":console.log(D(`\n${_(e)}\n\n${q("Usage:")}\n${C("hsm")} ${I(v[0])} ${z("<directory> [<script user>.<script name>...]")}\n\n${q("Options:")}\n${R("--skip-minify")}\n ${l}\n${R("--mangle-names")}\n ${f}\n${R("--force-quine-cheats")}\n ${$}`));break;case"dev":case"watch":console.log(D(`${R("Aliases")}: ${E("watch, dev")}\n\n${_(t)}\n\n${q("Usage:")}\n${C("hsm")} ${I(v[0])} ${z("<directory> [<script user>.<script name>...]")}\n\n${q("Options:")}\n${R("--skip-minify")}\n ${l}\n${R("--mangle-names")}\n ${f}\n${R("--type-declaration-path")}=${z("<path>")}\n Path to generate a type declaration file for the scripts\n${R("--force-quine-cheats")}\n ${$}`));break;case"pull":console.log(D(`\n${_(c)}\n\n${q("Usage:")}\n${C("hsm")} ${I(v[0])} ${z("<script user>")}${E(".")}${z("<script name>")}`));break;case"minify":case"golf":console.log(D(`${R("Aliases")}: ${E("minify, golf")}\n\n${_(n)}\n\n${q("Usage:")}\n${C("hsm")} ${I(v[0])} ${z("<target> [output path]")}\n\n${q("Options:")}\n${R("--skip-minify")}\n ${l}\n${R("--mangle-names")}\n ${f}\n${R("--force-quine-cheats")}\n ${$}\n${R("--watch")}\n Watch for changes`));break;case"generate-type-declaration":case"gen-type-declaration":case"gen-dts":case"gen-types":console.log(D(`${R("Aliases")}: ${E("generate-type-declaration, gen-type-declaration, gen-types, gen-dts")}\n\n${_(a)}\n\n${q("Usage:")}\n${C("hsm")} ${I(v[0])} ${z("<directory> [output path]")}`));break;case"sync-macros":console.log(`\n${_(s)}`);break;default:console.log(D(`\n${_("Hackmud Script Manager")}\n\n${q("Commands:")}\n${I("push")}\n ${e}\n${I("watch")}, ${I("dev")}\n ${t}\n${I("minify")}, ${I("golf")}\n ${n}\n${I("generate-type-declaration")}, ${I("gen-type-declaration")}, ${I("gen-types")}, ${I("gen-dts")}\n ${a}\n${I("sync-macros")}\n ${s}\n${I("config")}\n Modify and view the config file\n${I("pull")}\n ${c}`))}},exploreObject=(e,t,n=!1)=>{for(const a of t)e=n?"object"==typeof e[a]?e[a]:e[a]={}:e?.[a];return e},logInfo=({file:e,users:t,minLength:n,error:a},s)=>{a?logError(`error "${T.bold(a.message)}" in ${T.bold(e)}`):console.log(`pushed ${T.bold(e)} to ${t.map((e=>T.bold(S.get(e)))).join(", ")} | ${T.bold(String(n))} chars | ${T.bold(`${m(s,t[0],"scripts",u(e,h(e)))}.js`)}`)},log=e=>{console.log(D(e))},logError=e=>{console.error(W(e)),process.exitCode=1};for(const e of process.argv.slice(2))if("-"==e[0]){const[t,n]=e.split("=");let a=n;if(a)if("true"==a)a=!0;else if("false"==a)a=!1;else{const e=Number(a);isFinite(e)&&(a=e)}else a=!0;if("-"==e[1])w.set(t.slice(2),a);else for(const e of t.slice(1))w.set(e,a)}else v.push(e);("v"==v[0]||"version"==v[0]||w.get("version")||w.get("v"))&&(console.log(y),process.exit());let P=!1;const j=s(k,{encoding:"utf-8"}).then((e=>{let t;try{t=JSON.parse(e)}catch{return log("Config file was corrupted, resetting"),{}}return t&&"object"==typeof t?("hackmudPath"in t&&"string"!=typeof t.hackmudPath&&(log('Property "hackmudPath" of config file was corrupted, removing'),delete t.hackmudPath),t):(log("Config file was corrupted, resetting"),{})}),(()=>(P=!0,{}))),N=import("../push.js"),O=import("../processScript/index.js"),M=import("../watch.js"),U=import("chokidar"),{default:T}=await import("chalk"),q=T.rgb(255,255,255),z=T.rgb(202,202,202),C=T.rgb(155,155,155),W=T.rgb(255,0,0),_=T.rgb(255,244,4),x=T.rgb(243,249,152),I=T.rgb(30,255,0),J=T.rgb(179,255,155),R=T.rgb(0,255,255),D=T.rgb(122,178,244),E=T.rgb(255,0,236),F=T.rgb(255,150,224);(w.get("help")||w.get("h"))&&(logHelp(),process.exit());let A=!0;switch(v[0]){case"push":{const{hackmudPath:e}=await j;if(!e){logNeedHackmudPathMessage();break}const t=v[1];if(!t){logError("Must provide the directory to push from\n"),logHelp();break}const n=v.slice(2);if(n.length){const e=n.find((e=>!/^(?:[a-z_][a-z\d_]{0,24}|\*)\.(?:[a-z_][a-z\d_]{0,24}|\*)$/.test(e)));if(e){logError(`Invalid script name: ${JSON.stringify(e)}\n`),logHelp();break}}else n.push("*.*");if(w.has("skip-minify")&&w.has("mangle-names")){logError(`Option ${R("--mangle-names")} is not compatible with ${R("--skip-minify")}\n`),logHelp();break}const a=w.get("skip-minify");let s;if(null!=a){if("boolean"!=typeof a){logError(`The value for ${R("--skip-minify")} must be ${E("true")} or ${E("false")}\n`),logHelp();break}s=!a}const o=w.get("mangle-names");if(null!=o&&"boolean"!=typeof o){logError(`The value for ${R("--mangle-names")} must be ${E("true")} or ${E("false")}\n`),logHelp();break}const i=w.get("force-quine-cheats");if(null!=i&&"boolean"!=typeof i){logError(`The value for ${R("--force-quine-cheats")} must be ${E("true")} or ${E("false")}\n`),logHelp();break}const{push:r}=await N;(await r(t,e,{scripts:n,onPush:t=>logInfo(t,e),minify:s,mangleNames:o,forceQuineCheats:i})).length||logError("Could not find any scripts to push")}break;case"dev":case"watch":{const{hackmudPath:e}=await j;if(!e){logNeedHackmudPathMessage();break}const t=v[1];if(!t){logError("Must provide the directory to watch\n"),logHelp();break}const n=v.slice(2);if(n.length){const e=n.find((e=>!/^(?:[a-z_][a-z\d_]{0,24}|\*)\.(?:[a-z_][a-z\d_]{0,24}|\*)$/.test(e)));if(e){logError(`Invalid script name: ${JSON.stringify(e)}\n`),logHelp();break}}else n.push("*.*");if(w.has("skip-minify")&&w.has("mangle-names")){logError(`Option ${R("--mangle-names")} is not compatible with ${R("--skip-minify")}\n`),logHelp();break}const a=w.get("skip-minify");let s;if(null!=a){if("boolean"!=typeof a){logError(`The value for ${R("--skip-minify")} must be ${E("true")} or ${E("false")}\n`),logHelp();break}s=!a}const o=w.get("mangle-names");if(null!=o&&"boolean"!=typeof o){logError(`The value for ${R("--mangle-names")} must be ${E("true")} or ${E("false")}\n`),logHelp();break}const i=w.get("force-quine-cheats");if(null!=i&&"boolean"!=typeof i){logError(`The value for ${R("--force-quine-cheats")} must be ${E("true")} or ${E("false")}\n`),logHelp();break}const{watch:r}=await M;r(t,e,{scripts:n,onPush:t=>logInfo(t,e),typeDeclarationPath:(w.get("type-declaration-path")||w.get("type-declaration")||w.get("dts")||w.get("gen-types"))?.toString(),minify:s,mangleNames:o,onReady:()=>log("Watching"),forceQuineCheats:i}),A=!1}break;case"pull":{const{hackmudPath:e}=await j;if(!e){logNeedHackmudPathMessage();break}const t=v[1];if(!t){logError("Must provide the script to pull\n"),logHelp();break}const n=v[2]||".";try{await $(n,e,t)}catch(e){console.error(e),logError(`Something went wrong, did you forget to ${C("#down")} the script?`)}}break;case"sync-macros":{const{hackmudPath:e}=await j;if(!e){logNeedHackmudPathMessage();break}const{macrosSynced:t,usersSynced:n}=await p(e);log(`Synced ${t} macros to ${n} users`)}break;case"generate-type-declaration":case"gen-type-declaration":case"gen-dts":case"gen-types":{const e=v[1];if(!e){logError("Must provide target directory\n"),logHelp();break}const n=m(e),a=v[2]||"./player.d.ts",s=await f(n,(await j).hackmudPath);let i=m(a);try{await o(i,s)}catch(e){if(t(e instanceof Error),"EISDIR"!=e.code)throw e;i=m(i,"player.d.ts"),await o(i,s)}log(`Wrote type declaration to ${T.bold(i)}`)}break;case"config":switch(v[1]){case"get":{const e=v[2];e?log(exploreObject(await j,e.split("."))):console.log(await j)}break;case"delete":{const e=v[2];if(!e){logError("Must provide a key to delete\n"),logHelp();break}const t=e.split("."),n=t.map((e=>/^[a-z_$][\w$]*$/i.test(e)?e:JSON.stringify(e))).join("."),a=t.pop(),s=await j;delete exploreObject(s,t)?.[a],log(`Removed ${E(n)} from config file`)}break;case"set":{const e=v[2],t=v[3];if(!e){logError("Must provide a key and value\n"),logHelp();break}const n=e.split("."),a=n.map((e=>/^[a-z_$][\w$]*$/i.test(e)?e:JSON.stringify(e))).join(".");if(!t){logError(`Must provide a value for the key ${a}\n`),logHelp();break}const s=n.pop(),l=await j;if(n.length||"hackmudPath"!=s){let e=l;for(const t of n)"object"==typeof e[t]||(e[t]={}),e=e[t];e[s]=t}else l.hackmudPath=m(t.startsWith("~/")?c()+t.slice(1):t);console.log(l),await(async e=>{const t=JSON.stringify(e,void 0,"\t");P&&log(`Creating config file at ${k}`),await o(k,t).catch((async e=>{switch(e.code){case"EISDIR":await r(k);break;case"ENOENT":await i(b);break;default:throw e}await o(k,t)}))})(l)}break;default:v[1]&&logError(`Unknown command: ${JSON.stringify(v[1])}\n`),logHelp()}break;case"help":case"h":logHelp();break;case"golf":case"minify":{const e=v[1];if(!e){logError("Must provide target\n"),logHelp();break}const t=h(e);if(!l.includes(t)){logError(`Unsupported file extension "${T.bold(t)}"\nSupported extensions are "${l.map((e=>T.bold(e))).join('", "')}"`);break}const{processScript:o}=await O,i=u(e,t),r=i.endsWith(".src"),c=r?i.slice(0,-4):i,f="scripts"==u(m(e,".."))&&"hackmud"==u(m(e,"../../.."))?u(m(e,"../..")):"UNKNOWN",$=!w.get("skip-minify");if(w.has("skip-minify")&&w.has("mangle-names")){logError(`Option ${R("--mangle-names")} would have no effect if minification is skipped\n`),logHelp();break}const p=w.get("mangle-names");if(null!=p&&"boolean"!=typeof p){logError(`The value for ${R("--mangle-names")} must be ${E("true")} or ${E("false")}\n`),logHelp();break}const y=p,b=w.get("force-quine-cheats");if(null!=b&&"boolean"!=typeof b){logError(`the value for ${R("--force-quine-cheats")} must be ${E("true")} or ${E("false")}\n`),logHelp();break}const k=b;let S=v[2]||m(g(e),r?`${c}.js`:".js"==t?`${i}.min.js`:`${i}.js`);const golfFile=()=>s(e,{encoding:"utf-8"}).then((async s=>{const i=performance.now(),{script:r,warnings:l}=await o(s,{minify:$,scriptUser:f,scriptName:c,filePath:e,mangleNames:y,forceQuineCheats:k}),p=performance.now()-i;for(const{message:e,line:t}of l)log(`Warning "${T.bold(e)}" on line ${T.bold(String(t))}`);await a(S,r).catch((async n=>{if(!v[2]||"EISDIR"!=n.code)throw n;S=m(S,`${u(e,t)}.js`),await a(S,r)})).then((()=>log(`Wrote ${T.bold(n(r))} chars to ${T.bold(d(".",S))} | took ${Math.round(100*p)/100}ms`)),(e=>logError(e.message)))}),(e=>logError(e.message)));if(w.get("watch")){const{watch:t}=await U;t(e,{awaitWriteFinish:{stabilityThreshold:100}}).on("ready",(()=>log(`Watching ${e}`))).on("change",golfFile),A=!1}else await golfFile()}break;default:v[0]&&logError(`Unknown command: ${JSON.stringify(v[0])}\n`),logHelp()}A&&process.exit();