@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
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function manifestTemplate(scopedName) {
|
|
4
|
+
return `# Package manifest -- fill in the TODOs before publishing.
|
|
5
|
+
name: "${scopedName}"
|
|
6
|
+
version: "0.1.0"
|
|
7
|
+
type: "module" # core | module | world
|
|
8
|
+
display_name: "TODO: Human-readable name"
|
|
9
|
+
description: "TODO: One-line description for registry search"
|
|
10
|
+
author:
|
|
11
|
+
name: "TODO: Your Name"
|
|
12
|
+
handle: "TODO: your-registry-handle"
|
|
13
|
+
license: "MIT"
|
|
14
|
+
|
|
15
|
+
# Semver range: >=3.0.0 means any engine version at or above this.
|
|
16
|
+
engine: ">=3.0.0"
|
|
17
|
+
|
|
18
|
+
# ^ means compatible minor/patch changes (>=1.0.0 <2.0.0)
|
|
19
|
+
# dependencies:
|
|
20
|
+
# "@scope/pack-name": "^1.0.0"
|
|
21
|
+
|
|
22
|
+
# Optional: warn if not installed, never auto-installed
|
|
23
|
+
# peerDependencies:
|
|
24
|
+
# "@tapestry/sustenance": "^1.0.0"
|
|
25
|
+
|
|
26
|
+
# Capabilities this pack provides (for reverse-dependency lookups)
|
|
27
|
+
provides:
|
|
28
|
+
- example
|
|
29
|
+
|
|
30
|
+
# strict: undeclared tags on entities cause load failure
|
|
31
|
+
# lenient: undeclared tags log warnings, pack still loads
|
|
32
|
+
tag_validation: strict
|
|
33
|
+
|
|
34
|
+
# Path to tag declarations file
|
|
35
|
+
tags: "tags.yml"
|
|
36
|
+
|
|
37
|
+
# Glob patterns -- the engine uses these to find your content
|
|
38
|
+
content:
|
|
39
|
+
areas: "areas/**/area.yaml"
|
|
40
|
+
rooms: "areas/**/rooms/*.yaml"
|
|
41
|
+
items: "areas/**/items/*.yaml"
|
|
42
|
+
mobs: "areas/**/mobs/*.yaml"
|
|
43
|
+
scripts: "scripts/**/*.js"
|
|
44
|
+
help: "help/**/*.yaml"
|
|
45
|
+
|
|
46
|
+
# Discovery metadata (shown by tapestry search and tapestry info)
|
|
47
|
+
meta:
|
|
48
|
+
commands: []
|
|
49
|
+
keywords: ["example"]
|
|
50
|
+
`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function tagsTemplate() {
|
|
54
|
+
return `# Tag declarations for this pack.
|
|
55
|
+
# Tags listed here can be used on entities (items, npcs, rooms, areas).
|
|
56
|
+
# Undeclared tags cause load failure when tag_validation: strict.
|
|
57
|
+
#
|
|
58
|
+
# Convention: always snake_case (e.g., safe_recall, not safe-recall)
|
|
59
|
+
# applies_to: which entity types accept this tag
|
|
60
|
+
# valid values: item, npc, room, area, player
|
|
61
|
+
#
|
|
62
|
+
# Engine tags from @tapestry/core (like killable, no_kill, persistent)
|
|
63
|
+
# are available to all packs without declaring them here.
|
|
64
|
+
# Tags below are YOUR pack's custom tags.
|
|
65
|
+
tags:
|
|
66
|
+
safe_recall:
|
|
67
|
+
description: "Room is a safe recall destination with no combat"
|
|
68
|
+
applies_to: [room]
|
|
69
|
+
|
|
70
|
+
example_tag:
|
|
71
|
+
description: "An example tag -- replace or remove this"
|
|
72
|
+
applies_to: [item]
|
|
73
|
+
|
|
74
|
+
# More examples:
|
|
75
|
+
# cursed:
|
|
76
|
+
# description: "Item carries a curse -- must be removed before unequipping"
|
|
77
|
+
# applies_to: [item]
|
|
78
|
+
# vendor:
|
|
79
|
+
# description: "NPC offers specialized trade goods"
|
|
80
|
+
# applies_to: [npc]
|
|
81
|
+
`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function areaTemplate() {
|
|
85
|
+
return `# Area definition -- one per folder.
|
|
86
|
+
# Areas group rooms, mobs, and items into a named zone.
|
|
87
|
+
area:
|
|
88
|
+
id: example-area # unique within this pack, no spaces
|
|
89
|
+
name: "Example Area" # human-readable name shown in-game
|
|
90
|
+
level_range: [1, 5] # suggested mob level range for this zone
|
|
91
|
+
reset_interval: 1800 # seconds between mob/item respawns
|
|
92
|
+
occupied_modifier: 3.0 # respawn slows by this factor when players are present
|
|
93
|
+
weather_zone: temperate # weather pattern (requires @tapestry/weather)
|
|
94
|
+
flags: [safe_recall] # area flags: city, village, safe_recall, dangerous, safe
|
|
95
|
+
# weather_messages: # custom weather messages for this area
|
|
96
|
+
# storm:
|
|
97
|
+
# start: "Thunder rumbles across the square."
|
|
98
|
+
`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function roomTemplate(shortName) {
|
|
102
|
+
return `# Room definition -- one file per room.
|
|
103
|
+
# ID format: "pack-short-name:room-id" (short name = part after the slash in @scope/name)
|
|
104
|
+
id: "${shortName}:town-square"
|
|
105
|
+
area: example-area # must match area.id in area.yaml
|
|
106
|
+
name: "Town Square"
|
|
107
|
+
description: >
|
|
108
|
+
A cobblestone square at the heart of the example area.
|
|
109
|
+
<npc>A guard</npc> stands watch near the well.
|
|
110
|
+
A <item.uncommon>lantern</item.uncommon> hangs on a hook by the gate.
|
|
111
|
+
|
|
112
|
+
# Exits -- simple or with doors
|
|
113
|
+
exits:
|
|
114
|
+
north: "${shortName}:another-room"
|
|
115
|
+
# Complex exit with a door:
|
|
116
|
+
# south:
|
|
117
|
+
# target: "${shortName}:locked-room"
|
|
118
|
+
# door:
|
|
119
|
+
# name: "an iron gate"
|
|
120
|
+
# closed: true
|
|
121
|
+
# locked: true
|
|
122
|
+
# key: "${shortName}:iron-key"
|
|
123
|
+
|
|
124
|
+
# Tags -- engine tags (safe, recall_point, entry_point, no_wander) are from core
|
|
125
|
+
# Pack tags must be declared in tags.yml
|
|
126
|
+
tags: [safe_recall]
|
|
127
|
+
|
|
128
|
+
properties:
|
|
129
|
+
terrain: city # city, indoors, outdoors, forest, underground, road
|
|
130
|
+
# alignment_range: # restrict entry by alignment
|
|
131
|
+
# max: -500 # only evil players can enter
|
|
132
|
+
# alignment_block_message: "A holy barrier repels you."
|
|
133
|
+
|
|
134
|
+
# Entry point -- marks this room as a starting/recall location
|
|
135
|
+
# entry_point_description: "the town square"
|
|
136
|
+
# entry_point_direction: south
|
|
137
|
+
|
|
138
|
+
# Mobs that spawn here on area reset
|
|
139
|
+
spawns:
|
|
140
|
+
- mob: "${shortName}:example-guard"
|
|
141
|
+
count: 1
|
|
142
|
+
tags: [persistent] # persistent = respawns even while players are present
|
|
143
|
+
|
|
144
|
+
# Items placed in room on reset (not carried by mobs)
|
|
145
|
+
fixtures:
|
|
146
|
+
- "${shortName}:example-lantern"
|
|
147
|
+
`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function mobTemplate(shortName) {
|
|
151
|
+
return `# NPC (mob) definition -- one file per NPC type.
|
|
152
|
+
# ID format: "pack-short-name:mob-id"
|
|
153
|
+
id: "${shortName}:example-guard"
|
|
154
|
+
name: "a guard"
|
|
155
|
+
type: "npc"
|
|
156
|
+
|
|
157
|
+
# Engine tags: killable, no_kill, shop, skill_trainer, vendor, quest, persistent
|
|
158
|
+
# Pack tags must be declared in tags.yml
|
|
159
|
+
tags: [no_kill]
|
|
160
|
+
|
|
161
|
+
# friendly, neutral, hostile -- initial stance toward players
|
|
162
|
+
base_disposition: friendly
|
|
163
|
+
|
|
164
|
+
# Words players type to target this NPC: kill guard, talk guard
|
|
165
|
+
keywords: [guard, soldier]
|
|
166
|
+
|
|
167
|
+
# Behavior -- how the NPC acts when idle
|
|
168
|
+
# behavior: stationary # stationary, wander, patrol, aggro
|
|
169
|
+
# script: "mobs/guide.js" # custom JS behavior script
|
|
170
|
+
# patrol_route: ["${shortName}:room-a", "${shortName}:room-b"]
|
|
171
|
+
# patrol_interval: 60 # seconds between patrol moves
|
|
172
|
+
|
|
173
|
+
stats:
|
|
174
|
+
strength: 12
|
|
175
|
+
dexterity: 10
|
|
176
|
+
constitution: 12
|
|
177
|
+
intelligence: 8
|
|
178
|
+
wisdom: 8
|
|
179
|
+
luck: 6
|
|
180
|
+
max_hp: 100
|
|
181
|
+
max_resource: 0
|
|
182
|
+
max_movement: 100
|
|
183
|
+
|
|
184
|
+
properties:
|
|
185
|
+
level: 5
|
|
186
|
+
description: "A guard standing watch near the gate."
|
|
187
|
+
# gold: 50 # gold dropped on death
|
|
188
|
+
# xp_value: 100 # XP awarded on kill
|
|
189
|
+
# regen_hp: 2.0 # HP regeneration per tick
|
|
190
|
+
# regen_movement: 5 # movement regeneration per tick
|
|
191
|
+
# corpse_decay: 300 # seconds before corpse disappears
|
|
192
|
+
|
|
193
|
+
# Combat properties
|
|
194
|
+
# wimpy_threshold: 15 # % HP to trigger flee
|
|
195
|
+
# ac_slash: 5 # armor class by damage type
|
|
196
|
+
# ac_pierce: 5
|
|
197
|
+
# ac_bash: 5
|
|
198
|
+
# ac_exotic: 0
|
|
199
|
+
# flee_threshold: 0.1 # health % to flee (0.0-1.0)
|
|
200
|
+
|
|
201
|
+
# Idle behavior
|
|
202
|
+
# idle_chance: 0.3 # chance to perform idle action per tick
|
|
203
|
+
# idle_interval: 30 # seconds between idle checks
|
|
204
|
+
|
|
205
|
+
# Wander behavior
|
|
206
|
+
# wander_interval: 45 # seconds between wander moves
|
|
207
|
+
# wander_boundary: "area" # area = stay in area, room = stay put
|
|
208
|
+
|
|
209
|
+
# Dialogue
|
|
210
|
+
# dialogue: "guard-dialogue" # dialogue tree ID
|
|
211
|
+
|
|
212
|
+
# Commands the NPC says/does when idle
|
|
213
|
+
# idle_commands:
|
|
214
|
+
# - "say All quiet on the watch."
|
|
215
|
+
# - "emote scans the square."
|
|
216
|
+
|
|
217
|
+
# Commands the NPC uses in combat
|
|
218
|
+
# battle_commands:
|
|
219
|
+
# - "bash"
|
|
220
|
+
# - "say You dare challenge me?"
|
|
221
|
+
|
|
222
|
+
# Abilities the NPC can use
|
|
223
|
+
# abilities:
|
|
224
|
+
# - id: "bash"
|
|
225
|
+
# proficiency: 75
|
|
226
|
+
|
|
227
|
+
# Items equipped on spawn
|
|
228
|
+
# equipment:
|
|
229
|
+
# - "${shortName}:iron-sword"
|
|
230
|
+
|
|
231
|
+
# Loot dropped on death
|
|
232
|
+
# loot:
|
|
233
|
+
# guaranteed:
|
|
234
|
+
# - item: "${shortName}:guard-badge"
|
|
235
|
+
# count: 1
|
|
236
|
+
# pool:
|
|
237
|
+
# - item: "${shortName}:health-potion"
|
|
238
|
+
# weight: 10
|
|
239
|
+
# - item: "${shortName}:iron-helm"
|
|
240
|
+
# weight: 3
|
|
241
|
+
# pool_rolls: 1
|
|
242
|
+
# rare_bonus:
|
|
243
|
+
# chance: 0.05
|
|
244
|
+
# pool:
|
|
245
|
+
# - item: "${shortName}:rare-blade"
|
|
246
|
+
# weight: 1
|
|
247
|
+
|
|
248
|
+
# NPC trains players (skill_trainer tag required)
|
|
249
|
+
# trains:
|
|
250
|
+
# tier: "apprentice"
|
|
251
|
+
# abilities: ["bash", "parry"]
|
|
252
|
+
|
|
253
|
+
# NPC sells items (shop tag required)
|
|
254
|
+
# properties:
|
|
255
|
+
# shop:
|
|
256
|
+
# sells: ["${shortName}:health-potion", "${shortName}:iron-sword"]
|
|
257
|
+
`;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function itemTemplate(shortName) {
|
|
261
|
+
return `# Item definition -- one file per item type.
|
|
262
|
+
# ID format: "pack-short-name:item-id"
|
|
263
|
+
id: "${shortName}:example-lantern"
|
|
264
|
+
name: "a battered lantern"
|
|
265
|
+
type: "item"
|
|
266
|
+
|
|
267
|
+
# Engine tags: consumable, container, fixture, no_get, equippable, fillable,
|
|
268
|
+
# fill_source, readable, emits_light, drinkable, furniture
|
|
269
|
+
# Pack tags must be declared in tags.yml
|
|
270
|
+
tags: [emits_light]
|
|
271
|
+
|
|
272
|
+
# Words players type to target this item: get lantern, look lantern
|
|
273
|
+
keywords: [lantern, light]
|
|
274
|
+
|
|
275
|
+
properties:
|
|
276
|
+
weight: 2
|
|
277
|
+
rarity: common # common, uncommon, rare, epic, artifact
|
|
278
|
+
value: 5 # coin value when sold to a shop
|
|
279
|
+
# description: "A dented lantern that still glows faintly."
|
|
280
|
+
# level: 1 # required/recommended level
|
|
281
|
+
|
|
282
|
+
# Equipment (equippable tag required)
|
|
283
|
+
# slot: light # wield, head, feet, shield, finger, neck, hands, cloak, light, held
|
|
284
|
+
|
|
285
|
+
# Weapon properties (wield slot)
|
|
286
|
+
# damage_dice: "1d6+2" # dice notation
|
|
287
|
+
# hit_bonus: 1
|
|
288
|
+
# attack_speed: 3 # lower = faster
|
|
289
|
+
# combat_name: "slash" # verb for combat messages
|
|
290
|
+
# damage_type: slash # slash, pierce, bash
|
|
291
|
+
|
|
292
|
+
# Armor properties
|
|
293
|
+
# ac_slash: 3
|
|
294
|
+
# ac_pierce: 3
|
|
295
|
+
# ac_bash: 3
|
|
296
|
+
# ac_exotic: 0
|
|
297
|
+
|
|
298
|
+
# Container (container tag required)
|
|
299
|
+
# container_capacity: 10
|
|
300
|
+
|
|
301
|
+
# Consumable (consumable tag required)
|
|
302
|
+
# consume_method: quaff # quaff, drink, eat
|
|
303
|
+
# charges: 3
|
|
304
|
+
# max_charges: 3
|
|
305
|
+
# destroy_on_empty: true
|
|
306
|
+
# effect_id: "heal"
|
|
307
|
+
# effect_duration: 10
|
|
308
|
+
# effect_data:
|
|
309
|
+
# heal_hp: 50
|
|
310
|
+
# sustenance_value: 25 # hunger/thirst satiation
|
|
311
|
+
|
|
312
|
+
# Fillable (fillable tag required)
|
|
313
|
+
# fill_type: "water"
|
|
314
|
+
# fill_source: "water"
|
|
315
|
+
|
|
316
|
+
# Readable (readable tag required)
|
|
317
|
+
# text: "The inscription reads: 'Welcome to the realm.'"
|
|
318
|
+
|
|
319
|
+
# Furniture
|
|
320
|
+
# rest_bonus: 2 # bonus to rest/recovery
|
|
321
|
+
|
|
322
|
+
# Magical essence
|
|
323
|
+
# essence: fire # shadow, fire, earth, storm
|
|
324
|
+
|
|
325
|
+
# Stat modifiers applied when equipped
|
|
326
|
+
# modifiers:
|
|
327
|
+
# - stat: strength # strength, dexterity, constitution, intelligence,
|
|
328
|
+
# value: 2 # wisdom, luck, maxHp, maxMovement, maxResource
|
|
329
|
+
`;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function initScriptTemplate(scopedName) {
|
|
333
|
+
return `// init.js -- runs when this pack loads.
|
|
334
|
+
// Register commands, subscribe to events, declare properties.
|
|
335
|
+
// The tapestry object is injected by the engine at load time.
|
|
336
|
+
|
|
337
|
+
// --- Command registration ---
|
|
338
|
+
// Registers a command players can type in-game.
|
|
339
|
+
tapestry.commands.register({
|
|
340
|
+
name: 'example',
|
|
341
|
+
aliases: [],
|
|
342
|
+
description: 'An example command from ${scopedName}',
|
|
343
|
+
category: 'general',
|
|
344
|
+
roles: ['player'],
|
|
345
|
+
args: {
|
|
346
|
+
target: { type: 'text', required: false }
|
|
347
|
+
},
|
|
348
|
+
handler: function(actor, resolved) {
|
|
349
|
+
var msg = resolved.target
|
|
350
|
+
? 'You examine the ' + resolved.target + '.'
|
|
351
|
+
: 'Nothing to examine.';
|
|
352
|
+
actor.send(msg + '\\r\\n');
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// --- Event subscriptions ---
|
|
357
|
+
// Subscribe to events from the engine or other packs.
|
|
358
|
+
// Core events: entity:entered_room, entity:left_room,
|
|
359
|
+
// entity:attacked, entity:killed, item:picked_up, item:dropped
|
|
360
|
+
//
|
|
361
|
+
// tapestry.events.on('entity:entered_room', function(entity, room) {
|
|
362
|
+
// var weather = room.get('weather_current');
|
|
363
|
+
// if (weather === 'blizzard') {
|
|
364
|
+
// entity.send('The cold bites at you as you arrive.\\r\\n');
|
|
365
|
+
// }
|
|
366
|
+
// });
|
|
367
|
+
|
|
368
|
+
// --- Property registration ---
|
|
369
|
+
// Declare properties your pack writes to entities.
|
|
370
|
+
// Other packs read these via entity.get('your-property').
|
|
371
|
+
//
|
|
372
|
+
// tapestry.properties.register('example-status', {
|
|
373
|
+
// type: 'string',
|
|
374
|
+
// default: null,
|
|
375
|
+
// applies_to: ['player', 'npc'],
|
|
376
|
+
// });
|
|
377
|
+
`;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function helpTemplate(scopedName) {
|
|
381
|
+
return `# Help file -- documents a command or topic.
|
|
382
|
+
# Players read this in-game with: help example
|
|
383
|
+
id: "example"
|
|
384
|
+
title: "Example Command"
|
|
385
|
+
category: "general" # general, combat, social, building, admin
|
|
386
|
+
role: "player" # player, builder, admin (who can see this help)
|
|
387
|
+
keywords: [example, demo]
|
|
388
|
+
brief: "An example command from ${scopedName}."
|
|
389
|
+
syntax:
|
|
390
|
+
- "example"
|
|
391
|
+
- "example [target]"
|
|
392
|
+
body: |
|
|
393
|
+
The example command is a placeholder from the pack scaffold.
|
|
394
|
+
Replace this with documentation for your actual commands.
|
|
395
|
+
|
|
396
|
+
Use syntax entries to show all forms of the command.
|
|
397
|
+
Keep help text concise -- players read this at the terminal.
|
|
398
|
+
see_also: [help, commands]
|
|
399
|
+
`;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function generatePackFiles({ scopedName, shortName }) {
|
|
403
|
+
return [
|
|
404
|
+
{ path: 'tapestry.yaml', content: manifestTemplate(scopedName) },
|
|
405
|
+
{ path: 'tags.yml', content: tagsTemplate() },
|
|
406
|
+
{ path: 'areas/example-area/area.yaml', content: areaTemplate() },
|
|
407
|
+
{ path: 'areas/example-area/rooms/town-square.yaml', content: roomTemplate(shortName) },
|
|
408
|
+
{ path: 'areas/example-area/mobs/guard.yaml', content: mobTemplate(shortName) },
|
|
409
|
+
{ path: 'areas/example-area/items/lantern.yaml', content: itemTemplate(shortName) },
|
|
410
|
+
{ path: 'scripts/init.js', content: initScriptTemplate(scopedName) },
|
|
411
|
+
{ path: 'help/example.yaml', content: helpTemplate(scopedName) },
|
|
412
|
+
];
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
module.exports = { generatePackFiles };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { z } = require('zod');
|
|
4
|
+
|
|
5
|
+
const SCOPED_NAME = /^@[a-z0-9-]+\/[a-z0-9-]+$/;
|
|
6
|
+
|
|
7
|
+
const PackageManifestSchema = z.object({
|
|
8
|
+
name: z.string().regex(SCOPED_NAME, 'name must be @scope/package-name'),
|
|
9
|
+
version: z.string().min(1),
|
|
10
|
+
type: z.enum(['core', 'module', 'world']),
|
|
11
|
+
display_name: z.string().min(1),
|
|
12
|
+
description: z.string().min(1),
|
|
13
|
+
author: z.union([
|
|
14
|
+
z.string().min(1),
|
|
15
|
+
z.object({
|
|
16
|
+
name: z.string().min(1),
|
|
17
|
+
handle: z.string().min(1),
|
|
18
|
+
}),
|
|
19
|
+
]),
|
|
20
|
+
license: z.string().min(1),
|
|
21
|
+
engine: z.string().min(1),
|
|
22
|
+
tag_validation: z.enum(['strict', 'lenient']),
|
|
23
|
+
dependencies: z.record(z.string()).optional(),
|
|
24
|
+
peerDependencies: z.record(z.string()).optional(),
|
|
25
|
+
provides: z.array(z.string()).optional(),
|
|
26
|
+
tags: z.string().optional(),
|
|
27
|
+
module: z.object({
|
|
28
|
+
assembly: z.string(),
|
|
29
|
+
class: z.string(),
|
|
30
|
+
implements: z.string(),
|
|
31
|
+
after: z.string().optional(),
|
|
32
|
+
}).optional(),
|
|
33
|
+
content: z.record(z.string()).optional(),
|
|
34
|
+
client: z.object({
|
|
35
|
+
manifest: z.string(),
|
|
36
|
+
assets: z.string(),
|
|
37
|
+
min_client_version: z.string(),
|
|
38
|
+
}).optional(),
|
|
39
|
+
meta: z.object({
|
|
40
|
+
commands: z.array(z.string()).optional(),
|
|
41
|
+
properties: z.number().optional(),
|
|
42
|
+
keywords: z.array(z.string()).optional(),
|
|
43
|
+
}).optional(),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const ProjectManifestSchema = z.object({
|
|
47
|
+
name: z.string().min(1),
|
|
48
|
+
engine: z.union([
|
|
49
|
+
z.string().min(1),
|
|
50
|
+
z.object({
|
|
51
|
+
version: z.string().min(1),
|
|
52
|
+
mode: z.enum(['docker', 'binary', 'source']),
|
|
53
|
+
image: z.string().optional(),
|
|
54
|
+
}),
|
|
55
|
+
]),
|
|
56
|
+
dependencies: z.record(z.string()).optional(),
|
|
57
|
+
packs: z.array(z.string()).optional(),
|
|
58
|
+
tag_validation: z.enum(['strict', 'lenient']).optional(),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
function validatePackageManifest(data) {
|
|
62
|
+
return PackageManifestSchema.safeParse(data);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function validateProjectManifest(data) {
|
|
66
|
+
return ProjectManifestSchema.safeParse(data);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = {
|
|
70
|
+
PackageManifestSchema,
|
|
71
|
+
ProjectManifestSchema,
|
|
72
|
+
validatePackageManifest,
|
|
73
|
+
validateProjectManifest,
|
|
74
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const readline = require('readline');
|
|
4
|
+
|
|
5
|
+
function createInterface() {
|
|
6
|
+
return readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function ask(rl, question) {
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
rl.question(question, resolve);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function askPassword(rl, prompt) {
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
process.stdout.write(prompt);
|
|
18
|
+
const orig = rl._writeToOutput.bind(rl);
|
|
19
|
+
rl._writeToOutput = () => {};
|
|
20
|
+
rl.question('', (password) => {
|
|
21
|
+
rl._writeToOutput = orig;
|
|
22
|
+
process.stdout.write('\n');
|
|
23
|
+
resolve(password);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = { createInterface, ask, askPassword };
|
package/src/util/yaml.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const yaml = require('js-yaml');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
|
|
6
|
+
function readYaml(filePath) {
|
|
7
|
+
{
|
|
8
|
+
return yaml.load(fs.readFileSync(filePath, 'utf8'));
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function writeYaml(filePath, data) {
|
|
13
|
+
{
|
|
14
|
+
fs.writeFileSync(filePath, yaml.dump(data, { lineWidth: -1 }));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = { readYaml, writeYaml };
|