@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,62 @@
1
+ import * as fs from 'fs'
2
+ import * as fsPromises from 'fs/promises'
3
+
4
+ /**
5
+ * @public
6
+ *
7
+ * This may be moved to well-known-components in the future
8
+ */
9
+ export type IFileSystemComponent = Pick<typeof fs, 'createReadStream'> &
10
+ Pick<typeof fs, 'createWriteStream'> &
11
+ Pick<
12
+ typeof fsPromises,
13
+ 'access' | 'opendir' | 'stat' | 'unlink' | 'mkdir' | 'readFile' | 'writeFile' | 'rename' | 'rmdir'
14
+ > & {
15
+ constants: Pick<typeof fs.constants, 'F_OK' | 'R_OK'>
16
+ } & {
17
+ fileExists(path: string): Promise<boolean>
18
+ directoryExists(path: string): Promise<boolean>
19
+ readdir(path: string): Promise<string[]>
20
+ }
21
+
22
+ async function fileExists(path: string): Promise<boolean> {
23
+ try {
24
+ await fs.promises.access(path, fs.constants.F_OK | fs.constants.R_OK)
25
+ return true
26
+ } catch (error) {
27
+ return false
28
+ }
29
+ }
30
+ async function directoryExists(path: string): Promise<boolean> {
31
+ try {
32
+ return (await fs.promises.lstat(path)).isDirectory()
33
+ } catch (error) {
34
+ return false
35
+ }
36
+ }
37
+
38
+ /**
39
+ * @public
40
+ */
41
+ export function createFsComponent(): IFileSystemComponent {
42
+ return {
43
+ createReadStream: fs.createReadStream,
44
+ createWriteStream: fs.createWriteStream,
45
+ access: fsPromises.access,
46
+ writeFile: fsPromises.writeFile,
47
+ opendir: fsPromises.opendir,
48
+ stat: fsPromises.stat,
49
+ unlink: fsPromises.unlink,
50
+ mkdir: fsPromises.mkdir,
51
+ rmdir: fsPromises.rmdir,
52
+ readdir: fsPromises.readdir,
53
+ readFile: fsPromises.readFile,
54
+ constants: {
55
+ F_OK: fs.constants.F_OK,
56
+ R_OK: fs.constants.R_OK
57
+ },
58
+ rename: fsPromises.rename,
59
+ fileExists,
60
+ directoryExists
61
+ }
62
+ }
@@ -0,0 +1,18 @@
1
+ import { ILoggerComponent } from '@well-known-components/interfaces'
2
+ import { createFetchComponent, IFetchComponent } from './fetch'
3
+ import { createFsComponent, IFileSystemComponent } from './fs'
4
+ import { createStdoutCliLogger } from './log'
5
+
6
+ export type CliComponents = {
7
+ fs: IFileSystemComponent
8
+ fetch: IFetchComponent
9
+ logger: ILoggerComponent.ILogger
10
+ }
11
+
12
+ export function initComponents(): CliComponents {
13
+ return {
14
+ fs: createFsComponent(),
15
+ fetch: createFetchComponent(),
16
+ logger: createStdoutCliLogger()
17
+ }
18
+ }
@@ -0,0 +1,48 @@
1
+ import { ILoggerComponent } from '@well-known-components/interfaces'
2
+ import { createColors } from 'colorette'
3
+ import { CliError } from '../logic/error'
4
+
5
+ /**
6
+ * This file imitates "cargo" logs. The words are aligned with the colon like this:
7
+ * V
8
+ * Error: some text provided as argumen
9
+ * Info: some text provided as argumen
10
+ * Success: some text provided as argumen
11
+ * Warning: some text provided as argumen
12
+ * ^
13
+ */
14
+
15
+ const stderr = (...parameters: readonly unknown[]) => {
16
+ process.stderr.write(`${parameters.filter(($) => $ !== undefined).join('')}\n`)
17
+ }
18
+
19
+ // @see https://no-color.org
20
+ // @see https://www.npmjs.com/package/chalk
21
+ export const colors = createColors({
22
+ useColor: process.env.FORCE_COLOR !== '0' && !process.env.NO_COLOR
23
+ })
24
+
25
+ export function createStdoutCliLogger(): ILoggerComponent.ILogger {
26
+ return {
27
+ log(message, extra) {
28
+ stderr(message, extra && JSON.stringify(extra))
29
+ },
30
+ debug(message, extra) {
31
+ stderr(colors.blueBright('debug: '), message, extra && JSON.stringify(extra))
32
+ },
33
+ error(error, extra) {
34
+ stderr(colors.redBright('error: '), error, extra && JSON.stringify(extra))
35
+ /* istanbul ignore next */
36
+ if (!(error instanceof CliError) || process.env.DEBUG) {
37
+ // print the stacktrace if it is not a CliError
38
+ console.error(error)
39
+ }
40
+ },
41
+ info(message, extra) {
42
+ stderr(colors.blueBright('info: '), message, extra && JSON.stringify(extra))
43
+ },
44
+ warn(message, extra) {
45
+ stderr(colors.yellow('warning: '), message, extra && JSON.stringify(extra))
46
+ }
47
+ }
48
+ }
package/src/index.ts ADDED
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+
3
+ /* istanbul ignore file */
4
+
5
+ import { getArgs } from './logic/args'
6
+ import { CliError } from './logic/error'
7
+ import { COMMANDS_PATH, getCommands } from './logic/commands'
8
+ import { CliComponents, initComponents } from './components'
9
+ import { printCommand } from './logic/beautiful-logs'
10
+ import { colors } from './components/log'
11
+
12
+ export interface Options {
13
+ args: ReturnType<typeof getArgs>
14
+ components: CliComponents
15
+ }
16
+
17
+ // leaving args as "any" since we don't know yet if we will use them
18
+ type FileFn = (options: Options) => Promise<void>
19
+
20
+ interface FileExports {
21
+ help?: FileFn
22
+ main?: FileFn
23
+ args?: ReturnType<typeof getArgs>
24
+ }
25
+
26
+ const listCommandsStr = (commands: string[]) => commands.map(($) => `\t *sdk-commands ${$} \n`).join('')
27
+
28
+ const commandFnsAreValid = (fns: FileExports): fns is Required<FileExports> => {
29
+ const { help, main } = fns
30
+ if (!help || !main) {
31
+ throw new CliError(`Command does not follow implementation rules:
32
+ * Requires a "help" function
33
+ * Requires a "main" function
34
+ `)
35
+ }
36
+ return true
37
+ }
38
+
39
+ async function main() {
40
+ const helpMessage = (commands: string[]) => `Here is the list of commands:\n${listCommandsStr(commands)}`
41
+ const args = getArgs()
42
+ const command = process.argv[2]
43
+ const needsHelp = args['--help']
44
+ const components: CliComponents = initComponents()
45
+
46
+ const commands = await getCommands(components)
47
+
48
+ if (!commands.includes(command)) {
49
+ if (needsHelp) {
50
+ components.logger.info(helpMessage(commands))
51
+ return
52
+ }
53
+ throw new CliError(`Command ${command} is invalid. ${helpMessage(commands)}`)
54
+ }
55
+
56
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
57
+ const cmd = require(`${COMMANDS_PATH}/${command}`)
58
+
59
+ if (commandFnsAreValid(cmd)) {
60
+ const options = { args: cmd.args, components }
61
+ if (needsHelp) {
62
+ await cmd.help(options)
63
+ } else {
64
+ printCommand(components.logger, command)
65
+
66
+ const ret = await cmd.main(options)
67
+ // print the result of the evaluation as json in the standard output
68
+ if (cmd.args['--json']) {
69
+ process.stdout.write(JSON.stringify(ret, null, 2))
70
+ }
71
+ }
72
+ }
73
+
74
+ // rollup watcher leaves many open FSWatcher even in build mode. we must call
75
+ // process.exit at this point to prevent the program halting forever
76
+ process.exit(process.exitCode || 0)
77
+ }
78
+
79
+ main().catch(function handleError(err: Error) {
80
+ if (err instanceof CliError) {
81
+ console.error(colors.redBright('Error: ') + err.message)
82
+ } else {
83
+ // log with console to show stacktrace and debug information
84
+ console.error(err)
85
+ console.warn(`Developer: All errors thrown must be an instance of "CliError"`)
86
+ }
87
+
88
+ // set an exit code but not finish the program immediately to close any pending work
89
+ process.exitCode = 1
90
+ })
@@ -0,0 +1,19 @@
1
+ import arg, { Result } from 'arg'
2
+
3
+ export type Args = {
4
+ [key: string]: string | StringConstructor | NumberConstructor | BooleanConstructor
5
+ }
6
+
7
+ // updating to TS 4.9 will prevent losing types when
8
+ // enforcing type to be "Args" by using "satisfies Args"
9
+ export const DEFAULT_ARGS = {
10
+ '--help': Boolean,
11
+ '--json': Boolean,
12
+ '-h': '--help'
13
+ }
14
+
15
+ export function getArgs(): Result<typeof DEFAULT_ARGS>
16
+ export function getArgs<T extends Args>(args: T): Result<typeof DEFAULT_ARGS & T>
17
+ export function getArgs<T extends Args>(args?: T) {
18
+ return arg({ ...DEFAULT_ARGS, ...args }, { permissive: true })
19
+ }
@@ -0,0 +1,26 @@
1
+ import { ILoggerComponent } from '@well-known-components/interfaces'
2
+ import { colors } from '../components/log'
3
+
4
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
5
+ const { name, version } = require('../../package.json')
6
+
7
+ export function printProgressStep(logger: ILoggerComponent.ILogger, log: string, currentStep: number, maxStep: number) {
8
+ logger.log(colors.dim(`[${currentStep}/${maxStep}]`) + ' ' + log)
9
+ }
10
+
11
+ export function printProgressInfo(logger: ILoggerComponent.ILogger, log: string) {
12
+ logger.log(colors.dim(log))
13
+ }
14
+
15
+ export function printCommand(logger: ILoggerComponent.ILogger, commandName: string) {
16
+ logger.log(colors.bold(`${name} ${commandName} v${version}`))
17
+ }
18
+
19
+ export function printSuccess(logger: ILoggerComponent.ILogger, operationSuccessfulMessage: string, summary: string) {
20
+ // print a space before the success callout
21
+ logger.log('')
22
+ logger.log(colors.greenBright(operationSuccessfulMessage))
23
+ if (typeof summary === 'string') {
24
+ logger.log(summary)
25
+ }
26
+ }
@@ -0,0 +1,31 @@
1
+ import { Entity } from '@dcl/schemas'
2
+ import { fetch } from 'undici'
3
+
4
+ export async function fetchEntityByPointer(
5
+ baseUrl: string,
6
+ pointers: string[]
7
+ ): Promise<{
8
+ baseUrl: string
9
+ deployments: Entity[]
10
+ }> {
11
+ if (pointers.length === 0)
12
+ return {
13
+ baseUrl,
14
+ deployments: []
15
+ }
16
+
17
+ const activeEntities = baseUrl + '/content/entities/active'
18
+
19
+ const response = await fetch(activeEntities, {
20
+ method: 'post',
21
+ headers: { 'content-type': 'application/json', connection: 'close' },
22
+ body: JSON.stringify({ pointers })
23
+ })
24
+
25
+ const deployments: Entity[] = response.ok ? ((await response.json()) as Entity[]) : []
26
+
27
+ return {
28
+ baseUrl,
29
+ deployments
30
+ }
31
+ }
@@ -0,0 +1,28 @@
1
+ import { resolve } from 'path'
2
+ import { CliComponents } from '../components'
3
+ import { CliError } from './error'
4
+
5
+ export const COMMANDS_PATH = resolve(__dirname, '../commands')
6
+
7
+ export async function getCommands({ fs }: Pick<CliComponents, 'fs'>): Promise<string[]> {
8
+ const commandDirs = await fs.readdir(COMMANDS_PATH)
9
+
10
+ const commands = commandDirs.map(async (dir) => {
11
+ const path = resolve(COMMANDS_PATH, dir)
12
+
13
+ const statDir = await fs.stat(path)
14
+
15
+ if (!statDir.isDirectory()) {
16
+ throw new CliError('Developer: All commands must be inside a folder')
17
+ }
18
+
19
+ const statIndex = await fs.stat(`${path}/index.js`)
20
+ if (!statIndex.isFile()) {
21
+ throw new CliError('Developer: All commands must have an "index.js" file inside')
22
+ }
23
+
24
+ return dir
25
+ })
26
+
27
+ return Promise.all(commands)
28
+ }
@@ -0,0 +1,95 @@
1
+ export interface IBounds {
2
+ minX: number
3
+ minY: number
4
+ maxX: number
5
+ maxY: number
6
+ }
7
+
8
+ export type Coords = {
9
+ x: number
10
+ y: number
11
+ }
12
+
13
+ /**
14
+ * Returns metaverse coordinates bounds.
15
+ * TODO: use functions from @dcl/schemas
16
+ */
17
+ export function getBounds(): IBounds {
18
+ return {
19
+ minX: -150,
20
+ minY: -150,
21
+ maxX: 165,
22
+ maxY: 165
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Parses a string-based set of coordinates.
28
+ * - All spaces are removed
29
+ * - Leading zeroes are removed
30
+ * - `-0` is converted to `0`
31
+ * @param coordinates An string containing coordinates in the `x,y; x,y; ...` format
32
+ */
33
+ export function parse(coordinates: string): string[] {
34
+ return coordinates.split(';').map((coord: string) => {
35
+ const [x, y] = coord.split(',').map(($) => {
36
+ return parseInt($, 10)
37
+ .toString() // removes spaces :)
38
+ .replace('-0', '0')
39
+ .replace(/undefined|NaN/g, '0')
40
+ })
41
+ return `${x},${y}`
42
+ })
43
+ }
44
+
45
+ /**
46
+ * Converts a string-based set of coordinates to an object
47
+ * @param coords A string containing a set of coordinates
48
+ */
49
+ export function getObject(coords: string): Coords {
50
+ const [x, y] = parse(coords)[0].split(',')
51
+ return { x: parseInt(x.toString(), 10), y: parseInt(y.toString(), 10) }
52
+ }
53
+
54
+ /**
55
+ * Returns true if the given coordinates are in metaverse bounds
56
+ */
57
+ export function inBounds(x: number, y: number): boolean {
58
+ const { minX, minY, maxX, maxY } = getBounds()
59
+ return x >= minX && x <= maxX && y >= minY && y <= maxY
60
+ }
61
+
62
+ /**
63
+ * Returns true if the given parcels array are connected
64
+ */
65
+ export function areConnected(parcels: Coords[]): boolean {
66
+ if (parcels.length === 0) {
67
+ return false
68
+ }
69
+ const visited = visitParcel(parcels[0], parcels)
70
+ return visited.length === parcels.length
71
+ }
72
+
73
+ function visitParcel(parcel: Coords, allParcels: Coords[], visited: Coords[] = []): Coords[] {
74
+ const isVisited = visited.some((visitedParcel) => isEqual(visitedParcel, parcel))
75
+ if (!isVisited) {
76
+ visited.push(parcel)
77
+ const neighbours = getNeighbours(parcel.x, parcel.y, allParcels)
78
+ neighbours.forEach((neighbours) => visitParcel(neighbours, allParcels, visited))
79
+ }
80
+ return visited
81
+ }
82
+
83
+ function getIsNeighbourMatcher(x: number, y: number) {
84
+ return (coords: Coords) =>
85
+ (coords.x === x && (coords.y + 1 === y || coords.y - 1 === y)) ||
86
+ (coords.y === y && (coords.x + 1 === x || coords.x - 1 === x))
87
+ }
88
+
89
+ function getNeighbours(x: number, y: number, parcels: Coords[]): Coords[] {
90
+ return parcels.filter(getIsNeighbourMatcher(x, y))
91
+ }
92
+
93
+ export function isEqual(p1: Coords, p2: Coords): boolean {
94
+ return p1.x === p2.x && p1.y === p2.y
95
+ }
@@ -0,0 +1,49 @@
1
+ import path from 'path'
2
+ import { CliComponents } from '../components'
3
+
4
+ export const defaultDclIgnore = [
5
+ '.*',
6
+ 'package.json',
7
+ 'package-lock.json',
8
+ 'yarn-lock.json',
9
+ 'build.json',
10
+ 'export',
11
+ 'tsconfig.json',
12
+ 'tslint.json',
13
+ 'node_modules',
14
+ '**/*.ts',
15
+ '**/*.tsx',
16
+ 'Dockerfile',
17
+ 'dist',
18
+ 'README.md',
19
+ '*.blend',
20
+ '*.fbx',
21
+ '*.zip',
22
+ '*.rar'
23
+ ]
24
+
25
+ export async function getDCLIgnoreFileContents(
26
+ components: Pick<CliComponents, 'fs'>,
27
+ dir: string
28
+ ): Promise<string | null> {
29
+ try {
30
+ return components.fs.readFile(path.resolve(dir, '.dclignore'), 'utf8')
31
+ } catch (e) {}
32
+
33
+ return null
34
+ }
35
+
36
+ /**
37
+ * Returns the default .dclignore entries plus the ones provided by the user.
38
+ * In case of .dclignore not existing, it returns a pre-defined list.
39
+ */
40
+ export async function getDCLIgnorePatterns(components: Pick<CliComponents, 'fs'>, dir: string): Promise<string[]> {
41
+ const ignoredContent = await getDCLIgnoreFileContents(components, dir)
42
+ const ignored = (ignoredContent?.split('\n') || defaultDclIgnore).filter(Boolean)
43
+ ignored.push(...defaultDclIgnore)
44
+
45
+ // by default many files need to be ignored
46
+ ignored.push('.*', 'node_modules', '**/*.ts', '**/*.tsx')
47
+
48
+ return Array.from(new Set(ignored))
49
+ }
@@ -0,0 +1 @@
1
+ export class CliError extends Error {}
@@ -0,0 +1,36 @@
1
+ import { spawn } from 'child_process'
2
+
3
+ interface Options {
4
+ env: { [key: string]: string }
5
+ silent: boolean
6
+ }
7
+
8
+ export function exec(
9
+ cwd: string,
10
+ command: string,
11
+ args: string[],
12
+ { env, silent }: Partial<Options> = {}
13
+ ): Promise<void> {
14
+ return new Promise((resolve, reject) => {
15
+ const child = spawn(command, args, {
16
+ shell: true,
17
+ cwd,
18
+ env: { ...process.env, NODE_ENV: '', ...env }
19
+ })
20
+
21
+ if (!silent) {
22
+ child.stdout.pipe(process.stdout)
23
+ child.stderr.pipe(process.stderr)
24
+ }
25
+
26
+ child.on('close', (code: number) => {
27
+ if (code !== 0) {
28
+ const _ = `${command} ${args.join(' ')}`
29
+ reject(new Error(`Command "${_}" exited with code ${code}. Please try running the command manually`))
30
+ return
31
+ }
32
+
33
+ resolve(undefined)
34
+ })
35
+ })
36
+ }
@@ -0,0 +1,41 @@
1
+ import extractZip from 'extract-zip'
2
+ import { resolve } from 'path'
3
+ import { IFileSystemComponent } from '../components/fs'
4
+ import { IFetchComponent } from '../components/fetch'
5
+
6
+ /**
7
+ * Check's if directory is empty
8
+ * @param dir Directory to check for emptyness
9
+ */
10
+ export async function isDirectoryEmpty(components: { fs: IFileSystemComponent }, dir: string): Promise<boolean> {
11
+ const files = await components.fs.readdir(dir)
12
+ return !files.length
13
+ }
14
+
15
+ /**
16
+ * Download a file
17
+ * @param url URL of the file
18
+ * @param dest Path to where to save the file
19
+ */
20
+ export async function download(
21
+ components: { fs: IFileSystemComponent; fetch: IFetchComponent },
22
+ url: string,
23
+ dest: string
24
+ ): Promise<string> {
25
+ // we should remove this package and use the native "fetch" when Node
26
+ // releases it as stable: https://nodejs.org/docs/latest-v18.x/api/globals.html#fetch
27
+ const data = await (await components.fetch.fetch(url)).arrayBuffer()
28
+ await components.fs.writeFile(dest, Buffer.from(data))
29
+ return dest
30
+ }
31
+
32
+ /**
33
+ * Extracts a .zip file
34
+ * @param url Path of the zip file
35
+ * @param dest Path to where to extract the zip file
36
+ */
37
+ export async function extract(path: string, dest: string): Promise<string> {
38
+ const destPath = resolve(dest)
39
+ await extractZip(resolve(path), { dir: destPath })
40
+ return destPath
41
+ }
@@ -0,0 +1,15 @@
1
+ import portfinder from 'portfinder'
2
+
3
+ export async function previewPort() {
4
+ let resolvedPort = 0
5
+
6
+ if (!resolvedPort) {
7
+ try {
8
+ resolvedPort = await portfinder.getPortPromise()
9
+ } catch (e) {
10
+ resolvedPort = 2044
11
+ }
12
+ }
13
+
14
+ return resolvedPort
15
+ }
@@ -0,0 +1,76 @@
1
+ import { ContentMapping } from '@dcl/schemas/dist/misc/content-mapping'
2
+ import { CliComponents } from '../components'
3
+ import { getDCLIgnorePatterns } from './dcl-ignore'
4
+ import { sync as globSync } from 'glob'
5
+ import ignore from 'ignore'
6
+ import path from 'path'
7
+ import { CliError } from './error'
8
+
9
+ /**
10
+ * Returns an array of the publishable files for a given folder.
11
+ */
12
+ export async function getPublishableFiles(
13
+ components: Pick<CliComponents, 'fs'>,
14
+ projectRoot: string
15
+ ): Promise<Array<string>> {
16
+ const ignorePatterns = await getDCLIgnorePatterns(components, projectRoot)
17
+
18
+ const ig = ignore().add(ignorePatterns)
19
+
20
+ const allFiles = globSync('**/*', {
21
+ cwd: projectRoot,
22
+ absolute: false,
23
+ dot: false,
24
+ ignore: ignorePatterns,
25
+ nodir: true
26
+ })
27
+
28
+ return ig.filter(allFiles)
29
+ }
30
+
31
+ /**
32
+ * This function converts paths to decentraland-compatible paths.
33
+ * - From windows separators to unix separators.
34
+ * - All to lowercase
35
+ */
36
+ export function normalizeDecentralandFilename(filename: string) {
37
+ return filename.replace(/(\\)/g, '/').toLowerCase()
38
+ }
39
+
40
+ /**
41
+ * Returns the content mappings for a specific project folder.
42
+ */
43
+ export async function getProjectContentMappings(
44
+ components: Pick<CliComponents, 'fs'>,
45
+ projectRoot: string,
46
+ hashingFunction: (filePath: string) => Promise<string>
47
+ ): Promise<ContentMapping[]> {
48
+ const projectFiles = await getPublishableFiles(components, projectRoot)
49
+ const ret: ContentMapping[] = []
50
+
51
+ const usedFilenames = new Set<string>()
52
+
53
+ for (const file of projectFiles) {
54
+ const absolutePath = path.resolve(projectRoot, file)
55
+
56
+ /* istanbul ignore if */
57
+ if (!(await components.fs.fileExists(absolutePath))) continue
58
+
59
+ // remove heading '/'
60
+ const normalizedFile = normalizeDecentralandFilename(file).replace(/^\/+/, '')
61
+
62
+ /* istanbul ignore if */
63
+ if (usedFilenames.has(normalizedFile)) {
64
+ throw new CliError(
65
+ `DuplicatedFilenameError: the file ${file} exists with a different casing. Please manually remove one occurrence`
66
+ )
67
+ }
68
+
69
+ ret.push({
70
+ file: normalizedFile,
71
+ hash: await hashingFunction(absolutePath)
72
+ })
73
+ }
74
+
75
+ return ret
76
+ }