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.
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 writeFile3 } from "node:fs/promises";
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 writeFile2 } from "node:fs/promises";
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 writeFile2(archive, new Uint8Array(await response.arrayBuffer()));
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 writeFile3(`${path}.tmp`, JSON.stringify(state, null, 2) + `
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 writeFile4 } from "node:fs/promises";
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 writeFile4(join8(directory, filename), content, {
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
- prompts5.outro(`Created ${configFilename} in ${directory}`);
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",
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"