hackmud-script-manager 0.20.4-a5e6bcd → 0.20.4-a7d8309

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-a5e6bcd",
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,180 @@ 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 Your current version of Node.js is ${chalk.bold(process.version)}\n You should update your version of Node.js\n https://nodejs.org/en/download/package-manager\n`
62
+ )
63
+ )
64
+ if ("v" == commands[0] || "version" == commands[0] || popOption("version", "v")?.value) {
65
+ console.log("0.20.4-a7d8309")
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 dtsPathOption = popOption(
204
+ "dts-path",
205
+ "type-declaration-path",
206
+ "type-declaration",
207
+ "dts",
208
+ "gen-types"
209
+ )
210
+ dtsPathOption &&
211
+ "emit-dts" != dtsPathOption.name &&
212
+ "gen-dts" != dtsPathOption.name &&
213
+ console.warn(
214
+ colourF(
215
+ `Warning: ${colourN(dtsPathOption.name)} is being deprecated and will be removed in the\n next minor release of HSM\n You should switch to using its alias ${colourN("--dts-path")}\n`
216
+ )
217
+ )
218
+ complainAboutUnrecognisedOptions()
219
+ const { watch } = await watchModule
220
+ watch(sourcePath, hackmudPath, {
221
+ scripts,
222
+ onPush: info => logInfo(info, hackmudPath),
223
+ typeDeclarationPath: dtsPathOption?.value.toString(),
224
+ minify: noMinifyOption && !noMinifyOption.value,
225
+ mangleNames: mangleNamesOption?.value,
226
+ onReady: () => log("Watching"),
227
+ forceQuineCheats: forceQuineCheatsOption?.value
228
+ })
229
+ autoExit = !1
181
230
  }
182
- shouldMinify = !shouldSkipMinify
183
231
  }
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
232
  }
217
233
  break
218
234
  case "pull":
@@ -222,8 +238,9 @@ switch (commands[0]) {
222
238
  if (!script) {
223
239
  logError("Must provide the script to pull\n")
224
240
  logHelp()
225
- break
241
+ process.exit(1)
226
242
  }
243
+ complainAboutUnrecognisedOptions()
227
244
  const sourcePath = commands[2] || "."
228
245
  await pull(sourcePath, hackmudPath, script).catch(error => {
229
246
  console.error(error)
@@ -233,8 +250,9 @@ switch (commands[0]) {
233
250
  break
234
251
  case "sync-macros":
235
252
  {
236
- const hackmudPath = getHackmudPath(),
237
- { macrosSynced, usersSynced } = await syncMacros(hackmudPath)
253
+ const hackmudPath = getHackmudPath()
254
+ complainAboutUnrecognisedOptions()
255
+ const { macrosSynced, usersSynced } = await syncMacros(hackmudPath)
238
256
  log(`Synced ${macrosSynced} macros to ${usersSynced} users`)
239
257
  }
240
258
  break
@@ -242,19 +260,29 @@ switch (commands[0]) {
242
260
  case "gen-type-declaration":
243
261
  case "gen-dts":
244
262
  case "gen-types":
263
+ case "emit-dts":
245
264
  {
246
- const target = commands[1]
265
+ "emit-dts" != commands[0] &&
266
+ "gen-dts" != commands[0] &&
267
+ console.warn(
268
+ colourF(
269
+ `Warning: ${colourC("hsm")} ${colourL(commands[0])} is being deprecated and will be removed\n in the next minor release of HSM\n You should switch to using its alias ${colourC("hsm")} ${colourL("emit-dts")}\n`
270
+ )
271
+ )
272
+ const hackmudPath = getHackmudPath(),
273
+ target = commands[1]
247
274
  if (!target) {
248
275
  logError("Must provide target directory\n")
249
276
  logHelp()
250
- break
277
+ process.exit(1)
251
278
  }
279
+ complainAboutUnrecognisedOptions()
252
280
  const sourcePath = resolve(target),
253
281
  outputPath = commands[2] || "./player.d.ts",
254
- typeDeclaration = await generateTypeDeclaration(sourcePath, getHackmudPath())
282
+ typeDeclaration = await generateTypeDeclaration(sourcePath, hackmudPath)
255
283
  let typeDeclarationPath = resolve(outputPath)
256
284
  await writeFile(typeDeclarationPath, typeDeclaration).catch(error => {
257
- assert(error instanceof Error, "src/bin/hsm.ts:340:35")
285
+ assert(error instanceof Error, "src/bin/hsm.ts:361:35")
258
286
  if ("EISDIR" != error.code) throw error
259
287
  typeDeclarationPath = resolve(typeDeclarationPath, "player.d.ts")
260
288
  return writeFile(typeDeclarationPath, typeDeclaration)
@@ -263,102 +291,8 @@ switch (commands[0]) {
263
291
  }
264
292
  break
265
293
  case "help":
266
- case "h":
267
294
  logHelp()
268
295
  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
296
  default:
363
297
  commands[0] && logError(`Unknown command: ${colourL(commands[0])}\n`)
364
298
  logHelp()
@@ -368,21 +302,20 @@ function logHelp() {
368
302
  const pushCommandDescription = "Push scripts from a directory to hackmud user's scripts directories",
369
303
  forceQuineCheatsOptionDescription = `Force quine cheats on. Use ${colourN("--force-quine-cheats")}=${colourV("false")} to force off`,
370
304
  hackmudPathOption = `${colourN("--hackmud-path")}=${colourB("<path>")}\n Override hackmud path`
371
- console.log(colourN("Version") + colourS(": ") + colourV(version))
372
305
  switch (commands[0]) {
373
306
  case "dev":
374
307
  case "watch":
375
308
  case "push":
376
309
  console.log(
377
310
  colourS(
378
- `\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`
311
+ `${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("--dts-path")}=${colourB("<path>")}\n Path to generate a type declaration (.d.ts) 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`
379
312
  )
380
313
  )
381
314
  break
382
315
  case "pull":
383
316
  console.log(
384
317
  colourS(
385
- `\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>")}\n\n${colourA("Options:")}\n${hackmudPathOption}`
318
+ `${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>")}\n\n${colourA("Options:")}\n${hackmudPathOption}`
386
319
  )
387
320
  )
388
321
  break
@@ -390,7 +323,7 @@ function logHelp() {
390
323
  case "golf":
391
324
  console.log(
392
325
  colourS(
393
- `\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`
326
+ `${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`
394
327
  )
395
328
  )
396
329
  break
@@ -398,6 +331,14 @@ function logHelp() {
398
331
  case "gen-type-declaration":
399
332
  case "gen-dts":
400
333
  case "gen-types":
334
+ case "emit-dts":
335
+ "emit-dts" != commands[0] &&
336
+ "gen-dts" != commands[0] &&
337
+ console.warn(
338
+ colourF(
339
+ `Warning: ${colourC("hsm")} ${colourL(commands[0])} is being deprecated and will be removed\n in the next minor release of HSM\n You should switch to using its alias ${colourC("hsm")} ${colourL("emit-dts")}\n`
340
+ )
341
+ )
401
342
  console.log(
402
343
  colourS(
403
344
  `${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]")}\n\n${colourA("Options:")}\n${hackmudPathOption}`
@@ -407,14 +348,14 @@ function logHelp() {
407
348
  case "sync-macros":
408
349
  console.log(
409
350
  colourS(
410
- `\n${colourJ("Sync macros across all hackmud users")}\n\n${colourA("Options:")}\n${hackmudPathOption}`
351
+ `${colourJ("Sync macros across all hackmud users")}\n\n${colourA("Options:")}\n${hackmudPathOption}`
411
352
  )
412
353
  )
413
354
  break
414
355
  default:
415
356
  console.log(
416
357
  colourS(
417
- `\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("pull")}\n Pull a script a from a hackmud user's script directory`
358
+ `${colourJ("Hackmud Script Manager")}\n${colourN("Version") + colourS(": ") + colourV("0.20.4-a7d8309")}\n\n${colourA("Commands:")}\n${colourL("push")}\n ${pushCommandDescription}\n${colourL("dev")}\n Watch a directory and push a script when modified\n${colourL("minify")}\n Minify a script file on the spot\n${colourL("emit-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("pull")}\n Pull a script a from a hackmud user's script directory\n\n${colourA("Options:")}\n${colourN("--help")}\n Can be used on any command e.g. ${colourC("hsm")} ${colourL("push")} ${colourN("--help")} to show helpful information`
418
359
  )
419
360
  )
420
361
  }
@@ -432,7 +373,7 @@ function logError(message) {
432
373
  process.exitCode = 1
433
374
  }
434
375
  function getHackmudPath() {
435
- const hackmudPathOption = options.get("hackmud-path")
376
+ const hackmudPathOption = popOption("hackmud-path")?.value
436
377
  if (null != hackmudPathOption && "string" != typeof hackmudPathOption) {
437
378
  logError(`Option ${colourN("--hackmud-path")} must be a string, got ${colourV(hackmudPathOption)}\n`)
438
379
  logHelp()
@@ -444,3 +385,32 @@ function getHackmudPath() {
444
385
  ("win32" == process.platform ? resolve(process.env.APPDATA, "hackmud") : resolve(homedir(), ".config/hackmud"))
445
386
  )
446
387
  }
388
+ function assertOptionIsBoolean(option) {
389
+ if ("boolean" != typeof option.value) {
390
+ logError(`The value for ${formatOption(option.name)} must be ${colourV("true")} or ${colourV("false")}\n`)
391
+ logHelp()
392
+ process.exit(1)
393
+ }
394
+ }
395
+ function popOption(...names) {
396
+ const presentOptionNames = names.filter(name => options.has(name))
397
+ if (!presentOptionNames.length) return
398
+ const presentOptionNamesWithDashDash = presentOptionNames.map(formatOption)
399
+ if (presentOptionNames.length > 1) {
400
+ logError(
401
+ `The options ${presentOptionNamesWithDashDash.join(", ")} are aliases for each other. Please only specify one`
402
+ )
403
+ process.exit(1)
404
+ }
405
+ const value = options.get(presentOptionNames[0])
406
+ options.delete(presentOptionNames[0])
407
+ return { name: presentOptionNamesWithDashDash[0], value }
408
+ }
409
+ function complainAboutUnrecognisedOptions() {
410
+ if (options.size) {
411
+ logError(
412
+ `Unrecognised option${options.size > 1 ? "s" : ""}: ${[...options.keys()].map(formatOption).join(", ")}`
413
+ )
414
+ process.exit(1)
415
+ }
416
+ }