From 53d517e799d8b242dca7786055fc080d921a63a6 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Wed, 10 Jul 2024 11:37:13 +0200 Subject: [PATCH] Print packaged files --- src/main.ts | 4 +-- src/package.ts | 69 +++++++++++++++++++++++++++++----------- src/store.ts | 4 +-- src/util.ts | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 139 insertions(+), 24 deletions(-) diff --git a/src/main.ts b/src/main.ts index 34d1969f..7a570087 100644 --- a/src/main.ts +++ b/src/main.ts @@ -46,9 +46,7 @@ function main(task: Promise): void { task.catch(fatal).then(() => { if (latestVersion && semver.gt(latestVersion, pkg.version)) { - log.info( - `\nThe latest version of ${pkg.name} is ${latestVersion} and you have ${pkg.version}.\nUpdate it now: npm install -g ${pkg.name}` - ); + log.info(`The latest version of ${pkg.name} is ${latestVersion} and you have ${pkg.version}.\nUpdate it now: npm install -g ${pkg.name}`); } else { token.cancel(); } diff --git a/src/package.ts b/src/package.ts index 756e6d7d..e6a8a0af 100644 --- a/src/package.ts +++ b/src/package.ts @@ -14,6 +14,7 @@ import * as url from 'url'; import mime from 'mime'; import * as semver from 'semver'; import urljoin from 'url-join'; +import chalk from 'chalk'; import { validateExtensionName, validateVersion, @@ -573,21 +574,15 @@ export class ManifestProcessor extends BaseProcessor { async onEnd(): Promise { if (typeof this.manifest.extensionKind === 'string') { - util.log.warn( - `The 'extensionKind' property should be of type 'string[]'. Learn more at: https://aka.ms/vscode/api/incorrect-execution-location` - ); + util.log.warn(`The 'extensionKind' property should be of type 'string[]'. Learn more at: https://aka.ms/vscode/api/incorrect-execution-location`); } if (this.manifest.publisher === 'vscode-samples') { - throw new Error( - "It's not allowed to use the 'vscode-samples' publisher. Learn more at: https://code.visualstudio.com/api/working-with-extensions/publishing-extension." - ); + throw new Error("It's not allowed to use the 'vscode-samples' publisher. Learn more at: https://code.visualstudio.com/api/working-with-extensions/publishing-extension."); } if (!this.options.allowMissingRepository && !this.manifest.repository) { - util.log.warn( - `A 'repository' field is missing from the 'package.json' manifest file.\nUse --allow-missing-repository to bypass.` - ); + util.log.warn(`A 'repository' field is missing from the 'package.json' manifest file.\nUse --allow-missing-repository to bypass.`); if (!/^y$/i.test(await util.read('Do you want to continue? [y/N] '))) { throw new Error('Aborted'); @@ -595,9 +590,11 @@ export class ManifestProcessor extends BaseProcessor { } if (!this.options.allowStarActivation && this.manifest.activationEvents?.some(e => e === '*')) { - util.log.warn( - `Using '*' activation is usually a bad idea as it impacts performance.\nMore info: https://code.visualstudio.com/api/references/activation-events#Start-up\nUse --allow-star-activation to bypass.` - ); + let message = ''; + message += `Using '*' activation is usually a bad idea as it impacts performance.\n`; + message += `More info: https://code.visualstudio.com/api/references/activation-events#Start-up\n`; + message += `Use --allow-star-activation to bypass.`; + util.log.warn(message); if (!/^y$/i.test(await util.read('Do you want to continue? [y/N] '))) { throw new Error('Aborted'); @@ -1828,13 +1825,8 @@ export async function pack(options: IPackageOptions = {}): Promise /\.js$/i.test(f.path)); - if (files.length > 5000 || jsFiles.length > 100) { - console.log( - `This extension consists of ${files.length} files, out of which ${jsFiles.length} are JavaScript files. For performance reasons, you should bundle your extension: https://aka.ms/vscode-bundle-extension . You should also exclude unnecessary files by adding them to your .vscodeignore: https://aka.ms/vscode-vscodeignore` - ); - } + printPackagedFiles(files, cwd, manifest, options); if (options.version && !(options.updatePackageJson ?? true)) { manifest.version = options.version; @@ -1939,3 +1931,44 @@ export async function ls(options: IListFilesOptions = {}): Promise { console.log(`${file}`); } } + +/** + * Prints the packaged files of an extension. + */ +export function printPackagedFiles(files: IFile[], cwd: string, manifest: Manifest, options: IPackageOptions): void { + // Warn if the extension contains a lot of files + const jsFiles = files.filter(f => /\.js$/i.test(f.path)); + if (files.length > 5000 || jsFiles.length > 100) { + let message = '\n'; + message += `This extension consists of ${chalk.bold(String(files.length))} files, out of which ${chalk.bold(String(jsFiles.length))} are JavaScript files. `; + message += `For performance reasons, you should bundle your extension: ${chalk.underline('https://aka.ms/vscode-bundle-extension')}. `; + message += `You should also exclude unnecessary files by adding them to your .vscodeignore: ${chalk.underline('https://aka.ms/vscode-vscodeignore')}.\n`; + console.log(message); + } + + // Warn if the extension does not have a .vscodeignore file or a files property in package.json + if (!options.ignoreFile && !manifest.files) { + const hasDeaultIgnore = fs.existsSync(path.join(cwd, '.vscodeignore')); + if (!hasDeaultIgnore) { + let message = ''; + message += `Neither a ${chalk.bold('.vscodeignore')} file nor a ${chalk.bold('"files"')} property in package.json was found. `; + message += `To ensure only necessary files are included in your extension package, `; + message += `add a .vscodeignore file or specify the "files" property in package.json. More info: ${chalk.underline('https://aka.ms/vscode-vscodeignore')}`; + util.log.warn(message); + } + } + + // Print the files included in the package + const printableFileStructure = util.generateFileStructureTree(getDefaultPackageName(manifest, options), files.map(f => f.path), 35); + + let message = ''; + message += chalk.bold.blue(`Files included in the VSIX:\n`); + message += printableFileStructure.join('\n'); + + if (files.length + 1 > printableFileStructure.length) { + // If not all files have been printed, mention how all files can be printed + message += `\n\n=> Run ${chalk.bold('vsce ls')} to see a list of all included files.\n`; + } + + util.log.info(message); +} diff --git a/src/store.ts b/src/store.ts index 797f166c..7c5182e9 100644 --- a/src/store.ts +++ b/src/store.ts @@ -169,9 +169,7 @@ async function openDefaultStore(): Promise { } await fileStore.deleteStore(); - log.info( - `Migrated ${fileStore.size} publishers to system credential manager. Deleted local store '${fileStore.path}'.` - ); + log.info(`Migrated ${fileStore.size} publishers to system credential manager. Deleted local store '${fileStore.path}'.`); } return keytarStore; diff --git a/src/util.ts b/src/util.ts index 282e713a..8319fa21 100644 --- a/src/util.ts +++ b/src/util.ts @@ -183,3 +183,89 @@ export function patchOptionsWithManifest(options: any, manifest: Manifest): void } } } + +export function generateFileStructureTree(rootFolder: string, filePaths: string[], maxPrint: number = Number.MAX_VALUE): string[] { + const folderTree: any = {}; + const depthCounts: number[] = []; + + // Build a tree structure from the file paths + filePaths.forEach(filePath => { + const parts = filePath.split('/'); + let currentLevel = folderTree; + + parts.forEach((part, depth) => { + if (!currentLevel[part]) { + currentLevel[part] = depth === parts.length - 1 ? null : {}; + if (depthCounts.length <= depth) { + depthCounts.push(0); + } + depthCounts[depth]++; + } + currentLevel = currentLevel[part]; + }); + }); + + // Get max depth + let currentDepth = 0; + let countUpToCurrentDepth = depthCounts[0]; + for (let i = 1; i < depthCounts.length; i++) { + if (countUpToCurrentDepth + depthCounts[i] > maxPrint) { + break; + } + currentDepth++; + countUpToCurrentDepth += depthCounts[i]; + } + + const maxDepth = currentDepth; + let message: string[] = []; + + // Helper function to print the tree + const printTree = (tree: any, depth: number, prefix: string) => { + // Print all files before folders + const sortedFolderKeys = Object.keys(tree).filter(key => tree[key] !== null).sort(); + const sortedFileKeys = Object.keys(tree).filter(key => tree[key] === null).sort(); + const sortedKeys = [...sortedFileKeys, ...sortedFolderKeys]; + + for (let i = 0; i < sortedKeys.length; i++) { + + const key = sortedKeys[i]; + const isLast = i === sortedKeys.length - 1; + const localPrefix = prefix + (isLast ? '└─ ' : '├─ '); + const childPrefix = prefix + (isLast ? ' ' : '│ '); + + if (tree[key] === null) { + // It's a file + message.push(localPrefix + key); + } else { + // It's a folder + if (depth < maxDepth) { + // maxdepth is not reached, print the folder and its children + message.push(localPrefix + chalk.bold(`${key}/`)); + printTree(tree[key], depth + 1, childPrefix); + } else { + // max depth is reached, print the folder but not its children + const filesCount = countFiles(tree[key]); + message.push(localPrefix + chalk.bold(`${key}/`) + chalk.green(` (${filesCount} ${filesCount === 1 ? 'file' : 'files'})`)); + } + } + } + }; + + // Helper function to count the number of files in a tree + const countFiles = (tree: any): number => { + let filesCount = 0; + for (const key in tree) { + if (tree[key] === null) { + filesCount++; + } else { + filesCount += countFiles(tree[key]); + } + } + return filesCount; + }; + + message.push(chalk.bold(rootFolder)); + printTree(folderTree, 0, ''); + + return message; +} \ No newline at end of file