@dcl/sdk-commands 7.0.0-4286109573.commit-baaf951 → 7.0.0-4293371227.commit-54082c6

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 (40) hide show
  1. package/dist/commands/build/index.d.ts +1 -1
  2. package/dist/commands/build/index.js +9 -0
  3. package/dist/commands/export-static/index.d.ts +1 -1
  4. package/dist/commands/export-static/index.js +7 -0
  5. package/dist/commands/init/index.d.ts +1 -1
  6. package/dist/commands/init/index.js +1 -0
  7. package/dist/commands/start/index.d.ts +1 -1
  8. package/dist/commands/start/index.js +9 -1
  9. package/dist/commands/start/server/endpoints.js +2 -3
  10. package/dist/components/analytics.d.ts +38 -0
  11. package/dist/components/analytics.js +62 -0
  12. package/dist/components/dcl-info-config.d.ts +11 -0
  13. package/dist/components/dcl-info-config.js +75 -0
  14. package/dist/components/index.d.ts +5 -1
  15. package/dist/components/index.js +9 -3
  16. package/dist/index.js +2 -1
  17. package/dist/logic/config.d.ts +16 -0
  18. package/dist/logic/config.js +42 -0
  19. package/dist/logic/dcl-ignore.js +1 -0
  20. package/dist/logic/fs.d.ts +12 -0
  21. package/dist/logic/fs.js +29 -1
  22. package/dist/logic/project-files.d.ts +5 -0
  23. package/dist/logic/project-files.js +18 -2
  24. package/dist/logic/scene-validations.d.ts +4 -0
  25. package/dist/logic/scene-validations.js +11 -3
  26. package/package.json +7 -4
  27. package/src/commands/build/index.ts +11 -2
  28. package/src/commands/export-static/index.ts +10 -2
  29. package/src/commands/init/index.ts +2 -1
  30. package/src/commands/start/index.ts +12 -4
  31. package/src/commands/start/server/endpoints.ts +1 -3
  32. package/src/components/analytics.ts +92 -0
  33. package/src/components/dcl-info-config.ts +63 -0
  34. package/src/components/index.ts +11 -3
  35. package/src/index.ts +2 -2
  36. package/src/logic/config.ts +45 -0
  37. package/src/logic/dcl-ignore.ts +1 -0
  38. package/src/logic/fs.ts +35 -0
  39. package/src/logic/project-files.ts +16 -0
  40. package/src/logic/scene-validations.ts +10 -2
@@ -1,7 +1,7 @@
1
1
  import { CliComponents } from '../../components';
2
2
  interface Options {
3
3
  args: Omit<typeof args, '_'>;
4
- components: Pick<CliComponents, 'fs' | 'logger'>;
4
+ components: Pick<CliComponents, 'fs' | 'logger' | 'dclInfoConfig' | 'analytics'>;
5
5
  }
6
6
  export declare const args: import("arg").Result<{
7
7
  '--help': BooleanConstructor;
@@ -9,6 +9,8 @@ const args_1 = require("../../logic/args");
9
9
  const compile_1 = require("@dcl/dcl-rollup/compile");
10
10
  const fp_future_1 = __importDefault(require("fp-future"));
11
11
  const project_validations_1 = require("../../logic/project-validations");
12
+ const scene_validations_1 = require("../../logic/scene-validations");
13
+ const project_files_1 = require("../../logic/project-files");
12
14
  exports.args = (0, args_1.getArgs)({
13
15
  '--watch': Boolean,
14
16
  '-w': '--watch',
@@ -51,6 +53,13 @@ async function main(options) {
51
53
  if (!watch) {
52
54
  watchingFuture.resolve(null);
53
55
  }
56
+ const sceneJson = await (0, project_files_1.getSceneJson)(options.components, projectRoot);
57
+ const coords = (0, scene_validations_1.getBaseCoords)(sceneJson);
58
+ await options.components.analytics.track('Build scene', {
59
+ projectHash: await (0, project_files_1.b64HashingFunction)(projectRoot),
60
+ coords,
61
+ isWorkspace: false
62
+ });
54
63
  await watchingFuture;
55
64
  // track stuff...
56
65
  // https://github.com/decentraland/cli/blob/main/src/commands/build.ts
@@ -1,7 +1,7 @@
1
1
  import { CliComponents } from '../../components';
2
2
  interface Options {
3
3
  args: typeof args;
4
- components: Pick<CliComponents, 'fetch' | 'fs' | 'logger'>;
4
+ components: Pick<CliComponents, 'fetch' | 'fs' | 'logger' | 'dclInfoConfig' | 'analytics'>;
5
5
  }
6
6
  export declare const args: import("arg").Result<{
7
7
  '--help': BooleanConstructor;
@@ -11,6 +11,7 @@ const schemas_1 = require("@dcl/schemas");
11
11
  const log_1 = require("../../components/log");
12
12
  const beautiful_logs_1 = require("../../logic/beautiful-logs");
13
13
  const realm_1 = require("../../logic/realm");
14
+ const scene_validations_1 = require("../../logic/scene-validations");
14
15
  exports.args = (0, args_1.getArgs)({
15
16
  '--dir': String,
16
17
  '--destination': String,
@@ -114,6 +115,12 @@ async function main(options) {
114
115
  (0, beautiful_logs_1.printProgressInfo)(logger, `> ${realmName}/about -> [REALM FILE]`);
115
116
  }
116
117
  (0, beautiful_logs_1.printSuccess)(logger, `Export finished!`, `=> The entity URN is ${log_1.colors.bold(urn)}`);
118
+ const sceneJson = await (0, project_files_1.getSceneJson)(options.components, projectRoot);
119
+ const coords = (0, scene_validations_1.getBaseCoords)(sceneJson);
120
+ await options.components.analytics.track('Export static', {
121
+ projectHash: await (0, project_files_1.b64HashingFunction)(projectRoot),
122
+ coords
123
+ });
117
124
  return { urn, entityId, destination: destDirectory };
118
125
  }
119
126
  exports.main = main;
@@ -1,7 +1,7 @@
1
1
  import { CliComponents } from '../../components';
2
2
  interface Options {
3
3
  args: typeof args;
4
- components: Pick<CliComponents, 'fetch' | 'fs' | 'logger'>;
4
+ components: Pick<CliComponents, 'fetch' | 'fs' | 'logger' | 'dclInfoConfig' | 'analytics'>;
5
5
  }
6
6
  export declare const args: import("arg").Result<{
7
7
  '--help': BooleanConstructor;
@@ -34,6 +34,7 @@ async function main(options) {
34
34
  if (shouldInstallDeps && !options.args['--skip-install']) {
35
35
  await (0, project_validations_1.installDependencies)(options.components, dir);
36
36
  }
37
+ await options.components.analytics.track('Scene created', { projectType: scene, url });
37
38
  }
38
39
  exports.main = main;
39
40
  const moveFilesFromDir = async (components, dir, folder) => {
@@ -1,7 +1,7 @@
1
1
  import { CliComponents } from '../../components';
2
2
  interface Options {
3
3
  args: typeof args;
4
- components: Pick<CliComponents, 'fetch' | 'fs' | 'logger'>;
4
+ components: Pick<CliComponents, 'fetch' | 'fs' | 'logger' | 'dclInfoConfig' | 'analytics'>;
5
5
  }
6
6
  export declare const args: import("arg").Result<{
7
7
  '--help': BooleanConstructor;
@@ -48,6 +48,7 @@ const log_1 = require("../../components/log");
48
48
  const file_watch_notifier_1 = require("./server/file-watch-notifier");
49
49
  const routes_1 = require("./server/routes");
50
50
  const ws_1 = require("./server/ws");
51
+ const project_files_1 = require("../../logic/project-files");
51
52
  exports.args = (0, args_1.getArgs)({
52
53
  '--dir': String,
53
54
  '--help': Boolean,
@@ -102,7 +103,7 @@ async function main(options) {
102
103
  const skipBuild = exports.args['--skip-build'];
103
104
  const watch = !exports.args['--no-watch'];
104
105
  const enableWeb3 = exports.args['--web3'];
105
- const baseCoords = { x: 0, y: 0 };
106
+ // TODO: FIX this hardcoded values ?
106
107
  const hasPortableExperience = false;
107
108
  // first run `npm run build`, this can be disabled with --skip-build
108
109
  if (!skipBuild) {
@@ -113,6 +114,8 @@ async function main(options) {
113
114
  await (0, build_1.main)({ ...options, args: { '--dir': projectRoot, '--watch': watch } });
114
115
  }
115
116
  await (0, scene_validations_1.validateSceneJson)(options.components, projectRoot);
117
+ const sceneJson = await (0, project_files_1.getSceneJson)(options.components, projectRoot);
118
+ const baseCoords = (0, scene_validations_1.getBaseCoords)(sceneJson);
116
119
  if (await (0, project_validations_1.needsDependencies)(options.components, projectRoot)) {
117
120
  const npmModulesPath = path.resolve(projectRoot, 'node_modules');
118
121
  throw new error_1.CliError(`Couldn\'t find ${npmModulesPath}, please run: npm install`);
@@ -164,6 +167,11 @@ async function main(options) {
164
167
  await startComponents();
165
168
  const networkInterfaces = os.networkInterfaces();
166
169
  const availableURLs = [];
170
+ await components.analytics.track('Preview started', {
171
+ projectHash: await (0, project_files_1.b64HashingFunction)(projectRoot),
172
+ coords: baseCoords,
173
+ isWorkspace: false
174
+ });
167
175
  components.logger.log(`Preview server is now running!`);
168
176
  components.logger.log('Available on:\n');
169
177
  Object.keys(networkInterfaces).forEach((dev) => {
@@ -210,7 +210,6 @@ function serveFolders(components, router, baseFolders) {
210
210
  };
211
211
  });
212
212
  }
213
- const b64HashingFunction = async (str) => 'b64-' + Buffer.from(str).toString('base64');
214
213
  async function getAllPreviewWearables(components, { baseFolders, baseUrl }) {
215
214
  const wearablePathArray = [];
216
215
  for (const wearableDir of baseFolders) {
@@ -238,7 +237,7 @@ async function serveWearable(components, wearableJsonPath, baseUrl) {
238
237
  console.error(`Unable to validate wearable.json properly, please check it.`, errors);
239
238
  throw new Error(`Invalid wearable.json (${wearableJsonPath})`);
240
239
  }
241
- const hashedFiles = await (0, project_files_1.getProjectContentMappings)(components, wearableDir, b64HashingFunction);
240
+ const hashedFiles = await (0, project_files_1.getProjectContentMappings)(components, wearableDir, project_files_1.b64HashingFunction);
242
241
  const thumbnailFiltered = hashedFiles.filter(($) => $?.file === 'thumbnail.png');
243
242
  const thumbnail = thumbnailFiltered.length > 0 && thumbnailFiltered[0]?.hash && `${baseUrl}/${thumbnailFiltered[0].hash}`;
244
243
  const wearableId = 'urn:8dc2d7ad-97e3-44d0-ba89-e8305d795a6a';
@@ -274,7 +273,7 @@ async function serveWearable(components, wearableJsonPath, baseUrl) {
274
273
  async function getSceneJson(components, projectRoots, pointers) {
275
274
  const requestedPointers = new Set(pointers);
276
275
  const resultEntities = [];
277
- const allDeployments = await Promise.all(projectRoots.map(async (projectRoot) => fakeEntityV3FromFolder(components, projectRoot, b64HashingFunction)));
276
+ const allDeployments = await Promise.all(projectRoots.map(async (projectRoot) => fakeEntityV3FromFolder(components, projectRoot, project_files_1.b64HashingFunction)));
278
277
  for (const pointer of Array.from(requestedPointers)) {
279
278
  // get deployment by pointer
280
279
  const theDeployment = allDeployments.find(($) => $ && $.pointers.includes(pointer));
@@ -0,0 +1,38 @@
1
+ import { Analytics } from '@segment/analytics-node';
2
+ import { CliComponents } from '.';
3
+ export type IAnalyticsComponent = {
4
+ get(): Analytics;
5
+ identify(): Promise<void>;
6
+ track<T extends keyof Events>(eventName: T, eventProps: Events[T]): Promise<void>;
7
+ };
8
+ type Events = {
9
+ 'Scene created': {
10
+ projectType: string;
11
+ url: string;
12
+ };
13
+ 'Preview started': {
14
+ projectHash: string;
15
+ coords: {
16
+ x: number;
17
+ y: number;
18
+ };
19
+ isWorkspace: boolean;
20
+ };
21
+ 'Build scene': {
22
+ projectHash: string;
23
+ coords: {
24
+ x: number;
25
+ y: number;
26
+ };
27
+ isWorkspace: boolean;
28
+ };
29
+ 'Export static': {
30
+ projectHash: string;
31
+ coords: {
32
+ x: number;
33
+ y: number;
34
+ };
35
+ };
36
+ };
37
+ export declare function createAnalyticsComponent({ dclInfoConfig }: Pick<CliComponents, 'dclInfoConfig'>): Promise<IAnalyticsComponent>;
38
+ export {};
@@ -0,0 +1,62 @@
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.createAnalyticsComponent = void 0;
7
+ const uuid_1 = require("uuid");
8
+ const analytics_node_1 = require("@segment/analytics-node");
9
+ const fp_future_1 = __importDefault(require("fp-future"));
10
+ const log_1 = require("./log");
11
+ async function createAnalyticsComponent({ dclInfoConfig }) {
12
+ const USER_ID = 'sdk-commands-user';
13
+ const config = await dclInfoConfig.get();
14
+ const analytics = new analytics_node_1.Analytics({ writeKey: config.segmentKey ?? '' });
15
+ return {
16
+ get() {
17
+ return analytics;
18
+ },
19
+ async identify() {
20
+ if (!config.userId) {
21
+ console.log(`Decentraland CLI sends anonymous usage stats to improve their products, if you want to disable it change the configuration at ${log_1.colors.bold('~/.dclinfo')}\n`);
22
+ const userId = (0, uuid_1.v4)();
23
+ await dclInfoConfig.updateDCLInfo({ userId, trackStats: true });
24
+ analytics.identify({
25
+ userId: USER_ID,
26
+ traits: {
27
+ devId: userId,
28
+ createdAt: new Date()
29
+ }
30
+ });
31
+ }
32
+ },
33
+ async track(eventName, eventProps) {
34
+ const trackFuture = (0, fp_future_1.default)();
35
+ const { userId, trackStats } = await dclInfoConfig.get();
36
+ if (!trackStats) {
37
+ return trackFuture.resolve();
38
+ }
39
+ const trackInfo = {
40
+ userId: USER_ID,
41
+ event: eventName,
42
+ properties: {
43
+ ...eventProps,
44
+ os: process.platform,
45
+ nodeVersion: process.version,
46
+ cliVersion: await dclInfoConfig.getVersion(),
47
+ isCI: dclInfoConfig.isCI(),
48
+ isEditor: dclInfoConfig.isEditor(),
49
+ devId: userId
50
+ }
51
+ };
52
+ analytics.track(trackInfo, () => {
53
+ trackFuture.resolve();
54
+ });
55
+ if (!dclInfoConfig.isProduction()) {
56
+ console.log(trackInfo);
57
+ }
58
+ return trackFuture;
59
+ }
60
+ };
61
+ }
62
+ exports.createAnalyticsComponent = createAnalyticsComponent;
@@ -0,0 +1,11 @@
1
+ import { CliComponents } from '.';
2
+ import { DCLInfo } from '../logic/config';
3
+ export type IDCLInfoConfigComponent = {
4
+ get(): Promise<DCLInfo>;
5
+ updateDCLInfo(value: Partial<DCLInfo>): Promise<Partial<DCLInfo>>;
6
+ getVersion(): Promise<string>;
7
+ isCI(): boolean;
8
+ isEditor(): boolean;
9
+ isProduction(): boolean;
10
+ };
11
+ export declare function createDCLInfoConfigComponent({ fs }: Pick<CliComponents, 'fs'>): Promise<IDCLInfoConfigComponent>;
@@ -0,0 +1,75 @@
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.createDCLInfoConfigComponent = void 0;
27
+ const path_1 = __importStar(require("path"));
28
+ const config_1 = require("../logic/config");
29
+ const fs_1 = require("../logic/fs");
30
+ async function createDCLInfoConfigComponent({ fs }) {
31
+ function isProduction() {
32
+ return process.env.NODE_ENV === 'production' || __filename.includes('node_modules');
33
+ }
34
+ const defaultConfig = {
35
+ userId: '',
36
+ trackStats: false,
37
+ segmentKey: isProduction() ? 'sFdziRVDJo0taOnGzTZwafEL9nLIANZ3' : 'mjCV5Dc4VAKXLJAH5g7LyHyW1jrIR3to'
38
+ };
39
+ return {
40
+ async get() {
41
+ const dclInfoConfig = await (0, config_1.getDCLInfoConfig)({ fs });
42
+ const envConfig = (0, config_1.getEnvConfig)();
43
+ const config = { ...defaultConfig, ...dclInfoConfig, ...envConfig };
44
+ return config;
45
+ },
46
+ updateDCLInfo(value) {
47
+ return (0, fs_1.writeJSON)({ fs }, (0, config_1.getDclInfoPath)(), value);
48
+ },
49
+ async getVersion() {
50
+ try {
51
+ /* istanbul ignore next */
52
+ const sdkPath = path_1.default.dirname(require.resolve('@dcl/sdk/package.json', {
53
+ paths: [process.cwd()]
54
+ }));
55
+ /* istanbul ignore next */
56
+ const packageJson = await (0, fs_1.readJSON)({ fs }, (0, path_1.resolve)(sdkPath, 'package.json'));
57
+ /* istanbul ignore next */
58
+ return packageJson.version ?? 'unknown';
59
+ }
60
+ catch (e) {
61
+ return 'unknown';
62
+ }
63
+ },
64
+ isEditor() {
65
+ return process.env.EDITOR === 'true';
66
+ },
67
+ isCI() {
68
+ return process.env.CI === 'true' || process.argv.includes('--ci') || process.argv.includes('--c');
69
+ },
70
+ isProduction() {
71
+ return isProduction();
72
+ }
73
+ };
74
+ }
75
+ exports.createDCLInfoConfigComponent = createDCLInfoConfigComponent;
@@ -1,9 +1,13 @@
1
1
  import { ILoggerComponent } from '@well-known-components/interfaces';
2
+ import { IAnalyticsComponent } from './analytics';
3
+ import { IDCLInfoConfigComponent } from './dcl-info-config';
2
4
  import { IFetchComponent } from './fetch';
3
5
  import { IFileSystemComponent } from './fs';
4
6
  export type CliComponents = {
5
7
  fs: IFileSystemComponent;
6
8
  fetch: IFetchComponent;
7
9
  logger: ILoggerComponent.ILogger;
10
+ dclInfoConfig: IDCLInfoConfigComponent;
11
+ analytics: IAnalyticsComponent;
8
12
  };
9
- export declare function initComponents(): CliComponents;
13
+ export declare function initComponents(): Promise<CliComponents>;
@@ -1,14 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.initComponents = void 0;
4
+ const analytics_1 = require("./analytics");
5
+ const dcl_info_config_1 = require("./dcl-info-config");
4
6
  const fetch_1 = require("./fetch");
5
7
  const fs_1 = require("./fs");
6
8
  const log_1 = require("./log");
7
- function initComponents() {
9
+ async function initComponents() {
10
+ const fsComponent = (0, fs_1.createFsComponent)();
11
+ const dclInfoConfig = await (0, dcl_info_config_1.createDCLInfoConfigComponent)({ fs: fsComponent });
8
12
  return {
9
- fs: (0, fs_1.createFsComponent)(),
13
+ fs: fsComponent,
10
14
  fetch: (0, fetch_1.createFetchComponent)(),
11
- logger: (0, log_1.createStdoutCliLogger)()
15
+ logger: (0, log_1.createStdoutCliLogger)(),
16
+ dclInfoConfig,
17
+ analytics: await (0, analytics_1.createAnalyticsComponent)({ dclInfoConfig })
12
18
  };
13
19
  }
14
20
  exports.initComponents = initComponents;
package/dist/index.js CHANGED
@@ -24,7 +24,7 @@ async function main() {
24
24
  const args = (0, args_1.getArgs)();
25
25
  const command = process.argv[2];
26
26
  const needsHelp = args['--help'];
27
- const components = (0, components_1.initComponents)();
27
+ const components = await (0, components_1.initComponents)();
28
28
  const commands = await (0, commands_1.getCommands)(components);
29
29
  if (!commands.includes(command)) {
30
30
  if (needsHelp) {
@@ -36,6 +36,7 @@ async function main() {
36
36
  // eslint-disable-next-line @typescript-eslint/no-var-requires
37
37
  const cmd = require(`${commands_1.COMMANDS_PATH}/${command}`);
38
38
  if (commandFnsAreValid(cmd)) {
39
+ await components.analytics.identify();
39
40
  const options = { args: cmd.args, components };
40
41
  if (needsHelp) {
41
42
  await cmd.help(options);
@@ -0,0 +1,16 @@
1
+ import { CliComponents } from '../components';
2
+ export interface DCLInfo {
3
+ segmentKey?: string;
4
+ userId?: string;
5
+ trackStats?: boolean;
6
+ }
7
+ export declare const getDclInfoPath: () => string;
8
+ /**
9
+ * Reads the contents of the `.dclinfo` file
10
+ */
11
+ export declare function getDCLInfoConfig(components: Pick<CliComponents, 'fs'>): Promise<DCLInfo>;
12
+ /**
13
+ * Config that can be override via ENV variables
14
+ */
15
+ export declare function getEnvConfig(): Partial<DCLInfo>;
16
+ export declare function removeEmptyKeys(obj: Record<string, unknown>): Record<string, unknown>;
@@ -0,0 +1,42 @@
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.removeEmptyKeys = exports.getEnvConfig = exports.getDCLInfoConfig = exports.getDclInfoPath = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ /* istanbul ignore next */
9
+ const getDclInfoPath = () => path_1.default.resolve(process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME'] ?? '', '.dclinfo');
10
+ exports.getDclInfoPath = getDclInfoPath;
11
+ /**
12
+ * Reads the contents of the `.dclinfo` file
13
+ */
14
+ async function getDCLInfoConfig(components) {
15
+ try {
16
+ const content = await components.fs.readFile((0, exports.getDclInfoPath)(), 'utf8');
17
+ return JSON.parse(content);
18
+ }
19
+ catch (e) {
20
+ return {};
21
+ }
22
+ }
23
+ exports.getDCLInfoConfig = getDCLInfoConfig;
24
+ /**
25
+ * Config that can be override via ENV variables
26
+ */
27
+ function getEnvConfig() {
28
+ const { SEGMENT_KEY } = process.env;
29
+ const envConfig = {
30
+ segmentKey: SEGMENT_KEY
31
+ };
32
+ return removeEmptyKeys(envConfig);
33
+ }
34
+ exports.getEnvConfig = getEnvConfig;
35
+ function removeEmptyKeys(obj) {
36
+ const result = {};
37
+ Object.keys(obj)
38
+ .filter((k) => !!obj[k])
39
+ .forEach((k) => (result[k] = obj[k]));
40
+ return result;
41
+ }
42
+ exports.removeEmptyKeys = removeEmptyKeys;
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getDCLIgnorePatterns = exports.getDCLIgnoreFileContents = exports.defaultDclIgnore = void 0;
7
+ /* istanbul ignore file */
7
8
  const path_1 = __importDefault(require("path"));
8
9
  exports.defaultDclIgnore = [
9
10
  '.*',
@@ -1,5 +1,6 @@
1
1
  import { IFileSystemComponent } from '../components/fs';
2
2
  import { IFetchComponent } from '../components/fetch';
3
+ import { CliComponents } from '../components';
3
4
  /**
4
5
  * Check's if directory is empty
5
6
  * @param dir Directory to check for emptyness
@@ -22,3 +23,14 @@ export declare function download(components: {
22
23
  * @param dest Path to where to extract the zip file
23
24
  */
24
25
  export declare function extract(path: string, dest: string): Promise<string>;
26
+ /**
27
+ * Reads a file and parses it's JSON content
28
+ * @param path The path to the subject json file
29
+ */
30
+ export declare function readJSON<T>(components: Pick<CliComponents, 'fs'>, path: string): Promise<T>;
31
+ /**
32
+ * Merges the provided content with a json file
33
+ * @param path The path to the subject json file
34
+ * @param content The content to be applied (as a plain object)
35
+ */
36
+ export declare function writeJSON<T = unknown>(components: Pick<CliComponents, 'fs'>, path: string, content: Partial<T>): Promise<Partial<T>>;
package/dist/logic/fs.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.extract = exports.download = exports.isDirectoryEmpty = void 0;
6
+ exports.writeJSON = exports.readJSON = exports.extract = exports.download = exports.isDirectoryEmpty = void 0;
7
7
  const extract_zip_1 = __importDefault(require("extract-zip"));
8
8
  const path_1 = require("path");
9
9
  /**
@@ -39,3 +39,31 @@ async function extract(path, dest) {
39
39
  return destPath;
40
40
  }
41
41
  exports.extract = extract;
42
+ /**
43
+ * Reads a file and parses it's JSON content
44
+ * @param path The path to the subject json file
45
+ */
46
+ async function readJSON(components, path) {
47
+ const content = await components.fs.readFile(path, 'utf-8');
48
+ return JSON.parse(content);
49
+ }
50
+ exports.readJSON = readJSON;
51
+ /**
52
+ * Merges the provided content with a json file
53
+ * @param path The path to the subject json file
54
+ * @param content The content to be applied (as a plain object)
55
+ */
56
+ async function writeJSON(components, path, content) {
57
+ let currentFile;
58
+ try {
59
+ currentFile = await readJSON(components, path);
60
+ }
61
+ catch (e) {
62
+ currentFile = {};
63
+ }
64
+ const newJson = { ...currentFile, ...content };
65
+ const strContent = JSON.stringify(newJson, null, 2);
66
+ await components.fs.writeFile(path, strContent);
67
+ return newJson;
68
+ }
69
+ exports.writeJSON = writeJSON;
@@ -1,16 +1,21 @@
1
1
  import { ContentMapping } from '@dcl/schemas/dist/misc/content-mapping';
2
2
  import { CliComponents } from '../components';
3
+ import { Scene } from '@dcl/schemas';
3
4
  /**
4
5
  * Returns an array of the publishable files for a given folder.
6
+ *
5
7
  */
6
8
  export declare function getPublishableFiles(components: Pick<CliComponents, 'fs'>, projectRoot: string): Promise<Array<string>>;
7
9
  /**
8
10
  * This function converts paths to decentraland-compatible paths.
9
11
  * - From windows separators to unix separators.
10
12
  * - All to lowercase
13
+ *
11
14
  */
12
15
  export declare function normalizeDecentralandFilename(filename: string): string;
13
16
  /**
14
17
  * Returns the content mappings for a specific project folder.
15
18
  */
16
19
  export declare function getProjectContentMappings(components: Pick<CliComponents, 'fs'>, projectRoot: string, hashingFunction: (filePath: string) => Promise<string>): Promise<ContentMapping[]>;
20
+ export declare function getSceneJson(components: Pick<CliComponents, 'fs'>, projectRoot: string): Promise<Scene>;
21
+ export declare const b64HashingFunction: (str: string) => Promise<string>;
@@ -3,15 +3,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.getProjectContentMappings = exports.normalizeDecentralandFilename = exports.getPublishableFiles = void 0;
6
+ exports.b64HashingFunction = exports.getSceneJson = exports.getProjectContentMappings = exports.normalizeDecentralandFilename = exports.getPublishableFiles = void 0;
7
7
  const dcl_ignore_1 = require("./dcl-ignore");
8
8
  const glob_1 = require("glob");
9
9
  const ignore_1 = __importDefault(require("ignore"));
10
10
  const path_1 = __importDefault(require("path"));
11
11
  const error_1 = require("./error");
12
+ const scene_validations_1 = require("./scene-validations");
12
13
  /**
13
14
  * Returns an array of the publishable files for a given folder.
15
+ *
14
16
  */
17
+ /* istanbul ignore next */
15
18
  async function getPublishableFiles(components, projectRoot) {
16
19
  const ignorePatterns = await (0, dcl_ignore_1.getDCLIgnorePatterns)(components, projectRoot);
17
20
  const ig = (0, ignore_1.default)().add(ignorePatterns);
@@ -29,7 +32,9 @@ exports.getPublishableFiles = getPublishableFiles;
29
32
  * This function converts paths to decentraland-compatible paths.
30
33
  * - From windows separators to unix separators.
31
34
  * - All to lowercase
35
+ *
32
36
  */
37
+ /* istanbul ignore next */
33
38
  function normalizeDecentralandFilename(filename) {
34
39
  return filename.replace(/(\\)/g, '/').toLowerCase();
35
40
  }
@@ -37,7 +42,10 @@ exports.normalizeDecentralandFilename = normalizeDecentralandFilename;
37
42
  /**
38
43
  * Returns the content mappings for a specific project folder.
39
44
  */
40
- async function getProjectContentMappings(components, projectRoot, hashingFunction) {
45
+ /* istanbul ignore next */
46
+ async function getProjectContentMappings(components, projectRoot,
47
+ /* istanbul ignore next */
48
+ hashingFunction) {
41
49
  const projectFiles = await getPublishableFiles(components, projectRoot);
42
50
  const ret = [];
43
51
  const usedFilenames = new Set();
@@ -60,3 +68,11 @@ async function getProjectContentMappings(components, projectRoot, hashingFunctio
60
68
  return ret;
61
69
  }
62
70
  exports.getProjectContentMappings = getProjectContentMappings;
71
+ async function getSceneJson(components, projectRoot) {
72
+ const sceneJsonContent = await components.fs.readFile((0, scene_validations_1.getSceneFilePath)(projectRoot), 'utf8');
73
+ const sceneJson = JSON.parse(sceneJsonContent);
74
+ return sceneJson;
75
+ }
76
+ exports.getSceneJson = getSceneJson;
77
+ const b64HashingFunction = async (str) => 'b64-' + Buffer.from(str).toString('base64');
78
+ exports.b64HashingFunction = b64HashingFunction;
@@ -11,3 +11,7 @@ export declare function assertValidScene(scene: Scene): Scene;
11
11
  * Fails the execution if one of the parcel data is invalid
12
12
  */
13
13
  export declare function validateSceneJson(components: Pick<CliComponents, 'fs' | 'logger'>, projectRoot: string): Promise<Scene>;
14
+ export declare function getBaseCoords(scene: Scene): {
15
+ x: number;
16
+ y: number;
17
+ };
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.validateSceneJson = exports.assertValidScene = exports.getSceneFilePath = exports.SCENE_FILE = void 0;
3
+ exports.getBaseCoords = exports.validateSceneJson = exports.assertValidScene = exports.getSceneFilePath = exports.SCENE_FILE = void 0;
4
4
  const path_1 = require("path");
5
5
  const schemas_1 = require("@dcl/schemas");
6
6
  const error_1 = require("./error");
7
7
  const coordinates_1 = require("./coordinates");
8
+ const project_files_1 = require("./project-files");
8
9
  exports.SCENE_FILE = 'scene.json';
9
10
  /**
10
11
  * Composes the path to the `scene.json` file based on the provided path.
@@ -53,8 +54,7 @@ exports.assertValidScene = assertValidScene;
53
54
  */
54
55
  async function validateSceneJson(components, projectRoot) {
55
56
  try {
56
- const sceneJsonRaw = await components.fs.readFile(getSceneFilePath(projectRoot), 'utf8');
57
- const sceneJson = JSON.parse(sceneJsonRaw);
57
+ const sceneJson = await (0, project_files_1.getSceneJson)(components, projectRoot);
58
58
  return assertValidScene(sceneJson);
59
59
  }
60
60
  catch (err) {
@@ -62,3 +62,11 @@ async function validateSceneJson(components, projectRoot) {
62
62
  }
63
63
  }
64
64
  exports.validateSceneJson = validateSceneJson;
65
+ function getBaseCoords(scene) {
66
+ const [x, y] = scene.scene.base
67
+ .replace(/\ /g, '')
68
+ .split(',')
69
+ .map((a) => parseInt(a));
70
+ return { x, y };
71
+ }
72
+ exports.getBaseCoords = getBaseCoords;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcl/sdk-commands",
3
- "version": "7.0.0-4286109573.commit-baaf951",
3
+ "version": "7.0.0-4293371227.commit-54082c6",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "build": "tsc -p tsconfig.json",
@@ -16,11 +16,12 @@
16
16
  "author": "Decentraland",
17
17
  "license": "Apache-2.0",
18
18
  "dependencies": {
19
- "@dcl/dcl-rollup": "7.0.6-4286109573.commit-baaf951",
19
+ "@dcl/dcl-rollup": "7.0.6-4293371227.commit-54082c6",
20
20
  "@dcl/hashing": "1.1.3",
21
21
  "@dcl/mini-comms": "1.0.1-20230216163137.commit-a4c75be",
22
22
  "@dcl/protocol": "1.0.0-4114477251.commit-ccb88d6",
23
23
  "@dcl/schemas": "6.10.0",
24
+ "@segment/analytics-node": "^1.0.0-beta.22",
24
25
  "@well-known-components/env-config-provider": "^1.2.0",
25
26
  "@well-known-components/http-server": "^2.0.0-20230216161243.commit-bfe3f0a",
26
27
  "@well-known-components/logger": "^3.1.2",
@@ -33,10 +34,12 @@
33
34
  "node-fetch": "^2.6.8",
34
35
  "open": "^8.4.0",
35
36
  "portfinder": "^1.0.32",
36
- "undici": "^5.19.1"
37
+ "undici": "^5.19.1",
38
+ "uuid": "^9.0.0"
37
39
  },
38
40
  "devDependencies": {
39
41
  "@types/node-fetch": "^2.6.1",
42
+ "@types/uuid": "^9.0.1",
40
43
  "@types/ws": "^8.5.4"
41
44
  },
42
45
  "minCliVersion": "3.14.1",
@@ -46,5 +49,5 @@
46
49
  "displayName": "SDK",
47
50
  "tsconfig": "./tsconfig.json"
48
51
  },
49
- "commit": "baaf951941795db25386cff7a620976efbcabbac"
52
+ "commit": "54082c63f87b970864c7e4eab94db193656d6aaa"
50
53
  }
@@ -4,10 +4,12 @@ import { getArgs } from '../../logic/args'
4
4
  import { compile } from '@dcl/dcl-rollup/compile'
5
5
  import future from 'fp-future'
6
6
  import { assertValidProjectFolder, installDependencies, needsDependencies } from '../../logic/project-validations'
7
+ import { getBaseCoords } from '../../logic/scene-validations'
8
+ import { b64HashingFunction, getSceneJson } from '../../logic/project-files'
7
9
 
8
10
  interface Options {
9
11
  args: Omit<typeof args, '_'>
10
- components: Pick<CliComponents, 'fs' | 'logger'>
12
+ components: Pick<CliComponents, 'fs' | 'logger' | 'dclInfoConfig' | 'analytics'>
11
13
  }
12
14
 
13
15
  export const args = getArgs({
@@ -37,7 +39,6 @@ export function help() {
37
39
 
38
40
  export async function main(options: Options) {
39
41
  const projectRoot = resolve(process.cwd(), options.args['--dir'] || '.')
40
-
41
42
  await assertValidProjectFolder(options.components, projectRoot)
42
43
 
43
44
  const shouldInstallDeps = await needsDependencies(options.components, projectRoot)
@@ -60,6 +61,14 @@ export async function main(options: Options) {
60
61
  if (!watch) {
61
62
  watchingFuture.resolve(null)
62
63
  }
64
+ const sceneJson = await getSceneJson(options.components, projectRoot)
65
+ const coords = getBaseCoords(sceneJson)
66
+
67
+ await options.components.analytics.track('Build scene', {
68
+ projectHash: await b64HashingFunction(projectRoot),
69
+ coords,
70
+ isWorkspace: false
71
+ })
63
72
 
64
73
  await watchingFuture
65
74
 
@@ -3,16 +3,17 @@ import { getArgs } from '../../logic/args'
3
3
  import { hashV1 } from '@dcl/hashing'
4
4
  import { CliComponents } from '../../components'
5
5
  import { assertValidProjectFolder } from '../../logic/project-validations'
6
- import { getProjectContentMappings } from '../../logic/project-files'
6
+ import { b64HashingFunction, getProjectContentMappings, getSceneJson } from '../../logic/project-files'
7
7
  import { CliError } from '../../logic/error'
8
8
  import { Entity, EntityType } from '@dcl/schemas'
9
9
  import { colors } from '../../components/log'
10
10
  import { printProgressInfo, printProgressStep, printSuccess } from '../../logic/beautiful-logs'
11
11
  import { createStaticRealm } from '../../logic/realm'
12
+ import { getBaseCoords } from '../../logic/scene-validations'
12
13
 
13
14
  interface Options {
14
15
  args: typeof args
15
- components: Pick<CliComponents, 'fetch' | 'fs' | 'logger'>
16
+ components: Pick<CliComponents, 'fetch' | 'fs' | 'logger' | 'dclInfoConfig' | 'analytics'>
16
17
  }
17
18
 
18
19
  export const args = getArgs({
@@ -137,6 +138,13 @@ export async function main(options: Options) {
137
138
  }
138
139
 
139
140
  printSuccess(logger, `Export finished!`, `=> The entity URN is ${colors.bold(urn)}`)
141
+ const sceneJson = await getSceneJson(options.components, projectRoot)
142
+ const coords = getBaseCoords(sceneJson)
143
+
144
+ await options.components.analytics.track('Export static', {
145
+ projectHash: await b64HashingFunction(projectRoot),
146
+ coords
147
+ })
140
148
 
141
149
  return { urn, entityId, destination: destDirectory }
142
150
  }
@@ -10,7 +10,7 @@ import { installDependencies, needsDependencies } from '../../logic/project-vali
10
10
 
11
11
  interface Options {
12
12
  args: typeof args
13
- components: Pick<CliComponents, 'fetch' | 'fs' | 'logger'>
13
+ components: Pick<CliComponents, 'fetch' | 'fs' | 'logger' | 'dclInfoConfig' | 'analytics'>
14
14
  }
15
15
 
16
16
  export const args = getArgs({
@@ -44,6 +44,7 @@ export async function main(options: Options) {
44
44
  if (shouldInstallDeps && !options.args['--skip-install']) {
45
45
  await installDependencies(options.components, dir)
46
46
  }
47
+ await options.components.analytics.track('Scene created', { projectType: scene, url })
47
48
  }
48
49
 
49
50
  const moveFilesFromDir = async (components: Pick<CliComponents, 'fs'>, dir: string, folder: string) => {
@@ -7,7 +7,7 @@ import { CliComponents } from '../../components'
7
7
  import { main as build } from '../build'
8
8
  import { getArgs } from '../../logic/args'
9
9
  import { needsDependencies, npmRun } from '../../logic/project-validations'
10
- import { validateSceneJson } from '../../logic/scene-validations'
10
+ import { getBaseCoords, validateSceneJson } from '../../logic/scene-validations'
11
11
  import { CliError } from '../../logic/error'
12
12
  import { previewPort } from '../../logic/get-free-port'
13
13
  import { ISignalerComponent, PreviewComponents } from './types'
@@ -22,10 +22,11 @@ import { createStdoutCliLogger } from '../../components/log'
22
22
  import { wireFileWatcherToWebSockets } from './server/file-watch-notifier'
23
23
  import { wireRouter } from './server/routes'
24
24
  import { createWsComponent } from './server/ws'
25
+ import { b64HashingFunction, getSceneJson } from '../../logic/project-files'
25
26
 
26
27
  interface Options {
27
28
  args: typeof args
28
- components: Pick<CliComponents, 'fetch' | 'fs' | 'logger'>
29
+ components: Pick<CliComponents, 'fetch' | 'fs' | 'logger' | 'dclInfoConfig' | 'analytics'>
29
30
  }
30
31
 
31
32
  export const args = getArgs({
@@ -83,7 +84,8 @@ export async function main(options: Options) {
83
84
  const skipBuild = args['--skip-build']
84
85
  const watch = !args['--no-watch']
85
86
  const enableWeb3 = args['--web3']
86
- const baseCoords = { x: 0, y: 0 }
87
+
88
+ // TODO: FIX this hardcoded values ?
87
89
  const hasPortableExperience = false
88
90
 
89
91
  // first run `npm run build`, this can be disabled with --skip-build
@@ -97,6 +99,8 @@ export async function main(options: Options) {
97
99
  }
98
100
 
99
101
  await validateSceneJson(options.components, projectRoot)
102
+ const sceneJson = await getSceneJson(options.components, projectRoot)
103
+ const baseCoords = getBaseCoords(sceneJson)
100
104
 
101
105
  if (await needsDependencies(options.components, projectRoot)) {
102
106
  const npmModulesPath = path.resolve(projectRoot, 'node_modules')
@@ -154,7 +158,11 @@ export async function main(options: Options) {
154
158
 
155
159
  const networkInterfaces = os.networkInterfaces()
156
160
  const availableURLs: string[] = []
157
-
161
+ await components.analytics.track('Preview started', {
162
+ projectHash: await b64HashingFunction(projectRoot),
163
+ coords: baseCoords,
164
+ isWorkspace: false
165
+ })
158
166
  components.logger.log(`Preview server is now running!`)
159
167
  components.logger.log('Available on:\n')
160
168
 
@@ -6,7 +6,7 @@ import { Entity, EntityType, Locale, Wearable } from '@dcl/schemas'
6
6
  import fetch, { Headers } from 'node-fetch'
7
7
  import { fetchEntityByPointer } from '../../../logic/catalyst-requests'
8
8
  import { CliComponents } from '../../../components'
9
- import { getProjectContentMappings } from '../../../logic/project-files'
9
+ import { b64HashingFunction, getProjectContentMappings } from '../../../logic/project-files'
10
10
 
11
11
  function getCatalystUrl(): URL {
12
12
  return new URL('https://peer.decentraland.org')
@@ -227,8 +227,6 @@ function serveFolders(components: Pick<CliComponents, 'fs'>, router: Router<Prev
227
227
  })
228
228
  }
229
229
 
230
- const b64HashingFunction = async (str: string) => 'b64-' + Buffer.from(str).toString('base64')
231
-
232
230
  async function getAllPreviewWearables(
233
231
  components: Pick<CliComponents, 'fs'>,
234
232
  { baseFolders, baseUrl }: { baseFolders: string[]; baseUrl: string }
@@ -0,0 +1,92 @@
1
+ import { v4 as uuidv4 } from 'uuid'
2
+ import { Analytics } from '@segment/analytics-node'
3
+ import future from 'fp-future'
4
+
5
+ import { CliComponents } from '.'
6
+ import { colors } from './log'
7
+
8
+ export type IAnalyticsComponent = {
9
+ get(): Analytics
10
+ identify(): Promise<void>
11
+ track<T extends keyof Events>(eventName: T, eventProps: Events[T]): Promise<void>
12
+ }
13
+
14
+ type Events = {
15
+ 'Scene created': {
16
+ projectType: string
17
+ url: string
18
+ }
19
+ 'Preview started': {
20
+ projectHash: string
21
+ coords: { x: number; y: number }
22
+ isWorkspace: boolean
23
+ }
24
+ 'Build scene': {
25
+ projectHash: string
26
+ coords: { x: number; y: number }
27
+ isWorkspace: boolean
28
+ }
29
+ 'Export static': {
30
+ projectHash: string
31
+ coords: { x: number; y: number }
32
+ }
33
+ }
34
+
35
+ export async function createAnalyticsComponent({
36
+ dclInfoConfig
37
+ }: Pick<CliComponents, 'dclInfoConfig'>): Promise<IAnalyticsComponent> {
38
+ const USER_ID = 'sdk-commands-user'
39
+ const config = await dclInfoConfig.get()
40
+ const analytics: Analytics = new Analytics({ writeKey: config.segmentKey ?? '' })
41
+ return {
42
+ get() {
43
+ return analytics
44
+ },
45
+ async identify() {
46
+ if (!config.userId) {
47
+ console.log(
48
+ `Decentraland CLI sends anonymous usage stats to improve their products, if you want to disable it change the configuration at ${colors.bold(
49
+ '~/.dclinfo'
50
+ )}\n`
51
+ )
52
+
53
+ const userId = uuidv4()
54
+ await dclInfoConfig.updateDCLInfo({ userId, trackStats: true })
55
+ analytics.identify({
56
+ userId: USER_ID,
57
+ traits: {
58
+ devId: userId,
59
+ createdAt: new Date()
60
+ }
61
+ })
62
+ }
63
+ },
64
+ async track<T extends keyof Events>(eventName: T, eventProps: Events[T]) {
65
+ const trackFuture = future<void>()
66
+ const { userId, trackStats } = await dclInfoConfig.get()
67
+ if (!trackStats) {
68
+ return trackFuture.resolve()
69
+ }
70
+ const trackInfo = {
71
+ userId: USER_ID,
72
+ event: eventName,
73
+ properties: {
74
+ ...eventProps,
75
+ os: process.platform,
76
+ nodeVersion: process.version,
77
+ cliVersion: await dclInfoConfig.getVersion(),
78
+ isCI: dclInfoConfig.isCI(),
79
+ isEditor: dclInfoConfig.isEditor(),
80
+ devId: userId
81
+ }
82
+ }
83
+ analytics.track(trackInfo, () => {
84
+ trackFuture.resolve()
85
+ })
86
+ if (!dclInfoConfig.isProduction()) {
87
+ console.log(trackInfo)
88
+ }
89
+ return trackFuture
90
+ }
91
+ }
92
+ }
@@ -0,0 +1,63 @@
1
+ import path, { resolve } from 'path'
2
+ import { CliComponents } from '.'
3
+ import { DCLInfo, getDCLInfoConfig, getDclInfoPath, getEnvConfig } from '../logic/config'
4
+ import { readJSON, writeJSON } from '../logic/fs'
5
+
6
+ export type IDCLInfoConfigComponent = {
7
+ get(): Promise<DCLInfo>
8
+ updateDCLInfo(value: Partial<DCLInfo>): Promise<Partial<DCLInfo>>
9
+ getVersion(): Promise<string>
10
+ isCI(): boolean
11
+ isEditor(): boolean
12
+ isProduction(): boolean
13
+ }
14
+
15
+ export async function createDCLInfoConfigComponent({
16
+ fs
17
+ }: Pick<CliComponents, 'fs'>): Promise<IDCLInfoConfigComponent> {
18
+ function isProduction() {
19
+ return process.env.NODE_ENV === 'production' || __filename.includes('node_modules')
20
+ }
21
+ const defaultConfig: Partial<DCLInfo> = {
22
+ userId: '',
23
+ trackStats: false,
24
+ segmentKey: isProduction() ? 'sFdziRVDJo0taOnGzTZwafEL9nLIANZ3' : 'mjCV5Dc4VAKXLJAH5g7LyHyW1jrIR3to'
25
+ }
26
+
27
+ return {
28
+ async get() {
29
+ const dclInfoConfig = await getDCLInfoConfig({ fs })
30
+ const envConfig = getEnvConfig()
31
+ const config = { ...defaultConfig, ...dclInfoConfig, ...envConfig }
32
+ return config
33
+ },
34
+ updateDCLInfo(value: Partial<DCLInfo>) {
35
+ return writeJSON({ fs }, getDclInfoPath(), value)
36
+ },
37
+ async getVersion() {
38
+ try {
39
+ /* istanbul ignore next */
40
+ const sdkPath = path.dirname(
41
+ require.resolve('@dcl/sdk/package.json', {
42
+ paths: [process.cwd()]
43
+ })
44
+ )
45
+ /* istanbul ignore next */
46
+ const packageJson = await readJSON<{ version: string }>({ fs }, resolve(sdkPath, 'package.json'))
47
+ /* istanbul ignore next */
48
+ return packageJson.version ?? 'unknown'
49
+ } catch (e) {
50
+ return 'unknown'
51
+ }
52
+ },
53
+ isEditor() {
54
+ return process.env.EDITOR === 'true'
55
+ },
56
+ isCI() {
57
+ return process.env.CI === 'true' || process.argv.includes('--ci') || process.argv.includes('--c')
58
+ },
59
+ isProduction() {
60
+ return isProduction()
61
+ }
62
+ }
63
+ }
@@ -1,4 +1,6 @@
1
1
  import { ILoggerComponent } from '@well-known-components/interfaces'
2
+ import { createAnalyticsComponent, IAnalyticsComponent } from './analytics'
3
+ import { createDCLInfoConfigComponent, IDCLInfoConfigComponent } from './dcl-info-config'
2
4
  import { createFetchComponent, IFetchComponent } from './fetch'
3
5
  import { createFsComponent, IFileSystemComponent } from './fs'
4
6
  import { createStdoutCliLogger } from './log'
@@ -7,12 +9,18 @@ export type CliComponents = {
7
9
  fs: IFileSystemComponent
8
10
  fetch: IFetchComponent
9
11
  logger: ILoggerComponent.ILogger
12
+ dclInfoConfig: IDCLInfoConfigComponent
13
+ analytics: IAnalyticsComponent
10
14
  }
11
15
 
12
- export function initComponents(): CliComponents {
16
+ export async function initComponents(): Promise<CliComponents> {
17
+ const fsComponent = createFsComponent()
18
+ const dclInfoConfig = await createDCLInfoConfigComponent({ fs: fsComponent })
13
19
  return {
14
- fs: createFsComponent(),
20
+ fs: fsComponent,
15
21
  fetch: createFetchComponent(),
16
- logger: createStdoutCliLogger()
22
+ logger: createStdoutCliLogger(),
23
+ dclInfoConfig,
24
+ analytics: await createAnalyticsComponent({ dclInfoConfig })
17
25
  }
18
26
  }
package/src/index.ts CHANGED
@@ -41,7 +41,7 @@ async function main() {
41
41
  const args = getArgs()
42
42
  const command = process.argv[2]
43
43
  const needsHelp = args['--help']
44
- const components: CliComponents = initComponents()
44
+ const components: CliComponents = await initComponents()
45
45
 
46
46
  const commands = await getCommands(components)
47
47
 
@@ -52,11 +52,11 @@ async function main() {
52
52
  }
53
53
  throw new CliError(`Command ${command} is invalid. ${helpMessage(commands)}`)
54
54
  }
55
-
56
55
  // eslint-disable-next-line @typescript-eslint/no-var-requires
57
56
  const cmd = require(`${COMMANDS_PATH}/${command}`)
58
57
 
59
58
  if (commandFnsAreValid(cmd)) {
59
+ await components.analytics.identify()
60
60
  const options = { args: cmd.args, components }
61
61
  if (needsHelp) {
62
62
  await cmd.help(options)
@@ -0,0 +1,45 @@
1
+ import path from 'path'
2
+ import { CliComponents } from '../components'
3
+
4
+ export interface DCLInfo {
5
+ segmentKey?: string
6
+ userId?: string
7
+ trackStats?: boolean
8
+ }
9
+
10
+ /* istanbul ignore next */
11
+ export const getDclInfoPath = () =>
12
+ path.resolve(process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME'] ?? '', '.dclinfo')
13
+
14
+ /**
15
+ * Reads the contents of the `.dclinfo` file
16
+ */
17
+ export async function getDCLInfoConfig(components: Pick<CliComponents, 'fs'>): Promise<DCLInfo> {
18
+ try {
19
+ const content = await components.fs.readFile(getDclInfoPath(), 'utf8')
20
+ return JSON.parse(content) as DCLInfo
21
+ } catch (e) {
22
+ return {}
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Config that can be override via ENV variables
28
+ */
29
+ export function getEnvConfig(): Partial<DCLInfo> {
30
+ const { SEGMENT_KEY } = process.env
31
+
32
+ const envConfig = {
33
+ segmentKey: SEGMENT_KEY
34
+ }
35
+
36
+ return removeEmptyKeys(envConfig)
37
+ }
38
+
39
+ export function removeEmptyKeys(obj: Record<string, unknown>) {
40
+ const result: Record<string, unknown> = {}
41
+ Object.keys(obj)
42
+ .filter((k) => !!obj[k])
43
+ .forEach((k) => (result[k] = obj[k]))
44
+ return result
45
+ }
@@ -1,3 +1,4 @@
1
+ /* istanbul ignore file */
1
2
  import path from 'path'
2
3
  import { CliComponents } from '../components'
3
4
 
package/src/logic/fs.ts CHANGED
@@ -2,6 +2,7 @@ import extractZip from 'extract-zip'
2
2
  import { resolve } from 'path'
3
3
  import { IFileSystemComponent } from '../components/fs'
4
4
  import { IFetchComponent } from '../components/fetch'
5
+ import { CliComponents } from '../components'
5
6
 
6
7
  /**
7
8
  * Check's if directory is empty
@@ -39,3 +40,37 @@ export async function extract(path: string, dest: string): Promise<string> {
39
40
  await extractZip(resolve(path), { dir: destPath })
40
41
  return destPath
41
42
  }
43
+
44
+ /**
45
+ * Reads a file and parses it's JSON content
46
+ * @param path The path to the subject json file
47
+ */
48
+ export async function readJSON<T>(components: Pick<CliComponents, 'fs'>, path: string): Promise<T> {
49
+ const content = await components.fs.readFile(path, 'utf-8')
50
+ return JSON.parse(content) as T
51
+ }
52
+
53
+ /**
54
+ * Merges the provided content with a json file
55
+ * @param path The path to the subject json file
56
+ * @param content The content to be applied (as a plain object)
57
+ */
58
+ export async function writeJSON<T = unknown>(
59
+ components: Pick<CliComponents, 'fs'>,
60
+ path: string,
61
+ content: Partial<T>
62
+ ): Promise<Partial<T>> {
63
+ let currentFile
64
+
65
+ try {
66
+ currentFile = await readJSON<T>(components, path)
67
+ } catch (e) {
68
+ currentFile = {}
69
+ }
70
+
71
+ const newJson = { ...currentFile, ...content }
72
+ const strContent = JSON.stringify(newJson, null, 2)
73
+
74
+ await components.fs.writeFile(path, strContent)
75
+ return newJson
76
+ }
@@ -5,10 +5,14 @@ import { sync as globSync } from 'glob'
5
5
  import ignore from 'ignore'
6
6
  import path from 'path'
7
7
  import { CliError } from './error'
8
+ import { getSceneFilePath } from './scene-validations'
9
+ import { Scene } from '@dcl/schemas'
8
10
 
9
11
  /**
10
12
  * Returns an array of the publishable files for a given folder.
13
+ *
11
14
  */
15
+ /* istanbul ignore next */
12
16
  export async function getPublishableFiles(
13
17
  components: Pick<CliComponents, 'fs'>,
14
18
  projectRoot: string
@@ -32,7 +36,9 @@ export async function getPublishableFiles(
32
36
  * This function converts paths to decentraland-compatible paths.
33
37
  * - From windows separators to unix separators.
34
38
  * - All to lowercase
39
+ *
35
40
  */
41
+ /* istanbul ignore next */
36
42
  export function normalizeDecentralandFilename(filename: string) {
37
43
  return filename.replace(/(\\)/g, '/').toLowerCase()
38
44
  }
@@ -40,9 +46,11 @@ export function normalizeDecentralandFilename(filename: string) {
40
46
  /**
41
47
  * Returns the content mappings for a specific project folder.
42
48
  */
49
+ /* istanbul ignore next */
43
50
  export async function getProjectContentMappings(
44
51
  components: Pick<CliComponents, 'fs'>,
45
52
  projectRoot: string,
53
+ /* istanbul ignore next */
46
54
  hashingFunction: (filePath: string) => Promise<string>
47
55
  ): Promise<ContentMapping[]> {
48
56
  const projectFiles = await getPublishableFiles(components, projectRoot)
@@ -74,3 +82,11 @@ export async function getProjectContentMappings(
74
82
 
75
83
  return ret
76
84
  }
85
+
86
+ export async function getSceneJson(components: Pick<CliComponents, 'fs'>, projectRoot: string): Promise<Scene> {
87
+ const sceneJsonContent = await components.fs.readFile(getSceneFilePath(projectRoot), 'utf8')
88
+ const sceneJson = JSON.parse(sceneJsonContent) as Scene
89
+ return sceneJson
90
+ }
91
+
92
+ export const b64HashingFunction = async (str: string) => 'b64-' + Buffer.from(str).toString('base64')
@@ -3,6 +3,7 @@ import { Scene } from '@dcl/schemas'
3
3
  import { CliError } from './error'
4
4
  import { getObject, inBounds, getBounds, areConnected } from './coordinates'
5
5
  import { CliComponents } from '../components'
6
+ import { getSceneJson } from './project-files'
6
7
 
7
8
  export const SCENE_FILE = 'scene.json'
8
9
 
@@ -63,11 +64,18 @@ export async function validateSceneJson(
63
64
  projectRoot: string
64
65
  ): Promise<Scene> {
65
66
  try {
66
- const sceneJsonRaw = await components.fs.readFile(getSceneFilePath(projectRoot), 'utf8')
67
- const sceneJson = JSON.parse(sceneJsonRaw) as Scene
67
+ const sceneJson = await getSceneJson(components, projectRoot)
68
68
 
69
69
  return assertValidScene(sceneJson)
70
70
  } catch (err: any) {
71
71
  throw new CliError(`Error reading the scene.json file: ${err.message}`)
72
72
  }
73
73
  }
74
+
75
+ export function getBaseCoords(scene: Scene): { x: number; y: number } {
76
+ const [x, y] = scene.scene.base
77
+ .replace(/\ /g, '')
78
+ .split(',')
79
+ .map((a) => parseInt(a))
80
+ return { x, y }
81
+ }