@tapestry-mud/cli 0.5.0 → 0.6.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
@@ -27,7 +27,7 @@ const { stopCmd } = require('../src/commands/stop');
27
27
  const { changePassword } = require('../src/commands/change-password');
28
28
  const { unpublish } = require('../src/commands/unpublish');
29
29
  const { distTagSet, distTagList } = require('../src/commands/dist-tag');
30
- const { presetSet } = require('../src/commands/preset');
30
+ const { presetSet, presetDelete } = require('../src/commands/preset');
31
31
 
32
32
  const program = new Command();
33
33
 
@@ -169,6 +169,18 @@ presetCmd
169
169
  }
170
170
  });
171
171
 
172
+ presetCmd
173
+ .command('delete <name>')
174
+ .description('Delete a preset from the registry')
175
+ .action(async (name) => {
176
+ try {
177
+ await presetDelete(name);
178
+ } catch (e) {
179
+ console.error(`error: ${e.message}`);
180
+ process.exit(1);
181
+ }
182
+ });
183
+
172
184
  program
173
185
  .command('install [package]')
174
186
  .description('Install a package or all dependencies from tapestry.yaml')
@@ -342,12 +354,13 @@ program
342
354
  .command('link [path]')
343
355
  .description('Attach a local pack working copy to this project (use --list to show links)')
344
356
  .option('--list', 'List active links instead of creating one')
357
+ .option('--skip-install', 'Skip dependency resolution; warn about missing deps instead')
345
358
  .action(async (linkPath, options) => {
346
359
  try {
347
360
  if (options.list || !linkPath) {
348
361
  await linkList();
349
362
  } else {
350
- await link(linkPath);
363
+ await link(linkPath, { noInstall: !!options.skipInstall });
351
364
  }
352
365
  } catch (e) {
353
366
  console.error(`error: ${e.message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tapestry-mud/cli",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "CLI for the Tapestry MUD engine",
5
5
  "repository": {
6
6
  "type": "git",
@@ -129,4 +129,4 @@ async function install(packageArg, { cwd = process.cwd(), registryUrl = DEFAULT_
129
129
  console.log('Done.');
130
130
  }
131
131
 
132
- module.exports = { install };
132
+ module.exports = { install, installResolved, packInstallPath };
@@ -4,9 +4,14 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const {
6
6
  readLinks, addLink, removeLink, readPackManifest,
7
- removeMaterializedLink, checkMissingDeps,
7
+ removeMaterializedLink, checkMissingDeps, partitionDeps,
8
8
  } = require('../lib/links');
9
9
  const { addPackageToBoot, removePackageFromBoot } = require('../lib/boot');
10
+ const { resolve } = require('../lib/semver-resolver');
11
+ const { installResolved, packInstallPath } = require('./install');
12
+ const { readLock, writeLock } = require('../lib/lock-file');
13
+ const { loadToken } = require('../lib/auth');
14
+ const { DEFAULT_REGISTRY } = require('../lib/registry-client');
10
15
 
11
16
  function requireProject(cwd) {
12
17
  if (!fs.existsSync(path.join(cwd, 'tapestry.yaml'))) {
@@ -25,7 +30,7 @@ function ensureGitignore(cwd) {
25
30
  }
26
31
  }
27
32
 
28
- async function link(targetPath, { cwd = process.cwd() } = {}) {
33
+ async function link(targetPath, { cwd = process.cwd(), noInstall = false, registryUrl = DEFAULT_REGISTRY } = {}) {
29
34
  requireProject(cwd);
30
35
  const absPath = path.resolve(cwd, targetPath);
31
36
  if (!fs.existsSync(absPath)) {
@@ -41,14 +46,66 @@ async function link(targetPath, { cwd = process.cwd() } = {}) {
41
46
  addPackageToBoot(cwd, name, manifest);
42
47
  ensureGitignore(cwd);
43
48
 
44
- console.log(`linked ${name} -> ${absPath}`);
45
-
49
+ // Warn if the pack is marked inactive — fires on all paths
46
50
  if (manifest.active === false) {
47
51
  console.warn(` warning: ${name} is marked active: false; it will not load until activated`);
48
52
  }
49
- for (const dep of checkMissingDeps(cwd, manifest)) {
50
- const range = manifest.dependencies[dep];
51
- console.warn(` warning: missing dependency ${dep} (${range}) -- run: tapestry install ${dep}`);
53
+
54
+ if (noInstall) {
55
+ console.log(`linked ${name} -> ${absPath}`);
56
+ for (const dep of checkMissingDeps(cwd, manifest)) {
57
+ const range = manifest.dependencies[dep];
58
+ console.warn(` warning: missing dependency ${dep} (${range}) -- run: tapestry install ${dep}`);
59
+ }
60
+ return;
61
+ }
62
+
63
+ const { needsInstall } = partitionDeps(cwd, manifest);
64
+
65
+ if (Object.keys(needsInstall).length === 0) {
66
+ console.log(`linked ${name} -> ${absPath}`);
67
+ return;
68
+ }
69
+
70
+ let toRollback = [];
71
+ try {
72
+ const token = loadToken();
73
+ const resolved = await resolve(needsInstall, registryUrl, token);
74
+
75
+ // New installs: not on disk yet. Upgrade targets: in needsInstall AND on disk
76
+ // (installResolved deletes the old dir before downloading; track so rollback removes the boot entry)
77
+ toRollback = [
78
+ ...Object.keys(resolved).filter((n) => !fs.existsSync(packInstallPath(cwd, n))),
79
+ ...Object.keys(needsInstall).filter((n) => fs.existsSync(packInstallPath(cwd, n))),
80
+ ];
81
+
82
+ await installResolved(cwd, resolved, token);
83
+
84
+ const existingLock = readLock(cwd);
85
+ const mergedResolved = Object.assign({}, (existingLock && existingLock.resolved) || {}, resolved);
86
+ writeLock(cwd, {
87
+ lockfile_version: 1,
88
+ ...(existingLock && existingLock.deps_hash ? { deps_hash: existingLock.deps_hash } : {}),
89
+ resolved: mergedResolved,
90
+ });
91
+
92
+ console.log(`linked ${name} -> ${absPath}`);
93
+ for (const [pkgName, info] of Object.entries(resolved)) {
94
+ console.log(` installed ${pkgName}@${info.version} (dependency of ${name})`);
95
+ }
96
+ } catch (err) {
97
+ removeLink(cwd, name);
98
+ removePackageFromBoot(cwd, name);
99
+ for (const pkgName of toRollback) {
100
+ const installPath = packInstallPath(cwd, pkgName);
101
+ if (fs.existsSync(installPath)) {
102
+ fs.rmSync(installPath, { recursive: true });
103
+ }
104
+ removePackageFromBoot(cwd, pkgName);
105
+ }
106
+ throw new Error(
107
+ `Cannot resolve dependencies for ${name} — ${err.message}. Use --skip-install to link without dependency resolution.`
108
+ );
52
109
  }
53
110
  }
54
111
 
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { requireToken } = require('../lib/auth');
4
- const { patchPreset, DEFAULT_REGISTRY } = require('../lib/registry-client');
4
+ const { patchPreset, deletePreset, DEFAULT_REGISTRY } = require('../lib/registry-client');
5
5
 
6
6
  async function presetSet(name, version, engineChannel, packs, { registryUrl = DEFAULT_REGISTRY } = {}) {
7
7
  const token = requireToken();
@@ -10,4 +10,11 @@ async function presetSet(name, version, engineChannel, packs, { registryUrl = DE
10
10
  console.log('Done.');
11
11
  }
12
12
 
13
- module.exports = { presetSet };
13
+ async function presetDelete(name, { registryUrl = DEFAULT_REGISTRY } = {}) {
14
+ const token = requireToken();
15
+ await deletePreset(name, token, registryUrl);
16
+ console.log(` Deleted preset '${name}'`);
17
+ console.log('Done.');
18
+ }
19
+
20
+ module.exports = { presetSet, presetDelete };
package/src/lib/links.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  const path = require('path');
4
4
  const fs = require('fs');
5
+ const semver = require('semver');
5
6
  const { readYaml, writeYaml } = require('../util/yaml');
6
7
  const { PACK_MANIFEST } = require('./manifest');
7
8
 
@@ -113,8 +114,34 @@ function checkMissingDeps(cwd, manifest) {
113
114
  return missing;
114
115
  }
115
116
 
117
+ function partitionDeps(cwd, manifest) {
118
+ const deps = (manifest && manifest.dependencies) || {};
119
+ const needsInstall = {};
120
+ if (Object.keys(deps).length === 0) {
121
+ return { needsInstall };
122
+ }
123
+ const { links } = readLinks(cwd);
124
+ for (const [depName, range] of Object.entries(deps)) {
125
+ if (depName in links) {
126
+ continue;
127
+ }
128
+ const installPath = packLinkPath(cwd, depName);
129
+ if (fs.existsSync(installPath)) {
130
+ const manifestPath = path.join(installPath, PACK_MANIFEST);
131
+ if (fs.existsSync(manifestPath)) {
132
+ const installed = readYaml(manifestPath) || {};
133
+ if (installed.version && semver.satisfies(installed.version, range)) {
134
+ continue;
135
+ }
136
+ }
137
+ }
138
+ needsInstall[depName] = range;
139
+ }
140
+ return { needsInstall };
141
+ }
142
+
116
143
  module.exports = {
117
144
  LINKS_FILE, readLinks, writeLinks, addLink, removeLink,
118
145
  readPackManifest, packLinkPath, containerPackTarget, dockerLinkMounts,
119
- materializeLinks, removeMaterializedLink, checkMissingDeps,
146
+ materializeLinks, removeMaterializedLink, checkMissingDeps, partitionDeps,
120
147
  };
@@ -116,7 +116,19 @@ async function patchPreset(name, payload, token, registryUrl = DEFAULT_REGISTRY)
116
116
  return res.json();
117
117
  }
118
118
 
119
+ async function deletePreset(name, token, registryUrl = DEFAULT_REGISTRY) {
120
+ const url = `${registryUrl.replace(/\/$/, '')}/v1/admin/presets/${name}`;
121
+ const res = await fetch(url, {
122
+ method: 'DELETE',
123
+ headers: {
124
+ Authorization: `Bearer ${token}`,
125
+ },
126
+ });
127
+ await throwIfError(res, `Failed to delete preset '${name}'`);
128
+ return res.json();
129
+ }
130
+
119
131
  module.exports = {
120
132
  fetchPackageMetadata, fetchTarball, throwIfError, DEFAULT_REGISTRY,
121
- fetchPreset, fetchPresetList, patchDistTag, listDistTags, patchPreset,
133
+ fetchPreset, fetchPresetList, patchDistTag, listDistTags, patchPreset, deletePreset,
122
134
  };