hackmud-script-manager 0.19.0-50a29ed → 0.19.0-7c69a3b

Sign up to get free protection for your applications and to get access to all the features.
package/bin/hsm.js CHANGED
@@ -1,2 +1,683 @@
1
1
  #!/usr/bin/env node
2
- import{readFile as e,writeFile as t,mkdir as n,rmdir as a}from"fs/promises";import{homedir as s}from"os";import{s as o}from"../constants-9bb78688.js";import{generateTypeDeclaration as i}from"../generateTypeDeclaration.js";import{pull as r}from"../pull.js";import{syncMacros as c}from"../syncMacros.js";import{resolve as l,extname as f,basename as $,dirname as p,relative as h}from"path";import{D as m,w as d}from"../writeFilePersistent-ee9c9bfd.js";import{a as g}from"../assert-22a7ef8a.js";import{c as u}from"../countHackmudCharacters-a08a265f.js";import"fs";const y=l(s(),".config"),b=l(y,"hsm.json"),k=new Map,w=[],v=new m((e=>{let t=0;for(const n of e)t+=(t>>1)+t+"xi1_8ratvsw9hlbgm02y5zpdcn7uekof463qj".indexOf(n)+1;return[W,_,I,D,x,q][t%6](e)})),logNeedHackmudPathMessage=()=>console.error(R(`${C("You need to set hackmudPath in config before you can use this command")}\n\n${T("To fix this:")}\nOpen hackmud and run "${z("#dir")}"\nThis will open a file browser and print your hackmud user's script directory\nGo up 2 directories and then copy the path\nThen in a terminal run "${z("hsm")} ${x("config set")} ${A("hackmudPath")} ${q("<the path you copied>")}"`)),logHelp=()=>{const e="Push scripts from a directory to hackmud user's scripts directories",t="Watch a directory and push a script when modified",n="Minify a script file on the spot",a="Generate a type declaration file for a directory of scripts",s="Sync macros across all hackmud users",o="Retrieve a value from the config file",i="Assign a value to the config file",r="Remove a key and value from the config file",c="Pull a script a from a hackmud user's script directory",l="Skip minification to produce a readable script",f="Reduce character count further but lose function names in error call stacks",$="Force quine cheats even if the character count is higher";switch(console.log(J("Version")+R(": ")+A("0.19.0-50a29ed")),w[0]){case"config":switch(w[1]){case"get":console.log(`\n${W(o)}\n\n${T("Usage:")}\n${z("hsm")} ${x(`${w[0]} ${w[1]}`)} ${q("<key>")}`);break;case"set":console.log(`\n${W(i)}\n\n${T("Usage:")}\n${z("hsm")} ${x(`${w[0]} ${w[1]}`)} ${q("<key> <value>")}`);break;case"delete":console.log(`\n${W(r)}\n\n${T("Usage:")}\n${z("hsm")} ${x(`${w[0]} ${w[1]}`)} ${q("<key>")}`);break;default:console.log(R(`${J("Config path")}: ${A(b)}\n\n${W("Modify the config file")}\n\n${T("Usage:")}\n${z("hsm")} ${x(`${w[0]} get`)} ${q("<key>")}\n ${o}\n${z("hsm")} ${x(`${w[0]} set`)} ${q("<key> <value>")}\n ${i}\n${z("hsm")} ${x(`${w[0]} delete`)} ${q("<key>")}\n ${r}`))}break;case"push":console.log(R(`\n${W(e)}\n\n${T("Usage:")}\n${z("hsm")} ${x(w[0])} ${q("<directory> [<script user>.<script name>...]")}\n\n${T("Options:")}\n${J("--skip-minify")}\n ${l}\n${J("--mangle-names")}\n ${f}\n${J("--force-quine-cheats")}\n ${$}`));break;case"dev":case"watch":console.log(R(`${J("Aliases")}: ${A("watch, dev")}\n\n${W(t)}\n\n${T("Usage:")}\n${z("hsm")} ${x(w[0])} ${q("<directory> [<script user>.<script name>...]")}\n\n${T("Options:")}\n${J("--skip-minify")}\n ${l}\n${J("--mangle-names")}\n ${f}\n${J("--type-declaration-path")}=${q("<path>")}\n Path to generate a type declaration file for the scripts\n${J("--force-quine-cheats")}\n ${$}`));break;case"pull":console.log(R(`\n${W(c)}\n\n${T("Usage:")}\n${z("hsm")} ${x(w[0])} ${q("<script user>")}${A(".")}${q("<script name>")}`));break;case"minify":case"golf":console.log(R(`${J("Aliases")}: ${A("minify, golf")}\n\n${W(n)}\n\n${T("Usage:")}\n${z("hsm")} ${x(w[0])} ${q("<target> [output path]")}\n\n${T("Options:")}\n${J("--skip-minify")}\n ${l}\n${J("--mangle-names")}\n ${f}\n${J("--force-quine-cheats")}\n ${$}\n${J("--watch")}\n Watch for changes`));break;case"generate-type-declaration":case"gen-type-declaration":case"gen-dts":case"gen-types":console.log(R(`${J("Aliases")}: ${A("generate-type-declaration, gen-type-declaration, gen-types, gen-dts")}\n\n${W(a)}\n\n${T("Usage:")}\n${z("hsm")} ${x(w[0])} ${q("<directory> [output path]")}`));break;case"sync-macros":console.log(`\n${W(s)}`);break;default:console.log(R(`\n${W("Hackmud Script Manager")}\n\n${T("Commands:")}\n${x("push")}\n ${e}\n${x("watch")}, ${x("dev")}\n ${t}\n${x("minify")}, ${x("golf")}\n ${n}\n${x("generate-type-declaration")}, ${x("gen-type-declaration")}, ${x("gen-types")}, ${x("gen-dts")}\n ${a}\n${x("sync-macros")}\n ${s}\n${x("config")}\n Modify and view the config file\n${x("pull")}\n ${c}`))}},exploreObject=(e,t,n=!1)=>{for(const s of t){var a;e=n?"object"==typeof e[s]?e[s]:e[s]={}:null===(a=e)||void 0===a?void 0:a[s]}return e},logInfo=({file:e,users:t,minLength:n,error:a},s)=>{a?logError(`error "${U.bold(a.message)}" in ${U.bold(e)}`):console.log(`pushed ${U.bold(e)} to ${t.map((e=>U.bold(v.get(e)))).join(", ")} | ${U.bold(String(n))} chars | ${U.bold(`${l(s,t[0],"scripts",$(e,f(e)))}.js`)}`)},log=e=>{console.log(R(e))},logError=e=>{console.error(C(e)),process.exitCode=1};for(const e of process.argv.slice(2))if("-"==e[0]){const[t,n]=e.split("=");let a=n;if(a)if("true"==a)a=!0;else if("false"==a)a=!1;else{const e=Number(a);isFinite(e)&&(a=e)}else a=!0;if("-"==e[1])k.set(t.slice(2),a);else for(const e of t.slice(1))k.set(e,a)}else w.push(e);("v"==w[0]||"version"==w[0]||k.get("version")||k.get("v"))&&(console.log("0.19.0-50a29ed"),process.exit());let j=!1;const S=e(b,{encoding:"utf-8"}).then((e=>{let t;try{t=JSON.parse(e)}catch{return log("Config file was corrupted, resetting"),{}}return t&&"object"==typeof t?("hackmudPath"in t&&"string"!=typeof t.hackmudPath&&(log('Property "hackmudPath" of config file was corrupted, removing'),delete t.hackmudPath),t):(log("Config file was corrupted, resetting"),{})}),(()=>(j=!0,{}))),P=import("../push.js"),N=import("../processScript/index.js"),O=import("../watch.js"),M=import("chokidar"),{default:U}=await import("chalk"),T=U.rgb(255,255,255),q=U.rgb(202,202,202),z=U.rgb(155,155,155),C=U.rgb(255,0,0),W=U.rgb(255,244,4),_=U.rgb(243,249,152),x=U.rgb(30,255,0),I=U.rgb(179,255,155),J=U.rgb(0,255,255),R=U.rgb(122,178,244),A=U.rgb(255,0,236),D=U.rgb(255,150,224);(k.get("help")||k.get("h"))&&(logHelp(),process.exit());let E=!0;switch(w[0]){case"push":{const{hackmudPath:e}=await S;if(!e){logNeedHackmudPathMessage();break}const t=w[1];if(!t){logError("Must provide the directory to push from\n"),logHelp();break}const n=w.slice(2);if(n.length){const e=n.find((e=>!/^(?:[a-z_][a-z\d_]{0,24}|\*)\.(?:[a-z_][a-z\d_]{0,24}|\*)$/.test(e)));if(e){logError(`Invalid script name: ${JSON.stringify(e)}\n`),logHelp();break}}else n.push("*.*");if(k.has("skip-minify")&&k.has("mangle-names")){logError(`Option ${J("--mangle-names")} is not compatible with ${J("--skip-minify")}\n`),logHelp();break}const a=k.get("skip-minify");let s;if(null!=a){if("boolean"!=typeof a){logError(`The value for ${J("--skip-minify")} must be ${A("true")} or ${A("false")}\n`),logHelp();break}s=!a}const o=k.get("mangle-names");if(null!=o&&"boolean"!=typeof o){logError(`The value for ${J("--mangle-names")} must be ${A("true")} or ${A("false")}\n`),logHelp();break}const i=k.get("force-quine-cheats");if(null!=i&&"boolean"!=typeof i){logError(`The value for ${J("--force-quine-cheats")} must be ${A("true")} or ${A("false")}\n`),logHelp();break}const{push:r}=await P;(await r(t,e,{scripts:n,onPush:t=>logInfo(t,e),minify:s,mangleNames:o,forceQuineCheats:i})).length||logError("Could not find any scripts to push")}break;case"dev":case"watch":{var F;const{hackmudPath:e}=await S;if(!e){logNeedHackmudPathMessage();break}const t=w[1];if(!t){logError("Must provide the directory to watch\n"),logHelp();break}const n=w.slice(2);if(n.length){const e=n.find((e=>!/^(?:[a-z_][a-z\d_]{0,24}|\*)\.(?:[a-z_][a-z\d_]{0,24}|\*)$/.test(e)));if(e){logError(`Invalid script name: ${JSON.stringify(e)}\n`),logHelp();break}}else n.push("*.*");if(k.has("skip-minify")&&k.has("mangle-names")){logError(`Option ${J("--mangle-names")} is not compatible with ${J("--skip-minify")}\n`),logHelp();break}const a=k.get("skip-minify");let s;if(null!=a){if("boolean"!=typeof a){logError(`The value for ${J("--skip-minify")} must be ${A("true")} or ${A("false")}\n`),logHelp();break}s=!a}const o=k.get("mangle-names");if(null!=o&&"boolean"!=typeof o){logError(`The value for ${J("--mangle-names")} must be ${A("true")} or ${A("false")}\n`),logHelp();break}const i=k.get("force-quine-cheats");if(null!=i&&"boolean"!=typeof i){logError(`The value for ${J("--force-quine-cheats")} must be ${A("true")} or ${A("false")}\n`),logHelp();break}const{watch:r}=await O;r(t,e,{scripts:n,onPush:t=>logInfo(t,e),typeDeclarationPath:null===(F=k.get("type-declaration-path")||k.get("type-declaration")||k.get("dts")||k.get("gen-types"))||void 0===F?void 0:F.toString(),minify:s,mangleNames:o,onReady:()=>log("Watching"),forceQuineCheats:i}),E=!1}break;case"pull":{const{hackmudPath:e}=await S;if(!e){logNeedHackmudPathMessage();break}const t=w[1];if(!t){logError("Must provide the script to pull\n"),logHelp();break}const n=w[2]||".";try{await r(n,e,t)}catch(e){console.error(e),logError(`Something went wrong, did you forget to ${z("#down")} the script?`)}}break;case"sync-macros":{const{hackmudPath:e}=await S;if(!e){logNeedHackmudPathMessage();break}const{macrosSynced:t,usersSynced:n}=await c(e);log(`Synced ${t} macros to ${n} users`)}break;case"generate-type-declaration":case"gen-type-declaration":case"gen-dts":case"gen-types":{const e=w[1];if(!e){logError("Must provide target directory\n"),logHelp();break}const n=l(e),a=w[2]||"./player.d.ts",s=await i(n,(await S).hackmudPath);let o=l(a);try{await t(o,s)}catch(e){if(g(e instanceof Error),"EISDIR"!=e.code)throw e;o=l(o,"player.d.ts"),await t(o,s)}log(`Wrote type declaration to ${U.bold(o)}`)}break;case"config":switch(w[1]){case"get":{const e=w[2];e?log(exploreObject(await S,e.split("."))):console.log(await S)}break;case"delete":{var Q;const e=w[2];if(!e){logError("Must provide a key to delete\n"),logHelp();break}const t=e.split("."),n=t.map((e=>/^[A-Za-z_$][\w$]*$/.test(e)?e:JSON.stringify(e))).join("."),a=t.pop();null===(Q=exploreObject(await S,t))||void 0===Q||delete Q[a],log(`Removed ${A(n)} from config file`)}break;case"set":{const e=w[2],o=w[3];if(!e){logError("Must provide a key and value\n"),logHelp();break}const i=e.split("."),r=i.map((e=>/^[A-Za-z_$][\w$]*$/.test(e)?e:JSON.stringify(e))).join(".");if(!o){logError(`Must provide a value for the key ${r}\n`),logHelp();break}const c=i.pop(),f=await S;if(i.length||"hackmudPath"!=c){let e=f;for(const t of i)"object"==typeof e[t]||(e[t]={}),e=e[t];e[c]=o}else f.hackmudPath=l(o.startsWith("~/")?s()+o.slice(1):o);console.log(f),await(async e=>{const s=JSON.stringify(e,void 0,"\t");j&&log(`Creating config file at ${b}`),await t(b,s).catch((async e=>{switch(e.code){case"EISDIR":await a(b);break;case"ENOENT":await n(y);break;default:throw e}await t(b,s)}))})(f)}break;default:w[1]&&logError(`Unknown command: ${JSON.stringify(w[1])}\n`),logHelp()}break;case"help":case"h":logHelp();break;case"golf":case"minify":{const t=w[1];if(!t){logError("Must provide target\n"),logHelp();break}const n=f(t);if(!o.includes(n)){logError(`Unsupported file extension "${U.bold(n)}"\nSupported extensions are "${o.map((e=>U.bold(e))).join('", "')}"`);break}const{processScript:a}=await N,s=$(t,n),i=s.endsWith(".src"),r=i?s.slice(0,-4):s,c="scripts"==$(l(t,".."))&&"hackmud"==$(l(t,"../../.."))?$(l(t,"../..")):"UNKNOWN",m=!k.get("skip-minify");if(k.has("skip-minify")&&k.has("mangle-names")){logError(`Option ${J("--mangle-names")} would have no effect if minification is skipped\n`),logHelp();break}const g=k.get("mangle-names");if(null!=g&&"boolean"!=typeof g){logError(`The value for ${J("--mangle-names")} must be ${A("true")} or ${A("false")}\n`),logHelp();break}const y=g,b=k.get("force-quine-cheats");if(null!=b&&"boolean"!=typeof b){logError(`the value for ${J("--force-quine-cheats")} must be ${A("true")} or ${A("false")}\n`),logHelp();break}const v=b;let j=w[2]||l(p(t),i?`${r}.js`:".js"==n?`${s}.min.js`:`${s}.js`);const golfFile=()=>e(t,{encoding:"utf-8"}).then((async e=>{const s=performance.now(),{script:o,warnings:i}=await a(e,{minify:m,scriptUser:c,scriptName:r,filePath:t,mangleNames:y,forceQuineCheats:v}),f=performance.now()-s;for(const{message:e,line:t}of i)log(`Warning "${U.bold(e)}" on line ${U.bold(String(t))}`);await d(j,o).catch((async e=>{if(!w[2]||"EISDIR"!=e.code)throw e;j=l(j,`${$(t,n)}.js`),await d(j,o)})).then((()=>log(`Wrote ${U.bold(u(o))} chars to ${U.bold(h(".",j))} | took ${Math.round(100*f)/100}ms`)),(e=>logError(e.message)))}),(e=>logError(e.message)));if(k.get("watch")){const{watch:e}=await M;e(t,{awaitWriteFinish:{stabilityThreshold:100}}).on("ready",(()=>log(`Watching ${t}`))).on("change",golfFile),E=!1}else await golfFile()}break;default:w[0]&&logError(`Unknown command: ${JSON.stringify(w[0])}\n`),logHelp()}E&&process.exit();
2
+ import { DynamicMap } from '@samual/lib/DynamicMap';
3
+ import { assert } from '@samual/lib/assert';
4
+ import { countHackmudCharacters } from '@samual/lib/countHackmudCharacters';
5
+ import { writeFilePersistent } from '@samual/lib/writeFilePersistent';
6
+ import { readFile, writeFile, mkdir, rmdir } from 'fs/promises';
7
+ import { homedir } from 'os';
8
+ import { supportedExtensions } from '../constants.js';
9
+ import { generateTypeDeclaration } from '../generateTypeDeclaration.js';
10
+ import { pull } from '../pull.js';
11
+ import { syncMacros } from '../syncMacros.js';
12
+ import { resolve, extname, basename, dirname, relative } from 'path';
13
+ import '@samual/lib/copyFilePersistent';
14
+
15
+ const version = "0.19.0-7c69a3b";
16
+
17
+ /* | ArgValue[]*/
18
+
19
+ const configDirectoryPath = resolve(homedir(), `.config`);
20
+ const configFilePath = resolve(configDirectoryPath, `hsm.json`);
21
+ const options = new Map();
22
+ const commands = [];
23
+ const userColours = new DynamicMap(user => {
24
+ let hash = 0;
25
+ for (const char of user) hash += (hash >> 1) + hash + `xi1_8ratvsw9hlbgm02y5zpdcn7uekof463qj`.indexOf(char) + 1;
26
+ return [colourJ, colourK, colourM, colourW, colourL, colourB][hash % 6](user);
27
+ });
28
+ const logNeedHackmudPathMessage = () => console.error(colourS(`\
29
+ ${colourD(`You need to set hackmudPath in config before you can use this command`)}
30
+
31
+ ${colourA(`To fix this:`)}
32
+ Open hackmud and run "${colourC(`#dir`)}"
33
+ This will open a file browser and print your hackmud user's script directory
34
+ Go up 2 directories and then copy the path
35
+ Then in a terminal run "${colourC(`hsm`)} ${colourL(`config set`)} ${colourV(`hackmudPath`)} ${colourB(`<the path you copied>`)}"`));
36
+ const logHelp = () => {
37
+ const pushCommandDescription = `Push scripts from a directory to hackmud user's scripts directories`;
38
+ const watchCommandDescription = `Watch a directory and push a script when modified`;
39
+ const minifyCommandDescription = `Minify a script file on the spot`;
40
+ const generateTypeDeclarationCommandDescription = `Generate a type declaration file for a directory of scripts`;
41
+ const syncMacrosCommandDescription = `Sync macros across all hackmud users`;
42
+ const configCommandDescription = `Modify and view the config file`;
43
+ const configGetCommandDescription = `Retrieve a value from the config file`;
44
+ const configSetCommandDescription = `Assign a value to the config file`;
45
+ const configDeleteCommandDescription = `Remove a key and value from the config file`;
46
+ const pullCommandDescription = `Pull a script a from a hackmud user's script directory`;
47
+ const skipMinifyOptionDescription = `Skip minification to produce a readable script`;
48
+ const mangleNamesOptionDescription = `Reduce character count further but lose function names in error call stacks`;
49
+ const forceQuineCheatsOptionDescription = `Force quine cheats even if the character count is higher`;
50
+ console.log(colourN(`Version`) + colourS(`: `) + colourV(version));
51
+ switch (commands[0]) {
52
+ case `config`:
53
+ {
54
+ switch (commands[1]) {
55
+ case `get`:
56
+ {
57
+ console.log(`
58
+ ${colourJ(configGetCommandDescription)}
59
+
60
+ ${colourA(`Usage:`)}
61
+ ${colourC(`hsm`)} ${colourL(`${commands[0]} ${commands[1]}`)} ${colourB(`<key>`)}`);
62
+ }
63
+ break;
64
+ case `set`:
65
+ {
66
+ console.log(`
67
+ ${colourJ(configSetCommandDescription)}
68
+
69
+ ${colourA(`Usage:`)}
70
+ ${colourC(`hsm`)} ${colourL(`${commands[0]} ${commands[1]}`)} ${colourB(`<key> <value>`)}`);
71
+ }
72
+ break;
73
+ case `delete`:
74
+ {
75
+ console.log(`
76
+ ${colourJ(configDeleteCommandDescription)}
77
+
78
+ ${colourA(`Usage:`)}
79
+ ${colourC(`hsm`)} ${colourL(`${commands[0]} ${commands[1]}`)} ${colourB(`<key>`)}`);
80
+ }
81
+ break;
82
+ default:
83
+ {
84
+ console.log(colourS(`\
85
+ ${colourN(`Config path`)}: ${colourV(configFilePath)}
86
+
87
+ ${colourJ(`Modify the config file`)}
88
+
89
+ ${colourA(`Usage:`)}
90
+ ${colourC(`hsm`)} ${colourL(`${commands[0]} get`)} ${colourB(`<key>`)}
91
+ ${configGetCommandDescription}
92
+ ${colourC(`hsm`)} ${colourL(`${commands[0]} set`)} ${colourB(`<key> <value>`)}
93
+ ${configSetCommandDescription}
94
+ ${colourC(`hsm`)} ${colourL(`${commands[0]} delete`)} ${colourB(`<key>`)}
95
+ ${configDeleteCommandDescription}`));
96
+ }
97
+ }
98
+ }
99
+ break;
100
+ case `push`:
101
+ {
102
+ console.log(colourS(`
103
+ ${colourJ(pushCommandDescription)}
104
+
105
+ ${colourA(`Usage:`)}
106
+ ${colourC(`hsm`)} ${colourL(commands[0])} ${colourB(`<directory> [<script user>.<script name>...]`)}
107
+
108
+ ${colourA(`Options:`)}
109
+ ${colourN(`--skip-minify`)}
110
+ ${skipMinifyOptionDescription}
111
+ ${colourN(`--mangle-names`)}
112
+ ${mangleNamesOptionDescription}
113
+ ${colourN(`--force-quine-cheats`)}
114
+ ${forceQuineCheatsOptionDescription}`));
115
+ }
116
+ break;
117
+ case `dev`:
118
+ case `watch`:
119
+ {
120
+ console.log(colourS(`\
121
+ ${colourN(`Aliases`)}: ${colourV(`watch, dev`)}
122
+
123
+ ${colourJ(watchCommandDescription)}
124
+
125
+ ${colourA(`Usage:`)}
126
+ ${colourC(`hsm`)} ${colourL(commands[0])} ${colourB(`<directory> [<script user>.<script name>...]`)}
127
+
128
+ ${colourA(`Options:`)}
129
+ ${colourN(`--skip-minify`)}
130
+ ${skipMinifyOptionDescription}
131
+ ${colourN(`--mangle-names`)}
132
+ ${mangleNamesOptionDescription}
133
+ ${colourN(`--type-declaration-path`)}=${colourB(`<path>`)}
134
+ Path to generate a type declaration file for the scripts
135
+ ${colourN(`--force-quine-cheats`)}
136
+ ${forceQuineCheatsOptionDescription}`));
137
+ }
138
+ break;
139
+ case `pull`:
140
+ {
141
+ console.log(colourS(`
142
+ ${colourJ(pullCommandDescription)}
143
+
144
+ ${colourA(`Usage:`)}
145
+ ${colourC(`hsm`)} ${colourL(commands[0])} ${colourB(`<script user>`)}${colourV(`.`)}${colourB(`<script name>`)}`));
146
+ }
147
+ break;
148
+ case `minify`:
149
+ case `golf`:
150
+ {
151
+ console.log(colourS(`\
152
+ ${colourN(`Aliases`)}: ${colourV(`minify, golf`)}
153
+
154
+ ${colourJ(minifyCommandDescription)}
155
+
156
+ ${colourA(`Usage:`)}
157
+ ${colourC(`hsm`)} ${colourL(commands[0])} ${colourB(`<target> [output path]`)}
158
+
159
+ ${colourA(`Options:`)}
160
+ ${colourN(`--skip-minify`)}
161
+ ${skipMinifyOptionDescription}
162
+ ${colourN(`--mangle-names`)}
163
+ ${mangleNamesOptionDescription}
164
+ ${colourN(`--force-quine-cheats`)}
165
+ ${forceQuineCheatsOptionDescription}
166
+ ${colourN(`--watch`)}
167
+ Watch for changes`));
168
+ }
169
+ break;
170
+ case `generate-type-declaration`:
171
+ case `gen-type-declaration`:
172
+ case `gen-dts`:
173
+ case `gen-types`:
174
+ {
175
+ console.log(colourS(`\
176
+ ${colourN(`Aliases`)}: ${colourV(`generate-type-declaration, gen-type-declaration, gen-types, gen-dts`)}
177
+
178
+ ${colourJ(generateTypeDeclarationCommandDescription)}
179
+
180
+ ${colourA(`Usage:`)}
181
+ ${colourC(`hsm`)} ${colourL(commands[0])} ${colourB(`<directory> [output path]`)}`));
182
+ }
183
+ break;
184
+ case `sync-macros`:
185
+ {
186
+ console.log(`\n${colourJ(syncMacrosCommandDescription)}`);
187
+ }
188
+ break;
189
+ default:
190
+ {
191
+ console.log(colourS(`
192
+ ${colourJ(`Hackmud Script Manager`)}
193
+
194
+ ${colourA(`Commands:`)}
195
+ ${colourL(`push`)}
196
+ ${pushCommandDescription}
197
+ ${colourL(`watch`)}, ${colourL(`dev`)}
198
+ ${watchCommandDescription}
199
+ ${colourL(`minify`)}, ${colourL(`golf`)}
200
+ ${minifyCommandDescription}
201
+ ${colourL(`generate-type-declaration`)}, ${colourL(`gen-type-declaration`)}, ${colourL(`gen-types`)}, ${colourL(`gen-dts`)}
202
+ ${generateTypeDeclarationCommandDescription}
203
+ ${colourL(`sync-macros`)}
204
+ ${syncMacrosCommandDescription}
205
+ ${colourL(`config`)}
206
+ ${configCommandDescription}
207
+ ${colourL(`pull`)}
208
+ ${pullCommandDescription}`));
209
+ }
210
+ }
211
+ };
212
+ const exploreObject = (object, keys, createPath = false) => {
213
+ for (const key of keys) {
214
+ if (createPath) object = typeof object[key] == `object` ? object[key] : object[key] = {};else object = object?.[key];
215
+ }
216
+ return object;
217
+ };
218
+ const updateConfig = async config => {
219
+ const json = JSON.stringify(config, undefined, `\t`);
220
+ if (configDidNotExist) log(`Creating config file at ${configFilePath}`);
221
+ await writeFile(configFilePath, json).catch(async error => {
222
+ switch (error.code) {
223
+ case `EISDIR`:
224
+ {
225
+ await rmdir(configFilePath);
226
+ }
227
+ break;
228
+ case `ENOENT`:
229
+ {
230
+ await mkdir(configDirectoryPath);
231
+ }
232
+ break;
233
+ default:
234
+ throw error;
235
+ }
236
+ await writeFile(configFilePath, json);
237
+ });
238
+ };
239
+ const logInfo = ({
240
+ file,
241
+ users,
242
+ minLength,
243
+ error
244
+ }, hackmudPath) => {
245
+ if (error) {
246
+ logError(`error "${chalk.bold(error.message)}" in ${chalk.bold(file)}`);
247
+ return;
248
+ }
249
+ console.log(`pushed ${chalk.bold(file)} to ${users.map(user => chalk.bold(userColours.get(user))).join(`, `)} | ${chalk.bold(String(minLength))} chars | ${chalk.bold(`${resolve(hackmudPath, users[0], `scripts`, basename(file, extname(file)))}.js`)}`);
250
+ };
251
+ const log = message => {
252
+ console.log(colourS(message));
253
+ };
254
+ const logError = message => {
255
+ console.error(colourD(message));
256
+ process.exitCode = 1;
257
+ };
258
+ for (const argument of process.argv.slice(2)) {
259
+ if (argument[0] == `-`) {
260
+ const [key, valueRaw] = argument.split(`=`);
261
+ let value = valueRaw;
262
+ if (value) {
263
+ if (value == `true`) value = true;else if (value == `false`) value = false;else {
264
+ const number = Number(value);
265
+ if (isFinite(number)) value = number;
266
+ }
267
+ } else value = true;
268
+ if (argument[1] == `-`) options.set(key.slice(2), value);else {
269
+ for (const option of key.slice(1)) options.set(option, value);
270
+ }
271
+ } else commands.push(argument);
272
+ }
273
+ if (commands[0] == `v` || commands[0] == `version` || options.get(`version`) || options.get(`v`)) {
274
+ console.log(version);
275
+ process.exit();
276
+ }
277
+ let configDidNotExist = false;
278
+ const configPromise = readFile(configFilePath, {
279
+ encoding: `utf-8`
280
+ }).then(configFile => {
281
+ let temporaryConfig;
282
+ try {
283
+ temporaryConfig = JSON.parse(configFile);
284
+ } catch {
285
+ // TODO log to error log file
286
+ log(`Config file was corrupted, resetting`);
287
+ return {};
288
+ }
289
+ if (!temporaryConfig || typeof temporaryConfig != `object`) {
290
+ log(`Config file was corrupted, resetting`);
291
+ return {};
292
+ }
293
+ if (`hackmudPath` in temporaryConfig && typeof temporaryConfig.hackmudPath != `string`) {
294
+ log(`Property "hackmudPath" of config file was corrupted, removing`);
295
+ delete temporaryConfig.hackmudPath;
296
+ }
297
+ return temporaryConfig;
298
+ }, () => {
299
+ configDidNotExist = true;
300
+ return {};
301
+ });
302
+ const pushModule = import('../push.js');
303
+ const processScriptModule = import('../processScript/index.js');
304
+ const watchModule = import('../watch.js');
305
+ const chokidarModule = import('chokidar');
306
+ const {
307
+ default: chalk
308
+ } = await import('chalk');
309
+ const colourA = chalk.rgb(0xFF, 0xFF, 0xFF);
310
+ const colourB = chalk.rgb(0xCA, 0xCA, 0xCA);
311
+ const colourC = chalk.rgb(0x9B, 0x9B, 0x9B);
312
+ const colourD = chalk.rgb(0xFF, 0x00, 0x00);
313
+ const colourJ = chalk.rgb(0xFF, 0xF4, 0x04);
314
+ const colourK = chalk.rgb(0xF3, 0xF9, 0x98);
315
+ const colourL = chalk.rgb(0x1E, 0xFF, 0x00);
316
+ const colourM = chalk.rgb(0xB3, 0xFF, 0x9B);
317
+ const colourN = chalk.rgb(0x00, 0xFF, 0xFF);
318
+ const colourS = chalk.rgb(0x7A, 0xB2, 0xF4);
319
+ const colourV = chalk.rgb(0xFF, 0x00, 0xEC);
320
+ const colourW = chalk.rgb(0xFF, 0x96, 0xE0);
321
+ if (options.get(`help`) || options.get(`h`)) {
322
+ logHelp();
323
+ process.exit();
324
+ }
325
+ let autoExit = true;
326
+ switch (commands[0]) {
327
+ case `push`:
328
+ {
329
+ const {
330
+ hackmudPath
331
+ } = await configPromise;
332
+ if (!hackmudPath) {
333
+ logNeedHackmudPathMessage();
334
+ break;
335
+ }
336
+ const sourcePath = commands[1];
337
+ if (!sourcePath) {
338
+ logError(`Must provide the directory to push from\n`);
339
+ logHelp();
340
+ break;
341
+ }
342
+ const scripts = commands.slice(2);
343
+ if (scripts.length) {
344
+ const invalidScript = scripts.find(script => !/^(?:[a-z_][a-z\d_]{0,24}|\*)\.(?:[a-z_][a-z\d_]{0,24}|\*)$/.test(script));
345
+ if (invalidScript) {
346
+ logError(`Invalid script name: ${JSON.stringify(invalidScript)}\n`);
347
+ logHelp();
348
+ break;
349
+ }
350
+ } else scripts.push(`*.*`);
351
+ if (options.has(`skip-minify`) && options.has(`mangle-names`)) {
352
+ logError(`Option ${colourN(`--mangle-names`)} is not compatible with ${colourN(`--skip-minify`)}\n`);
353
+ logHelp();
354
+ break;
355
+ }
356
+ const shouldSkipMinify = options.get(`skip-minify`);
357
+ let shouldMinify;
358
+ if (shouldSkipMinify != undefined) {
359
+ if (typeof shouldSkipMinify != `boolean`) {
360
+ logError(`The value for ${colourN(`--skip-minify`)} must be ${colourV(`true`)} or ${colourV(`false`)}\n`);
361
+ logHelp();
362
+ break;
363
+ }
364
+ shouldMinify = !shouldSkipMinify;
365
+ }
366
+ const shouldMangleNames = options.get(`mangle-names`);
367
+ if (shouldMangleNames != undefined && typeof shouldMangleNames != `boolean`) {
368
+ logError(`The value for ${colourN(`--mangle-names`)} must be ${colourV(`true`)} or ${colourV(`false`)}\n`);
369
+ logHelp();
370
+ break;
371
+ }
372
+ const shouldforceQuineCheats = options.get(`force-quine-cheats`);
373
+ if (shouldforceQuineCheats != undefined && typeof shouldforceQuineCheats != `boolean`) {
374
+ logError(`The value for ${colourN(`--force-quine-cheats`)} must be ${colourV(`true`)} or ${colourV(`false`)}\n`);
375
+ logHelp();
376
+ break;
377
+ }
378
+ const {
379
+ push
380
+ } = await pushModule;
381
+ const infos = await push(sourcePath, hackmudPath, {
382
+ scripts,
383
+ onPush: info => logInfo(info, hackmudPath),
384
+ minify: shouldMinify,
385
+ mangleNames: shouldMangleNames,
386
+ forceQuineCheats: shouldforceQuineCheats
387
+ });
388
+ if (!infos.length) logError(`Could not find any scripts to push`);
389
+ }
390
+ break;
391
+ case `dev`:
392
+ case `watch`:
393
+ {
394
+ const {
395
+ hackmudPath
396
+ } = await configPromise;
397
+ if (!hackmudPath) {
398
+ logNeedHackmudPathMessage();
399
+ break;
400
+ }
401
+ const sourcePath = commands[1];
402
+ if (!sourcePath) {
403
+ logError(`Must provide the directory to watch\n`);
404
+ logHelp();
405
+ break;
406
+ }
407
+ const scripts = commands.slice(2);
408
+ if (scripts.length) {
409
+ const invalidScript = scripts.find(script => !/^(?:[a-z_][a-z\d_]{0,24}|\*)\.(?:[a-z_][a-z\d_]{0,24}|\*)$/.test(script));
410
+ if (invalidScript) {
411
+ logError(`Invalid script name: ${JSON.stringify(invalidScript)}\n`);
412
+ logHelp();
413
+ break;
414
+ }
415
+ } else scripts.push(`*.*`);
416
+ if (options.has(`skip-minify`) && options.has(`mangle-names`)) {
417
+ logError(`Option ${colourN(`--mangle-names`)} is not compatible with ${colourN(`--skip-minify`)}\n`);
418
+ logHelp();
419
+ break;
420
+ }
421
+ const shouldSkipMinify = options.get(`skip-minify`);
422
+ let shouldMinify;
423
+ if (shouldSkipMinify != undefined) {
424
+ if (typeof shouldSkipMinify != `boolean`) {
425
+ logError(`The value for ${colourN(`--skip-minify`)} must be ${colourV(`true`)} or ${colourV(`false`)}\n`);
426
+ logHelp();
427
+ break;
428
+ }
429
+ shouldMinify = !shouldSkipMinify;
430
+ }
431
+ const shouldMangleNames = options.get(`mangle-names`);
432
+ if (shouldMangleNames != undefined && typeof shouldMangleNames != `boolean`) {
433
+ logError(`The value for ${colourN(`--mangle-names`)} must be ${colourV(`true`)} or ${colourV(`false`)}\n`);
434
+ logHelp();
435
+ break;
436
+ }
437
+ const shouldforceQuineCheats = options.get(`force-quine-cheats`);
438
+ if (shouldforceQuineCheats != undefined && typeof shouldforceQuineCheats != `boolean`) {
439
+ logError(`The value for ${colourN(`--force-quine-cheats`)} must be ${colourV(`true`)} or ${colourV(`false`)}\n`);
440
+ logHelp();
441
+ break;
442
+ }
443
+ const {
444
+ watch
445
+ } = await watchModule;
446
+ watch(sourcePath, hackmudPath, {
447
+ scripts,
448
+ onPush: info => logInfo(info, hackmudPath),
449
+ typeDeclarationPath: (options.get(`type-declaration-path`) || options.get(`type-declaration`) || options.get(`dts`) || options.get(`gen-types`))?.toString(),
450
+ minify: shouldMinify,
451
+ mangleNames: shouldMangleNames,
452
+ onReady: () => log(`Watching`),
453
+ forceQuineCheats: shouldforceQuineCheats
454
+ });
455
+ autoExit = false;
456
+ }
457
+ break;
458
+ case `pull`:
459
+ {
460
+ const {
461
+ hackmudPath
462
+ } = await configPromise;
463
+ if (!hackmudPath) {
464
+ logNeedHackmudPathMessage();
465
+ break;
466
+ }
467
+ const script = commands[1];
468
+ if (!script) {
469
+ logError(`Must provide the script to pull\n`);
470
+ logHelp();
471
+ break;
472
+ }
473
+ const sourcePath = commands[2] || `.`;
474
+ try {
475
+ await pull(sourcePath, hackmudPath, script);
476
+ } catch (error) {
477
+ console.error(error);
478
+ logError(`Something went wrong, did you forget to ${colourC(`#down`)} the script?`);
479
+ }
480
+ }
481
+ break;
482
+ case `sync-macros`:
483
+ {
484
+ const {
485
+ hackmudPath
486
+ } = await configPromise;
487
+ if (!hackmudPath) {
488
+ logNeedHackmudPathMessage();
489
+ break;
490
+ }
491
+ const {
492
+ macrosSynced,
493
+ usersSynced
494
+ } = await syncMacros(hackmudPath);
495
+ log(`Synced ${macrosSynced} macros to ${usersSynced} users`);
496
+ }
497
+ break;
498
+ case `generate-type-declaration`:
499
+ case `gen-type-declaration`:
500
+ case `gen-dts`:
501
+ case `gen-types`:
502
+ {
503
+ const target = commands[1];
504
+ if (!target) {
505
+ logError(`Must provide target directory\n`);
506
+ logHelp();
507
+ break;
508
+ }
509
+ const sourcePath = resolve(target);
510
+ const outputPath = commands[2] || `./player.d.ts`;
511
+ const typeDeclaration = await generateTypeDeclaration(sourcePath, (await configPromise).hackmudPath);
512
+ let typeDeclarationPath = resolve(outputPath);
513
+ try {
514
+ await writeFile(typeDeclarationPath, typeDeclaration);
515
+ } catch (error) {
516
+ assert(error instanceof Error);
517
+ if (!(error.code == `EISDIR`)) throw error;
518
+ typeDeclarationPath = resolve(typeDeclarationPath, `player.d.ts`);
519
+ await writeFile(typeDeclarationPath, typeDeclaration);
520
+ }
521
+ log(`Wrote type declaration to ${chalk.bold(typeDeclarationPath)}`);
522
+ }
523
+ break;
524
+ case `config`:
525
+ {
526
+ switch (commands[1]) {
527
+ case `get`:
528
+ {
529
+ const key = commands[2];
530
+ if (key) log(exploreObject(await configPromise, key.split(`.`)));else console.log(await configPromise);
531
+ }
532
+ break;
533
+ case `delete`:
534
+ {
535
+ const key = commands[2];
536
+ if (!key) {
537
+ logError(`Must provide a key to delete\n`);
538
+ logHelp();
539
+ break;
540
+ }
541
+ const keyParts = key.split(`.`);
542
+ const pathName = keyParts.map(name => /^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name)).join(`.`);
543
+ const lastKey = keyParts.pop();
544
+ const config = await configPromise;
545
+ delete exploreObject(config, keyParts)?.[lastKey];
546
+ log(`Removed ${colourV(pathName)} from config file`);
547
+ }
548
+ break;
549
+ case `set`:
550
+ {
551
+ const key = commands[2];
552
+ const value = commands[3];
553
+ if (!key) {
554
+ logError(`Must provide a key and value\n`);
555
+ logHelp();
556
+ break;
557
+ }
558
+ const keys = key.split(`.`);
559
+ const pathName = keys.map(name => /^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name)).join(`.`);
560
+ if (!value) {
561
+ logError(`Must provide a value for the key ${pathName}\n`);
562
+ logHelp();
563
+ break;
564
+ }
565
+ const lastKey = keys.pop();
566
+ const config = await configPromise;
567
+ if (!keys.length && lastKey == `hackmudPath`) config.hackmudPath = resolve(value.startsWith(`~/`) ? homedir() + value.slice(1) : value);else {
568
+ let object = config;
569
+ for (const key of keys) {
570
+ if (typeof object[key] == `object`) object = object[key];else {
571
+ object[key] = {};
572
+ object = object[key];
573
+ }
574
+ }
575
+ object[lastKey] = value;
576
+ }
577
+ console.log(config);
578
+ await updateConfig(config);
579
+ }
580
+ break;
581
+ default:
582
+ {
583
+ if (commands[1]) logError(`Unknown command: ${JSON.stringify(commands[1])}\n`);
584
+ logHelp();
585
+ }
586
+ }
587
+ }
588
+ break;
589
+ case `help`:
590
+ case `h`:
591
+ {
592
+ logHelp();
593
+ }
594
+ break;
595
+ case `golf`:
596
+ case `minify`:
597
+ {
598
+ const target = commands[1];
599
+ if (!target) {
600
+ logError(`Must provide target\n`);
601
+ logHelp();
602
+ break;
603
+ }
604
+ const fileExtension = extname(target);
605
+ if (!supportedExtensions.includes(fileExtension)) {
606
+ logError(`Unsupported file extension "${chalk.bold(fileExtension)}"\nSupported extensions are "${supportedExtensions.map(extension => chalk.bold(extension)).join(`", "`)}"`);
607
+ break;
608
+ }
609
+ const {
610
+ processScript
611
+ } = await processScriptModule;
612
+ const fileBaseName = basename(target, fileExtension);
613
+ // eslint-disable-next-line unicorn/prevent-abbreviations -- the file extension is `src` not `source`
614
+ const fileBaseNameEndsWithDotSrc = fileBaseName.endsWith(`.src`);
615
+ const scriptName = fileBaseNameEndsWithDotSrc ? fileBaseName.slice(0, -4) : fileBaseName;
616
+ const scriptUser = basename(resolve(target, `..`)) == `scripts` && basename(resolve(target, `../../..`)) == `hackmud` ? basename(resolve(target, `../..`)) : `UNKNOWN`;
617
+ const minify = !options.get(`skip-minify`);
618
+ if (options.has(`skip-minify`) && options.has(`mangle-names`)) {
619
+ logError(`Option ${colourN(`--mangle-names`)} would have no effect if minification is skipped\n`);
620
+ logHelp();
621
+ break;
622
+ }
623
+ const mangleNames_ = options.get(`mangle-names`);
624
+ if (mangleNames_ != undefined && typeof mangleNames_ != `boolean`) {
625
+ logError(`The value for ${colourN(`--mangle-names`)} must be ${colourV(`true`)} or ${colourV(`false`)}\n`);
626
+ logHelp();
627
+ break;
628
+ }
629
+ const mangleNames = mangleNames_;
630
+ const forceQuineCheats_ = options.get(`force-quine-cheats`);
631
+ if (forceQuineCheats_ != undefined && typeof forceQuineCheats_ != `boolean`) {
632
+ logError(`the value for ${colourN(`--force-quine-cheats`)} must be ${colourV(`true`)} or ${colourV(`false`)}\n`);
633
+ logHelp();
634
+ break;
635
+ }
636
+ const forceQuineCheats = forceQuineCheats_;
637
+ let outputPath = commands[2] || resolve(dirname(target), fileBaseNameEndsWithDotSrc ? `${scriptName}.js` : fileExtension == `.js` ? `${fileBaseName}.min.js` : `${fileBaseName}.js`);
638
+ const golfFile = () => readFile(target, {
639
+ encoding: `utf-8`
640
+ }).then(async source => {
641
+ const timeStart = performance.now();
642
+ const {
643
+ script,
644
+ warnings
645
+ } = await processScript(source, {
646
+ minify,
647
+ scriptUser,
648
+ scriptName,
649
+ filePath: target,
650
+ mangleNames,
651
+ forceQuineCheats
652
+ });
653
+ const timeTook = performance.now() - timeStart;
654
+ for (const {
655
+ message,
656
+ line
657
+ } of warnings) log(`Warning "${chalk.bold(message)}" on line ${chalk.bold(String(line))}`);
658
+ await writeFilePersistent(outputPath, script).catch(async error => {
659
+ if (!commands[2] || error.code != `EISDIR`) throw error;
660
+ outputPath = resolve(outputPath, `${basename(target, fileExtension)}.js`);
661
+ await writeFilePersistent(outputPath, script);
662
+ }).then(() => log(`Wrote ${chalk.bold(countHackmudCharacters(script))} chars to ${chalk.bold(relative(`.`, outputPath))} | took ${Math.round(timeTook * 100) / 100}ms`), error => logError(error.message));
663
+ }, error => logError(error.message));
664
+ if (options.get(`watch`)) {
665
+ const {
666
+ watch: watchFile
667
+ } = await chokidarModule;
668
+ watchFile(target, {
669
+ awaitWriteFinish: {
670
+ stabilityThreshold: 100
671
+ }
672
+ }).on(`ready`, () => log(`Watching ${target}`)).on(`change`, golfFile);
673
+ autoExit = false;
674
+ } else await golfFile();
675
+ }
676
+ break;
677
+ default:
678
+ {
679
+ if (commands[0]) logError(`Unknown command: ${JSON.stringify(commands[0])}\n`);
680
+ logHelp();
681
+ }
682
+ }
683
+ if (autoExit) process.exit();