@tapestry-mud/cli 0.3.10 → 0.4.0

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/bin/tapestry.js CHANGED
@@ -255,7 +255,7 @@ program
255
255
 
256
256
  program
257
257
  .command('validate')
258
- .description('Validate tapestry.yaml in the current directory')
258
+ .description('Validate pack.yaml in the current directory')
259
259
  .action(() => {
260
260
  try {
261
261
  validate();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tapestry-mud/cli",
3
- "version": "0.3.10",
3
+ "version": "0.4.0",
4
4
  "description": "CLI for the Tapestry MUD engine",
5
5
  "bin": {
6
6
  "tapestry": "./bin/tapestry.js"
@@ -3,6 +3,7 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const { generatePackFiles } = require('../scaffold/templates');
6
+ const { PACK_MANIFEST } = require('../lib/manifest');
6
7
 
7
8
  function parseName(name) {
8
9
  const scopedMatch = name.match(/^@([a-z0-9-]+)\/([a-z0-9-]+)$/);
@@ -59,7 +60,7 @@ function createPack(name, cwd) {
59
60
  console.log(` ${file.path}`);
60
61
  }
61
62
  }
62
- console.log('\nEdit tapestry.yaml, then run: tapestry validate');
63
+ console.log(`\nEdit ${PACK_MANIFEST}, then run: tapestry validate`);
63
64
  }
64
65
  }
65
66
 
@@ -28,7 +28,7 @@ function buildManifest(name, deps, engineVersion, engineChannel) {
28
28
  ].join('\n');
29
29
  }
30
30
 
31
- function buildServerYaml({ serverName, adminHandle, adminPassword, telemetry }) {
31
+ function buildServerYaml({ serverName, adminHandle, adminEmail, adminPassword, telemetry }) {
32
32
  const telemetryBlock = telemetry
33
33
  ? [
34
34
  `telemetry:`,
@@ -59,6 +59,7 @@ function buildServerYaml({ serverName, adminHandle, adminPassword, telemetry })
59
59
  ``,
60
60
  `admin:`,
61
61
  ` handle: ${adminHandle}`,
62
+ ` email: ${adminEmail}`,
62
63
  ` password: ${adminPassword}`,
63
64
  ``,
64
65
  `# --- Telemetry (OpenTelemetry) ---`,
@@ -105,6 +106,15 @@ function buildServerYaml({ serverName, adminHandle, adminPassword, telemetry })
105
106
  `# pre_auth:`,
106
107
  `# enabled: false`,
107
108
  `# token_expiry_seconds: 60`,
109
+ ``,
110
+ `# --- Accounts ---`,
111
+ `# accounts:`,
112
+ `# max_concurrent_characters: 1`,
113
+ ``,
114
+ `# --- Link-Dead (player disconnect grace period) ---`,
115
+ `# link_dead:`,
116
+ `# enabled: true`,
117
+ `# timeout_seconds: 120`,
108
118
  ].join('\n');
109
119
  }
110
120
 
@@ -157,6 +167,7 @@ async function init(cwd, { registryUrl = DEFAULT_REGISTRY, yes = false, prompter
157
167
  answers = {
158
168
  gameName: dirName,
159
169
  adminHandle: 'admin',
170
+ adminEmail: 'admin@localhost',
160
171
  adminPassword: 'changeme',
161
172
  telemetry: false,
162
173
  };
@@ -177,6 +188,17 @@ async function init(cwd, { registryUrl = DEFAULT_REGISTRY, yes = false, prompter
177
188
  message: 'Admin handle:',
178
189
  validate: (v) => (v.trim().length > 0 && !/\s/.test(v)) || 'Required, no spaces',
179
190
  },
191
+ {
192
+ type: 'input',
193
+ name: 'adminEmail',
194
+ message: 'Admin email:',
195
+ validate: (v) => {
196
+ v = v.trim();
197
+ if (!v) { return 'Required'; }
198
+ if (!v.includes('@') || !v.includes('.')) { return 'Must be a valid email'; }
199
+ return true;
200
+ },
201
+ },
180
202
  {
181
203
  type: 'password',
182
204
  name: 'adminPassword',
@@ -208,6 +230,7 @@ async function init(cwd, { registryUrl = DEFAULT_REGISTRY, yes = false, prompter
208
230
  buildServerYaml({
209
231
  serverName: answers.gameName,
210
232
  adminHandle: answers.adminHandle,
233
+ adminEmail: answers.adminEmail,
211
234
  adminPassword: answers.adminPassword,
212
235
  telemetry: answers.telemetry,
213
236
  })
@@ -5,11 +5,12 @@ const os = require('os');
5
5
  const path = require('path');
6
6
  const { readYaml, writeYaml } = require('../util/yaml');
7
7
  const { resolve } = require('../lib/semver-resolver');
8
- const { readLock, writeLock } = require('../lib/lock-file');
8
+ const { readLock, writeLock, hashDeps } = require('../lib/lock-file');
9
9
  const { fetchTarball, DEFAULT_REGISTRY } = require('../lib/registry-client');
10
10
  const { verifyIntegrity, saveTarball, extractTarball } = require('../lib/tarball');
11
11
  const { addPackageToBoot } = require('../lib/boot');
12
12
  const { loadToken } = require('../lib/auth');
13
+ const { PACK_MANIFEST } = require('../lib/manifest');
13
14
 
14
15
  function packInstallPath(cwd, packageName) {
15
16
  const parts = packageName.split('/');
@@ -25,6 +26,9 @@ function parsePackageArg(arg) {
25
26
  }
26
27
 
27
28
  function isLockCurrent(manifestDeps, lock) {
29
+ if (!lock.deps_hash || lock.deps_hash !== hashDeps(manifestDeps)) {
30
+ return false;
31
+ }
28
32
  const lockResolved = lock.resolved || {};
29
33
  return Object.keys(manifestDeps).every((name) => lockResolved[name]);
30
34
  }
@@ -34,8 +38,18 @@ async function installResolved(cwd, resolved, token) {
34
38
  const destDir = packInstallPath(cwd, packageName);
35
39
 
36
40
  if (fs.existsSync(destDir)) {
37
- console.log(` already installed ${packageName}@${info.version}`);
38
- continue;
41
+ const installedManifestPath = path.join(destDir, PACK_MANIFEST);
42
+ if (fs.existsSync(installedManifestPath)) {
43
+ const installed = readYaml(installedManifestPath);
44
+ if (installed.version === info.version) {
45
+ console.log(` already installed ${packageName}@${info.version}`);
46
+ continue;
47
+ }
48
+ console.log(` upgrading ${packageName} ${installed.version} -> ${info.version}`);
49
+ } else {
50
+ console.log(` reinstalling ${packageName}@${info.version} (missing manifest)`);
51
+ }
52
+ fs.rmSync(destDir, { recursive: true });
39
53
  }
40
54
 
41
55
  console.log(` installing ${packageName}@${info.version}`);
@@ -54,7 +68,7 @@ async function installResolved(cwd, resolved, token) {
54
68
  }
55
69
  }
56
70
 
57
- const packManifest = readYaml(path.join(destDir, 'tapestry.yaml'));
71
+ const packManifest = readYaml(path.join(destDir, PACK_MANIFEST));
58
72
  addPackageToBoot(cwd, packageName, packManifest);
59
73
  }
60
74
  }
@@ -95,7 +109,8 @@ async function install(packageArg, { cwd = process.cwd(), registryUrl = DEFAULT_
95
109
  }
96
110
 
97
111
  await installResolved(cwd, resolved, token);
98
- writeLock(cwd, { lockfile_version: 1, resolved });
112
+ const deps = manifest.dependencies || {};
113
+ writeLock(cwd, { lockfile_version: 1, deps_hash: hashDeps(deps), resolved });
99
114
  console.log('Done.');
100
115
  }
101
116
 
@@ -5,6 +5,7 @@ const path = require('path');
5
5
  const { readLock } = require('../lib/lock-file');
6
6
  const { readBoot } = require('../lib/boot');
7
7
  const { readYaml } = require('../util/yaml');
8
+ const { PACK_MANIFEST } = require('../lib/manifest');
8
9
 
9
10
  function packInstallPath(cwd, packageName) {
10
11
  const parts = packageName.split('/');
@@ -32,7 +33,7 @@ async function list({ cwd = process.cwd() } = {}) {
32
33
  const enabled = boot.packs[pkgName]?.enabled !== false ? 'enabled' : 'disabled';
33
34
 
34
35
  let type = '';
35
- const packManifestPath = path.join(packInstallPath(cwd, pkgName), 'tapestry.yaml');
36
+ const packManifestPath = path.join(packInstallPath(cwd, pkgName), PACK_MANIFEST);
36
37
  if (fs.existsSync(packManifestPath)) {
37
38
  try {
38
39
  type = readYaml(packManifestPath).type || '';
@@ -3,12 +3,13 @@
3
3
  const path = require('path');
4
4
  const { readYaml } = require('../util/yaml');
5
5
  const { buildTarball, computeIntegrity } = require('../lib/tarball-builder');
6
+ const { PACK_MANIFEST } = require('../lib/manifest');
6
7
  const { validate } = require('./validate');
7
8
 
8
9
  async function pack({ cwd = process.cwd() } = {}) {
9
10
  validate({ cwd });
10
11
 
11
- const manifest = readYaml(path.join(cwd, 'tapestry.yaml'));
12
+ const manifest = readYaml(path.join(cwd, PACK_MANIFEST));
12
13
  const shortName = manifest.name.split('/')[1];
13
14
  const outputPath = path.join(cwd, `${shortName}-${manifest.version}.tgz`);
14
15
 
@@ -8,13 +8,14 @@ const FormData = require('form-data');
8
8
  const { readYaml } = require('../util/yaml');
9
9
  const { validate } = require('./validate');
10
10
  const { buildTarball, computeIntegrity } = require('../lib/tarball-builder');
11
+ const { PACK_MANIFEST } = require('../lib/manifest');
11
12
  const { requireToken } = require('../lib/auth');
12
13
  const { DEFAULT_REGISTRY, throwIfError } = require('../lib/registry-client');
13
14
 
14
15
  async function publish({ cwd = process.cwd(), registryUrl = DEFAULT_REGISTRY } = {}) {
15
16
  validate({ cwd });
16
17
 
17
- const manifest = readYaml(path.join(cwd, 'tapestry.yaml'));
18
+ const manifest = readYaml(path.join(cwd, PACK_MANIFEST));
18
19
  const token = requireToken();
19
20
 
20
21
  const shortName = manifest.name.split('/')[1];
@@ -9,6 +9,7 @@ const { readLock, writeLock } = require('../lib/lock-file');
9
9
  const { fetchTarball, DEFAULT_REGISTRY } = require('../lib/registry-client');
10
10
  const { verifyIntegrity, saveTarball, extractTarball } = require('../lib/tarball');
11
11
  const { addPackageToBoot } = require('../lib/boot');
12
+ const { PACK_MANIFEST } = require('../lib/manifest');
12
13
 
13
14
  function packInstallPath(cwd, packageName) {
14
15
  const parts = packageName.split('/');
@@ -69,7 +70,7 @@ async function update(packageArg, { cwd = process.cwd(), registryUrl = DEFAULT_R
69
70
  }
70
71
  }
71
72
 
72
- const packManifest = readYaml(path.join(destDir, 'tapestry.yaml'));
73
+ const packManifest = readYaml(path.join(destDir, PACK_MANIFEST));
73
74
  addPackageToBoot(cwd, packageName, packManifest);
74
75
  }
75
76
 
@@ -4,11 +4,20 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const { readYaml } = require('../util/yaml');
6
6
  const { validatePackageManifest } = require('../schema/manifest');
7
+ const { PACK_MANIFEST } = require('../lib/manifest');
7
8
 
8
9
  function validate({ cwd = process.cwd() } = {}) {
9
- const manifestPath = path.join(cwd, 'tapestry.yaml');
10
+ const manifestPath = path.join(cwd, PACK_MANIFEST);
10
11
  if (!fs.existsSync(manifestPath)) {
11
- throw new Error('No tapestry.yaml found in current directory');
12
+ const serverPath = path.join(cwd, 'tapestry.yaml');
13
+ if (fs.existsSync(serverPath)) {
14
+ throw new Error(
15
+ `No ${PACK_MANIFEST} found in current directory. ` +
16
+ `The tapestry.yaml here is a server manifest. ` +
17
+ `Pack validation requires ${PACK_MANIFEST}.`
18
+ );
19
+ }
20
+ throw new Error(`No ${PACK_MANIFEST} found in current directory`);
12
21
  }
13
22
 
14
23
  const data = readYaml(manifestPath);
@@ -18,7 +27,11 @@ function validate({ cwd = process.cwd() } = {}) {
18
27
  if (!result.success) {
19
28
  for (const issue of result.error.issues) {
20
29
  const fieldPath = issue.path.join('.') || 'root';
21
- console.log(` error: ${fieldPath} - ${issue.message}`);
30
+ let message = issue.message;
31
+ if (fieldPath === 'engine' && data.engine && typeof data.engine === 'object') {
32
+ message += `. engine must be a version constraint string in pack manifests (e.g. '>=0.0.1'). Object format is for server manifests (tapestry.yaml).`;
33
+ }
34
+ console.log(` error: ${fieldPath} - ${message}`);
22
35
  }
23
36
  throw new Error(`${result.error.issues.length} validation error(s)`);
24
37
  }
@@ -1,11 +1,17 @@
1
1
  'use strict';
2
2
 
3
+ const crypto = require('crypto');
3
4
  const fs = require('fs');
4
5
  const path = require('path');
5
6
  const { readYaml, writeYaml } = require('../util/yaml');
6
7
 
7
8
  const LOCK_FILE = 'tapestry-lock.yaml';
8
9
 
10
+ function hashDeps(deps) {
11
+ const sorted = Object.keys(deps).sort().map((k) => `${k}@${deps[k]}`).join('\n');
12
+ return crypto.createHash('sha256').update(sorted).digest('hex');
13
+ }
14
+
9
15
  function readLock(cwd) {
10
16
  const lockPath = path.join(cwd, LOCK_FILE);
11
17
  if (!fs.existsSync(lockPath)) {
@@ -18,4 +24,4 @@ function writeLock(cwd, lock) {
18
24
  writeYaml(path.join(cwd, LOCK_FILE), lock);
19
25
  }
20
26
 
21
- module.exports = { readLock, writeLock };
27
+ module.exports = { readLock, writeLock, hashDeps };
@@ -0,0 +1,5 @@
1
+ 'use strict';
2
+
3
+ const PACK_MANIFEST = 'pack.yaml';
4
+
5
+ module.exports = { PACK_MANIFEST };
@@ -404,7 +404,7 @@ see_also: [help, commands]
404
404
 
405
405
  function generatePackFiles({ scopedName, shortName }) {
406
406
  return [
407
- { path: 'tapestry.yaml', content: manifestTemplate(scopedName) },
407
+ { path: 'pack.yaml', content: manifestTemplate(scopedName) },
408
408
  { path: 'tags.yml', content: tagsTemplate() },
409
409
  { path: 'areas/example-area/area.yaml', content: areaTemplate() },
410
410
  { path: 'areas/example-area/rooms/town-square.yaml', content: roomTemplate(shortName) },