hackmud-script-manager 0.19.1-4bde221 → 0.19.1-531ddb2

Sign up to get free protection for your applications and to get access to all the features.
package/bin/hsm.js CHANGED
@@ -1,7 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { Cache } from "@samual/lib/Cache"
3
3
  import { assert } from "@samual/lib/assert"
4
+ import { catchError } from "@samual/lib/catchError"
4
5
  import { countHackmudCharacters } from "@samual/lib/countHackmudCharacters"
6
+ import { getDeepObjectProperty } from "@samual/lib/getDeepObjectProperty"
7
+ import { isRecord } from "@samual/lib/isRecord"
8
+ import { setDeepObjectProperty } from "@samual/lib/setDeepObjectProperty"
5
9
  import { writeFilePersistent } from "@samual/lib/writeFilePersistent"
6
10
  import { readFile, writeFile, mkdir, rmdir } from "fs/promises"
7
11
  import { homedir } from "os"
@@ -10,6 +14,7 @@ import { supportedExtensions } from "../constants.js"
10
14
  import { generateTypeDeclaration } from "../generateTypeDeclaration.js"
11
15
  import { pull } from "../pull.js"
12
16
  import { syncMacros } from "../syncMacros.js"
17
+ import "@samual/lib/readDirectoryWithStats"
13
18
  import "@samual/lib/copyFilePersistent"
14
19
  const configDirectoryPath = resolve(homedir(), ".config"),
15
20
  configFilePath = resolve(configDirectoryPath, "hsm.json"),
@@ -20,117 +25,7 @@ const configDirectoryPath = resolve(homedir(), ".config"),
20
25
  for (const char of user) hash += (hash >> 1) + hash + "xi1_8ratvsw9hlbgm02y5zpdcn7uekof463qj".indexOf(char) + 1
21
26
  return [colourJ, colourK, colourM, colourW, colourL, colourB][hash % 6](user)
22
27
  }),
23
- logNeedHackmudPathMessage = () =>
24
- console.error(
25
- colourS(
26
- `${colourD("You need to set hackmudPath in config before you can use this command")}\n\n${colourA("To fix this:")}\nOpen hackmud and run "${colourC("#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 "${colourC("hsm")} ${colourL("config set")} ${colourV("hackmudPath")} ${colourB("<the path you copied>")}"`
27
- )
28
- ),
29
- logHelp = () => {
30
- const pushCommandDescription = "Push scripts from a directory to hackmud user's scripts directories",
31
- mangleNamesOptionDescription = "Reduce character count further but lose function names in error call stacks"
32
- console.log(colourN("Version") + colourS(": ") + colourV("0.19.1-4bde221"))
33
- switch (commands[0]) {
34
- case "config":
35
- switch (commands[1]) {
36
- case "get":
37
- console.log(
38
- `\n${colourJ("Retrieve a value from the config file")}\n\n${colourA("Usage:")}\n${colourC("hsm")} ${colourL(`${commands[0]} ${commands[1]}`)} ${colourB("<key>")}`
39
- )
40
- break
41
- case "set":
42
- console.log(
43
- `\n${colourJ("Assign a value to the config file")}\n\n${colourA("Usage:")}\n${colourC("hsm")} ${colourL(`${commands[0]} ${commands[1]}`)} ${colourB("<key> <value>")}`
44
- )
45
- break
46
- case "delete":
47
- console.log(
48
- `\n${colourJ("Remove a key and value from the config file")}\n\n${colourA("Usage:")}\n${colourC("hsm")} ${colourL(`${commands[0]} ${commands[1]}`)} ${colourB("<key>")}`
49
- )
50
- break
51
- default:
52
- console.log(
53
- colourS(
54
- `${colourN("Config path")}: ${colourV(configFilePath)}\n\n${colourJ("Modify the config file")}\n\n${colourA("Usage:")}\n${colourC("hsm")} ${colourL(commands[0] + " get")} ${colourB("<key>")}\n Retrieve a value from the config file\n${colourC("hsm")} ${colourL(commands[0] + " set")} ${colourB("<key> <value>")}\n Assign a value to the config file\n${colourC("hsm")} ${colourL(commands[0] + " delete")} ${colourB("<key>")}\n Remove a key and value from the config file`
55
- )
56
- )
57
- }
58
- break
59
- case "push":
60
- console.log(
61
- colourS(
62
- `\n${colourJ(pushCommandDescription)}\n\n${colourA("Usage:")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourB("<directory> [<script user>.<script name>...]")}\n\n${colourA("Options:")}\n${colourN("--skip-minify")}\n Skip minification to produce a readable script\n${colourN("--mangle-names")}\n ${mangleNamesOptionDescription}\n${colourN("--force-quine-cheats")}\n Force quine cheats even if the character count is higher`
63
- )
64
- )
65
- break
66
- case "dev":
67
- case "watch":
68
- console.log(
69
- colourS(
70
- `${colourN("Aliases")}: ${colourV("watch, dev")}\n\n${colourJ("Watch a directory and push a script when modified")}\n\n${colourA("Usage:")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourB("<directory> [<script user>.<script name>...]")}\n\n${colourA("Options:")}\n${colourN("--skip-minify")}\n Skip minification to produce a readable script\n${colourN("--mangle-names")}\n ${mangleNamesOptionDescription}\n${colourN("--type-declaration-path")}=${colourB("<path>")}\n Path to generate a type declaration file for the scripts\n${colourN("--force-quine-cheats")}\n Force quine cheats even if the character count is higher`
71
- )
72
- )
73
- break
74
- case "pull":
75
- console.log(
76
- colourS(
77
- `\n${colourJ("Pull a script a from a hackmud user's script directory")}\n\n${colourA("Usage:")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourB("<script user>")}${colourV(".")}${colourB("<script name>")}`
78
- )
79
- )
80
- break
81
- case "minify":
82
- case "golf":
83
- console.log(
84
- colourS(
85
- `${colourN("Aliases")}: ${colourV("minify, golf")}\n\n${colourJ("Minify a script file on the spot")}\n\n${colourA("Usage:")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourB("<target> [output path]")}\n\n${colourA("Options:")}\n${colourN("--skip-minify")}\n Skip minification to produce a readable script\n${colourN("--mangle-names")}\n ${mangleNamesOptionDescription}\n${colourN("--force-quine-cheats")}\n Force quine cheats even if the character count is higher\n${colourN("--watch")}\n Watch for changes`
86
- )
87
- )
88
- break
89
- case "generate-type-declaration":
90
- case "gen-type-declaration":
91
- case "gen-dts":
92
- case "gen-types":
93
- console.log(
94
- colourS(
95
- `${colourN("Aliases")}: ${colourV("generate-type-declaration, gen-type-declaration, gen-types, gen-dts")}\n\n${colourJ("Generate a type declaration file for a directory of scripts")}\n\n${colourA("Usage:")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourB("<directory> [output path]")}`
96
- )
97
- )
98
- break
99
- case "sync-macros":
100
- console.log("\n" + colourJ("Sync macros across all hackmud users"))
101
- break
102
- default:
103
- console.log(
104
- colourS(
105
- `\n${colourJ("Hackmud Script Manager")}\n\n${colourA("Commands:")}\n${colourL("push")}\n ${pushCommandDescription}\n${colourL("watch")}, ${colourL("dev")}\n Watch a directory and push a script when modified\n${colourL("minify")}, ${colourL("golf")}\n Minify a script file on the spot\n${colourL("generate-type-declaration")}, ${colourL("gen-type-declaration")}, ${colourL("gen-types")}, ${colourL("gen-dts")}\n Generate a type declaration file for a directory of scripts\n${colourL("sync-macros")}\n Sync macros across all hackmud users\n${colourL("config")}\n Modify and view the config file\n${colourL("pull")}\n Pull a script a from a hackmud user's script directory`
106
- )
107
- )
108
- }
109
- },
110
- exploreObject = (object, keys, createPath = !1) => {
111
- for (const key of keys)
112
- object =
113
- createPath ?
114
- "object" == typeof object[key] ?
115
- object[key]
116
- : (object[key] = {})
117
- : object?.[key]
118
- return object
119
- },
120
- logInfo = ({ file, users, minLength, error }, hackmudPath) => {
121
- error ?
122
- logError(`error "${chalk.bold(error.message)}" in ${chalk.bold(file)}`)
123
- : console.log(
124
- `pushed ${chalk.bold(file)} to ${users.map(user => chalk.bold(userColours.get(user))).join(", ")} | ${chalk.bold(minLength + "")} chars | ${chalk.bold(resolve(hackmudPath, users[0], "scripts", basename(file, extname(file))) + ".js")}`
125
- )
126
- },
127
- log = message => {
128
- console.log(colourS(message))
129
- },
130
- logError = message => {
131
- console.error(colourD(message))
132
- process.exitCode = 1
133
- }
28
+ log = message => console.log(colourS(message))
134
29
  for (const argument of process.argv.slice(2))
135
30
  if ("-" == argument[0]) {
136
31
  const [key, valueRaw] = argument.split("=")
@@ -147,20 +42,14 @@ for (const argument of process.argv.slice(2))
147
42
  else for (const option of key.slice(1)) options.set(option, value)
148
43
  } else commands.push(argument)
149
44
  if ("v" == commands[0] || "version" == commands[0] || options.get("version") || options.get("v")) {
150
- console.log("0.19.1-4bde221")
45
+ console.log("0.19.1-531ddb2")
151
46
  process.exit()
152
47
  }
153
48
  let configDidNotExist = !1
154
49
  const configPromise = readFile(configFilePath, { encoding: "utf-8" }).then(
155
50
  configFile => {
156
- let temporaryConfig
157
- try {
158
- temporaryConfig = JSON.parse(configFile)
159
- } catch {
160
- log("Config file was corrupted, resetting")
161
- return {}
162
- }
163
- if (!temporaryConfig || "object" != typeof temporaryConfig) {
51
+ const [temporaryConfig, error] = catchError(() => JSON.parse(configFile))
52
+ if (error || !isRecord(temporaryConfig)) {
164
53
  log("Config file was corrupted, resetting")
165
54
  return {}
166
55
  }
@@ -197,15 +86,13 @@ if (options.get("help") || options.get("h")) {
197
86
  process.exit()
198
87
  }
199
88
  let autoExit = !0
89
+ const getDefaultHackmudPath = () =>
90
+ "win32" == process.platform ? resolve(process.env.APPDATA, "hackmud") : resolve(homedir(), ".config/hackmud")
200
91
  switch (commands[0]) {
201
92
  case "push":
202
93
  {
203
- const { hackmudPath } = await configPromise
204
- if (!hackmudPath) {
205
- logNeedHackmudPathMessage()
206
- break
207
- }
208
- const sourcePath = commands[1]
94
+ const { hackmudPath = getDefaultHackmudPath() } = await configPromise,
95
+ sourcePath = commands[1]
209
96
  if (!sourcePath) {
210
97
  logError("Must provide the directory to push from\n")
211
98
  logHelp()
@@ -222,17 +109,20 @@ switch (commands[0]) {
222
109
  break
223
110
  }
224
111
  } else scripts.push("*.*")
225
- if (options.has("skip-minify") && options.has("mangle-names")) {
226
- logError(`Option ${colourN("--mangle-names")} is not compatible with ${colourN("--skip-minify")}\n`)
112
+ const optionsHasNoMinify = options.has("no-minify")
113
+ if ((optionsHasNoMinify || options.has("skip-minify")) && options.has("mangle-names")) {
114
+ logError(
115
+ `Options ${colourN("--mangle-names")} and ${colourN(optionsHasNoMinify ? "--no-minify" : "--skip-minify")} are incompatible\n`
116
+ )
227
117
  logHelp()
228
118
  break
229
119
  }
230
- const shouldSkipMinify = options.get("skip-minify")
120
+ const shouldSkipMinify = options.get("no-minify") || options.get("skip-minify")
231
121
  let shouldMinify
232
122
  if (null != shouldSkipMinify) {
233
123
  if ("boolean" != typeof shouldSkipMinify) {
234
124
  logError(
235
- `The value for ${colourN("--skip-minify")} must be ${colourV("true")} or ${colourV("false")}\n`
125
+ `The value for ${colourN(optionsHasNoMinify ? "--no-minify" : "--skip-minify")} must be ${colourV("true")} or ${colourV("false")}\n`
236
126
  )
237
127
  logHelp()
238
128
  break
@@ -270,12 +160,8 @@ switch (commands[0]) {
270
160
  case "dev":
271
161
  case "watch":
272
162
  {
273
- const { hackmudPath } = await configPromise
274
- if (!hackmudPath) {
275
- logNeedHackmudPathMessage()
276
- break
277
- }
278
- const sourcePath = commands[1]
163
+ const { hackmudPath = getDefaultHackmudPath() } = await configPromise,
164
+ sourcePath = commands[1]
279
165
  if (!sourcePath) {
280
166
  logError("Must provide the directory to watch\n")
281
167
  logHelp()
@@ -292,17 +178,20 @@ switch (commands[0]) {
292
178
  break
293
179
  }
294
180
  } else scripts.push("*.*")
295
- if (options.has("skip-minify") && options.has("mangle-names")) {
296
- logError(`Option ${colourN("--mangle-names")} is not compatible with ${colourN("--skip-minify")}\n`)
181
+ const optionsHasNoMinify = options.has("no-minify")
182
+ if ((optionsHasNoMinify || options.has("skip-minify")) && options.has("mangle-names")) {
183
+ logError(
184
+ `Options ${colourN("--mangle-names")} and ${colourN(optionsHasNoMinify ? "--no-minify" : "--skip-minify")} are incompatible\n`
185
+ )
297
186
  logHelp()
298
187
  break
299
188
  }
300
- const shouldSkipMinify = options.get("skip-minify")
189
+ const shouldSkipMinify = options.get("no-minify") || options.get("skip-minify")
301
190
  let shouldMinify
302
191
  if (null != shouldSkipMinify) {
303
192
  if ("boolean" != typeof shouldSkipMinify) {
304
193
  logError(
305
- `The value for ${colourN("--skip-minify")} must be ${colourV("true")} or ${colourV("false")}\n`
194
+ `The value for ${colourN(optionsHasNoMinify ? "--no-minify" : "--skip-minify")} must be ${colourV("true")} or ${colourV("false")}\n`
306
195
  )
307
196
  logHelp()
308
197
  break
@@ -345,34 +234,24 @@ switch (commands[0]) {
345
234
  break
346
235
  case "pull":
347
236
  {
348
- const { hackmudPath } = await configPromise
349
- if (!hackmudPath) {
350
- logNeedHackmudPathMessage()
351
- break
352
- }
353
- const script = commands[1]
237
+ const { hackmudPath = getDefaultHackmudPath() } = await configPromise,
238
+ script = commands[1]
354
239
  if (!script) {
355
240
  logError("Must provide the script to pull\n")
356
241
  logHelp()
357
242
  break
358
243
  }
359
244
  const sourcePath = commands[2] || "."
360
- try {
361
- await pull(sourcePath, hackmudPath, script)
362
- } catch (error) {
245
+ await pull(sourcePath, hackmudPath, script).catch(error => {
363
246
  console.error(error)
364
247
  logError(`Something went wrong, did you forget to ${colourC("#down")} the script?`)
365
- }
248
+ })
366
249
  }
367
250
  break
368
251
  case "sync-macros":
369
252
  {
370
- const { hackmudPath } = await configPromise
371
- if (!hackmudPath) {
372
- logNeedHackmudPathMessage()
373
- break
374
- }
375
- const { macrosSynced, usersSynced } = await syncMacros(hackmudPath)
253
+ const { hackmudPath = getDefaultHackmudPath() } = await configPromise,
254
+ { macrosSynced, usersSynced } = await syncMacros(hackmudPath)
376
255
  log(`Synced ${macrosSynced} macros to ${usersSynced} users`)
377
256
  }
378
257
  break
@@ -389,16 +268,17 @@ switch (commands[0]) {
389
268
  }
390
269
  const sourcePath = resolve(target),
391
270
  outputPath = commands[2] || "./player.d.ts",
392
- typeDeclaration = await generateTypeDeclaration(sourcePath, (await configPromise).hackmudPath)
271
+ typeDeclaration = await generateTypeDeclaration(
272
+ sourcePath,
273
+ (await configPromise).hackmudPath || getDefaultHackmudPath()
274
+ )
393
275
  let typeDeclarationPath = resolve(outputPath)
394
- try {
395
- await writeFile(typeDeclarationPath, typeDeclaration)
396
- } catch (error) {
397
- assert(error instanceof Error)
276
+ await writeFile(typeDeclarationPath, typeDeclaration).catch(error => {
277
+ assert(error instanceof Error, "src/bin/hsm.ts:365:35")
398
278
  if ("EISDIR" != error.code) throw error
399
279
  typeDeclarationPath = resolve(typeDeclarationPath, "player.d.ts")
400
- await writeFile(typeDeclarationPath, typeDeclaration)
401
- }
280
+ return writeFile(typeDeclarationPath, typeDeclaration)
281
+ })
402
282
  log("Wrote type declaration to " + chalk.bold(typeDeclarationPath))
403
283
  }
404
284
  break
@@ -406,8 +286,14 @@ switch (commands[0]) {
406
286
  switch (commands[1]) {
407
287
  case "get":
408
288
  {
409
- const key = commands[2]
410
- key ? log(exploreObject(await configPromise, key.split("."))) : console.log(await configPromise)
289
+ const key = commands[2],
290
+ config = await configPromise
291
+ if (key) {
292
+ const [value, error] = catchError(() => getDeepObjectProperty(config, key.split(".")))
293
+ error ? logError("Could not get key " + colourV(key))
294
+ : "string" == typeof value ? log(value)
295
+ : console.log(value)
296
+ } else console.log(config)
411
297
  }
412
298
  break
413
299
  case "delete":
@@ -418,14 +304,16 @@ switch (commands[0]) {
418
304
  logHelp()
419
305
  break
420
306
  }
421
- const keyParts = key.split("."),
422
- pathName = keyParts
423
- .map(name => (/^[a-z_$][\w$]*$/i.test(name) ? name : JSON.stringify(name)))
424
- .join("."),
425
- lastKey = keyParts.pop(),
426
- config = await configPromise
427
- delete exploreObject(config, keyParts)?.[lastKey]
428
- log(`Removed ${colourV(pathName)} from config file`)
307
+ const keys = key.split("."),
308
+ lastKey = keys.pop(),
309
+ config = await configPromise,
310
+ object = getDeepObjectProperty(config, keys)
311
+ if (isRecord(object)) {
312
+ delete object[lastKey]
313
+ await updateConfig(config)
314
+ log(`Removed ${colourV(key)} from config file:`)
315
+ console.log(config)
316
+ } else log("Could not delete " + colourV(key))
429
317
  }
430
318
  break
431
319
  case "set":
@@ -437,49 +325,20 @@ switch (commands[0]) {
437
325
  logHelp()
438
326
  break
439
327
  }
440
- const keys = key.split("."),
441
- pathName = keys
442
- .map(name => (/^[a-z_$][\w$]*$/i.test(name) ? name : JSON.stringify(name)))
443
- .join(".")
444
328
  if (!value) {
445
- logError(`Must provide a value for the key ${pathName}\n`)
329
+ logError(`Must provide a value for the key ${colourV(key)}\n`)
446
330
  logHelp()
447
331
  break
448
332
  }
449
- const lastKey = keys.pop(),
450
- config = await configPromise
451
- if (keys.length || "hackmudPath" != lastKey) {
452
- let object = config
453
- for (const key of keys)
454
- if ("object" == typeof object[key]) object = object[key]
455
- else {
456
- object[key] = {}
457
- object = object[key]
458
- }
459
- object[lastKey] = value
460
- } else config.hackmudPath = resolve(value.startsWith("~/") ? homedir() + value.slice(1) : value)
333
+ const config = await configPromise
334
+ setDeepObjectProperty(config, key.split("."), value)
335
+ log(`Set ${colourV(key)} to ${colourV(value)}:`)
461
336
  console.log(config)
462
- await (async config => {
463
- const json = JSON.stringify(config, void 0, "\t")
464
- configDidNotExist && log("Creating config file at " + configFilePath)
465
- await writeFile(configFilePath, json).catch(async error => {
466
- switch (error.code) {
467
- case "EISDIR":
468
- await rmdir(configFilePath)
469
- break
470
- case "ENOENT":
471
- await mkdir(configDirectoryPath)
472
- break
473
- default:
474
- throw error
475
- }
476
- await writeFile(configFilePath, json)
477
- })
478
- })(config)
337
+ await updateConfig(config)
479
338
  }
480
339
  break
481
340
  default:
482
- commands[1] && logError(`Unknown command: ${JSON.stringify(commands[1])}\n`)
341
+ commands[1] && logError(`Unknown command: ${colourL(commands[1])}\n`)
483
342
  logHelp()
484
343
  }
485
344
  break
@@ -511,9 +370,11 @@ switch (commands[0]) {
511
370
  "scripts" == basename(resolve(target, "..")) && "hackmud" == basename(resolve(target, "../../..")) ?
512
371
  basename(resolve(target, "../.."))
513
372
  : "UNKNOWN",
514
- minify = !options.get("skip-minify")
515
- if (options.has("skip-minify") && options.has("mangle-names")) {
516
- logError(`Option ${colourN("--mangle-names")} would have no effect if minification is skipped\n`)
373
+ optionsHasNoMinify = options.has("no-minify")
374
+ if ((optionsHasNoMinify || options.has("skip-minify")) && options.has("mangle-names")) {
375
+ logError(
376
+ `Options ${colourN("--mangle-names")} and ${colourN(optionsHasNoMinify ? "--no-minify" : "--skip-minify")} are incompatible\n`
377
+ )
517
378
  logHelp()
518
379
  break
519
380
  }
@@ -544,36 +405,31 @@ switch (commands[0]) {
544
405
  : fileBaseName + ".js"
545
406
  )
546
407
  const golfFile = () =>
547
- readFile(target, { encoding: "utf-8" }).then(
548
- async source => {
549
- const timeStart = performance.now(),
550
- { script, warnings } = await processScript(source, {
551
- minify,
552
- scriptUser,
553
- scriptName,
554
- filePath: target,
555
- mangleNames,
556
- forceQuineCheats
557
- }),
558
- timeTook = performance.now() - timeStart
559
- for (const { message, line } of warnings)
560
- log(`Warning "${chalk.bold(message)}" on line ${chalk.bold(line + "")}`)
561
- await writeFilePersistent(outputPath, script)
562
- .catch(async error => {
563
- if (!commands[2] || "EISDIR" != error.code) throw error
564
- outputPath = resolve(outputPath, basename(target, fileExtension) + ".js")
565
- await writeFilePersistent(outputPath, script)
566
- })
567
- .then(
568
- () =>
569
- log(
570
- `Wrote ${chalk.bold(countHackmudCharacters(script))} chars to ${chalk.bold(relative(".", outputPath))} | took ${Math.round(100 * timeTook) / 100}ms`
571
- ),
572
- error => logError(error.message)
408
+ readFile(target, { encoding: "utf-8" }).then(async source => {
409
+ const timeStart = performance.now(),
410
+ { script, warnings } = await processScript(source, {
411
+ minify: !(options.get("no-minify") || options.get("skip-minify")),
412
+ scriptUser,
413
+ scriptName,
414
+ filePath: target,
415
+ mangleNames,
416
+ forceQuineCheats
417
+ }),
418
+ timeTook = performance.now() - timeStart
419
+ for (const { message, line } of warnings)
420
+ log(`Warning "${chalk.bold(message)}" on line ${chalk.bold(line + "")}`)
421
+ await writeFilePersistent(outputPath, script)
422
+ .catch(error => {
423
+ if (!commands[2] || "EISDIR" != error.code) throw error
424
+ outputPath = resolve(outputPath, basename(target, fileExtension) + ".js")
425
+ return writeFilePersistent(outputPath, script)
426
+ })
427
+ .then(() =>
428
+ log(
429
+ `Wrote ${chalk.bold(countHackmudCharacters(script))} chars to ${chalk.bold(relative(".", outputPath))} | took ${Math.round(100 * timeTook) / 100}ms`
573
430
  )
574
- },
575
- error => logError(error.message)
576
- )
431
+ )
432
+ })
577
433
  if (options.get("watch")) {
578
434
  const { watch: watchFile } = await chokidarModule
579
435
  watchFile(target, { awaitWriteFinish: { stabilityThreshold: 100 } })
@@ -584,7 +440,110 @@ switch (commands[0]) {
584
440
  }
585
441
  break
586
442
  default:
587
- commands[0] && logError(`Unknown command: ${JSON.stringify(commands[0])}\n`)
443
+ commands[0] && logError(`Unknown command: ${colourL(commands[0])}\n`)
588
444
  logHelp()
589
445
  }
590
446
  autoExit && process.exit()
447
+ function logHelp() {
448
+ const pushCommandDescription = "Push scripts from a directory to hackmud user's scripts directories",
449
+ forceQuineCheatsOptionDescription = `Force quine cheats on. Use ${colourN("--force-quine-cheats")}=${colourV("false")} to force off`
450
+ console.log(colourN("Version") + colourS(": ") + colourV("0.19.1-531ddb2"))
451
+ switch (commands[0]) {
452
+ case "config":
453
+ switch (commands[1]) {
454
+ case "get":
455
+ console.log(
456
+ `\n${colourJ("Retrieve a value from the config file")}\n\n${colourA("Usage:")}\n${colourC("hsm")} ${colourL(`${commands[0]} ${commands[1]}`)} ${colourB("<key>")}`
457
+ )
458
+ break
459
+ case "set":
460
+ console.log(
461
+ `\n${colourJ("Assign a value to the config file")}\n\n${colourA("Usage:")}\n${colourC("hsm")} ${colourL(`${commands[0]} ${commands[1]}`)} ${colourB("<key> <value>")}`
462
+ )
463
+ break
464
+ case "delete":
465
+ console.log(
466
+ `\n${colourJ("Remove a key and value from the config file")}\n\n${colourA("Usage:")}\n${colourC("hsm")} ${colourL(`${commands[0]} ${commands[1]}`)} ${colourB("<key>")}`
467
+ )
468
+ break
469
+ default:
470
+ console.log(
471
+ colourS(
472
+ `${colourN("Config path")}: ${colourV(configFilePath)}\n\n${colourJ("Modify the config file")}\n\n${colourA("Usage:")}\n${colourC("hsm")} ${colourL(commands[0] + " get")} ${colourB("<key>")}\n Retrieve a value from the config file\n${colourC("hsm")} ${colourL(commands[0] + " set")} ${colourB("<key> <value>")}\n Assign a value to the config file\n${colourC("hsm")} ${colourL(commands[0] + " delete")} ${colourB("<key>")}\n Remove a key and value from the config file`
473
+ )
474
+ )
475
+ }
476
+ break
477
+ case "dev":
478
+ case "watch":
479
+ case "push":
480
+ console.log(
481
+ colourS(
482
+ `\n${colourJ("push" == commands[0] ? pushCommandDescription : "Watch a directory and push a script when modified")}\n\n${colourA("Usage:")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourB('<directory> ["<script user>.<script name>"...]')}\n\n${colourA("Arguments:")}\n${colourB("<directory>")}\n The source directory containing your scripts\n${colourB("<script user>")}\n A user to push script(s) to. Can be set to wild card (${colourV("*")}) which will try\n and discover users to push to\n${colourB("<script name>")}\n Name of a script to push. Can be set to wild card (${colourV("*")}) to find all scripts\n\n${colourA("Options:")}\n${colourN("--no-minify")}\n Skip minification to produce a "readable" script\n${colourN("--mangle-names")}\n Reduce character count further but lose function names in error call stacks\n${colourN("--force-quine-cheats")}\n ${forceQuineCheatsOptionDescription}\n${"push" == commands[0] ? "" : `${colourN("--type-declaration-path")}=${colourB("<path>")}\n Path to generate a type declaration file for the scripts\n`}\n${colourA("Examples:")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourV("src")}\n\tPushes all scripts found in ${colourV("src")} folder to all users\n${colourC("hsm")} ${colourL(commands[0])} ${colourV("src")} ${colourC("foo")}${colourV(".")}${colourL("bar")}\n Pushes a script named ${colourL("bar")} found in ${colourV("src")} folder to user ${userColours.get("foo")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourV("src")} ${colourC("foo")}${colourV(".")}${colourL("bar")} ${colourC("baz")}${colourV(".")}${colourL("qux")}\n Multiple can be specified.\n${colourC("hsm")} ${colourL(commands[0])} ${colourV("src")} ${colourC("foo")}${colourV(".")}${colourL("*")}\n\tPushes all scripts found in ${colourV("src")} folder to user ${userColours.get("foo")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourV("src")} ${colourC("*")}${colourV(".")}${colourL("foo")}\n\tPushes all scripts named ${colourL("foo")} found in ${colourV("src")} folder to all user\n${colourC("hsm")} ${colourL(commands[0])} ${colourV("src")} ${colourC("*")}${colourV(".")}${colourL("*")}\n\tPushes all scripts found in ${colourV("src")} folder to all users`
483
+ )
484
+ )
485
+ break
486
+ case "pull":
487
+ console.log(
488
+ colourS(
489
+ `\n${colourJ("Pull a script a from a hackmud user's script directory")}\n\n${colourA("Usage:")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourB("<script user>")}${colourV(".")}${colourB("<script name>")}`
490
+ )
491
+ )
492
+ break
493
+ case "minify":
494
+ case "golf":
495
+ console.log(
496
+ colourS(
497
+ `\n${colourJ("Minify a script file on the spot")}\n\n${colourA("Usage:")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourB("<target> [output path]")}\n\n${colourA("Options:")}\n${colourN("--no-minify")}\n Skip minification to produce a "readable" script\n${colourN("--mangle-names")}\n Reduce character count further but lose function names in error call stacks\n${colourN("--force-quine-cheats")}\n ${forceQuineCheatsOptionDescription}\n${colourN("--watch")}\n Watch for changes`
498
+ )
499
+ )
500
+ break
501
+ case "generate-type-declaration":
502
+ case "gen-type-declaration":
503
+ case "gen-dts":
504
+ case "gen-types":
505
+ console.log(
506
+ colourS(
507
+ `${colourJ("Generate a type declaration file for a directory of scripts")}\n\n${colourA("Usage:")}\n${colourC("hsm")} ${colourL(commands[0])} ${colourB("<directory> [output path]")}`
508
+ )
509
+ )
510
+ break
511
+ case "sync-macros":
512
+ console.log("\n" + colourJ("Sync macros across all hackmud users"))
513
+ break
514
+ default:
515
+ console.log(
516
+ colourS(
517
+ `\n${colourJ("Hackmud Script Manager")}\n\n${colourA("Commands:")}\n${colourL("push")}\n ${pushCommandDescription}\n${colourL("dev")}\n Watch a directory and push a script when modified\n${colourL("golf")}\n Minify a script file on the spot\n${colourL("gen-dts")}\n Generate a type declaration file for a directory of scripts\n${colourL("sync-macros")}\n Sync macros across all hackmud users\n${colourL("config")}\n Modify and view the config file\n${colourL("pull")}\n Pull a script a from a hackmud user's script directory`
518
+ )
519
+ )
520
+ }
521
+ }
522
+ async function updateConfig(config) {
523
+ const json = JSON.stringify(config, void 0, "\t")
524
+ configDidNotExist && log("Creating config file at " + configFilePath)
525
+ await writeFile(configFilePath, json).catch(async error => {
526
+ switch (error.code) {
527
+ case "EISDIR":
528
+ await rmdir(configFilePath)
529
+ break
530
+ case "ENOENT":
531
+ await mkdir(configDirectoryPath)
532
+ break
533
+ default:
534
+ throw error
535
+ }
536
+ await writeFile(configFilePath, json)
537
+ })
538
+ }
539
+ function logInfo({ file, users, minLength, error }, hackmudPath) {
540
+ error ?
541
+ logError(`error "${chalk.bold(error.message)}" in ${chalk.bold(file)}`)
542
+ : console.log(
543
+ `pushed ${chalk.bold(file)} to ${users.map(user => chalk.bold(userColours.get(user))).join(", ")} | ${chalk.bold(minLength + "")} chars | ${chalk.bold(resolve(hackmudPath, users[0], "scripts", basename(file, extname(file))) + ".js")}`
544
+ )
545
+ }
546
+ function logError(message) {
547
+ console.error(colourD(message))
548
+ process.exitCode = 1
549
+ }
@@ -1,31 +1,31 @@
1
- import { readdir } from "fs/promises"
1
+ import { readDirectoryWithStats } from "@samual/lib/readDirectoryWithStats"
2
2
  import { basename, resolve } from "path"
3
- const generateTypeDeclaration = async (sourceDirectory, hackmudPath) => {
3
+ async function generateTypeDeclaration(sourceDirectory, hackmudPath) {
4
4
  const users = new Set()
5
5
  if (hackmudPath)
6
- for (const dirent of await readdir(hackmudPath, { withFileTypes: !0 }))
7
- dirent.isFile() && dirent.name.endsWith(".key") && users.add(basename(dirent.name, ".key"))
6
+ for (const { stats, name } of await readDirectoryWithStats(hackmudPath))
7
+ stats.isFile() && name.endsWith(".key") && users.add(basename(name, ".key"))
8
8
  const wildScripts = [],
9
9
  wildAnyScripts = [],
10
10
  allScripts = {},
11
11
  allAnyScripts = {}
12
12
  await Promise.all(
13
- (await readdir(sourceDirectory, { withFileTypes: !0 })).map(async dirent => {
14
- if (dirent.isFile())
15
- dirent.name.endsWith(".ts") ?
16
- dirent.name.endsWith(".d.ts") || wildScripts.push(basename(dirent.name, ".ts"))
17
- : dirent.name.endsWith(".js") && wildAnyScripts.push(basename(dirent.name, ".js"))
18
- else if (dirent.isDirectory()) {
13
+ (await readDirectoryWithStats(sourceDirectory)).map(async ({ stats, name }) => {
14
+ if (stats.isFile())
15
+ name.endsWith(".ts") ?
16
+ name.endsWith(".d.ts") || wildScripts.push(basename(name, ".ts"))
17
+ : name.endsWith(".js") && wildAnyScripts.push(basename(name, ".js"))
18
+ else if (stats.isDirectory()) {
19
19
  const scripts = [],
20
20
  anyScripts = []
21
- allScripts[dirent.name] = scripts
22
- allAnyScripts[dirent.name] = anyScripts
23
- users.add(dirent.name)
24
- for (const file of await readdir(resolve(sourceDirectory, dirent.name), { withFileTypes: !0 }))
25
- file.isFile() &&
26
- (file.name.endsWith(".ts") ?
27
- dirent.name.endsWith(".d.ts") || scripts.push(basename(file.name, ".ts"))
28
- : file.name.endsWith(".js") && anyScripts.push(basename(file.name, ".js")))
21
+ allScripts[name] = scripts
22
+ allAnyScripts[name] = anyScripts
23
+ users.add(name)
24
+ for (const child of await readDirectoryWithStats(resolve(sourceDirectory, name)))
25
+ child.stats.isFile() &&
26
+ (child.name.endsWith(".ts") ?
27
+ name.endsWith(".d.ts") || scripts.push(basename(child.name, ".ts"))
28
+ : child.name.endsWith(".js") && anyScripts.push(basename(child.name, ".js")))
29
29
  }
30
30
  })
31
31
  )