vsix-utils
Version:
utilities for working with VSIX packages
1,177 lines (1,161 loc) • 43 kB
JavaScript
;
var child_process = require('child_process');
var path2 = require('path');
var process = require('process');
var util = require('util');
var fs = require('fs');
var promises = require('fs/promises');
var ignore = require('ignore');
var mime = require('mime');
var packageManagerDetector = require('package-manager-detector');
var tinyglobby = require('tinyglobby');
var remark = require('remark');
var remarkTransformLinks = require('remark-transform-links');
var semver = require('semver');
var buffer = require('buffer');
var fastXmlParser = require('fast-xml-parser');
var yauzl = require('yauzl');
var yazl = require('yazl');
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
var path2__default = /*#__PURE__*/_interopDefault(path2);
var process__default = /*#__PURE__*/_interopDefault(process);
var ignore__default = /*#__PURE__*/_interopDefault(ignore);
var mime__default = /*#__PURE__*/_interopDefault(mime);
var remarkTransformLinks__default = /*#__PURE__*/_interopDefault(remarkTransformLinks);
var yauzl__default = /*#__PURE__*/_interopDefault(yauzl);
var yazl__default = /*#__PURE__*/_interopDefault(yazl);
// src/dependencies.ts
var execAsync = util.promisify(child_process.exec);
async function getExtensionDependencies(manifest, options) {
const {
packageManager,
cwd = process__default.default.cwd()
} = options;
const dependencies = /* @__PURE__ */ new Set();
if (packageManager === "npm") {
const { stdout } = await execAsync("npm list --production --parseable --depth=99999 --loglevel=error", { cwd });
const lines = stdout.split(/[\r\n]/).filter((path4) => path2.isAbsolute(path4));
for (const line of lines) {
if (line === path2.resolve(cwd)) {
continue;
}
const dependency = line.split(`${path2__default.default.sep}node_modules${path2__default.default.sep}`)[1];
if (dependency == null) {
throw new Error(`could not parse dependency: ${line}`);
}
dependencies.add({
name: dependency.replace(/\\/g, "/"),
version: manifest.dependencies != null ? manifest.dependencies[dependency.replace(/\\/g, "/")] : undefined,
path: line
});
}
} else if (packageManager === "yarn") {
let asYarnDependency2 = function(prefix, tree, prune2) {
if (prune2 && /@[\^~]/.test(tree.name)) {
return null;
}
let name;
let version = "";
try {
const tmp = tree.name.split("@");
if (tmp[0] === "") {
tmp.shift();
tmp[0] = `@${tmp[0]}`;
}
name = tmp[0];
version = tmp[1] || "";
} catch {
name = tree.name.replace(/^([^@+])@.*$/, "$1");
}
const dependencyPath = path2__default.default.join(prefix, name);
const children = [];
for (const child of tree.children || []) {
const dep = asYarnDependency2(path2__default.default.join(prefix, name, "node_modules"), child, prune2);
if (dep) {
children.push(dep);
}
}
return { name, path: dependencyPath, children, version };
};
const { stdout } = await execAsync("yarn list --prod --json", { cwd });
const match = /^\{"type":"tree".*$/m.exec(stdout);
if (!match || match.length !== 1) {
throw new Error("Could not parse result of `yarn list --json`");
}
const trees = JSON.parse(match[0]).data.trees;
if (!Array.isArray(trees) || trees.length === 0) {
return [];
}
const prune = true;
const result = trees.map((tree) => asYarnDependency2(path2.join(cwd, "node_modules"), tree, prune)).filter((dep) => dep != null);
const internalDeps = /* @__PURE__ */ new Set();
const flatten = (dep) => {
if (internalDeps.has(dep.path)) {
return;
}
dependencies.add({
name: dep.name,
version: dep.version,
path: dep.path
});
internalDeps.add(dep.path);
if (dep.children) {
for (const child of dep.children) {
flatten(child);
}
}
};
for (const dep of result) {
flatten(dep);
}
} else if (packageManager === "pnpm") {
const { stdout } = await execAsync("pnpm list --production --json --depth=99999 --loglevel=error --ignore-workspace", { cwd });
let entryList = [];
try {
entryList = JSON.parse(stdout);
} catch {
return [];
}
if (!Array.isArray(entryList) || entryList.length === 0) {
return [];
}
const entry = entryList[0];
if (entry == null || typeof entry !== "object" || entry.dependencies == null || typeof entry.dependencies !== "object") {
return [];
}
const internalDeps = /* @__PURE__ */ new Set();
const flatten = (dep) => {
if (internalDeps.has(dep.path)) {
return;
}
dependencies.add({
name: dep.from,
version: dep.version,
path: dep.path
});
internalDeps.add(dep.path);
if (dep.dependencies) {
for (const child of Object.values(dep.dependencies)) {
flatten(child);
}
}
};
for (const value of Object.values(entry.dependencies)) {
flatten(value);
}
} else {
throw new Error(`unsupported package manager: ${packageManager}`);
}
return Array.from(dependencies);
}
async function transformMarkdown(manifest, options) {
const {
content,
rewrite = true,
branch = "HEAD"
} = options;
if (!rewrite) {
return content;
}
let { baseContentUrl, baseImagesUrl } = options;
const baseUrls = inferBaseUrls(manifest, branch);
if (baseUrls == null) {
throw new Error("Couldn't detect the repository where this extension is published.");
}
if (baseContentUrl == null) {
baseContentUrl = baseUrls.contentUrl;
}
if (baseImagesUrl == null) {
baseImagesUrl = baseUrls.imagesUrl;
}
const detectedSyntax = detectMarkdownSyntax(content);
const file = await remark.remark().data("settings", {
bullet: detectedSyntax.bullet,
bulletOrdered: detectedSyntax.bulletOrdered,
emphasis: detectedSyntax.emphasis,
listItemIndent: detectedSyntax.listItemIndent,
fence: detectedSyntax.fence,
strong: detectedSyntax.strong,
rule: detectedSyntax.rule
}).use(remarkTransformLinks__default.default, {
baseUrl(_, type) {
if (type === "image" || type === "html_img" || type === "html_video") {
return baseImagesUrl;
}
return baseContentUrl;
}
}).process(content);
return String(file);
}
function inferBaseUrls(manifest, branch) {
let repository = null;
if (typeof manifest.repository === "string") {
repository = manifest.repository;
} else if (manifest.repository && typeof manifest.repository === "object" && "url" in manifest.repository && typeof manifest.repository.url === "string") {
repository = manifest.repository.url;
}
if (!repository) {
return null;
}
if (repository.startsWith("git@")) {
repository = repository.replace(":", "/").replace("git@", "https://");
}
let url;
try {
url = new URL(repository);
} catch {
return null;
}
const ownerWithRepo = url.pathname.split("/").slice(1, 3).join("/").replace(/\.git$/, "");
const branchName = branch ?? "HEAD";
if (url.hostname === "github.com") {
return {
contentUrl: `https://github.com/${ownerWithRepo}/blob/${branchName}`,
imagesUrl: `https://github.com/${ownerWithRepo}/raw/${branchName}`
};
}
if (url.hostname === "gitlab.com") {
return {
contentUrl: `https://gitlab.com/${ownerWithRepo}/-/blob/${branchName}`,
imagesUrl: `https://gitlab.com/${ownerWithRepo}/-/raw/${branchName}`
};
}
if (url.hostname === "gitea.com") {
return {
contentUrl: `https://gitea.com/${ownerWithRepo}/src/branch/${branchName}`,
imagesUrl: `https://gitea.com/${ownerWithRepo}/raw/branch/${branchName}`
};
}
return null;
}
function detectMarkdownSyntax(content) {
const result = {};
const hyphenBullets = content.match(/^[\t ]*-[\t ][^\n]+/gm);
const asteriskBullets = content.match(/^[\t ]*\*[\t ][^\n]+/gm);
const plusBullets = content.match(/^[\t ]*\+[\t ][^\n]+/gm);
if (hyphenBullets?.length) result.bullet = "-";
else if (asteriskBullets?.length) result.bullet = "*";
else if (plusBullets?.length) result.bullet = "+";
const dotOrdered = content.match(/^\s*\d+\.[\t ][^\n]+/gm);
const parenthesisOrdered = content.match(/^\s*\d+\)[\t ][^\n]+/gm);
if (dotOrdered?.length) result.bulletOrdered = ".";
else if (parenthesisOrdered?.length) result.bulletOrdered = ")";
const asteriskEmphasis = content.match(/(?<!\*)\*[^*\n]+\*(?!\*)/g);
const underscoreEmphasis = content.match(/(?<!_)_[^_\n]+_(?!_)/g);
if (asteriskEmphasis?.length) result.emphasis = "*";
else if (underscoreEmphasis?.length) result.emphasis = "_";
const asteriskStrong = content.match(/\*\*[^*\n]+\*\*/g);
const underscoreStrong = content.match(/__[^_\n]+__/g);
if (asteriskStrong?.length) result.strong = "*";
else if (underscoreStrong?.length) result.strong = "_";
const backtickFence = content.match(/^```[^`]*```/gm);
const tildeFence = content.match(/^~~~[^~]*~~~/gm);
if (backtickFence?.length) result.fence = "`";
else if (tildeFence?.length) result.fence = "~";
const listItems = content.match(/^[\t ]*([-+*]|\d+[.)])\s+[^\n]+/gm);
if (listItems?.length) {
const indentTypes = new Set(
listItems.map((item) => {
const leadingSpace = item.match(/^[\t ]*/)?.[0] || "";
if (leadingSpace.includes(" ")) return "tab";
return leadingSpace.length === 1 ? "one" : leadingSpace.length > 1 ? "mixed" : "one";
})
);
if (indentTypes.size === 1) {
result.listItemIndent = indentTypes.values().next().value;
} else {
result.listItemIndent = "mixed";
}
}
const asteriskRule = content.match(/^[\t ]*(\*[\t ]*){3,}$/m);
const hyphenRule = content.match(/^[\t ]*(-[\t ]*){3,}$/m);
const underscoreRule = content.match(/^[\t ]*(_[\t ]*){3,}$/m);
if (asteriskRule?.length) result.rule = "*";
else if (hyphenRule?.length) result.rule = "-";
else if (underscoreRule?.length) result.rule = "_";
return result;
}
// src/vsce-constants.ts
var VSCE_TRUSTED_SOURCES = [
"api.bintray.com",
"api.travis-ci.com",
"api.travis-ci.org",
"app.fossa.io",
"badge.buildkite.com",
"badge.fury.io",
"badge.waffle.io",
"badgen.net",
"badges.frapsoft.com",
"badges.gitter.im",
"badges.greenkeeper.io",
"cdn.travis-ci.com",
"cdn.travis-ci.org",
"ci.appveyor.com",
"circleci.com",
"cla.opensource.microsoft.com",
"codacy.com",
"codeclimate.com",
"codecov.io",
"coveralls.io",
"david-dm.org",
"deepscan.io",
"dev.azure.com",
"docs.rs",
"flat.badgen.net",
"gemnasium.com",
"githost.io",
"gitlab.com",
"godoc.org",
"goreportcard.com",
"img.shields.io",
"isitmaintained.com",
"marketplace.visualstudio.com",
"nodesecurity.io",
"opencollective.com",
"snyk.io",
"travis-ci.com",
"travis-ci.org",
"visualstudio.com",
"vsmarketplacebadges.dev",
"www.bithound.io",
"www.versioneye.com"
];
var VSCE_DEFAULT_IGNORE = [
".vscodeignore",
"package-lock.json",
"npm-debug.log",
"yarn.lock",
"yarn-error.log",
"npm-shrinkwrap.json",
".editorconfig",
".npmrc",
".yarnrc",
".gitattributes",
"*.todo",
"tslint.yaml",
".eslintrc*",
".babelrc*",
".prettierrc*",
".cz-config.js",
".commitlintrc*",
"webpack.config.js",
"ISSUE_TEMPLATE.md",
"CONTRIBUTING.md",
"PULL_REQUEST_TEMPLATE.md",
"CODE_OF_CONDUCT.md",
".github",
".travis.yml",
"appveyor.yml",
"**/.git",
"**/.git/**",
"**/*.vsix",
"**/.DS_Store",
"**/*.vsixmanifest",
"**/.vscode-test/**",
"**/.vscode-test-web/**"
];
// src/files.ts
function isLocalFile(file) {
return file.type === "local";
}
function isInMemoryFile(file) {
return file.type === "in-memory";
}
async function collect(manifest, options) {
const {
cwd = process__default.default.cwd(),
ignoreFile = ".vscodeignore",
dependencies = [],
readme = "README.md"
} = options;
const gitignorePath = path2__default.default.join(cwd, ".gitignore");
const vscodeIgnorePath = path2__default.default.join(cwd, ignoreFile);
const ig = ignore__default.default();
if (fs.existsSync(gitignorePath)) {
const ignoreContent = await promises.readFile(gitignorePath, "utf8");
ig.add(ignoreContent);
}
if (fs.existsSync(vscodeIgnorePath)) {
const vsceIgnoreContent = await promises.readFile(vscodeIgnorePath, "utf8");
ig.add(vsceIgnoreContent);
}
const globbedFiles = await tinyglobby.glob("**", {
cwd,
followSymbolicLinks: true,
expandDirectories: true,
ignore: [...VSCE_DEFAULT_IGNORE, "!package.json", `!${readme}`, "node_modules/**"],
dot: true,
onlyFiles: true
});
const filteredFiles = globbedFiles.filter((file) => !ig.ignores(file));
const files = filteredFiles.map((file) => ({
type: "local",
localPath: path2__default.default.join(cwd, file),
path: path2__default.default.join("extension/", file)
}));
if (dependencies.length > 0) {
for (const dep of dependencies) {
files.push({
type: "local",
path: path2__default.default.join("extension/node_modules", dep.name),
localPath: dep.path
});
}
}
return files;
}
async function getExtensionPackageManager(cwd) {
const result = await packageManagerDetector.detect({ cwd });
if (result == null) {
throw new Error("could not detect package manager");
}
if (result.name === "deno" || result.name === "bun") {
throw new Error(`unsupported package manager: ${result.name}`);
}
return result.name;
}
var DEFAULT_MIME_TYPES = /* @__PURE__ */ new Map([
[".json", "application/json"],
[".vsixmanifest", "text/xml"],
[".md", "text/markdown"],
[".png", "image/png"],
[".txt", "text/plain"],
[".js", "application/javascript"],
[".yml", "text/yaml"],
[".html", "text/html"],
[".markdown", "text/markdown"],
[".css", "text/css"]
]);
function getContentTypesForFiles(files) {
const contentTypes = {};
for (const file of files) {
const ext = path2__default.default.extname(file.path).toLowerCase();
if (ext == null) continue;
if (!DEFAULT_MIME_TYPES.has(ext)) {
const contentType = mime__default.default.getType(ext);
if (contentType == null) {
throw new Error(`could not determine content type for file: ${file.path}`);
}
contentTypes[ext] = contentType;
} else {
contentTypes[ext] = DEFAULT_MIME_TYPES.get(ext);
}
}
const xml = (
/* xml */
`<?xml version="1.0" encoding="utf-8"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
${Object.entries(contentTypes).map(([ext, contentType]) => `<Default Extension="${ext}" ContentType="${contentType}" />
`).join("")}
</Types>
`
);
return {
xml,
contentTypes
};
}
async function transformFiles(options) {
const { manifest, files, readme } = options;
const assets = [];
let license;
let icon;
const hasLicenseFile = hasExtensionFile(files, ["LICENSE", "LICENSE.md", "LICENSE.txt", "LICENSE.markdown"]);
const hasIconFile = hasExtensionFile(files, [manifest.icon]);
const hasReadmeFile = hasExtensionFile(files, [readme, "README.md"]);
const hasChangelogFile = hasExtensionFile(files, ["CHANGELOG.md", "CHANGELOG.markdown", "CHANGELOG.txt"]);
const hasTranslationsFiles = hasExtensionFile(files, ["package.nls.json"]);
if (hasLicenseFile.found) {
if (!path2.extname(hasLicenseFile.path)) {
const entryIndex = files.findIndex((f) => f.path === hasLicenseFile.path);
if (entryIndex === -1) {
throw new Error(`could not find license file: ${hasLicenseFile.path}`);
}
const entry = files[entryIndex];
files[entryIndex] = {
...entry,
path: `${hasLicenseFile.path}.md`
};
hasLicenseFile.path = files[entryIndex].path;
}
license = hasLicenseFile.path;
assets.push({
type: "Microsoft.VisualStudio.Services.Content.License",
path: license
});
}
if (hasIconFile.found) {
icon = hasIconFile.path;
assets.push({
type: "Microsoft.VisualStudio.Services.Icons.Default",
path: icon
});
}
if (hasReadmeFile.found) {
const entryIndex = files.findIndex((f) => f.path === hasReadmeFile.path);
if (entryIndex === -1) {
throw new Error("could not find readme file");
}
const entry = files[entryIndex];
let contents = entry.type === "in-memory" ? entry.contents : await promises.readFile(entry.localPath, "utf-8");
if (typeof contents !== "string") {
contents = String(contents);
}
files[entryIndex] = {
type: "in-memory",
contents: await transformMarkdown(manifest, { content: contents, ...options.markdown }),
path: hasReadmeFile.path
};
assets.push({
type: "Microsoft.VisualStudio.Services.Content.Details",
path: hasReadmeFile.path
});
}
if (hasChangelogFile.found) {
assets.push({
type: "Microsoft.VisualStudio.Services.Content.Changelog",
path: hasChangelogFile.path
});
}
if (hasTranslationsFiles.found) {
assets.push({
type: "Microsoft.VisualStudio.Code.Translation.en",
path: hasTranslationsFiles.path
});
}
return {
assets,
icon,
license
};
}
function hasExtensionFile(files, fileNames) {
for (const fileName of fileNames) {
if (fileName == null) continue;
const file = files.find((f) => f.path.endsWith(fileName));
if (file) {
return { found: true, path: file.path };
}
}
return { found: false, path: undefined };
}
async function readProjectManifest(projectDir) {
try {
const manifestPath = path2__default.default.join(projectDir, "package.json");
const manifest = JSON.parse(await promises.readFile(manifestPath, "utf-8"));
return {
fileName: manifestPath,
manifest
};
} catch {
return null;
}
}
var escapeChars = /* @__PURE__ */ new Map([
["'", "'"],
['"', """],
["<", "<"],
[">", ">"],
["&", "&"]
]);
function escapeXml(value) {
return String(value).replace(/(['"<>&])/g, (_, char) => escapeChars.get(char));
}
var GITHUB_REPOSITORY_REGEX = /^https:\/\/github\.com\/|^git@github\.com:/;
function createVsixManifest(manifest, options) {
const { assets = [], icon, license, target, tags = [], flags = [], qna = false, preRelease = false } = options;
const galleryBanner = manifest.galleryBanner ?? {};
const extensionKind = transformExtensionKind(manifest);
const localizedLanguages = manifest.contributes?.localizations ? manifest.contributes.localizations.map((loc) => loc.localizedLanguageName ?? loc.languageName ?? loc.languageId).join(",") : "";
const isQnaEnabled = qna !== false;
let repository;
if (manifest.repository != null) {
if (typeof manifest.repository === "string") {
repository = manifest.repository;
} else if (typeof manifest.repository === "object" && "url" in manifest.repository && typeof manifest.repository.url === "string") {
repository = manifest.repository.url;
}
}
let bugs;
if (manifest.bugs != null) {
if (typeof manifest.bugs === "string") {
bugs = manifest.bugs;
} else if (typeof manifest.bugs === "object" && "url" in manifest.bugs && typeof manifest.bugs.url === "string") {
bugs = manifest.bugs.url;
} else if (typeof manifest.bugs === "object" && "email" in manifest.bugs && typeof manifest.bugs.email === "string") {
bugs = `mailto:${manifest.bugs.email}`;
}
}
let homepage;
if (manifest.homepage != null && typeof manifest.homepage === "string") {
homepage = manifest.homepage;
}
return (
/* xml */
`<?xml version="1.0" encoding="utf-8"?>
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
<Metadata>
<Identity Language="en-US" Id="${escapeXml(manifest.name)}" Version="${escapeXml(manifest.version)}" Publisher="${escapeXml(manifest.publisher)}" ${target != null ? `TargetPlatform="${escapeXml(target)}"` : ""} />
<DisplayName>${escapeXml(manifest.displayName ?? manifest.name)}</DisplayName>
<Description xml:space="preserve">${escapeXml(manifest.description ?? "")}</Description>
<Tags>${escapeXml(tags.join(",") ?? "")}</Tags>
<GalleryFlags>${escapeXml(flags.join(" ") ?? "")}</GalleryFlags>
${manifest.badges == null ? "" : `
<Badges>
${manifest.badges.map(
(badge) => `
<Badge Link="${escapeXml(badge.href)}" ImgUri="${escapeXml(badge.url)}" Description="${escapeXml(badge.description)}" />
`
).join("\n")}
</Badges>
`}
<Properties>
<Property Id="Microsoft.VisualStudio.Code.Engine" Value="${escapeXml(manifest.engines.vscode)}" />
<Property Id="Microsoft.VisualStudio.Code.ExtensionDependencies" Value="${escapeXml((manifest.extensionDependencies || []).join(","))}" />
<Property Id="Microsoft.VisualStudio.Code.ExtensionPack" Value="${escapeXml((manifest.extensionPack || []).join(","))}" />
<Property Id="Microsoft.VisualStudio.Code.ExtensionKind" Value="${escapeXml(extensionKind.join(","))}" />
<Property Id="Microsoft.VisualStudio.Code.LocalizedLanguages" Value="${escapeXml(localizedLanguages)}" />
<Property Id="Microsoft.VisualStudio.Code.EnabledApiProposals" Value="${escapeXml((manifest.enabledApiProposals || []).join(","))}" />
<Property Id="Microsoft.VisualStudio.Code.PreRelease" Value="${escapeXml(preRelease)}" />
<Property Id="Microsoft.VisualStudio.Code.ExecutesCode" Value="${escapeXml(!!(manifest.main ?? manifest.browser))}" />
${manifest.sponsor?.url != null ? `<Property Id="Microsoft.VisualStudio.Code.SponsorLink" Value="${escapeXml(manifest.sponsor.url)}" />` : ""}
${repository != null ? `<Property Id="Microsoft.VisualStudio.Services.Links.Source" Value="${escapeXml(repository)}" />` : ""}
${repository != null ? `<Property Id="Microsoft.VisualStudio.Services.Links.Getstarted" Value="${escapeXml(repository)}" />` : ""}
${repository != null ? `<Property Id="Microsoft.VisualStudio.Services.Links.${GITHUB_REPOSITORY_REGEX.test(repository) ? "GitHub" : "Repository"}" Value="${escapeXml(repository)}" />` : ""}
${bugs != null ? `<Property Id="Microsoft.VisualStudio.Services.Links.Support" Value="${escapeXml(bugs)}" />` : ""}
${homepage != null ? `<Property Id="Microsoft.VisualStudio.Services.Links.Learn" Value="${escapeXml(homepage)}" />` : ""}
${galleryBanner.color != null ? `<Property Id="Microsoft.VisualStudio.Services.Branding.Color Value="${escapeXml(galleryBanner.color)}" />` : ""}
${galleryBanner.theme != null ? `<Property Id="Microsoft.VisualStudio.Services.Branding.Theme Value="${escapeXml(galleryBanner.theme)}" />` : ""}
<Property Id="Microsoft.VisualStudio.Services.GitHubFlavoredMarkdown" Value="${escapeXml(manifest.markdown !== "standard")}" />
<Property Id="Microsoft.VisualStudio.Services.Content.Pricing" Value="${escapeXml(manifest.pricing ?? "Free")}" />
${isQnaEnabled ? `<Property Id="Microsoft.VisualStudio.Services.EnableMarketplaceQnA" Value="${escapeXml(qna)}" />` : ""}
${isQnaEnabled && qna !== "marketplace" ? `<Property Id="Microsoft.VisualStudio.Services.CustomerQnALink" Value="${escapeXml(qna)}" />` : ""}
</Properties>
${license != null ? `<License>${escapeXml(license)}</License>` : ""}
${icon != null ? `<Icon>${escapeXml(icon)}</Icon>` : ""}
</Metadata>
<Installation>
<InstallationTarget Id="Microsoft.VisualStudio.Code" />
</Installation>
<Dependencies />
<Assets>
<Asset Type="Microsoft.VisualStudio.Code.Manifest" Path="package.json" Addressable="true" />
${assets.map((asset) => `<Asset Type="${escapeXml(asset.type)}" Path="${escapeXml(asset.path)}" Addressable="true" />`).join("\n")}
</Assets>
</PackageManifest>`
);
}
var CONTRIBUTION_POINTS_FOR_EXTENSION_KIND = /* @__PURE__ */ new Map([
["jsonValidation", ["workspace", "web"]],
["localizations", ["ui", "workspace"]],
["debuggers", ["workspace"]],
["terminal", ["workspace"]],
["typescriptServerPlugins", ["workspace"]],
["markdown.previewStyles", ["workspace", "web"]],
["markdown.previewScripts", ["workspace", "web"]],
["markdown.markdownItPlugins", ["workspace", "web"]],
["html.customData", ["workspace", "web"]],
["css.customData", ["workspace", "web"]]
]);
function transformExtensionKind(manifest) {
const isWebSupported = manifest.browser != null;
if (manifest.extensionKind != null) {
const explicitKinds = Array.isArray(manifest.extensionKind) ? manifest.extensionKind : manifest.extensionKind === "ui" ? ["ui", "workspace"] : [manifest.extensionKind];
if (isWebSupported && !explicitKinds.includes("web")) {
explicitKinds.push("web");
}
return explicitKinds;
}
if (manifest.main != null && isWebSupported) {
return ["workspace", "web"];
}
if (manifest.main != null) {
return ["workspace"];
}
if (isWebSupported) {
return ["web"];
}
const isNonEmptyArray = (obj) => Array.isArray(obj) && obj.length > 0;
if (isNonEmptyArray(manifest.extensionPack) || isNonEmptyArray(manifest.extensionDependencies)) {
return ["workspace", "web"];
}
let result = ["ui", "workspace", "web"];
if (manifest.contributes != null) {
for (const contribution of Object.keys(manifest.contributes)) {
const supportedKinds = CONTRIBUTION_POINTS_FOR_EXTENSION_KIND.get(contribution);
if (supportedKinds) {
result = result.filter((kind) => supportedKinds.includes(kind));
}
}
}
return result;
}
function isWebKind(manifest) {
return transformExtensionKind(manifest).includes("web");
}
function getManifestTags(manifest) {
const { contributes = {}, activationEvents = [], keywords = [] } = manifest;
const tags = new Set(keywords);
const doesContribute = (obj, ...properties) => {
return properties.every((property) => obj && (obj = obj[property]) && obj.length > 0);
};
if (doesContribute(contributes, "themes")) tags.add("theme").add("color-theme");
if (doesContribute(contributes, "iconThemes")) tags.add("theme").add("icon-theme");
if (doesContribute(contributes, "productIconThemes")) tags.add("theme").add("product-icon-theme");
if (doesContribute(contributes, "snippets")) tags.add("snippet");
if (doesContribute(contributes, "keybindings")) tags.add("keybindings");
if (doesContribute(contributes, "debuggers")) tags.add("debuggers");
if (doesContribute(contributes, "jsonValidation")) tags.add("json");
if (doesContribute(contributes, "menus", "statusBar/remoteIndicator")) tags.add("remote-menu");
if (doesContribute(contributes, "chatParticipants")) tags.add("chat-participant").add("github-copilot");
for (const localization of contributes.localizations ?? []) {
tags.add(`lp-${localization.languageId}`);
for (const { id } of localization.translations ?? []) {
tags.add(`__lp_${id}`).add(`__lp-${localization.languageId}_${id}`);
}
}
for (const language of contributes.languages ?? []) {
tags.add(language.id);
for (const alias of language.aliases ?? []) {
tags.add(alias);
}
for (const ext of language.extensions ?? []) {
const cleanedExt = ext.replace(/\W/g, "");
if (cleanedExt) tags.add(`__ext_${cleanedExt}`);
}
}
for (const event of activationEvents) {
if (!event.startsWith("onLanguage:")) continue;
tags.add(event.replace("onLanguage:", ""));
}
for (const grammar of contributes.grammars ?? []) {
tags.add(grammar.language);
}
if (isWebKind(manifest)) {
tags.add("__web_extension");
}
if (manifest.sponsor != null && manifest.sponsor.url != null) {
tags.add("__sponsor_extension");
}
return [...tags].filter(Boolean);
}
async function prepublish(options) {
const { cwd, packageManager, manifest, preRelease } = options;
if (!manifest.scripts) {
return;
}
const scripts = [];
if ("vscode:prepublish" in manifest.scripts) {
scripts.push("vscode:prepublish");
}
if ("vscode:prepublish:release" in manifest.scripts && !preRelease) {
scripts.push("vscode:prepublish:release");
}
if ("vscode:prepublish:prerelease" in manifest.scripts && preRelease) {
scripts.push("vscode:prepublish:prerelease");
}
if (scripts.length === 0) {
return;
}
const results = await Promise.all(
scripts.map((script) => runScript({ cwd, packageManager, script }))
);
if (results.some((result) => !result)) {
throw new Error("failed to run one or more scripts");
}
}
async function runScript(options) {
const { cwd, packageManager, script } = options;
try {
const { stderr } = await new Promise((resolve2, reject) => {
child_process.exec(`${packageManager} run ${script}`, { cwd }, (err, stdout, stderr2) => {
if (err) {
reject(err);
} else {
resolve2({ stdout, stderr: stderr2 });
}
});
});
if (stderr.trim() !== "") {
return false;
}
return true;
} catch {
return false;
}
}
function validateVSCodeTypesCompatability(engineVersion, typesVersion) {
if (engineVersion === "*") {
return;
}
if (semver.validRange(engineVersion) == null) {
throw new Error(`invalid engine version '${engineVersion}'`);
}
if (semver.validRange(typesVersion) == null) {
throw new Error(`invalid types version '${typesVersion}'`);
}
const [engineMajor, engineMinor] = engineVersion.replace(/^\D+/, "").replace(/x/g, "0").split(".").map((x) => Number.parseInt(x, 10));
const [typesMajor, typesMinor] = typesVersion.replace(/^\D+/, "").replace(/x/g, "0").split(".").map((x) => Number.parseInt(x, 10) || 0);
if (typesMajor == null || typesMinor == null || engineMajor == null || engineMinor == null) {
throw new Error("invalid engine or types version");
}
if (typesMajor > engineMajor || typesMajor === engineMajor && typesMinor > engineMinor) {
throw new Error(
`@types/vscode version ${typesVersion} is higher than the specified engine version ${engineVersion}`
);
}
}
var ALLOWED_SPONSOR_PROTOCOLS = ["http:", "https:"];
var VALID_EXTENSION_KINDS = ["ui", "workspace"];
var EXTENSION_PRICING = ["Free", "Trial"];
var EXTENSION_NAME_REGEX = /^[a-z0-9][a-z0-9\-]*$/i;
var VSCODE_ENGINE_COMPATIBILITY_REGEX = /^\*$|^(\^|>=)?((\d+)|x)\.((\d+)|x)\.((\d+)|x)(-.*)?$/;
var GITHUB_BADGE_URL_REGEX = /^https:\/\/github\.com\/[^/]+\/[^/]+\/(actions\/)?workflows\/.*badge\.svg/;
async function validateProjectManifest(manifest) {
const errors = [];
if (manifest.name == null) {
errors.push({
field: "name",
message: "The `name` field is required.",
type: "MISSING_FIELD"
});
}
if (manifest.version == null) {
errors.push({
field: "version",
message: "The `version` field is required.",
type: "MISSING_FIELD"
});
}
if (manifest.publisher == null) {
errors.push({
field: "publisher",
message: "The `publisher` field is required.",
type: "MISSING_FIELD"
});
}
if (manifest.engines == null) {
errors.push({
field: "engines",
message: "The `engines` field is required.",
type: "MISSING_FIELD"
});
}
if (manifest.engines?.vscode == null) {
errors.push({
field: "engines.vscode",
message: "The `engines.vscode` field is required.",
type: "MISSING_FIELD"
});
}
const vscodeEngineVersion = manifest.engines?.vscode ?? "";
if (!VSCODE_ENGINE_COMPATIBILITY_REGEX.test(vscodeEngineVersion)) {
errors.push({
type: "INVALID_VALUE",
field: "engines.vscode",
message: "The `engines.vscode` field must be a valid semver version range, or 'x' for any version."
});
}
const engines = { ...manifest.engines || {}, vscode: vscodeEngineVersion };
const name = manifest.name || "";
if (!EXTENSION_NAME_REGEX.test(name)) {
errors.push({
type: "INVALID_VALUE",
field: "name",
message: "The `name` field should be an identifier and not its human-friendly name."
});
}
const version = manifest.version || "";
if (semver.valid(version) == null) {
errors.push({
type: "INVALID_VALUE",
field: "version",
message: "The `version` field must be a valid semver version."
});
}
const publisher = manifest.publisher || "";
if (!EXTENSION_NAME_REGEX.test(publisher)) {
errors.push({
type: "INVALID_VALUE",
field: "publisher",
message: "The `publisher` field should be an identifier and not its human-friendly name."
});
}
if (manifest.pricing && !EXTENSION_PRICING.includes(manifest.pricing)) {
errors.push({
type: "INVALID_PRICING",
value: manifest.pricing,
message: "The `pricing` field must be either 'Free' or 'Paid'."
});
}
const hasActivationEvents = !!manifest.activationEvents;
const hasImplicitLanguageActivationEvents = manifest.contributes?.languages;
const hasOtherImplicitActivationEvents = manifest.contributes?.commands || manifest.contributes?.authentication || manifest.contributes?.customEditors || manifest.contributes?.views;
const hasImplicitActivationEvents = hasImplicitLanguageActivationEvents || hasOtherImplicitActivationEvents;
const hasMain = !!manifest.main;
const hasBrowser = !!manifest.browser;
if (hasActivationEvents || (vscodeEngineVersion === "*" || semver.satisfies(vscodeEngineVersion, ">=1.74", { includePrerelease: true })) && hasImplicitActivationEvents) {
if (!hasMain && !hasBrowser && (hasActivationEvents || !hasImplicitLanguageActivationEvents)) {
errors.push(
{
type: "MISSING_FIELD",
field: "main",
message: "The use of `activationEvents` field requires either `browser` or `main` to be set."
},
{
type: "MISSING_FIELD",
field: "browser",
message: "The use of `activationEvents` field requires either `browser` or `main` to be set."
}
);
}
} else if (hasMain) {
errors.push({
type: "MISSING_FIELD",
field: "activationEvents",
message: "Manifest needs the 'activationEvents' property, given it has a 'main' property."
});
} else if (hasBrowser) {
errors.push({
type: "MISSING_FIELD",
field: "activationEvents",
message: "Manifest needs the 'activationEvents' property, given it has a 'browser' property."
});
}
if (manifest.devDependencies != null && manifest.devDependencies["@types/vscode"] != null) {
try {
validateVSCodeTypesCompatability(engines.vscode, manifest.devDependencies["@types/vscode"]);
} catch {
errors.push({
type: "VSCODE_TYPES_INCOMPATIBILITY",
message: "@types/vscode version is either higher than the specified engine version or invalid"
});
}
}
if (manifest.icon?.endsWith(".svg")) {
errors.push({
type: "INVALID_ICON",
field: "icon",
message: "SVG icons are not supported. Use PNG icons instead."
});
}
if (manifest.badges != null) {
for (const badge of manifest.badges) {
const decodedUrl = decodeURI(badge.url);
let srcURL = null;
try {
srcURL = new URL(decodedUrl);
} catch {
errors.push({
type: "INVALID_BADGE_URL",
field: "badges",
message: `The badge URL '${decodedUrl}' must be a valid URL.`
});
}
if (!decodedUrl.startsWith("https://")) {
errors.push({
type: "INVALID_BADGE_URL",
field: "badges",
message: "Badge URL must use the 'https' protocol"
});
}
if (decodedUrl.endsWith(".svg")) {
errors.push({
type: "INVALID_BADGE_URL",
field: "badges",
message: "SVG badges are not supported. Use PNG badges instead"
});
}
if (srcURL && !(srcURL.host != null && VSCE_TRUSTED_SOURCES.includes(srcURL.host.toLowerCase()) || GITHUB_BADGE_URL_REGEX.test(srcURL.href))) {
errors.push({
type: "UNTRUSTED_HOST",
field: "badges",
message: "Badge URL must use a trusted host"
});
}
}
}
if (manifest.dependencies != null && manifest.dependencies.vscode != null) {
errors.push({
type: "DEPENDS_ON_VSCODE_IN_DEPENDENCIES",
field: "dependencies.vscode",
message: `You should not depend on 'vscode' in your 'dependencies'. Did you mean to add it to 'devDependencies'?`
});
}
if (manifest.extensionKind != null) {
const extensionKinds = Array.isArray(manifest.extensionKind) ? manifest.extensionKind : [manifest.extensionKind];
for (const extensionKind of extensionKinds) {
if (!VALID_EXTENSION_KINDS.includes(extensionKind)) {
errors.push({
type: "INVALID_EXTENSION_KIND",
field: "extensionKind",
message: `Invalid extension kind '${extensionKind}'. Expected one of: ${VALID_EXTENSION_KINDS.join(", ")}`
});
}
}
}
if (manifest.sponsor != null && manifest.sponsor.url != null) {
try {
const sponsorUrl = new URL(manifest.sponsor.url);
if (!ALLOWED_SPONSOR_PROTOCOLS.includes(sponsorUrl.protocol)) {
errors.push({
type: "INVALID_SPONSOR_URL",
field: "sponsor.url",
message: `The protocol '${sponsorUrl.protocol.slice(0, sponsorUrl.protocol.lastIndexOf(":"))}' is not allowed. Use one of: ${ALLOWED_SPONSOR_PROTOCOLS.map((protocol) => protocol.slice(0, protocol.lastIndexOf(":"))).join(", ")}`
});
}
} catch {
errors.push({
type: "INVALID_SPONSOR_URL",
field: "sponsor.url",
message: "The `sponsor.url` field must be a valid URL."
});
}
}
if (errors.length === 0) {
return null;
}
return errors;
}
async function writeVsix(options) {
const { files, packagePath, force, epoch } = options;
if (!files || files.length === 0) {
throw new Error("no files specified to package");
}
if (!packagePath) {
throw new Error("no package path specified");
}
if (fs.existsSync(packagePath)) {
if (!force) {
throw new Error(`package already exists at ${packagePath}`);
}
await promises.unlink(packagePath);
}
const zip = new yazl__default.default.ZipFile();
const zipOptions = {};
if (epoch != null) {
zipOptions.mtime = new Date(epoch * 1e3);
files.sort((a, b) => a.path.localeCompare(b.path));
}
try {
for (const f of files) {
if (isInMemoryFile(f)) {
zip.addBuffer(
typeof f.contents === "string" ? buffer.Buffer.from(f.contents, "utf8") : f.contents,
f.path,
{ ...zipOptions }
);
continue;
}
zip.addFile(f.localPath, f.path, { ...zipOptions });
}
zip.end();
const zipStream = fs.createWriteStream(packagePath, {
mode: promises.constants.S_IRWXU | promises.constants.S_IRGRP | promises.constants.S_IXGRP | promises.constants.S_IROTH | promises.constants.S_IXOTH
});
zip.outputStream.pipe(zipStream);
await new Promise((resolve2, reject) => {
zipStream.once("finish", resolve2);
zipStream.once("error", reject);
zip.once("error", (err) => {
zipStream.destroy();
reject(err);
});
});
return true;
} catch (err) {
if (fs.existsSync(packagePath)) {
await promises.unlink(packagePath);
}
if (err instanceof Error) {
throw new TypeError(`failed to create package: ${err.message}`);
}
throw new Error(`failed to create package: ${err}`);
}
}
async function readVsix(options) {
const { packagePath } = options;
const zip = await new Promise((resolve2, reject) => {
yauzl__default.default.open(packagePath, { lazyEntries: true }, (err, zip2) => {
if (err) {
reject(err);
} else {
resolve2(zip2);
}
});
});
const files = await new Promise((resolve2, reject) => {
const fileMap = /* @__PURE__ */ new Map();
zip.once("close", () => resolve2(fileMap));
zip.on("entry", async (entry) => {
try {
const buffer = await new Promise((resolve3, reject2) => {
zip.openReadStream(entry, (err, stream) => {
if (err) reject2(err);
stream.once("error", reject2);
stream.once("end", () => {
if (stream.readable) {
reject2(new Error("Stream ended before all data was read"));
}
});
return resolve3(bufferStream(stream));
});
});
fileMap.set(entry.fileName, buffer);
zip.readEntry();
} catch (err) {
zip.close();
reject(err);
}
});
zip.readEntry();
});
if (!files.has("extension.vsixmanifest")) {
throw new Error("extension.vsixmanifest file is missing");
}
const rawManifest = files.get("extension.vsixmanifest").toString("utf8");
const parser = new fastXmlParser.XMLParser({
preserveOrder: false,
transformTagName(tagName) {
return tagName.toLowerCase();
}
});
const manifest = parser.parse(rawManifest);
return {
files,
manifest
};
}
async function bufferStream(stream) {
return await new Promise((resolve2, reject) => {
const buffers = [];
stream.on("data", (buffer) => buffers.push(buffer));
stream.once("error", reject);
stream.once("end", () => resolve2(buffer.Buffer.concat(buffers)));
});
}
exports.ALLOWED_SPONSOR_PROTOCOLS = ALLOWED_SPONSOR_PROTOCOLS;
exports.EXTENSION_NAME_REGEX = EXTENSION_NAME_REGEX;
exports.EXTENSION_PRICING = EXTENSION_PRICING;
exports.GITHUB_BADGE_URL_REGEX = GITHUB_BADGE_URL_REGEX;
exports.VALID_EXTENSION_KINDS = VALID_EXTENSION_KINDS;
exports.VSCODE_ENGINE_COMPATIBILITY_REGEX = VSCODE_ENGINE_COMPATIBILITY_REGEX;
exports.collect = collect;
exports.createVsixManifest = createVsixManifest;
exports.getContentTypesForFiles = getContentTypesForFiles;
exports.getExtensionDependencies = getExtensionDependencies;
exports.getExtensionPackageManager = getExtensionPackageManager;
exports.getManifestTags = getManifestTags;
exports.inferBaseUrls = inferBaseUrls;
exports.isInMemoryFile = isInMemoryFile;
exports.isLocalFile = isLocalFile;
exports.isWebKind = isWebKind;
exports.prepublish = prepublish;
exports.readProjectManifest = readProjectManifest;
exports.readVsix = readVsix;
exports.transformExtensionKind = transformExtensionKind;
exports.transformFiles = transformFiles;
exports.transformMarkdown = transformMarkdown;
exports.validateProjectManifest = validateProjectManifest;
exports.validateVSCodeTypesCompatability = validateVSCodeTypesCompatability;
exports.writeVsix = writeVsix;