@dcl/sdk-commands 7.0.0-4293371227.commit-54082c6 → 7.0.0-4295573637.commit-6d503ad
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/dist/commands/build/index.js +1 -1
- package/dist/commands/deploy/index.d.ts +31 -0
- package/dist/commands/deploy/index.js +131 -0
- package/dist/commands/deploy/linker-dapp/api.d.ts +24 -0
- package/dist/commands/deploy/linker-dapp/api.js +92 -0
- package/dist/commands/deploy/linker-dapp/catalyst-pointers.d.ts +20 -0
- package/dist/commands/deploy/linker-dapp/catalyst-pointers.js +45 -0
- package/dist/commands/deploy/linker-dapp/routes.d.ts +8 -0
- package/dist/commands/deploy/linker-dapp/routes.js +91 -0
- package/dist/commands/export-static/index.js +1 -1
- package/dist/commands/start/index.js +2 -3
- package/dist/components/analytics.js +5 -1
- package/dist/logic/account.d.ts +6 -0
- package/dist/logic/account.js +21 -0
- package/dist/logic/dcl-info.d.ts +26 -0
- package/dist/logic/dcl-info.js +90 -0
- package/dist/logic/get-free-port.d.ts +1 -1
- package/dist/logic/get-free-port.js +6 -6
- package/dist/logic/project-files.d.ts +0 -2
- package/dist/logic/project-files.js +2 -8
- package/dist/logic/project-validations.js +1 -1
- package/dist/logic/scene-validations.d.ts +18 -3
- package/dist/logic/scene-validations.js +40 -7
- package/package.json +8 -3
- package/src/commands/build/index.ts +0 -77
- package/src/commands/export-static/index.ts +0 -150
- package/src/commands/init/index.ts +0 -68
- package/src/commands/init/repos.ts +0 -17
- package/src/commands/start/index.ts +0 -221
- package/src/commands/start/server/endpoints.ts +0 -471
- package/src/commands/start/server/file-watch-notifier.ts +0 -45
- package/src/commands/start/server/realm.ts +0 -63
- package/src/commands/start/server/routes.ts +0 -36
- package/src/commands/start/server/ws.ts +0 -24
- package/src/commands/start/types.ts +0 -26
- package/src/components/analytics.ts +0 -92
- package/src/components/dcl-info-config.ts +0 -63
- package/src/components/eth.ts +0 -3
- package/src/components/fetch.ts +0 -11
- package/src/components/fs.ts +0 -62
- package/src/components/index.ts +0 -26
- package/src/components/log.ts +0 -48
- package/src/index.ts +0 -90
- package/src/logic/args.ts +0 -19
- package/src/logic/beautiful-logs.ts +0 -26
- package/src/logic/catalyst-requests.ts +0 -31
- package/src/logic/commands.ts +0 -28
- package/src/logic/config.ts +0 -45
- package/src/logic/coordinates.ts +0 -95
- package/src/logic/dcl-ignore.ts +0 -50
- package/src/logic/error.ts +0 -1
- package/src/logic/exec.ts +0 -36
- package/src/logic/fs.ts +0 -76
- package/src/logic/get-free-port.ts +0 -15
- package/src/logic/project-files.ts +0 -92
- package/src/logic/project-validations.ts +0 -61
- package/src/logic/realm.ts +0 -28
- package/src/logic/scene-validations.ts +0 -81
- package/tsconfig.json +0 -28
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import * as os from 'os'
|
|
2
|
-
import * as path from 'path'
|
|
3
|
-
import open from 'open'
|
|
4
|
-
import future from 'fp-future'
|
|
5
|
-
|
|
6
|
-
import { CliComponents } from '../../components'
|
|
7
|
-
import { main as build } from '../build'
|
|
8
|
-
import { getArgs } from '../../logic/args'
|
|
9
|
-
import { needsDependencies, npmRun } from '../../logic/project-validations'
|
|
10
|
-
import { getBaseCoords, validateSceneJson } from '../../logic/scene-validations'
|
|
11
|
-
import { CliError } from '../../logic/error'
|
|
12
|
-
import { previewPort } from '../../logic/get-free-port'
|
|
13
|
-
import { ISignalerComponent, PreviewComponents } from './types'
|
|
14
|
-
import { createTestMetricsComponent } from '@well-known-components/metrics'
|
|
15
|
-
import { Lifecycle, IBaseComponent } from '@well-known-components/interfaces'
|
|
16
|
-
import { createRecordConfigComponent } from '@well-known-components/env-config-provider'
|
|
17
|
-
import { createRoomsComponent, roomsMetrics } from '@dcl/mini-comms/dist/adapters/rooms'
|
|
18
|
-
import { createServerComponent } from '@well-known-components/http-server'
|
|
19
|
-
import { createConsoleLogComponent } from '@well-known-components/logger'
|
|
20
|
-
import { providerInstance } from '../../components/eth'
|
|
21
|
-
import { createStdoutCliLogger } from '../../components/log'
|
|
22
|
-
import { wireFileWatcherToWebSockets } from './server/file-watch-notifier'
|
|
23
|
-
import { wireRouter } from './server/routes'
|
|
24
|
-
import { createWsComponent } from './server/ws'
|
|
25
|
-
import { b64HashingFunction, getSceneJson } from '../../logic/project-files'
|
|
26
|
-
|
|
27
|
-
interface Options {
|
|
28
|
-
args: typeof args
|
|
29
|
-
components: Pick<CliComponents, 'fetch' | 'fs' | 'logger' | 'dclInfoConfig' | 'analytics'>
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export const args = getArgs({
|
|
33
|
-
'--dir': String,
|
|
34
|
-
'--help': Boolean,
|
|
35
|
-
'--port': Number,
|
|
36
|
-
'--no-debug': Boolean,
|
|
37
|
-
'--no-browser': Boolean,
|
|
38
|
-
'--no-watch': Boolean,
|
|
39
|
-
'--ci': Boolean,
|
|
40
|
-
'--skip-install': Boolean,
|
|
41
|
-
'--web3': Boolean,
|
|
42
|
-
'-h': '--help',
|
|
43
|
-
'-p': '--port',
|
|
44
|
-
'-d': '--no-debug',
|
|
45
|
-
'-b': '--no-browser',
|
|
46
|
-
'-w': '--no-watch',
|
|
47
|
-
'--skip-build': Boolean,
|
|
48
|
-
'--desktop-client': Boolean
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
export function help() {
|
|
52
|
-
return `
|
|
53
|
-
Usage: sdk-commands start [options]
|
|
54
|
-
|
|
55
|
-
Options:
|
|
56
|
-
|
|
57
|
-
-h, --help Displays complete help
|
|
58
|
-
-p, --port [port] Select a custom port for the development server
|
|
59
|
-
-d, --no-debug Disable debugging panel
|
|
60
|
-
-b, --no-browser Do not open a new browser window
|
|
61
|
-
-w, --no-watch Do not open watch for filesystem changes
|
|
62
|
-
-c, --ci Run the parcel previewer on a remote unix server
|
|
63
|
-
--web3 Connects preview to browser wallet to use the associated avatar and account
|
|
64
|
-
--skip-build Skip build and only serve the files in preview mode
|
|
65
|
-
--desktop-client Show URL to launch preview in the desktop client (BETA)
|
|
66
|
-
|
|
67
|
-
Examples:
|
|
68
|
-
|
|
69
|
-
- Start a local development server for a Decentraland Scene at port 3500
|
|
70
|
-
|
|
71
|
-
$ sdk-commands start -p 3500
|
|
72
|
-
|
|
73
|
-
- Start a local development server for a Decentraland Scene at a docker container
|
|
74
|
-
|
|
75
|
-
$ sdk-commands start --ci
|
|
76
|
-
`
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export async function main(options: Options) {
|
|
80
|
-
const projectRoot = path.resolve(process.cwd(), options.args['--dir'] || '.')
|
|
81
|
-
const isCi = args['--ci'] || process.env.CI || false
|
|
82
|
-
const debug = !args['--no-debug'] && !isCi
|
|
83
|
-
const openBrowser = !args['--no-browser'] && !isCi
|
|
84
|
-
const skipBuild = args['--skip-build']
|
|
85
|
-
const watch = !args['--no-watch']
|
|
86
|
-
const enableWeb3 = args['--web3']
|
|
87
|
-
|
|
88
|
-
// TODO: FIX this hardcoded values ?
|
|
89
|
-
const hasPortableExperience = false
|
|
90
|
-
|
|
91
|
-
// first run `npm run build`, this can be disabled with --skip-build
|
|
92
|
-
if (!skipBuild) {
|
|
93
|
-
await npmRun(projectRoot, 'build')
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// then start the embedded compiler, this can be disabled with --no-watch
|
|
97
|
-
if (watch) {
|
|
98
|
-
await build({ ...options, args: { '--dir': projectRoot, '--watch': watch } })
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
await validateSceneJson(options.components, projectRoot)
|
|
102
|
-
const sceneJson = await getSceneJson(options.components, projectRoot)
|
|
103
|
-
const baseCoords = getBaseCoords(sceneJson)
|
|
104
|
-
|
|
105
|
-
if (await needsDependencies(options.components, projectRoot)) {
|
|
106
|
-
const npmModulesPath = path.resolve(projectRoot, 'node_modules')
|
|
107
|
-
throw new CliError(`Couldn\'t find ${npmModulesPath}, please run: npm install`)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const port = options.args['--port'] || (await previewPort())
|
|
111
|
-
|
|
112
|
-
const program = await Lifecycle.run<PreviewComponents>({
|
|
113
|
-
async initComponents() {
|
|
114
|
-
const metrics = createTestMetricsComponent(roomsMetrics)
|
|
115
|
-
const config = createRecordConfigComponent({
|
|
116
|
-
HTTP_SERVER_PORT: port.toString(),
|
|
117
|
-
HTTP_SERVER_HOST: '0.0.0.0',
|
|
118
|
-
...process.env
|
|
119
|
-
})
|
|
120
|
-
const logs = await createConsoleLogComponent({})
|
|
121
|
-
const ws = await createWsComponent({ logs })
|
|
122
|
-
const server = await createServerComponent<PreviewComponents>({ config, logs, ws: ws.ws }, {})
|
|
123
|
-
const rooms = await createRoomsComponent({
|
|
124
|
-
metrics,
|
|
125
|
-
logs,
|
|
126
|
-
config
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
const programClosed = future<void>()
|
|
130
|
-
const signaler: IBaseComponent & ISignalerComponent = {
|
|
131
|
-
programClosed,
|
|
132
|
-
async stop() {
|
|
133
|
-
// this promise is resolved upon SIGTERM or SIGHUP
|
|
134
|
-
// or when program.stop is called
|
|
135
|
-
programClosed.resolve()
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return {
|
|
140
|
-
...options.components,
|
|
141
|
-
logger: createStdoutCliLogger(),
|
|
142
|
-
logs,
|
|
143
|
-
ethereumProvider: providerInstance,
|
|
144
|
-
rooms,
|
|
145
|
-
config,
|
|
146
|
-
metrics,
|
|
147
|
-
server,
|
|
148
|
-
ws,
|
|
149
|
-
signaler
|
|
150
|
-
}
|
|
151
|
-
},
|
|
152
|
-
async main({ components, startComponents }) {
|
|
153
|
-
await wireRouter(components, projectRoot)
|
|
154
|
-
if (watch) {
|
|
155
|
-
await wireFileWatcherToWebSockets(components, projectRoot)
|
|
156
|
-
}
|
|
157
|
-
await startComponents()
|
|
158
|
-
|
|
159
|
-
const networkInterfaces = os.networkInterfaces()
|
|
160
|
-
const availableURLs: string[] = []
|
|
161
|
-
await components.analytics.track('Preview started', {
|
|
162
|
-
projectHash: await b64HashingFunction(projectRoot),
|
|
163
|
-
coords: baseCoords,
|
|
164
|
-
isWorkspace: false
|
|
165
|
-
})
|
|
166
|
-
components.logger.log(`Preview server is now running!`)
|
|
167
|
-
components.logger.log('Available on:\n')
|
|
168
|
-
|
|
169
|
-
Object.keys(networkInterfaces).forEach((dev) => {
|
|
170
|
-
;(networkInterfaces[dev] || []).forEach((details) => {
|
|
171
|
-
if (details.family === 'IPv4') {
|
|
172
|
-
let addr = `http://${details.address}:${port}?position=${baseCoords.x}%2C${baseCoords.y}&ENABLE_ECS7`
|
|
173
|
-
if (debug) {
|
|
174
|
-
addr = `${addr}&SCENE_DEBUG_PANEL`
|
|
175
|
-
}
|
|
176
|
-
if (enableWeb3 || hasPortableExperience) {
|
|
177
|
-
addr = `${addr}&ENABLE_WEB3`
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
availableURLs.push(addr)
|
|
181
|
-
}
|
|
182
|
-
})
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
// Push localhost and 127.0.0.1 at top
|
|
186
|
-
const sortedURLs = availableURLs.sort((a, _b) => {
|
|
187
|
-
return a.toLowerCase().includes('localhost') || a.includes('127.0.0.1') || a.includes('0.0.0.0') ? -1 : 1
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
for (const addr of sortedURLs) {
|
|
191
|
-
components.logger.log(` ${addr}`)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (args['--desktop-client']) {
|
|
195
|
-
components.logger.log('\n Desktop client:\n')
|
|
196
|
-
for (const addr of sortedURLs) {
|
|
197
|
-
const searchParams = new URLSearchParams()
|
|
198
|
-
searchParams.append('PREVIEW-MODE', addr)
|
|
199
|
-
components.logger.log(` dcl://${searchParams.toString()}&`)
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
components.logger.log('\n Details:\n')
|
|
204
|
-
components.logger.log('\nPress CTRL+C to exit\n')
|
|
205
|
-
|
|
206
|
-
// Open preferably localhost/127.0.0.1
|
|
207
|
-
if (openBrowser && sortedURLs.length && !args['--desktop-client']) {
|
|
208
|
-
try {
|
|
209
|
-
await open(sortedURLs[0])
|
|
210
|
-
} catch (_) {
|
|
211
|
-
components.logger.warn('Unable to open browser automatically.')
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
// this signal is resolved by: (wkc)program.stop(), SIGTERM, SIGHUP
|
|
218
|
-
// we must wait for it to resolve (when the server stops) to continue with the
|
|
219
|
-
// program
|
|
220
|
-
await program.components.signaler.programClosed
|
|
221
|
-
}
|
|
@@ -1,471 +0,0 @@
|
|
|
1
|
-
import { Router } from '@well-known-components/http-server'
|
|
2
|
-
import { PreviewComponents } from '../types'
|
|
3
|
-
import * as path from 'path'
|
|
4
|
-
import { WearableJson } from '@dcl/schemas/dist/sdk'
|
|
5
|
-
import { Entity, EntityType, Locale, Wearable } from '@dcl/schemas'
|
|
6
|
-
import fetch, { Headers } from 'node-fetch'
|
|
7
|
-
import { fetchEntityByPointer } from '../../../logic/catalyst-requests'
|
|
8
|
-
import { CliComponents } from '../../../components'
|
|
9
|
-
import { b64HashingFunction, getProjectContentMappings } from '../../../logic/project-files'
|
|
10
|
-
|
|
11
|
-
function getCatalystUrl(): URL {
|
|
12
|
-
return new URL('https://peer.decentraland.org')
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function smartWearableNameToId(name: string) {
|
|
16
|
-
return name.toLocaleLowerCase().replace(/ /g, '-')
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
type LambdasWearable = Wearable & {
|
|
20
|
-
baseUrl: string
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function setupEcs6Endpoints(components: CliComponents, dir: string, router: Router<PreviewComponents>) {
|
|
24
|
-
const baseFolders = [dir]
|
|
25
|
-
// handle old preview scene.json
|
|
26
|
-
router.get('/scene.json', async () => {
|
|
27
|
-
return {
|
|
28
|
-
headers: { 'content-type': 'application/json' },
|
|
29
|
-
body: components.fs.createReadStream(path.join(dir, 'scene.json'))
|
|
30
|
-
}
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
router.get('/lambdas/explore/realms', async (ctx) => {
|
|
34
|
-
return {
|
|
35
|
-
body: [
|
|
36
|
-
{
|
|
37
|
-
serverName: 'localhost',
|
|
38
|
-
url: `http://${ctx.url.host}`,
|
|
39
|
-
layer: 'stub',
|
|
40
|
-
usersCount: 0,
|
|
41
|
-
maxUsers: 100,
|
|
42
|
-
userParcels: []
|
|
43
|
-
}
|
|
44
|
-
]
|
|
45
|
-
}
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
router.get('/lambdas/contracts/servers', async (ctx) => {
|
|
49
|
-
return {
|
|
50
|
-
body: [
|
|
51
|
-
{
|
|
52
|
-
address: `http://${ctx.url.host}`,
|
|
53
|
-
owner: '0x0000000000000000000000000000000000000000',
|
|
54
|
-
id: '0x0000000000000000000000000000000000000000000000000000000000000000'
|
|
55
|
-
}
|
|
56
|
-
]
|
|
57
|
-
}
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
router.get('/lambdas/profiles', async (ctx, next) => {
|
|
61
|
-
const baseUrl = `${ctx.url.protocol}//${ctx.url.host}/content/contents`
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
const previewWearables = await getAllPreviewWearables(components, {
|
|
65
|
-
baseFolders,
|
|
66
|
-
baseUrl
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
if (previewWearables.length === 1) {
|
|
70
|
-
const catalystUrl = getCatalystUrl()
|
|
71
|
-
|
|
72
|
-
const u = new URL(ctx.url.toString())
|
|
73
|
-
u.host = catalystUrl.host
|
|
74
|
-
u.protocol = catalystUrl.protocol
|
|
75
|
-
u.port = catalystUrl.port
|
|
76
|
-
const req = await fetch(u.toString(), {
|
|
77
|
-
headers: {
|
|
78
|
-
connection: 'close'
|
|
79
|
-
},
|
|
80
|
-
method: ctx.request.method,
|
|
81
|
-
body: ctx.request.method === 'get' ? undefined : ctx.request.body
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
const deployedProfile = (await req.json()) as any[]
|
|
85
|
-
|
|
86
|
-
if (deployedProfile?.length === 1) {
|
|
87
|
-
deployedProfile[0].avatars[0].avatar.wearables.push(...previewWearables.map(($) => $.id))
|
|
88
|
-
return {
|
|
89
|
-
headers: {
|
|
90
|
-
'content-type': req.headers.get('content-type') || 'application/binary'
|
|
91
|
-
},
|
|
92
|
-
body: deployedProfile
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
} catch (err) {
|
|
97
|
-
console.warn(`Failed to catch profile and fill with preview wearables.`, err)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return next()
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
router.all('/lambdas/:path+', async (ctx) => {
|
|
104
|
-
const catalystUrl = getCatalystUrl()
|
|
105
|
-
const u = new URL(ctx.url.toString())
|
|
106
|
-
u.host = catalystUrl.host
|
|
107
|
-
u.protocol = catalystUrl.protocol
|
|
108
|
-
u.port = catalystUrl.port
|
|
109
|
-
const req = await fetch(u.toString(), {
|
|
110
|
-
headers: {
|
|
111
|
-
connection: 'close'
|
|
112
|
-
},
|
|
113
|
-
method: ctx.request.method,
|
|
114
|
-
body: ctx.request.method === 'get' ? undefined : ctx.request.body
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
headers: {
|
|
119
|
-
'content-type': req.headers.get('content-type') || 'application/binary'
|
|
120
|
-
},
|
|
121
|
-
body: req.body
|
|
122
|
-
}
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
router.post('/content/entities', async (ctx) => {
|
|
126
|
-
const catalystUrl = getCatalystUrl()
|
|
127
|
-
const headers = new Headers()
|
|
128
|
-
console.log(ctx.request.headers)
|
|
129
|
-
const res = await fetch(`${catalystUrl.toString()}/content/entities`, {
|
|
130
|
-
method: 'post',
|
|
131
|
-
headers,
|
|
132
|
-
body: ctx.request.body
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
return res
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
serveStatic(components, dir, router)
|
|
139
|
-
|
|
140
|
-
// TODO: get workspace scenes & wearables...
|
|
141
|
-
|
|
142
|
-
serveFolders(components, router, baseFolders)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function serveFolders(components: Pick<CliComponents, 'fs'>, router: Router<PreviewComponents>, baseFolders: string[]) {
|
|
146
|
-
router.get('/content/contents/:hash', async (ctx: any, next: any) => {
|
|
147
|
-
if (ctx.params.hash && ctx.params.hash.startsWith('b64-')) {
|
|
148
|
-
const fullPath = path.resolve(Buffer.from(ctx.params.hash.replace(/^b64-/, ''), 'base64').toString('utf8'))
|
|
149
|
-
|
|
150
|
-
// only return files IF the file is within a baseFolder
|
|
151
|
-
if (!baseFolders.find((folder: string) => fullPath.startsWith(folder))) {
|
|
152
|
-
return next()
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return {
|
|
156
|
-
headers: {
|
|
157
|
-
'x-timestamp': Date.now(),
|
|
158
|
-
'x-sent': true,
|
|
159
|
-
'cache-control': 'no-cache,private,max-age=1'
|
|
160
|
-
},
|
|
161
|
-
body: components.fs.createReadStream(fullPath)
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return next()
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
async function pointerRequestHandler(pointers: string[]) {
|
|
169
|
-
if (!pointers || pointers.length === 0) {
|
|
170
|
-
return []
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const requestedPointers = new Set<string>(
|
|
174
|
-
pointers && typeof pointers === 'string' ? [pointers as string] : (pointers as string[])
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
const resultEntities = await getSceneJson(components, baseFolders, Array.from(requestedPointers))
|
|
178
|
-
const catalystUrl = getCatalystUrl()
|
|
179
|
-
const remote = fetchEntityByPointer(
|
|
180
|
-
catalystUrl.toString(),
|
|
181
|
-
pointers.filter(($: string) => !$.match(/-?\d+,-?\d+/))
|
|
182
|
-
)
|
|
183
|
-
|
|
184
|
-
const serverEntities = Array.isArray(remote) ? remote : []
|
|
185
|
-
|
|
186
|
-
return [...resultEntities, ...serverEntities]
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// REVIEW RESPONSE FORMAT
|
|
190
|
-
router.get('/content/entities/scene', async (ctx) => {
|
|
191
|
-
return {
|
|
192
|
-
body: await pointerRequestHandler(ctx.url.searchParams.getAll('pointer'))
|
|
193
|
-
}
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
// REVIEW RESPONSE FORMAT
|
|
197
|
-
router.post('/content/entities/active', async (ctx) => {
|
|
198
|
-
const body = await ctx.request.json()
|
|
199
|
-
return {
|
|
200
|
-
body: await pointerRequestHandler(body.pointers)
|
|
201
|
-
}
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
router.get('/preview-wearables/:id', async (ctx) => {
|
|
205
|
-
const baseUrl = `${ctx.url.protocol}//${ctx.url.host}/content/contents`
|
|
206
|
-
const wearables = await getAllPreviewWearables(components, {
|
|
207
|
-
baseUrl,
|
|
208
|
-
baseFolders
|
|
209
|
-
})
|
|
210
|
-
const wearableId = ctx.params.id
|
|
211
|
-
return {
|
|
212
|
-
body: {
|
|
213
|
-
ok: true,
|
|
214
|
-
data: wearables.filter((wearable) => smartWearableNameToId(wearable?.name) === wearableId)
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
router.get('/preview-wearables', async (ctx) => {
|
|
220
|
-
const baseUrl = `${ctx.url.protocol}//${ctx.url.host}/content/contents`
|
|
221
|
-
return {
|
|
222
|
-
body: {
|
|
223
|
-
ok: true,
|
|
224
|
-
data: await getAllPreviewWearables(components, { baseUrl, baseFolders })
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
})
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
async function getAllPreviewWearables(
|
|
231
|
-
components: Pick<CliComponents, 'fs'>,
|
|
232
|
-
{ baseFolders, baseUrl }: { baseFolders: string[]; baseUrl: string }
|
|
233
|
-
) {
|
|
234
|
-
const wearablePathArray: string[] = []
|
|
235
|
-
for (const wearableDir of baseFolders) {
|
|
236
|
-
const wearableJsonPath = path.resolve(wearableDir, 'wearable.json')
|
|
237
|
-
if (await components.fs.fileExists(wearableJsonPath)) {
|
|
238
|
-
wearablePathArray.push(wearableJsonPath)
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const ret: LambdasWearable[] = []
|
|
243
|
-
for (const wearableJsonPath of wearablePathArray) {
|
|
244
|
-
try {
|
|
245
|
-
ret.push(await serveWearable(components, wearableJsonPath, baseUrl))
|
|
246
|
-
} catch (err) {
|
|
247
|
-
console.error(`Couldn't mock the wearable ${wearableJsonPath}. Please verify the correct format and scheme.`, err)
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
return ret
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
async function serveWearable(
|
|
254
|
-
components: Pick<CliComponents, 'fs'>,
|
|
255
|
-
wearableJsonPath: string,
|
|
256
|
-
baseUrl: string
|
|
257
|
-
): Promise<LambdasWearable> {
|
|
258
|
-
const wearableDir = path.dirname(wearableJsonPath)
|
|
259
|
-
const wearableJson = JSON.parse((await components.fs.readFile(wearableJsonPath)).toString())
|
|
260
|
-
|
|
261
|
-
if (!WearableJson.validate(wearableJson)) {
|
|
262
|
-
const errors = (WearableJson.validate.errors || []).map((a) => `${a.data} ${a.message}`).join('')
|
|
263
|
-
|
|
264
|
-
console.error(`Unable to validate wearable.json properly, please check it.`, errors)
|
|
265
|
-
throw new Error(`Invalid wearable.json (${wearableJsonPath})`)
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
const hashedFiles = await getProjectContentMappings(components, wearableDir, b64HashingFunction)
|
|
269
|
-
|
|
270
|
-
const thumbnailFiltered = hashedFiles.filter(($) => $?.file === 'thumbnail.png')
|
|
271
|
-
const thumbnail =
|
|
272
|
-
thumbnailFiltered.length > 0 && thumbnailFiltered[0]?.hash && `${baseUrl}/${thumbnailFiltered[0].hash}`
|
|
273
|
-
|
|
274
|
-
const wearableId = 'urn:8dc2d7ad-97e3-44d0-ba89-e8305d795a6a'
|
|
275
|
-
|
|
276
|
-
const representations = wearableJson.data.representations.map((representation) => ({
|
|
277
|
-
...representation,
|
|
278
|
-
mainFile: `male/${representation.mainFile}`,
|
|
279
|
-
contents: hashedFiles.map(($) => ({
|
|
280
|
-
key: `male/${$?.file}`,
|
|
281
|
-
url: `${baseUrl}/${$?.hash}`,
|
|
282
|
-
hash: $?.hash
|
|
283
|
-
}))
|
|
284
|
-
}))
|
|
285
|
-
|
|
286
|
-
return {
|
|
287
|
-
id: wearableId,
|
|
288
|
-
rarity: wearableJson.rarity,
|
|
289
|
-
i18n: [{ code: 'en' as Locale, text: wearableJson.name }],
|
|
290
|
-
description: wearableJson.description,
|
|
291
|
-
thumbnail: thumbnail || '',
|
|
292
|
-
image: thumbnail || '',
|
|
293
|
-
collectionAddress: '0x0',
|
|
294
|
-
baseUrl: `${baseUrl}/`,
|
|
295
|
-
name: wearableJson.name || '',
|
|
296
|
-
data: {
|
|
297
|
-
category: wearableJson.data.category,
|
|
298
|
-
replaces: [],
|
|
299
|
-
hides: [],
|
|
300
|
-
tags: [],
|
|
301
|
-
representations: representations as any
|
|
302
|
-
// scene: hashedFiles as any,
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
async function getSceneJson(
|
|
308
|
-
components: Pick<CliComponents, 'fs'>,
|
|
309
|
-
projectRoots: string[],
|
|
310
|
-
pointers: string[]
|
|
311
|
-
): Promise<Entity[]> {
|
|
312
|
-
const requestedPointers = new Set<string>(pointers)
|
|
313
|
-
const resultEntities: Entity[] = []
|
|
314
|
-
|
|
315
|
-
const allDeployments = await Promise.all(
|
|
316
|
-
projectRoots.map(async (projectRoot) => fakeEntityV3FromFolder(components, projectRoot, b64HashingFunction))
|
|
317
|
-
)
|
|
318
|
-
|
|
319
|
-
for (const pointer of Array.from(requestedPointers)) {
|
|
320
|
-
// get deployment by pointer
|
|
321
|
-
const theDeployment = allDeployments.find(($) => $ && $.pointers.includes(pointer))
|
|
322
|
-
if (theDeployment) {
|
|
323
|
-
// remove all the required pointers from the requestedPointers set
|
|
324
|
-
// to prevent sending duplicated entities
|
|
325
|
-
theDeployment.pointers.forEach(($) => requestedPointers.delete($))
|
|
326
|
-
|
|
327
|
-
// add the deployment to the results
|
|
328
|
-
resultEntities.push(theDeployment)
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
return resultEntities
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
function serveStatic(components: Pick<CliComponents, 'fs'>, projectRoot: string, router: Router<PreviewComponents>) {
|
|
336
|
-
const sdkPath = path.dirname(
|
|
337
|
-
require.resolve('@dcl/sdk/package.json', {
|
|
338
|
-
paths: [projectRoot]
|
|
339
|
-
})
|
|
340
|
-
)
|
|
341
|
-
const dclExplorerJsonPath = path.dirname(
|
|
342
|
-
require.resolve('@dcl/explorer/package.json', {
|
|
343
|
-
paths: [projectRoot, sdkPath]
|
|
344
|
-
})
|
|
345
|
-
)
|
|
346
|
-
|
|
347
|
-
const dclKernelDefaultProfilePath = path.resolve(dclExplorerJsonPath, 'default-profile')
|
|
348
|
-
const dclKernelImagesDecentralandConnect = path.resolve(dclExplorerJsonPath, 'images', 'decentraland-connect')
|
|
349
|
-
|
|
350
|
-
const routes = [
|
|
351
|
-
{
|
|
352
|
-
route: '/',
|
|
353
|
-
path: path.resolve(dclExplorerJsonPath, 'preview.html'),
|
|
354
|
-
type: 'text/html'
|
|
355
|
-
},
|
|
356
|
-
{
|
|
357
|
-
route: '/favicon.ico',
|
|
358
|
-
path: path.resolve(dclExplorerJsonPath, 'favicon.ico'),
|
|
359
|
-
type: 'text/html'
|
|
360
|
-
},
|
|
361
|
-
{
|
|
362
|
-
route: '/@/explorer/index.js',
|
|
363
|
-
path: path.resolve(dclExplorerJsonPath, 'index.js'),
|
|
364
|
-
type: 'text/javascript'
|
|
365
|
-
}
|
|
366
|
-
]
|
|
367
|
-
|
|
368
|
-
for (const route of routes) {
|
|
369
|
-
router.get(route.route, async (_ctx) => {
|
|
370
|
-
return {
|
|
371
|
-
headers: { 'Content-Type': route.type },
|
|
372
|
-
body: components.fs.createReadStream(route.path)
|
|
373
|
-
}
|
|
374
|
-
})
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
function createStaticRoutes(
|
|
378
|
-
components: Pick<CliComponents, 'fs'>,
|
|
379
|
-
route: string,
|
|
380
|
-
folder: string,
|
|
381
|
-
transform = (str: string) => str
|
|
382
|
-
) {
|
|
383
|
-
router.get(route, async (ctx, next) => {
|
|
384
|
-
const file = ctx.params.path
|
|
385
|
-
const fullPath = path.resolve(folder, transform(file))
|
|
386
|
-
|
|
387
|
-
// only return files IF the file is within a baseFolder
|
|
388
|
-
if (!(await components.fs.fileExists(fullPath))) {
|
|
389
|
-
return next()
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
const headers: Record<string, any> = {
|
|
393
|
-
'x-timestamp': Date.now(),
|
|
394
|
-
'x-sent': true,
|
|
395
|
-
'cache-control': 'no-cache,private,max-age=1'
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
if (fullPath.endsWith('.wasm')) {
|
|
399
|
-
headers['content-type'] = 'application/wasm'
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
return {
|
|
403
|
-
headers,
|
|
404
|
-
body: components.fs.createReadStream(fullPath)
|
|
405
|
-
}
|
|
406
|
-
})
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
createStaticRoutes(components, '/images/decentraland-connect/:path+', dclKernelImagesDecentralandConnect)
|
|
410
|
-
createStaticRoutes(components, '/default-profile/:path+', dclKernelDefaultProfilePath)
|
|
411
|
-
createStaticRoutes(components, '/@/explorer/:path+', dclExplorerJsonPath, (filePath) => filePath.replace(/.br+$/, ''))
|
|
412
|
-
|
|
413
|
-
router.get('/feature-flags/:file', async (ctx) => {
|
|
414
|
-
const res = await fetch(`https://feature-flags.decentraland.zone/${ctx.params.file}`, {
|
|
415
|
-
headers: {
|
|
416
|
-
connection: 'close'
|
|
417
|
-
}
|
|
418
|
-
})
|
|
419
|
-
return {
|
|
420
|
-
body: await res.arrayBuffer()
|
|
421
|
-
}
|
|
422
|
-
})
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
async function fakeEntityV3FromFolder(
|
|
426
|
-
components: Pick<CliComponents, 'fs'>,
|
|
427
|
-
projectRoot: string,
|
|
428
|
-
hashingFunction: (filePath: string) => Promise<string>
|
|
429
|
-
): Promise<Entity | null> {
|
|
430
|
-
const sceneJsonPath = path.resolve(projectRoot, 'scene.json')
|
|
431
|
-
let isParcelScene = true
|
|
432
|
-
|
|
433
|
-
const wearableJsonPath = path.resolve(projectRoot, 'wearable.json')
|
|
434
|
-
if (await components.fs.fileExists(wearableJsonPath)) {
|
|
435
|
-
try {
|
|
436
|
-
const wearableJson = JSON.parse(await components.fs.readFile(wearableJsonPath, 'utf-8'))
|
|
437
|
-
if (!WearableJson.validate(wearableJson)) {
|
|
438
|
-
const errors = (WearableJson.validate.errors || []).map((a) => `${a.data} ${a.message}`).join('')
|
|
439
|
-
|
|
440
|
-
console.error(`Unable to validate wearable.json properly, please check it.`, errors)
|
|
441
|
-
console.error(`Invalid wearable.json (${wearableJsonPath})`)
|
|
442
|
-
} else {
|
|
443
|
-
isParcelScene = false
|
|
444
|
-
}
|
|
445
|
-
} catch (err) {
|
|
446
|
-
console.error(`Unable to load wearable.json properly`, err)
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
if ((await components.fs.fileExists(sceneJsonPath)) && isParcelScene) {
|
|
451
|
-
const sceneJson = JSON.parse(await components.fs.readFile(sceneJsonPath, 'utf-8'))
|
|
452
|
-
const { base, parcels }: { base: string; parcels: string[] } = sceneJson.scene
|
|
453
|
-
const pointers = new Set<string>()
|
|
454
|
-
pointers.add(base)
|
|
455
|
-
parcels.forEach(($) => pointers.add($))
|
|
456
|
-
|
|
457
|
-
const mappedFiles = await getProjectContentMappings(components, projectRoot, hashingFunction)
|
|
458
|
-
|
|
459
|
-
return {
|
|
460
|
-
version: 'v3',
|
|
461
|
-
type: EntityType.SCENE,
|
|
462
|
-
id: await hashingFunction(projectRoot),
|
|
463
|
-
pointers: Array.from(pointers),
|
|
464
|
-
timestamp: Date.now(),
|
|
465
|
-
metadata: sceneJson,
|
|
466
|
-
content: mappedFiles
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
return null
|
|
471
|
-
}
|