@tapestry-mud/cli 0.1.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 +288 -0
- package/package.json +26 -0
- package/src/commands/create-pack.js +66 -0
- package/src/commands/disable.js +16 -0
- package/src/commands/enable.js +16 -0
- package/src/commands/engine.js +25 -0
- package/src/commands/info.js +51 -0
- package/src/commands/init.js +63 -0
- package/src/commands/install.js +100 -0
- package/src/commands/list.js +50 -0
- package/src/commands/login.js +40 -0
- package/src/commands/outdated.js +43 -0
- package/src/commands/pack.js +24 -0
- package/src/commands/publish.js +63 -0
- package/src/commands/register.js +41 -0
- package/src/commands/search.js +41 -0
- package/src/commands/start.js +9 -0
- package/src/commands/stop.js +9 -0
- package/src/commands/uninstall.js +46 -0
- package/src/commands/update.js +80 -0
- package/src/commands/validate.js +29 -0
- package/src/lib/auth.js +34 -0
- package/src/lib/boot.js +112 -0
- package/src/lib/engine-manager.js +294 -0
- package/src/lib/lock-file.js +21 -0
- package/src/lib/process-tracker.js +28 -0
- package/src/lib/registry-client.js +46 -0
- package/src/lib/semver-resolver.js +75 -0
- package/src/lib/tarball-builder.js +35 -0
- package/src/lib/tarball.js +26 -0
- package/src/scaffold/templates.js +415 -0
- package/src/schema/manifest.js +74 -0
- package/src/util/prompt.js +28 -0
- package/src/util/yaml.js +18 -0
package/bin/tapestry.js
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { Command } = require('commander');
|
|
5
|
+
const { init } = require('../src/commands/init');
|
|
6
|
+
const { createPack } = require('../src/commands/create-pack');
|
|
7
|
+
const { install } = require('../src/commands/install');
|
|
8
|
+
const { uninstall } = require('../src/commands/uninstall');
|
|
9
|
+
const { update } = require('../src/commands/update');
|
|
10
|
+
const { enable } = require('../src/commands/enable');
|
|
11
|
+
const { disable } = require('../src/commands/disable');
|
|
12
|
+
const { login } = require('../src/commands/login');
|
|
13
|
+
const { register } = require('../src/commands/register');
|
|
14
|
+
const { validate } = require('../src/commands/validate');
|
|
15
|
+
const { pack } = require('../src/commands/pack');
|
|
16
|
+
const { publish } = require('../src/commands/publish');
|
|
17
|
+
const { search } = require('../src/commands/search');
|
|
18
|
+
const { info } = require('../src/commands/info');
|
|
19
|
+
const { list } = require('../src/commands/list');
|
|
20
|
+
const { outdated } = require('../src/commands/outdated');
|
|
21
|
+
const { engineInstall, engineUpdate, engineInfo } = require('../src/commands/engine');
|
|
22
|
+
const { startCmd } = require('../src/commands/start');
|
|
23
|
+
const { stopCmd } = require('../src/commands/stop');
|
|
24
|
+
|
|
25
|
+
const program = new Command();
|
|
26
|
+
|
|
27
|
+
program
|
|
28
|
+
.name('tapestry')
|
|
29
|
+
.description('Tapestry Package Manager')
|
|
30
|
+
.version('0.1.0');
|
|
31
|
+
|
|
32
|
+
program
|
|
33
|
+
.command('init')
|
|
34
|
+
.description('Initialize a new Tapestry game project in the current directory')
|
|
35
|
+
.action(() => {
|
|
36
|
+
try {
|
|
37
|
+
init();
|
|
38
|
+
} catch (e) {
|
|
39
|
+
console.error(`error: ${e.message}`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const createCmd = program.command('create');
|
|
45
|
+
|
|
46
|
+
createCmd
|
|
47
|
+
.command('pack <name>')
|
|
48
|
+
.description('Scaffold a new pack with annotated example content')
|
|
49
|
+
.action((name) => {
|
|
50
|
+
try {
|
|
51
|
+
createPack(name);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.error(`error: ${e.message}`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
program
|
|
59
|
+
.command('install [package]')
|
|
60
|
+
.description('Install a package or all dependencies from tapestry.yaml')
|
|
61
|
+
.action(async (pkg) => {
|
|
62
|
+
try {
|
|
63
|
+
await install(pkg || undefined);
|
|
64
|
+
} catch (e) {
|
|
65
|
+
console.error(`error: ${e.message}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
program
|
|
71
|
+
.command('uninstall <package>')
|
|
72
|
+
.description('Remove an installed package')
|
|
73
|
+
.action(async (pkg) => {
|
|
74
|
+
try {
|
|
75
|
+
await uninstall(pkg);
|
|
76
|
+
} catch (e) {
|
|
77
|
+
console.error(`error: ${e.message}`);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
program
|
|
83
|
+
.command('update [package]')
|
|
84
|
+
.description('Update a package or all packages to latest compatible versions')
|
|
85
|
+
.action(async (pkg) => {
|
|
86
|
+
try {
|
|
87
|
+
await update(pkg || undefined);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
console.error(`error: ${e.message}`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
program
|
|
95
|
+
.command('enable <package>')
|
|
96
|
+
.description('Activate a package in the engine boot order')
|
|
97
|
+
.action(async (pkg) => {
|
|
98
|
+
try {
|
|
99
|
+
await enable(pkg);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
console.error(`error: ${e.message}`);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
program
|
|
107
|
+
.command('disable <package>')
|
|
108
|
+
.description('Remove a package from the engine boot order without deleting files')
|
|
109
|
+
.action(async (pkg) => {
|
|
110
|
+
try {
|
|
111
|
+
await disable(pkg);
|
|
112
|
+
} catch (e) {
|
|
113
|
+
console.error(`error: ${e.message}`);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
program
|
|
119
|
+
.command('login')
|
|
120
|
+
.description('Authenticate with the registry and store token in ~/.tapestryrc')
|
|
121
|
+
.action(async () => {
|
|
122
|
+
try {
|
|
123
|
+
await login();
|
|
124
|
+
} catch (e) {
|
|
125
|
+
console.error(`error: ${e.message}`);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
program
|
|
131
|
+
.command('register')
|
|
132
|
+
.description('Create an account on the registry')
|
|
133
|
+
.action(async () => {
|
|
134
|
+
try {
|
|
135
|
+
await register();
|
|
136
|
+
} catch (e) {
|
|
137
|
+
console.error(`error: ${e.message}`);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
program
|
|
143
|
+
.command('validate')
|
|
144
|
+
.description('Validate tapestry.yaml in the current directory')
|
|
145
|
+
.action(() => {
|
|
146
|
+
try {
|
|
147
|
+
validate();
|
|
148
|
+
} catch (e) {
|
|
149
|
+
console.error(`error: ${e.message}`);
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
program
|
|
155
|
+
.command('pack')
|
|
156
|
+
.description('Build a tarball from the current pack directory for local inspection')
|
|
157
|
+
.action(async () => {
|
|
158
|
+
try {
|
|
159
|
+
await pack();
|
|
160
|
+
} catch (e) {
|
|
161
|
+
console.error(`error: ${e.message}`);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
program
|
|
167
|
+
.command('publish')
|
|
168
|
+
.description('Build and upload the current pack to the registry')
|
|
169
|
+
.action(async () => {
|
|
170
|
+
try {
|
|
171
|
+
await publish();
|
|
172
|
+
} catch (e) {
|
|
173
|
+
console.error(`error: ${e.message}`);
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
program
|
|
179
|
+
.command('search <query>')
|
|
180
|
+
.description('Search the registry by keyword')
|
|
181
|
+
.action(async (query) => {
|
|
182
|
+
try {
|
|
183
|
+
await search(query);
|
|
184
|
+
} catch (e) {
|
|
185
|
+
console.error(`error: ${e.message}`);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
program
|
|
191
|
+
.command('info <package>')
|
|
192
|
+
.description('Show details for a registry package')
|
|
193
|
+
.action(async (pkg) => {
|
|
194
|
+
try {
|
|
195
|
+
await info(pkg);
|
|
196
|
+
} catch (e) {
|
|
197
|
+
console.error(`error: ${e.message}`);
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
program
|
|
203
|
+
.command('list')
|
|
204
|
+
.description('Show installed packages with version, type, and enabled/disabled status')
|
|
205
|
+
.action(async () => {
|
|
206
|
+
try {
|
|
207
|
+
await list();
|
|
208
|
+
} catch (e) {
|
|
209
|
+
console.error(`error: ${e.message}`);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
program
|
|
215
|
+
.command('outdated')
|
|
216
|
+
.description('Show installed packages with newer versions available in the registry')
|
|
217
|
+
.action(async () => {
|
|
218
|
+
try {
|
|
219
|
+
await outdated();
|
|
220
|
+
} catch (e) {
|
|
221
|
+
console.error(`error: ${e.message}`);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const engineCmd = program.command('engine').description('Manage the Tapestry engine');
|
|
227
|
+
|
|
228
|
+
engineCmd
|
|
229
|
+
.command('install')
|
|
230
|
+
.description('Fetch the engine artifact for the configured mode (docker/binary/source)')
|
|
231
|
+
.action(async () => {
|
|
232
|
+
try {
|
|
233
|
+
await engineInstall();
|
|
234
|
+
} catch (e) {
|
|
235
|
+
console.error(`error: ${e.message}`);
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
engineCmd
|
|
241
|
+
.command('update')
|
|
242
|
+
.description('Update the engine to the configured version')
|
|
243
|
+
.action(async () => {
|
|
244
|
+
try {
|
|
245
|
+
await engineUpdate();
|
|
246
|
+
} catch (e) {
|
|
247
|
+
console.error(`error: ${e.message}`);
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
engineCmd
|
|
253
|
+
.command('info')
|
|
254
|
+
.description('Show installed engine version, mode, and image or path')
|
|
255
|
+
.action(() => {
|
|
256
|
+
try {
|
|
257
|
+
engineInfo();
|
|
258
|
+
} catch (e) {
|
|
259
|
+
console.error(`error: ${e.message}`);
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
program
|
|
265
|
+
.command('start')
|
|
266
|
+
.description('Launch the Tapestry engine')
|
|
267
|
+
.action(async () => {
|
|
268
|
+
try {
|
|
269
|
+
await startCmd();
|
|
270
|
+
} catch (e) {
|
|
271
|
+
console.error(`error: ${e.message}`);
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
program
|
|
277
|
+
.command('stop')
|
|
278
|
+
.description('Stop the running Tapestry engine')
|
|
279
|
+
.action(async () => {
|
|
280
|
+
try {
|
|
281
|
+
await stopCmd();
|
|
282
|
+
} catch (e) {
|
|
283
|
+
console.error(`error: ${e.message}`);
|
|
284
|
+
process.exit(1);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
program.parse(process.argv);
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tapestry-mud/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for the Tapestry MUD engine",
|
|
5
|
+
"bin": {
|
|
6
|
+
"tapestry": "./bin/tapestry.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"test": "jest"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"commander": "^11.1.0",
|
|
16
|
+
"form-data": "^4.0.5",
|
|
17
|
+
"js-yaml": "^4.1.0",
|
|
18
|
+
"node-fetch": "^2.7.0",
|
|
19
|
+
"semver": "^7.6.2",
|
|
20
|
+
"tar": "^6.2.1",
|
|
21
|
+
"zod": "^3.22.4"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"jest": "^29.7.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { generatePackFiles } = require('../scaffold/templates');
|
|
6
|
+
|
|
7
|
+
function parseName(name) {
|
|
8
|
+
const scopedMatch = name.match(/^@([a-z0-9-]+)\/([a-z0-9-]+)$/);
|
|
9
|
+
if (scopedMatch) {
|
|
10
|
+
{
|
|
11
|
+
return { scopedName: name, shortName: scopedMatch[2], scope: scopedMatch[1] };
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const plainMatch = name.match(/^[a-z0-9-]+$/);
|
|
15
|
+
if (plainMatch) {
|
|
16
|
+
{
|
|
17
|
+
return { scopedName: `@todo/${name}`, shortName: name, scope: 'todo' };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function createPack(name, cwd) {
|
|
24
|
+
{
|
|
25
|
+
if (cwd === undefined) {
|
|
26
|
+
{
|
|
27
|
+
cwd = process.cwd();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const parsed = parseName(name);
|
|
31
|
+
if (!parsed) {
|
|
32
|
+
{
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Invalid pack name: ${name}\n` +
|
|
35
|
+
'Expected @scope/name or plain-name (lowercase letters and hyphens only)'
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const packDir = path.join(cwd, parsed.shortName);
|
|
41
|
+
if (fs.existsSync(packDir)) {
|
|
42
|
+
{
|
|
43
|
+
throw new Error(`Directory already exists: ${packDir}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const files = generatePackFiles(parsed);
|
|
48
|
+
for (const file of files) {
|
|
49
|
+
{
|
|
50
|
+
const filePath = path.join(packDir, file.path);
|
|
51
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
52
|
+
fs.writeFileSync(filePath, file.content);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log(`Created pack: ${parsed.scopedName}`);
|
|
57
|
+
for (const file of files) {
|
|
58
|
+
{
|
|
59
|
+
console.log(` ${file.path}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
console.log('\nEdit tapestry.yaml, then run: tapestry validate');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = { createPack, parseName };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { disablePackage } = require('../lib/boot');
|
|
6
|
+
|
|
7
|
+
async function disable(packageName, { cwd = process.cwd() } = {}) {
|
|
8
|
+
const manifestPath = path.join(cwd, 'tapestry.yaml');
|
|
9
|
+
if (!fs.existsSync(manifestPath)) {
|
|
10
|
+
throw new Error('No tapestry.yaml found. Run `tapestry init` first.');
|
|
11
|
+
}
|
|
12
|
+
disablePackage(cwd, packageName);
|
|
13
|
+
console.log(`Disabled ${packageName}. Files remain on disk. Run \`tapestry enable ${packageName}\` to re-activate.`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = { disable };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { enablePackage } = require('../lib/boot');
|
|
6
|
+
|
|
7
|
+
async function enable(packageName, { cwd = process.cwd() } = {}) {
|
|
8
|
+
const manifestPath = path.join(cwd, 'tapestry.yaml');
|
|
9
|
+
if (!fs.existsSync(manifestPath)) {
|
|
10
|
+
throw new Error('No tapestry.yaml found. Run `tapestry init` first.');
|
|
11
|
+
}
|
|
12
|
+
enablePackage(cwd, packageName);
|
|
13
|
+
console.log(`Enabled ${packageName}.`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = { enable };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { installEngine, updateEngine, getEngineInfo } = require('../lib/engine-manager');
|
|
4
|
+
|
|
5
|
+
async function engineInstall({ cwd = process.cwd() } = {}) {
|
|
6
|
+
await installEngine(cwd);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function engineUpdate({ cwd = process.cwd() } = {}) {
|
|
10
|
+
await updateEngine(cwd);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function engineInfo({ cwd = process.cwd() } = {}) {
|
|
14
|
+
const info = getEngineInfo(cwd);
|
|
15
|
+
console.log(`Mode: ${info.mode}`);
|
|
16
|
+
console.log(`Version: ${info.version}`);
|
|
17
|
+
if (info.mode === 'docker') {
|
|
18
|
+
console.log(`Image: ${info.image}`);
|
|
19
|
+
} else {
|
|
20
|
+
console.log(`Path: ${info.path}`);
|
|
21
|
+
console.log(`Status: ${info.installed ? 'installed' : 'not installed'}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = { engineInstall, engineUpdate, engineInfo };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { fetchPackageMetadata } = require('../lib/registry-client');
|
|
4
|
+
|
|
5
|
+
async function info(packageName, { registryUrl } = {}) {
|
|
6
|
+
if (!packageName) {
|
|
7
|
+
throw new Error('Usage: tapestry info <package>');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const data = await fetchPackageMetadata(packageName, registryUrl);
|
|
11
|
+
const latest = data.versions?.[0];
|
|
12
|
+
const m = latest?.manifest || {};
|
|
13
|
+
|
|
14
|
+
console.log(data.name);
|
|
15
|
+
if (m.description) {
|
|
16
|
+
console.log(m.description);
|
|
17
|
+
}
|
|
18
|
+
console.log('');
|
|
19
|
+
if (m.author) {
|
|
20
|
+
console.log(` Author: ${typeof m.author === 'string' ? m.author : m.author.handle}`);
|
|
21
|
+
}
|
|
22
|
+
if (m.license) {
|
|
23
|
+
console.log(` License: ${m.license}`);
|
|
24
|
+
}
|
|
25
|
+
if (m.type) {
|
|
26
|
+
console.log(` Type: ${m.type}`);
|
|
27
|
+
}
|
|
28
|
+
if (latest?.version) {
|
|
29
|
+
console.log(` Latest: ${latest.version}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (data.versions?.length > 1) {
|
|
33
|
+
console.log(` Versions: ${data.versions.map((v) => v.version).join(' ')}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const deps = m.dependencies ? Object.entries(m.dependencies) : [];
|
|
37
|
+
if (deps.length) {
|
|
38
|
+
console.log('');
|
|
39
|
+
console.log(' Dependencies:');
|
|
40
|
+
for (const [dep, range] of deps) {
|
|
41
|
+
console.log(` ${dep} ${range}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (m.meta?.keywords?.length) {
|
|
46
|
+
console.log('');
|
|
47
|
+
console.log(` Keywords: ${m.meta.keywords.join(', ')}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = { info };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function buildManifest(name) {
|
|
7
|
+
return [
|
|
8
|
+
`name: ${name}`,
|
|
9
|
+
`engine:`,
|
|
10
|
+
` version: '0.0.1'`,
|
|
11
|
+
` mode: docker`,
|
|
12
|
+
` image: ghcr.io/tapestry-mud/tapestry`,
|
|
13
|
+
`dependencies:`,
|
|
14
|
+
` '@tapestry/core': '^0.0.1'`,
|
|
15
|
+
` # Starter races, classes, and tutorial area. Remove or replace with your own content pack.`,
|
|
16
|
+
` '@tapestry/example-pack': '^0.0.1'`,
|
|
17
|
+
`packs: []`,
|
|
18
|
+
`tag_validation: strict`,
|
|
19
|
+
``,
|
|
20
|
+
].join('\n');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function init(cwd) {
|
|
24
|
+
{
|
|
25
|
+
if (cwd === undefined) {
|
|
26
|
+
{
|
|
27
|
+
cwd = process.cwd();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const manifestPath = path.join(cwd, 'tapestry.yaml');
|
|
32
|
+
if (fs.existsSync(manifestPath)) {
|
|
33
|
+
{
|
|
34
|
+
throw new Error('tapestry.yaml already exists. Run tapestry install to install dependencies.');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const name = path.basename(cwd);
|
|
39
|
+
fs.writeFileSync(manifestPath, buildManifest(name));
|
|
40
|
+
fs.writeFileSync(path.join(cwd, 'server.yaml'), '# Tapestry server configuration\n# See https://tapestryengine.com/docs/config for full options\nport: 4000\n');
|
|
41
|
+
fs.mkdirSync(path.join(cwd, 'packs'), { recursive: true });
|
|
42
|
+
fs.writeFileSync(
|
|
43
|
+
path.join(cwd, '.gitignore'),
|
|
44
|
+
'# Installed packages (managed by tapestry install)\npacks/\n\n# Engine artifacts (managed by tapestry engine install)\n.tapestry-engine/\n'
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
console.log(`Initialized: ${name}`);
|
|
48
|
+
console.log(' tapestry.yaml project manifest');
|
|
49
|
+
console.log(' server.yaml engine config');
|
|
50
|
+
console.log(' packs/ installed packages');
|
|
51
|
+
console.log(' .gitignore excludes packs/ and .tapestry-engine/ from git');
|
|
52
|
+
|
|
53
|
+
if (!fs.existsSync(path.join(cwd, '.git'))) {
|
|
54
|
+
{
|
|
55
|
+
console.log('\nHint: no git repo detected. Run: git init');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log('\nNext: run tapestry install, then tapestry engine install, then tapestry start');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = { init };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { readYaml, writeYaml } = require('../util/yaml');
|
|
7
|
+
const { resolve } = require('../lib/semver-resolver');
|
|
8
|
+
const { readLock, writeLock } = require('../lib/lock-file');
|
|
9
|
+
const { fetchTarball, DEFAULT_REGISTRY } = require('../lib/registry-client');
|
|
10
|
+
const { verifyIntegrity, saveTarball, extractTarball } = require('../lib/tarball');
|
|
11
|
+
const { addPackageToBoot } = require('../lib/boot');
|
|
12
|
+
|
|
13
|
+
function packInstallPath(cwd, packageName) {
|
|
14
|
+
const parts = packageName.split('/');
|
|
15
|
+
return path.join(cwd, 'packs', ...parts);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parsePackageArg(arg) {
|
|
19
|
+
const match = arg.match(/^(@[^@/]+\/[^@]+)(?:@(.+))?$/);
|
|
20
|
+
if (!match) {
|
|
21
|
+
throw new Error(`Invalid package name: ${arg}. Expected @scope/name or @scope/name@range`);
|
|
22
|
+
}
|
|
23
|
+
return { name: match[1], rawRange: match[2] || null };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isLockCurrent(manifestDeps, lock) {
|
|
27
|
+
const lockResolved = lock.resolved || {};
|
|
28
|
+
return Object.keys(manifestDeps).every((name) => lockResolved[name]);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function installResolved(cwd, resolved) {
|
|
32
|
+
for (const [packageName, info] of Object.entries(resolved)) {
|
|
33
|
+
const destDir = packInstallPath(cwd, packageName);
|
|
34
|
+
|
|
35
|
+
if (fs.existsSync(destDir)) {
|
|
36
|
+
console.log(` already installed ${packageName}@${info.version}`);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log(` installing ${packageName}@${info.version}`);
|
|
41
|
+
|
|
42
|
+
const safeId = packageName.replace('@', '').replace('/', '-');
|
|
43
|
+
const tmpPath = path.join(os.tmpdir(), `tapestry-${safeId}-${info.version}.tgz`);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const buffer = await fetchTarball(info.tarball);
|
|
47
|
+
verifyIntegrity(buffer, info.integrity);
|
|
48
|
+
saveTarball(buffer, tmpPath);
|
|
49
|
+
await extractTarball(tmpPath, destDir);
|
|
50
|
+
} finally {
|
|
51
|
+
if (fs.existsSync(tmpPath)) {
|
|
52
|
+
fs.unlinkSync(tmpPath);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const packManifest = readYaml(path.join(destDir, 'tapestry.yaml'));
|
|
57
|
+
addPackageToBoot(cwd, packageName, packManifest);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function install(packageArg, { cwd = process.cwd(), registryUrl = DEFAULT_REGISTRY } = {}) {
|
|
62
|
+
const manifestPath = path.join(cwd, 'tapestry.yaml');
|
|
63
|
+
if (!fs.existsSync(manifestPath)) {
|
|
64
|
+
throw new Error('No tapestry.yaml found. Run `tapestry init` first.');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const manifest = readYaml(manifestPath);
|
|
68
|
+
let resolved;
|
|
69
|
+
|
|
70
|
+
if (packageArg) {
|
|
71
|
+
const { name, rawRange } = parsePackageArg(packageArg);
|
|
72
|
+
manifest.dependencies = manifest.dependencies || {};
|
|
73
|
+
const tempRange = rawRange || '*';
|
|
74
|
+
manifest.dependencies[name] = tempRange;
|
|
75
|
+
|
|
76
|
+
console.log('Resolving dependencies...');
|
|
77
|
+
resolved = await resolve(manifest.dependencies, registryUrl);
|
|
78
|
+
|
|
79
|
+
if (!rawRange) {
|
|
80
|
+
manifest.dependencies[name] = `^${resolved[name].version}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
writeYaml(manifestPath, manifest);
|
|
84
|
+
} else {
|
|
85
|
+
const lock = readLock(cwd);
|
|
86
|
+
if (lock && isLockCurrent(manifest.dependencies || {}, lock)) {
|
|
87
|
+
console.log('Installing from lock file...');
|
|
88
|
+
resolved = lock.resolved;
|
|
89
|
+
} else {
|
|
90
|
+
console.log('Resolving dependencies...');
|
|
91
|
+
resolved = await resolve(manifest.dependencies || {}, registryUrl);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await installResolved(cwd, resolved);
|
|
96
|
+
writeLock(cwd, { lockfile_version: 1, resolved });
|
|
97
|
+
console.log('Done.');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
module.exports = { install };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { readLock } = require('../lib/lock-file');
|
|
6
|
+
const { readBoot } = require('../lib/boot');
|
|
7
|
+
const { readYaml } = require('../util/yaml');
|
|
8
|
+
|
|
9
|
+
function packInstallPath(cwd, packageName) {
|
|
10
|
+
const parts = packageName.split('/');
|
|
11
|
+
return path.join(cwd, 'packs', ...parts);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function list({ cwd = process.cwd() } = {}) {
|
|
15
|
+
const lock = readLock(cwd);
|
|
16
|
+
if (!lock || !lock.resolved || !Object.keys(lock.resolved).length) {
|
|
17
|
+
console.log('No packages installed.');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const boot = readBoot(cwd);
|
|
22
|
+
const packages = Object.entries(lock.resolved);
|
|
23
|
+
|
|
24
|
+
const nameWidth = Math.max(7, ...packages.map(([n]) => n.length));
|
|
25
|
+
const verWidth = Math.max(7, ...packages.map(([, r]) => r.version.length));
|
|
26
|
+
|
|
27
|
+
console.log(
|
|
28
|
+
`${'PACKAGE'.padEnd(nameWidth)} ${'VERSION'.padEnd(verWidth)} TYPE STATUS`
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
for (const [pkgName, resolved] of packages) {
|
|
32
|
+
const enabled = boot.packs[pkgName]?.enabled !== false ? 'enabled' : 'disabled';
|
|
33
|
+
|
|
34
|
+
let type = '';
|
|
35
|
+
const packManifestPath = path.join(packInstallPath(cwd, pkgName), 'tapestry.yaml');
|
|
36
|
+
if (fs.existsSync(packManifestPath)) {
|
|
37
|
+
try {
|
|
38
|
+
type = readYaml(packManifestPath).type || '';
|
|
39
|
+
} catch {
|
|
40
|
+
//
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log(
|
|
45
|
+
`${pkgName.padEnd(nameWidth)} ${resolved.version.padEnd(verWidth)} ${type.padEnd(8)} ${enabled}`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = { list };
|