UNPKG

vsix-utils

Version:

utilities for working with VSIX packages

379 lines (371 loc) 12.6 kB
'use strict'; var fs = require('fs'); var promises = require('fs/promises'); var path = require('path'); var process = require('process'); 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'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } var path__default = /*#__PURE__*/_interopDefault(path); var process__default = /*#__PURE__*/_interopDefault(process); var ignore__default = /*#__PURE__*/_interopDefault(ignore); var mime__default = /*#__PURE__*/_interopDefault(mime); var remarkTransformLinks__default = /*#__PURE__*/_interopDefault(remarkTransformLinks); // src/files.ts 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_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 = path__default.default.join(cwd, ".gitignore"); const vscodeIgnorePath = path__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: path__default.default.join(cwd, file), path: path__default.default.join("extension/", file) })); if (dependencies.length > 0) { for (const dep of dependencies) { files.push({ type: "local", path: path__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 = path__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 (!path.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 }; } exports.collect = collect; exports.getContentTypesForFiles = getContentTypesForFiles; exports.getExtensionPackageManager = getExtensionPackageManager; exports.isInMemoryFile = isInMemoryFile; exports.isLocalFile = isLocalFile; exports.transformFiles = transformFiles;