Skip to content

Commit

Permalink
Merge pull request #1013 from microsoft/benibenj/psychiatric-orangutan
Browse files Browse the repository at this point in the history
Print packaged files/folders
  • Loading branch information
benibenj authored Jul 10, 2024
2 parents b6ccc05 + 53d517e commit f06c324
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 24 deletions.
4 changes: 1 addition & 3 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@ function main(task: Promise<any>): 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();
}
Expand Down
69 changes: 51 additions & 18 deletions src/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -573,31 +574,27 @@ export class ManifestProcessor extends BaseProcessor {

async onEnd(): Promise<void> {
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');
}
}

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');
Expand Down Expand Up @@ -1828,13 +1825,8 @@ export async function pack(options: IPackageOptions = {}): Promise<IPackageResul
const cwd = options.cwd || process.cwd();
const manifest = await readManifest(cwd);
const files = await collect(manifest, options);
const jsFiles = files.filter(f => /\.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;
Expand Down Expand Up @@ -1939,3 +1931,44 @@ export async function ls(options: IListFilesOptions = {}): Promise<void> {
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);
}
4 changes: 1 addition & 3 deletions src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,7 @@ async function openDefaultStore(): Promise<IStore> {
}

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;
Expand Down
86 changes: 86 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

0 comments on commit f06c324

Please sign in to comment.