hackmud-script-manager 0.20.4-3354314 → 0.20.4-34f0749

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
@@ -5,14 +5,16 @@ 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-3354314",
16
+ const version = "0.20.4-34f0749",
17
+ formatOption = name => colourN(`-${1 == name.length ? "" : "-"}${name}`),
16
18
  options = new Map(),
17
19
  commands = [],
18
20
  userColours = new Cache(user => {
@@ -36,10 +38,6 @@ for (const argument of process.argv.slice(2))
36
38
  if ("-" == argument[1]) options.set(key.slice(2), value)
37
39
  else for (const option of key.slice(1)) options.set(option, value)
38
40
  } 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
41
  const pushModule = import("../push.js"),
44
42
  processScriptModule = import("../processScript/index.js"),
45
43
  watchModule = import("../watch.js"),
@@ -49,6 +47,7 @@ const pushModule = import("../push.js"),
49
47
  colourB = chalk.rgb(202, 202, 202),
50
48
  colourC = chalk.rgb(155, 155, 155),
51
49
  colourD = chalk.rgb(255, 0, 0),
50
+ colourF = chalk.rgb(255, 128, 0),
52
51
  colourJ = chalk.rgb(255, 244, 4),
53
52
  colourK = chalk.rgb(243, 249, 152),
54
53
  colourL = chalk.rgb(30, 255, 0),
@@ -57,153 +56,171 @@ const pushModule = import("../push.js"),
57
56
  colourS = chalk.rgb(122, 178, 244),
58
57
  colourV = chalk.rgb(255, 0, 236),
59
58
  colourW = chalk.rgb(255, 150, 224)
60
- if (options.get("help") || options.get("h")) {
59
+ process.version.startsWith("v21.") &&
60
+ console.warn(
61
+ colourF(
62
+ "Warning: Support for Node.js 21 will be dropped in the next minor version of HSM\n You should update your version of Node.js\n https://nodejs.org/en/download/package-manager"
63
+ )
64
+ )
65
+ if ("v" == commands[0] || "version" == commands[0] || popOption("version", "v")?.value) {
66
+ console.log(version)
67
+ process.exit()
68
+ }
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")) {
81
+ const noMinifyOption = popOption("no-minify", "skip-minify"),
82
+ mangleNamesOption = popOption("mangle-names"),
83
+ forceQuineCheatsOption = popOption("force-quine-cheats"),
84
+ noMinifyIncompatibleOption = mangleNamesOption || forceQuineCheatsOption
85
+ if (noMinifyOption && noMinifyIncompatibleOption) {
88
86
  logError(
89
- `Options ${colourN("--mangle-names")} and ${colourN(optionsHasNoMinify ? "--no-minify" : "--skip-minify")} are incompatible\n`
87
+ `Options ${formatOption(noMinifyOption.name)} and ${formatOption(noMinifyIncompatibleOption.name)} are incompatible\n`
90
88
  )
91
89
  logHelp()
92
- break
90
+ process.exit(1)
93
91
  }
94
- const shouldSkipMinify = options.get("no-minify") || options.get("skip-minify")
95
- let shouldMinify
96
- if (null != shouldSkipMinify) {
97
- if ("boolean" != typeof shouldSkipMinify) {
92
+ noMinifyOption && assertOptionIsBoolean(noMinifyOption)
93
+ mangleNamesOption && assertOptionIsBoolean(mangleNamesOption)
94
+ forceQuineCheatsOption && assertOptionIsBoolean(forceQuineCheatsOption)
95
+ if ("golf" == commands[0] || "minify" == commands[0]) {
96
+ const watchOption = popOption("watch"),
97
+ target = commands[1]
98
+ if (!target) {
99
+ logError("Must provide target\n")
100
+ logHelp()
101
+ process.exit(1)
102
+ }
103
+ const fileExtension = extname(target)
104
+ if (!supportedExtensions.includes(fileExtension)) {
98
105
  logError(
99
- `The value for ${colourN(optionsHasNoMinify ? "--no-minify" : "--skip-minify")} must be ${colourV("true")} or ${colourV("false")}\n`
106
+ `Unsupported file extension "${chalk.bold(fileExtension)}"\nSupported extensions are "${supportedExtensions.map(extension => chalk.bold(extension)).join('", "')}"`
100
107
  )
101
- logHelp()
102
- break
108
+ process.exit(1)
103
109
  }
104
- shouldMinify = !shouldSkipMinify
105
- }
106
- const shouldMangleNames = options.get("mangle-names")
107
- if (null != shouldMangleNames && "boolean" != typeof shouldMangleNames) {
108
- logError(
109
- `The value for ${colourN("--mangle-names")} must be ${colourV("true")} or ${colourV("false")}\n`
110
- )
111
- logHelp()
112
- break
113
- }
114
- const shouldforceQuineCheats = options.get("force-quine-cheats")
115
- if (null != shouldforceQuineCheats && "boolean" != typeof shouldforceQuineCheats) {
116
- logError(
117
- `The value for ${colourN("--force-quine-cheats")} must be ${colourV("true")} or ${colourV("false")}\n`
118
- )
119
- logHelp()
120
- break
121
- }
122
- const { push } = await pushModule
123
- ;(
124
- await push(sourcePath, hackmudPath, {
125
- scripts,
126
- onPush: info => logInfo(info, hackmudPath),
127
- minify: shouldMinify,
128
- mangleNames: shouldMangleNames,
129
- forceQuineCheats: shouldforceQuineCheats
130
- })
131
- ).length || logError("Could not find any scripts to push")
132
- }
133
- break
134
- case "dev":
135
- case "watch":
136
- {
137
- const hackmudPath = getHackmudPath(),
138
- sourcePath = commands[1]
139
- if (!sourcePath) {
140
- logError("Must provide the directory to watch\n")
141
- logHelp()
142
- break
143
- }
144
- const scripts = commands.slice(2)
145
- if (scripts.length) {
146
- const invalidScript = scripts.find(
147
- script => !/^(?:[a-z_][a-z\d_]{0,24}|\*)\.(?:[a-z_][a-z\d_]{0,24}|\*)$/.test(script)
148
- )
149
- if (invalidScript) {
150
- logError(`Invalid script name: ${JSON.stringify(invalidScript)}\n`)
110
+ complainAboutUnrecognisedOptions()
111
+ const { processScript } = await processScriptModule,
112
+ fileBaseName = basename(target, fileExtension),
113
+ fileBaseNameEndsWithDotSrc = fileBaseName.endsWith(".src"),
114
+ scriptName = fileBaseNameEndsWithDotSrc ? fileBaseName.slice(0, -4) : fileBaseName,
115
+ scriptUser =
116
+ (
117
+ "scripts" == basename(resolve(target, "..")) &&
118
+ "hackmud" == basename(resolve(target, "../../.."))
119
+ ) ?
120
+ basename(resolve(target, "../.."))
121
+ : void 0
122
+ let outputPath =
123
+ commands[2] ||
124
+ resolve(
125
+ dirname(target),
126
+ fileBaseNameEndsWithDotSrc ? scriptName + ".js"
127
+ : ".js" == fileExtension ? fileBaseName + ".min.js"
128
+ : fileBaseName + ".js"
129
+ )
130
+ const golfFile = () =>
131
+ readFile(target, { encoding: "utf8" }).then(async source => {
132
+ const timeStart = performance.now(),
133
+ { script, warnings } = await processScript(source, {
134
+ minify: noMinifyOption && !noMinifyOption.value,
135
+ scriptUser,
136
+ scriptName,
137
+ filePath: target,
138
+ mangleNames: mangleNamesOption?.value,
139
+ forceQuineCheats: forceQuineCheatsOption?.value
140
+ }),
141
+ timeTook = performance.now() - timeStart
142
+ for (const { message, line } of warnings)
143
+ log(`Warning "${chalk.bold(message)}" on line ${chalk.bold(line + "")}`)
144
+ await writeFilePersistent(outputPath, script)
145
+ .catch(error => {
146
+ if (!commands[2] || "EISDIR" != error.code) throw error
147
+ outputPath = resolve(outputPath, basename(target, fileExtension) + ".js")
148
+ return writeFilePersistent(outputPath, script)
149
+ })
150
+ .then(() =>
151
+ log(
152
+ `Wrote ${chalk.bold(countHackmudCharacters(script))} chars to ${chalk.bold(relative(".", outputPath))} | took ${Math.round(100 * timeTook) / 100}ms`
153
+ )
154
+ )
155
+ })
156
+ if (watchOption) {
157
+ const { watch: watchFile } = await chokidarModule
158
+ watchFile(target, { awaitWriteFinish: { stabilityThreshold: 100 } })
159
+ .on("ready", () => log("Watching " + target))
160
+ .on("change", golfFile)
161
+ autoExit = !1
162
+ } else await golfFile()
163
+ } else {
164
+ const hackmudPath = getHackmudPath(),
165
+ sourcePath = commands[1]
166
+ if (!sourcePath) {
167
+ logError(`Must provide the directory to ${"push" == commands[0] ? "push from" : "watch"}\n`)
151
168
  logHelp()
152
- break
169
+ process.exit(1)
153
170
  }
154
- } else scripts.push("*.*")
155
- const optionsHasNoMinify = options.has("no-minify")
156
- if ((optionsHasNoMinify || options.has("skip-minify")) && options.has("mangle-names")) {
157
- logError(
158
- `Options ${colourN("--mangle-names")} and ${colourN(optionsHasNoMinify ? "--no-minify" : "--skip-minify")} are incompatible\n`
159
- )
160
- logHelp()
161
- break
162
- }
163
- const shouldSkipMinify = options.get("no-minify") || options.get("skip-minify")
164
- let shouldMinify
165
- if (null != shouldSkipMinify) {
166
- if ("boolean" != typeof shouldSkipMinify) {
167
- logError(
168
- `The value for ${colourN(optionsHasNoMinify ? "--no-minify" : "--skip-minify")} must be ${colourV("true")} or ${colourV("false")}\n`
171
+ const scripts = commands.slice(2)
172
+ if (scripts.length) {
173
+ const invalidScript = scripts.find(
174
+ script => !/^(?:[a-z_][a-z\d_]{0,24}|\*)\.(?:[a-z_][a-z\d_]{0,24}|\*)$/.test(script)
169
175
  )
170
- logHelp()
171
- break
176
+ if (invalidScript) {
177
+ logError(`Invalid script name: ${JSON.stringify(invalidScript)}\n`)
178
+ logHelp()
179
+ process.exit(1)
180
+ }
181
+ } else scripts.push("*.*")
182
+ if ("push" == commands[0]) {
183
+ const { push, MissingSourceFolderError, MissingHackmudFolderError, NoUsersError } = await pushModule
184
+ complainAboutUnrecognisedOptions()
185
+ const infos = await push(sourcePath, hackmudPath, {
186
+ scripts,
187
+ onPush: info => logInfo(info, hackmudPath),
188
+ minify: noMinifyOption && !noMinifyOption.value,
189
+ mangleNames: mangleNamesOption?.value,
190
+ forceQuineCheats: forceQuineCheatsOption?.value
191
+ })
192
+ if (infos instanceof Error) {
193
+ logError(infos.message)
194
+ if (infos instanceof MissingSourceFolderError || infos instanceof NoUsersError) {
195
+ console.log()
196
+ logHelp()
197
+ } else
198
+ infos instanceof MissingHackmudFolderError &&
199
+ log(
200
+ `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`
201
+ )
202
+ } else infos.length || logError("Could not find any scripts to push")
203
+ } else {
204
+ const typeDeclarationPathOption = popOption(
205
+ "type-declaration-path",
206
+ "type-declaration",
207
+ "dts",
208
+ "gen-types"
209
+ )
210
+ complainAboutUnrecognisedOptions()
211
+ const { watch } = await watchModule
212
+ watch(sourcePath, hackmudPath, {
213
+ scripts,
214
+ onPush: info => logInfo(info, hackmudPath),
215
+ typeDeclarationPath: typeDeclarationPathOption?.value.toString(),
216
+ minify: noMinifyOption && !noMinifyOption.value,
217
+ mangleNames: mangleNamesOption?.value,
218
+ onReady: () => log("Watching"),
219
+ forceQuineCheats: forceQuineCheatsOption?.value
220
+ })
221
+ autoExit = !1
172
222
  }
173
- shouldMinify = !shouldSkipMinify
174
223
  }
175
- const shouldMangleNames = options.get("mangle-names")
176
- if (null != shouldMangleNames && "boolean" != typeof shouldMangleNames) {
177
- logError(
178
- `The value for ${colourN("--mangle-names")} must be ${colourV("true")} or ${colourV("false")}\n`
179
- )
180
- logHelp()
181
- break
182
- }
183
- const shouldforceQuineCheats = options.get("force-quine-cheats")
184
- if (null != shouldforceQuineCheats && "boolean" != typeof shouldforceQuineCheats) {
185
- logError(
186
- `The value for ${colourN("--force-quine-cheats")} must be ${colourV("true")} or ${colourV("false")}\n`
187
- )
188
- logHelp()
189
- break
190
- }
191
- const { watch } = await watchModule
192
- watch(sourcePath, hackmudPath, {
193
- scripts,
194
- onPush: info => logInfo(info, hackmudPath),
195
- typeDeclarationPath: (
196
- options.get("type-declaration-path") ||
197
- options.get("type-declaration") ||
198
- options.get("dts") ||
199
- options.get("gen-types")
200
- )?.toString(),
201
- minify: shouldMinify,
202
- mangleNames: shouldMangleNames,
203
- onReady: () => log("Watching"),
204
- forceQuineCheats: shouldforceQuineCheats
205
- })
206
- autoExit = !1
207
224
  }
208
225
  break
209
226
  case "pull":
@@ -213,8 +230,9 @@ switch (commands[0]) {
213
230
  if (!script) {
214
231
  logError("Must provide the script to pull\n")
215
232
  logHelp()
216
- break
233
+ process.exit(1)
217
234
  }
235
+ complainAboutUnrecognisedOptions()
218
236
  const sourcePath = commands[2] || "."
219
237
  await pull(sourcePath, hackmudPath, script).catch(error => {
220
238
  console.error(error)
@@ -224,8 +242,9 @@ switch (commands[0]) {
224
242
  break
225
243
  case "sync-macros":
226
244
  {
227
- const hackmudPath = getHackmudPath(),
228
- { macrosSynced, usersSynced } = await syncMacros(hackmudPath)
245
+ const hackmudPath = getHackmudPath()
246
+ complainAboutUnrecognisedOptions()
247
+ const { macrosSynced, usersSynced } = await syncMacros(hackmudPath)
229
248
  log(`Synced ${macrosSynced} macros to ${usersSynced} users`)
230
249
  }
231
250
  break
@@ -234,18 +253,20 @@ switch (commands[0]) {
234
253
  case "gen-dts":
235
254
  case "gen-types":
236
255
  {
237
- const target = commands[1]
256
+ const hackmudPath = getHackmudPath(),
257
+ target = commands[1]
238
258
  if (!target) {
239
259
  logError("Must provide target directory\n")
240
260
  logHelp()
241
- break
261
+ process.exit(1)
242
262
  }
263
+ complainAboutUnrecognisedOptions()
243
264
  const sourcePath = resolve(target),
244
265
  outputPath = commands[2] || "./player.d.ts",
245
- typeDeclaration = await generateTypeDeclaration(sourcePath, getHackmudPath())
266
+ typeDeclaration = await generateTypeDeclaration(sourcePath, hackmudPath)
246
267
  let typeDeclarationPath = resolve(outputPath)
247
268
  await writeFile(typeDeclarationPath, typeDeclaration).catch(error => {
248
- assert(error instanceof Error, "src/bin/hsm.ts:327:35")
269
+ assert(error instanceof Error, "src/bin/hsm.ts:343:35")
249
270
  if ("EISDIR" != error.code) throw error
250
271
  typeDeclarationPath = resolve(typeDeclarationPath, "player.d.ts")
251
272
  return writeFile(typeDeclarationPath, typeDeclaration)
@@ -257,99 +278,6 @@ switch (commands[0]) {
257
278
  case "h":
258
279
  logHelp()
259
280
  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
281
  default:
354
282
  commands[0] && logError(`Unknown command: ${colourL(commands[0])}\n`)
355
283
  logHelp()
@@ -423,7 +351,7 @@ function logError(message) {
423
351
  process.exitCode = 1
424
352
  }
425
353
  function getHackmudPath() {
426
- const hackmudPathOption = options.get("hackmud-path")
354
+ const hackmudPathOption = popOption("hackmud-path")
427
355
  if (null != hackmudPathOption && "string" != typeof hackmudPathOption) {
428
356
  logError(`Option ${colourN("--hackmud-path")} must be a string, got ${colourV(hackmudPathOption)}\n`)
429
357
  logHelp()
@@ -435,3 +363,32 @@ function getHackmudPath() {
435
363
  ("win32" == process.platform ? resolve(process.env.APPDATA, "hackmud") : resolve(homedir(), ".config/hackmud"))
436
364
  )
437
365
  }
366
+ function assertOptionIsBoolean(option) {
367
+ if ("boolean" != typeof option.value) {
368
+ logError(`The value for ${formatOption(option.name)} must be ${colourV("true")} or ${colourV("false")}\n`)
369
+ logHelp()
370
+ process.exit(1)
371
+ }
372
+ }
373
+ function popOption(...names) {
374
+ const presentOptionNames = names.filter(name => options.has(name))
375
+ if (!presentOptionNames.length) return
376
+ const presentOptionNamesWithDashDash = presentOptionNames.map(formatOption)
377
+ if (presentOptionNames.length > 1) {
378
+ logError(
379
+ `The options ${presentOptionNamesWithDashDash.join(", ")} are aliases for each other. Please only specify one`
380
+ )
381
+ process.exit(1)
382
+ }
383
+ const value = options.get(presentOptionNames[0])
384
+ options.delete(presentOptionNames[0])
385
+ return { name: presentOptionNamesWithDashDash[0], value }
386
+ }
387
+ function complainAboutUnrecognisedOptions() {
388
+ if (options.size) {
389
+ logError(
390
+ `Unrecognised option${options.size > 1 ? "s" : ""}: ${[...options.keys()].map(formatOption).join(", ")}`
391
+ )
392
+ process.exit(1)
393
+ }
394
+ }
package/env.d.ts CHANGED
@@ -3,13 +3,7 @@ type ScriptSuccess<T = object> = { ok: true } & T
3
3
  type ScriptFailure = { ok: false, msg?: string }
4
4
  type ScriptResponse<T = object> = ScriptSuccess<T> | ScriptFailure
5
5
  type ErrorScripts = Record<string, () => ScriptFailure>
6
-
7
- type AllOptional<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? true : false }[keyof T]
8
-
9
- type Scriptor<Args = unknown, Ret = unknown> = {
10
- name: string
11
- call: AllOptional<Args> extends true ? (args?: Args) => Ret : (args: Args) => Ret
12
- }
6
+ type Scriptor<TArgs extends any[] = any[]> = { name: string, call: (...args: TArgs) => unknown }
13
7
 
14
8
  type Subscripts = Record<string, Record<string, (...args: any) => any>> & {
15
9
  accts: ErrorScripts
@@ -151,7 +145,16 @@ type Fullsec = Subscripts & PlayerFullsec & {
151
145
  market: {
152
146
  /** **FULLSEC** */ browse: {
153
147
  (args:
154
- Partial<{ seller: string, listed_before: number, listed_after: number, cost: number | string } & Omit<CliUpgrade, "rarity">>
148
+ Partial<{
149
+ seller: string | MongoQuerySelector<string>,
150
+ listed_before: number | MongoQuerySelector<number>,
151
+ listed_after: number,
152
+ cost: number | MongoQuerySelector<number> | string,
153
+ rarity: UpgradeRarityNumber | MongoQuerySelector<UpgradeRarityNumber>,
154
+ name: string | MongoQuerySelector<string>
155
+ } & Omit<{
156
+ [k in keyof CliUpgrade]: CliUpgrade[k] | MongoQuerySelector<CliUpgrade[k]>
157
+ }, "rarity">>
155
158
  ): { i: string, name: string, rarity: Upgrade["rarity"], cost: number }[] | ScriptFailure
156
159
 
157
160
  <I extends string>(args: { i: I }): {
@@ -710,10 +713,50 @@ type Nullsec = Lowsec & PlayerNullsec & {
710
713
  }
711
714
  }
712
715
 
716
+ type MongoTypeString = "minKey" | "double" | "string" | "object" | "array" | "binData" | "undefined" | "objectId" |
717
+ "bool" | "date" | "null" | "regex" | "dbPointer" | "javascript" | "symbol" | "int" | "timestamp" | "long" | "decimal" | "maxKey";
718
+ type MongoTypeNumber = -1 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 127;
719
+
713
720
  type MongoValue = string | number | boolean | Date | MongoValue[] | { [key: string]: MongoValue } | null
714
721
 
715
722
  type MongoCommandValue = string | number | boolean | Date | MongoCommandValue[] | { [key: string]: MongoCommandValue } |
716
- null | undefined
723
+ null | undefined
724
+
725
+ /**
726
+ * Currently unused
727
+ */
728
+ type MongoLogicalSelectors<T extends MongoValue = MongoValue> = {
729
+ $not: T | MongoComparisonSelectors<T> | MongoLogicalSelectors<T>
730
+ $nor: T[]
731
+ $or: T[]
732
+ $and: T[]
733
+ }
734
+
735
+ type MongoArraySelectors<T extends Array<MongoValue> = Array<MongoValue>> = {
736
+ $all: T
737
+ $elemMatch: T
738
+ $size: number
739
+ }
740
+
741
+ type MongoComparisonSelectors<T extends MongoValue = MongoValue> = {
742
+ $eq: T
743
+ $gt: T
744
+ $gte: T
745
+ $in: T[]
746
+ $lt: T
747
+ $lte: T
748
+ $ne: T
749
+ $nin: T[]
750
+ }
751
+
752
+ type MongoElementSelectors = {
753
+ $exists: boolean
754
+ $type: MongoTypeNumber | MongoTypeString
755
+ }
756
+
757
+ type MongoQuerySelector<T extends MongoValue = MongoValue> = Partial<T extends MongoValue[] ?
758
+ (MongoArraySelectors<T> & MongoElementSelectors & MongoComparisonSelectors<T>) :
759
+ (MongoElementSelectors & MongoComparisonSelectors<T>)>
717
760
 
718
761
  type Query = { [key: string]: MongoValue | Query } & { _id?: Id, $in?: MongoValue[] }
719
762
  type Projection = Record<string, boolean | 0 | 1>
@@ -1,5 +1,6 @@
1
1
  import { readDirectoryWithStats } from "@samual/lib/readDirectoryWithStats"
2
2
  import { basename, resolve } from "path"
3
+ import * as PathPosix from "path/posix"
3
4
  async function generateTypeDeclaration(sourceDirectory, hackmudPath) {
4
5
  const users = new Set()
5
6
  if (hackmudPath)
@@ -29,7 +30,7 @@ async function generateTypeDeclaration(sourceDirectory, hackmudPath) {
29
30
  }
30
31
  })
31
32
  )
32
- sourceDirectory = resolve(sourceDirectory)
33
+ sourceDirectory = PathPosix.resolve(sourceDirectory)
33
34
  let o = ""
34
35
  for (const script of wildScripts) o += `type $${script}$ = typeof import("${sourceDirectory}/${script}").default\n`
35
36
  o += "\n"
package/index.js CHANGED
@@ -7,6 +7,7 @@ export { syncMacros } from "./syncMacros.js"
7
7
  export { watch } from "./watch.js"
8
8
  import "@samual/lib/readDirectoryWithStats"
9
9
  import "path"
10
+ import "path/posix"
10
11
  import "@babel/generator"
11
12
  import "@babel/parser"
12
13
  import "@babel/plugin-proposal-decorators"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hackmud-script-manager",
3
- "version": "0.20.4-3354314",
3
+ "version": "0.20.4-34f0749",
4
4
  "description": "Script manager for game hackmud, with minification, TypeScript support, and player script type definition generation.",
5
5
  "keywords": [
6
6
  "api",
@@ -323,7 +323,7 @@ async function processScript(
323
323
  trailingComma: "none"
324
324
  })
325
325
  }
326
- code = postprocess(code, seclevel, uniqueId)
326
+ code = postprocess(code, uniqueId)
327
327
  if (includesIllegalString(code))
328
328
  throw Error(
329
329
  'you found a weird edge case where I wasn\'t able to replace illegal strings like "SC$", please report thx'
@@ -43,15 +43,6 @@ async function minify(file, { uniqueId = "00000000000", mangleNames = !1, forceQ
43
43
  )
44
44
  }
45
45
  }
46
- const hashGReferencePaths = getReferencePathsToGlobal(`$${uniqueId}$GLOBAL$`, program)
47
- if (hashGReferencePaths.length > 3) {
48
- for (const path of hashGReferencePaths) path.replaceWith(t.identifier(`_${uniqueId}_G_`))
49
- mainFunctionPath.node.body.body.unshift(
50
- t.variableDeclaration("let", [
51
- t.variableDeclarator(t.identifier(`_${uniqueId}_G_`), t.identifier(`$${uniqueId}$GLOBAL$`))
52
- ])
53
- )
54
- }
55
46
  const jsonValues = []
56
47
  let scriptBeforeJSONValueReplacement,
57
48
  comment,
@@ -61,7 +52,7 @@ async function minify(file, { uniqueId = "00000000000", mangleNames = !1, forceQ
61
52
  traverse(fileBeforeJSONValueReplacement, {
62
53
  MemberExpression({ node: memberExpression }) {
63
54
  if (!memberExpression.computed) {
64
- assert("Identifier" == memberExpression.property.type, "src/processScript/minify.ts:127:60")
55
+ assert("Identifier" == memberExpression.property.type, "src/processScript/minify.ts:115:60")
65
56
  if ("prototype" == memberExpression.property.name) {
66
57
  memberExpression.computed = !0
67
58
  memberExpression.property = t.identifier(`_${uniqueId}_PROTOTYPE_PROPERTY_`)
@@ -173,7 +164,7 @@ async function minify(file, { uniqueId = "00000000000", mangleNames = !1, forceQ
173
164
  },
174
165
  MemberExpression({ node: memberExpression }) {
175
166
  if (!memberExpression.computed) {
176
- assert("Identifier" == memberExpression.property.type, "src/processScript/minify.ts:261:62")
167
+ assert("Identifier" == memberExpression.property.type, "src/processScript/minify.ts:249:62")
177
168
  if (!(memberExpression.property.name.length < 3)) {
178
169
  memberExpression.computed = !0
179
170
  memberExpression.property = t.stringLiteral(memberExpression.property.name)
@@ -247,7 +238,7 @@ async function minify(file, { uniqueId = "00000000000", mangleNames = !1, forceQ
247
238
  })
248
239
  await Promise.all(promises)
249
240
  const functionDeclaration = file.program.body[0]
250
- assert("FunctionDeclaration" == functionDeclaration.type, "src/processScript/minify.ts:366:61")
241
+ assert("FunctionDeclaration" == functionDeclaration.type, "src/processScript/minify.ts:354:61")
251
242
  if (jsonValues.length) {
252
243
  hasComment = !0
253
244
  if (1 == jsonValues.length)
@@ -259,7 +250,10 @@ async function minify(file, { uniqueId = "00000000000", mangleNames = !1, forceQ
259
250
  t.memberExpression(
260
251
  t.taggedTemplateExpression(
261
252
  t.memberExpression(
262
- t.callExpression(t.identifier(`$${uniqueId}$SUBSCRIPT$scripts$quine$`), []),
253
+ t.callExpression(
254
+ t.identifier(`$${uniqueId}$4$SUBSCRIPT$scripts$quine$`),
255
+ []
256
+ ),
263
257
  t.identifier("split")
264
258
  ),
265
259
  t.templateLiteral([t.templateElement({ raw: "\t", cooked: "\t" }, !0)], [])
@@ -283,7 +277,7 @@ async function minify(file, { uniqueId = "00000000000", mangleNames = !1, forceQ
283
277
  t.memberExpression(
284
278
  t.taggedTemplateExpression(
285
279
  t.memberExpression(
286
- t.callExpression(t.identifier(`$${uniqueId}$SUBSCRIPT$scripts$quine$`), []),
280
+ t.callExpression(t.identifier(`$${uniqueId}$4$SUBSCRIPT$scripts$quine$`), []),
287
281
  t.identifier("split")
288
282
  ),
289
283
  t.templateLiteral([t.templateElement({ raw: "\t", cooked: "\t" }, !0)], [])
@@ -308,7 +302,7 @@ async function minify(file, { uniqueId = "00000000000", mangleNames = !1, forceQ
308
302
  t.memberExpression(
309
303
  t.taggedTemplateExpression(
310
304
  t.memberExpression(
311
- t.callExpression(t.identifier(`$${uniqueId}$SUBSCRIPT$scripts$quine$`), []),
305
+ t.callExpression(t.identifier(`$${uniqueId}$4$SUBSCRIPT$scripts$quine$`), []),
312
306
  t.identifier("split")
313
307
  ),
314
308
  t.templateLiteral([t.templateElement({ raw: "\t", cooked: "\t" }, !0)], [])
@@ -364,7 +358,7 @@ async function minify(file, { uniqueId = "00000000000", mangleNames = !1, forceQ
364
358
  )
365
359
  }
366
360
  if (1 == forceQuineCheats) return code
367
- assert(scriptBeforeJSONValueReplacement, "src/processScript/minify.ts:497:43")
361
+ assert(scriptBeforeJSONValueReplacement, "src/processScript/minify.ts:485:43")
368
362
  return (
369
363
  countHackmudCharacters(scriptBeforeJSONValueReplacement) <=
370
364
  countHackmudCharacters(code) + Number(hasComment)
@@ -380,7 +374,7 @@ function parseObjectExpression(node, o) {
380
374
  "Identifier" == property.key.type ||
381
375
  "NumericLiteral" == property.key.type ||
382
376
  "StringLiteral" == property.key.type,
383
- "src/processScript/minify.ts:519:4"
377
+ "src/processScript/minify.ts:507:4"
384
378
  )
385
379
  if ("ArrayExpression" == property.value.type) {
386
380
  const childArray = []
@@ -1 +1 @@
1
- export declare const postprocess: (code: string, seclevel: number, uniqueId: string) => string;
1
+ export declare const postprocess: (code: string, uniqueId: string) => string;
@@ -1,12 +1,12 @@
1
- const postprocess = (code, seclevel, uniqueId) =>
1
+ const postprocess = (code, uniqueId) =>
2
2
  code
3
- .replace(/^function\s*\w+\(/, "function(")
3
+ .replace(/^function\s*[\w$]+\(/, "function(")
4
4
  .replace(RegExp(`\\$${uniqueId}\\$\\\\(?:\\\\)?\\$SC_DOLLAR\\$`, "g"), "S\\C$")
5
5
  .replace(RegExp(`\\$${uniqueId}\\$\\\\(?:\\\\)?\\$DB_DOLLAR\\$`, "g"), "D\\B$")
6
6
  .replace(RegExp(`\\$${uniqueId}\\$\\\\(?:\\\\)?\\$D\\$`, "g"), "_\\_D_S")
7
7
  .replace(RegExp(`\\$${uniqueId}\\$\\\\(?:\\\\)?\\$FMCL\\$`, "g"), "_\\_FMCL_")
8
8
  .replace(RegExp(`\\$${uniqueId}\\$\\\\(?:\\\\)?\\$G\\$`, "g"), "_\\_G_")
9
- .replace(RegExp(`\\$${uniqueId}\\$SUBSCRIPT\\$(\\w+)\\$(\\w+)\\$`, "g"), `#${"nlmhf"[seclevel]}s.$1.$2`)
9
+ .replace(RegExp(`\\$${uniqueId}\\$(\\d)\\$SUBSCRIPT\\$(\\w+)\\$(\\w+)\\$`, "g"), "#$1s.$2.$3")
10
10
  .replace(RegExp(`\\$${uniqueId}\\$DEBUG\\$`, "g"), "#D")
11
11
  .replace(RegExp(`\\$${uniqueId}\\$FMCL\\$`, "g"), "#FMCL")
12
12
  .replace(RegExp(`\\$${uniqueId}\\$GLOBAL\\$`, "g"), "#G")
@@ -98,8 +98,10 @@ async function preprocess(code, { uniqueId = "00000000000" } = {}) {
98
98
  t.stringLiteral(resolve("proxy-polyfill/src/proxy.js", import.meta.url).slice(7))
99
99
  )
100
100
  )
101
- return 1 == program.node.body.length && "FunctionDeclaration" == program.node.body[0].type ?
102
- { code: "export default " + generate(file).code }
103
- : { code: generate(file).code }
101
+ if (1 == program.node.body.length && "FunctionDeclaration" == program.node.body[0].type)
102
+ throw Error(
103
+ "Scripts that only contain a single function declaration are no longer supported.\nPrefix the function declaration with `export default`."
104
+ )
105
+ return { code: generate(file).code }
104
106
  }
105
107
  export { preprocess }
@@ -22,9 +22,7 @@ const { default: traverse } = babelTraverse,
22
22
  "BigInt"
23
23
  ]
24
24
  function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scriptName, seclevel = 4 }) {
25
- const topFunctionName = `_${uniqueId}_SCRIPT_`,
26
- exports = new Map(),
27
- liveExports = new Map()
25
+ const topFunctionName = `_${uniqueId}_SCRIPT_`
28
26
  let program
29
27
  traverse(file, {
30
28
  Program(path) {
@@ -70,30 +68,30 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
70
68
  const referencePath = FunctionReferencePaths[0]
71
69
  assert(
72
70
  "MemberExpression" == referencePath.parent.type,
73
- "src/processScript/transform.ts:105:8 `Function` isn't available in hackmud, only `Function.prototype` is accessible"
71
+ "src/processScript/transform.ts:103:8 `Function` isn't available in hackmud, only `Function.prototype` is accessible"
74
72
  )
75
73
  assert(
76
74
  "Identifier" == referencePath.parent.property.type,
77
- "src/processScript/transform.ts:110:8 `Function` isn't available in hackmud, only `Function.prototype` is accessible"
75
+ "src/processScript/transform.ts:108:8 `Function` isn't available in hackmud, only `Function.prototype` is accessible"
78
76
  )
79
77
  assert(
80
78
  "prototype" == referencePath.parent.property.name,
81
- "src/processScript/transform.ts:115:8 `Function` isn't available in hackmud, only `Function.prototype` is accessible"
79
+ "src/processScript/transform.ts:113:8 `Function` isn't available in hackmud, only `Function.prototype` is accessible"
82
80
  )
83
81
  referencePath.parentPath.replaceWith(createGetFunctionPrototypeNode())
84
82
  } else {
85
83
  for (const referencePath of FunctionReferencePaths) {
86
84
  assert(
87
85
  "MemberExpression" == referencePath.parent.type,
88
- "src/processScript/transform.ts:123:9 `Function` isn't available in hackmud, only `Function.prototype` is accessible"
86
+ "src/processScript/transform.ts:121:9 `Function` isn't available in hackmud, only `Function.prototype` is accessible"
89
87
  )
90
88
  assert(
91
89
  "Identifier" == referencePath.parent.property.type,
92
- "src/processScript/transform.ts:128:9 `Function` isn't available in hackmud, only `Function.prototype` is accessible"
90
+ "src/processScript/transform.ts:126:9 `Function` isn't available in hackmud, only `Function.prototype` is accessible"
93
91
  )
94
92
  assert(
95
93
  "prototype" == referencePath.parent.property.name,
96
- "src/processScript/transform.ts:133:9 `Function` isn't available in hackmud, only `Function.prototype` is accessible"
94
+ "src/processScript/transform.ts:131:9 `Function` isn't available in hackmud, only `Function.prototype` is accessible"
97
95
  )
98
96
  functionDotPrototypeIsReferencedMultipleTimes = !0
99
97
  referencePath.parentPath.replaceWith(t.identifier(`_${uniqueId}_FUNCTION_DOT_PROTOTYPE_`))
@@ -101,40 +99,40 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
101
99
  functionDotPrototypeIsReferencedMultipleTimes = !0
102
100
  }
103
101
  }
104
- const neededSubscriptLets = new Set()
102
+ const neededSubscriptLets = new Map()
105
103
  let detectedSeclevel = 4
106
104
  for (const fakeSubscriptObjectName of ["$fs", "$4s", "$s"])
107
- program.scope.hasGlobal(fakeSubscriptObjectName) && processFakeSubscriptObject(fakeSubscriptObjectName)
105
+ program.scope.hasGlobal(fakeSubscriptObjectName) && processFakeSubscriptObject(fakeSubscriptObjectName, 4)
108
106
  for (const fakeSubscriptObjectName of ["$hs", "$3s"])
109
107
  if (program.scope.hasGlobal(fakeSubscriptObjectName)) {
110
108
  detectedSeclevel = 3
111
- processFakeSubscriptObject(fakeSubscriptObjectName)
109
+ processFakeSubscriptObject(fakeSubscriptObjectName, 3)
112
110
  }
113
111
  for (const fakeSubscriptObjectName of ["$ms", "$2s"])
114
112
  if (program.scope.hasGlobal(fakeSubscriptObjectName)) {
115
113
  detectedSeclevel = 2
116
- processFakeSubscriptObject(fakeSubscriptObjectName)
114
+ processFakeSubscriptObject(fakeSubscriptObjectName, 2)
117
115
  }
118
116
  for (const fakeSubscriptObjectName of ["$ls", "$1s"])
119
117
  if (program.scope.hasGlobal(fakeSubscriptObjectName)) {
120
118
  detectedSeclevel = 1
121
- processFakeSubscriptObject(fakeSubscriptObjectName)
119
+ processFakeSubscriptObject(fakeSubscriptObjectName, 1)
122
120
  }
123
121
  for (const fakeSubscriptObjectName of ["$ns", "$0s"])
124
122
  if (program.scope.hasGlobal(fakeSubscriptObjectName)) {
125
123
  detectedSeclevel = 0
126
- processFakeSubscriptObject(fakeSubscriptObjectName)
124
+ processFakeSubscriptObject(fakeSubscriptObjectName, 0)
127
125
  }
128
126
  seclevel = Math.min(seclevel, detectedSeclevel)
129
127
  const neededDbMethodLets = new Set()
130
128
  if (program.scope.hasGlobal("$db"))
131
129
  for (const referencePath of getReferencePathsToGlobal("$db", program)) {
132
- assert("MemberExpression" == referencePath.parentPath.node.type, "src/processScript/transform.ts:187:69")
133
- assert("Identifier" == referencePath.parentPath.node.property.type, "src/processScript/transform.ts:188:72")
130
+ assert("MemberExpression" == referencePath.parentPath.node.type, "src/processScript/transform.ts:185:69")
131
+ assert("Identifier" == referencePath.parentPath.node.property.type, "src/processScript/transform.ts:186:72")
134
132
  const databaseOpMethodName = referencePath.parentPath.node.property.name
135
133
  assert(
136
134
  validDBMethods.includes(databaseOpMethodName),
137
- `src/processScript/transform.ts:194:8 invalid db method "${databaseOpMethodName}", valid db methods are "${validDBMethods.join('", "')}"`
135
+ `src/processScript/transform.ts:192:8 invalid db method "${databaseOpMethodName}", valid db methods are "${validDBMethods.join('", "')}"`
138
136
  )
139
137
  if ("CallExpression" == referencePath.parentPath.parentPath?.type)
140
138
  referencePath.parentPath.replaceWith(t.identifier(`$${uniqueId}$DB$${databaseOpMethodName}$`))
@@ -157,9 +155,10 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
157
155
  if (program.scope.hasGlobal("$FMCL"))
158
156
  for (const referencePath of getReferencePathsToGlobal("$FMCL", program))
159
157
  referencePath.replaceWith(t.identifier(`$${uniqueId}$FMCL$`))
160
- if (program.scope.hasGlobal("$G"))
158
+ let needG = program.scope.hasGlobal("$G")
159
+ if (needG)
161
160
  for (const referencePath of getReferencePathsToGlobal("$G", program))
162
- referencePath.replaceWith(t.identifier(`$${uniqueId}$GLOBAL$`))
161
+ referencePath.replaceWith(t.identifier(`_${uniqueId}_G_`))
163
162
  if (program.scope.hasGlobal("_SECLEVEL"))
164
163
  for (const referencePath of getReferencePathsToGlobal("_SECLEVEL", program))
165
164
  referencePath.replaceWith(t.numericLiteral(seclevel))
@@ -193,11 +192,12 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
193
192
  "ExportSpecifier" == specifier.type,
194
193
  `src/processScript/transform.ts:276:51 ${specifier.type} is currently unsupported`
195
194
  )
196
- const exportedName =
197
- "Identifier" == specifier.exported.type ? specifier.exported.name : specifier.exported.value
198
- "default" == exportedName ?
199
- (exportDefaultName = specifier.local.name)
200
- : exports.set(specifier.local.name, exportedName)
195
+ if (
196
+ "default" !=
197
+ ("Identifier" == specifier.exported.type ? specifier.exported.name : specifier.exported.value)
198
+ )
199
+ throw Error("Only default exports are supported")
200
+ exportDefaultName = specifier.local.name
201
201
  }
202
202
  }
203
203
  const globalBlock = t.blockStatement([])
@@ -223,10 +223,6 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
223
223
  t.returnStatement(t.callExpression(t.identifier(exportDefaultName), []))
224
224
  ])
225
225
  ))
226
- if ("const" != statement.kind && exports.has(identifierName)) {
227
- liveExports.set(identifierName, exports.get(identifierName))
228
- exports.delete(identifierName)
229
- }
230
226
  globalBlock.body.push(
231
227
  t.variableDeclaration("let", [t.variableDeclarator(t.identifier(identifierName))])
232
228
  )
@@ -291,34 +287,16 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
291
287
  }
292
288
  program.node.body = [mainFunction]
293
289
  if (globalBlock.body.length) {
294
- ;(exports.size || liveExports.size) &&
295
- mainFunction.body.body.push(
296
- t.returnStatement(
297
- t.objectExpression([
298
- ...[...exports].map(([local, exported]) =>
299
- t.objectProperty(t.identifier(exported), t.identifier(local))
300
- ),
301
- ...[...liveExports].map(([local, exported]) =>
302
- t.objectMethod(
303
- "get",
304
- t.identifier(exported),
305
- [],
306
- t.blockStatement([t.returnStatement(t.identifier(local))])
307
- )
308
- )
309
- ])
310
- )
311
- )
312
290
  program.scope.crawl()
313
291
  const globalBlockVariables = new Set()
314
292
  let hoistedGlobalBlockFunctions = 0
315
293
  for (const [globalBlockIndex, globalBlockStatement] of [...globalBlock.body.entries()].reverse())
316
294
  if ("VariableDeclaration" == globalBlockStatement.type) {
317
- assert(1 == globalBlockStatement.declarations.length, "src/processScript/transform.ts:410:59")
295
+ assert(1 == globalBlockStatement.declarations.length, "src/processScript/transform.ts:390:59")
318
296
  const declarator = globalBlockStatement.declarations[0]
319
297
  assert(
320
298
  "Identifier" == declarator.id.type,
321
- `src/processScript/transform.ts:414:51 declarator.id.type was "${declarator.id.type}"`
299
+ `src/processScript/transform.ts:394:51 declarator.id.type was "${declarator.id.type}"`
322
300
  )
323
301
  program.scope.crawl()
324
302
  if (program.scope.hasGlobal(declarator.id.name)) {
@@ -333,15 +311,16 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
333
311
  Object.keys(program.scope.globals).some(global => globalBlockVariables.has(global))
334
312
  ) {
335
313
  const binding = program.scope.getBinding(declarator.id.name)
336
- assert(binding, "src/processScript/transform.ts:433:23")
314
+ assert(binding, "src/processScript/transform.ts:413:23")
337
315
  for (const referencePath of binding.referencePaths) {
338
- assert("Identifier" == referencePath.node.type, "src/processScript/transform.ts:436:56")
316
+ assert("Identifier" == referencePath.node.type, "src/processScript/transform.ts:416:56")
339
317
  referencePath.replaceWith(
340
318
  t.memberExpression(
341
- t.identifier(`$${uniqueId}$GLOBAL$`),
319
+ t.identifier(`_${uniqueId}_G_`),
342
320
  t.identifier(referencePath.node.name)
343
321
  )
344
322
  )
323
+ needG = !0
345
324
  }
346
325
  for (const referencePath of binding.constantViolations)
347
326
  if ("AssignmentExpression" == referencePath.node.type)
@@ -350,12 +329,13 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
350
329
  clearObject(node)
351
330
  Object.assign(
352
331
  node,
353
- t.memberExpression(t.identifier(`$${uniqueId}$GLOBAL$`), t.identifier(name))
332
+ t.memberExpression(t.identifier(`_${uniqueId}_G_`), t.identifier(name))
354
333
  )
334
+ needG = !0
355
335
  }
356
336
  globalBlockPath.remove()
357
337
  globalBlockStatementPath.remove()
358
- declarator.init &&
338
+ if (declarator.init) {
359
339
  globalBlock.body.splice(
360
340
  globalBlockIndex,
361
341
  0,
@@ -363,13 +343,15 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
363
343
  t.assignmentExpression(
364
344
  "=",
365
345
  t.memberExpression(
366
- t.identifier(`$${uniqueId}$GLOBAL$`),
346
+ t.identifier(`_${uniqueId}_G_`),
367
347
  t.identifier(declarator.id.name)
368
348
  ),
369
349
  declarator.init
370
350
  )
371
351
  )
372
352
  )
353
+ needG = !0
354
+ }
373
355
  } else {
374
356
  globalBlockPath.remove()
375
357
  globalBlockStatementPath.remove()
@@ -379,22 +361,20 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
379
361
  } else globalBlockVariables.add(declarator.id.name)
380
362
  } else if ("ClassDeclaration" == globalBlockStatement.type) {
381
363
  program.scope.crawl()
382
- assert(globalBlockStatement.id, "src/processScript/transform.ts:487:37")
364
+ assert(globalBlockStatement.id, "src/processScript/transform.ts:473:37")
383
365
  if (program.scope.hasGlobal(globalBlockStatement.id.name)) {
384
366
  globalBlock.body.splice(globalBlockIndex, 1)
385
367
  const [globalBlockPath] = program.unshiftContainer("body", globalBlock),
386
368
  [globalBlockStatementPath] = program.unshiftContainer("body", globalBlockStatement)
387
369
  program.scope.crawl()
388
370
  const binding = program.scope.getBinding(globalBlockStatement.id.name)
389
- assert(binding, "src/processScript/transform.ts:499:22")
371
+ assert(binding, "src/processScript/transform.ts:485:22")
390
372
  for (const referencePath of binding.referencePaths) {
391
- assert("Identifier" == referencePath.node.type, "src/processScript/transform.ts:502:55")
373
+ assert("Identifier" == referencePath.node.type, "src/processScript/transform.ts:488:55")
392
374
  referencePath.replaceWith(
393
- t.memberExpression(
394
- t.identifier(`$${uniqueId}$GLOBAL$`),
395
- t.identifier(referencePath.node.name)
396
- )
375
+ t.memberExpression(t.identifier(`_${uniqueId}_G_`), t.identifier(referencePath.node.name))
397
376
  )
377
+ needG = !0
398
378
  }
399
379
  globalBlockPath.remove()
400
380
  globalBlockStatementPath.remove()
@@ -405,7 +385,7 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
405
385
  t.assignmentExpression(
406
386
  "=",
407
387
  t.memberExpression(
408
- t.identifier(`$${uniqueId}$GLOBAL$`),
388
+ t.identifier(`_${uniqueId}_G_`),
409
389
  t.identifier(globalBlockStatement.id.name)
410
390
  ),
411
391
  t.classExpression(
@@ -417,13 +397,9 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
417
397
  )
418
398
  )
419
399
  )
400
+ needG = !0
420
401
  }
421
402
  }
422
- if (program.scope.hasGlobal("_EXPORTS"))
423
- for (const referencePath of getReferencePathsToGlobal("_EXPORTS", program))
424
- referencePath.replaceWith(
425
- t.arrayExpression([...exports.keys(), ...liveExports.keys()].map(name => t.stringLiteral(name)))
426
- )
427
403
  globalBlock.body.length &&
428
404
  mainFunction.body.body.splice(
429
405
  hoistedGlobalBlockFunctions,
@@ -522,12 +498,12 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
522
498
  mainFunction.body.body.unshift(
523
499
  t.variableDeclaration(
524
500
  "let",
525
- [...neededSubscriptLets].map(name =>
501
+ [...neededSubscriptLets].map(([name, seclevel]) =>
526
502
  t.variableDeclarator(
527
503
  t.identifier(`_${uniqueId}_SUBSCRIPT_${name}_`),
528
504
  t.arrowFunctionExpression(
529
505
  [t.restElement(t.identifier("args"))],
530
- t.callExpression(t.identifier(`$${uniqueId}$SUBSCRIPT$${name}$`), [
506
+ t.callExpression(t.identifier(`$${uniqueId}$${seclevel}$SUBSCRIPT$${name}$`), [
531
507
  t.spreadElement(t.identifier("args"))
532
508
  ])
533
509
  )
@@ -535,6 +511,12 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
535
511
  )
536
512
  )
537
513
  )
514
+ needG &&
515
+ mainFunction.body.body.unshift(
516
+ t.variableDeclaration("let", [
517
+ t.variableDeclarator(t.identifier(`_${uniqueId}_G_`), t.identifier(`$${uniqueId}$GLOBAL$`))
518
+ ])
519
+ )
538
520
  traverse(file, {
539
521
  BlockStatement({ node: blockStatement }) {
540
522
  for (const [index, functionDeclaration] of blockStatement.body.entries())
@@ -555,7 +537,7 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
555
537
  }
556
538
  },
557
539
  ClassBody({ node: classBody, scope, parent }) {
558
- assert(t.isClass(parent), "src/processScript/transform.ts:669:30")
540
+ assert(t.isClass(parent), "src/processScript/transform.ts:658:30")
559
541
  let thisIsReferenced = !1
560
542
  for (const classMethod of classBody.body) {
561
543
  if ("ClassMethod" != classMethod.type) continue
@@ -661,36 +643,37 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
661
643
  t.identifier("prototype")
662
644
  )
663
645
  }
664
- function processFakeSubscriptObject(fakeSubscriptObjectName) {
646
+ function processFakeSubscriptObject(fakeSubscriptObjectName, seclevel) {
665
647
  for (const referencePath of getReferencePathsToGlobal(fakeSubscriptObjectName, program)) {
666
- assert("MemberExpression" == referencePath.parent.type, "src/processScript/transform.ts:785:60")
648
+ assert("MemberExpression" == referencePath.parent.type, "src/processScript/transform.ts:774:60")
667
649
  assert("Identifier" == referencePath.parent.property.type)
668
650
  assert(
669
651
  "MemberExpression" == referencePath.parentPath.parentPath?.node.type,
670
- "src/processScript/transform.ts:787:81"
652
+ "src/processScript/transform.ts:776:81"
671
653
  )
672
654
  assert(
673
655
  "Identifier" == referencePath.parentPath.parentPath.node.property.type,
674
- "src/processScript/transform.ts:788:83"
656
+ "src/processScript/transform.ts:777:83"
675
657
  )
676
658
  assert(
677
659
  /^[_a-z][\d_a-z]{0,24}$/.test(referencePath.parent.property.name),
678
- `src/processScript/transform.ts:792:8 invalid user "${referencePath.parent.property.name}" in subscript`
660
+ `src/processScript/transform.ts:781:8 invalid user "${referencePath.parent.property.name}" in subscript`
679
661
  )
680
662
  assert(
681
663
  /^[_a-z][\d_a-z]{0,24}$/.test(referencePath.parentPath.parentPath.node.property.name),
682
- `src/processScript/transform.ts:797:8 invalid script name "${referencePath.parentPath.parentPath.node.property.name}" in subscript`
664
+ `src/processScript/transform.ts:786:8 invalid script name "${referencePath.parentPath.parentPath.node.property.name}" in subscript`
683
665
  )
684
666
  if ("CallExpression" == referencePath.parentPath.parentPath.parentPath?.type)
685
667
  referencePath.parentPath.parentPath.replaceWith(
686
668
  t.identifier(
687
- `$${uniqueId}$SUBSCRIPT$${referencePath.parent.property.name}$${referencePath.parentPath.parentPath.node.property.name}$`
669
+ `$${uniqueId}$${seclevel}$SUBSCRIPT$${referencePath.parent.property.name}$${referencePath.parentPath.parentPath.node.property.name}$`
688
670
  )
689
671
  )
690
672
  else {
691
673
  const name = `${referencePath.parent.property.name}$${referencePath.parentPath.parentPath.node.property.name}`
692
674
  referencePath.parentPath.parentPath.replaceWith(t.identifier(`_${uniqueId}_SUBSCRIPT_${name}_`))
693
- neededSubscriptLets.add(name)
675
+ const maxSecLevel = Math.max(neededSubscriptLets.get(name) || 0, seclevel)
676
+ neededSubscriptLets.set(name, maxSecLevel)
694
677
  }
695
678
  }
696
679
  }
package/push.d.ts CHANGED
@@ -18,6 +18,14 @@ export type PushOptions = LaxPartial<{
18
18
  */
19
19
  forceQuineCheats: boolean;
20
20
  }>;
21
+ export declare class MissingSourceFolderError extends Error {
22
+ }
23
+ export declare class MissingHackmudFolderError extends Error {
24
+ }
25
+ export declare class NoUsersError extends Error {
26
+ }
27
+ export declare class NoScriptsError extends Error {
28
+ }
21
29
  /** Push scripts from a source directory to the hackmud directory.
22
30
  *
23
31
  * Pushes files directly in the source folder to all users
@@ -25,4 +33,4 @@ export type PushOptions = LaxPartial<{
25
33
  * @param hackmudPath directory created by hackmud containing user data including scripts
26
34
  * @param options {@link PushOptions details}
27
35
  * @returns array of info on pushed scripts */
28
- export declare function push(sourcePath: string, hackmudPath: string, { scripts, onPush, minify, mangleNames, forceQuineCheats }?: PushOptions): Promise<Info[]>;
36
+ export declare function push(sourcePath: string, hackmudPath: string, { scripts, onPush, minify, mangleNames, forceQuineCheats }?: PushOptions): Promise<MissingSourceFolderError | MissingHackmudFolderError | NoUsersError | NoScriptsError | Info[]>;
package/push.js CHANGED
@@ -42,19 +42,37 @@ import "./processScript/preprocess.js"
42
42
  import "import-meta-resolve"
43
43
  import "./processScript/transform.js"
44
44
  import "@samual/lib/clearObject"
45
+ class MissingSourceFolderError extends Error {}
46
+ Object.defineProperty(MissingSourceFolderError.prototype, "name", { value: "MissingSourceFolderError" })
47
+ class MissingHackmudFolderError extends Error {}
48
+ Object.defineProperty(MissingHackmudFolderError.prototype, "name", { value: "MissingHackmudFolderError" })
49
+ class NoUsersError extends Error {}
50
+ Object.defineProperty(NoUsersError.prototype, "name", { value: "NoUsersError" })
51
+ class NoScriptsError extends Error {}
52
+ Object.defineProperty(NoScriptsError.prototype, "name", { value: "NoScriptsError" })
45
53
  async function push(
46
54
  sourcePath,
47
55
  hackmudPath,
48
56
  { scripts = ["*.*"], onPush = () => {}, minify = !0, mangleNames = !1, forceQuineCheats } = {}
49
57
  ) {
50
58
  const [sourceFolder, hackmudFolder] = await Promise.all([
51
- readDirectoryWithStats(sourcePath),
52
- readDirectoryWithStats(hackmudPath)
53
- ]),
54
- sourceFolderFolders = sourceFolder.filter(({ stats }) => stats.isDirectory()),
59
+ readDirectoryWithStats(sourcePath).catch(error => {
60
+ if (error && "ENOENT" == error.code)
61
+ return new MissingSourceFolderError("There is no folder at " + sourcePath)
62
+ throw error
63
+ }),
64
+ readDirectoryWithStats(hackmudPath).catch(error => {
65
+ if (error && "ENOENT" == error.code)
66
+ return new MissingHackmudFolderError("There is no folder at " + hackmudPath)
67
+ throw error
68
+ })
69
+ ])
70
+ if (sourceFolder instanceof Error) return sourceFolder
71
+ if (hackmudFolder instanceof Error) return hackmudFolder
72
+ const sourceFolderFolders = sourceFolder.filter(({ stats }) => stats.isDirectory()),
55
73
  allUsers = new Set([
56
74
  ...scripts
57
- .map(scriptName => ensure(scriptName.split(".")[0], "src/push.ts:52:65"))
75
+ .map(scriptName => ensure(scriptName.split(".")[0], "src/push.ts:82:65"))
58
76
  .filter(name => "*" != name),
59
77
  ...sourceFolderFolders.map(({ name }) => name),
60
78
  ...hackmudFolder.filter(({ stats }) => stats.isDirectory()).map(({ name }) => name),
@@ -63,13 +81,15 @@ async function push(
63
81
  .map(({ name }) => name.slice(0, -4))
64
82
  ])
65
83
  if (!allUsers.size)
66
- throw Error("Could not find any users. Either provide the names of your users or log into a user in hackmud.")
84
+ return new NoUsersError(
85
+ "Could not find any users. Either provide the names of your users or log into a user in hackmud"
86
+ )
67
87
  const usersToScriptsToPush = new Cache(_user => new Map()),
68
88
  scriptNamesToUsers = new Cache(_scriptName => new Set())
69
89
  for (const script of scripts) {
70
90
  const [user, scriptName] = script.split(".")
71
- assert(user, "src/push.ts:72:16")
72
- assert(scriptName, "src/push.ts:73:22")
91
+ assert(user, "src/push.ts:105:16")
92
+ assert(scriptName, "src/push.ts:106:22")
73
93
  "*" == user ? scriptNamesToUsers.set(scriptName, allUsers) : scriptNamesToUsers.get(scriptName).add(user)
74
94
  }
75
95
  const sourceFolderFiles = sourceFolder.filter(({ stats }) => stats.isFile()),
@@ -100,7 +120,7 @@ async function push(
100
120
  for (const [scriptName, users] of scriptNamesToUsers)
101
121
  for (const user of users)
102
122
  if (!usersToScriptsToPush.get(user).has(scriptName))
103
- throw Error(`Could not find script ${user}.${scriptName} to push`)
123
+ return new NoScriptsError(`Could not find script ${user}.${scriptName} to push`)
104
124
  const pathsToUsers = new Cache(_path => new Set())
105
125
  for (const [user, scriptsToPush] of usersToScriptsToPush)
106
126
  for (const path of scriptsToPush.values()) pathsToUsers.get(path).add(user)
@@ -137,4 +157,4 @@ async function push(
137
157
  )
138
158
  return allInfo
139
159
  }
140
- export { push }
160
+ export { MissingHackmudFolderError, MissingSourceFolderError, NoScriptsError, NoUsersError, push }
package/watch.js CHANGED
@@ -4,11 +4,12 @@ import { countHackmudCharacters } from "@samual/lib/countHackmudCharacters"
4
4
  import { readDirectoryWithStats } from "@samual/lib/readDirectoryWithStats"
5
5
  import { writeFilePersistent } from "@samual/lib/writeFilePersistent"
6
6
  import { watch as watch$1 } from "chokidar"
7
- import { readFile, writeFile } from "fs/promises"
7
+ import { stat, readFile, writeFile } from "fs/promises"
8
8
  import { extname, basename, resolve } from "path"
9
9
  import { supportedExtensions } from "./constants.js"
10
10
  import { generateTypeDeclaration } from "./generateTypeDeclaration.js"
11
11
  import { processScript } from "./processScript/index.js"
12
+ import "path/posix"
12
13
  import "@babel/generator"
13
14
  import "@babel/parser"
14
15
  import "@babel/plugin-proposal-decorators"
@@ -58,6 +59,7 @@ async function watch(
58
59
  } = {}
59
60
  ) {
60
61
  if (!scripts.length) throw Error("scripts option was an empty array")
62
+ if (!(await stat(sourceDirectory)).isDirectory()) throw Error("Target folder must be a folder")
61
63
  const scriptNamesToUsers = new Cache(_scriptName => new Set()),
62
64
  wildScriptUsers = new Set(),
63
65
  wildUserScripts = new Set()
@@ -135,7 +137,7 @@ async function watch(
135
137
  forceQuineCheats
136
138
  }))
137
139
  } catch (error) {
138
- assert(error instanceof Error, "src/watch.ts:141:36")
140
+ assert(error instanceof Error, "src/watch.ts:146:36")
139
141
  onPush?.({ path, users: [], characterCount: 0, error })
140
142
  return
141
143
  }
@@ -180,7 +182,7 @@ async function watch(
180
182
  forceQuineCheats
181
183
  }))
182
184
  } catch (error) {
183
- assert(error instanceof Error, "src/watch.ts:177:35")
185
+ assert(error instanceof Error, "src/watch.ts:182:35")
184
186
  onPush?.({ path, users: [], characterCount: 0, error })
185
187
  return
186
188
  }
@@ -195,7 +197,7 @@ async function watch(
195
197
  try {
196
198
  await writeFile(typeDeclarationPath, typeDeclaration)
197
199
  } catch (error) {
198
- assert(error instanceof Error, "src/watch.ts:210:35")
200
+ assert(error instanceof Error, "src/watch.ts:215:35")
199
201
  if ("EISDIR" != error.code) throw error
200
202
  typeDeclarationPath = resolve(typeDeclarationPath, "player.d.ts")
201
203
  await writeFile(typeDeclarationPath, typeDeclaration)