hackmud-script-manager 0.20.3 → 0.20.4-03d5600

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Hackmud Script Manager
2
- Command made for [hackmud-environment](https://github.com/samualtnorman/hackmud-environment), which is a scripting environment for hackmud with minification, autocompletes / intellisense, and TypeScript support.
2
+ Command made for [Hackmud Scripting Environment](https://github.com/samualtnorman/hackmud-environment), which is a scripting environment for hackmud with minification, autocompletes / intellisense, and TypeScript support.
3
+
4
+ [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R0XN5CX)
5
+
6
+ You can read about how HSM works [in my blog post](https://samual.uk/blog/js-code-transformation-niche-environment/).
3
7
 
4
8
  ## Install
5
9
  1. [Install Node.js](https://nodejs.org/en/download)
package/bin/hsm.js CHANGED
@@ -5,14 +5,15 @@ import { countHackmudCharacters } from "@samual/lib/countHackmudCharacters"
5
5
  import { writeFilePersistent } from "@samual/lib/writeFilePersistent"
6
6
  import { writeFile, readFile } from "fs/promises"
7
7
  import { homedir } from "os"
8
- import { extname, basename, resolve, dirname, relative } from "path"
8
+ import { resolve, extname, basename, dirname, relative } from "path"
9
9
  import { supportedExtensions } from "../constants.js"
10
10
  import { generateTypeDeclaration } from "../generateTypeDeclaration.js"
11
11
  import { pull } from "../pull.js"
12
12
  import { syncMacros } from "../syncMacros.js"
13
13
  import "@samual/lib/readDirectoryWithStats"
14
+ import "path/posix"
14
15
  import "@samual/lib/copyFilePersistent"
15
- const version = "0.20.3",
16
+ const version = "0.20.4-03d5600",
16
17
  options = new Map(),
17
18
  commands = [],
18
19
  userColours = new Cache(user => {
@@ -36,10 +37,6 @@ for (const argument of process.argv.slice(2))
36
37
  if ("-" == argument[1]) options.set(key.slice(2), value)
37
38
  else for (const option of key.slice(1)) options.set(option, value)
38
39
  } else commands.push(argument)
39
- if ("v" == commands[0] || "version" == commands[0] || options.get("version") || options.get("v")) {
40
- console.log(version)
41
- process.exit()
42
- }
43
40
  const pushModule = import("../push.js"),
44
41
  processScriptModule = import("../processScript/index.js"),
45
42
  watchModule = import("../watch.js"),
@@ -57,153 +54,163 @@ const pushModule = import("../push.js"),
57
54
  colourS = chalk.rgb(122, 178, 244),
58
55
  colourV = chalk.rgb(255, 0, 236),
59
56
  colourW = chalk.rgb(255, 150, 224)
60
- if (options.get("help") || options.get("h")) {
57
+ if ("v" == commands[0] || "version" == commands[0] || popOption("version", "v")?.value) {
58
+ console.log(version)
59
+ process.exit()
60
+ }
61
+ if (popOption("help", "h")?.value) {
61
62
  logHelp()
62
63
  process.exit()
63
64
  }
64
65
  let autoExit = !0
65
66
  switch (commands[0]) {
66
67
  case "push":
68
+ case "dev":
69
+ case "watch":
70
+ case "golf":
71
+ case "minify":
67
72
  {
68
- const hackmudPath = getHackmudPath(),
69
- sourcePath = commands[1]
70
- if (!sourcePath) {
71
- logError("Must provide the directory to push from\n")
72
- logHelp()
73
- break
74
- }
75
- const scripts = commands.slice(2)
76
- if (scripts.length) {
77
- const invalidScript = scripts.find(
78
- script => !/^(?:[a-z_][a-z\d_]{0,24}|\*)\.(?:[a-z_][a-z\d_]{0,24}|\*)$/.test(script)
79
- )
80
- if (invalidScript) {
81
- logError(`Invalid script name: ${JSON.stringify(invalidScript)}\n`)
82
- logHelp()
83
- break
84
- }
85
- } else scripts.push("*.*")
86
- const optionsHasNoMinify = options.has("no-minify")
87
- if ((optionsHasNoMinify || options.has("skip-minify")) && options.has("mangle-names")) {
73
+ const noMinifyOption = popOption("no-minify", "skip-minify"),
74
+ mangleNamesOption = popOption("mangle-names"),
75
+ forceQuineCheatsOption = popOption("force-quine-cheats"),
76
+ noMinifyIncompatibleOption = mangleNamesOption || forceQuineCheatsOption
77
+ if (noMinifyOption && noMinifyIncompatibleOption) {
88
78
  logError(
89
- `Options ${colourN("--mangle-names")} and ${colourN(optionsHasNoMinify ? "--no-minify" : "--skip-minify")} are incompatible\n`
79
+ `Options ${colourN(noMinifyOption.name)} and ${colourN(noMinifyIncompatibleOption.name)} are incompatible\n`
90
80
  )
91
81
  logHelp()
92
- break
82
+ process.exit(1)
93
83
  }
94
- const shouldSkipMinify = options.get("no-minify") || options.get("skip-minify")
95
- let shouldMinify
96
- if (null != shouldSkipMinify) {
97
- if ("boolean" != typeof shouldSkipMinify) {
84
+ noMinifyOption && assertOptionIsBoolean(noMinifyOption)
85
+ mangleNamesOption && assertOptionIsBoolean(mangleNamesOption)
86
+ forceQuineCheatsOption && assertOptionIsBoolean(forceQuineCheatsOption)
87
+ if ("golf" == commands[0] || "minify" == commands[0]) {
88
+ const watchOption = popOption("watch"),
89
+ target = commands[1]
90
+ if (!target) {
91
+ logError("Must provide target\n")
92
+ logHelp()
93
+ process.exit(1)
94
+ }
95
+ const fileExtension = extname(target)
96
+ if (!supportedExtensions.includes(fileExtension)) {
98
97
  logError(
99
- `The value for ${colourN(optionsHasNoMinify ? "--no-minify" : "--skip-minify")} must be ${colourV("true")} or ${colourV("false")}\n`
98
+ `Unsupported file extension "${chalk.bold(fileExtension)}"\nSupported extensions are "${supportedExtensions.map(extension => chalk.bold(extension)).join('", "')}"`
100
99
  )
101
- logHelp()
102
- break
100
+ process.exit(1)
103
101
  }
104
- shouldMinify = !shouldSkipMinify
105
- }
106
- const shouldMangleNames = options.get("mangle-names")
107
- if (null != shouldMangleNames && "boolean" != typeof shouldMangleNames) {
108
- logError(
109
- `The value for ${colourN("--mangle-names")} must be ${colourV("true")} or ${colourV("false")}\n`
110
- )
111
- logHelp()
112
- break
113
- }
114
- const shouldforceQuineCheats = options.get("force-quine-cheats")
115
- if (null != shouldforceQuineCheats && "boolean" != typeof shouldforceQuineCheats) {
116
- logError(
117
- `The value for ${colourN("--force-quine-cheats")} must be ${colourV("true")} or ${colourV("false")}\n`
118
- )
119
- logHelp()
120
- break
121
- }
122
- const { push } = await pushModule
123
- ;(
124
- await push(sourcePath, hackmudPath, {
125
- scripts,
126
- onPush: info => logInfo(info, hackmudPath),
127
- minify: shouldMinify,
128
- mangleNames: shouldMangleNames,
129
- forceQuineCheats: shouldforceQuineCheats
130
- })
131
- ).length || logError("Could not find any scripts to push")
132
- }
133
- break
134
- case "dev":
135
- case "watch":
136
- {
137
- const hackmudPath = getHackmudPath(),
138
- sourcePath = commands[1]
139
- if (!sourcePath) {
140
- logError("Must provide the directory to watch\n")
141
- logHelp()
142
- break
143
- }
144
- const scripts = commands.slice(2)
145
- if (scripts.length) {
146
- const invalidScript = scripts.find(
147
- script => !/^(?:[a-z_][a-z\d_]{0,24}|\*)\.(?:[a-z_][a-z\d_]{0,24}|\*)$/.test(script)
148
- )
149
- if (invalidScript) {
150
- logError(`Invalid script name: ${JSON.stringify(invalidScript)}\n`)
102
+ const { processScript } = await processScriptModule,
103
+ fileBaseName = basename(target, fileExtension),
104
+ fileBaseNameEndsWithDotSrc = fileBaseName.endsWith(".src"),
105
+ scriptName = fileBaseNameEndsWithDotSrc ? fileBaseName.slice(0, -4) : fileBaseName,
106
+ scriptUser =
107
+ (
108
+ "scripts" == basename(resolve(target, "..")) &&
109
+ "hackmud" == basename(resolve(target, "../../.."))
110
+ ) ?
111
+ basename(resolve(target, "../.."))
112
+ : void 0
113
+ let outputPath =
114
+ commands[2] ||
115
+ resolve(
116
+ dirname(target),
117
+ fileBaseNameEndsWithDotSrc ? scriptName + ".js"
118
+ : ".js" == fileExtension ? fileBaseName + ".min.js"
119
+ : fileBaseName + ".js"
120
+ )
121
+ const golfFile = () =>
122
+ readFile(target, { encoding: "utf8" }).then(async source => {
123
+ const timeStart = performance.now(),
124
+ { script, warnings } = await processScript(source, {
125
+ minify: noMinifyOption && !noMinifyOption.value,
126
+ scriptUser,
127
+ scriptName,
128
+ filePath: target,
129
+ mangleNames: mangleNamesOption?.value,
130
+ forceQuineCheats: forceQuineCheatsOption?.value
131
+ }),
132
+ timeTook = performance.now() - timeStart
133
+ for (const { message, line } of warnings)
134
+ log(`Warning "${chalk.bold(message)}" on line ${chalk.bold(line + "")}`)
135
+ await writeFilePersistent(outputPath, script)
136
+ .catch(error => {
137
+ if (!commands[2] || "EISDIR" != error.code) throw error
138
+ outputPath = resolve(outputPath, basename(target, fileExtension) + ".js")
139
+ return writeFilePersistent(outputPath, script)
140
+ })
141
+ .then(() =>
142
+ log(
143
+ `Wrote ${chalk.bold(countHackmudCharacters(script))} chars to ${chalk.bold(relative(".", outputPath))} | took ${Math.round(100 * timeTook) / 100}ms`
144
+ )
145
+ )
146
+ })
147
+ if (watchOption) {
148
+ const { watch: watchFile } = await chokidarModule
149
+ watchFile(target, { awaitWriteFinish: { stabilityThreshold: 100 } })
150
+ .on("ready", () => log("Watching " + target))
151
+ .on("change", golfFile)
152
+ autoExit = !1
153
+ } else await golfFile()
154
+ } else {
155
+ const hackmudPath = getHackmudPath(),
156
+ sourcePath = commands[1]
157
+ if (!sourcePath) {
158
+ logError(`Must provide the directory to ${"push" == commands[0] ? "push from" : "watch"}\n`)
151
159
  logHelp()
152
- break
160
+ process.exit(1)
153
161
  }
154
- } else scripts.push("*.*")
155
- const optionsHasNoMinify = options.has("no-minify")
156
- if ((optionsHasNoMinify || options.has("skip-minify")) && options.has("mangle-names")) {
157
- logError(
158
- `Options ${colourN("--mangle-names")} and ${colourN(optionsHasNoMinify ? "--no-minify" : "--skip-minify")} are incompatible\n`
159
- )
160
- logHelp()
161
- break
162
- }
163
- const shouldSkipMinify = options.get("no-minify") || options.get("skip-minify")
164
- let shouldMinify
165
- if (null != shouldSkipMinify) {
166
- if ("boolean" != typeof shouldSkipMinify) {
167
- logError(
168
- `The value for ${colourN(optionsHasNoMinify ? "--no-minify" : "--skip-minify")} must be ${colourV("true")} or ${colourV("false")}\n`
162
+ const scripts = commands.slice(2)
163
+ if (scripts.length) {
164
+ const invalidScript = scripts.find(
165
+ script => !/^(?:[a-z_][a-z\d_]{0,24}|\*)\.(?:[a-z_][a-z\d_]{0,24}|\*)$/.test(script)
169
166
  )
170
- logHelp()
171
- break
167
+ if (invalidScript) {
168
+ logError(`Invalid script name: ${JSON.stringify(invalidScript)}\n`)
169
+ logHelp()
170
+ process.exit(1)
171
+ }
172
+ } else scripts.push("*.*")
173
+ if ("push" == commands[0]) {
174
+ const { push, MissingSourceFolderError, MissingHackmudFolderError, NoUsersError } =
175
+ await pushModule,
176
+ infos = await push(sourcePath, hackmudPath, {
177
+ scripts,
178
+ onPush: info => logInfo(info, hackmudPath),
179
+ minify: noMinifyOption && !noMinifyOption.value,
180
+ mangleNames: mangleNamesOption?.value,
181
+ forceQuineCheats: forceQuineCheatsOption?.value
182
+ })
183
+ if (infos instanceof Error) {
184
+ logError(infos.message)
185
+ if (infos instanceof MissingSourceFolderError || infos instanceof NoUsersError) {
186
+ console.log()
187
+ logHelp()
188
+ } else
189
+ infos instanceof MissingHackmudFolderError &&
190
+ log(
191
+ `If this is not where your hackmud folder is, you can specify it with the\n${colourN("--hackmud-path")}=${colourB("<path>")} option or ${colourN("HSM_HACKMUD_PATH")} environment variable`
192
+ )
193
+ } else infos.length || logError("Could not find any scripts to push")
194
+ } else {
195
+ const typeDeclarationPathOption = popOption(
196
+ "type-declaration-path",
197
+ "type-declaration",
198
+ "dts",
199
+ "gen-types"
200
+ ),
201
+ { watch } = await watchModule
202
+ watch(sourcePath, hackmudPath, {
203
+ scripts,
204
+ onPush: info => logInfo(info, hackmudPath),
205
+ typeDeclarationPath: typeDeclarationPathOption?.value.toString(),
206
+ minify: noMinifyOption && !noMinifyOption.value,
207
+ mangleNames: mangleNamesOption?.value,
208
+ onReady: () => log("Watching"),
209
+ forceQuineCheats: forceQuineCheatsOption?.value
210
+ })
211
+ autoExit = !1
172
212
  }
173
- shouldMinify = !shouldSkipMinify
174
213
  }
175
- const shouldMangleNames = options.get("mangle-names")
176
- if (null != shouldMangleNames && "boolean" != typeof shouldMangleNames) {
177
- logError(
178
- `The value for ${colourN("--mangle-names")} must be ${colourV("true")} or ${colourV("false")}\n`
179
- )
180
- logHelp()
181
- break
182
- }
183
- const shouldforceQuineCheats = options.get("force-quine-cheats")
184
- if (null != shouldforceQuineCheats && "boolean" != typeof shouldforceQuineCheats) {
185
- logError(
186
- `The value for ${colourN("--force-quine-cheats")} must be ${colourV("true")} or ${colourV("false")}\n`
187
- )
188
- logHelp()
189
- break
190
- }
191
- const { watch } = await watchModule
192
- watch(sourcePath, hackmudPath, {
193
- scripts,
194
- onPush: info => logInfo(info, hackmudPath),
195
- typeDeclarationPath: (
196
- options.get("type-declaration-path") ||
197
- options.get("type-declaration") ||
198
- options.get("dts") ||
199
- options.get("gen-types")
200
- )?.toString(),
201
- minify: shouldMinify,
202
- mangleNames: shouldMangleNames,
203
- onReady: () => log("Watching"),
204
- forceQuineCheats: shouldforceQuineCheats
205
- })
206
- autoExit = !1
207
214
  }
208
215
  break
209
216
  case "pull":
@@ -213,7 +220,7 @@ switch (commands[0]) {
213
220
  if (!script) {
214
221
  logError("Must provide the script to pull\n")
215
222
  logHelp()
216
- break
223
+ process.exit(1)
217
224
  }
218
225
  const sourcePath = commands[2] || "."
219
226
  await pull(sourcePath, hackmudPath, script).catch(error => {
@@ -234,18 +241,19 @@ switch (commands[0]) {
234
241
  case "gen-dts":
235
242
  case "gen-types":
236
243
  {
237
- const target = commands[1]
244
+ const hackmudPath = getHackmudPath(),
245
+ target = commands[1]
238
246
  if (!target) {
239
247
  logError("Must provide target directory\n")
240
248
  logHelp()
241
- break
249
+ process.exit(1)
242
250
  }
243
251
  const sourcePath = resolve(target),
244
252
  outputPath = commands[2] || "./player.d.ts",
245
- typeDeclaration = await generateTypeDeclaration(sourcePath, getHackmudPath())
253
+ typeDeclaration = await generateTypeDeclaration(sourcePath, hackmudPath)
246
254
  let typeDeclarationPath = resolve(outputPath)
247
255
  await writeFile(typeDeclarationPath, typeDeclaration).catch(error => {
248
- assert(error instanceof Error, "src/bin/hsm.ts:327:35")
256
+ assert(error instanceof Error, "src/bin/hsm.ts:321:35")
249
257
  if ("EISDIR" != error.code) throw error
250
258
  typeDeclarationPath = resolve(typeDeclarationPath, "player.d.ts")
251
259
  return writeFile(typeDeclarationPath, typeDeclaration)
@@ -257,99 +265,6 @@ switch (commands[0]) {
257
265
  case "h":
258
266
  logHelp()
259
267
  break
260
- case "golf":
261
- case "minify":
262
- {
263
- const target = commands[1]
264
- if (!target) {
265
- logError("Must provide target\n")
266
- logHelp()
267
- break
268
- }
269
- const fileExtension = extname(target)
270
- if (!supportedExtensions.includes(fileExtension)) {
271
- logError(
272
- `Unsupported file extension "${chalk.bold(fileExtension)}"\nSupported extensions are "${supportedExtensions.map(extension => chalk.bold(extension)).join('", "')}"`
273
- )
274
- break
275
- }
276
- const { processScript } = await processScriptModule,
277
- fileBaseName = basename(target, fileExtension),
278
- fileBaseNameEndsWithDotSrc = fileBaseName.endsWith(".src"),
279
- scriptName = fileBaseNameEndsWithDotSrc ? fileBaseName.slice(0, -4) : fileBaseName,
280
- scriptUser =
281
- "scripts" == basename(resolve(target, "..")) && "hackmud" == basename(resolve(target, "../../..")) ?
282
- basename(resolve(target, "../.."))
283
- : void 0,
284
- optionsHasNoMinify = options.has("no-minify")
285
- if ((optionsHasNoMinify || options.has("skip-minify")) && options.has("mangle-names")) {
286
- logError(
287
- `Options ${colourN("--mangle-names")} and ${colourN(optionsHasNoMinify ? "--no-minify" : "--skip-minify")} are incompatible\n`
288
- )
289
- logHelp()
290
- break
291
- }
292
- const mangleNames_ = options.get("mangle-names")
293
- if (null != mangleNames_ && "boolean" != typeof mangleNames_) {
294
- logError(
295
- `The value for ${colourN("--mangle-names")} must be ${colourV("true")} or ${colourV("false")}\n`
296
- )
297
- logHelp()
298
- break
299
- }
300
- const mangleNames = mangleNames_,
301
- forceQuineCheats_ = options.get("force-quine-cheats")
302
- if (null != forceQuineCheats_ && "boolean" != typeof forceQuineCheats_) {
303
- logError(
304
- `the value for ${colourN("--force-quine-cheats")} must be ${colourV("true")} or ${colourV("false")}\n`
305
- )
306
- logHelp()
307
- break
308
- }
309
- const forceQuineCheats = forceQuineCheats_
310
- let outputPath =
311
- commands[2] ||
312
- resolve(
313
- dirname(target),
314
- fileBaseNameEndsWithDotSrc ? scriptName + ".js"
315
- : ".js" == fileExtension ? fileBaseName + ".min.js"
316
- : fileBaseName + ".js"
317
- )
318
- const golfFile = () =>
319
- readFile(target, { encoding: "utf8" }).then(async source => {
320
- const timeStart = performance.now(),
321
- { script, warnings } = await processScript(source, {
322
- minify: !(options.get("no-minify") || options.get("skip-minify")),
323
- scriptUser,
324
- scriptName,
325
- filePath: target,
326
- mangleNames,
327
- forceQuineCheats
328
- }),
329
- timeTook = performance.now() - timeStart
330
- for (const { message, line } of warnings)
331
- log(`Warning "${chalk.bold(message)}" on line ${chalk.bold(line + "")}`)
332
- await writeFilePersistent(outputPath, script)
333
- .catch(error => {
334
- if (!commands[2] || "EISDIR" != error.code) throw error
335
- outputPath = resolve(outputPath, basename(target, fileExtension) + ".js")
336
- return writeFilePersistent(outputPath, script)
337
- })
338
- .then(() =>
339
- log(
340
- `Wrote ${chalk.bold(countHackmudCharacters(script))} chars to ${chalk.bold(relative(".", outputPath))} | took ${Math.round(100 * timeTook) / 100}ms`
341
- )
342
- )
343
- })
344
- if (options.get("watch")) {
345
- const { watch: watchFile } = await chokidarModule
346
- watchFile(target, { awaitWriteFinish: { stabilityThreshold: 100 } })
347
- .on("ready", () => log("Watching " + target))
348
- .on("change", golfFile)
349
- autoExit = !1
350
- } else await golfFile()
351
- }
352
- break
353
268
  default:
354
269
  commands[0] && logError(`Unknown command: ${colourL(commands[0])}\n`)
355
270
  logHelp()
@@ -366,7 +281,7 @@ function logHelp() {
366
281
  case "push":
367
282
  console.log(
368
283
  colourS(
369
- `\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${hackmudPathOption}\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`
284
+ `\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${hackmudPathOption}\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 Pushes 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 Pushes 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 Pushes 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 Pushes all scripts found in ${colourV("src")} folder to all users`
370
285
  )
371
286
  )
372
287
  break
@@ -423,7 +338,7 @@ function logError(message) {
423
338
  process.exitCode = 1
424
339
  }
425
340
  function getHackmudPath() {
426
- const hackmudPathOption = options.get("hackmud-path")
341
+ const hackmudPathOption = popOption("hackmud-path")
427
342
  if (null != hackmudPathOption && "string" != typeof hackmudPathOption) {
428
343
  logError(`Option ${colourN("--hackmud-path")} must be a string, got ${colourV(hackmudPathOption)}\n`)
429
344
  logHelp()
@@ -435,3 +350,26 @@ function getHackmudPath() {
435
350
  ("win32" == process.platform ? resolve(process.env.APPDATA, "hackmud") : resolve(homedir(), ".config/hackmud"))
436
351
  )
437
352
  }
353
+ function assertOptionIsBoolean(option) {
354
+ if ("boolean" != typeof option.value) {
355
+ logError(`The value for ${colourN(option.name)} must be ${colourV("true")} or ${colourV("false")}\n`)
356
+ logHelp()
357
+ process.exit(1)
358
+ }
359
+ }
360
+ function popOption(...names) {
361
+ const presentOptionNames = names.filter(name => options.has(name))
362
+ if (!presentOptionNames.length) return
363
+ const presentOptionNamesWithDashDash = presentOptionNames.map(name =>
364
+ colourN(`-${1 == name.length ? "" : "-"}${name}`)
365
+ )
366
+ if (presentOptionNames.length > 1) {
367
+ logError(
368
+ `The options ${presentOptionNamesWithDashDash.join(", ")} are aliases for each other. Please only specify one`
369
+ )
370
+ process.exit(1)
371
+ }
372
+ const value = options.get(presentOptionNames[0])
373
+ options.delete(presentOptionNames[0])
374
+ return { name: presentOptionNamesWithDashDash[0], value }
375
+ }