diff --git a/README.md b/README.md index 0405971..9f0cdf3 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,30 @@ Triggered when GDCore wants to print a message. ##### `error`: Triggered when GDCore errors. -#### `loadGD(version?: string): Promise` - -The entrypoint of the module. Accept a github release tag to specify a specific version to download and use. +##### `loadGD(version?: string | {` +#####    `versionTag?: string,` +#####    `user?: string,` +#####    `authToken?: string,` +#####    `fetchProvider?: {` +#####        `libGDPath: string` +#####      `} | {` +#####        `useReleaseAssets: true } |` +#####      `} | {` +#####        `"libGD.js": (gdPath: string) => Promise,` +#####        `"libGD.wasm"?: (gdPath: string) => Promise,` +#####        `"libGD.js.mem"?: (gdPath: string) => Promise,` +#####      `}` +#####    `}` +##### `}): Promise` + +The entrypoint of the module. Accept a github release tag to specify a specific version to download and use or configuration object where: +- versionTag - optional github release tag +- user - optional github GDevelop project or fork owner. +- authToken - optional github private token for github API authorization +- fetchProvider - optional configuration for libGD assets loading. You can provide next settings: + - libGDPath - url to libGD assets + - useReleaseAssets - defines to load assets from release attachments + - libGD.js, libGD.wasm, libGD.js.mem - function providers to rely on for appropriate files loading #### `WrappedGD.gd: gd` diff --git a/package-lock.json b/package-lock.json index 4f8b003..e4af43d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gdcore-tools", - "version": "1.0.5", + "version": "1.0.9", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "gdcore-tools", - "version": "1.0.5", + "version": "1.0.9", "license": "MIT", "dependencies": { "@octokit/request": "^5.4.12", diff --git a/src/WrappedGD.js b/src/WrappedGD.js index 15236f0..5bc603a 100644 --- a/src/WrappedGD.js +++ b/src/WrappedGD.js @@ -8,12 +8,13 @@ const { } = require("./EventsFunctionsExtensionsLoader/LocalEventsFunctionCodeWriter"); const saveProject = require("./LocalProjectWriter"); const assignIn = require("lodash/assignIn"); -const { getGD, getRuntimePath } = require("./downloadGD"); +const getGD = require("./getGD"); +const { getRuntimePath } = require("./fsUtils"); const { join, resolve } = require("path"); const { makeFS } = require("./LocalFileSystem"); class WrappedGD extends EventEmitter { - constructor(version) { + constructor(fetchOptions) { super(); /** @@ -43,10 +44,10 @@ class WrappedGD extends EventEmitter { * The path to the current version. * @private */ - this.versionPath = getRuntimePath(version); + this.versionPath = getRuntimePath(fetchOptions.versionTag, fetchOptions.user); // Begin async loading of GDCore and extensions - getGD(version, { + getGD(fetchOptions, { print: (message) => this.emit("print", message), printErr: (e) => this.emit("error", e), onAbort: (e) => this.emit("error", e), diff --git a/src/downloadGD.js b/src/downloadGD.js index df8e3b3..717cf91 100644 --- a/src/downloadGD.js +++ b/src/downloadGD.js @@ -1,222 +1,168 @@ const fs = require("fs-extra-promise"); const path = require("path"); -const { request } = require("@octokit/request"); -const { https } = require("follow-redirects"); - -const getRuntimePath = (version) => path.join(__dirname, "Versions", version); - -const downloadFile = (file, savePath, required = true) => - new Promise((resolve) => { - https.get(file, function (response) { - if (response.statusCode !== 200) { - if (required) - throw new Error( - `❌ Cannot download ${file}! Error ${response.statusCode}: ${response.statusMessage}` - ); - // Silently fail if the file is not required - else return resolve(true); - } - response.pipe(fs.createWriteStream(savePath)).addListener("close", () => { - resolve(); - }); - }); - }); - -const findLatestVersion = () => { - return new Promise((resolve, reject) => { - // Fetch base release infos - console.info(`🕗 Getting latest release tag...`); - return request("GET /repos/4ian/GDevelop/releases/latest") - .then(({ data }) => { - resolve(data.tag_name); - }) - .catch(() => { - console.error( - "❌ Couldn't fetch latest version, using latest local version." - ); - fs.readdirAsync(path.join(__dirname, "Versions")) - .then((versions) => resolve(versions[0])) - .catch(() => { - console.error( - "💀 Fatal Error! Couldn't find or download the latest version." - ); - reject(); - }); - }); - }); +const assert = require("assert"); +const { getLatestCiCommit, getAssetsIdsMap, downloadGitBinaryAsset } = require("./gitUtils"); +const { findLatestVersion, getRuntimePath, downloadFile, extractGdRuntimes } = require("./fsUtils"); + +const gdAuthor = "4ian"; +const libGdAssets = { + required: [ + "libGD.js" + ], + optional: [ + "libGD.wasm", + "libGD.js.mem", + ], + getAll() { + return this.required.concat(this.optional); + }, + isRequired(assetName) { + return this.required.includes(assetName); + } }; /** - * Downloads a GDevelop version (libGD.js, the runtime and the extensions). - * @param {string} versionTag The GDevelop version tag + * @typedef {{ + * 'libGD.js': (string) => Promise, + * 'libGD.wasm'?: (string) => Promise, + * 'libGD.js.mem'?: (string) => Promise + * } | { + * libGDPath: string + * } | { + * useReleaseAssets: true + * } + * } GdFetchDataProvider + * Fetch configuration that provides methods or URL to load libGD assets. */ -const downloadVersion = async function (versionTag) { - const StreamZip = require("node-stream-zip"); - const tasks = []; - const gdPath = getRuntimePath(versionTag); - - // Make sure "Versions" directory exists - const versionsDir = path.join(__dirname, "Versions"); - await fs.accessAsync(versionsDir).catch(() => fs.mkdirAsync(versionsDir)); - - // Clear directory - await fs - .accessAsync(gdPath) - .catch(() => null) // Swallow the error as it is expected to error - .then(() => fs.removeAsync(gdPath)) - .finally(() => fs.mkdirAsync(gdPath)); - - let commit = ( - await request("GET /repos/4ian/GDevelop/commits/{ref}", { - ref: ( - await request("GET /repos/4ian/GDevelop/git/ref/tags/{tag}", { - tag: versionTag, - }) - ).data.object.sha, - }) - ).data; - - // Go up to the latest commit for which libGD.js was built - while (commit.commit.message.indexOf("[skip ci]") !== -1) { - commit = ( - await request("GET /repos/4ian/GDevelop/commits/{ref}", { - ref: commit.parents[0].sha, - }) - ).data; - console.log(commit.commit.message, commit.parents); + +/** + * Verifies and complements passed fetch configuration object. + * + * @param {{ versionTag?: string, user?: string, fetchProvider?: GdFetchDataProvider, authToken?: string } | string} options Fetch configuration to complete. + * @returns {Promise<{ versionTag: string, user: string, fetchProvider: GdFetchDataProvider, authToken?: string }>} Complete fetch configuration object. + */ +const getFetchConfiguration = async (options) => { + if (typeof options === "string") options = { versionTag: options } + + options.user ??= gdAuthor; + options.versionTag ??= await findLatestVersion(options.user, options.authToken); + + if (options.user === gdAuthor) { + const { sha } = await getLatestCiCommit(options.versionTag, options.authToken); + + options.fetchProvider = { + libGDPath: `https://s3.amazonaws.com/gdevelop-gdevelop.js/master/commit/${sha}/`, + }; + } else { + assert(options.fetchProvider, "❌ You should pass fetchProvider instance if fork is used."); + assert( + libGdAssets.required.every((libGdAsset) => typeof options.fetchProvider[libGdAsset] === "function") || + options.fetchProvider.libGDPath || + options.fetchProvider.useReleaseAssets, + `❌ You should provide fetch methods or fetch url for necessary libGD assets: ${libGdAssets.required}.` + ); } - // Fetch the file with the GDJS Runtime and extensions - console.info(`🕗 Starting download of GDevelop Runtime '${versionTag}'...`); + return options; +}; + +/** + * Downloads GDevelop Runtime sources and compiles them. + * + * @param {{ versionTag: string, user: string, fetchProvider: GdFetchDataProvider, authToken?: string }} fetchConfiguration Fetch configuration. + * @param {string} gdPath Path to save Runtime sources folder to. + */ +const downloadGdRuntimes = async ({ versionTag, user }, gdPath) => { const zipPath = path.join(gdPath, "gd.zip"); - tasks.push( - downloadFile( - "https://codeload.github.com/4ian/GDevelop/legacy.zip/" + versionTag, - zipPath - ) - .then(async () => { - console.info(`✅ Done downloading GDevelop Runtime '${versionTag}'`); - console.info(`🕗 Extracting GDevelop Runtime '${versionTag}'...`); - await fs.mkdirAsync(path.join(gdPath, "Runtime")); - await fs.mkdirAsync(path.join(gdPath, "Runtime", "Extensions")); - const zip = new StreamZip({ - file: zipPath, - storeEntries: true, - }); - const prefix = `4ian-GDevelop-${commit.sha.slice(0, 7)}/`; - return Promise.all([ - new Promise((resolve) => { - zip.on("ready", () => { - zip.extract( - prefix + "Extensions", - path.join(gdPath, "Runtime", "Extensions"), - (e) => { - if (e) - console.error( - "❌ Error while extracting the GDevelop Runtime extensions! ", - e - ); - else resolve(); - } - ); - }); - }), - new Promise((resolve) => { - zip.on("ready", () => { - zip.extract( - prefix + "GDJS/Runtime", - path.join(gdPath, "Runtime"), - (e) => { - if (e) - console.error( - "❌ Error while extracting the GDevelop Runtime! ", - e - ); - else resolve(); - } - ); - }); - }), - ]); - }) - .finally(() => fs.removeAsync(zipPath)) - .then(() => console.info(`✅ Done extracting the GDevelop Runtime`)) - .then(() => { - try { - fs.statSync(path.join(gdPath, "Runtime", "gd.ts")); - } catch { - console.info("↪️ Skipping TypeScript compilation, already compiled."); - return; - } - console.info(`🕗 Compiling Runtime...`); - return require("./build")(gdPath); - }) - .catch((e) => console.error("❌ Fatal error! ", e)) - ); + + console.info(`🕗 Starting download of GDevelop Runtime '${versionTag}'...`); + await downloadFile(`https://codeload.github.com/${user}/GDevelop/legacy.zip/${versionTag}`, zipPath); + console.info(`✅ Done downloading GDevelop Runtime '${versionTag}'`); + console.info(`🕗 Extracting GDevelop Runtime '${versionTag}'...`); + await extractGdRuntimes(zipPath, gdPath, user); + await fs.remove(zipPath); + console.info(`✅ Done extracting the GDevelop Runtime`); - // Download the fitting libGD version - const libGDPath = - "https://s3.amazonaws.com/gdevelop-gdevelop.js/master/commit/" + - commit.sha + - "/"; + if (!fs.existsSync(path.join(gdPath, "Runtime/gd.js"))) { + console.info(`🕗 Compiling Runtime...`); + return require("./build")(gdPath); + } +} + +/** + * Downloads a GDevelop version libGD assets. + * + * @param {{ versionTag: string, user: string, fetchProvider: GdFetchDataProvider, authToken?: string }} fetchOptions Fetch configuration. + * @param {string} gdPath Path to save libGd assets to. + */ +const downloadLibGdAssets = async function ({ versionTag, user, fetchProvider, authToken }, gdPath) { console.info(`🕗 Starting download of GDevelop Core...`); - tasks.push( - downloadFile(libGDPath + "libGD.js", path.join(gdPath, "libGD.js")).then( - () => console.info(`✅ Done downloading libGD.js`) - ) - ); - tasks.push( - downloadFile( - libGDPath + "libGD.js.mem", - path.join(gdPath, "libGD.js.mem"), - false - ).then( - (errored) => !errored && console.info(`✅ Done downloading libGD.js.mem`) - ) - ); - tasks.push( - downloadFile( - libGDPath + "libGD.wasm", - path.join(gdPath, "libGD.wasm"), - false - ).then( - (errored) => !errored && console.info(`✅ Done downloading libGD.wasm`) - ) - ); + if (fetchProvider.useReleaseAssets) { + console.info(`🕗 Using ${versionTag} release assets...`); + const assetsIdsMap = await getAssetsIdsMap(user, versionTag, authToken); - return Promise.all(tasks).then(() => - console.info(`✅ Successfully downloaded GDevelop version '${versionTag}'`) + assert( + libGdAssets.required.every((libGdAsset) => assetsIdsMap[libGdAsset]), + `❌ Your release does not provide all required libGd assets: ${libGdAssets.required}. Provided: ${Object.values(assetsIdsMap)}.` + ); + + return Promise.all( + libGdAssets + .getAll() + .filter((libGdAsset) => assetsIdsMap[libGdAsset]) + .map(async (libGdAsset) => { + await downloadGitBinaryAsset(path.join(gdPath, libGdAsset), user, assetsIdsMap[libGdAsset], authToken); + console.info(`✅ Done downloading ${libGdAsset}`); + }) + ); + } + + if (fetchProvider.libGDPath) { + console.info(`🕗 Using ${fetchProvider.libGDPath} url...`); + return Promise.all( + libGdAssets + .getAll() + .map((libGdAsset) => ( + downloadFile(fetchProvider.libGDPath + libGdAsset, path.join(gdPath, libGdAsset), libGdAssets.isRequired(libGdAsset)) + .then((errored) => !errored && console.info(`✅ Done downloading ${libGdAsset}`)) + )) + ); + } + console.info(`🕗 Using provider functions...`); + return Promise.all( + libGdAssets + .getAll() + .filter((libGdAsset) => fetchProvider[libGdAsset]) + .map(async (libGdAsset) => { + await fetchProvider[libGdAsset](gdPath); + console.info(`✅ Loading of '${libGdAsset}' is finished by provider.`); + }) ); -}; +} /** - * Initialize libGD.js. - * If the version is not present, download it. - * Returning `gd` doesn't work, so a hacky workaround with global is used. - * @param {string} [versionTag] The GDevelop version to use. If not precised, the latest is used. + * Downloads a GDevelop version (libGD.js, the runtime and the extensions). + * + * @param {{ versionTag: string, user: string, fetchProvider: GdFetchDataProvider, authToken?: string }} fetchOptions Fetch configuration. */ -const getGD = async function (versionTag, gdOptions) { - const runtimePath = getRuntimePath(versionTag); - // Download the version if it isn't present - try { - fs.accessSync(runtimePath); - } catch { - console.log("❌ The GDevelop version was not found, downloading it!"); - await downloadVersion(versionTag).catch(console.error); - } +const downloadVersion = function (fetchOptions) { + const { + versionTag, + user + } = fetchOptions; + const gdPath = getRuntimePath(versionTag, user); + + fs.emptyDirSync(gdPath); - const gd = require(path.join(runtimePath, "libGD.js"))(gdOptions); - return new Promise((resolve) => { - gd.then(() => { - // Make sure gd is not thenable as the promise would think it is one as well. - delete gd.then; - resolve(gd); - }); - }); + return Promise.all([ + downloadGdRuntimes(fetchOptions, gdPath), + downloadLibGdAssets(fetchOptions, gdPath), + ]).then(() => + console.info(`✅ Successfully downloaded GDevelop version '${versionTag}'`) + ); }; module.exports = { - getRuntimePath, - getGD, - findLatestVersion, + getFetchConfiguration, + downloadVersion, }; diff --git a/src/fsUtils.js b/src/fsUtils.js new file mode 100644 index 0000000..4074ec3 --- /dev/null +++ b/src/fsUtils.js @@ -0,0 +1,99 @@ +const fs = require("fs-extra-promise"); +const https = require("https"); +const path = require("path"); +const StreamZip = require("node-stream-zip"); +const { findLatestRelease } = require("./gitUtils"); + +/** + * @param {string} user User to get the path to versions of. + * @returns {string} Path to specific user Gdevelop versions folder. + */ +const getUserPath = (user) => path.join(__dirname, "Versions", user); + +/** + * @param {string} version Version to get path to. + * @param {string} user User to get the path to the version of. + * @returns {string} Path to specific Gdevelop version folder. + */ +const getRuntimePath = (version, user) => path.join(getUserPath(user), version); + +/** + * @param {string} user User to get the latest Gdevelop version of. + * @param {string} [authToken] Github private token for authorization. + * @returns {Promise} Promisified latest available version for the specified user. + */ +const findLatestVersion = async (user, authToken) => { + console.info(`🕗 Getting latest release tag...`); + return ( + findLatestRelease(user, authToken) + .catch(() => { + console.error("❌ Couldn't fetch latest version, using latest local version."); + fs.readdirAsync(getUserPath(user)) + .then((versions) => resolve(versions[0])) + .catch(() => { + console.error("💀 Fatal Error! Couldn't find or download the latest version."); + reject(); + }); + }) + ) +} + +/** + * @param {string} file File url to download. + * @param {string} savePath Path to save the downloaded file. + * @param {boolean} required Defines if need to throw if loading error occurs. Throw if true is passed. + */ +const downloadFile = (file, savePath, required = true) => + new Promise((resolve) => { + https.get(file, function (response) { + if (response.statusCode !== 200) { + if (required) throw new Error(`❌ Cannot download ${file}! Error ${response.statusCode}: ${response.statusMessage}`); + // Silently fail if the file is not required + else return resolve(true); + } + response.pipe(fs.createWriteStream(savePath)).addListener("close", () => { + resolve(); + }); + }); + }); + +const getDir = (pathToFile) => path.extname(pathToFile) ? path.dirname(pathToFile) : pathToFile; + +/** + * Extracts Runtime sources from GDevelop GitHub release archive. + * @param {string} zipPath Path to Gdevelop zip archive. + * @param {string} savePath Path to save extracted Runtime sources folder to. + * @param {string} prefixUser Github user of GDevelop GitHub release archive. + */ +const extractGdRuntimes = async (zipPath, savePath, prefixUser) => { + const zip = new StreamZip.async({ + file: zipPath, + storeEntries: true, + }); + const prefix = `${prefixUser}-GDevelop-${(await zip.comment).slice(0, 7)}`; + const runtimesPaths = { + "GDJS/Runtime": "Runtime", + "Extensions": "Runtime/Extensions", + }; + + try { + for (const relatedPathToExtract in runtimesPaths) { + const saveDir = savePath + "/" + getDir(runtimesPaths[relatedPathToExtract]); + + await fs.ensureDir(saveDir); + await zip.extract(prefix + "/" + relatedPathToExtract, saveDir); + } + await zip.close(); + } catch (err) { + console.error("❌ Error while extracting the GDevelop archive! ", e); + } +} + + +module.exports = { + getUserPath, + getRuntimePath, + downloadFile, + extractGdRuntimes, + findLatestVersion, +}; \ No newline at end of file diff --git a/src/getGD.js b/src/getGD.js new file mode 100644 index 0000000..889d75b --- /dev/null +++ b/src/getGD.js @@ -0,0 +1,34 @@ +const fs = require("fs-extra-promise"); +const path = require("path"); +const { getRuntimePath } = require("./fsUtils"); +const { downloadVersion } = require("./downloadGD"); + +/** + * Initialize libGD.js. + * If the version is not present, download it. + * Returning `gd` doesn't work, so a hacky workaround with global is used. + * @param {{ versionTag: string, user: string, fetchProvider: import("./downloadGD").GdFetchDataProvider, authToken?: string }} fetchOptions Fetch configuration. + */ +module.exports = async function (fetchOptions, gdOptions) { + const runtimePath = getRuntimePath(fetchOptions.versionTag, fetchOptions.user); + // Download the version if it isn't present + if (!fs.existsSync(runtimePath)) { + console.log("❌ The GDevelop versionTag was not found, downloading it!"); + + try { + await downloadVersion(fetchOptions); + } catch (err) { + fs.rmSync(runtimePath, { recursive: true }); + throw err; + } + } + + const gd = require(path.join(runtimePath, "libGD.js"))(gdOptions); + return new Promise((resolve) => { + gd.then(() => { + // Make sure gd is not thenable as the promise would think it is one as well. + delete gd.then; + resolve(gd); + }); + }); +}; \ No newline at end of file diff --git a/src/gitUtils.js b/src/gitUtils.js new file mode 100644 index 0000000..684f792 --- /dev/null +++ b/src/gitUtils.js @@ -0,0 +1,100 @@ +const { request } = require("@octokit/request"); +const fs = require("fs-extra-promise"); + +/** + * Returns octokit request authorization object if the token is provided. + * @param {string} [authToken] Github private token for authorization. + * @returns {{ authorization: string } | { }} Octokit request authorization object. + */ +const getAuthHeader = (authToken) => authToken ? { authorization: authToken } : {}; + +/** + * Returns map object of release asset names as keys and asset ids as values. + * @param {string} user Github user of release. + * @param {string} versionTag Release tag to get assets of. + * @param {string} [authToken] Github private token for authorization. + * @returns {Promise<{ [assetName: string]: string }>} Map object of asset names to ids. + */ +const getAssetsIdsMap = async (user, versionTag, authToken) => { + const { + data: { assets } + } = await request('GET /repos/{user}/GDevelop/releases/tags/{tag}', { + user, + tag: versionTag, + headers: getAuthHeader(authToken), + }); + + return assets.reduce((data, { id, name }) => (data[name] = id) && data, {}); +}; + +/** + * Downloads GitHub release asset. + * @param {string} filePath Path to save asset by. + * @param {string} user Github user of release asset. + * @param {string} id Specific release asset id. + * @param {string} [authToken] Github private token for authorization. + * @returns {Promise} + */ +const downloadGitBinaryAsset = (filePath, user, id, authToken) => + request('GET /repos/{user}/GDevelop/releases/assets/{id}', { + user, + id, + headers: { + ...getAuthHeader(authToken), + accept: 'application/octet-stream', + }, + }) + .then(({ data }) => fs.writeFile(filePath, Buffer.from(data))); + +/** + * Returns the latest commit for which libGD.js was built for the official GDevelop repo. + * @param {string} versionTag Release tag to get assets of. + * @param {string} [authToken] Github private token for authorization. + * @returns {Object} Octokit commit object. + */ +const getLatestCiCommit = async (versionTag, authToken) => { + const headers = getAuthHeader(authToken); + let { data: commit } = + await request("GET /repos/4ian/GDevelop/commits/{ref}", { + ref: ( + await request("GET /repos/4ian/GDevelop/git/ref/tags/{tag}", { + tag: versionTag, + }) + ).data.object.sha, + headers, + }); + + // Go up to the latest commit for which libGD.js was built + while (commit.commit.message.indexOf("[skip ci]") !== -1) { + commit = ( + await request("GET /repos/4ian/GDevelop/commits/{ref}", { + ref: commit.parents[0].sha, + headers, + }) + ).data; + } + + return commit; +} + +/** + * Returns the latest GDevelop repo release for the specified user. + * @param {string} user Github user of release. + * @param {string} [authToken] Github private token for authorization. + * @returns {Promise} Latest GDevelop repo release for specified user + */ +const findLatestRelease = (user, authToken) => + request(`GET /repos/${user}/GDevelop/releases/latest`, { + headers: getAuthHeader(authToken) + }) + .then(({ data }) => { + resolve(data.tag_name); + }) + +module.exports = { + getAssetsIdsMap, + getAuthHeader, + downloadGitBinaryAsset, + getLatestCiCommit, + findLatestRelease, +}; \ No newline at end of file diff --git a/src/index.js b/src/index.js index 19d954b..6ebf7ea 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1,22 @@ -const { findLatestVersion } = require("./downloadGD"); +const { getFetchConfiguration } = require("./downloadGD"); const WrappedGD = require("./WrappedGD"); -const loadGD = async (version) => { - if (version === undefined) version = await findLatestVersion(); +/** + * @typedef {{ versionTag?: string, user?: string, fetchProvider?: import("./downloadGD").GdFetchDataProvider, authToken?: string }} LoadGDOptions + * @property {string} [versionTag] The version of GDevelop to load. + * @property {string} [user] The GitHub user of GDevelop project. + * @property {string} [authToken] The GitHub token for GitHub API authorization. + * @property {import("./downloadGD").GdFetchDataProvider} [fetchProvider] The fetch options. + */ + +/** + * @param {LoadGDOptions | string} [loadOptions] Optional loading configuration or GitHub release tag. + */ +const loadGD = async (loadOptions) => { + fetchOptions = await getFetchConfiguration(loadOptions || {}); + return new Promise((resolve, reject) => { - const wgd = new WrappedGD(version); + const wgd = new WrappedGD(fetchOptions); wgd.once("ready", () => { wgd.removeAllListeners(); resolve(wgd);