hackmud-script-manager 0.20.4-abe4703 → 0.20.4-b779329

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-abe4703",
15
+ const version = "0.20.4-b779329",
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
@@ -4,6 +4,13 @@ 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> = { [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
+ }
13
+
7
14
  type Subscripts = Record<string, Record<string, (...args: any) => any>> & {
8
15
  accts: ErrorScripts
9
16
  autos: ErrorScripts
@@ -26,23 +33,27 @@ interface PlayerMidsec {}
26
33
  interface PlayerLowsec {}
27
34
  interface PlayerNullsec {}
28
35
 
29
- type UpgradeCore = {
36
+ type UpgradeRarityString = "`0noob`" | "`1kiddie`" | "`2h4x0r`" | "`3h4rdc0r3`" | "`4|_|b3|2`" | "`531337`"
37
+ type UpgradeRarityNumber = 0 | 1 | 2 | 3 | 4 | 5;
38
+ type UpgradeRarity = UpgradeRarityString | UpgradeRarityNumber;
39
+
40
+ type UpgradeBase = {
30
41
  name: string
31
42
  type: "lock" | "script_space" | "chat" | "script" | "tool" | "bot_brain" | "glam"
32
43
  up_class?: -1 | 0 | 1 | 2 | 3
33
44
  tier: 1 | 2 | 3 | 4
34
- rarity: 0 | 1 | 2 | 3 | 4 | 5
45
+ rarity: UpgradeRarityNumber
35
46
  i: number
36
47
  loaded: boolean
37
48
  sn: string
38
49
  description: string
39
50
  }
40
51
 
41
- type Upgrade = UpgradeCore & Record<string, null | boolean | number | string>
52
+ type Upgrade = UpgradeBase & Record<string, null | boolean | number | string>
42
53
 
43
- type CLIUpgrade = Omit<UpgradeCore, `rarity`> & {
54
+ type CliUpgrade = Omit<UpgradeBase, `rarity`> & {
44
55
  [x: string]: null | boolean | number | string
45
- rarity: "`0noob`" | "`1kiddie`" | "`2h4x0r`" | "`3h4rdc0r3`" | "`4|_|b3|2`" | "`531337`"
56
+ rarity: UpgradeRarityString
46
57
  }
47
58
 
48
59
  type UsersTopItem<R> = { rank: R, name: string, last_activity: string, balance: string }
@@ -140,7 +151,16 @@ type Fullsec = Subscripts & PlayerFullsec & {
140
151
  market: {
141
152
  /** **FULLSEC** */ browse: {
142
153
  (args:
143
- 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">>
144
164
  ): { i: string, name: string, rarity: Upgrade["rarity"], cost: number }[] | ScriptFailure
145
165
 
146
166
  <I extends string>(args: { i: I }): {
@@ -409,17 +429,17 @@ type Fullsec = Subscripts & PlayerFullsec & {
409
429
  upgrades_of_owner: {
410
430
  <F extends Partial<Upgrade & { loaded: boolean }> = object>(args?: { filter?: F, full?: false }): (
411
431
  Omit<
412
- Pick<UpgradeCore, "tier" | "rarity" | "name" | "type" | "i" | "loaded">,
432
+ Pick<UpgradeBase, "tier" | "rarity" | "name" | "type" | "i" | "loaded">,
413
433
  keyof F
414
434
  > & Pick<F, "tier" | "rarity" | "name" | "type" | "i" | "loaded">
415
435
  )[] | ScriptFailure
416
436
 
417
437
  <F extends Partial<Upgrade & { loaded: boolean }> = object>(args: { filter?: F, full: true }): (
418
- Omit<UpgradeCore, keyof F> & F & Record<string, null | boolean | number | string>
438
+ Omit<UpgradeBase, keyof F> & F & Record<string, null | boolean | number | string>
419
439
  )[] | ScriptFailure
420
440
 
421
441
  <I extends number>(args: { i: I }): (
422
- Omit<UpgradeCore, "i"> & { [x: string]: null | boolean | number | string, i: I }
442
+ Omit<UpgradeBase, "i"> & { [x: string]: null | boolean | number | string, i: I }
423
443
  ) | ScriptFailure
424
444
  }
425
445
 
@@ -496,7 +516,7 @@ type Highsec = Fullsec & PlayerHighsec & {
496
516
  /** **HIGHSEC** */
497
517
  upgrades: {
498
518
  <I extends number>(args: { i: I }): (
499
- Omit<UpgradeCore, "i"> & { [x: string]: null | boolean | number | string, i: I }
519
+ Omit<UpgradeBase, "i"> & { [x: string]: null | boolean | number | string, i: I }
500
520
  ) | ScriptFailure
501
521
 
502
522
  <F extends Partial<Upgrade & { loaded: boolean }> = object>(args?: {
@@ -504,22 +524,22 @@ type Highsec = Fullsec & PlayerHighsec & {
504
524
  is_script?: true
505
525
  full?: false
506
526
  }): (
507
- 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 &
508
528
  Record<string, null | boolean | number | string>
509
529
  )[] | ScriptFailure
510
530
 
511
531
  <F extends Partial<Upgrade & { loaded: boolean }> = object>(args?:
512
532
  { filter?: F, is_script?: true, full: true }
513
- ): (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
514
534
 
515
535
  (args?: { filter?: Partial<Upgrade & { loaded: boolean }>, is_script: false, full?: false }):
516
536
  { msg: string, upgrades: string[] } | ScriptFailure
517
537
 
518
538
  <F extends Partial<Upgrade & { loaded: boolean }> = object>(
519
539
  args?: { filter?: F, is_script: false, full: true }
520
- ): (Omit<UpgradeCore, keyof F | `rarity`> & F & {
540
+ ): (Omit<UpgradeBase, keyof F | `rarity`> & F & {
521
541
  [x: string]: null | boolean | number | string
522
- rarity: "`0noob`" | "`1kiddie`" | "`2h4x0r`" | "`3h4rdc0r3`" | "`4|_|b3|2`" | "`531337`"
542
+ rarity: UpgradeRarityString
523
543
  })[] | ScriptFailure
524
544
  }
525
545
  }
@@ -699,10 +719,50 @@ type Nullsec = Lowsec & PlayerNullsec & {
699
719
  }
700
720
  }
701
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
+
702
726
  type MongoValue = string | number | boolean | Date | MongoValue[] | { [key: string]: MongoValue } | null
703
727
 
704
728
  type MongoCommandValue = string | number | boolean | Date | MongoCommandValue[] | { [key: string]: MongoCommandValue } |
705
- 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>)>
706
766
 
707
767
  type Query = { [key: string]: MongoValue | Query } & { _id?: Id, $in?: MongoValue[] }
708
768
  type Projection = Record<string, boolean | 0 | 1>
@@ -751,7 +811,7 @@ type Cursor = {
751
811
  ObjectId: () => any
752
812
  }
753
813
 
754
- type CLIContext = {
814
+ type CliContext = {
755
815
  /** The name of the user who is calling the script. */ caller: string
756
816
  /** The name of this script. */ this_script: string
757
817
  /** The number of columns in the caller’s terminal. */ cols: number
@@ -759,17 +819,23 @@ type CLIContext = {
759
819
 
760
820
  /** The name of the script that directly called this script, or null if called on the command line or as a
761
821
  * scriptor. */ calling_script: null
822
+ is_scriptor?: undefined
823
+ is_brain?: undefined
762
824
  }
763
825
 
764
- type SubscriptContext = Replace<CLIContext, {
826
+ type SubscriptContext = Replace<CliContext, {
765
827
  /** The name of the script that directly called this script, or null if called on the command line or as a scriptor.
766
828
  */
767
829
  calling_script: string
768
830
  }>
769
831
 
770
- type ScriptorContext = CLIContext & { /** Whether the script is being run as a scriptor. */ is_scriptor: true }
771
- type BrainContext = CLIContext & { /** Whether the script is being run via a bot brain. */ is_brain: true }
772
- 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
773
839
 
774
840
  /** Subscript space that can call FULLSEC scripts. */ declare const $fs: Fullsec
775
841
 
@@ -812,6 +878,8 @@ declare const $0s: typeof $ns
812
878
  * } */
813
879
  declare const $s: Nullsec
814
880
 
881
+ type ObjectId = { $oid: string }
882
+
815
883
  declare const $db: {
816
884
  /** Insert a document or documents into a collection.
817
885
  * @param documents A document or array of documents to insert into the collection. */
@@ -905,6 +973,8 @@ declare const $db: {
905
973
  signature: { hash: "Undefined Conversion", keyId: "Undefined Conversion" }
906
974
  }
907
975
  }
976
+
977
+ ObjectId: () => ObjectId
908
978
  }
909
979
 
910
980
  /** Debug Log.
@@ -941,7 +1011,7 @@ declare const $FMCL: undefined | true
941
1011
  * @example
942
1012
  * if (!$G.dbCache)
943
1013
  * $G.dbCache = $db.f({ whatever: true }).first() */
944
- declare const $G: any
1014
+ declare const $G: Record<string | symbol, any>
945
1015
 
946
1016
  /** This contains a JS timestamp (not Date) set immediately before your code begins running.
947
1017
  * @example
@@ -990,3 +1060,14 @@ declare const _FULL_SCRIPT_NAME: string
990
1060
  *
991
1061
  * In rare cases where it's not known at build time, it's `-1`. */
992
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-abe4703",
3
+ "version": "0.20.4-b779329",
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'
@@ -259,7 +259,10 @@ async function minify(file, { uniqueId = "00000000000", mangleNames = !1, forceQ
259
259
  t.memberExpression(
260
260
  t.taggedTemplateExpression(
261
261
  t.memberExpression(
262
- t.callExpression(t.identifier(`$${uniqueId}$SUBSCRIPT$scripts$quine$`), []),
262
+ t.callExpression(
263
+ t.identifier(`$${uniqueId}$4$SUBSCRIPT$scripts$quine$`),
264
+ []
265
+ ),
263
266
  t.identifier("split")
264
267
  ),
265
268
  t.templateLiteral([t.templateElement({ raw: "\t", cooked: "\t" }, !0)], [])
@@ -283,7 +286,7 @@ async function minify(file, { uniqueId = "00000000000", mangleNames = !1, forceQ
283
286
  t.memberExpression(
284
287
  t.taggedTemplateExpression(
285
288
  t.memberExpression(
286
- t.callExpression(t.identifier(`$${uniqueId}$SUBSCRIPT$scripts$quine$`), []),
289
+ t.callExpression(t.identifier(`$${uniqueId}$4$SUBSCRIPT$scripts$quine$`), []),
287
290
  t.identifier("split")
288
291
  ),
289
292
  t.templateLiteral([t.templateElement({ raw: "\t", cooked: "\t" }, !0)], [])
@@ -308,7 +311,7 @@ async function minify(file, { uniqueId = "00000000000", mangleNames = !1, forceQ
308
311
  t.memberExpression(
309
312
  t.taggedTemplateExpression(
310
313
  t.memberExpression(
311
- t.callExpression(t.identifier(`$${uniqueId}$SUBSCRIPT$scripts$quine$`), []),
314
+ t.callExpression(t.identifier(`$${uniqueId}$4$SUBSCRIPT$scripts$quine$`), []),
312
315
  t.identifier("split")
313
316
  ),
314
317
  t.templateLiteral([t.templateElement({ raw: "\t", cooked: "\t" }, !0)], [])
@@ -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()
@@ -522,12 +522,12 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
522
522
  mainFunction.body.body.unshift(
523
523
  t.variableDeclaration(
524
524
  "let",
525
- [...neededSubscriptLets].map(name =>
525
+ [...neededSubscriptLets].map(([name, seclevel]) =>
526
526
  t.variableDeclarator(
527
527
  t.identifier(`_${uniqueId}_SUBSCRIPT_${name}_`),
528
528
  t.arrowFunctionExpression(
529
529
  [t.restElement(t.identifier("args"))],
530
- t.callExpression(t.identifier(`$${uniqueId}$SUBSCRIPT$${name}$`), [
530
+ t.callExpression(t.identifier(`$${uniqueId}$${seclevel}$SUBSCRIPT$${name}$`), [
531
531
  t.spreadElement(t.identifier("args"))
532
532
  ])
533
533
  )
@@ -661,7 +661,7 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
661
661
  t.identifier("prototype")
662
662
  )
663
663
  }
664
- function processFakeSubscriptObject(fakeSubscriptObjectName) {
664
+ function processFakeSubscriptObject(fakeSubscriptObjectName, seclevel) {
665
665
  for (const referencePath of getReferencePathsToGlobal(fakeSubscriptObjectName, program)) {
666
666
  assert("MemberExpression" == referencePath.parent.type, "src/processScript/transform.ts:785:60")
667
667
  assert("Identifier" == referencePath.parent.property.type)
@@ -684,13 +684,14 @@ function transform(file, sourceCode, { uniqueId = "00000000000", scriptUser, scr
684
684
  if ("CallExpression" == referencePath.parentPath.parentPath.parentPath?.type)
685
685
  referencePath.parentPath.parentPath.replaceWith(
686
686
  t.identifier(
687
- `$${uniqueId}$SUBSCRIPT$${referencePath.parent.property.name}$${referencePath.parentPath.parentPath.node.property.name}$`
687
+ `$${uniqueId}$${seclevel}$SUBSCRIPT$${referencePath.parent.property.name}$${referencePath.parentPath.parentPath.node.property.name}$`
688
688
  )
689
689
  )
690
690
  else {
691
691
  const name = `${referencePath.parent.property.name}$${referencePath.parentPath.parentPath.node.property.name}`
692
692
  referencePath.parentPath.parentPath.replaceWith(t.identifier(`_${uniqueId}_SUBSCRIPT_${name}_`))
693
- neededSubscriptLets.add(name)
693
+ const maxSecLevel = Math.max(neededSubscriptLets.get(name) || 0, seclevel)
694
+ neededSubscriptLets.set(name, maxSecLevel)
694
695
  }
695
696
  }
696
697
  }
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 }