hackmud-script-manager 0.20.4-d9a4cf1 → 0.20.4-ec30de9

Sign up to get free protection for your applications and to get access to all the features.
package/bin/hsm.js CHANGED
@@ -5,14 +5,14 @@ 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
14
  import "@samual/lib/copyFilePersistent"
15
- const version = "0.20.4-d9a4cf1",
15
+ const version = "0.20.4-ec30de9",
16
16
  options = new Map(),
17
17
  commands = [],
18
18
  userColours = new Cache(user => {
@@ -36,10 +36,6 @@ for (const argument of process.argv.slice(2))
36
36
  if ("-" == argument[1]) options.set(key.slice(2), value)
37
37
  else for (const option of key.slice(1)) options.set(option, value)
38
38
  } 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
39
  const pushModule = import("../push.js"),
44
40
  processScriptModule = import("../processScript/index.js"),
45
41
  watchModule = import("../watch.js"),
@@ -57,153 +53,163 @@ const pushModule = import("../push.js"),
57
53
  colourS = chalk.rgb(122, 178, 244),
58
54
  colourV = chalk.rgb(255, 0, 236),
59
55
  colourW = chalk.rgb(255, 150, 224)
60
- if (options.get("help") || options.get("h")) {
56
+ if ("v" == commands[0] || "version" == commands[0] || popOption("version", "v")?.value) {
57
+ console.log(version)
58
+ process.exit()
59
+ }
60
+ if (popOption("help", "h")?.value) {
61
61
  logHelp()
62
62
  process.exit()
63
63
  }
64
64
  let autoExit = !0
65
65
  switch (commands[0]) {
66
66
  case "push":
67
+ case "dev":
68
+ case "watch":
69
+ case "golf":
70
+ case "minify":
67
71
  {
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")) {
72
+ const noMinifyOption = popOption("no-minify", "skip-minify"),
73
+ mangleNamesOption = popOption("mangle-names"),
74
+ forceQuineCheatsOption = popOption("force-quine-cheats"),
75
+ noMinifyIncompatibleOption = mangleNamesOption || forceQuineCheatsOption
76
+ if (noMinifyOption && noMinifyIncompatibleOption) {
88
77
  logError(
89
- `Options ${colourN("--mangle-names")} and ${colourN(optionsHasNoMinify ? "--no-minify" : "--skip-minify")} are incompatible\n`
78
+ `Options ${colourN(noMinifyOption.name)} and ${colourN(noMinifyIncompatibleOption.name)} are incompatible\n`
90
79
  )
91
80
  logHelp()
92
- break
81
+ process.exit(1)
93
82
  }
94
- const shouldSkipMinify = options.get("no-minify") || options.get("skip-minify")
95
- let shouldMinify
96
- if (null != shouldSkipMinify) {
97
- if ("boolean" != typeof shouldSkipMinify) {
83
+ noMinifyOption && assertOptionIsBoolean(noMinifyOption)
84
+ mangleNamesOption && assertOptionIsBoolean(mangleNamesOption)
85
+ forceQuineCheatsOption && assertOptionIsBoolean(forceQuineCheatsOption)
86
+ if ("golf" == commands[0] || "minify" == commands[0]) {
87
+ const watchOption = popOption("watch"),
88
+ target = commands[1]
89
+ if (!target) {
90
+ logError("Must provide target\n")
91
+ logHelp()
92
+ process.exit(1)
93
+ }
94
+ const fileExtension = extname(target)
95
+ if (!supportedExtensions.includes(fileExtension)) {
98
96
  logError(
99
- `The value for ${colourN(optionsHasNoMinify ? "--no-minify" : "--skip-minify")} must be ${colourV("true")} or ${colourV("false")}\n`
97
+ `Unsupported file extension "${chalk.bold(fileExtension)}"\nSupported extensions are "${supportedExtensions.map(extension => chalk.bold(extension)).join('", "')}"`
100
98
  )
101
- logHelp()
102
- break
99
+ process.exit(1)
103
100
  }
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`)
101
+ const { processScript } = await processScriptModule,
102
+ fileBaseName = basename(target, fileExtension),
103
+ fileBaseNameEndsWithDotSrc = fileBaseName.endsWith(".src"),
104
+ scriptName = fileBaseNameEndsWithDotSrc ? fileBaseName.slice(0, -4) : fileBaseName,
105
+ scriptUser =
106
+ (
107
+ "scripts" == basename(resolve(target, "..")) &&
108
+ "hackmud" == basename(resolve(target, "../../.."))
109
+ ) ?
110
+ basename(resolve(target, "../.."))
111
+ : void 0
112
+ let outputPath =
113
+ commands[2] ||
114
+ resolve(
115
+ dirname(target),
116
+ fileBaseNameEndsWithDotSrc ? scriptName + ".js"
117
+ : ".js" == fileExtension ? fileBaseName + ".min.js"
118
+ : fileBaseName + ".js"
119
+ )
120
+ const golfFile = () =>
121
+ readFile(target, { encoding: "utf8" }).then(async source => {
122
+ const timeStart = performance.now(),
123
+ { script, warnings } = await processScript(source, {
124
+ minify: noMinifyOption && !noMinifyOption.value,
125
+ scriptUser,
126
+ scriptName,
127
+ filePath: target,
128
+ mangleNames: mangleNamesOption?.value,
129
+ forceQuineCheats: forceQuineCheatsOption?.value
130
+ }),
131
+ timeTook = performance.now() - timeStart
132
+ for (const { message, line } of warnings)
133
+ log(`Warning "${chalk.bold(message)}" on line ${chalk.bold(line + "")}`)
134
+ await writeFilePersistent(outputPath, script)
135
+ .catch(error => {
136
+ if (!commands[2] || "EISDIR" != error.code) throw error
137
+ outputPath = resolve(outputPath, basename(target, fileExtension) + ".js")
138
+ return writeFilePersistent(outputPath, script)
139
+ })
140
+ .then(() =>
141
+ log(
142
+ `Wrote ${chalk.bold(countHackmudCharacters(script))} chars to ${chalk.bold(relative(".", outputPath))} | took ${Math.round(100 * timeTook) / 100}ms`
143
+ )
144
+ )
145
+ })
146
+ if (watchOption) {
147
+ const { watch: watchFile } = await chokidarModule
148
+ watchFile(target, { awaitWriteFinish: { stabilityThreshold: 100 } })
149
+ .on("ready", () => log("Watching " + target))
150
+ .on("change", golfFile)
151
+ autoExit = !1
152
+ } else await golfFile()
153
+ } else {
154
+ const hackmudPath = getHackmudPath(),
155
+ sourcePath = commands[1]
156
+ if (!sourcePath) {
157
+ logError(`Must provide the directory to ${"push" == commands[0] ? "push from" : "watch"}\n`)
151
158
  logHelp()
152
- break
159
+ process.exit(1)
153
160
  }
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`
161
+ const scripts = commands.slice(2)
162
+ if (scripts.length) {
163
+ const invalidScript = scripts.find(
164
+ script => !/^(?:[a-z_][a-z\d_]{0,24}|\*)\.(?:[a-z_][a-z\d_]{0,24}|\*)$/.test(script)
169
165
  )
170
- logHelp()
171
- break
166
+ if (invalidScript) {
167
+ logError(`Invalid script name: ${JSON.stringify(invalidScript)}\n`)
168
+ logHelp()
169
+ process.exit(1)
170
+ }
171
+ } else scripts.push("*.*")
172
+ if ("push" == commands[0]) {
173
+ const { push, MissingSourceFolderError, MissingHackmudFolderError, NoUsersError } =
174
+ await pushModule,
175
+ infos = await push(sourcePath, hackmudPath, {
176
+ scripts,
177
+ onPush: info => logInfo(info, hackmudPath),
178
+ minify: noMinifyOption && !noMinifyOption.value,
179
+ mangleNames: mangleNamesOption?.value,
180
+ forceQuineCheats: forceQuineCheatsOption?.value
181
+ })
182
+ if (infos instanceof Error) {
183
+ logError(infos.message)
184
+ if (infos instanceof MissingSourceFolderError || infos instanceof NoUsersError) {
185
+ console.log()
186
+ logHelp()
187
+ } else
188
+ infos instanceof MissingHackmudFolderError &&
189
+ log(
190
+ `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`
191
+ )
192
+ } else infos.length || logError("Could not find any scripts to push")
193
+ } else {
194
+ const typeDeclarationPathOption = popOption(
195
+ "type-declaration-path",
196
+ "type-declaration",
197
+ "dts",
198
+ "gen-types"
199
+ ),
200
+ { watch } = await watchModule
201
+ watch(sourcePath, hackmudPath, {
202
+ scripts,
203
+ onPush: info => logInfo(info, hackmudPath),
204
+ typeDeclarationPath: typeDeclarationPathOption?.value.toString(),
205
+ minify: noMinifyOption && !noMinifyOption.value,
206
+ mangleNames: mangleNamesOption?.value,
207
+ onReady: () => log("Watching"),
208
+ forceQuineCheats: forceQuineCheatsOption?.value
209
+ })
210
+ autoExit = !1
172
211
  }
173
- shouldMinify = !shouldSkipMinify
174
212
  }
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
213
  }
208
214
  break
209
215
  case "pull":
@@ -213,7 +219,7 @@ switch (commands[0]) {
213
219
  if (!script) {
214
220
  logError("Must provide the script to pull\n")
215
221
  logHelp()
216
- break
222
+ process.exit(1)
217
223
  }
218
224
  const sourcePath = commands[2] || "."
219
225
  await pull(sourcePath, hackmudPath, script).catch(error => {
@@ -234,18 +240,19 @@ switch (commands[0]) {
234
240
  case "gen-dts":
235
241
  case "gen-types":
236
242
  {
237
- const target = commands[1]
243
+ const hackmudPath = getHackmudPath(),
244
+ target = commands[1]
238
245
  if (!target) {
239
246
  logError("Must provide target directory\n")
240
247
  logHelp()
241
- break
248
+ process.exit(1)
242
249
  }
243
250
  const sourcePath = resolve(target),
244
251
  outputPath = commands[2] || "./player.d.ts",
245
- typeDeclaration = await generateTypeDeclaration(sourcePath, getHackmudPath())
252
+ typeDeclaration = await generateTypeDeclaration(sourcePath, hackmudPath)
246
253
  let typeDeclarationPath = resolve(outputPath)
247
254
  await writeFile(typeDeclarationPath, typeDeclaration).catch(error => {
248
- assert(error instanceof Error, "src/bin/hsm.ts:327:35")
255
+ assert(error instanceof Error, "src/bin/hsm.ts:321:35")
249
256
  if ("EISDIR" != error.code) throw error
250
257
  typeDeclarationPath = resolve(typeDeclarationPath, "player.d.ts")
251
258
  return writeFile(typeDeclarationPath, typeDeclaration)
@@ -257,99 +264,6 @@ switch (commands[0]) {
257
264
  case "h":
258
265
  logHelp()
259
266
  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
267
  default:
354
268
  commands[0] && logError(`Unknown command: ${colourL(commands[0])}\n`)
355
269
  logHelp()
@@ -423,7 +337,7 @@ function logError(message) {
423
337
  process.exitCode = 1
424
338
  }
425
339
  function getHackmudPath() {
426
- const hackmudPathOption = options.get("hackmud-path")
340
+ const hackmudPathOption = popOption("hackmud-path")
427
341
  if (null != hackmudPathOption && "string" != typeof hackmudPathOption) {
428
342
  logError(`Option ${colourN("--hackmud-path")} must be a string, got ${colourV(hackmudPathOption)}\n`)
429
343
  logHelp()
@@ -435,3 +349,26 @@ function getHackmudPath() {
435
349
  ("win32" == process.platform ? resolve(process.env.APPDATA, "hackmud") : resolve(homedir(), ".config/hackmud"))
436
350
  )
437
351
  }
352
+ function assertOptionIsBoolean(option) {
353
+ if ("boolean" != typeof option.value) {
354
+ logError(`The value for ${colourN(option.name)} must be ${colourV("true")} or ${colourV("false")}\n`)
355
+ logHelp()
356
+ process.exit(1)
357
+ }
358
+ }
359
+ function popOption(...names) {
360
+ const presentOptionNames = names.filter(name => options.has(name))
361
+ if (!presentOptionNames.length) return
362
+ const presentOptionNamesWithDashDash = presentOptionNames.map(name =>
363
+ colourN(`-${1 == name.length ? "" : "-"}${name}`)
364
+ )
365
+ if (presentOptionNames.length > 1) {
366
+ logError(
367
+ `The options ${presentOptionNamesWithDashDash.join(", ")} are aliases for each other. Please only specify one`
368
+ )
369
+ process.exit(1)
370
+ }
371
+ const value = options.get(presentOptionNames[0])
372
+ options.delete(presentOptionNames[0])
373
+ return { name: presentOptionNamesWithDashDash[0], value }
374
+ }
package/env.d.ts CHANGED
@@ -1,18 +1,14 @@
1
- type Replace<T, R> = Omit<T, Extract<keyof R, keyof T>> & R
1
+ type Replace<A, B> = Omit<A, keyof B> & B
2
2
  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
6
 
7
- type AllOptional<T> = {
8
- [K in keyof T]-?: {} extends Pick<T, K> ? true : false
9
- }[keyof T]
7
+ type AllOptional<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? true : false }[keyof T]
10
8
 
11
9
  type Scriptor<Args = unknown, Ret = unknown> = {
12
- name: string,
13
- call: AllOptional<Args> extends true
14
- ? (args?: Args) => Ret
15
- : (args: Args) => Ret
10
+ name: string
11
+ call: AllOptional<Args> extends true ? (args?: Args) => Ret : (args: Args) => Ret
16
12
  }
17
13
 
18
14
  type Subscripts = Record<string, Record<string, (...args: any) => any>> & {
@@ -41,7 +37,7 @@ type UpgradeRarityString = "`0noob`" | "`1kiddie`" | "`2h4x0r`" | "`3h4rdc0r3`"
41
37
  type UpgradeRarityNumber = 0 | 1 | 2 | 3 | 4 | 5;
42
38
  type UpgradeRarity = UpgradeRarityString | UpgradeRarityNumber;
43
39
 
44
- type UpgradeCore = {
40
+ type UpgradeBase = {
45
41
  name: string
46
42
  type: "lock" | "script_space" | "chat" | "script" | "tool" | "bot_brain" | "glam"
47
43
  up_class?: -1 | 0 | 1 | 2 | 3
@@ -53,9 +49,9 @@ type UpgradeCore = {
53
49
  description: string
54
50
  }
55
51
 
56
- type Upgrade = UpgradeCore & Record<string, null | boolean | number | string>
52
+ type Upgrade = UpgradeBase & Record<string, null | boolean | number | string>
57
53
 
58
- type CLIUpgrade = Omit<UpgradeCore, `rarity`> & {
54
+ type CliUpgrade = Omit<UpgradeBase, `rarity`> & {
59
55
  [x: string]: null | boolean | number | string
60
56
  rarity: UpgradeRarityString
61
57
  }
@@ -155,7 +151,16 @@ type Fullsec = Subscripts & PlayerFullsec & {
155
151
  market: {
156
152
  /** **FULLSEC** */ browse: {
157
153
  (args:
158
- Partial<{ seller: string, listed_before: number, listed_after: number, cost: number | string } & Omit<CLIUpgrade, "rarity">>
154
+ Partial<{
155
+ seller: string | MongoQuerySelector<string>,
156
+ listed_before: number | MongoQuerySelector<number>,
157
+ listed_after: number,
158
+ cost: number | MongoQuerySelector<number> | string,
159
+ rarity: UpgradeRarityNumber | MongoQuerySelector<UpgradeRarityNumber>,
160
+ name: string | MongoQuerySelector<string>
161
+ } & Omit<{
162
+ [k in keyof CliUpgrade]: CliUpgrade[k] | MongoQuerySelector<CliUpgrade[k]>
163
+ }, "rarity">>
159
164
  ): { i: string, name: string, rarity: Upgrade["rarity"], cost: number }[] | ScriptFailure
160
165
 
161
166
  <I extends string>(args: { i: I }): {
@@ -357,7 +362,7 @@ type Fullsec = Subscripts & PlayerFullsec & {
357
362
  * const arr = [ 1, 2, 2, 3, 2 ]
358
363
  *
359
364
  * $D(uniq(arr)) // [ 1, 2, 3, 2 ] */
360
- uniq: (array: T[]) => T[]
365
+ uniq: <T>(array: T[]) => T[]
361
366
 
362
367
  /** Sorts an array of numbers or number-coercible strings in descending order. */
363
368
  u_sort_num_arr_desc: <T>(array: T[]) => T[]
@@ -424,17 +429,17 @@ type Fullsec = Subscripts & PlayerFullsec & {
424
429
  upgrades_of_owner: {
425
430
  <F extends Partial<Upgrade & { loaded: boolean }> = object>(args?: { filter?: F, full?: false }): (
426
431
  Omit<
427
- Pick<UpgradeCore, "tier" | "rarity" | "name" | "type" | "i" | "loaded">,
432
+ Pick<UpgradeBase, "tier" | "rarity" | "name" | "type" | "i" | "loaded">,
428
433
  keyof F
429
434
  > & Pick<F, "tier" | "rarity" | "name" | "type" | "i" | "loaded">
430
435
  )[] | ScriptFailure
431
436
 
432
437
  <F extends Partial<Upgrade & { loaded: boolean }> = object>(args: { filter?: F, full: true }): (
433
- Omit<UpgradeCore, keyof F> & F & Record<string, null | boolean | number | string>
438
+ Omit<UpgradeBase, keyof F> & F & Record<string, null | boolean | number | string>
434
439
  )[] | ScriptFailure
435
440
 
436
441
  <I extends number>(args: { i: I }): (
437
- Omit<UpgradeCore, "i"> & { [x: string]: null | boolean | number | string, i: I }
442
+ Omit<UpgradeBase, "i"> & { [x: string]: null | boolean | number | string, i: I }
438
443
  ) | ScriptFailure
439
444
  }
440
445
 
@@ -511,7 +516,7 @@ type Highsec = Fullsec & PlayerHighsec & {
511
516
  /** **HIGHSEC** */
512
517
  upgrades: {
513
518
  <I extends number>(args: { i: I }): (
514
- Omit<UpgradeCore, "i"> & { [x: string]: null | boolean | number | string, i: I }
519
+ Omit<UpgradeBase, "i"> & { [x: string]: null | boolean | number | string, i: I }
515
520
  ) | ScriptFailure
516
521
 
517
522
  <F extends Partial<Upgrade & { loaded: boolean }> = object>(args?: {
@@ -519,20 +524,20 @@ type Highsec = Fullsec & PlayerHighsec & {
519
524
  is_script?: true
520
525
  full?: false
521
526
  }): (
522
- Omit<Pick<UpgradeCore, "tier" | "rarity" | "name" | "type" | "i" | "loaded">, keyof F> & F &
527
+ Omit<Pick<UpgradeBase, "tier" | "rarity" | "name" | "type" | "i" | "loaded">, keyof F> & F &
523
528
  Record<string, null | boolean | number | string>
524
529
  )[] | ScriptFailure
525
530
 
526
531
  <F extends Partial<Upgrade & { loaded: boolean }> = object>(args?:
527
532
  { filter?: F, is_script?: true, full: true }
528
- ): (Omit<UpgradeCore, keyof F> & F & Record<string, null | boolean | number | string>)[] | ScriptFailure
533
+ ): (Omit<UpgradeBase, keyof F> & F & Record<string, null | boolean | number | string>)[] | ScriptFailure
529
534
 
530
535
  (args?: { filter?: Partial<Upgrade & { loaded: boolean }>, is_script: false, full?: false }):
531
536
  { msg: string, upgrades: string[] } | ScriptFailure
532
537
 
533
538
  <F extends Partial<Upgrade & { loaded: boolean }> = object>(
534
539
  args?: { filter?: F, is_script: false, full: true }
535
- ): (Omit<UpgradeCore, keyof F | `rarity`> & F & {
540
+ ): (Omit<UpgradeBase, keyof F | `rarity`> & F & {
536
541
  [x: string]: null | boolean | number | string
537
542
  rarity: UpgradeRarityString
538
543
  })[] | ScriptFailure
@@ -714,10 +719,50 @@ type Nullsec = Lowsec & PlayerNullsec & {
714
719
  }
715
720
  }
716
721
 
722
+ type MongoTypeString = "minKey" | "double" | "string" | "object" | "array" | "binData" | "undefined" | "objectId" |
723
+ "bool" | "date" | "null" | "regex" | "dbPointer" | "javascript" | "symbol" | "int" | "timestamp" | "long" | "decimal" | "maxKey";
724
+ type MongoTypeNumber = -1 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 127;
725
+
717
726
  type MongoValue = string | number | boolean | Date | MongoValue[] | { [key: string]: MongoValue } | null
718
727
 
719
728
  type MongoCommandValue = string | number | boolean | Date | MongoCommandValue[] | { [key: string]: MongoCommandValue } |
720
- null | undefined
729
+ null | undefined
730
+
731
+ /**
732
+ * Currently unused
733
+ */
734
+ type MongoLogicalSelectors<T extends MongoValue = MongoValue> = {
735
+ $not: T | MongoComparisonSelectors<T> | MongoLogicalSelectors<T>
736
+ $nor: T[]
737
+ $or: T[]
738
+ $and: T[]
739
+ }
740
+
741
+ type MongoArraySelectors<T extends Array<MongoValue> = Array<MongoValue>> = {
742
+ $all: T
743
+ $elemMatch: T
744
+ $size: number
745
+ }
746
+
747
+ type MongoComparisonSelectors<T extends MongoValue = MongoValue> = {
748
+ $eq: T
749
+ $gt: T
750
+ $gte: T
751
+ $in: T[]
752
+ $lt: T
753
+ $lte: T
754
+ $ne: T
755
+ $nin: T[]
756
+ }
757
+
758
+ type MongoElementSelectors = {
759
+ $exists: boolean
760
+ $type: MongoTypeNumber | MongoTypeString
761
+ }
762
+
763
+ type MongoQuerySelector<T extends MongoValue = MongoValue> = Partial<T extends MongoValue[] ?
764
+ (MongoArraySelectors<T> & MongoElementSelectors & MongoComparisonSelectors<T>) :
765
+ (MongoElementSelectors & MongoComparisonSelectors<T>)>
721
766
 
722
767
  type Query = { [key: string]: MongoValue | Query } & { _id?: Id, $in?: MongoValue[] }
723
768
  type Projection = Record<string, boolean | 0 | 1>
@@ -766,7 +811,7 @@ type Cursor = {
766
811
  ObjectId: () => any
767
812
  }
768
813
 
769
- type CLIContext = {
814
+ type CliContext = {
770
815
  /** The name of the user who is calling the script. */ caller: string
771
816
  /** The name of this script. */ this_script: string
772
817
  /** The number of columns in the caller’s terminal. */ cols: number
@@ -774,17 +819,23 @@ type CLIContext = {
774
819
 
775
820
  /** The name of the script that directly called this script, or null if called on the command line or as a
776
821
  * scriptor. */ calling_script: null
822
+ is_scriptor?: undefined
823
+ is_brain?: undefined
777
824
  }
778
825
 
779
- type SubscriptContext = Replace<CLIContext, {
826
+ type SubscriptContext = Replace<CliContext, {
780
827
  /** The name of the script that directly called this script, or null if called on the command line or as a scriptor.
781
828
  */
782
829
  calling_script: string
783
830
  }>
784
831
 
785
- type ScriptorContext = CLIContext & { /** Whether the script is being run as a scriptor. */ is_scriptor: true }
786
- type BrainContext = CLIContext & { /** Whether the script is being run via a bot brain. */ is_brain: true }
787
- type Context = CLIContext | SubscriptContext | ScriptorContext | BrainContext
832
+ type ScriptorContext =
833
+ Replace<CliContext, { /** Whether the script is being run as a scriptor. */ is_scriptor: true }>
834
+
835
+ type BrainContext =
836
+ Replace<CliContext, { /** Whether the script is being run via a bot brain. */ is_brain: true }>
837
+
838
+ type Context = CliContext | SubscriptContext | ScriptorContext | BrainContext
788
839
 
789
840
  /** Subscript space that can call FULLSEC scripts. */ declare const $fs: Fullsec
790
841
 
@@ -827,6 +878,8 @@ declare const $0s: typeof $ns
827
878
  * } */
828
879
  declare const $s: Nullsec
829
880
 
881
+ type ObjectId = { $oid: string }
882
+
830
883
  declare const $db: {
831
884
  /** Insert a document or documents into a collection.
832
885
  * @param documents A document or array of documents to insert into the collection. */
@@ -920,6 +973,8 @@ declare const $db: {
920
973
  signature: { hash: "Undefined Conversion", keyId: "Undefined Conversion" }
921
974
  }
922
975
  }
976
+
977
+ ObjectId: () => ObjectId
923
978
  }
924
979
 
925
980
  /** Debug Log.
@@ -956,7 +1011,7 @@ declare const $FMCL: undefined | true
956
1011
  * @example
957
1012
  * if (!$G.dbCache)
958
1013
  * $G.dbCache = $db.f({ whatever: true }).first() */
959
- declare const $G: any
1014
+ declare const $G: Record<string | symbol, any>
960
1015
 
961
1016
  /** This contains a JS timestamp (not Date) set immediately before your code begins running.
962
1017
  * @example
@@ -1005,3 +1060,14 @@ declare const _FULL_SCRIPT_NAME: string
1005
1060
  *
1006
1061
  * In rare cases where it's not known at build time, it's `-1`. */
1007
1062
  declare const _SECLEVEL: -1 | 0 | 1 | 2 | 3 | 4
1063
+
1064
+ type DeepFreeze<T> = { readonly [P in keyof T]: DeepFreeze<T[P]> }
1065
+
1066
+ /** Recursively
1067
+ * [`Object.freeze()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze)
1068
+ * an object and its properties' objects and its properties' objects and so on.
1069
+ *
1070
+ * [Official Hackmud Wiki](https://wiki.hackmud.com/scripting/extensions/deep_freeze) */
1071
+ declare const DEEP_FREEZE: <T>(value: T) => DeepFreeze<T>
1072
+
1073
+ declare const _RUN_ID: string
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hackmud-script-manager",
3
- "version": "0.20.4-d9a4cf1",
3
+ "version": "0.20.4-ec30de9",
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_`)
@@ -136,7 +127,8 @@ async function minify(file, { uniqueId = "00000000000", mangleNames = !1, forceQ
136
127
  const promises = []
137
128
  traverse(file, {
138
129
  FunctionDeclaration(path) {
139
- path.traverse({
130
+ const body = path.get("body")
131
+ body.traverse({
140
132
  Function(path) {
141
133
  "CallExpression" != path.parent.type && "callee" != path.parentKey && path.skip()
142
134
  },
@@ -152,7 +144,7 @@ async function minify(file, { uniqueId = "00000000000", mangleNames = !1, forceQ
152
144
  path.replaceWith(t.identifier(`_${uniqueId}_JSON_VALUE_${jsonValues.push(o) - 1}_`))
153
145
  }
154
146
  })
155
- path.traverse({
147
+ body.traverse({
156
148
  TemplateLiteral(path) {
157
149
  if ("TaggedTemplateExpression" == path.parent.type) return
158
150
  const templateLiteral = path.node
@@ -172,7 +164,7 @@ async function minify(file, { uniqueId = "00000000000", mangleNames = !1, forceQ
172
164
  },
173
165
  MemberExpression({ node: memberExpression }) {
174
166
  if (!memberExpression.computed) {
175
- assert("Identifier" == memberExpression.property.type, "src/processScript/minify.ts:259:62")
167
+ assert("Identifier" == memberExpression.property.type, "src/processScript/minify.ts:249:62")
176
168
  if (!(memberExpression.property.name.length < 3)) {
177
169
  memberExpression.computed = !0
178
170
  memberExpression.property = t.stringLiteral(memberExpression.property.name)
@@ -246,7 +238,7 @@ async function minify(file, { uniqueId = "00000000000", mangleNames = !1, forceQ
246
238
  })
247
239
  await Promise.all(promises)
248
240
  const functionDeclaration = file.program.body[0]
249
- assert("FunctionDeclaration" == functionDeclaration.type, "src/processScript/minify.ts:364:61")
241
+ assert("FunctionDeclaration" == functionDeclaration.type, "src/processScript/minify.ts:354:61")
250
242
  if (jsonValues.length) {
251
243
  hasComment = !0
252
244
  if (1 == jsonValues.length)
@@ -258,7 +250,10 @@ async function minify(file, { uniqueId = "00000000000", mangleNames = !1, forceQ
258
250
  t.memberExpression(
259
251
  t.taggedTemplateExpression(
260
252
  t.memberExpression(
261
- t.callExpression(t.identifier(`$${uniqueId}$SUBSCRIPT$scripts$quine$`), []),
253
+ t.callExpression(
254
+ t.identifier(`$${uniqueId}$4$SUBSCRIPT$scripts$quine$`),
255
+ []
256
+ ),
262
257
  t.identifier("split")
263
258
  ),
264
259
  t.templateLiteral([t.templateElement({ raw: "\t", cooked: "\t" }, !0)], [])
@@ -282,7 +277,7 @@ async function minify(file, { uniqueId = "00000000000", mangleNames = !1, forceQ
282
277
  t.memberExpression(
283
278
  t.taggedTemplateExpression(
284
279
  t.memberExpression(
285
- t.callExpression(t.identifier(`$${uniqueId}$SUBSCRIPT$scripts$quine$`), []),
280
+ t.callExpression(t.identifier(`$${uniqueId}$4$SUBSCRIPT$scripts$quine$`), []),
286
281
  t.identifier("split")
287
282
  ),
288
283
  t.templateLiteral([t.templateElement({ raw: "\t", cooked: "\t" }, !0)], [])
@@ -307,7 +302,7 @@ async function minify(file, { uniqueId = "00000000000", mangleNames = !1, forceQ
307
302
  t.memberExpression(
308
303
  t.taggedTemplateExpression(
309
304
  t.memberExpression(
310
- t.callExpression(t.identifier(`$${uniqueId}$SUBSCRIPT$scripts$quine$`), []),
305
+ t.callExpression(t.identifier(`$${uniqueId}$4$SUBSCRIPT$scripts$quine$`), []),
311
306
  t.identifier("split")
312
307
  ),
313
308
  t.templateLiteral([t.templateElement({ raw: "\t", cooked: "\t" }, !0)], [])
@@ -363,7 +358,7 @@ async function minify(file, { uniqueId = "00000000000", mangleNames = !1, forceQ
363
358
  )
364
359
  }
365
360
  if (1 == forceQuineCheats) return code
366
- assert(scriptBeforeJSONValueReplacement, "src/processScript/minify.ts:495:43")
361
+ assert(scriptBeforeJSONValueReplacement, "src/processScript/minify.ts:485:43")
367
362
  return (
368
363
  countHackmudCharacters(scriptBeforeJSONValueReplacement) <=
369
364
  countHackmudCharacters(code) + Number(hasComment)
@@ -379,7 +374,7 @@ function parseObjectExpression(node, o) {
379
374
  "Identifier" == property.key.type ||
380
375
  "NumericLiteral" == property.key.type ||
381
376
  "StringLiteral" == property.key.type,
382
- "src/processScript/minify.ts:517:4"
377
+ "src/processScript/minify.ts:507:4"
383
378
  )
384
379
  if ("ArrayExpression" == property.value.type) {
385
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")
@@ -101,29 +101,29 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
101
101
  functionDotPrototypeIsReferencedMultipleTimes = !0
102
102
  }
103
103
  }
104
- const neededSubscriptLets = new Set()
104
+ const neededSubscriptLets = new Map()
105
105
  let detectedSeclevel = 4
106
106
  for (const fakeSubscriptObjectName of ["$fs", "$4s", "$s"])
107
- program.scope.hasGlobal(fakeSubscriptObjectName) && processFakeSubscriptObject(fakeSubscriptObjectName)
107
+ program.scope.hasGlobal(fakeSubscriptObjectName) && processFakeSubscriptObject(fakeSubscriptObjectName, 4)
108
108
  for (const fakeSubscriptObjectName of ["$hs", "$3s"])
109
109
  if (program.scope.hasGlobal(fakeSubscriptObjectName)) {
110
110
  detectedSeclevel = 3
111
- processFakeSubscriptObject(fakeSubscriptObjectName)
111
+ processFakeSubscriptObject(fakeSubscriptObjectName, 3)
112
112
  }
113
113
  for (const fakeSubscriptObjectName of ["$ms", "$2s"])
114
114
  if (program.scope.hasGlobal(fakeSubscriptObjectName)) {
115
115
  detectedSeclevel = 2
116
- processFakeSubscriptObject(fakeSubscriptObjectName)
116
+ processFakeSubscriptObject(fakeSubscriptObjectName, 2)
117
117
  }
118
118
  for (const fakeSubscriptObjectName of ["$ls", "$1s"])
119
119
  if (program.scope.hasGlobal(fakeSubscriptObjectName)) {
120
120
  detectedSeclevel = 1
121
- processFakeSubscriptObject(fakeSubscriptObjectName)
121
+ processFakeSubscriptObject(fakeSubscriptObjectName, 1)
122
122
  }
123
123
  for (const fakeSubscriptObjectName of ["$ns", "$0s"])
124
124
  if (program.scope.hasGlobal(fakeSubscriptObjectName)) {
125
125
  detectedSeclevel = 0
126
- processFakeSubscriptObject(fakeSubscriptObjectName)
126
+ processFakeSubscriptObject(fakeSubscriptObjectName, 0)
127
127
  }
128
128
  seclevel = Math.min(seclevel, detectedSeclevel)
129
129
  const neededDbMethodLets = new Set()
@@ -157,9 +157,10 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
157
157
  if (program.scope.hasGlobal("$FMCL"))
158
158
  for (const referencePath of getReferencePathsToGlobal("$FMCL", program))
159
159
  referencePath.replaceWith(t.identifier(`$${uniqueId}$FMCL$`))
160
- if (program.scope.hasGlobal("$G"))
160
+ let needG = program.scope.hasGlobal("$G")
161
+ if (needG)
161
162
  for (const referencePath of getReferencePathsToGlobal("$G", program))
162
- referencePath.replaceWith(t.identifier(`$${uniqueId}$GLOBAL$`))
163
+ referencePath.replaceWith(t.identifier(`_${uniqueId}_G_`))
163
164
  if (program.scope.hasGlobal("_SECLEVEL"))
164
165
  for (const referencePath of getReferencePathsToGlobal("_SECLEVEL", program))
165
166
  referencePath.replaceWith(t.numericLiteral(seclevel))
@@ -167,7 +168,7 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
167
168
  if (program.scope.hasGlobal("Object"))
168
169
  for (const referencePath of getReferencePathsToGlobal("Object", program))
169
170
  if ("MemberExpression" == referencePath.parent.type && !referencePath.parent.computed) {
170
- assert("Identifier" == referencePath.parent.property.type, "src/processScript/transform.ts:241:64")
171
+ assert("Identifier" == referencePath.parent.property.type, "src/processScript/transform.ts:243:64")
171
172
  if ("getPrototypeOf" == referencePath.parent.property.name) {
172
173
  referencePath.parentPath.replaceWith(t.identifier(`_${uniqueId}_GET_PROTOTYPE_OF_`))
173
174
  needGetPrototypeOf = !0
@@ -177,7 +178,7 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
177
178
  if (program.scope.hasGlobal("console"))
178
179
  for (const referencePath of getReferencePathsToGlobal("console", program))
179
180
  if ("MemberExpression" == referencePath.parent.type && !referencePath.parent.computed) {
180
- assert("Identifier" == referencePath.parent.property.type, "src/processScript/transform.ts:256:64")
181
+ assert("Identifier" == referencePath.parent.property.type, "src/processScript/transform.ts:258:64")
181
182
  referencePath.parentPath.replaceWith(
182
183
  t.identifier(`_${uniqueId}_CONSOLE_METHOD_${referencePath.parent.property.name}_`)
183
184
  )
@@ -185,13 +186,13 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
185
186
  }
186
187
  const lastStatement = program.node.body.at(-1)
187
188
  let exportDefaultName
188
- assert(lastStatement, "src/processScript/transform.ts:270:27 program is empty")
189
+ assert(lastStatement, "src/processScript/transform.ts:272:27 program is empty")
189
190
  if ("ExportNamedDeclaration" == lastStatement.type) {
190
191
  program.node.body.pop()
191
192
  for (const specifier of lastStatement.specifiers) {
192
193
  assert(
193
194
  "ExportSpecifier" == specifier.type,
194
- `src/processScript/transform.ts:276:51 ${specifier.type} is currently unsupported`
195
+ `src/processScript/transform.ts:278:51 ${specifier.type} is currently unsupported`
195
196
  )
196
197
  const exportedName =
197
198
  "Identifier" == specifier.exported.type ? specifier.exported.name : specifier.exported.value
@@ -314,11 +315,11 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
314
315
  let hoistedGlobalBlockFunctions = 0
315
316
  for (const [globalBlockIndex, globalBlockStatement] of [...globalBlock.body.entries()].reverse())
316
317
  if ("VariableDeclaration" == globalBlockStatement.type) {
317
- assert(1 == globalBlockStatement.declarations.length, "src/processScript/transform.ts:410:59")
318
+ assert(1 == globalBlockStatement.declarations.length, "src/processScript/transform.ts:412:59")
318
319
  const declarator = globalBlockStatement.declarations[0]
319
320
  assert(
320
321
  "Identifier" == declarator.id.type,
321
- `src/processScript/transform.ts:414:51 declarator.id.type was "${declarator.id.type}"`
322
+ `src/processScript/transform.ts:416:51 declarator.id.type was "${declarator.id.type}"`
322
323
  )
323
324
  program.scope.crawl()
324
325
  if (program.scope.hasGlobal(declarator.id.name)) {
@@ -333,15 +334,16 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
333
334
  Object.keys(program.scope.globals).some(global => globalBlockVariables.has(global))
334
335
  ) {
335
336
  const binding = program.scope.getBinding(declarator.id.name)
336
- assert(binding, "src/processScript/transform.ts:433:23")
337
+ assert(binding, "src/processScript/transform.ts:435:23")
337
338
  for (const referencePath of binding.referencePaths) {
338
- assert("Identifier" == referencePath.node.type, "src/processScript/transform.ts:436:56")
339
+ assert("Identifier" == referencePath.node.type, "src/processScript/transform.ts:438:56")
339
340
  referencePath.replaceWith(
340
341
  t.memberExpression(
341
- t.identifier(`$${uniqueId}$GLOBAL$`),
342
+ t.identifier(`_${uniqueId}_G_`),
342
343
  t.identifier(referencePath.node.name)
343
344
  )
344
345
  )
346
+ needG = !0
345
347
  }
346
348
  for (const referencePath of binding.constantViolations)
347
349
  if ("AssignmentExpression" == referencePath.node.type)
@@ -350,12 +352,13 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
350
352
  clearObject(node)
351
353
  Object.assign(
352
354
  node,
353
- t.memberExpression(t.identifier(`$${uniqueId}$GLOBAL$`), t.identifier(name))
355
+ t.memberExpression(t.identifier(`_${uniqueId}_G_`), t.identifier(name))
354
356
  )
357
+ needG = !0
355
358
  }
356
359
  globalBlockPath.remove()
357
360
  globalBlockStatementPath.remove()
358
- declarator.init &&
361
+ if (declarator.init) {
359
362
  globalBlock.body.splice(
360
363
  globalBlockIndex,
361
364
  0,
@@ -363,13 +366,15 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
363
366
  t.assignmentExpression(
364
367
  "=",
365
368
  t.memberExpression(
366
- t.identifier(`$${uniqueId}$GLOBAL$`),
369
+ t.identifier(`_${uniqueId}_G_`),
367
370
  t.identifier(declarator.id.name)
368
371
  ),
369
372
  declarator.init
370
373
  )
371
374
  )
372
375
  )
376
+ needG = !0
377
+ }
373
378
  } else {
374
379
  globalBlockPath.remove()
375
380
  globalBlockStatementPath.remove()
@@ -379,22 +384,20 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
379
384
  } else globalBlockVariables.add(declarator.id.name)
380
385
  } else if ("ClassDeclaration" == globalBlockStatement.type) {
381
386
  program.scope.crawl()
382
- assert(globalBlockStatement.id, "src/processScript/transform.ts:487:37")
387
+ assert(globalBlockStatement.id, "src/processScript/transform.ts:495:37")
383
388
  if (program.scope.hasGlobal(globalBlockStatement.id.name)) {
384
389
  globalBlock.body.splice(globalBlockIndex, 1)
385
390
  const [globalBlockPath] = program.unshiftContainer("body", globalBlock),
386
391
  [globalBlockStatementPath] = program.unshiftContainer("body", globalBlockStatement)
387
392
  program.scope.crawl()
388
393
  const binding = program.scope.getBinding(globalBlockStatement.id.name)
389
- assert(binding, "src/processScript/transform.ts:499:22")
394
+ assert(binding, "src/processScript/transform.ts:507:22")
390
395
  for (const referencePath of binding.referencePaths) {
391
- assert("Identifier" == referencePath.node.type, "src/processScript/transform.ts:502:55")
396
+ assert("Identifier" == referencePath.node.type, "src/processScript/transform.ts:510:55")
392
397
  referencePath.replaceWith(
393
- t.memberExpression(
394
- t.identifier(`$${uniqueId}$GLOBAL$`),
395
- t.identifier(referencePath.node.name)
396
- )
398
+ t.memberExpression(t.identifier(`_${uniqueId}_G_`), t.identifier(referencePath.node.name))
397
399
  )
400
+ needG = !0
398
401
  }
399
402
  globalBlockPath.remove()
400
403
  globalBlockStatementPath.remove()
@@ -405,7 +408,7 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
405
408
  t.assignmentExpression(
406
409
  "=",
407
410
  t.memberExpression(
408
- t.identifier(`$${uniqueId}$GLOBAL$`),
411
+ t.identifier(`_${uniqueId}_G_`),
409
412
  t.identifier(globalBlockStatement.id.name)
410
413
  ),
411
414
  t.classExpression(
@@ -417,6 +420,7 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
417
420
  )
418
421
  )
419
422
  )
423
+ needG = !0
420
424
  }
421
425
  }
422
426
  if (program.scope.hasGlobal("_EXPORTS"))
@@ -522,12 +526,12 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
522
526
  mainFunction.body.body.unshift(
523
527
  t.variableDeclaration(
524
528
  "let",
525
- [...neededSubscriptLets].map(name =>
529
+ [...neededSubscriptLets].map(([name, seclevel]) =>
526
530
  t.variableDeclarator(
527
531
  t.identifier(`_${uniqueId}_SUBSCRIPT_${name}_`),
528
532
  t.arrowFunctionExpression(
529
533
  [t.restElement(t.identifier("args"))],
530
- t.callExpression(t.identifier(`$${uniqueId}$SUBSCRIPT$${name}$`), [
534
+ t.callExpression(t.identifier(`$${uniqueId}$${seclevel}$SUBSCRIPT$${name}$`), [
531
535
  t.spreadElement(t.identifier("args"))
532
536
  ])
533
537
  )
@@ -535,6 +539,12 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
535
539
  )
536
540
  )
537
541
  )
542
+ needG &&
543
+ mainFunction.body.body.unshift(
544
+ t.variableDeclaration("let", [
545
+ t.variableDeclarator(t.identifier(`_${uniqueId}_G_`), t.identifier(`$${uniqueId}$GLOBAL$`))
546
+ ])
547
+ )
538
548
  traverse(file, {
539
549
  BlockStatement({ node: blockStatement }) {
540
550
  for (const [index, functionDeclaration] of blockStatement.body.entries())
@@ -555,7 +565,7 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
555
565
  }
556
566
  },
557
567
  ClassBody({ node: classBody, scope, parent }) {
558
- assert(t.isClass(parent), "src/processScript/transform.ts:669:30")
568
+ assert(t.isClass(parent), "src/processScript/transform.ts:688:30")
559
569
  let thisIsReferenced = !1
560
570
  for (const classMethod of classBody.body) {
561
571
  if ("ClassMethod" != classMethod.type) continue
@@ -661,36 +671,37 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
661
671
  t.identifier("prototype")
662
672
  )
663
673
  }
664
- function processFakeSubscriptObject(fakeSubscriptObjectName) {
674
+ function processFakeSubscriptObject(fakeSubscriptObjectName, seclevel) {
665
675
  for (const referencePath of getReferencePathsToGlobal(fakeSubscriptObjectName, program)) {
666
- assert("MemberExpression" == referencePath.parent.type, "src/processScript/transform.ts:785:60")
676
+ assert("MemberExpression" == referencePath.parent.type, "src/processScript/transform.ts:804:60")
667
677
  assert("Identifier" == referencePath.parent.property.type)
668
678
  assert(
669
679
  "MemberExpression" == referencePath.parentPath.parentPath?.node.type,
670
- "src/processScript/transform.ts:787:81"
680
+ "src/processScript/transform.ts:806:81"
671
681
  )
672
682
  assert(
673
683
  "Identifier" == referencePath.parentPath.parentPath.node.property.type,
674
- "src/processScript/transform.ts:788:83"
684
+ "src/processScript/transform.ts:807:83"
675
685
  )
676
686
  assert(
677
687
  /^[_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`
688
+ `src/processScript/transform.ts:811:8 invalid user "${referencePath.parent.property.name}" in subscript`
679
689
  )
680
690
  assert(
681
691
  /^[_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`
692
+ `src/processScript/transform.ts:816:8 invalid script name "${referencePath.parentPath.parentPath.node.property.name}" in subscript`
683
693
  )
684
694
  if ("CallExpression" == referencePath.parentPath.parentPath.parentPath?.type)
685
695
  referencePath.parentPath.parentPath.replaceWith(
686
696
  t.identifier(
687
- `$${uniqueId}$SUBSCRIPT$${referencePath.parent.property.name}$${referencePath.parentPath.parentPath.node.property.name}$`
697
+ `$${uniqueId}$${seclevel}$SUBSCRIPT$${referencePath.parent.property.name}$${referencePath.parentPath.parentPath.node.property.name}$`
688
698
  )
689
699
  )
690
700
  else {
691
701
  const name = `${referencePath.parent.property.name}$${referencePath.parentPath.parentPath.node.property.name}`
692
702
  referencePath.parentPath.parentPath.replaceWith(t.identifier(`_${uniqueId}_SUBSCRIPT_${name}_`))
693
- neededSubscriptLets.add(name)
703
+ const maxSecLevel = Math.max(neededSubscriptLets.get(name) || 0, seclevel)
704
+ neededSubscriptLets.set(name, maxSecLevel)
694
705
  }
695
706
  }
696
707
  }
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,32 +42,54 @@ 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),
61
79
  ...hackmudFolder
62
80
  .filter(({ stats, name }) => stats.isFile() && name.endsWith(".key"))
63
81
  .map(({ name }) => name.slice(0, -4))
64
- ]),
65
- usersToScriptsToPush = new Cache(_user => new Map()),
82
+ ])
83
+ if (!allUsers.size)
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
+ )
87
+ const usersToScriptsToPush = new Cache(_user => new Map()),
66
88
  scriptNamesToUsers = new Cache(_scriptName => new Set())
67
89
  for (const script of scripts) {
68
90
  const [user, scriptName] = script.split(".")
69
- assert(user, "src/push.ts:69:16")
70
- assert(scriptName, "src/push.ts:70:22")
91
+ assert(user, "src/push.ts:105:16")
92
+ assert(scriptName, "src/push.ts:106:22")
71
93
  "*" == user ? scriptNamesToUsers.set(scriptName, allUsers) : scriptNamesToUsers.get(scriptName).add(user)
72
94
  }
73
95
  const sourceFolderFiles = sourceFolder.filter(({ stats }) => stats.isFile()),
@@ -98,7 +120,7 @@ async function push(
98
120
  for (const [scriptName, users] of scriptNamesToUsers)
99
121
  for (const user of users)
100
122
  if (!usersToScriptsToPush.get(user).has(scriptName))
101
- throw Error(`Could not find script ${user}.${scriptName} to push`)
123
+ return new NoScriptsError(`Could not find script ${user}.${scriptName} to push`)
102
124
  const pathsToUsers = new Cache(_path => new Set())
103
125
  for (const [user, scriptsToPush] of usersToScriptsToPush)
104
126
  for (const path of scriptsToPush.values()) pathsToUsers.get(path).add(user)
@@ -135,4 +157,4 @@ async function push(
135
157
  )
136
158
  return allInfo
137
159
  }
138
- export { push }
160
+ export { MissingHackmudFolderError, MissingSourceFolderError, NoScriptsError, NoUsersError, push }