From f65e683cda317098611dd6ef65a0f6815c85c56e Mon Sep 17 00:00:00 2001 From: Joel Mut Date: Thu, 12 Dec 2024 16:03:22 -0300 Subject: [PATCH] changes --- libraries/botbuilder-dialogs/package.json | 1 - .../botbuilder-repo-utils/src/package.ts | 5 + libraries/botbuilder-vendors/README.md | 3 + libraries/botbuilder-vendors/package.json | 5 +- .../botbuilder-vendors/src/actions/build.ts | 66 ++++ .../botbuilder-vendors/src/actions/index.ts | 12 + .../botbuilder-vendors/src/actions/install.ts | 45 +++ libraries/botbuilder-vendors/src/index.ts | 315 +----------------- .../botbuilder-vendors/src/utils/colors.ts | 13 + .../botbuilder-vendors/src/utils/failures.ts | 21 ++ .../src/utils/formatting.ts | 13 + .../botbuilder-vendors/src/utils/index.ts | 7 + .../botbuilder-vendors/src/utils/logger.ts | 80 +++++ libraries/botbuilder-vendors/src/vendors.ts | 76 +++++ package.json | 2 +- 15 files changed, 358 insertions(+), 306 deletions(-) create mode 100644 libraries/botbuilder-vendors/README.md create mode 100644 libraries/botbuilder-vendors/src/actions/build.ts create mode 100644 libraries/botbuilder-vendors/src/actions/index.ts create mode 100644 libraries/botbuilder-vendors/src/actions/install.ts create mode 100644 libraries/botbuilder-vendors/src/utils/colors.ts create mode 100644 libraries/botbuilder-vendors/src/utils/failures.ts create mode 100644 libraries/botbuilder-vendors/src/utils/formatting.ts create mode 100644 libraries/botbuilder-vendors/src/utils/index.ts create mode 100644 libraries/botbuilder-vendors/src/utils/logger.ts create mode 100644 libraries/botbuilder-vendors/src/vendors.ts diff --git a/libraries/botbuilder-dialogs/package.json b/libraries/botbuilder-dialogs/package.json index 837834d9a5..b26290474e 100644 --- a/libraries/botbuilder-dialogs/package.json +++ b/libraries/botbuilder-dialogs/package.json @@ -41,7 +41,6 @@ "line-reader": "^0.4.0" }, "localDependencies": { - "@microsoft/recognizers-text-choice2": "1.1.4", "@microsoft/recognizers-text-choice": "1.1.4", "@microsoft/recognizers-text-date-time": "1.1.4", "@microsoft/recognizers-text-number": "1.1.4", diff --git a/libraries/botbuilder-repo-utils/src/package.ts b/libraries/botbuilder-repo-utils/src/package.ts index e54f61b7ad..c6548b5cf7 100644 --- a/libraries/botbuilder-repo-utils/src/package.ts +++ b/libraries/botbuilder-repo-utils/src/package.ts @@ -21,3 +21,8 @@ export interface Package { scripts?: Record; } + +export interface Dependency { + name: string; + version: string; +} \ No newline at end of file diff --git a/libraries/botbuilder-vendors/README.md b/libraries/botbuilder-vendors/README.md new file mode 100644 index 0000000000..3932348816 --- /dev/null +++ b/libraries/botbuilder-vendors/README.md @@ -0,0 +1,3 @@ +TODO: +- Explain how it works, now to add a new vendor, how to connect it with the rest of botbuilder libraries, etc. +- Add unit tests. \ No newline at end of file diff --git a/libraries/botbuilder-vendors/package.json b/libraries/botbuilder-vendors/package.json index 886b14c40a..8bc0666fb7 100644 --- a/libraries/botbuilder-vendors/package.json +++ b/libraries/botbuilder-vendors/package.json @@ -11,9 +11,8 @@ "url": "https://github.com/Microsoft/botbuilder-js.git" }, "scripts": { - "postinstall": "ts-node src/index.ts install", - "postbuild": "ts-node src/index.ts build", - "lint": "eslint ." + "lint": "eslint .", + "connect": "ts-node src/index.ts connect" }, "dependencies": { "fast-glob": "^3.3.2", diff --git a/libraries/botbuilder-vendors/src/actions/build.ts b/libraries/botbuilder-vendors/src/actions/build.ts new file mode 100644 index 0000000000..5b75a1a7f8 --- /dev/null +++ b/libraries/botbuilder-vendors/src/actions/build.ts @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import path from 'path'; +import { readFile, writeFile } from 'fs/promises'; +import { readJsonFile } from 'botbuilder-repo-utils/src/file'; +import { glob } from 'fast-glob'; +import { logger } from '../utils'; + +export async function build({ dir, vendors, location }: any) { + if (vendors.length === 0) { + logger.package.compilation.header({ files: 0 }); + return; + } + + const tsconfig = await readJsonFile(path.join(dir, 'tsconfig.json')); + const configDir = tsconfig.compilerOptions.outDir; + const outDir = path.resolve(dir, configDir); + const files = await glob(`**/*.js`, { cwd: outDir }); + + // Find and replace all vendor references in the compiled files + const references: Record = {}; + for (let i = 0; i < files.length; i++) { + const file = files[i]; + const filePath = path.join(outDir, file); + const content = await readFile(filePath, 'utf8'); + + for (const vendor of vendors) { + const vendorDir = path.join(dir, location, path.basename(vendor.dir)); + const relative = path.relative(path.dirname(filePath), vendorDir).split(path.sep).join('/'); + const from = `require("${vendor.name}")`; + const to = `require("${relative}")`; + if (!content.includes(from)) { + continue; + } + const line = content.split('\n').findIndex((line) => line.includes(from)) + 1; + references[file] ??= []; + references[file].push({ from, to, line }); + const newContent = content.replace(from, to); + await writeFile(filePath, newContent, 'utf8'); + } + } + + // Log the replaced references + const entries = Object.entries(references); + logger.package.compilation.header({ files: entries.length }); + for (let i = 0; i < entries.length; i++) { + const [file, refs] = entries[i]; + logger.package.compilation.file.header({ + isLast: i === entries.length - 1, + dir: configDir, + file, + references: refs.length, + }); + for (let j = 0; j < refs.length; j++) { + const { line, from, to } = refs[j]; + logger.package.compilation.file.reference({ + isLast: j === refs.length - 1, + isLastParent: i === entries.length - 1, + line, + from, + to, + }); + } + } +} diff --git a/libraries/botbuilder-vendors/src/actions/index.ts b/libraries/botbuilder-vendors/src/actions/index.ts new file mode 100644 index 0000000000..345b40e964 --- /dev/null +++ b/libraries/botbuilder-vendors/src/actions/index.ts @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export * from './build'; +export * from './install'; + +export const actions = { + supported: ['connect'], + valid(value: string) { + return actions.supported.includes(value); + }, +}; diff --git a/libraries/botbuilder-vendors/src/actions/install.ts b/libraries/botbuilder-vendors/src/actions/install.ts new file mode 100644 index 0000000000..c74f0695e5 --- /dev/null +++ b/libraries/botbuilder-vendors/src/actions/install.ts @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import path from 'path'; +import { execSync } from 'child_process'; +import { existsSync } from 'fs'; +import { copyFile, mkdir } from 'fs/promises'; +import { logger } from '../utils'; + +export async function install({ vendors, dependencies, dir, location }: any) { + for (let i = 0; i < vendors.length; i++) { + const vendor = vendors[i]; + + if (!vendor.dir) { + logger.package.vendors.vendor({ + isLast: i === vendors.length - 1, + name: vendor.name, + version: vendor.version, + isUnknown: true, + }); + continue; + } + + const source = path.join(vendor.dir, vendor.main); + const vendorDir = path.join(dir, location, path.basename(vendor.dir)); + const destination = path.join(vendorDir, vendor.main); + + if (!existsSync(vendorDir)) { + await mkdir(vendorDir, { recursive: true }); + } + + logger.package.vendors.vendor({ isLast: i === vendors.length - 1, name: vendor.name, version: vendor.version }); + await copyFile(source, destination); + } + + logger.package.dependencies.header({ dependencies: dependencies.length }); + for (let i = 0; i < dependencies.length; i++) { + const { name, version } = dependencies[i]; + logger.package.dependencies.dependency({ isLast: i === dependencies.length - 1, name, version }); + if (process.env.GITHUB_ACTIONS === 'true') { + // Only modify package.json if running in GitHub Actions, preventing changes to local files and pushing them back to the repository. + execSync(`npm pkg set dependencies["${name}"]="${version}"`, { cwd: dir }); + } + } +} diff --git a/libraries/botbuilder-vendors/src/index.ts b/libraries/botbuilder-vendors/src/index.ts index 0076e613d8..ae4e5c66c8 100644 --- a/libraries/botbuilder-vendors/src/index.ts +++ b/libraries/botbuilder-vendors/src/index.ts @@ -1,311 +1,30 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { execSync } from 'child_process'; import path from 'path'; import { existsSync } from 'fs'; -import { mkdir, readFile, writeFile, copyFile } from 'fs/promises'; -import glob from 'fast-glob'; import minimist from 'minimist'; import { Package } from 'botbuilder-repo-utils/src/package'; import { collectWorkspacePackages } from 'botbuilder-repo-utils/src/workspace'; import { failure, run, success } from 'botbuilder-repo-utils/src/run'; import { gitRoot } from 'botbuilder-repo-utils/src/git'; import { readJsonFile } from 'botbuilder-repo-utils/src/file'; +import { install, build, actions } from './actions'; +import { logger, failures } from './utils'; +import { findAll, findByPackage } from './vendors'; const VENDORS_DIR = 'libraries/botbuilder-vendors/vendors'; -const IS_GITHUB_ACTIONS = process.env.GITHUB_ACTIONS === 'true'; - -interface Vendor extends Package { - dir: string; -} - -interface Dependency { - name: string; - version: string; -} - -const colors = { - reset: '\x1b[0m', - dim: '\x1b[2m', - red: '\x1b[31m', - green: '\x1b[32m', - yellow: '\x1b[33m', - blue: '\x1b[34m', - magenta: '\x1b[35m', - cyan: '\x1b[36m', -}; - -const actions = { - install: 'install', - build: 'build', -}; - -function plural(n: number, text: string, plural: string = 's') { - return `${text}${n === 1 ? '' : plural}`; -} - -function padLeft(text: string) { - return text - .split('\n') - .map((line) => line.trim()) - .join('\n'); -} - -const logs = { - summary({ action, vendors, workspaces }: any) { - console.log( - padLeft(` - Connecting vendors to workspaces... - - ${colors.blue}summary${colors.reset} - ---------------------- - action : ${colors.magenta}${action}${colors.reset} - vendors : ${colors.magenta}${vendors} ${plural(vendors, 'package')}${colors.reset} - workspaces: ${colors.magenta}${workspaces} ${plural(workspaces, 'package')}${colors.reset} - `), - ); - }, - package: { - header({ name }: any) { - console.log(`${colors.blue}${name} ${colors.cyan}[workspace]${colors.reset}`); - }, - footer() { - console.log(`└─ ${colors.dim}done${colors.reset}\r\n`); - }, - require: { - header({ files }: any) { - const tags = files > 0 ? [`${colors.green}[replaced]`] : [`${colors.red}[not found]`]; - console.log( - `├─ require statements: ${colors.magenta}${files} ${plural(files, 'file')} ${tags.join('')}${colors.reset}`, - ); - }, - file: { - header({ isLast, dir, file, references }: any) { - const prefix = isLast ? '└─' : '├─'; - console.log( - `│ ${prefix} ${colors.cyan}${dir}/${file}: ${colors.magenta}${references} ${plural(references, 'reference')}${colors.reset}`, - ); - }, - reference({ isLast, line, from, to }: any) { - const prefix = isLast ? '└─' : '├─'; - console.log( - `│ │ ${prefix} ${colors.dim}line:${line} | ${colors.red}${from}${colors.reset} ${colors.dim}-> ${colors.green}${to}${colors.reset}`, - ); - }, - }, - }, - vendors: { - header({ vendors }: any) { - const tags = vendors > 0 ? [`${colors.green}[linked]`] : []; - console.log( - `├─ vendors: ${colors.magenta}${vendors} ${plural(vendors, 'package')} ${tags.join('')}${colors.reset}`, - ); - }, - vendor({ isLast, name, version }: any) { - const prefix = isLast ? '└─' : '├─'; - console.log(`│ ${prefix} ${colors.dim}${name}@${colors.cyan}${version}${colors.reset}`); - }, - }, - dependencies: { - header({ dependencies }: any) { - console.log( - `├─ dependencies: ${colors.magenta}${dependencies} ${plural(dependencies, 'package')} ${colors.green}[added]${colors.reset}`, - ); - }, - dependency({ isLast, name, version }: any) { - const prefix = isLast ? '└─' : '├─'; - console.log(`│ ${prefix} ${colors.dim}${name}@${colors.cyan}${version}${colors.reset}`); - }, - }, - unknown: { - vendors: { - header({ vendors }: any) { - console.log( - `├─ unknown vendors: ${colors.magenta}${vendors.length} ${plural(vendors.length, 'package')} ${colors.red}[not found]${colors.reset}`, - ); - for (let i = 0; i < vendors.length; i++) { - const { name, version } = vendors[i]; - logs.package.unknown.vendors.vendor({ isLast: i === vendors.length - 1, name, version }); - } - }, - vendor({ isLast, name, version }: any) { - const prefix = isLast ? '└─' : '├─'; - console.log(`│ ${prefix} ${colors.dim}${name}@${colors.cyan}${version}${colors.reset}`); - }, - }, - }, - }, -}; - -const failures = { - validAction() { - return new Error(`Please provide a valid action: ${Object.values(actions).join(' or ')}`); - }, - packageJsonNotFound(pkgPath: string) { - return new Error(`Unable to find package.json file at ${pkgPath}`); - }, - packageJsonNotFoundWithWorkspaces(pkgPath: string) { - return new Error(`Unable to find package.json file with workspaces at ${pkgPath}`); - }, -}; - -async function collectVendors(gitRoot: string): Promise { - const dir = path.resolve(gitRoot, VENDORS_DIR); - const packages = await glob('**/package.json', { cwd: dir }); - - if (packages.length === 0) { - throw new Error(`Unable to find vendor packages under '${dir}' folder`); - } - - const promises = packages.map(async (file) => { - const pkgPath = path.join(dir, file); - const pkg = await readJsonFile(pkgPath); - if (!pkg) { - throw new Error(`Unable to load ${pkgPath}. Please provide a valid package.json.`); - } - - return { - ...pkg, - dir: path.dirname(pkgPath), - }; - }); - return Promise.all(promises); -} - -async function getConnectedVendors(pkg: Package, vendors: Vendor[]) { - const result: { vendors: Vendor[]; dependencies: Dependency[]; unknown: Dependency[] } = { - vendors: [], - dependencies: [], - unknown: [], - }; - - async function inner(pkg: Package, memo: Set = new Set()) { - const localDependencies = Object.keys(pkg.localDependencies || {}); - for (const name of localDependencies) { - if (memo.has(name)) { - continue; - } - const vendor = vendors.find((vendor) => vendor.name === name); - if (!vendor) { - result.unknown.push({ name, version: pkg.localDependencies![name] }); - continue; - } - memo.add(vendor.name); - result.vendors.push(vendor); - - if (vendor.localDependencies) { - await inner(vendor, memo); - } - - if (vendor.dependencies) { - for (const [name, version] of Object.entries(vendor.dependencies)) { - if (memo.has(name)) { - continue; - } - memo.add(name); - result.dependencies.push({ name, version }); - } - } - } - } - - await inner(pkg); - - return result; -} - -async function build({ dir, vendors, location }: any) { - logs.package.vendors.header({ vendors: vendors.length }); - - if (vendors.length === 0) { - return; - } - - const tsconfig = await readJsonFile(path.join(dir, 'tsconfig.json')); - const configDir = tsconfig.compilerOptions.outDir; - const outDir = path.resolve(dir, configDir); - const files = await glob(`**/*.js`, { cwd: outDir }); - - const references: Record = {}; - for (const file of files) { - const filePath = path.join(outDir, file); - const content = await readFile(filePath, 'utf8'); - for (const vendor of vendors) { - const vendorDir = path.join(dir, location, path.basename(vendor.dir)); - const relative = path.relative(path.dirname(filePath), vendorDir).split(path.sep).join('/'); - const from = `require("${vendor.name}")`; - const to = `require("${relative}")`; - if (!content.includes(from)) { - continue; - } - const line = content.split('\n').findIndex((line) => line.includes(from)) + 1; - references[file] ??= []; - references[file].push({ from, to, line }); - const newContent = content.replace(from, to); - await writeFile(filePath, newContent, 'utf8'); - } - } - - const entries = Object.entries(references); - logs.package.require.header({ files: entries.length }); - for (let i = 0; i < entries.length; i++) { - const [file, refs] = entries[i]; - logs.package.require.file.header({ - isLast: i === entries.length - 1, - dir: outDir, - file, - references: refs.length, - }); - for (let j = 0; j < refs.length; j++) { - const ref = refs[j]; - logs.package.require.file.reference({ - isLast: i === entries.length - 1, - line: ref.line, - from: ref.from, - to: ref.to, - }); - } - } -} - -async function install({ vendors, dependencies, dir, location }: any) { - logs.package.vendors.header({ vendors: vendors.length }); - for (let i = 0; i < vendors.length; i++) { - const vendor = vendors[i]; - const source = path.join(vendor.dir, vendor.main); - const vendorDir = path.join(dir, location, path.basename(vendor.dir)); - const destination = path.join(vendorDir, vendor.main); - - if (!existsSync(vendorDir)) { - await mkdir(vendorDir, { recursive: true }); - } - - logs.package.vendors.vendor({ isLast: i === vendors.length - 1, name: vendor.name, version: vendor.version }); - await copyFile(source, destination); - } - - logs.package.dependencies.header({ dependencies: dependencies.length }); - for (let i = 0; i < dependencies.length; i++) { - const { name, version } = dependencies[i]; - logs.package.dependencies.dependency({ isLast: i === dependencies.length - 1, name, version }); - if (IS_GITHUB_ACTIONS) { - // Only modify package.json if running in GitHub Actions. - execSync(`npm pkg set dependencies["${name}"]="${version}"`, { cwd: dir }); - } - } -} export const command = (argv: string[]) => async () => { try { const flags = minimist(argv); const action = flags._[0]; - if (!action) { - throw failures.validAction(); + if (!actions.valid(action)) { + throw failures.actionNotFound(action); } const rootDir = await gitRoot(); - const globalVendors = await collectVendors(rootDir); + const allVendors = await findAll(path.resolve(rootDir, VENDORS_DIR)); const pkgPath = path.join(rootDir, 'package.json'); if (!existsSync(pkgPath)) { @@ -322,30 +41,24 @@ export const command = (argv: string[]) => async () => { ignorePath: [`**/${VENDORS_DIR}/**/*`], }); - logs.summary({ action, vendors: globalVendors.length, workspaces: workspaces.length }); + logger.summary({ action, vendors: allVendors.length, workspaces: workspaces.length }); for (const { pkg, absPath } of workspaces) { - logs.package.header({ name: pkg.name }); + logger.package.header({ name: pkg.name }); const dir = path.dirname(absPath); const location = pkg.localDependencies!.__directory ?? 'vendors'; delete pkg.localDependencies!.__directory; - const { vendors, dependencies, unknown } = await getConnectedVendors(pkg, globalVendors); - - if (unknown.length > 0) { - logs.package.unknown.vendors.header({ vendors: unknown }); - } + const { vendors, dependencies, unknown } = await findByPackage(pkg, allVendors); + const newVendors = [...unknown, ...vendors]; - if (action === actions.build) { - await build({ dir, vendors, location }); - } + logger.package.vendors.header({ vendors: newVendors.length }); - if (action === actions.install) { - await install({ vendors, dependencies, dir, location }); - } + await install({ vendors: newVendors, dependencies, dir, location }); + await build({ dir, vendors, location }); - logs.package.footer(); + logger.package.footer(); } return success(); diff --git a/libraries/botbuilder-vendors/src/utils/colors.ts b/libraries/botbuilder-vendors/src/utils/colors.ts new file mode 100644 index 0000000000..36575d03d7 --- /dev/null +++ b/libraries/botbuilder-vendors/src/utils/colors.ts @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export const colors = { + reset: '\x1b[0m', + dim: '\x1b[2m', + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + magenta: '\x1b[35m', + cyan: '\x1b[36m', +}; diff --git a/libraries/botbuilder-vendors/src/utils/failures.ts b/libraries/botbuilder-vendors/src/utils/failures.ts new file mode 100644 index 0000000000..4d77b114ca --- /dev/null +++ b/libraries/botbuilder-vendors/src/utils/failures.ts @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { actions } from '../actions'; + +export const failures = { + actionNotFound(action: string) { + return new Error( + `Unable to use the '${action}' action. Supported actions are ${actions.supported.join(' or ')}`, + ); + }, + packageJsonNotFound(pkgPath: string) { + return new Error(`Unable to find package.json file at ${pkgPath}`); + }, + packageJsonNotFoundWithWorkspaces(pkgPath: string) { + return new Error(`Unable to find package.json file with workspaces at ${pkgPath}`); + }, + vendorPackagesNotFound(dir: string) { + return new Error(`Unable to find vendor packages in ${dir}`); + }, +}; diff --git a/libraries/botbuilder-vendors/src/utils/formatting.ts b/libraries/botbuilder-vendors/src/utils/formatting.ts new file mode 100644 index 0000000000..c9fb898268 --- /dev/null +++ b/libraries/botbuilder-vendors/src/utils/formatting.ts @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export function plural(n: number, text: string, plural: string = 's') { + return `${text}${n === 1 ? '' : plural}`; +} + +export function padLeft(text: string) { + return text + .split('\n') + .map((line) => line.trim()) + .join('\n'); +} \ No newline at end of file diff --git a/libraries/botbuilder-vendors/src/utils/index.ts b/libraries/botbuilder-vendors/src/utils/index.ts new file mode 100644 index 0000000000..06b015a734 --- /dev/null +++ b/libraries/botbuilder-vendors/src/utils/index.ts @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export * from './colors'; +export * from './failures'; +export * from './formatting'; +export * from './logger'; diff --git a/libraries/botbuilder-vendors/src/utils/logger.ts b/libraries/botbuilder-vendors/src/utils/logger.ts new file mode 100644 index 0000000000..fe68b40cd5 --- /dev/null +++ b/libraries/botbuilder-vendors/src/utils/logger.ts @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { colors } from './colors'; +import { padLeft, plural } from './formatting'; + +export const logger = { + summary({ action, vendors, workspaces }: any) { + console.log( + padLeft(` + Connecting vendors to workspaces... + + ${colors.blue}summary${colors.reset} + ---------------------- + action : ${colors.magenta}${action}${colors.reset} + vendors : ${colors.magenta}${vendors} ${plural(vendors, 'package')}${colors.reset} + workspaces: ${colors.magenta}${workspaces} ${plural(workspaces, 'package')}${colors.reset} + `), + ); + }, + package: { + header({ name }: any) { + console.log(`${colors.blue}${name} ${colors.cyan}[workspace]${colors.reset}`); + }, + footer() { + console.log(`└─ ${colors.dim}done${colors.reset}\r\n`); + }, + compilation: { + header({ files }: any) { + const tags = files > 0 ? [`${colors.green}[replaced]`] : [`${colors.red}[not found]`]; + console.log( + `├─ compilation: ${colors.magenta}${files} ${plural(files, 'file')} ${tags.join('')}${colors.reset}`, + ); + }, + file: { + header({ isLast, dir, file, references }: any) { + const prefix = isLast ? '└─' : '├─'; + console.log( + `│ ${prefix} ${colors.cyan}${dir}/${file}: ${colors.magenta}${references} ${plural(references, 'reference')}${colors.reset}`, + ); + }, + reference({ isLast, isLastParent, line, from, to }: any) { + const prefix = isLast ? '└─' : '├─'; + const prefixParent = isLastParent ? ' ' : '│'; + console.log( + `│ ${prefixParent} ${prefix} ${colors.dim}line:${line} | ${colors.red}${from}${colors.reset} ${colors.dim}-> ${colors.green}${to}${colors.reset}`, + ); + }, + }, + }, + vendors: { + header({ vendors }: any) { + const tags = vendors > 0 ? [`${colors.green}[linked]`] : [`${colors.red}[not found]`]; + console.log( + `├─ vendors: ${colors.magenta}${vendors} ${plural(vendors, 'package')} ${tags.join('')}${colors.reset}`, + ); + }, + vendor({ isLast, name, version, isUnknown }: any) { + const unknown = isUnknown ? `${colors.yellow}[unknown]` : ''; + console.log(packageItem({ isLast, name, version }), unknown, colors.reset); + }, + }, + dependencies: { + header({ dependencies }: any) { + const tags = dependencies > 0 ? [`${colors.green}[added]`] : [`${colors.red}[not found]`]; + console.log( + `├─ dependencies: ${colors.magenta}${dependencies} ${plural(dependencies, 'package')} ${tags.join('')}${colors.reset}`, + ); + }, + dependency({ isLast, name, version }: any) { + console.log(packageItem({ isLast, name, version })); + }, + }, + }, +}; + +function packageItem({ isLast, name, version }: any) { + const prefix = isLast ? '└─' : '├─'; + return `│ ${prefix} ${colors.dim}${name}@${colors.cyan}${version}${colors.reset}`; +} diff --git a/libraries/botbuilder-vendors/src/vendors.ts b/libraries/botbuilder-vendors/src/vendors.ts new file mode 100644 index 0000000000..1717f98e62 --- /dev/null +++ b/libraries/botbuilder-vendors/src/vendors.ts @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import path from 'path'; +import { glob } from 'fast-glob'; +import { readJsonFile } from 'botbuilder-repo-utils/src/file'; +import { Dependency, Package } from 'botbuilder-repo-utils/src/package'; +import { failures } from './utils'; + +export interface Vendor extends Package { + dir: string; +} + +export async function findAll(dir: string): Promise { + const packages = await glob('**/package.json', { cwd: dir }); + + if (packages.length === 0) { + throw failures.vendorPackagesNotFound(dir); + } + + const promises = packages.map(async (file) => { + const pkgPath = path.join(dir, file); + const pkg = await readJsonFile(pkgPath); + if (!pkg) { + throw failures.packageJsonNotFound(pkgPath); + } + + return { + ...pkg, + dir: path.dirname(pkgPath), + }; + }); + return Promise.all(promises); +} + +export async function findByPackage(pkg: Package, vendors: Vendor[]) { + const result: { vendors: Vendor[]; dependencies: Dependency[]; unknown: Dependency[] } = { + vendors: [], + dependencies: [], + unknown: [], + }; + + async function inner(pkg: Package, memo: Set = new Set()) { + const localDependencies = Object.entries(pkg.localDependencies || {}); + for (const [name, version] of localDependencies) { + if (memo.has(name)) { + continue; + } + const vendor = vendors.find((vendor) => vendor.name === name); + if (!vendor) { + result.unknown.push({ name, version }); + continue; + } + memo.add(vendor.name); + result.vendors.push(vendor); + + if (vendor.localDependencies) { + await inner(vendor, memo); + } + + if (vendor.dependencies) { + for (const [name, version] of Object.entries(vendor.dependencies)) { + if (memo.has(name)) { + continue; + } + memo.add(name); + result.dependencies.push({ name, version }); + } + } + } + } + + await inner(pkg); + + return result; +} diff --git a/package.json b/package.json index bf8b9fe92d..5fc26eb3dd 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "test:schemas": "mocha libraries/botbuilder-dialogs-declarative/tests/schemaMergeTest.js --exit --bail", "update-version": "yarn workspace botbuilder-repo-utils update-versions", "update-versions": "node -e \"console.log('test')\"", - "postupdate-versions": "yarn workspace botbuilder-vendors postinstall && yarn workspace botbuilder-vendors postbuild" + "postupdate-versions": "yarn workspace botbuilder-vendors connect" }, "resolutions": { "@types/ramda": "0.26.0",