hackmud-script-manager 0.20.4-4605f71 → 0.20.4-484afbe

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-4605f71",
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-4605f71",
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,162 +55,222 @@ 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-484afbe")
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 being 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, 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()
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")
134
116
  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`)
160
- logHelp()
161
- break
117
+ process.exit(1)
162
118
  }
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) {
119
+ const fileExtension = extname(target)
120
+ if (!supportedExtensions.includes(fileExtension)) {
176
121
  logError(
177
- `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('", "')}"`
178
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"
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, line } of warnings)
159
+ log(`Warning "${chalk.bold(message)}" on line ${chalk.bold(line + "")}`)
160
+ await writeFilePersistent(outputPath, script)
161
+ .catch(error => {
162
+ if (!commands[2] || "EISDIR" != error.code) throw error
163
+ outputPath = resolve(outputPath, basename(target, fileExtension) + ".js")
164
+ return writeFilePersistent(outputPath, script)
165
+ })
166
+ .then(() =>
167
+ log(
168
+ `Wrote ${chalk.bold(countHackmudCharacters(script))} chars to ${chalk.bold(relative(".", outputPath))} | took ${Math.round(100 * timeTook) / 100}ms`
169
+ )
170
+ )
171
+ })
172
+ if (watchOption) {
173
+ const { watch: watchFile } = await chokidarModule
174
+ watchFile(target, { awaitWriteFinish: { stabilityThreshold: 100 } })
175
+ .on("ready", () => log("Watching " + target))
176
+ .on("change", golfFile)
177
+ autoExit = !1
178
+ } else await golfFile()
179
+ } else {
180
+ const hackmudPath = getHackmudPath(),
181
+ sourcePath = commands[1]
182
+ if (!sourcePath) {
183
+ logError(`Must provide the directory to ${"push" == commands[0] ? "push from" : "watch"}\n`)
179
184
  logHelp()
180
- break
185
+ process.exit(1)
186
+ }
187
+ const scripts = commands.slice(2)
188
+ if (scripts.length) {
189
+ const invalidScript = scripts.find(
190
+ script => !/^(?:[a-z_][a-z\d_]{0,24}|\*)\.(?:[a-z_][a-z\d_]{0,24}|\*)$/.test(script)
191
+ )
192
+ if (invalidScript) {
193
+ logError(`Invalid script name: ${JSON.stringify(invalidScript)}\n`)
194
+ logHelp()
195
+ process.exit(1)
196
+ }
197
+ } else scripts.push("*.*")
198
+ const watchOption = popOption("watch")
199
+ if ("push" != commands[0] || watchOption?.value) {
200
+ const dtsPathOption = popOption(
201
+ "dts-path",
202
+ "type-declaration-path",
203
+ "type-declaration",
204
+ "dts",
205
+ "gen-types"
206
+ )
207
+ dtsPathOption &&
208
+ "dts-path" != dtsPathOption.name &&
209
+ "type-declaration-path" != dtsPathOption.name &&
210
+ console.warn(
211
+ colourF(
212
+ `Warning: ${formatOption(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`
213
+ )
214
+ )
215
+ complainAboutUnrecognisedOptions()
216
+ const { watch } = await watchModule
217
+ watch(sourcePath, hackmudPath, {
218
+ scripts,
219
+ onPush: info => logInfo(info, hackmudPath),
220
+ typeDeclarationPath: dtsPathOption?.value.toString(),
221
+ minify: noMinifyOption && !noMinifyOption.value,
222
+ mangleNames: mangleNamesOption?.value,
223
+ onReady: () => log("Watching"),
224
+ forceQuineCheats: forceQuineCheatsOption?.value ?? !noQuineCheatsOptions?.value
225
+ })
226
+ autoExit = !1
227
+ } else {
228
+ const dtsPathOption = popOption("dts-path")
229
+ complainAboutUnrecognisedOptions()
230
+ let declarationPathPromise
231
+ if (dtsPathOption) {
232
+ if ("string" != typeof dtsPathOption.value) {
233
+ logError(
234
+ `Option ${formatOption(dtsPathOption.name)} must be a string, got ${colourV(dtsPathOption.value)}\n`
235
+ )
236
+ logHelp()
237
+ process.exit(1)
238
+ }
239
+ let typeDeclarationPath = resolve(dtsPathOption.value)
240
+ const typeDeclaration = await generateTypeDeclaration(sourcePath, hackmudPath)
241
+ declarationPathPromise = writeFile(typeDeclarationPath, typeDeclaration)
242
+ .catch(error => {
243
+ assert(error instanceof Error, "src/bin/hsm.ts:288:38")
244
+ if ("EISDIR" != error.code) throw error
245
+ typeDeclarationPath = resolve(typeDeclarationPath, "player.d.ts")
246
+ return writeFile(typeDeclarationPath, typeDeclaration)
247
+ })
248
+ .then(() => typeDeclarationPath)
249
+ }
250
+ const { push, MissingSourceFolderError, MissingHackmudFolderError, NoUsersError } =
251
+ await pushModule,
252
+ infos = await push(sourcePath, hackmudPath, {
253
+ scripts,
254
+ onPush: info => logInfo(info, hackmudPath),
255
+ minify: noMinifyOption && !noMinifyOption.value,
256
+ mangleNames: mangleNamesOption?.value,
257
+ forceQuineCheats: forceQuineCheatsOption?.value ?? !noQuineCheatsOptions?.value
258
+ })
259
+ if (infos instanceof Error) {
260
+ logError(infos.message)
261
+ if (infos instanceof MissingSourceFolderError || infos instanceof NoUsersError) {
262
+ console.log()
263
+ logHelp()
264
+ } else
265
+ infos instanceof MissingHackmudFolderError &&
266
+ log(
267
+ `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`
268
+ )
269
+ } else infos.length || logError("Could not find any scripts to push")
270
+ declarationPathPromise &&
271
+ log("Wrote type declaration to " + chalk.bold(await declarationPathPromise))
181
272
  }
182
- shouldMinify = !shouldSkipMinify
183
- }
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
273
  }
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
274
  }
217
275
  break
218
276
  case "pull":
@@ -222,8 +280,9 @@ switch (commands[0]) {
222
280
  if (!script) {
223
281
  logError("Must provide the script to pull\n")
224
282
  logHelp()
225
- break
283
+ process.exit(1)
226
284
  }
285
+ complainAboutUnrecognisedOptions()
227
286
  const sourcePath = commands[2] || "."
228
287
  await pull(sourcePath, hackmudPath, script).catch(error => {
229
288
  console.error(error)
@@ -233,8 +292,9 @@ switch (commands[0]) {
233
292
  break
234
293
  case "sync-macros":
235
294
  {
236
- const hackmudPath = getHackmudPath(),
237
- { macrosSynced, usersSynced } = await syncMacros(hackmudPath)
295
+ const hackmudPath = getHackmudPath()
296
+ complainAboutUnrecognisedOptions()
297
+ const { macrosSynced, usersSynced } = await syncMacros(hackmudPath)
238
298
  log(`Synced ${macrosSynced} macros to ${usersSynced} users`)
239
299
  }
240
300
  break
@@ -242,19 +302,30 @@ switch (commands[0]) {
242
302
  case "gen-type-declaration":
243
303
  case "gen-dts":
244
304
  case "gen-types":
305
+ case "emit-dts":
245
306
  {
246
- const target = commands[1]
307
+ if ("emit-dts" != commands[0] && "gen-dts" != commands[0]) {
308
+ warnedDeprecatedEmitDtsAlias = !0
309
+ console.warn(
310
+ colourF(
311
+ `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`
312
+ )
313
+ )
314
+ }
315
+ const hackmudPath = getHackmudPath(),
316
+ target = commands[1]
247
317
  if (!target) {
248
318
  logError("Must provide target directory\n")
249
319
  logHelp()
250
- break
320
+ process.exit(1)
251
321
  }
322
+ complainAboutUnrecognisedOptions()
252
323
  const sourcePath = resolve(target),
253
324
  outputPath = commands[2] || "./player.d.ts",
254
- typeDeclaration = await generateTypeDeclaration(sourcePath, getHackmudPath())
325
+ typeDeclaration = await generateTypeDeclaration(sourcePath, hackmudPath)
255
326
  let typeDeclarationPath = resolve(outputPath)
256
327
  await writeFile(typeDeclarationPath, typeDeclaration).catch(error => {
257
- assert(error instanceof Error, "src/bin/hsm.ts:340:35")
328
+ assert(error instanceof Error, "src/bin/hsm.ts:422:35")
258
329
  if ("EISDIR" != error.code) throw error
259
330
  typeDeclarationPath = resolve(typeDeclarationPath, "player.d.ts")
260
331
  return writeFile(typeDeclarationPath, typeDeclaration)
@@ -263,102 +334,8 @@ switch (commands[0]) {
263
334
  }
264
335
  break
265
336
  case "help":
266
- case "h":
267
337
  logHelp()
268
338
  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
339
  default:
363
340
  commands[0] && logError(`Unknown command: ${colourL(commands[0])}\n`)
364
341
  logHelp()
@@ -366,23 +343,21 @@ switch (commands[0]) {
366
343
  autoExit && process.exit()
367
344
  function logHelp() {
368
345
  const pushCommandDescription = "Push scripts from a directory to hackmud user's scripts directories",
369
- forceQuineCheatsOptionDescription = `Force quine cheats on. Use ${colourN("--force-quine-cheats")}=${colourV("false")} to force off`,
370
346
  hackmudPathOption = `${colourN("--hackmud-path")}=${colourB("<path>")}\n Override hackmud path`
371
- console.log(colourN("Version") + colourS(": ") + colourV(version))
372
347
  switch (commands[0]) {
373
348
  case "dev":
374
349
  case "watch":
375
350
  case "push":
376
351
  console.log(
377
352
  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`
353
+ `${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`
379
354
  )
380
355
  )
381
356
  break
382
357
  case "pull":
383
358
  console.log(
384
359
  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}`
360
+ `${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
361
  )
387
362
  )
388
363
  break
@@ -390,7 +365,7 @@ function logHelp() {
390
365
  case "golf":
391
366
  console.log(
392
367
  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`
368
+ `${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`
394
369
  )
395
370
  )
396
371
  break
@@ -398,6 +373,15 @@ function logHelp() {
398
373
  case "gen-type-declaration":
399
374
  case "gen-dts":
400
375
  case "gen-types":
376
+ case "emit-dts":
377
+ warnedDeprecatedEmitDtsAlias ||
378
+ "emit-dts" == commands[0] ||
379
+ "gen-dts" == commands[0] ||
380
+ console.warn(
381
+ colourF(
382
+ `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`
383
+ )
384
+ )
401
385
  console.log(
402
386
  colourS(
403
387
  `${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 +391,14 @@ function logHelp() {
407
391
  case "sync-macros":
408
392
  console.log(
409
393
  colourS(
410
- `\n${colourJ("Sync macros across all hackmud users")}\n\n${colourA("Options:")}\n${hackmudPathOption}`
394
+ `${colourJ("Sync macros across all hackmud users")}\n\n${colourA("Options:")}\n${hackmudPathOption}`
411
395
  )
412
396
  )
413
397
  break
414
398
  default:
415
399
  console.log(
416
400
  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`
401
+ `${colourJ("Hackmud Script Manager")}\n${colourN("Version") + colourS(": ") + colourV("0.20.4-484afbe")}\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`
418
402
  )
419
403
  )
420
404
  }
@@ -432,15 +416,55 @@ function logError(message) {
432
416
  process.exitCode = 1
433
417
  }
434
418
  function getHackmudPath() {
435
- const hackmudPathOption = options.get("hackmud-path")
436
- if (null != hackmudPathOption && "string" != typeof hackmudPathOption) {
437
- logError(`Option ${colourN("--hackmud-path")} must be a string, got ${colourV(hackmudPathOption)}\n`)
419
+ const hackmudPathOption = popOption("hackmud-path")
420
+ if (hackmudPathOption) {
421
+ if ("string" != typeof hackmudPathOption.value) {
422
+ logError(`Option ${colourN("--hackmud-path")} must be a string, got ${colourV(hackmudPathOption.value)}\n`)
423
+ logHelp()
424
+ process.exit(1)
425
+ }
426
+ if (!hackmudPathOption.value) {
427
+ logError(`Option ${colourN("--hackmud-path")} was specified but empty\n`)
428
+ logHelp()
429
+ process.exit(1)
430
+ }
431
+ return hackmudPathOption.value
432
+ }
433
+ if (null != process.env.HSM_HACKMUD_PATH) {
434
+ if (!process.env.HSM_HACKMUD_PATH) {
435
+ logError(`Environment variable ${colourN("HSM_HACKMUD_PATH")} was specified but empty\n`)
436
+ logHelp()
437
+ process.exit(1)
438
+ }
439
+ return process.env.HSM_HACKMUD_PATH
440
+ }
441
+ return "win32" == process.platform ? resolve(process.env.APPDATA, "hackmud") : resolve(homedir(), ".config/hackmud")
442
+ }
443
+ function assertOptionIsBoolean(option) {
444
+ if ("boolean" != typeof option.value) {
445
+ logError(`The value for ${formatOption(option.name)} must be ${colourV("true")} or ${colourV("false")}\n`)
438
446
  logHelp()
439
447
  process.exit(1)
440
448
  }
441
- return (
442
- hackmudPathOption ||
443
- process.env.HSM_HACKMUD_PATH ||
444
- ("win32" == process.platform ? resolve(process.env.APPDATA, "hackmud") : resolve(homedir(), ".config/hackmud"))
445
- )
449
+ }
450
+ function popOption(...names) {
451
+ const presentOptionNames = names.filter(name => options.has(name))
452
+ if (!presentOptionNames.length) return
453
+ if (presentOptionNames.length > 1) {
454
+ logError(
455
+ `The options ${presentOptionNames.map(formatOption).join(", ")} are aliases for each other. Please only specify one`
456
+ )
457
+ process.exit(1)
458
+ }
459
+ const value = options.get(presentOptionNames[0])
460
+ options.delete(presentOptionNames[0])
461
+ return { name: presentOptionNames[0], value }
462
+ }
463
+ function complainAboutUnrecognisedOptions() {
464
+ if (options.size) {
465
+ logError(
466
+ `Unrecognised option${options.size > 1 ? "s" : ""}: ${[...options.keys()].map(formatOption).join(", ")}`
467
+ )
468
+ process.exit(1)
469
+ }
446
470
  }