Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add smart werable pack and fix preview of SW on sdk7 #682

Merged
merged 5 commits into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add description for --skip-install

--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