gamedev 0.0.3-alpha.0 → 0.0.4-alpha.2
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/app-server/WorldManifest.js +1 -1
- package/app-server/cli.js +1 -1
- package/app-server/commands.js +7 -7
- package/app-server/direct.js +3 -3
- package/app-server/scaffold.js +91 -6
- package/app-server/targets.js +1 -1
- package/app-server/templates/claude/skills/hyperfy-app-scripting/SKILL.md +2 -2
- package/bin/gamedev.mjs +19 -125
- package/build/public/admin.html +2 -2
- package/build/public/index.html +2 -2
- package/build/world-node-client.js +1339 -214
- package/build/world-node-client.js.map +4 -4
- package/package.json +1 -1
|
@@ -22,7 +22,7 @@ export class WorldManifest {
|
|
|
22
22
|
if (parsed.formatVersion !== 2) {
|
|
23
23
|
throw new Error(
|
|
24
24
|
'Invalid world.json format version. Expected formatVersion 2.\n' +
|
|
25
|
-
'Run "
|
|
25
|
+
'Run "gamedev world export" to regenerate world.json (formatVersion 2).'
|
|
26
26
|
)
|
|
27
27
|
}
|
|
28
28
|
|
package/app-server/cli.js
CHANGED
|
@@ -17,7 +17,7 @@ async function main() {
|
|
|
17
17
|
console.error(`❌ ${err?.message || err}`)
|
|
18
18
|
process.exit(1)
|
|
19
19
|
}
|
|
20
|
-
const exitCode = await runAppCommand({ command, args, helpPrefix: '
|
|
20
|
+
const exitCode = await runAppCommand({ command, args, helpPrefix: 'gamedev apps' })
|
|
21
21
|
process.exit(exitCode)
|
|
22
22
|
}
|
|
23
23
|
|
package/app-server/commands.js
CHANGED
|
@@ -246,7 +246,7 @@ export class HyperfyCLI {
|
|
|
246
246
|
const blueprints = listLocalBlueprints(this.appsDir)
|
|
247
247
|
if (blueprints.length === 0) {
|
|
248
248
|
console.log(`📝 No local blueprints found in ${this.appsDir}`)
|
|
249
|
-
console.log(`💡 Run "
|
|
249
|
+
console.log(`💡 Run "gamedev world export" to pull blueprints from the world.`)
|
|
250
250
|
console.log(` Use --include-built-scripts if you need script code locally.`)
|
|
251
251
|
return
|
|
252
252
|
}
|
|
@@ -399,7 +399,7 @@ app.on("update", (delta) => {
|
|
|
399
399
|
console.log(`✅ Successfully created app in world: ${appName}`)
|
|
400
400
|
console.log(` • Blueprint: ${blueprintId}`)
|
|
401
401
|
console.log(` • Entity: ${entityId}`)
|
|
402
|
-
console.log(`💡 Run "
|
|
402
|
+
console.log(`💡 Run "gamedev world export" to sync into ${this.appsDir}.`)
|
|
403
403
|
console.log(` Use --include-built-scripts if you need script code locally.`)
|
|
404
404
|
} catch (error) {
|
|
405
405
|
console.error(`❌ Error creating app:`, error?.message || error)
|
|
@@ -563,7 +563,7 @@ app.on("update", (delta) => {
|
|
|
563
563
|
console.log(`✅ Script validation passed for ${appName}`)
|
|
564
564
|
console.log(`🔗 Hash: ${localHash}`)
|
|
565
565
|
} else {
|
|
566
|
-
console.log(`💡 Run '
|
|
566
|
+
console.log(`💡 Run 'gamedev apps deploy ${appName}' (or save the file with app-server running)`)
|
|
567
567
|
}
|
|
568
568
|
} catch (error) {
|
|
569
569
|
console.error(`❌ Error validating app:`, error?.message || error)
|
|
@@ -677,9 +677,9 @@ app.on("update", (delta) => {
|
|
|
677
677
|
}
|
|
678
678
|
}
|
|
679
679
|
|
|
680
|
-
showHelp({ commandPrefix = '
|
|
680
|
+
showHelp({ commandPrefix = 'gamedev apps' } = {}) {
|
|
681
681
|
console.log(`
|
|
682
|
-
🚀
|
|
682
|
+
🚀 Gamedev CLI (direct /admin mode)
|
|
683
683
|
|
|
684
684
|
Usage:
|
|
685
685
|
${commandPrefix} <command> [options]
|
|
@@ -697,7 +697,7 @@ Commands:
|
|
|
697
697
|
reset [--force] Delete local apps/assets/world.json
|
|
698
698
|
status Show /admin snapshot summary
|
|
699
699
|
help Show this help
|
|
700
|
-
--target <name> Use .
|
|
700
|
+
--target <name> Use .lobby/targets.json entry for WORLD_URL/WORLD_ID/ADMIN_CODE/DEPLOY_CODE
|
|
701
701
|
|
|
702
702
|
Options:
|
|
703
703
|
--dry-run, -n Show deploy plan without applying changes
|
|
@@ -739,7 +739,7 @@ export async function runAppCommand({ command, args = [], rootDir = process.cwd(
|
|
|
739
739
|
}
|
|
740
740
|
}
|
|
741
741
|
const cli = new HyperfyCLI({ rootDir })
|
|
742
|
-
const commandPrefix = helpPrefix || '
|
|
742
|
+
const commandPrefix = helpPrefix || 'gamedev apps'
|
|
743
743
|
let exitCode = 0
|
|
744
744
|
|
|
745
745
|
switch (command) {
|
package/app-server/direct.js
CHANGED
|
@@ -558,7 +558,7 @@ export class DirectAppServer {
|
|
|
558
558
|
} else if (!hasWorldFile && hasApps) {
|
|
559
559
|
throw new Error(
|
|
560
560
|
'world.json missing; cannot safely apply exact world layout. ' +
|
|
561
|
-
'Run "
|
|
561
|
+
'Run "gamedev world export" to generate it from the world, or create world.json to seed a new world.'
|
|
562
562
|
)
|
|
563
563
|
} else {
|
|
564
564
|
const manifest = this.manifest.read()
|
|
@@ -586,7 +586,7 @@ export class DirectAppServer {
|
|
|
586
586
|
const err = new Error(
|
|
587
587
|
'Local project is empty and this world already has content. ' +
|
|
588
588
|
'Script code is not downloaded by default. ' +
|
|
589
|
-
'Run "
|
|
589
|
+
'Run "gamedev world export --include-built-scripts" to scaffold from the world.'
|
|
590
590
|
)
|
|
591
591
|
err.code = 'empty_project_requires_export'
|
|
592
592
|
throw err
|
|
@@ -657,7 +657,7 @@ export class DirectAppServer {
|
|
|
657
657
|
async importWorldFromDisk() {
|
|
658
658
|
const manifest = this.manifest.read()
|
|
659
659
|
if (!manifest) {
|
|
660
|
-
throw new Error('world.json missing. Run "
|
|
660
|
+
throw new Error('world.json missing. Run "gamedev world export" to generate it first.')
|
|
661
661
|
}
|
|
662
662
|
const errors = this.manifest.validate(manifest)
|
|
663
663
|
if (errors.length) {
|
package/app-server/scaffold.js
CHANGED
|
@@ -12,7 +12,7 @@ const CLAUDE_SKILL_TEMPLATE = path.join(
|
|
|
12
12
|
TEMPLATES_DIR,
|
|
13
13
|
'claude',
|
|
14
14
|
'skills',
|
|
15
|
-
'
|
|
15
|
+
'hyperfy-app-scripting',
|
|
16
16
|
'SKILL.md'
|
|
17
17
|
)
|
|
18
18
|
|
|
@@ -51,7 +51,7 @@ const DEFAULT_TSCONFIG = {
|
|
|
51
51
|
skipLibCheck: true,
|
|
52
52
|
types: ['gamedev/app-runtime'],
|
|
53
53
|
},
|
|
54
|
-
include: ['apps/**/*', '
|
|
54
|
+
include: ['apps/**/*', 'hyperfy.app-runtime.d.ts'],
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
function normalizePackageName(name, fallback) {
|
|
@@ -100,7 +100,7 @@ function resolveSdkVersion() {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
function buildEnvExample() {
|
|
103
|
-
return `#
|
|
103
|
+
return `# Hyperfy project environment (example)
|
|
104
104
|
# Run "gamedev dev" to generate a local .env automatically.
|
|
105
105
|
WORLD_URL=http://localhost:5000
|
|
106
106
|
WORLD_ID=local-your-world-id
|
|
@@ -202,6 +202,58 @@ function resolveBuiltinScriptPath(filename) {
|
|
|
202
202
|
return null
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
function resolveBuiltinAssetPath(filename) {
|
|
206
|
+
const buildPath = path.join(__dirname, '..', 'build', 'world', 'assets', filename)
|
|
207
|
+
if (fs.existsSync(buildPath)) return buildPath
|
|
208
|
+
const srcPath = path.join(__dirname, '..', 'src', 'world', 'assets', filename)
|
|
209
|
+
if (fs.existsSync(srcPath)) return srcPath
|
|
210
|
+
return null
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function collectAssetFilenames(value, out) {
|
|
214
|
+
if (!out) out = new Set()
|
|
215
|
+
if (typeof value === 'string') {
|
|
216
|
+
if (value.startsWith('asset://')) {
|
|
217
|
+
out.add(value.slice('asset://'.length))
|
|
218
|
+
} else if (value.startsWith('assets/')) {
|
|
219
|
+
out.add(value.slice('assets/'.length))
|
|
220
|
+
}
|
|
221
|
+
return out
|
|
222
|
+
}
|
|
223
|
+
if (Array.isArray(value)) {
|
|
224
|
+
for (const item of value) {
|
|
225
|
+
collectAssetFilenames(item, out)
|
|
226
|
+
}
|
|
227
|
+
return out
|
|
228
|
+
}
|
|
229
|
+
if (value && typeof value === 'object') {
|
|
230
|
+
for (const item of Object.values(value)) {
|
|
231
|
+
collectAssetFilenames(item, out)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return out
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function toLocalAssetUrls(value) {
|
|
238
|
+
if (typeof value === 'string') {
|
|
239
|
+
if (value.startsWith('asset://')) {
|
|
240
|
+
return `assets/${value.slice('asset://'.length)}`
|
|
241
|
+
}
|
|
242
|
+
return value
|
|
243
|
+
}
|
|
244
|
+
if (Array.isArray(value)) {
|
|
245
|
+
return value.map(item => toLocalAssetUrls(item))
|
|
246
|
+
}
|
|
247
|
+
if (value && typeof value === 'object') {
|
|
248
|
+
const next = {}
|
|
249
|
+
for (const [key, item] of Object.entries(value)) {
|
|
250
|
+
next[key] = toLocalAssetUrls(item)
|
|
251
|
+
}
|
|
252
|
+
return next
|
|
253
|
+
}
|
|
254
|
+
return value
|
|
255
|
+
}
|
|
256
|
+
|
|
205
257
|
function readBuiltinScript(template) {
|
|
206
258
|
const scriptPath = resolveBuiltinScriptPath(template.scriptAsset)
|
|
207
259
|
if (!scriptPath) {
|
|
@@ -256,7 +308,7 @@ export function scaffoldBaseProject({
|
|
|
256
308
|
report,
|
|
257
309
|
})
|
|
258
310
|
|
|
259
|
-
writeFileWithPolicy(path.join(rootDir, '
|
|
311
|
+
writeFileWithPolicy(path.join(rootDir, 'hyperfy.app-runtime.d.ts'), APP_RUNTIME_TYPES_REFERENCE, {
|
|
260
312
|
force,
|
|
261
313
|
writeFile,
|
|
262
314
|
report,
|
|
@@ -266,7 +318,7 @@ export function scaffoldBaseProject({
|
|
|
266
318
|
const skillContent = readText(CLAUDE_SKILL_TEMPLATE)
|
|
267
319
|
if (skillContent != null) {
|
|
268
320
|
writeFileWithPolicy(
|
|
269
|
-
path.join(rootDir, '.claude', 'skills', '
|
|
321
|
+
path.join(rootDir, '.claude', 'skills', 'hyperfy-app-scripting', 'SKILL.md'),
|
|
270
322
|
skillContent.endsWith('\n') ? skillContent : `${skillContent}\n`,
|
|
271
323
|
{ force, writeFile, report }
|
|
272
324
|
)
|
|
@@ -296,16 +348,21 @@ export function createDefaultManifest() {
|
|
|
296
348
|
export function scaffoldBuiltins({ rootDir, force = false, writeFile } = {}) {
|
|
297
349
|
const report = { created: [], updated: [], skipped: [] }
|
|
298
350
|
const appsDir = path.join(rootDir, 'apps')
|
|
351
|
+
const assetsDir = path.join(rootDir, 'assets')
|
|
299
352
|
ensureDir(appsDir)
|
|
300
353
|
|
|
301
354
|
const templates = [...BUILTIN_APP_TEMPLATES, SCENE_TEMPLATE]
|
|
355
|
+
const assetFiles = new Set()
|
|
356
|
+
|
|
302
357
|
for (const template of templates) {
|
|
358
|
+
collectAssetFilenames(template.config, assetFiles)
|
|
303
359
|
const appDir = path.join(appsDir, template.appName)
|
|
304
360
|
ensureDir(appDir)
|
|
305
361
|
|
|
306
362
|
const blueprintPath = path.join(appDir, `${template.fileBase}.json`)
|
|
307
363
|
if (!fs.existsSync(blueprintPath) || force) {
|
|
308
|
-
|
|
364
|
+
const localConfig = toLocalAssetUrls(template.config)
|
|
365
|
+
writeFileWithPolicy(blueprintPath, JSON.stringify(localConfig, null, 2) + '\n', {
|
|
309
366
|
force,
|
|
310
367
|
writeFile,
|
|
311
368
|
report,
|
|
@@ -320,6 +377,34 @@ export function scaffoldBuiltins({ rootDir, force = false, writeFile } = {}) {
|
|
|
320
377
|
}
|
|
321
378
|
}
|
|
322
379
|
|
|
380
|
+
if (assetFiles.size) {
|
|
381
|
+
ensureDir(assetsDir)
|
|
382
|
+
for (const filename of assetFiles) {
|
|
383
|
+
const srcPath = resolveBuiltinAssetPath(filename)
|
|
384
|
+
if (!srcPath) {
|
|
385
|
+
throw new Error(`missing_builtin_asset:${filename}`)
|
|
386
|
+
}
|
|
387
|
+
const destPath = path.join(assetsDir, filename)
|
|
388
|
+
const exists = fs.existsSync(destPath)
|
|
389
|
+
if (exists && !force) {
|
|
390
|
+
report.skipped.push(destPath)
|
|
391
|
+
continue
|
|
392
|
+
}
|
|
393
|
+
const buffer = fs.readFileSync(srcPath)
|
|
394
|
+
if (writeFile) {
|
|
395
|
+
writeFile(destPath, buffer)
|
|
396
|
+
} else {
|
|
397
|
+
ensureDir(path.dirname(destPath))
|
|
398
|
+
fs.writeFileSync(destPath, buffer)
|
|
399
|
+
}
|
|
400
|
+
if (exists) {
|
|
401
|
+
report.updated.push(destPath)
|
|
402
|
+
} else {
|
|
403
|
+
report.created.push(destPath)
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
323
408
|
return { report, manifest: createDefaultManifest() }
|
|
324
409
|
}
|
|
325
410
|
|
package/app-server/targets.js
CHANGED
|
@@ -16,7 +16,7 @@ description: Hyperfy world-project app scripting workflow and constraints. Use w
|
|
|
16
16
|
1. Identify the target app(s) and update the entry script at `apps/<AppName>/index.ts`.
|
|
17
17
|
2. Update blueprint defaults in `apps/<AppName>/*.json` (model, props, flags).
|
|
18
18
|
3. Keep per-instance overrides in `world.json`.
|
|
19
|
-
4. Use `
|
|
19
|
+
4. Use `gamedev dev` for continuous sync; use `gamedev apps build --all` and `gamedev apps deploy <app>` for explicit deploys.
|
|
20
20
|
|
|
21
21
|
## Runtime Constraints
|
|
22
22
|
|
|
@@ -38,4 +38,4 @@ description: Hyperfy world-project app scripting workflow and constraints. Use w
|
|
|
38
38
|
## Guardrails
|
|
39
39
|
|
|
40
40
|
- Do not edit `dist/` (build output).
|
|
41
|
-
- Do not edit `.
|
|
41
|
+
- Do not edit `.lobby/*` except `targets.json` (local only).
|
package/bin/gamedev.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'url'
|
|
|
7
7
|
import { spawn } from 'child_process'
|
|
8
8
|
import { customAlphabet } from 'nanoid'
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { runAppCommand } from '../app-server/commands.js'
|
|
11
11
|
import { DirectAppServer } from '../app-server/direct.js'
|
|
12
12
|
import { scaffoldBaseProject, scaffoldBuiltins, writeManifest } from '../app-server/scaffold.js'
|
|
13
13
|
import { applyTargetEnv, parseTargetArgs, resolveTarget, readTargets } from '../app-server/targets.js'
|
|
@@ -59,7 +59,7 @@ function writeDotEnv(filePath, content) {
|
|
|
59
59
|
fs.writeFileSync(filePath, content.endsWith('\n') ? content : `${content}\n`, 'utf8')
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
const TARGETS_FILE = path.join('.
|
|
62
|
+
const TARGETS_FILE = path.join('.lobby', 'targets.json')
|
|
63
63
|
|
|
64
64
|
function writeTargetsFile(targets, rootDir = projectDir) {
|
|
65
65
|
const filePath = path.join(rootDir, TARGETS_FILE)
|
|
@@ -68,13 +68,6 @@ function writeTargetsFile(targets, rootDir = projectDir) {
|
|
|
68
68
|
return filePath
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
function isProjectEmpty(dirPath) {
|
|
72
|
-
const ignore = new Set(['.git', '.DS_Store'])
|
|
73
|
-
if (!fs.existsSync(dirPath)) return true
|
|
74
|
-
const entries = fs.readdirSync(dirPath, { withFileTypes: true })
|
|
75
|
-
return entries.every(entry => ignore.has(entry.name))
|
|
76
|
-
}
|
|
77
|
-
|
|
78
71
|
function generateAdminCode() {
|
|
79
72
|
return crypto.randomBytes(16).toString('base64url')
|
|
80
73
|
}
|
|
@@ -161,7 +154,7 @@ function isLocalWorld({ worldUrl }) {
|
|
|
161
154
|
}
|
|
162
155
|
|
|
163
156
|
function getWorldDir(worldId) {
|
|
164
|
-
return path.join(projectDir, '.
|
|
157
|
+
return path.join(projectDir, '.lobby', worldId)
|
|
165
158
|
}
|
|
166
159
|
|
|
167
160
|
function hasKey(env, key) {
|
|
@@ -282,15 +275,6 @@ function validateLocalEnv(env, derived) {
|
|
|
282
275
|
return issues
|
|
283
276
|
}
|
|
284
277
|
|
|
285
|
-
async function confirmAction(prompt) {
|
|
286
|
-
if (!process.stdin.isTTY) return false
|
|
287
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
288
|
-
const answer = await new Promise(resolve => rl.question(prompt, resolve))
|
|
289
|
-
rl.close()
|
|
290
|
-
const normalized = (answer || '').trim().toLowerCase()
|
|
291
|
-
return normalized === 'y' || normalized === 'yes'
|
|
292
|
-
}
|
|
293
|
-
|
|
294
278
|
async function promptValue(prompt) {
|
|
295
279
|
if (!process.stdin.isTTY) return null
|
|
296
280
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
@@ -302,12 +286,6 @@ async function promptValue(prompt) {
|
|
|
302
286
|
|
|
303
287
|
function ensureEnvForStart() {
|
|
304
288
|
if (!fs.existsSync(envPath)) {
|
|
305
|
-
if (!isProjectEmpty(projectDir)) {
|
|
306
|
-
console.error('Error: Missing .env in a non-empty project.')
|
|
307
|
-
console.error('Hint: Create a .env with WORLD_URL, WORLD_ID, ADMIN_CODE, and world server settings.')
|
|
308
|
-
return { ok: false }
|
|
309
|
-
}
|
|
310
|
-
|
|
311
289
|
const worldId = `local-${uuid()}`
|
|
312
290
|
const adminCode = generateAdminCode()
|
|
313
291
|
const jwtSecret = generateJwtSecret()
|
|
@@ -469,10 +447,10 @@ async function startCommand(args = []) {
|
|
|
469
447
|
|
|
470
448
|
function printInitHelp() {
|
|
471
449
|
console.log(`
|
|
472
|
-
|
|
450
|
+
Gamedev Init
|
|
473
451
|
|
|
474
452
|
Usage:
|
|
475
|
-
|
|
453
|
+
gamedev init [options]
|
|
476
454
|
|
|
477
455
|
Options:
|
|
478
456
|
--name <package> Package name (defaults to folder name)
|
|
@@ -563,12 +541,15 @@ async function initCommand(args = []) {
|
|
|
563
541
|
console.log(`↪️ Skipped ${skipped.length} existing file(s)`)
|
|
564
542
|
}
|
|
565
543
|
|
|
544
|
+
const envResult = ensureEnvForStart()
|
|
545
|
+
if (!envResult.ok) return 1
|
|
546
|
+
|
|
566
547
|
return 0
|
|
567
548
|
}
|
|
568
549
|
|
|
569
550
|
async function appsCommand(args) {
|
|
570
551
|
if (!args.length || ['help', '--help', '-h'].includes(args[0])) {
|
|
571
|
-
await runAppCommand({ command: 'help', args: [], rootDir: projectDir, helpPrefix: '
|
|
552
|
+
await runAppCommand({ command: 'help', args: [], rootDir: projectDir, helpPrefix: 'gamedev apps' })
|
|
572
553
|
return 0
|
|
573
554
|
}
|
|
574
555
|
|
|
@@ -589,7 +570,7 @@ async function appsCommand(args) {
|
|
|
589
570
|
const env = readDotEnv(envPath)
|
|
590
571
|
if (env) applyEnvToProcess(env)
|
|
591
572
|
|
|
592
|
-
return runAppCommand({ command, args: commandArgs, rootDir: projectDir, helpPrefix: '
|
|
573
|
+
return runAppCommand({ command, args: commandArgs, rootDir: projectDir, helpPrefix: 'gamedev apps' })
|
|
593
574
|
}
|
|
594
575
|
|
|
595
576
|
async function connectAdminServer({ worldUrl, adminCode, rootDir }) {
|
|
@@ -610,53 +591,6 @@ async function connectAdminServer({ worldUrl, adminCode, rootDir }) {
|
|
|
610
591
|
}
|
|
611
592
|
}
|
|
612
593
|
|
|
613
|
-
async function projectCommand(args) {
|
|
614
|
-
if (!args.length || ['help', '--help', '-h'].includes(args[0])) {
|
|
615
|
-
printHelp()
|
|
616
|
-
return 0
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
if (args[0] !== 'reset') {
|
|
620
|
-
console.error(`Error: Unknown project command: ${args[0]}`)
|
|
621
|
-
printHelp()
|
|
622
|
-
return 1
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
const force = args.includes('--force') || args.includes('-f')
|
|
626
|
-
const cli = new HyperfyCLI({ rootDir: projectDir })
|
|
627
|
-
await cli.reset({ force })
|
|
628
|
-
return 0
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
async function worldsCommand(args) {
|
|
632
|
-
if (!args.length || ['help', '--help', '-h'].includes(args[0]) || args[0] === 'list') {
|
|
633
|
-
const root = path.join(projectDir, '.hyperfy')
|
|
634
|
-
if (!fs.existsSync(root)) {
|
|
635
|
-
console.log('Worlds: No local worlds found.')
|
|
636
|
-
return 0
|
|
637
|
-
}
|
|
638
|
-
const entries = fs
|
|
639
|
-
.readdirSync(root, { withFileTypes: true })
|
|
640
|
-
.filter(entry => entry.isDirectory())
|
|
641
|
-
.map(entry => entry.name)
|
|
642
|
-
|
|
643
|
-
if (!entries.length) {
|
|
644
|
-
console.log('Worlds: No local worlds found.')
|
|
645
|
-
return 0
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
console.log('Worlds: Local worlds:')
|
|
649
|
-
for (const entry of entries) {
|
|
650
|
-
console.log(` - ${entry}`)
|
|
651
|
-
}
|
|
652
|
-
return 0
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
console.error(`Error: Unknown worlds command: ${args[0]}`)
|
|
656
|
-
printHelp()
|
|
657
|
-
return 1
|
|
658
|
-
}
|
|
659
|
-
|
|
660
594
|
async function worldCommand(args) {
|
|
661
595
|
if (!args.length || ['help', '--help', '-h'].includes(args[0])) {
|
|
662
596
|
printHelp()
|
|
@@ -665,7 +599,7 @@ async function worldCommand(args) {
|
|
|
665
599
|
|
|
666
600
|
const action = args[0]
|
|
667
601
|
|
|
668
|
-
if (action === 'export' || action === 'import'
|
|
602
|
+
if (action === 'export' || action === 'import') {
|
|
669
603
|
const env = readDotEnv(envPath)
|
|
670
604
|
if (!env) {
|
|
671
605
|
console.error('Error: Missing .env in this project.')
|
|
@@ -680,37 +614,6 @@ async function worldCommand(args) {
|
|
|
680
614
|
return 1
|
|
681
615
|
}
|
|
682
616
|
|
|
683
|
-
if (action === 'wipe') {
|
|
684
|
-
if (!isLocalWorld({ worldUrl })) {
|
|
685
|
-
console.error('Error: WORLD_URL does not indicate a local world. Refusing to wipe.')
|
|
686
|
-
return 1
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
const worldDir = getWorldDir(worldId)
|
|
690
|
-
if (!fs.existsSync(worldDir)) {
|
|
691
|
-
console.log(`Worlds: No local world found at ${worldDir}`)
|
|
692
|
-
return 0
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
const force = args.includes('--force') || args.includes('-f')
|
|
696
|
-
if (!force) {
|
|
697
|
-
const ok = await confirmAction(`Delete local world data at ${worldDir}? (y/N): `)
|
|
698
|
-
if (!ok) {
|
|
699
|
-
console.log('World wipe cancelled')
|
|
700
|
-
return 1
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
try {
|
|
705
|
-
fs.rmSync(worldDir, { recursive: true, force: true })
|
|
706
|
-
console.log(`Deleted ${worldDir}`)
|
|
707
|
-
return 0
|
|
708
|
-
} catch (error) {
|
|
709
|
-
console.error(`Error: Failed to delete ${worldDir}`, error?.message || error)
|
|
710
|
-
return 1
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
|
|
714
617
|
let server
|
|
715
618
|
try {
|
|
716
619
|
server = await connectAdminServer({ worldUrl, adminCode: env.ADMIN_CODE, rootDir: projectDir })
|
|
@@ -740,10 +643,10 @@ async function worldCommand(args) {
|
|
|
740
643
|
|
|
741
644
|
function printFlyHelp() {
|
|
742
645
|
console.log(`
|
|
743
|
-
|
|
646
|
+
Gamedev Fly
|
|
744
647
|
|
|
745
648
|
Usage:
|
|
746
|
-
|
|
649
|
+
gamedev fly <command> [options]
|
|
747
650
|
|
|
748
651
|
Commands:
|
|
749
652
|
init Generate fly.toml and optional GitHub workflow
|
|
@@ -755,11 +658,11 @@ Init options:
|
|
|
755
658
|
--region <code> Fly region (default: ams)
|
|
756
659
|
--persist Enable volume mount + SAVE_INTERVAL
|
|
757
660
|
--world-id <id> WORLD_ID override (default: fly-<app>)
|
|
758
|
-
--target <name> Update .
|
|
661
|
+
--target <name> Update .lobby/targets.json with worldUrl/worldId
|
|
759
662
|
--force, -f Overwrite existing fly.toml / workflow
|
|
760
663
|
|
|
761
664
|
Secrets options:
|
|
762
|
-
--target <name> Update .
|
|
665
|
+
--target <name> Update .lobby/targets.json with generated codes
|
|
763
666
|
--no-deploy-code Skip DEPLOY_CODE generation
|
|
764
667
|
--force, -f Overwrite existing target codes
|
|
765
668
|
`)
|
|
@@ -1067,26 +970,22 @@ async function flyCommand(args) {
|
|
|
1067
970
|
|
|
1068
971
|
function printHelp() {
|
|
1069
972
|
console.log(`
|
|
1070
|
-
|
|
973
|
+
Gamedev CLI
|
|
1071
974
|
|
|
1072
975
|
Usage:
|
|
1073
|
-
|
|
976
|
+
gamedev <command> [options]
|
|
1074
977
|
|
|
1075
978
|
Commands:
|
|
1076
979
|
init Scaffold a new world project in the current folder
|
|
1077
|
-
|
|
1078
|
-
dev Alias for start
|
|
980
|
+
dev Start the world (local or remote) + app-server sync
|
|
1079
981
|
apps <command> Manage apps (create, list, build, clean, deploy, update, validate, status)
|
|
1080
982
|
fly <command> Fly.io deployment helpers
|
|
1081
|
-
project reset [--force] Delete local apps/assets/world.json in this project
|
|
1082
983
|
world export Export world.json + apps/assets from the world (use --include-built-scripts for scripts)
|
|
1083
984
|
world import Import local apps + world.json into the world
|
|
1084
|
-
world wipe [--force] Delete the local world runtime directory for this project
|
|
1085
|
-
worlds list List local world directories in ./.hyperfy
|
|
1086
985
|
help Show this help
|
|
1087
986
|
|
|
1088
987
|
Options:
|
|
1089
|
-
--target <name> Use .
|
|
988
|
+
--target <name> Use .lobby/targets.json entry (applies to dev/apps)
|
|
1090
989
|
`)
|
|
1091
990
|
}
|
|
1092
991
|
|
|
@@ -1096,17 +995,12 @@ async function main() {
|
|
|
1096
995
|
switch (command) {
|
|
1097
996
|
case 'init':
|
|
1098
997
|
return initCommand(args)
|
|
1099
|
-
case 'start':
|
|
1100
998
|
case 'dev':
|
|
1101
999
|
return startCommand(args)
|
|
1102
1000
|
case 'apps':
|
|
1103
1001
|
return appsCommand(args)
|
|
1104
|
-
case 'project':
|
|
1105
|
-
return projectCommand(args)
|
|
1106
1002
|
case 'world':
|
|
1107
1003
|
return worldCommand(args)
|
|
1108
|
-
case 'worlds':
|
|
1109
|
-
return worldsCommand(args)
|
|
1110
1004
|
case 'fly':
|
|
1111
1005
|
return flyCommand(args)
|
|
1112
1006
|
case 'help':
|
package/build/public/admin.html
CHANGED
|
@@ -25,14 +25,14 @@
|
|
|
25
25
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
26
26
|
<meta name="mobile-web-app-capable" content="yes" />
|
|
27
27
|
<link rel="preload" href="/rubik.woff2" as="font" type="font/woff2" crossorigin />
|
|
28
|
-
<link rel="stylesheet" type="text/css" href="/index.css?v=
|
|
28
|
+
<link rel="stylesheet" type="text/css" href="/index.css?v=1768878465145" />
|
|
29
29
|
<script>
|
|
30
30
|
window.PARTICLES_PATH = '/particles-4YQR4CFO.js'
|
|
31
31
|
</script>
|
|
32
32
|
</head>
|
|
33
33
|
<body>
|
|
34
34
|
<div id="root"></div>
|
|
35
|
-
<script src="/env.js?v=
|
|
35
|
+
<script src="/env.js?v=1768878465145"></script>
|
|
36
36
|
<script src="/admin-EJMJPJ7M.js" type="module"></script>
|
|
37
37
|
</body>
|
|
38
38
|
</html>
|
package/build/public/index.html
CHANGED
|
@@ -26,14 +26,14 @@
|
|
|
26
26
|
<meta name="mobile-web-app-capable" content="yes" />
|
|
27
27
|
<!-- <link rel='icon' href='/favicon.ico' /> -->
|
|
28
28
|
<link rel="preload" href="/rubik.woff2" as="font" type="font/woff2" crossorigin />
|
|
29
|
-
<link rel="stylesheet" type="text/css" href="/index.css?v=
|
|
29
|
+
<link rel="stylesheet" type="text/css" href="/index.css?v=1768878465144" />
|
|
30
30
|
<script>
|
|
31
31
|
window.PARTICLES_PATH = '/particles-4YQR4CFO.js'
|
|
32
32
|
</script>
|
|
33
33
|
</head>
|
|
34
34
|
<body>
|
|
35
35
|
<div id="root"></div>
|
|
36
|
-
<script src="/env.js?v=
|
|
36
|
+
<script src="/env.js?v=1768878465144"></script>
|
|
37
37
|
<script src="/index-RJRKEY6V.js" type="module"></script>
|
|
38
38
|
</body>
|
|
39
39
|
</html>
|