@dcl/sdk-commands 7.0.0-4217957637.commit-a393ef7

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