From d61cffee48e5ed57c069db319bf72f63cf425aa6 Mon Sep 17 00:00:00 2001 From: Nicolas Kruk Date: Thu, 15 Aug 2024 18:51:12 -0400 Subject: [PATCH] feat: update sites locally after publish (#136) --- package.json | 1 + src/commands/lightning/dev/site.ts | 53 ++++----- src/shared/experience/expSite.ts | 180 +++++++++++++++++------------ src/shared/orgUtils.ts | 36 +----- src/shared/prompt.ts | 12 +- yarn.lock | 125 +++++++++++++++++++- 6 files changed, 262 insertions(+), 145 deletions(-) diff --git a/package.json b/package.json index 2decbf2..5bef9f6 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@salesforce/lwc-dev-mobile-core": "4.0.0-alpha.7", "@salesforce/sf-plugins-core": "^11.2.4", "@inquirer/select": "^2.4.7", + "@inquirer/prompts": "^5.3.8", "chalk": "^5.3.0", "lwc": "7.1.3", "lwr": "0.14.0", diff --git a/src/commands/lightning/dev/site.ts b/src/commands/lightning/dev/site.ts index f62f54f..ac6cb80 100644 --- a/src/commands/lightning/dev/site.ts +++ b/src/commands/lightning/dev/site.ts @@ -4,22 +4,16 @@ * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -// import fs from 'node:fs'; -// import path from 'node:path'; +import fs from 'node:fs'; import { SfCommand, Flags } from '@salesforce/sf-plugins-core'; import { Messages } from '@salesforce/core'; import { expDev } from '@lwrjs/api'; import { PromptUtils } from '../../../shared/prompt.js'; -// import { OrgUtils } from '../../../shared/orgUtils.js'; import { ExperienceSite } from '../../../shared/experience/expSite.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-lightning-dev', 'lightning.dev.site'); -export type LightningDevSiteResult = { - path: string; -}; - export default class LightningDevSite extends SfCommand { public static readonly summary = messages.getMessage('summary'); public static readonly description = messages.getMessage('description'); @@ -40,42 +34,42 @@ export default class LightningDevSite extends SfCommand { public async run(): Promise { const { flags } = await this.parse(LightningDevSite); - // TODO short circuit all this if user specifies a site name and it exists locally - try { - // 1. Connect to Org const org = flags['target-org']; let siteName = flags.name; - // 2. If we don't have a site to use, prompt the user for one + // If user doesn't specify a site, prompt the user for one if (!siteName) { - this.log('No site was specified'); - // Allow user to pick a site - const siteList = await ExperienceSite.getAllExpSites(org.getConnection()); - siteName = await PromptUtils.promptUserToSelectSite(siteList); + const allSites = await ExperienceSite.getAllExpSites(org); + siteName = await PromptUtils.promptUserToSelectSite(allSites); } - // 3. Setup local dev directory structure: '.localdev/${site}' - this.log(`Setting up Local Development for: ${siteName}`); const selectedSite = new ExperienceSite(org, siteName); - let siteZip; + let siteZip: string | undefined; + if (!selectedSite.isSiteSetup()) { - // TODO Verify the bundle has been published and download - this.log('Downloading Site...'); + this.log(`[local-dev] initializing: ${siteName}`); siteZip = await selectedSite.downloadSite(); } else { - // If we do have the site setup already, don't do anything / TODO prompt the user if they want to get latest? - // Check if the site has been published - // const result = await connection.query<{ Id: string; Name: string; LastModifiedDate: string }>( - // "SELECT Id, Name, LastModifiedDate FROM StaticResource WHERE Name LIKE 'MRT%" + siteName + "'" - // ); - // this.log('Setup already complete!'); + // If local-dev is already setup, check if an updated site has been published to download + const updateAvailable = await selectedSite.isUpdateAvailable(); + if (updateAvailable) { + const shouldUpdate = await PromptUtils.promptUserToConfirmUpdate(siteName); + if (shouldUpdate) { + this.log(`[local-dev] updating: ${siteName}`); + siteZip = await selectedSite.downloadSite(); + // delete oldSitePath recursive + const oldSitePath = selectedSite.getExtractDirectory(); + if (fs.existsSync(oldSitePath)) { + fs.rmdirSync(oldSitePath, { recursive: true }); + } + } + } } - // 6. Start the dev server - this.log('Starting local development server...'); + // Start the dev server await expDev({ - open: false, + open: true, port: 3000, logLevel: 'error', mode: 'dev', @@ -83,7 +77,6 @@ export default class LightningDevSite extends SfCommand { siteDir: selectedSite.getSiteDirectory(), }); } catch (e) { - // this.error(e); this.log('Local Development setup failed', e); } } diff --git a/src/shared/experience/expSite.ts b/src/shared/experience/expSite.ts index 9072487..38ea2b5 100644 --- a/src/shared/experience/expSite.ts +++ b/src/shared/experience/expSite.ts @@ -6,10 +6,20 @@ */ import fs from 'node:fs'; import path from 'node:path'; -import { Connection, Org, SfError } from '@salesforce/core'; +import { Org, SfError } from '@salesforce/core'; + +export type SiteMetadata = { + bundleName: string; + bundleLastModified: string; +}; + +export type SiteMetadataCache = { + [key: string]: SiteMetadata; +}; /** * Experience Site class. + * https://developer.salesforce.com/docs/platform/lwc/guide/get-started-test-components.html#enable-local-dev * * @param {string} siteName - The name of the experience site. * @param {string} status - The status of the experience site. @@ -19,19 +29,13 @@ import { Connection, Org, SfError } from '@salesforce/core'; export class ExperienceSite { public siteDisplayName: string; public siteName: string; - public status: string; - private org: Org; - private bundleName: string; - private bundleLastModified: string; + private metadataCache: SiteMetadataCache = {}; - public constructor(org: Org, siteName: string, status?: string, bundleName?: string, bundleLastModified?: string) { + public constructor(org: Org, siteName: string) { this.org = org; this.siteDisplayName = siteName.trim(); this.siteName = this.siteDisplayName.replace(' ', '_'); - this.status = status ?? ''; - this.bundleName = bundleName ?? ''; - this.bundleLastModified = bundleLastModified ?? ''; } /** @@ -45,7 +49,6 @@ export class ExperienceSite { * @returns */ public static getLocalExpSite(siteName: string): ExperienceSite { - // TODO cleanup const siteJsonPath = path.join('.localdev', siteName.trim().replace(' ', '_'), 'site.json'); const siteJson = fs.readFileSync(siteJsonPath, 'utf8'); const site = JSON.parse(siteJson) as ExperienceSite; @@ -67,14 +70,7 @@ export class ExperienceSite { // Example of creating ExperienceSite instances const experienceSites: ExperienceSite[] = result.records.map( - (record) => - new ExperienceSite( - org, - getSiteNameFromStaticResource(record.Name), - 'live', - record.Name, - record.LastModifiedDate - ) + (record) => new ExperienceSite(org, getSiteNameFromStaticResource(record.Name)) ); return experienceSites; @@ -86,8 +82,8 @@ export class ExperienceSite { * @param {Connection} conn - Salesforce connection object. * @returns {Promise} - List of experience sites. */ - public static async getAllExpSites(conn: Connection): Promise { - const result = await conn.query<{ + public static async getAllExpSites(org: Org): Promise { + const result = await org.getConnection().query<{ Id: string; Name: string; LastModifiedDate: string; @@ -98,42 +94,84 @@ export class ExperienceSite { return experienceSites; } - public isSiteSetup(): boolean { - return fs.existsSync(path.join(this.getExtractDirectory(), 'ssr.js')); + public async isUpdateAvailable(): Promise { + const localMetadata = this.getLocalMetadata(); + if (!localMetadata) { + return true; // If no local metadata, assume update is available + } + + const remoteMetadata = await this.getRemoteMetadata(); + if (!remoteMetadata) { + return false; // If no org bundle found, no update available + } + + return new Date(remoteMetadata.bundleLastModified) > new Date(localMetadata.bundleLastModified); } - public isSitePublished(): boolean { - // TODO + // Is the site extracted locally + public isSiteSetup(): boolean { return fs.existsSync(path.join(this.getExtractDirectory(), 'ssr.js')); } - public async getBundleName(): Promise { - if (!this.bundleName) { - await this.initBundle(); + // Is the static resource available on the server + public async isSitePublished(): Promise { + const remoteMetadata = await this.getRemoteMetadata(); + if (!remoteMetadata) { + return false; } - - return this.bundleName; + return true; } - public async getBundleLastModified(): Promise { - if (!this.bundleLastModified) { - await this.initBundle(); + // Is there a local gz file of the site + public isSiteDownloaded(): boolean { + const metadata = this.getLocalMetadata(); + if (!metadata) { + return false; } - return this.bundleLastModified; + return fs.existsSync(this.getSiteZipPath(metadata)); } - /** - * Save the site metadata to the file system. - */ - public save(): void { + public saveMetadata(metadata: SiteMetadata): void { const siteJsonPath = path.join(this.getSiteDirectory(), 'site.json'); - const siteJson = JSON.stringify(this, null, 4); - - // write out the site metadata - fs.mkdirSync(this.getSiteDirectory(), { recursive: true }); + const siteJson = JSON.stringify(metadata, null, 2); fs.writeFileSync(siteJsonPath, siteJson); } + public getLocalMetadata(): SiteMetadata | undefined { + if (this.metadataCache.localMetadata) return this.metadataCache.localMetadata; + const siteJsonPath = path.join(this.getSiteDirectory(), 'site.json'); + let siteJson; + if (fs.existsSync(siteJsonPath)) { + try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + siteJson = JSON.parse(fs.readFileSync(siteJsonPath, 'utf-8')) as SiteMetadata; + this.metadataCache.localMetadata = siteJson; + } catch (error) { + // eslint-disable-next-line no-console + console.error('Error reading site.json file', error); + } + } + return siteJson; + } + + public async getRemoteMetadata(): Promise { + if (this.metadataCache.remoteMetadata) return this.metadataCache.remoteMetadata; + const result = await this.org + .getConnection() + .query<{ Name: string; LastModifiedDate: string }>( + `SELECT Name, LastModifiedDate FROM StaticResource WHERE Name LIKE 'MRT_experience_%_${this.siteName}'` + ); + if (result.records.length === 0) { + return undefined; + } + const staticResource = result.records[0]; + this.metadataCache.remoteMetadata = { + bundleName: staticResource.Name, + bundleLastModified: staticResource.LastModifiedDate, + }; + return this.metadataCache.remoteMetadata; + } + /** * Get the local site directory path * @@ -147,47 +185,45 @@ export class ExperienceSite { return path.join('.localdev', this.siteName, 'app'); } + public getSiteZipPath(metadata: SiteMetadata): string { + const lastModifiedDate = new Date(metadata.bundleLastModified); + const timestamp = `${ + lastModifiedDate.getMonth() + 1 + }-${lastModifiedDate.getDate()}_${lastModifiedDate.getHours()}-${lastModifiedDate.getMinutes()}`; + const fileName = `${metadata.bundleName}_${timestamp}.gz`; + const resourcePath = path.join(this.getSiteDirectory(), fileName); + return resourcePath; + } + /** * Download and return the site resource bundle * * @returns path of downloaded site zip */ public async downloadSite(): Promise { - // 3a. Locate the site bundle - const bundleName = await this.getBundleName(); - - // 3b. Download the site from static resources - const resourcePath = path.join(this.getSiteDirectory(), `${bundleName}.gz`); - - // TODO configure redownloading - if (!fs.existsSync(resourcePath)) { - const staticresource = await this.org.getConnection().metadata.read('StaticResource', bundleName); - if (staticresource?.content) { - fs.mkdirSync(this.getSiteDirectory(), { recursive: true }); - // Save the static resource - const buffer = Buffer.from(staticresource.content, 'base64'); - // this.log(`Writing file to path: ${resourcePath}`); - fs.writeFileSync(resourcePath, buffer); - } else { - throw new SfError(`Error occured downloading your site: ${this.siteDisplayName}`); - } + const remoteMetadata = await this.getRemoteMetadata(); + if (!remoteMetadata) { + throw new SfError(`No published site found for: ${this.siteDisplayName}`); } - return resourcePath; - } - private async initBundle(): Promise { - const result = await this.org - .getConnection() - .query<{ Id: string; Name: string; LastModifiedDate: string }>( - "SELECT Id, Name, LastModifiedDate FROM StaticResource WHERE Name LIKE 'MRT_experience_%_" + this.siteName + "'" - ); - if (result.records.length === 0) { - throw new Error(`No experience site found for siteName: ${this.siteDisplayName}`); + // Download the site from static resources + // eslint-disable-next-line no-console + console.log('[local-dev] Downloading site...'); // TODO spinner + const resourcePath = this.getSiteZipPath(remoteMetadata); + const staticresource = await this.org.getConnection().metadata.read('StaticResource', remoteMetadata.bundleName); + if (staticresource?.content) { + // Save the static resource + fs.mkdirSync(this.getSiteDirectory(), { recursive: true }); + const buffer = Buffer.from(staticresource.content, 'base64'); + fs.writeFileSync(resourcePath, buffer); + + // Save the site's metadata + this.saveMetadata(remoteMetadata); + } else { + throw new SfError(`Error occurred downloading your site: ${this.siteDisplayName}`); } - const staticResource = result.records[0]; - this.bundleName = staticResource.Name; - this.bundleLastModified = staticResource.LastModifiedDate; + return resourcePath; } } diff --git a/src/shared/orgUtils.ts b/src/shared/orgUtils.ts index 76809ba..48b5664 100644 --- a/src/shared/orgUtils.ts +++ b/src/shared/orgUtils.ts @@ -5,7 +5,7 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { Connection, SfError } from '@salesforce/core'; +import { Connection } from '@salesforce/core'; export class OrgUtils { /** @@ -38,38 +38,4 @@ export class OrgUtils { return undefined; } - - public static async retrieveSites(conn: Connection): Promise { - const result = await conn.query<{ Name: string; UrlPathPrefix: string; SiteType: string; Status: string }>( - 'SELECT Name, UrlPathPrefix, SiteType, Status FROM Site' - ); - if (!result.records.length) { - throw new SfError('No sites found.'); - } - const siteNames = result.records.map((record) => record.Name).sort(); - return siteNames; - } - - /** - * Given a site name, it queries the org to find the matching site. - * - * @param connection the connection to the org - * @param siteName the name of the app - * @returns the site prefix or empty string if no match is found - */ - public static async getSitePathPrefix(connection: Connection, siteName: string): Promise { - // TODO seems like there are 2 copies of each site? ask about this - as the #1 is apended to our site type - const devNameQuery = `SELECT Id, Name, SiteType, UrlPathPrefix FROM Site WHERE Name LIKE '${siteName}1'`; - const result = await connection.query<{ UrlPathPrefix: string }>(devNameQuery); - if (result.totalSize > 0) { - return '/' + result.records[0].UrlPathPrefix; - } - return ''; - } - - public static async getDomains(connection: Connection): Promise { - const devNameQuery = 'SELECT Id, Domain, LastModifiedDate FROM Domain'; - const results = await connection.query<{ Domain: string }>(devNameQuery); - return results.records.map((result) => result.Domain); - } } diff --git a/src/shared/prompt.ts b/src/shared/prompt.ts index 3cc60b7..dc0689c 100644 --- a/src/shared/prompt.ts +++ b/src/shared/prompt.ts @@ -5,6 +5,7 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ import select from '@inquirer/select'; +import { confirm } from '@inquirer/prompts'; export class PromptUtils { public static async promptUserToSelectSite(sites: string[]): Promise { @@ -17,13 +18,10 @@ export class PromptUtils { return response; } - public static async promptUserToSelectDomain(domains: string[]): Promise { - const choices = domains.map((domain) => ({ value: domain })); - const response = await select({ - message: 'Select a Domain:', - choices, + public static async promptUserToConfirmUpdate(siteName: string): Promise { + return confirm({ + message: `An updated site bundle is available for "${siteName}". Would you like to download and apply the update?`, + default: true, }); - - return response; } } diff --git a/yarn.lock b/yarn.lock index 62204f1..8d984d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2326,6 +2326,17 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== +"@inquirer/checkbox@^2.4.7": + version "2.4.7" + resolved "https://registry.yarnpkg.com/@inquirer/checkbox/-/checkbox-2.4.7.tgz#0a2867a3a8c5853c79e43e99634e80c1721934ca" + integrity sha512-5YwCySyV1UEgqzz34gNsC38eKxRBtlRDpJLlKcRtTjlYA/yDKuc1rfw+hjw+2WJxbAZtaDPsRl5Zk7J14SBoBw== + dependencies: + "@inquirer/core" "^9.0.10" + "@inquirer/figures" "^1.0.5" + "@inquirer/type" "^1.5.2" + ansi-escapes "^4.3.2" + yoctocolors-cjs "^2.1.2" + "@inquirer/confirm@^3.1.14", "@inquirer/confirm@^3.1.16", "@inquirer/confirm@^3.1.17": version "3.1.18" resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-3.1.18.tgz#e94316c1ff63890841db171be5910d1c40b98435" @@ -2334,6 +2345,14 @@ "@inquirer/core" "^9.0.6" "@inquirer/type" "^1.5.1" +"@inquirer/confirm@^3.1.22": + version "3.1.22" + resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-3.1.22.tgz#23990624c11f60c6f7a5b0558c7505c35076a037" + integrity sha512-gsAKIOWBm2Q87CDfs9fEo7wJT3fwWIJfnDGMn9Qy74gBnNFOACDNfhUzovubbJjWnKLGBln7/NcSmZwj5DuEXg== + dependencies: + "@inquirer/core" "^9.0.10" + "@inquirer/type" "^1.5.2" + "@inquirer/core@^9.0.10", "@inquirer/core@^9.0.6": version "9.0.10" resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-9.0.10.tgz#4270191e2ad3bea6223530a093dd9479bcbc7dd0" @@ -2353,6 +2372,24 @@ wrap-ansi "^6.2.0" yoctocolors-cjs "^2.1.2" +"@inquirer/editor@^2.1.22": + version "2.1.22" + resolved "https://registry.yarnpkg.com/@inquirer/editor/-/editor-2.1.22.tgz#f97eda20954da1dab47df9f4c3ae11604d56360c" + integrity sha512-K1QwTu7GCK+nKOVRBp5HY9jt3DXOfPGPr6WRDrPImkcJRelG9UTx2cAtK1liXmibRrzJlTWOwqgWT3k2XnS62w== + dependencies: + "@inquirer/core" "^9.0.10" + "@inquirer/type" "^1.5.2" + external-editor "^3.1.0" + +"@inquirer/expand@^2.1.22": + version "2.1.22" + resolved "https://registry.yarnpkg.com/@inquirer/expand/-/expand-2.1.22.tgz#7593e93a516a49434629c41f3738479c8234d2df" + integrity sha512-wTZOBkzH+ItPuZ3ZPa9lynBsdMp6kQ9zbjVPYEtSBG7UulGjg2kQiAnUjgyG4SlntpTce5bOmXAPvE4sguXjpA== + dependencies: + "@inquirer/core" "^9.0.10" + "@inquirer/type" "^1.5.2" + yoctocolors-cjs "^2.1.2" + "@inquirer/figures@^1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.5.tgz#57f9a996d64d3e3345d2a3ca04d36912e94f8790" @@ -2366,6 +2403,22 @@ "@inquirer/core" "^9.0.6" "@inquirer/type" "^1.5.1" +"@inquirer/input@^2.2.9": + version "2.2.9" + resolved "https://registry.yarnpkg.com/@inquirer/input/-/input-2.2.9.tgz#08fdf9a48e4f6fc64c2d508b9d10afac843f9bd8" + integrity sha512-7Z6N+uzkWM7+xsE+3rJdhdG/+mQgejOVqspoW+w0AbSZnL6nq5tGMEVASaYVWbkoSzecABWwmludO2evU3d31g== + dependencies: + "@inquirer/core" "^9.0.10" + "@inquirer/type" "^1.5.2" + +"@inquirer/number@^1.0.10": + version "1.0.10" + resolved "https://registry.yarnpkg.com/@inquirer/number/-/number-1.0.10.tgz#ac2b440ca57b5de5a231e4898c12d4453683c055" + integrity sha512-kWTxRF8zHjQOn2TJs+XttLioBih6bdc5CcosXIzZsrTY383PXI35DuhIllZKu7CdXFi2rz2BWPN9l0dPsvrQOA== + dependencies: + "@inquirer/core" "^9.0.10" + "@inquirer/type" "^1.5.2" + "@inquirer/password@^2.1.18": version "2.1.18" resolved "https://registry.yarnpkg.com/@inquirer/password/-/password-2.1.18.tgz#7d204649b65ed3094508ba34211eedce0d1307fb" @@ -2375,6 +2428,50 @@ "@inquirer/type" "^1.5.1" ansi-escapes "^4.3.2" +"@inquirer/password@^2.1.22": + version "2.1.22" + resolved "https://registry.yarnpkg.com/@inquirer/password/-/password-2.1.22.tgz#ec7ee5709923cf285b3e0ae53eed4fdc3c05b422" + integrity sha512-5Fxt1L9vh3rAKqjYwqsjU4DZsEvY/2Gll+QkqR4yEpy6wvzLxdSgFhUcxfDAOtO4BEoTreWoznC0phagwLU5Kw== + dependencies: + "@inquirer/core" "^9.0.10" + "@inquirer/type" "^1.5.2" + ansi-escapes "^4.3.2" + +"@inquirer/prompts@^5.3.8": + version "5.3.8" + resolved "https://registry.yarnpkg.com/@inquirer/prompts/-/prompts-5.3.8.tgz#f394050d95076c2f1b046be324f06f619b257c3e" + integrity sha512-b2BudQY/Si4Y2a0PdZZL6BeJtl8llgeZa7U2j47aaJSCeAl1e4UI7y8a9bSkO3o/ZbZrgT5muy/34JbsjfIWxA== + dependencies: + "@inquirer/checkbox" "^2.4.7" + "@inquirer/confirm" "^3.1.22" + "@inquirer/editor" "^2.1.22" + "@inquirer/expand" "^2.1.22" + "@inquirer/input" "^2.2.9" + "@inquirer/number" "^1.0.10" + "@inquirer/password" "^2.1.22" + "@inquirer/rawlist" "^2.2.4" + "@inquirer/search" "^1.0.7" + "@inquirer/select" "^2.4.7" + +"@inquirer/rawlist@^2.2.4": + version "2.2.4" + resolved "https://registry.yarnpkg.com/@inquirer/rawlist/-/rawlist-2.2.4.tgz#73d5d4fafa2ca012e6cfb9eb1d8ddf33bab2dde0" + integrity sha512-pb6w9pWrm7EfnYDgQObOurh2d2YH07+eDo3xQBsNAM2GRhliz6wFXGi1thKQ4bN6B0xDd6C3tBsjdr3obsCl3Q== + dependencies: + "@inquirer/core" "^9.0.10" + "@inquirer/type" "^1.5.2" + yoctocolors-cjs "^2.1.2" + +"@inquirer/search@^1.0.7": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@inquirer/search/-/search-1.0.7.tgz#72ab9ccfb57f05dd81a8b2df26214588e048be18" + integrity sha512-p1wpV+3gd1eST/o5N3yQpYEdFNCzSP0Klrl+5bfD3cTTz8BGG6nf4Z07aBW0xjlKIj1Rp0y3x/X4cZYi6TfcLw== + dependencies: + "@inquirer/core" "^9.0.10" + "@inquirer/figures" "^1.0.5" + "@inquirer/type" "^1.5.2" + yoctocolors-cjs "^2.1.2" + "@inquirer/select@^2.3.10", "@inquirer/select@^2.4.7": version "2.4.7" resolved "https://registry.yarnpkg.com/@inquirer/select/-/select-2.4.7.tgz#6a23742b4f76d228186dfd42571d973def378ffa" @@ -6619,6 +6716,11 @@ character-reference-invalid@^1.0.0: resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + check-error@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694" @@ -8364,6 +8466,15 @@ extend@^3.0.0, extend@^3.0.2: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== +external-editor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + extglob@^2.0.2, extglob@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" @@ -9503,7 +9614,7 @@ husky@^7.0.4: resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535" integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ== -iconv-lite@0.4.24: +iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -12360,6 +12471,11 @@ optionator@^0.9.3: type-check "^0.4.0" word-wrap "^1.2.5" +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + p-cancelable@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-3.0.0.tgz#63826694b54d61ca1c20ebcb6d3ecf5e14cd8050" @@ -14526,6 +14642,13 @@ tiny-jsonc@^1.0.1: resolved "https://registry.yarnpkg.com/tiny-jsonc/-/tiny-jsonc-1.0.1.tgz#71de47c9d812b411e87a9f3ab4a5fe42cd8d8f9c" integrity sha512-ik6BCxzva9DoiEfDX/li0L2cWKPPENYvixUprFdl3YPi4bZZUhDnNI9YUkacrv+uIG90dnxR5mNqaoD6UhD6Bw== +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"