gemwright 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/small-forge.png +0 -0
- package/README.md +7 -1
- package/dist/index.js +75 -11
- package/package.json +5 -3
|
Binary file
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# <img src=".github/small-forge.png" align="left" alt="Forge your own shard" width="50" height="50" /> Gemwright — Forge your own shard
|
|
1
|
+
# <img src="https://cdn.jsdelivr.net/npm/gemwright@latest/.github/small-forge.png" align="left" alt="Forge your own shard" width="50" height="50" /> Gemwright — Forge your own shard
|
|
2
2
|
|
|
3
3
|
## Motivation
|
|
4
4
|
|
|
@@ -45,6 +45,12 @@ gemwright deploy
|
|
|
45
45
|
gemwright deploy --files /path/to/game-files
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
### Next steps
|
|
49
|
+
|
|
50
|
+
- Push your shard configuration and scripts to a private Git repository to track changes.
|
|
51
|
+
- Set up a custom login hostname, such as `login.yourshard.com`. Create a DNS CNAME pointing to your Railway TCP proxy hostname.
|
|
52
|
+
- Keep separate copies of your saves and accounts outside Railway. (Railway's built-in volume backups require the Pro plan)
|
|
53
|
+
|
|
48
54
|
## Development
|
|
49
55
|
|
|
50
56
|
- [x] `init` to initialize shard with a few answers
|
package/dist/index.js
CHANGED
|
@@ -8,12 +8,12 @@ import chalk from "chalk";
|
|
|
8
8
|
|
|
9
9
|
// src/deploy.ts
|
|
10
10
|
import { randomUUID } from "node:crypto";
|
|
11
|
-
import { mkdir as mkdir3, readFile as readFile6, rename, writeFile as
|
|
11
|
+
import { mkdir as mkdir3, readFile as readFile6, rename, writeFile as writeFile4 } from "node:fs/promises";
|
|
12
12
|
import { join as join7, resolve as resolve2 } from "node:path";
|
|
13
13
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
14
14
|
|
|
15
15
|
// src/emulators/servuo/staging.ts
|
|
16
|
-
import { cp, mkdir, readFile as readFile2, rm as rm2, stat } from "node:fs/promises";
|
|
16
|
+
import { cp, mkdir, readFile as readFile2, rm as rm2, stat, writeFile as writeFile2 } from "node:fs/promises";
|
|
17
17
|
import { join as join3 } from "node:path";
|
|
18
18
|
|
|
19
19
|
// src/assets.ts
|
|
@@ -181,7 +181,21 @@ function prepareServuoFiles(upstream, config, settings, password) {
|
|
|
181
181
|
files["Config/General.cfg"] = setConfigValue(files["Config/General.cfg"] ?? "", "SupportWebsite", config.url);
|
|
182
182
|
files["Config/Server.cfg"] = setConfigValue(files["Config/Server.cfg"], "Name", config.name);
|
|
183
183
|
files["Config/Expansion.cfg"] = setConfigValue(files["Config/Expansion.cfg"], "CurrentExpansion", expansionNames[config.expansion]);
|
|
184
|
-
files["Config/DataPath.cfg"] = setConfigValue(files["Config/DataPath.cfg"], "CustomPath", "data/client");
|
|
184
|
+
files["Config/DataPath.cfg"] = setConfigValue(files["Config/DataPath.cfg"], "CustomPath", settings.clientFiles ?? "data/client");
|
|
185
|
+
if (settings.accountsPerIp !== undefined) {
|
|
186
|
+
if (!Number.isInteger(settings.accountsPerIp) || settings.accountsPerIp < 0 || settings.accountsPerIp > 2147483647)
|
|
187
|
+
throw new Error("Accounts per IP must be a whole number between 0 and 2147483647.");
|
|
188
|
+
files["Config/Accounts.cfg"] = setConfigValue(files["Config/Accounts.cfg"], "AccountsPerIp", String(settings.accountsPerIp));
|
|
189
|
+
}
|
|
190
|
+
if (settings.globalChat !== undefined)
|
|
191
|
+
files["Config/Chat.cfg"] = setConfigValue(files["Config/Chat.cfg"] ?? "", "Enabled", String(settings.globalChat));
|
|
192
|
+
if (settings.allowedClients !== undefined) {
|
|
193
|
+
const keys = ["AllowRegular", "AllowUOTD", "AllowGod", "AllowEC"];
|
|
194
|
+
if (!settings.allowedClients.length || settings.allowedClients.some((key) => !keys.includes(key)))
|
|
195
|
+
throw new Error("Choose at least one supported client.");
|
|
196
|
+
for (const key of keys)
|
|
197
|
+
files["Config/Client.cfg"] = setConfigValue(files["Config/Client.cfg"] ?? "", key, String(settings.allowedClients.includes(key)));
|
|
198
|
+
}
|
|
185
199
|
files["Config/Accounts.cfg"] = setConfigValue(files["Config/Accounts.cfg"], "AutoCreateAccounts", settings.autoCreateAccounts ? "True" : "False");
|
|
186
200
|
const protection = configValue(files["Config/Accounts.cfg"], "ProtectPasswords");
|
|
187
201
|
const hashes = {
|
|
@@ -273,7 +287,7 @@ async function validateServuoShard(directory, config) {
|
|
|
273
287
|
throw new Error("Railway deployment currently requires Server.cfg Listen=0.0.0.0 and Port=2593. Config was not changed.");
|
|
274
288
|
}
|
|
275
289
|
const data = await readFile2(join3(directory, "Config/DataPath.cfg"), "utf8");
|
|
276
|
-
if (configValue(data, "CustomPath") !== "data/client")
|
|
290
|
+
if (configValue(data, "CustomPath") !== "data/client" && configValue(data, "CustomPath") !== config.servuo?.clientFiles)
|
|
277
291
|
throw new Error("Deployment currently supports DataPath.cfg CustomPath=data/client. Config was not changed.");
|
|
278
292
|
const restart = await readFile2(join3(directory, "Config/AutoRestart.cfg"), "utf8").catch(() => "Enabled=False");
|
|
279
293
|
if (configValue(restart, "Enabled")?.toLowerCase() === "true") {
|
|
@@ -292,6 +306,8 @@ async function stageServuo(directory, config) {
|
|
|
292
306
|
await rm2(join3(server, ".git"), { recursive: true, force: true });
|
|
293
307
|
await rm2(join3(server, "Config"), { recursive: true, force: true });
|
|
294
308
|
await cp(join3(directory, "Config"), join3(server, "Config"), { recursive: true });
|
|
309
|
+
const dataPath = join3(server, "Config/DataPath.cfg");
|
|
310
|
+
await writeFile2(dataPath, setConfigValue(await readFile2(dataPath, "utf8"), "CustomPath", "data/client"));
|
|
295
311
|
for (const folder of ["Scripts", "Data"]) {
|
|
296
312
|
if ((await stat(join3(directory, folder)).catch(() => {
|
|
297
313
|
return;
|
|
@@ -322,7 +338,7 @@ import { join as join5 } from "node:path";
|
|
|
322
338
|
|
|
323
339
|
// src/emulators/sphere/config.ts
|
|
324
340
|
import { createHash as createHash3 } from "node:crypto";
|
|
325
|
-
import { mkdtemp as mkdtemp2, readdir as readdir3, readFile as readFile3, rm as rm3, writeFile as
|
|
341
|
+
import { mkdtemp as mkdtemp2, readdir as readdir3, readFile as readFile3, rm as rm3, writeFile as writeFile3 } from "node:fs/promises";
|
|
326
342
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
327
343
|
import { join as join4 } from "node:path";
|
|
328
344
|
|
|
@@ -634,7 +650,7 @@ async function fetchSphereFiles(sources) {
|
|
|
634
650
|
if (!response.ok)
|
|
635
651
|
throw new Error(`Could not fetch Sphere ${repository}: HTTP ${response.status}`);
|
|
636
652
|
const archive = join4(temporary, `${kind}.tar.gz`);
|
|
637
|
-
await
|
|
653
|
+
await writeFile3(archive, new Uint8Array(await response.arrayBuffer()));
|
|
638
654
|
await command(["tar", "-xzf", archive, "-C", temporary], temporary);
|
|
639
655
|
return join4(temporary, `${repository}-${revision}`);
|
|
640
656
|
}
|
|
@@ -961,7 +977,7 @@ async function loadShard(directory) {
|
|
|
961
977
|
async function saveState(directory, state) {
|
|
962
978
|
const path = join7(directory, ".gemwright/railway.json");
|
|
963
979
|
await mkdir3(join7(directory, ".gemwright"), { recursive: true });
|
|
964
|
-
await
|
|
980
|
+
await writeFile4(`${path}.tmp`, JSON.stringify(state, null, 2) + `
|
|
965
981
|
`);
|
|
966
982
|
await rename(`${path}.tmp`, path);
|
|
967
983
|
}
|
|
@@ -1175,7 +1191,7 @@ async function deploy(options, directory = process.cwd()) {
|
|
|
1175
1191
|
}
|
|
1176
1192
|
|
|
1177
1193
|
// src/init.ts
|
|
1178
|
-
import { resolve as resolve5 } from "node:path";
|
|
1194
|
+
import { relative, resolve as resolve5 } from "node:path";
|
|
1179
1195
|
import * as prompts5 from "@clack/prompts";
|
|
1180
1196
|
|
|
1181
1197
|
// src/emulators/servuo/init.ts
|
|
@@ -1220,6 +1236,40 @@ async function initializeServuo(config, yes = false, loadConfig = fetchServuoCon
|
|
|
1220
1236
|
message: "Allow players to create accounts on first login?",
|
|
1221
1237
|
initialValue: registration.toLowerCase() === "true"
|
|
1222
1238
|
}));
|
|
1239
|
+
const accountDefault = configValue(upstream["Config/Accounts.cfg"] ?? "", "AccountsPerIp");
|
|
1240
|
+
if (!accountDefault || !/^\d+$/.test(accountDefault) || Number(accountDefault) > 2147483647)
|
|
1241
|
+
throw new Error("Upstream accounts per IP default is missing or invalid.");
|
|
1242
|
+
const chatDefault = configValue(upstream["Config/Chat.cfg"] ?? "", "Enabled");
|
|
1243
|
+
if (!chatDefault || !/^(true|false)$/i.test(chatDefault))
|
|
1244
|
+
throw new Error("Upstream global chat default is missing or invalid.");
|
|
1245
|
+
const clientOptions = [
|
|
1246
|
+
{ value: "AllowRegular", label: "Classic client" },
|
|
1247
|
+
{ value: "AllowUOTD", label: "Third Dawn client" },
|
|
1248
|
+
{ value: "AllowGod", label: "God client" },
|
|
1249
|
+
{ value: "AllowEC", label: "Enhanced client" }
|
|
1250
|
+
];
|
|
1251
|
+
const clientDefaults = clientOptions.filter(({ value }) => {
|
|
1252
|
+
const enabled = configValue(upstream["Config/Client.cfg"] ?? "", value);
|
|
1253
|
+
if (!enabled || !/^(true|false)$/i.test(enabled))
|
|
1254
|
+
throw new Error(`Upstream client default is missing or invalid: ${value}`);
|
|
1255
|
+
return enabled.toLowerCase() === "true";
|
|
1256
|
+
}).map(({ value }) => value);
|
|
1257
|
+
const accountsPerIp = yes ? Number(accountDefault) : Number(answer(await prompts2.text({
|
|
1258
|
+
message: "Accounts allowed per IP address",
|
|
1259
|
+
defaultValue: accountDefault,
|
|
1260
|
+
placeholder: accountDefault,
|
|
1261
|
+
validate: (value) => value && (!/^\d+$/.test(value) || Number(value) > 2147483647) ? "Enter a whole number between 0 and 2147483647." : undefined
|
|
1262
|
+
})));
|
|
1263
|
+
const globalChat = yes ? chatDefault.toLowerCase() === "true" : answer(await prompts2.confirm({
|
|
1264
|
+
message: "Enable global chat?",
|
|
1265
|
+
initialValue: chatDefault.toLowerCase() === "true"
|
|
1266
|
+
}));
|
|
1267
|
+
const allowedClients = yes ? clientDefaults : answer(await prompts2.multiselect({
|
|
1268
|
+
message: "Allowed clients",
|
|
1269
|
+
options: clientOptions,
|
|
1270
|
+
initialValues: clientDefaults,
|
|
1271
|
+
required: true
|
|
1272
|
+
}));
|
|
1223
1273
|
const owner = yes ? "admin" : answer(await prompts2.text({
|
|
1224
1274
|
message: "Owner username",
|
|
1225
1275
|
placeholder: "admin",
|
|
@@ -1235,6 +1285,9 @@ async function initializeServuo(config, yes = false, loadConfig = fetchServuoCon
|
|
|
1235
1285
|
const settings = {
|
|
1236
1286
|
...clientFiles ? { clientFiles: resolve3(clientFiles) } : {},
|
|
1237
1287
|
autoCreateAccounts,
|
|
1288
|
+
accountsPerIp,
|
|
1289
|
+
globalChat,
|
|
1290
|
+
allowedClients,
|
|
1238
1291
|
owner
|
|
1239
1292
|
};
|
|
1240
1293
|
return { settings, files: prepareServuoFiles(upstream, config, settings, password2), generatedPassword };
|
|
@@ -1427,7 +1480,7 @@ async function initializeSphere(config, sources, yes = false, loadFiles = fetchS
|
|
|
1427
1480
|
}
|
|
1428
1481
|
|
|
1429
1482
|
// src/instance.ts
|
|
1430
|
-
import { appendFile, lstat, mkdir as mkdir4, readFile as readFile7, stat as stat5, writeFile as
|
|
1483
|
+
import { appendFile, lstat, mkdir as mkdir4, readFile as readFile7, stat as stat5, writeFile as writeFile5 } from "node:fs/promises";
|
|
1431
1484
|
import { dirname, isAbsolute, join as join8 } from "node:path";
|
|
1432
1485
|
async function existingAncestor(directory) {
|
|
1433
1486
|
try {
|
|
@@ -1517,7 +1570,7 @@ async function saveInstance(directory, config, initializeGit, files = {}) {
|
|
|
1517
1570
|
`)}
|
|
1518
1571
|
`);
|
|
1519
1572
|
} else {
|
|
1520
|
-
await
|
|
1573
|
+
await writeFile5(join8(directory, filename), content, {
|
|
1521
1574
|
flag: "wx",
|
|
1522
1575
|
mode: ["Saves/Accounts/accounts.xml", "accounts/sphereaccu.scp"].includes(filename) ? 384 : 420
|
|
1523
1576
|
});
|
|
@@ -1618,7 +1671,18 @@ async function initialize(options, resolveServuoSource = resolveLatestServuoSour
|
|
|
1618
1671
|
prompts5.note(`Username: ${config.servuo?.owner ?? config.sphere?.owner}
|
|
1619
1672
|
Password: ${generatedPassword}
|
|
1620
1673
|
Save this password; it will not be shown again.`, "Owner account");
|
|
1621
|
-
|
|
1674
|
+
const target = relative(process.cwd(), directory);
|
|
1675
|
+
const commands = [];
|
|
1676
|
+
if (target) {
|
|
1677
|
+
const path = /^[\w./-]+$/.test(target) ? target : `'${target.replaceAll("'", "'\\''")}'`;
|
|
1678
|
+
commands.push(`cd -- ${path}`);
|
|
1679
|
+
}
|
|
1680
|
+
commands.push("gemwright deploy");
|
|
1681
|
+
prompts5.outro(`Created ${configFilename} in ${directory}
|
|
1682
|
+
|
|
1683
|
+
Next steps:
|
|
1684
|
+
${commands.join(`
|
|
1685
|
+
`)}`);
|
|
1622
1686
|
}
|
|
1623
1687
|
|
|
1624
1688
|
// src/index.ts
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gemwright",
|
|
3
|
-
"description": "Forge your own shard
|
|
4
|
-
"version": "0.1.
|
|
3
|
+
"description": "Forge your own shard",
|
|
4
|
+
"version": "0.1.5",
|
|
5
|
+
"license": "MIT",
|
|
5
6
|
"repository": {
|
|
6
7
|
"type": "git",
|
|
7
8
|
"url": "git+https://github.com/gokaygurcan/gemwright.git"
|
|
@@ -13,7 +14,8 @@
|
|
|
13
14
|
"files": [
|
|
14
15
|
"dist/index.js",
|
|
15
16
|
"templates/",
|
|
16
|
-
"patches/"
|
|
17
|
+
"patches/",
|
|
18
|
+
".github/small-forge.png"
|
|
17
19
|
],
|
|
18
20
|
"engines": {
|
|
19
21
|
"node": ">=24"
|