Skip to content

Commit

Permalink
add smart werable pack and fix preview of SW on sdk7 (#682)
Browse files Browse the repository at this point in the history
* add smart werable pack and fix preview of SW on sdk7

* add sw ENABLE_WEB3

* update wearable id so the popup is shown on every restart

* fix build for smart wearables

* fix typo
  • Loading branch information
gonpombo8 authored Jul 24, 2023
1 parent 7cd3d38 commit 4abe944
Show file tree
Hide file tree
Showing 11 changed files with 876 additions and 16 deletions.
662 changes: 662 additions & 0 deletions packages/@dcl/sdk-commands/package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions packages/@dcl/sdk-commands/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@well-known-components/http-server": "^2.0.0-20230501134558.commit-be9a25d",
"@well-known-components/logger": "^3.1.2",
"@well-known-components/metrics": "^2.0.1",
"archiver": "^5.3.1",
"arg": "^5.0.2",
"chokidar": "^3.5.3",
"colorette": "^2.0.19",
Expand All @@ -37,6 +38,7 @@
"uuid": "^9.0.0"
},
"devDependencies": {
"@types/archiver": "^5.3.2",
"@types/node-fetch": "^2.6.1",
"@types/uuid": "^9.0.1",
"@types/ws": "^8.5.4"
Expand Down
9 changes: 5 additions & 4 deletions packages/@dcl/sdk-commands/src/commands/build/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import path from 'path'
import { CliComponents } from '../../components'
import { declareArgs } from '../../logic/args'
import { installDependencies, needsDependencies, SceneProject } from '../../logic/project-validations'
import { installDependencies, needsDependencies, SceneProject, WearableProject } from '../../logic/project-validations'
import { getBaseCoords } from '../../logic/scene-validations'
import { b64HashingFunction } from '../../logic/project-files'
import { bundleProject } from '../../logic/bundle'
Expand Down Expand Up @@ -50,13 +50,13 @@ export async function main(options: Options) {

for (const project of workspace.projects) {
printCurrentProjectStarting(options.components.logger, project, workspace)
if (project.kind === 'scene') {
if (project.kind === 'scene' || project.kind === 'wearable') {
await buildScene(options, project)
}
}
}

export async function buildScene(options: Options, project: SceneProject) {
export async function buildScene(options: Options, project: SceneProject | WearableProject) {
const shouldInstallDeps = await needsDependencies(options.components, project.workingDirectory)

if (shouldInstallDeps && !options.args['--skip-install']) {
Expand Down Expand Up @@ -84,6 +84,7 @@ export async function buildScene(options: Options, project: SceneProject) {
options.components.analytics.track('Build scene', {
projectHash: await b64HashingFunction(project.workingDirectory),
coords,
isWorkspace: inputs.length > 1
isWorkspace: inputs.length > 1,
isPortableExperience: !!sceneJson.isPortableExperience
})
}
1 change: 1 addition & 0 deletions packages/@dcl/sdk-commands/src/commands/init/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export async function main(options: Options) {
// download and extract template project
const projectTemplate = (requestedProjectTemplate as ScaffoldedProject) || 'scene-template'
const url = requestedTemplateZipUrl || getScaffoldedProjectUrl(projectTemplate)

await downloadAndUnzipUrlContainFolder(url, dir, options)

// replace scene.json for portable experience template...
Expand Down
7 changes: 4 additions & 3 deletions packages/@dcl/sdk-commands/src/commands/init/repos.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
export type ScaffoldedProject = 'scene-template' | 'px-template'
export type ScaffoldedProject = 'scene-template' | 'px-template' | 'smart-wearable'

export const scaffoldedProjectUrls: Record<ScaffoldedProject, string> = {
const scaffoldedProjectUrls: Record<ScaffoldedProject, string> = {
'scene-template': 'https://github.com/decentraland/sdk7-scene-template/archive/refs/heads/main.zip',
'px-template': 'https://github.com/decentraland/sdk7-scene-template/archive/refs/heads/main.zip'
'px-template': 'https://github.com/decentraland/sdk7-scene-template/archive/refs/heads/main.zip',
'smart-wearable': 'https://github.com/decentraland/smart-wearable-sample/archive/refs/heads/sdk7.zip'
}

export function getScaffoldedProjectUrl(scene: ScaffoldedProject): string {
Expand Down
127 changes: 127 additions & 0 deletions packages/@dcl/sdk-commands/src/commands/pack-smart-wearable/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import path from 'path'
import archiver from 'archiver'

import { CliComponents } from '../../components'
import { declareArgs } from '../../logic/args'
import { installDependencies, needsDependencies, WearableProject } from '../../logic/project-validations'
import { b64HashingFunction, getProjectPublishableFilesWithHashes } from '../../logic/project-files'
import { printCurrentProjectStarting } from '../../logic/beautiful-logs'
import { getValidWorkspace } from '../../logic/workspace-validations'
import { Result } from 'arg'
import { buildScene } from '../build'

interface Options {
args: Result<typeof args>
components: Pick<CliComponents, 'fs' | 'logger' | 'analytics' | 'spawner'>
}

export const args = declareArgs({
'--skip-build': Boolean,
'--skip-install': Boolean,
'--dir': String
})

export function help(options: Options) {
options.components.logger.log(`
Usage: 'sdk-commands pack-smart-wearable [options]'
Options:'
-h, --help Displays complete help
--skip-build Skip build and use the file defined in scene.json
--skip-install Skip installing dependencies
--dir Path to directory to build
Example:
- Pack your smart-wearable scene:
'$ sdk-commands pack-smart-wearable'
`)
}

export async function main(options: Options) {
const workingDirectory = path.resolve(process.cwd(), options.args['--dir'] || '.')

const workspace = await getValidWorkspace(options.components, workingDirectory)

for (const project of workspace.projects) {
printCurrentProjectStarting(options.components.logger, project, workspace)
if (project.kind === 'wearable') {
await packSmartWearable(options, project)
}
}
}

export async function packSmartWearable(options: Options, project: WearableProject) {
const shouldInstallDeps =
!options.args['--skip-install'] && (await needsDependencies(options.components, project.workingDirectory))
const shouldBuild = !options.args['--skip-build']

if (shouldInstallDeps && !options.args['--skip-install']) {
await installDependencies(options.components, project.workingDirectory)
}

if (shouldBuild) {
await buildScene({ ...options, args: { '--dir': project.workingDirectory, _: [], '--production': true } }, project)
}

const files = await getProjectPublishableFilesWithHashes(options.components, project.workingDirectory, async ($) => $)
let totalSize = 0
for (const filePath of files) {
const stat = await options.components.fs.stat(filePath.absolutePath)
if (stat.isFile()) {
totalSize += stat.size
}
}
const MAX_WEARABLE_SIZE = 2097152
if (totalSize > MAX_WEARABLE_SIZE) {
options.components.logger.info(`Smart Wearable max size (${MAX_WEARABLE_SIZE} bytes) reached: ${totalSize} bytes.
Please try to remove unneccessary files and/or reduce the files size, you can ignore file adding in .dclignore.`)
}
const ZIP_FILE_NAME = 'smart-wearable.zip'
const packDir = path.resolve(project.workingDirectory, ZIP_FILE_NAME)
if (await options.components.fs.fileExists(packDir)) {
await options.components.fs.rm(packDir)
}
options.components.logger.info(packDir)

try {
await zipProject(
options.components.fs,
files.map(($) => $.absolutePath.replace(project.workingDirectory + '/', '')),
packDir
)
} catch (e) {
options.components.logger.error('Error creating zip file', (e as any).message)
}

options.components.analytics.track('Pack smart wearable', {
projectHash: await b64HashingFunction(project.workingDirectory)
})
options.components.logger.log('Smart wearable packed successfully.')
}

function zipProject(fs: CliComponents['fs'], files: string[], target: string) {
const output = fs.createWriteStream(target)
const archive = archiver('zip')

return new Promise<void>((resolve, reject) => {
output.on('close', () => {
resolve()
})

archive.on('warning', (err) => {
reject(err)
})

archive.on('error', (err) => {
reject(err)
})

archive.pipe(output)

for (const file of files) {
if (file === '') continue
archive.file(file, { name: file })
}

return archive.finalize()
})
}
8 changes: 4 additions & 4 deletions packages/@dcl/sdk-commands/src/commands/start/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,7 @@ export async function main(options: Options) {
const withDataLayer = options.args['--data-layer']
const enableWeb3 = options.args['--web3']

// TODO: FIX this hardcoded values ?
const hasPortableExperience = false
let hasSmartWearable = false

const workspace = await getValidWorkspace(options.components, workingDirectory)

Expand All @@ -101,7 +100,8 @@ export async function main(options: Options) {
printWarning(options.components.logger, 'Support for multiple projects is still experimental.')

for (const project of workspace.projects) {
if (project.kind === 'scene') {
if (project.kind === 'wearable') hasSmartWearable = true
if (project.kind === 'scene' || project.kind === 'wearable') {
printCurrentProjectStarting(options.components.logger, project, workspace)

// first run `npm run build`, this can be disabled with --skip-build
Expand Down Expand Up @@ -188,7 +188,7 @@ export async function main(options: Options) {
if (debug) {
addr = `${addr}&SCENE_DEBUG_PANEL`
}
if (enableWeb3 || hasPortableExperience) {
if (enableWeb3 || hasSmartWearable) {
addr = `${addr}&ENABLE_WEB3`
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Router } from '@well-known-components/http-server'
import { PreviewComponents } from '../types'
import * as path from 'path'
import { WearableJson } from '@dcl/schemas/dist/sdk'
import { Entity, EntityType, Locale, Wearable } from '@dcl/schemas'
import fetch, { Headers } from 'node-fetch'
import { v4 as uuidv4 } from 'uuid'

import { PreviewComponents } from '../types'
import { fetchEntityByPointer } from '../../../logic/catalyst-requests'
import { CliComponents } from '../../../components'
import {
Expand Down Expand Up @@ -290,6 +292,8 @@ async function getAllPreviewWearables(
return ret
}

const wearableCache = new Map<string, string>()

async function serveWearable(
components: Pick<CliComponents, 'fs' | 'logger'>,
project: WearableProject,
Expand All @@ -316,7 +320,10 @@ async function serveWearable(
const thumbnail =
thumbnailFiltered.length > 0 && thumbnailFiltered[0]!.hash && `${baseUrl}/${thumbnailFiltered[0].hash}`

const wearableId = 'urn:8dc2d7ad-97e3-44d0-ba89-e8305d795a6a'
// Set wearable ID.
const sceneHash = await b64HashingFunction(JSON.stringify(project.scene))
const wearableId = wearableCache.get(sceneHash) ?? `urn:${uuidv4()}`
wearableCache.set(sceneHash, wearableId)

const representations = wearableJson.data.representations.map((representation) => ({
...representation,
Expand Down
4 changes: 4 additions & 0 deletions packages/@dcl/sdk-commands/src/components/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export type Events = {
projectHash: string
coords: { x: number; y: number }
isWorkspace: boolean
isPortableExperience: boolean
}
'Export static': {
projectHash: string
Expand All @@ -53,6 +54,9 @@ export type Events = {
isWorld: boolean
error: string
}
'Pack smart wearable': {
projectHash: string
}
}

const noopAnalytics: IAnalyticsComponent = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { resolve } from 'path'
import { Scene } from '@dcl/schemas'

import { CliError } from './error'
import { CliComponents } from '../components'
export interface IFile {
path: string
content: Buffer
size: number
}

export const SMART_WEARABLE_FILE = 'wearable.json'

/**
* Composes the path to the `scene.json` file based on the provided path.
* @param projectRoot The path to the directory containing the scene file.
*/
export function getSmartWearableFile(projectRoot: string): string {
return resolve(projectRoot, SMART_WEARABLE_FILE)
}

export function assertValidSmartWearable(scene: Scene) {
if (!Scene.validate(scene)) {
const errors: string[] = []
if (Scene.validate.errors) {
for (const error of Scene.validate.errors) {
errors.push(`Error validating scene.json: ${error.message}`)
}
}
throw new CliError('Invalid scene.json file:\n' + errors.join('\n'))
}
// TODO
return true
}

/**
* Get valid Scene JSON
*/
export async function getValidWearableJson(
components: Pick<CliComponents, 'fs' | 'logger'>,
projectRoot: string
): Promise<Scene> {
try {
const wearableJsonRaw = await components.fs.readFile(getSmartWearableFile(projectRoot), 'utf8')
const wearableJson = JSON.parse(wearableJsonRaw) as Scene
return wearableJson
} catch (err: any) {
throw new CliError(`Error reading the wearable.json file: ${err.message}`)
}
}
11 changes: 8 additions & 3 deletions packages/@dcl/sdk-commands/src/logic/project-validations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import { printProgressInfo } from './beautiful-logs'
import { CliError } from './error'
import { getSceneFilePath, getValidSceneJson } from './scene-validations'
import { getInstalledPackageVersion } from './config'
import { getSmartWearableFile, getValidWearableJson } from './portable-experience-sw-validations'

export type BaseProject = { workingDirectory: string }
export type SceneProject = { kind: 'scene'; scene: Scene } & BaseProject
export type WearableProject = { kind: 'wearable'; wearable: any } & BaseProject
export type WearableProject = { kind: 'wearable'; scene: Scene } & BaseProject
export type ProjectUnion = SceneProject | WearableProject

/**
Expand All @@ -25,11 +26,15 @@ export async function assertValidProjectFolder(

// now we will iterate over different file to evaluate the project kind
switch (true) {
// TODO: case wearable
// case scene
case await components.fs.fileExists(getSmartWearableFile(workingDirectory)): {
await getValidWearableJson(components, workingDirectory)
return { kind: 'wearable', scene: await getValidSceneJson(components, workingDirectory), workingDirectory }
}

case await components.fs.fileExists(getSceneFilePath(workingDirectory)): {
return { kind: 'scene', scene: await getValidSceneJson(components, workingDirectory), workingDirectory }
}

default: {
throw new CliError(
`UnknownProjectKind: the kind of project of the folder ${workingDirectory} cannot be identified`
Expand Down

0 comments on commit 4abe944

Please sign in to comment.