From 3cf9fa2abd8a99bb124d156229e4bc81cf213ecf Mon Sep 17 00:00:00 2001 From: Jason Dent Date: Sun, 5 Jan 2025 17:02:14 +0100 Subject: [PATCH] chore: Clean up extension package.json (#1101) --- .github/workflows/manual-publish.yml | 8 +- dict-extensions.code-workspace | 4 +- package-lock.json | 18 --- scripts/fix-extensions.mjs | 27 +++++ scripts/gen-extension-list.mjs | 65 +++-------- scripts/lib/extensionHelper.mjs | 165 +++++++++++++++++++++++++++ scripts/lib/index.d.ts | 1 + scripts/lib/mdastBuilder.mjs | 128 +++++++++++++++++++++ scripts/package-lock.json | 16 --- scripts/package.json | 4 +- scripts/update-manual-pub-list.mjs | 31 +++-- 11 files changed, 367 insertions(+), 100 deletions(-) create mode 100755 scripts/fix-extensions.mjs create mode 100644 scripts/lib/extensionHelper.mjs create mode 100644 scripts/lib/index.d.ts create mode 100644 scripts/lib/mdastBuilder.mjs diff --git a/.github/workflows/manual-publish.yml b/.github/workflows/manual-publish.yml index 8c999169..7e76ca81 100644 --- a/.github/workflows/manual-publish.yml +++ b/.github/workflows/manual-publish.yml @@ -9,9 +9,8 @@ on: inputs: ref: description: >- - The branch, tag or SHA to checkout. When checking out the repository - that triggered a workflow, this defaults to the reference or SHA for - that event. Otherwise, uses the default branch. + The branch, tag or SHA to checkout. When checking out the repository that triggered a workflow, this defaults + to the reference or SHA for that event. Otherwise, uses the default branch. required: true default: main extension: @@ -50,6 +49,7 @@ on: - extensions/latin - extensions/latvian - extensions/lithuanian + - extensions/macedonian - extensions/medical-terms - extensions/norwegian-bokmal - extensions/persian @@ -65,6 +65,7 @@ on: - extensions/spanish - extensions/swedish - extensions/swiss-german + - extensions/timestamp-hover - extensions/turkish - extensions/ukrainian - extensions/vietnamese @@ -116,4 +117,5 @@ jobs: secrets: VSCE_TOKEN: ${{ secrets.VSCE_TOKEN }} OVSX_TOKEN: ${{ secrets.OVSX_TOKEN }} + # cspell:ignore vsix xargs OVSX diff --git a/dict-extensions.code-workspace b/dict-extensions.code-workspace index 42fc23d3..acbc5a74 100644 --- a/dict-extensions.code-workspace +++ b/dict-extensions.code-workspace @@ -160,5 +160,7 @@ "path": "test-runner" } ], - "settings": {} + "settings": { + "editor.formatOnSave": true + } } diff --git a/package-lock.json b/package-lock.json index 88d22369..6950f3c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5250,23 +5250,6 @@ "node": ">= 0.4" } }, - "node_modules/mdast-builder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/mdast-builder/-/mdast-builder-1.1.1.tgz", - "integrity": "sha512-a3KBk/LmYD6wKsWi8WJrGU/rXR4yuF4Men0JO0z6dSZCm5FrXXWTRDjqK0vGSqa+1M6p9edeuypZAZAzSehTUw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@types/unist": "^2.0.3" - } - }, - "node_modules/mdast-builder/node_modules/@types/unist": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", - "dev": true, - "license": "MIT" - }, "node_modules/mdast-util-find-and-replace": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", @@ -10037,7 +10020,6 @@ "license": "MIT", "dependencies": { "@types/mdast": "^4.0.4", - "mdast-builder": "^1.1.1", "mdast-util-to-markdown": "^2.1.2" }, "bin": { diff --git a/scripts/fix-extensions.mjs b/scripts/fix-extensions.mjs new file mode 100755 index 00000000..4d818067 --- /dev/null +++ b/scripts/fix-extensions.mjs @@ -0,0 +1,27 @@ +#!/usr/bin/env node + +/** + * Fix extensions of files. + */ + +// @ts-check + +import { + fixExtensionPackageJson, + getExtensionList, + readExtensionPackageJson, + writeExtensionPackageJson, +} from './lib/extensionHelper.mjs'; + +async function run() { + console.log('Fixing extensions...'); + const extensions = await getExtensionList(); + + for (const ext of extensions) { + const pkg = await readExtensionPackageJson(ext); + fixExtensionPackageJson(ext, pkg); + await writeExtensionPackageJson(ext, pkg); + } +} + +await run(); diff --git a/scripts/gen-extension-list.mjs b/scripts/gen-extension-list.mjs index 804820bb..35359c5b 100755 --- a/scripts/gen-extension-list.mjs +++ b/scripts/gen-extension-list.mjs @@ -1,11 +1,18 @@ #!/usr/bin/env node -// ts-check +// @ts-check import * as fs from 'node:fs/promises'; import { fileURLToPath } from 'url'; import * as path from 'path'; import { toMarkdown } from 'mdast-util-to-markdown'; -import { root, paragraph, text, heading, list, listItem, link } from 'mdast-builder'; + +import { root, paragraph, text, heading, list, listItem, link } from './lib/mdastBuilder.mjs'; + +import { getExtensionInfo } from './lib/extensionHelper.mjs'; + +/** + * @typedef {import('./lib/extensionHelper.mjs').ExtensionInfo} ExtensionInfo + */ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -15,6 +22,9 @@ const targetExtensionFolderListMarkdown = path.join(__root, 'static/generated/ex const targetMarketplaceLanguageExtensions = path.join(__root, 'static/generated/marketplace_language_extensions.md'); const targetMarketplaceExtensions = path.join(__root, 'static/generated/marketplace_extensions.md'); +/** + * @type {import('mdast-util-to-markdown').Options} + */ const markdownOptions = { bullet: '-', }; @@ -43,7 +53,7 @@ function pathToLink(url, name) { function makeExtensionListItem(extensionInfo) { return listItem( paragraph([ - link(extensionInfo.extensionPath + '#readme', undefined, text(extensionInfo.displayNameShort)), + link(extensionInfo.extensionPath + '#readme', text(extensionInfo.displayNameShort)), text(` - ${extensionInfo.description}`), ]), ); @@ -59,7 +69,6 @@ function makeMarketplaceExtensionListItem(extensionInfo) { paragraph([ link( 'https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.' + extensionInfo.name, - undefined, text(extensionInfo.displayNameShort), ), text(' - ' + extensionInfo.description), @@ -67,54 +76,6 @@ function makeMarketplaceExtensionListItem(extensionInfo) { ); } -/** - * - * @typedef {Object} ExtensionInfo - * @property {string} name - * @property {string} displayName - * @property {string} displayNameShort - * @property {string} version - * @property {string} description - * @property {string} extensionPath - * @property {string} dictionaryType - */ - -/** @type {import('../static/dictionary-types.json')} */ -const dictionaryTypes = JSON.parse(await fs.readFile(path.join(__root, 'static/dictionary-types.json'))); - -/** - * - * @param {string} extensionPath - * @returns {string} - */ -function lookUpDictionaryType(extensionPath) { - for (const [category, extensions] of Object.entries(dictionaryTypes.types)) { - if (extensions.includes(extensionPath)) return category; - } - - return dictionaryTypes.default; -} - -/** - * - * @param {string} extensionPath - * @returns {Promise} - */ -async function getExtensionInfo(extensionPath) { - const pkg = JSON.parse(await fs.readFile(path.join(__root, extensionPath, 'package.json'), 'utf8')); - - /** @type {ExtensionInfo} */ - const info = { - name: pkg.name, - description: pkg.description, - displayName: pkg.displayName, - displayNameShort: pkg.displayName.replace(/ -.*/, '').trim(), - version: pkg.version, - extensionPath, - dictionaryType: lookUpDictionaryType(extensionPath), - }; - return info; -} /** * diff --git a/scripts/lib/extensionHelper.mjs b/scripts/lib/extensionHelper.mjs new file mode 100644 index 00000000..c66a8eef --- /dev/null +++ b/scripts/lib/extensionHelper.mjs @@ -0,0 +1,165 @@ +// @ts-check +import fs from 'node:fs/promises'; +import pathPosix from 'node:path/posix'; + +const rootUrl = new URL('../../', import.meta.url); +const extensionsUrl = new URL('extensions/', rootUrl); + +const repositoryUrl = new URL('https://github.com/streetsidesoftware/vscode-cspell-dict-extensions'); +const repositoryRawUrl = new URL( + 'https://raw.githubusercontent.com/streetsidesoftware/vscode-cspell-dict-extensions/refs/heads/main/', +); + +/** @type {Repository} */ +const defaultRepository = { + type: 'git', + url: repositoryUrl.href, +}; + +/** + * @returns {Promise} + */ +export async function getExtensionList() { + const extensionFolders = await fs.readdir(extensionsUrl, { withFileTypes: true }); + return extensionFolders + .filter((f) => f.isDirectory()) + .map((f) => new URL(f.name + '/', extensionsUrl)) + .map((u) => urlRelativeToRoot(u)); +} + +/** + * return a relative path to the repository root. + * @param {URL} url + * @returns {string} + */ +export function urlRelativeToRoot(url) { + const addSlash = url.pathname.endsWith('/') ? '/' : ''; + return pathPosix.relative(rootUrl.pathname, url.pathname) + addSlash; +} + +/** + * @typedef {Object} ExtensionInfo + * @property {string} name + * @property {string} displayName + * @property {string} displayNameShort + * @property {string} version + * @property {string} description + * @property {string} extensionPath + * @property {string} dictionaryType + */ + +/** + * + * @param {string} extensionPath + * @returns {Promise} + */ +export async function getExtensionInfo(extensionPath) { + const pkgUrl = new URL(pathPosix.join(extensionPath, 'package.json'), rootUrl); + const pkg = JSON.parse(await fs.readFile(pkgUrl, 'utf8')); + + /** @type {ExtensionInfo} */ + const info = { + name: pkg.name, + description: pkg.description, + displayName: pkg.displayName, + displayNameShort: pkg.displayName.replace(/ -.*/, '').trim(), + version: pkg.version, + extensionPath, + dictionaryType: lookUpDictionaryType(extensionPath), + }; + return info; +} + +/** @type {import('../../static/dictionary-types.json')} */ +const dictionaryTypes = JSON.parse(await fs.readFile(new URL('static/dictionary-types.json', rootUrl), 'utf8')); + +/** + * + * @param {string} extensionPath + * @returns {string} + */ +function lookUpDictionaryType(extensionPath) { + for (const [category, extensions] of Object.entries(dictionaryTypes.types)) { + if (extensions.includes(extensionPath)) return category; + } + + return dictionaryTypes.default; +} + +/** + * @template T + * @typedef {import('./index').Writable} Writable + */ + +/** + * @typedef {Writable} VSCEPackageOptions + */ + +/** + * @typedef {Object} Repository + * @property {'git'} type + * @property {string} url + * @property {string} [directory] + */ + +/** + * @typedef {Object} PackageJson + * @property {string} name + * @property {string} displayName + * @property {string} version + * @property {string} description + * @property {string} main + * @property {string} [browser] + * @property {Repository} [repository] + * @property {VSCEPackageOptions} [vsce] + * @property {boolean} private + * @property {boolean | undefined} [preview] + */ + +/** + * + * @param {URL | string} pkgUrl + * @returns {Promise} + */ +export async function readPackageJson(pkgUrl) { + return JSON.parse(await fs.readFile(pkgUrl, 'utf8')); +} + +/** + * + * @param {string | URL} extensionPath + * @returns {Promise} + */ +export async function readExtensionPackageJson(extensionPath) { + const extUrl = new URL(extensionPath, rootUrl); + const pkgUrl = new URL('package.json', extUrl); + return readPackageJson(pkgUrl); +} + +/** + * + * @param {string | URL} extensionPath + * @param {PackageJson} pkg + */ +export async function writeExtensionPackageJson(extensionPath, pkg) { + const extUrl = new URL(extensionPath, rootUrl); + const pkgUrl = new URL('package.json', extUrl); + await fs.writeFile(pkgUrl, JSON.stringify(pkg, null, 2) + '\n'); +} + +/** + * @param {string | URL} extensionDir + * @param {PackageJson} pkg + * @returns {PackageJson} + */ +export function fixExtensionPackageJson(extensionDir, pkg) { + extensionDir = urlRelativeToRoot(new URL(extensionDir, rootUrl)); + pkg.private = true; + pkg.preview = undefined; + pkg.repository = pkg.repository || { ...defaultRepository }; + pkg.repository.directory = extensionDir.replace(/\/$/, ''); + pkg.vsce = pkg.vsce || {}; + pkg.vsce.baseContentUrl = new URL(extensionDir, repositoryRawUrl).href; + pkg.vsce.baseImagesUrl = new URL(extensionDir, repositoryRawUrl).href; + return pkg; +} diff --git a/scripts/lib/index.d.ts b/scripts/lib/index.d.ts new file mode 100644 index 00000000..2f9a0501 --- /dev/null +++ b/scripts/lib/index.d.ts @@ -0,0 +1 @@ +export type Writable = { -readonly [P in keyof T]: T[P] }; diff --git a/scripts/lib/mdastBuilder.mjs b/scripts/lib/mdastBuilder.mjs new file mode 100644 index 00000000..1c459af0 --- /dev/null +++ b/scripts/lib/mdastBuilder.mjs @@ -0,0 +1,128 @@ +// @ts-check + +/** + * @typedef {import('mdast').Root} Root + * @typedef {import('mdast').RootData} RootData + * @typedef {import('mdast').RootContent} RootContent + * @typedef {import('mdast').Paragraph} Paragraph + * @typedef {import('mdast').ParagraphData} ParagraphData + * @typedef {import('mdast').PhrasingContent} PhrasingContent + * @typedef {import('mdast').Heading} Heading + * @typedef {import('mdast').HeadingData} HeadingData + * @typedef {import('mdast').Text} Text + * @typedef {import('mdast').TextData} TextData + * @typedef {import('mdast').List} List + * @typedef {import('mdast').ListData} ListData + * @typedef {import('mdast').ListItem} ListItem + * @typedef {import('mdast').ListItemData} ListItemData + * @typedef {import('mdast').Link} Link + * @typedef {import('mdast').LinkData} LinkData + * @typedef {import('mdast').BlockContent} BlockContent + */ + +/** + * Create a root node + * @param {RootContent | RootContent[]} children + * @param {RootData} [data] + * @returns {Root} + */ +export function root(children, data) { + return { + type: 'root', + children: Array.isArray(children) ? children : [children], + data + }; +} + +/** + * Create a paragraph node + * @param {PhrasingContent | PhrasingContent[]} children + * @param {ParagraphData} [data] + * @returns {Paragraph} + */ +export function paragraph(children, data) { + return { + type: 'paragraph', + children: Array.isArray(children) ? children : [children], + data, + }; +} + +/** + * Create a heading node + * @param {1 | 2 | 3 | 4 | 5 | 6} depth + * @param {PhrasingContent | PhrasingContent[]} children + * @param {HeadingData} [data] + * @returns {Heading} + */ +export function heading(depth, children, data) { + return { + type: 'heading', + depth, + children: Array.isArray(children) ? children : [children], + data, + }; +} + +/** + * + * @param {string} value + * @param {TextData} [data] + * @returns {Text} + */ +export function text(value, data) { + return { + type: 'text', + value, + data, + }; +} + +/** + * create a list node + * @param {BlockContent | BlockContent[]} children + * @param {ListItemData} [data] + * @returns {ListItem} + */ +export function listItem(children, data) { + return { + type: 'listItem', + children: Array.isArray(children) ? children : [children], + data, + }; +} + +/** + * + * @param {boolean | 'ordered' | 'unordered'} ordered + * @param {ListItem[]} children + * @param {ListData} [data] + * @returns {List} + */ +export function list(ordered, children, data) { + ordered = ordered === 'ordered' ? true : ordered === 'unordered' ? false : ordered; + return { + type: 'list', + ordered, + children, + data, + }; +} + +/** + * + * @param {string} url + * @param {PhrasingContent | PhrasingContent[]} children + * @param {string} [title] + * @param {LinkData} [data] + * @returns {Link} + */ +export function link(url, children, title, data) { + return { + type: 'link', + url, + children: Array.isArray(children) ? children : [children], + title, + data, + }; +} diff --git a/scripts/package-lock.json b/scripts/package-lock.json index 4395bd03..369752f0 100644 --- a/scripts/package-lock.json +++ b/scripts/package-lock.json @@ -10,7 +10,6 @@ "license": "MIT", "dependencies": { "@types/mdast": "^4.0.4", - "mdast-builder": "^1.1.1", "mdast-util-to-markdown": "^2.1.2" }, "bin": { @@ -70,21 +69,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/mdast-builder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/mdast-builder/-/mdast-builder-1.1.1.tgz", - "integrity": "sha512-a3KBk/LmYD6wKsWi8WJrGU/rXR4yuF4Men0JO0z6dSZCm5FrXXWTRDjqK0vGSqa+1M6p9edeuypZAZAzSehTUw==", - "license": "BSD-2-Clause", - "dependencies": { - "@types/unist": "^2.0.3" - } - }, - "node_modules/mdast-builder/node_modules/@types/unist": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", - "license": "MIT" - }, "node_modules/mdast-util-phrasing": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", diff --git a/scripts/package.json b/scripts/package.json index 10012b8b..869907a0 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -4,14 +4,14 @@ "description": "Useful scripts", "bin": { "gen-extension-list": "./gen-extension-list.mjs", - "update-manual-pub-list": "./update-manual-pub-list.mjs" + "update-manual-pub-list": "./update-manual-pub-list.mjs", + "fix-extensions": "./fix-extensions.mjs" }, "engines": { "node": ">=20.0.0" }, "dependencies": { "@types/mdast": "^4.0.4", - "mdast-builder": "^1.1.1", "mdast-util-to-markdown": "^2.1.2" }, "devDependencies": {}, diff --git a/scripts/update-manual-pub-list.mjs b/scripts/update-manual-pub-list.mjs index 2d06e41c..283374ce 100755 --- a/scripts/update-manual-pub-list.mjs +++ b/scripts/update-manual-pub-list.mjs @@ -1,5 +1,6 @@ #!/usr/bin/env node +// @ts-check import * as fs from 'node:fs/promises'; import { fileURLToPath } from 'url'; import * as path from 'path'; @@ -19,19 +20,15 @@ async function main() { /** @type {string[]} */ const allFolders = workspace.folders.map((f) => f.path); - const extensions = allFolders.filter((f) => f.startsWith('extension') || f === '.'); + const extensions = allFolders.filter((f) => f.startsWith('extension')); const setOfExt = new Set(extensions); const workflowContent = await fs.readFile(workflow, 'utf8'); const doc = yaml.parseDocument(workflowContent); - /** @type {import('yaml').YAMLSeq<>} */ const options = doc.getIn('on.workflow_dispatch.inputs.extension.options'.split('.')); - - options.items = options.items - .filter((item) => setOfExt.has(item.value)) - .map((item) => (item.value === '.' ? item : (item.type = undefined || item))); + assertYAMLSeq(options); const current = new Set(options.items.map((item) => item.value)); @@ -41,13 +38,31 @@ async function main() { } }); - options.items.sort((a, b) => intl.compare(a.value, b.value)); + options.items.sort((a, b) => compareExtensions(a.value, b.value)); // console.log('%o', options); options.flow = false; - await fs.writeFile(workflow, doc.toString(), 'utf8'); + await fs.writeFile(workflow, doc.toString({ lineWidth: 120 }), 'utf8'); +} + +/** + * @param {unknown} value + * @returns {asserts value is import('yaml').YAMLSeq} + */ +function assertYAMLSeq(value) { + if (!(value instanceof yaml.YAMLSeq)) { + throw new Error('Expected a YAMLSeq'); + } +} + +function compareExtensions(a, b) { + if (a === 'none') return -1; + if (b === 'none') return 1; + if (a === 'all') return -1; + if (b === 'all') return 1; + return intl.compare(a, b); } await main();