hackmud-script-manager 0.20.4-abe4703 → 0.20.4-ae3052c

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/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-abe4703",
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)
@@ -23,23 +24,19 @@ const version = "0.20.4-abe4703",
23
24
  log = message => console.log(colourS(message))
24
25
  for (const argument of process.argv.slice(2))
25
26
  if ("-" == argument[0]) {
26
- const [key, valueRaw] = argument.split("=")
27
- let value = valueRaw
28
- if (value)
29
- if ("true" == value) value = !0
30
- else if ("false" == value) value = !1
31
- else {
32
- const number = Number(value)
33
- isFinite(number) && (value = number)
34
- }
35
- else value = !0
27
+ const argumentEqualsIndex = argument.indexOf("=")
28
+ let key, value
29
+ if (-1 == argumentEqualsIndex) {
30
+ key = argument
31
+ value = !0
32
+ } else {
33
+ key = argument.slice(0, argumentEqualsIndex)
34
+ value = argument.slice(argumentEqualsIndex + 1)
35
+ "true" == value ? (value = !0) : "false" == value && (value = !1)
36
+ }
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,153 +55,221 @@ 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-ae3052c")
66
+ process.exit()
67
+ }
68
+ let warnedDeprecatedEmitDtsAlias = !1
69
+ if (popOption("help", "h")?.value) {
61
70
  logHelp()
62
71
  process.exit()
63
72
  }
64
73
  let autoExit = !0
65
74
  switch (commands[0]) {
66
75
  case "push":
76
+ case "dev":
77
+ case "watch":
78
+ case "golf":
79
+ case "minify":
67
80
  {
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")) {
88
- logError(
89
- `Options ${colourN("--mangle-names")} and ${colourN(optionsHasNoMinify ? "--no-minify" : "--skip-minify")} are incompatible\n`
90
- )
91
- logHelp()
92
- break
93
- }
94
- const shouldSkipMinify = options.get("no-minify") || options.get("skip-minify")
95
- let shouldMinify
96
- if (null != shouldSkipMinify) {
97
- if ("boolean" != typeof shouldSkipMinify) {
98
- logError(
99
- `The value for ${colourN(optionsHasNoMinify ? "--no-minify" : "--skip-minify")} must be ${colourV("true")} or ${colourV("false")}\n`
81
+ const noMinifyOption = popOption("no-minify", "skip-minify")
82
+ noMinifyOption &&
83
+ "no-minify" != noMinifyOption.name &&
84
+ console.warn(
85
+ colourF(
86
+ `Warning: ${formatOption(noMinifyOption.name)} is deprecated and will be removed in the next minor\n release of HSM\n You should switch to using its alias ${colourN("--no-minify")}\n`
100
87
  )
101
- logHelp()
102
- break
103
- }
104
- shouldMinify = !shouldSkipMinify
105
- }
106
- const shouldMangleNames = options.get("mangle-names")
107
- if (null != shouldMangleNames && "boolean" != typeof shouldMangleNames) {
88
+ )
89
+ const mangleNamesOption = popOption("mangle-names"),
90
+ forceQuineCheatsOption = popOption("force-quine-cheats"),
91
+ noQuineCheatsOptions = popOption("no-quine-cheats"),
92
+ noMinifyIncompatibleOption = mangleNamesOption || forceQuineCheatsOption || noQuineCheatsOptions
93
+ if (noMinifyOption && noMinifyIncompatibleOption) {
108
94
  logError(
109
- `The value for ${colourN("--mangle-names")} must be ${colourV("true")} or ${colourV("false")}\n`
95
+ `Options ${formatOption(noMinifyOption.name)} and ${formatOption(noMinifyIncompatibleOption.name)} are incompatible\n`
110
96
  )
111
97
  logHelp()
112
- break
98
+ process.exit(1)
113
99
  }
114
- const shouldforceQuineCheats = options.get("force-quine-cheats")
115
- if (null != shouldforceQuineCheats && "boolean" != typeof shouldforceQuineCheats) {
100
+ if (forceQuineCheatsOption && noQuineCheatsOptions) {
116
101
  logError(
117
- `The value for ${colourN("--force-quine-cheats")} must be ${colourV("true")} or ${colourV("false")}\n`
102
+ `Options ${formatOption(forceQuineCheatsOption.name)} and ${formatOption(noQuineCheatsOptions.name)} are incompatible\n`
118
103
  )
119
104
  logHelp()
120
- break
105
+ process.exit(1)
121
106
  }
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`)
107
+ noMinifyOption && assertOptionIsBoolean(noMinifyOption)
108
+ mangleNamesOption && assertOptionIsBoolean(mangleNamesOption)
109
+ forceQuineCheatsOption && assertOptionIsBoolean(forceQuineCheatsOption)
110
+ noQuineCheatsOptions && assertOptionIsBoolean(noQuineCheatsOptions)
111
+ if ("golf" == commands[0] || "minify" == commands[0]) {
112
+ const watchOption = popOption("watch"),
113
+ target = commands[1]
114
+ if (!target) {
115
+ logError("Must provide target\n")
151
116
  logHelp()
152
- break
117
+ process.exit(1)
153
118
  }
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) {
119
+ const fileExtension = extname(target)
120
+ if (!supportedExtensions.includes(fileExtension)) {
167
121
  logError(
168
- `The value for ${colourN(optionsHasNoMinify ? "--no-minify" : "--skip-minify")} must be ${colourV("true")} or ${colourV("false")}\n`
122
+ `Unsupported file extension "${chalk.bold(fileExtension)}"\nSupported extensions are "${supportedExtensions.map(extension => chalk.bold(extension)).join('", "')}"`
123
+ )
124
+ process.exit(1)
125
+ }
126
+ complainAboutUnrecognisedOptions()
127
+ const { processScript } = await processScriptModule,
128
+ fileBaseName = basename(target, fileExtension),
129
+ fileBaseNameEndsWithDotSrc = fileBaseName.endsWith(".src"),
130
+ scriptName = fileBaseNameEndsWithDotSrc ? fileBaseName.slice(0, -4) : fileBaseName,
131
+ scriptUser =
132
+ (
133
+ "scripts" == basename(resolve(target, "..")) &&
134
+ "hackmud" == basename(resolve(target, "../../.."))
135
+ ) ?
136
+ basename(resolve(target, "../.."))
137
+ : void 0
138
+ let outputPath =
139
+ commands[2] ||
140
+ resolve(
141
+ dirname(target),
142
+ fileBaseNameEndsWithDotSrc ? scriptName + ".js"
143
+ : ".js" == fileExtension ? fileBaseName + ".min.js"
144
+ : fileBaseName + ".js"
169
145
  )
146
+ const golfFile = () =>
147
+ readFile(target, { encoding: "utf8" }).then(async source => {
148
+ const timeStart = performance.now(),
149
+ { script, warnings } = await processScript(source, {
150
+ minify: noMinifyOption && !noMinifyOption.value,
151
+ scriptUser,
152
+ scriptName,
153
+ filePath: target,
154
+ mangleNames: mangleNamesOption?.value,
155
+ forceQuineCheats: forceQuineCheatsOption?.value ?? !noQuineCheatsOptions?.value
156
+ }),
157
+ timeTook = performance.now() - timeStart
158
+ for (const { message } of warnings) log("Warning: " + chalk.bold(message))
159
+ await writeFilePersistent(outputPath, script)
160
+ .catch(error => {
161
+ if (!commands[2] || "EISDIR" != error.code) throw error
162
+ outputPath = resolve(outputPath, basename(target, fileExtension) + ".js")
163
+ return writeFilePersistent(outputPath, script)
164
+ })
165
+ .then(() =>
166
+ log(
167
+ `Wrote ${chalk.bold(countHackmudCharacters(script))} chars to ${chalk.bold(relative(".", outputPath))} | took ${Math.round(100 * timeTook) / 100}ms`
168
+ )
169
+ )
170
+ })
171
+ if (watchOption) {
172
+ const { watch: watchFile } = await chokidarModule
173
+ watchFile(target, { awaitWriteFinish: { stabilityThreshold: 100 } })
174
+ .on("ready", () => log("Watching " + target))
175
+ .on("change", golfFile)
176
+ autoExit = !1
177
+ } else await golfFile()
178
+ } else {
179
+ const hackmudPath = getHackmudPath(),
180
+ sourcePath = commands[1]
181
+ if (!sourcePath) {
182
+ logError(`Must provide the directory to ${"push" == commands[0] ? "push from" : "watch"}\n`)
170
183
  logHelp()
171
- break
184
+ process.exit(1)
185
+ }
186
+ const scripts = commands.slice(2)
187
+ if (scripts.length) {
188
+ const invalidScript = scripts.find(
189
+ script => !/^(?:[a-z_][a-z\d_]{0,24}|\*)\.(?:[a-z_][a-z\d_]{0,24}|\*)$/.test(script)
190
+ )
191
+ if (invalidScript) {
192
+ logError(`Invalid script name: ${JSON.stringify(invalidScript)}\n`)
193
+ logHelp()
194
+ process.exit(1)
195
+ }
196
+ } else scripts.push("*.*")
197
+ const watchOption = popOption("watch")
198
+ if ("push" != commands[0] || watchOption?.value) {
199
+ const dtsPathOption = popOption(
200
+ "dts-path",
201
+ "type-declaration-path",
202
+ "type-declaration",
203
+ "dts",
204
+ "gen-types"
205
+ )
206
+ dtsPathOption &&
207
+ "dts-path" != dtsPathOption.name &&
208
+ "type-declaration-path" != dtsPathOption.name &&
209
+ console.warn(
210
+ colourF(
211
+ `Warning: ${formatOption(dtsPathOption.name)} is 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`
212
+ )
213
+ )
214
+ complainAboutUnrecognisedOptions()
215
+ const { watch } = await watchModule
216
+ watch(sourcePath, hackmudPath, {
217
+ scripts,
218
+ onPush: info => logInfo(info, hackmudPath),
219
+ typeDeclarationPath: dtsPathOption?.value.toString(),
220
+ minify: noMinifyOption && !noMinifyOption.value,
221
+ mangleNames: mangleNamesOption?.value,
222
+ onReady: () => log("Watching"),
223
+ forceQuineCheats: forceQuineCheatsOption?.value ?? !noQuineCheatsOptions?.value
224
+ })
225
+ autoExit = !1
226
+ } else {
227
+ const dtsPathOption = popOption("dts-path")
228
+ complainAboutUnrecognisedOptions()
229
+ let declarationPathPromise
230
+ if (dtsPathOption) {
231
+ if ("string" != typeof dtsPathOption.value) {
232
+ logError(
233
+ `Option ${formatOption(dtsPathOption.name)} must be a string, got ${colourV(dtsPathOption.value)}\n`
234
+ )
235
+ logHelp()
236
+ process.exit(1)
237
+ }
238
+ let typeDeclarationPath = resolve(dtsPathOption.value)
239
+ const typeDeclaration = await generateTypeDeclaration(sourcePath, hackmudPath)
240
+ declarationPathPromise = writeFile(typeDeclarationPath, typeDeclaration)
241
+ .catch(error => {
242
+ assert(error instanceof Error, "src/bin/hsm.ts:288:38")
243
+ if ("EISDIR" != error.code) throw error
244
+ typeDeclarationPath = resolve(typeDeclarationPath, "player.d.ts")
245
+ return writeFile(typeDeclarationPath, typeDeclaration)
246
+ })
247
+ .then(() => typeDeclarationPath)
248
+ }
249
+ const { push, MissingSourceFolderError, MissingHackmudFolderError, NoUsersError } =
250
+ await pushModule,
251
+ infos = await push(sourcePath, hackmudPath, {
252
+ scripts,
253
+ onPush: info => logInfo(info, hackmudPath),
254
+ minify: noMinifyOption && !noMinifyOption.value,
255
+ mangleNames: mangleNamesOption?.value,
256
+ forceQuineCheats: forceQuineCheatsOption?.value ?? !noQuineCheatsOptions?.value
257
+ })
258
+ if (infos instanceof Error) {
259
+ logError(infos.message)
260
+ if (infos instanceof MissingSourceFolderError || infos instanceof NoUsersError) {
261
+ console.log()
262
+ logHelp()
263
+ } else
264
+ infos instanceof MissingHackmudFolderError &&
265
+ log(
266
+ `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`
267
+ )
268
+ } else infos.length || logError("Could not find any scripts to push")
269
+ declarationPathPromise &&
270
+ log("Wrote type declaration to " + chalk.bold(await declarationPathPromise))
172
271
  }
173
- shouldMinify = !shouldSkipMinify
174
- }
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
272
  }
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
273
  }
208
274
  break
209
275
  case "pull":
@@ -213,8 +279,9 @@ switch (commands[0]) {
213
279
  if (!script) {
214
280
  logError("Must provide the script to pull\n")
215
281
  logHelp()
216
- break
282
+ process.exit(1)
217
283
  }
284
+ complainAboutUnrecognisedOptions()
218
285
  const sourcePath = commands[2] || "."
219
286
  await pull(sourcePath, hackmudPath, script).catch(error => {
220
287
  console.error(error)
@@ -224,8 +291,9 @@ switch (commands[0]) {
224
291
  break
225
292
  case "sync-macros":
226
293
  {
227
- const hackmudPath = getHackmudPath(),
228
- { macrosSynced, usersSynced } = await syncMacros(hackmudPath)
294
+ const hackmudPath = getHackmudPath()
295
+ complainAboutUnrecognisedOptions()
296
+ const { macrosSynced, usersSynced } = await syncMacros(hackmudPath)
229
297
  log(`Synced ${macrosSynced} macros to ${usersSynced} users`)
230
298
  }
231
299
  break
@@ -233,19 +301,30 @@ switch (commands[0]) {
233
301
  case "gen-type-declaration":
234
302
  case "gen-dts":
235
303
  case "gen-types":
304
+ case "emit-dts":
236
305
  {
237
- const target = commands[1]
306
+ if ("emit-dts" != commands[0] && "gen-dts" != commands[0]) {
307
+ warnedDeprecatedEmitDtsAlias = !0
308
+ console.warn(
309
+ colourF(
310
+ `Warning: ${colourC("hsm")} ${colourL(commands[0])} is 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`
311
+ )
312
+ )
313
+ }
314
+ const hackmudPath = getHackmudPath(),
315
+ target = commands[1]
238
316
  if (!target) {
239
317
  logError("Must provide target directory\n")
240
318
  logHelp()
241
- break
319
+ process.exit(1)
242
320
  }
321
+ complainAboutUnrecognisedOptions()
243
322
  const sourcePath = resolve(target),
244
323
  outputPath = commands[2] || "./player.d.ts",
245
- typeDeclaration = await generateTypeDeclaration(sourcePath, getHackmudPath())
324
+ typeDeclaration = await generateTypeDeclaration(sourcePath, hackmudPath)
246
325
  let typeDeclarationPath = resolve(outputPath)
247
326
  await writeFile(typeDeclarationPath, typeDeclaration).catch(error => {
248
- assert(error instanceof Error, "src/bin/hsm.ts:327:35")
327
+ assert(error instanceof Error, "src/bin/hsm.ts:422:35")
249
328
  if ("EISDIR" != error.code) throw error
250
329
  typeDeclarationPath = resolve(typeDeclarationPath, "player.d.ts")
251
330
  return writeFile(typeDeclarationPath, typeDeclaration)
@@ -254,102 +333,8 @@ switch (commands[0]) {
254
333
  }
255
334
  break
256
335
  case "help":
257
- case "h":
258
336
  logHelp()
259
337
  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
338
  default:
354
339
  commands[0] && logError(`Unknown command: ${colourL(commands[0])}\n`)
355
340
  logHelp()
@@ -357,23 +342,21 @@ switch (commands[0]) {
357
342
  autoExit && process.exit()
358
343
  function logHelp() {
359
344
  const pushCommandDescription = "Push scripts from a directory to hackmud user's scripts directories",
360
- forceQuineCheatsOptionDescription = `Force quine cheats on. Use ${colourN("--force-quine-cheats")}=${colourV("false")} to force off`,
361
345
  hackmudPathOption = `${colourN("--hackmud-path")}=${colourB("<path>")}\n Override hackmud path`
362
- console.log(colourN("Version") + colourS(": ") + colourV(version))
363
346
  switch (commands[0]) {
364
347
  case "dev":
365
348
  case "watch":
366
349
  case "push":
367
350
  console.log(
368
351
  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 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`
352
+ `${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")}, ${colourN("--no-quine-cheats")}\n Force quine cheats on or off\n${hackmudPathOption}\n${colourN("--dts-path")}=${colourB("<path>")}\n Path to generate a type declaration (.d.ts) file for the scripts\n${colourN("--watch")}\n Watch for changes\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
353
  )
371
354
  )
372
355
  break
373
356
  case "pull":
374
357
  console.log(
375
358
  colourS(
376
- `\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}`
359
+ `${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}`
377
360
  )
378
361
  )
379
362
  break
@@ -381,7 +364,7 @@ function logHelp() {
381
364
  case "golf":
382
365
  console.log(
383
366
  colourS(
384
- `\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`
367
+ `${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")}, ${colourN("--no-quine-cheats")}\n Force quine cheats on or off\n${colourN("--watch")}\n Watch for changes`
385
368
  )
386
369
  )
387
370
  break
@@ -389,6 +372,15 @@ function logHelp() {
389
372
  case "gen-type-declaration":
390
373
  case "gen-dts":
391
374
  case "gen-types":
375
+ case "emit-dts":
376
+ warnedDeprecatedEmitDtsAlias ||
377
+ "emit-dts" == commands[0] ||
378
+ "gen-dts" == commands[0] ||
379
+ console.warn(
380
+ colourF(
381
+ `Warning: ${colourC("hsm")} ${colourL(commands[0])} is 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`
382
+ )
383
+ )
392
384
  console.log(
393
385
  colourS(
394
386
  `${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}`
@@ -398,40 +390,82 @@ function logHelp() {
398
390
  case "sync-macros":
399
391
  console.log(
400
392
  colourS(
401
- `\n${colourJ("Sync macros across all hackmud users")}\n\n${colourA("Options:")}\n${hackmudPathOption}`
393
+ `${colourJ("Sync macros across all hackmud users")}\n\n${colourA("Options:")}\n${hackmudPathOption}`
402
394
  )
403
395
  )
404
396
  break
405
397
  default:
406
398
  console.log(
407
399
  colourS(
408
- `\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`
400
+ `${colourJ("Hackmud Script Manager")}\n${colourN("Version") + colourS(": ") + colourV("0.20.4-ae3052c")}\n\n${colourA("Commands:")}\n${colourL("push")}\n ${pushCommandDescription}\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`
409
401
  )
410
402
  )
411
403
  }
412
404
  }
413
- function logInfo({ path, users, characterCount, error }, hackmudPath) {
405
+ function logInfo({ path, users, characterCount, error, warnings }, hackmudPath) {
414
406
  path = relative(".", path)
415
- error ?
416
- logError(`Error "${chalk.bold(error.message)}" in ${chalk.bold(path)}`)
417
- : log(
407
+ if (error) logError(`Error "${chalk.bold(error.message)}" in ${chalk.bold(path)}`)
408
+ else {
409
+ for (const warning of warnings) console.warn(colourF("Warning: " + warning.message))
410
+ log(
418
411
  `Pushed ${chalk.bold(path)} to ${users.map(user => chalk.bold(userColours.get(user))).join(", ")} | ${chalk.bold(characterCount + "")} chars | ${chalk.bold(resolve(hackmudPath, users[0], "scripts", basename(path, extname(path))) + ".js")}`
419
412
  )
413
+ }
420
414
  }
421
415
  function logError(message) {
422
416
  console.error(colourD(message))
423
417
  process.exitCode = 1
424
418
  }
425
419
  function getHackmudPath() {
426
- const hackmudPathOption = options.get("hackmud-path")
427
- if (null != hackmudPathOption && "string" != typeof hackmudPathOption) {
428
- logError(`Option ${colourN("--hackmud-path")} must be a string, got ${colourV(hackmudPathOption)}\n`)
420
+ const hackmudPathOption = popOption("hackmud-path")
421
+ if (hackmudPathOption) {
422
+ if ("string" != typeof hackmudPathOption.value) {
423
+ logError(`Option ${colourN("--hackmud-path")} must be a string, got ${colourV(hackmudPathOption.value)}\n`)
424
+ logHelp()
425
+ process.exit(1)
426
+ }
427
+ if (!hackmudPathOption.value) {
428
+ logError(`Option ${colourN("--hackmud-path")} was specified but empty\n`)
429
+ logHelp()
430
+ process.exit(1)
431
+ }
432
+ return hackmudPathOption.value
433
+ }
434
+ if (null != process.env.HSM_HACKMUD_PATH) {
435
+ if (!process.env.HSM_HACKMUD_PATH) {
436
+ logError(`Environment variable ${colourN("HSM_HACKMUD_PATH")} was specified but empty\n`)
437
+ logHelp()
438
+ process.exit(1)
439
+ }
440
+ return process.env.HSM_HACKMUD_PATH
441
+ }
442
+ return "win32" == process.platform ? resolve(process.env.APPDATA, "hackmud") : resolve(homedir(), ".config/hackmud")
443
+ }
444
+ function assertOptionIsBoolean(option) {
445
+ if ("boolean" != typeof option.value) {
446
+ logError(`The value for ${formatOption(option.name)} must be ${colourV("true")} or ${colourV("false")}\n`)
429
447
  logHelp()
430
448
  process.exit(1)
431
449
  }
432
- return (
433
- hackmudPathOption ||
434
- process.env.HSM_HACKMUD_PATH ||
435
- ("win32" == process.platform ? resolve(process.env.APPDATA, "hackmud") : resolve(homedir(), ".config/hackmud"))
436
- )
450
+ }
451
+ function popOption(...names) {
452
+ const presentOptionNames = names.filter(name => options.has(name))
453
+ if (!presentOptionNames.length) return
454
+ if (presentOptionNames.length > 1) {
455
+ logError(
456
+ `The options ${presentOptionNames.map(formatOption).join(", ")} are aliases for each other. Please only specify one`
457
+ )
458
+ process.exit(1)
459
+ }
460
+ const value = options.get(presentOptionNames[0])
461
+ options.delete(presentOptionNames[0])
462
+ return { name: presentOptionNames[0], value }
463
+ }
464
+ function complainAboutUnrecognisedOptions() {
465
+ if (options.size) {
466
+ logError(
467
+ `Unrecognised option${options.size > 1 ? "s" : ""}: ${[...options.keys()].map(formatOption).join(", ")}`
468
+ )
469
+ process.exit(1)
470
+ }
437
471
  }