hackmud-script-manager 0.20.4-c1681d7 → 0.20.4-c17726e

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
@@ -20,6 +20,10 @@ You can read about how HSM works [in my blog post](https://samual.uk/blog/js-cod
20
20
  > ```
21
21
  > You will need to run `Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope CurrentUser` in PowerShell as an administrator. For more information, see [Microsoft's page about Execution Policies](https://learn.microsoft.com/en-gb/powershell/module/microsoft.powershell.core/about/about_execution_policies?view=powershell-7.4).
22
22
 
23
+ ![image](https://github.com/samualtnorman/hackmud-script-manager/assets/18307063/69a371fe-f8c8-43fe-b3c7-39f3735ce6fb)
24
+ ![image](https://github.com/samualtnorman/hackmud-script-manager/assets/18307063/08103f9e-74fa-4a56-a739-94858ba8c139)
25
+ ![image](https://github.com/samualtnorman/hackmud-script-manager/assets/18307063/25ccb86d-1fe3-4632-b703-ac47f5b32c9c)
26
+
23
27
  ## Features
24
28
  - Minification
25
29
  - This includes auto quine cheating.
package/bin/hsm.js CHANGED
@@ -1,21 +1,22 @@
1
1
  #!/usr/bin/env node
2
- import { Cache } from "@samual/lib/Cache"
2
+ import { AutoMap } from "@samual/lib/AutoMap"
3
3
  import { assert } from "@samual/lib/assert"
4
4
  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.4-c1681d7",
16
+ const formatOption = name => colourN(`-${1 == name.length ? "" : "-"}${name}`),
16
17
  options = new Map(),
17
18
  commands = [],
18
- userColours = new Cache(user => {
19
+ userColours = new AutoMap(user => {
19
20
  let hash = 0
20
21
  for (const char of user) hash += (hash >> 1) + hash + "xi1_8ratvsw9hlbgm02y5zpdcn7uekof463qj".indexOf(char) + 1
21
22
  return [colourJ, colourK, colourM, colourW, colourL, colourB][hash % 6](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"),
@@ -49,6 +46,7 @@ const pushModule = import("../push.js"),
49
46
  colourB = chalk.rgb(202, 202, 202),
50
47
  colourC = chalk.rgb(155, 155, 155),
51
48
  colourD = chalk.rgb(255, 0, 0),
49
+ colourF = chalk.rgb(255, 128, 0),
52
50
  colourJ = chalk.rgb(255, 244, 4),
53
51
  colourK = chalk.rgb(243, 249, 152),
54
52
  colourL = chalk.rgb(30, 255, 0),
@@ -57,162 +55,171 @@ const pushModule = import("../push.js"),
57
55
  colourS = chalk.rgb(122, 178, 244),
58
56
  colourV = chalk.rgb(255, 0, 236),
59
57
  colourW = chalk.rgb(255, 150, 224)
60
- if (options.get("help") || options.get("h")) {
58
+ process.version.startsWith("v21.") &&
59
+ console.warn(
60
+ colourF(
61
+ "Warning: Support for Node.js 21 will be dropped in the next minor version of HSM\n You should update your version of Node.js\n https://nodejs.org/en/download/package-manager"
62
+ )
63
+ )
64
+ if ("v" == commands[0] || "version" == commands[0] || popOption("version", "v")?.value) {
65
+ console.log("0.20.4-c17726e")
66
+ process.exit()
67
+ }
68
+ if (popOption("help", "h")?.value) {
61
69
  logHelp()
62
70
  process.exit()
63
71
  }
64
72
  let autoExit = !0
65
73
  switch (commands[0]) {
66
74
  case "push":
75
+ case "dev":
76
+ case "watch":
77
+ case "golf":
78
+ case "minify":
67
79
  {
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")) {
80
+ const noMinifyOption = popOption("no-minify", "skip-minify"),
81
+ mangleNamesOption = popOption("mangle-names"),
82
+ forceQuineCheatsOption = popOption("force-quine-cheats"),
83
+ noMinifyIncompatibleOption = mangleNamesOption || forceQuineCheatsOption
84
+ if (noMinifyOption && noMinifyIncompatibleOption) {
88
85
  logError(
89
- `Options ${colourN("--mangle-names")} and ${colourN(optionsHasNoMinify ? "--no-minify" : "--skip-minify")} are incompatible\n`
86
+ `Options ${formatOption(noMinifyOption.name)} and ${formatOption(noMinifyIncompatibleOption.name)} are incompatible\n`
90
87
  )
91
88
  logHelp()
92
- break
89
+ process.exit(1)
93
90
  }
94
- const shouldSkipMinify = options.get("no-minify") || options.get("skip-minify")
95
- let shouldMinify
96
- if (null != shouldSkipMinify) {
97
- if ("boolean" != typeof shouldSkipMinify) {
91
+ noMinifyOption && assertOptionIsBoolean(noMinifyOption)
92
+ mangleNamesOption && assertOptionIsBoolean(mangleNamesOption)
93
+ forceQuineCheatsOption && assertOptionIsBoolean(forceQuineCheatsOption)
94
+ if ("golf" == commands[0] || "minify" == commands[0]) {
95
+ const watchOption = popOption("watch"),
96
+ target = commands[1]
97
+ if (!target) {
98
+ logError("Must provide target\n")
99
+ logHelp()
100
+ process.exit(1)
101
+ }
102
+ const fileExtension = extname(target)
103
+ if (!supportedExtensions.includes(fileExtension)) {
98
104
  logError(
99
- `The value for ${colourN(optionsHasNoMinify ? "--no-minify" : "--skip-minify")} must be ${colourV("true")} or ${colourV("false")}\n`
105
+ `Unsupported file extension "${chalk.bold(fileExtension)}"\nSupported extensions are "${supportedExtensions.map(extension => chalk.bold(extension)).join('", "')}"`
100
106
  )
101
- logHelp()
102
- break
107
+ process.exit(1)
103
108
  }
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, MissingSourceFolderError, MissingHackmudFolderError, NoUsersError } = await pushModule,
123
- infos = await push(sourcePath, hackmudPath, {
124
- scripts,
125
- onPush: info => logInfo(info, hackmudPath),
126
- minify: shouldMinify,
127
- mangleNames: shouldMangleNames,
128
- forceQuineCheats: shouldforceQuineCheats
129
- })
130
- if (infos instanceof Error) {
131
- logError(infos.message)
132
- if (infos instanceof MissingSourceFolderError || infos instanceof NoUsersError) {
133
- console.log()
134
- logHelp()
135
- } else
136
- infos instanceof MissingHackmudFolderError &&
137
- log(
138
- `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`
139
- )
140
- } else infos.length || logError("Could not find any scripts to push")
141
- }
142
- break
143
- case "dev":
144
- case "watch":
145
- {
146
- const hackmudPath = getHackmudPath(),
147
- sourcePath = commands[1]
148
- if (!sourcePath) {
149
- logError("Must provide the directory to watch\n")
150
- logHelp()
151
- break
152
- }
153
- const scripts = commands.slice(2)
154
- if (scripts.length) {
155
- const invalidScript = scripts.find(
156
- script => !/^(?:[a-z_][a-z\d_]{0,24}|\*)\.(?:[a-z_][a-z\d_]{0,24}|\*)$/.test(script)
157
- )
158
- if (invalidScript) {
159
- logError(`Invalid script name: ${JSON.stringify(invalidScript)}\n`)
109
+ complainAboutUnrecognisedOptions()
110
+ const { processScript } = await processScriptModule,
111
+ fileBaseName = basename(target, fileExtension),
112
+ fileBaseNameEndsWithDotSrc = fileBaseName.endsWith(".src"),
113
+ scriptName = fileBaseNameEndsWithDotSrc ? fileBaseName.slice(0, -4) : fileBaseName,
114
+ scriptUser =
115
+ (
116
+ "scripts" == basename(resolve(target, "..")) &&
117
+ "hackmud" == basename(resolve(target, "../../.."))
118
+ ) ?
119
+ basename(resolve(target, "../.."))
120
+ : void 0
121
+ let outputPath =
122
+ commands[2] ||
123
+ resolve(
124
+ dirname(target),
125
+ fileBaseNameEndsWithDotSrc ? scriptName + ".js"
126
+ : ".js" == fileExtension ? fileBaseName + ".min.js"
127
+ : fileBaseName + ".js"
128
+ )
129
+ const golfFile = () =>
130
+ readFile(target, { encoding: "utf8" }).then(async source => {
131
+ const timeStart = performance.now(),
132
+ { script, warnings } = await processScript(source, {
133
+ minify: noMinifyOption && !noMinifyOption.value,
134
+ scriptUser,
135
+ scriptName,
136
+ filePath: target,
137
+ mangleNames: mangleNamesOption?.value,
138
+ forceQuineCheats: forceQuineCheatsOption?.value
139
+ }),
140
+ timeTook = performance.now() - timeStart
141
+ for (const { message, line } of warnings)
142
+ log(`Warning "${chalk.bold(message)}" on line ${chalk.bold(line + "")}`)
143
+ await writeFilePersistent(outputPath, script)
144
+ .catch(error => {
145
+ if (!commands[2] || "EISDIR" != error.code) throw error
146
+ outputPath = resolve(outputPath, basename(target, fileExtension) + ".js")
147
+ return writeFilePersistent(outputPath, script)
148
+ })
149
+ .then(() =>
150
+ log(
151
+ `Wrote ${chalk.bold(countHackmudCharacters(script))} chars to ${chalk.bold(relative(".", outputPath))} | took ${Math.round(100 * timeTook) / 100}ms`
152
+ )
153
+ )
154
+ })
155
+ if (watchOption) {
156
+ const { watch: watchFile } = await chokidarModule
157
+ watchFile(target, { awaitWriteFinish: { stabilityThreshold: 100 } })
158
+ .on("ready", () => log("Watching " + target))
159
+ .on("change", golfFile)
160
+ autoExit = !1
161
+ } else await golfFile()
162
+ } else {
163
+ const hackmudPath = getHackmudPath(),
164
+ sourcePath = commands[1]
165
+ if (!sourcePath) {
166
+ logError(`Must provide the directory to ${"push" == commands[0] ? "push from" : "watch"}\n`)
160
167
  logHelp()
161
- break
168
+ process.exit(1)
162
169
  }
163
- } else scripts.push("*.*")
164
- const optionsHasNoMinify = options.has("no-minify")
165
- if ((optionsHasNoMinify || options.has("skip-minify")) && options.has("mangle-names")) {
166
- logError(
167
- `Options ${colourN("--mangle-names")} and ${colourN(optionsHasNoMinify ? "--no-minify" : "--skip-minify")} are incompatible\n`
168
- )
169
- logHelp()
170
- break
171
- }
172
- const shouldSkipMinify = options.get("no-minify") || options.get("skip-minify")
173
- let shouldMinify
174
- if (null != shouldSkipMinify) {
175
- if ("boolean" != typeof shouldSkipMinify) {
176
- logError(
177
- `The value for ${colourN(optionsHasNoMinify ? "--no-minify" : "--skip-minify")} must be ${colourV("true")} or ${colourV("false")}\n`
170
+ const scripts = commands.slice(2)
171
+ if (scripts.length) {
172
+ const invalidScript = scripts.find(
173
+ script => !/^(?:[a-z_][a-z\d_]{0,24}|\*)\.(?:[a-z_][a-z\d_]{0,24}|\*)$/.test(script)
178
174
  )
179
- logHelp()
180
- break
175
+ if (invalidScript) {
176
+ logError(`Invalid script name: ${JSON.stringify(invalidScript)}\n`)
177
+ logHelp()
178
+ process.exit(1)
179
+ }
180
+ } else scripts.push("*.*")
181
+ if ("push" == commands[0]) {
182
+ const { push, MissingSourceFolderError, MissingHackmudFolderError, NoUsersError } = await pushModule
183
+ complainAboutUnrecognisedOptions()
184
+ const infos = await push(sourcePath, hackmudPath, {
185
+ scripts,
186
+ onPush: info => logInfo(info, hackmudPath),
187
+ minify: noMinifyOption && !noMinifyOption.value,
188
+ mangleNames: mangleNamesOption?.value,
189
+ forceQuineCheats: forceQuineCheatsOption?.value
190
+ })
191
+ if (infos instanceof Error) {
192
+ logError(infos.message)
193
+ if (infos instanceof MissingSourceFolderError || infos instanceof NoUsersError) {
194
+ console.log()
195
+ logHelp()
196
+ } else
197
+ infos instanceof MissingHackmudFolderError &&
198
+ log(
199
+ `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`
200
+ )
201
+ } else infos.length || logError("Could not find any scripts to push")
202
+ } else {
203
+ const typeDeclarationPathOption = popOption(
204
+ "type-declaration-path",
205
+ "type-declaration",
206
+ "dts",
207
+ "gen-types"
208
+ )
209
+ complainAboutUnrecognisedOptions()
210
+ const { watch } = await watchModule
211
+ watch(sourcePath, hackmudPath, {
212
+ scripts,
213
+ onPush: info => logInfo(info, hackmudPath),
214
+ typeDeclarationPath: typeDeclarationPathOption?.value.toString(),
215
+ minify: noMinifyOption && !noMinifyOption.value,
216
+ mangleNames: mangleNamesOption?.value,
217
+ onReady: () => log("Watching"),
218
+ forceQuineCheats: forceQuineCheatsOption?.value
219
+ })
220
+ autoExit = !1
181
221
  }
182
- shouldMinify = !shouldSkipMinify
183
222
  }
184
- const shouldMangleNames = options.get("mangle-names")
185
- if (null != shouldMangleNames && "boolean" != typeof shouldMangleNames) {
186
- logError(
187
- `The value for ${colourN("--mangle-names")} must be ${colourV("true")} or ${colourV("false")}\n`
188
- )
189
- logHelp()
190
- break
191
- }
192
- const shouldforceQuineCheats = options.get("force-quine-cheats")
193
- if (null != shouldforceQuineCheats && "boolean" != typeof shouldforceQuineCheats) {
194
- logError(
195
- `The value for ${colourN("--force-quine-cheats")} must be ${colourV("true")} or ${colourV("false")}\n`
196
- )
197
- logHelp()
198
- break
199
- }
200
- const { watch } = await watchModule
201
- watch(sourcePath, hackmudPath, {
202
- scripts,
203
- onPush: info => logInfo(info, hackmudPath),
204
- typeDeclarationPath: (
205
- options.get("type-declaration-path") ||
206
- options.get("type-declaration") ||
207
- options.get("dts") ||
208
- options.get("gen-types")
209
- )?.toString(),
210
- minify: shouldMinify,
211
- mangleNames: shouldMangleNames,
212
- onReady: () => log("Watching"),
213
- forceQuineCheats: shouldforceQuineCheats
214
- })
215
- autoExit = !1
216
223
  }
217
224
  break
218
225
  case "pull":
@@ -222,8 +229,9 @@ switch (commands[0]) {
222
229
  if (!script) {
223
230
  logError("Must provide the script to pull\n")
224
231
  logHelp()
225
- break
232
+ process.exit(1)
226
233
  }
234
+ complainAboutUnrecognisedOptions()
227
235
  const sourcePath = commands[2] || "."
228
236
  await pull(sourcePath, hackmudPath, script).catch(error => {
229
237
  console.error(error)
@@ -233,8 +241,9 @@ switch (commands[0]) {
233
241
  break
234
242
  case "sync-macros":
235
243
  {
236
- const hackmudPath = getHackmudPath(),
237
- { macrosSynced, usersSynced } = await syncMacros(hackmudPath)
244
+ const hackmudPath = getHackmudPath()
245
+ complainAboutUnrecognisedOptions()
246
+ const { macrosSynced, usersSynced } = await syncMacros(hackmudPath)
238
247
  log(`Synced ${macrosSynced} macros to ${usersSynced} users`)
239
248
  }
240
249
  break
@@ -243,18 +252,20 @@ switch (commands[0]) {
243
252
  case "gen-dts":
244
253
  case "gen-types":
245
254
  {
246
- const target = commands[1]
255
+ const hackmudPath = getHackmudPath(),
256
+ target = commands[1]
247
257
  if (!target) {
248
258
  logError("Must provide target directory\n")
249
259
  logHelp()
250
- break
260
+ process.exit(1)
251
261
  }
262
+ complainAboutUnrecognisedOptions()
252
263
  const sourcePath = resolve(target),
253
264
  outputPath = commands[2] || "./player.d.ts",
254
- typeDeclaration = await generateTypeDeclaration(sourcePath, getHackmudPath())
265
+ typeDeclaration = await generateTypeDeclaration(sourcePath, hackmudPath)
255
266
  let typeDeclarationPath = resolve(outputPath)
256
267
  await writeFile(typeDeclarationPath, typeDeclaration).catch(error => {
257
- assert(error instanceof Error, "src/bin/hsm.ts:340:35")
268
+ assert(error instanceof Error, "src/bin/hsm.ts:343:35")
258
269
  if ("EISDIR" != error.code) throw error
259
270
  typeDeclarationPath = resolve(typeDeclarationPath, "player.d.ts")
260
271
  return writeFile(typeDeclarationPath, typeDeclaration)
@@ -266,99 +277,6 @@ switch (commands[0]) {
266
277
  case "h":
267
278
  logHelp()
268
279
  break
269
- case "golf":
270
- case "minify":
271
- {
272
- const target = commands[1]
273
- if (!target) {
274
- logError("Must provide target\n")
275
- logHelp()
276
- break
277
- }
278
- const fileExtension = extname(target)
279
- if (!supportedExtensions.includes(fileExtension)) {
280
- logError(
281
- `Unsupported file extension "${chalk.bold(fileExtension)}"\nSupported extensions are "${supportedExtensions.map(extension => chalk.bold(extension)).join('", "')}"`
282
- )
283
- break
284
- }
285
- const { processScript } = await processScriptModule,
286
- fileBaseName = basename(target, fileExtension),
287
- fileBaseNameEndsWithDotSrc = fileBaseName.endsWith(".src"),
288
- scriptName = fileBaseNameEndsWithDotSrc ? fileBaseName.slice(0, -4) : fileBaseName,
289
- scriptUser =
290
- "scripts" == basename(resolve(target, "..")) && "hackmud" == basename(resolve(target, "../../..")) ?
291
- basename(resolve(target, "../.."))
292
- : void 0,
293
- optionsHasNoMinify = options.has("no-minify")
294
- if ((optionsHasNoMinify || options.has("skip-minify")) && options.has("mangle-names")) {
295
- logError(
296
- `Options ${colourN("--mangle-names")} and ${colourN(optionsHasNoMinify ? "--no-minify" : "--skip-minify")} are incompatible\n`
297
- )
298
- logHelp()
299
- break
300
- }
301
- const mangleNames_ = options.get("mangle-names")
302
- if (null != mangleNames_ && "boolean" != typeof mangleNames_) {
303
- logError(
304
- `The value for ${colourN("--mangle-names")} must be ${colourV("true")} or ${colourV("false")}\n`
305
- )
306
- logHelp()
307
- break
308
- }
309
- const mangleNames = mangleNames_,
310
- forceQuineCheats_ = options.get("force-quine-cheats")
311
- if (null != forceQuineCheats_ && "boolean" != typeof forceQuineCheats_) {
312
- logError(
313
- `the value for ${colourN("--force-quine-cheats")} must be ${colourV("true")} or ${colourV("false")}\n`
314
- )
315
- logHelp()
316
- break
317
- }
318
- const forceQuineCheats = forceQuineCheats_
319
- let outputPath =
320
- commands[2] ||
321
- resolve(
322
- dirname(target),
323
- fileBaseNameEndsWithDotSrc ? scriptName + ".js"
324
- : ".js" == fileExtension ? fileBaseName + ".min.js"
325
- : fileBaseName + ".js"
326
- )
327
- const golfFile = () =>
328
- readFile(target, { encoding: "utf8" }).then(async source => {
329
- const timeStart = performance.now(),
330
- { script, warnings } = await processScript(source, {
331
- minify: !(options.get("no-minify") || options.get("skip-minify")),
332
- scriptUser,
333
- scriptName,
334
- filePath: target,
335
- mangleNames,
336
- forceQuineCheats
337
- }),
338
- timeTook = performance.now() - timeStart
339
- for (const { message, line } of warnings)
340
- log(`Warning "${chalk.bold(message)}" on line ${chalk.bold(line + "")}`)
341
- await writeFilePersistent(outputPath, script)
342
- .catch(error => {
343
- if (!commands[2] || "EISDIR" != error.code) throw error
344
- outputPath = resolve(outputPath, basename(target, fileExtension) + ".js")
345
- return writeFilePersistent(outputPath, script)
346
- })
347
- .then(() =>
348
- log(
349
- `Wrote ${chalk.bold(countHackmudCharacters(script))} chars to ${chalk.bold(relative(".", outputPath))} | took ${Math.round(100 * timeTook) / 100}ms`
350
- )
351
- )
352
- })
353
- if (options.get("watch")) {
354
- const { watch: watchFile } = await chokidarModule
355
- watchFile(target, { awaitWriteFinish: { stabilityThreshold: 100 } })
356
- .on("ready", () => log("Watching " + target))
357
- .on("change", golfFile)
358
- autoExit = !1
359
- } else await golfFile()
360
- }
361
- break
362
280
  default:
363
281
  commands[0] && logError(`Unknown command: ${colourL(commands[0])}\n`)
364
282
  logHelp()
@@ -368,7 +286,7 @@ function logHelp() {
368
286
  const pushCommandDescription = "Push scripts from a directory to hackmud user's scripts directories",
369
287
  forceQuineCheatsOptionDescription = `Force quine cheats on. Use ${colourN("--force-quine-cheats")}=${colourV("false")} to force off`,
370
288
  hackmudPathOption = `${colourN("--hackmud-path")}=${colourB("<path>")}\n Override hackmud path`
371
- console.log(colourN("Version") + colourS(": ") + colourV(version))
289
+ console.log(colourN("Version") + colourS(": ") + colourV("0.20.4-c17726e"))
372
290
  switch (commands[0]) {
373
291
  case "dev":
374
292
  case "watch":
@@ -432,7 +350,7 @@ function logError(message) {
432
350
  process.exitCode = 1
433
351
  }
434
352
  function getHackmudPath() {
435
- const hackmudPathOption = options.get("hackmud-path")
353
+ const hackmudPathOption = popOption("hackmud-path")?.value
436
354
  if (null != hackmudPathOption && "string" != typeof hackmudPathOption) {
437
355
  logError(`Option ${colourN("--hackmud-path")} must be a string, got ${colourV(hackmudPathOption)}\n`)
438
356
  logHelp()
@@ -444,3 +362,32 @@ function getHackmudPath() {
444
362
  ("win32" == process.platform ? resolve(process.env.APPDATA, "hackmud") : resolve(homedir(), ".config/hackmud"))
445
363
  )
446
364
  }
365
+ function assertOptionIsBoolean(option) {
366
+ if ("boolean" != typeof option.value) {
367
+ logError(`The value for ${formatOption(option.name)} must be ${colourV("true")} or ${colourV("false")}\n`)
368
+ logHelp()
369
+ process.exit(1)
370
+ }
371
+ }
372
+ function popOption(...names) {
373
+ const presentOptionNames = names.filter(name => options.has(name))
374
+ if (!presentOptionNames.length) return
375
+ const presentOptionNamesWithDashDash = presentOptionNames.map(formatOption)
376
+ if (presentOptionNames.length > 1) {
377
+ logError(
378
+ `The options ${presentOptionNamesWithDashDash.join(", ")} are aliases for each other. Please only specify one`
379
+ )
380
+ process.exit(1)
381
+ }
382
+ const value = options.get(presentOptionNames[0])
383
+ options.delete(presentOptionNames[0])
384
+ return { name: presentOptionNamesWithDashDash[0], value }
385
+ }
386
+ function complainAboutUnrecognisedOptions() {
387
+ if (options.size) {
388
+ logError(
389
+ `Unrecognised option${options.size > 1 ? "s" : ""}: ${[...options.keys()].map(formatOption).join(", ")}`
390
+ )
391
+ process.exit(1)
392
+ }
393
+ }