From d35127dfa9d34415fa71fdad34c6551db9cf2534 Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Thu, 14 Nov 2024 11:17:43 -0800 Subject: [PATCH] Typescript src/assets (#53110) --- package-lock.json | 27 ++++++++++--------- package.json | 4 +-- src/assets/lib/image-density.d.ts | 1 + ...nt-1.js => deleted-assets-pr-comment-1.ts} | 15 +++++++---- ...omment.js => deleted-assets-pr-comment.ts} | 22 ++++++++++----- ...aned-assets.js => find-orphaned-assets.ts} | 14 +++++++--- ...ist-image-sizes.js => list-image-sizes.ts} | 22 ++++++--------- ...set-images.js => validate-asset-images.ts} | 21 +++++++++------ .../{static-assets.js => static-assets.ts} | 2 +- tsconfig.json | 12 ++------- 10 files changed, 78 insertions(+), 62 deletions(-) create mode 100644 src/assets/lib/image-density.d.ts rename src/assets/scripts/{deleted-assets-pr-comment-1.js => deleted-assets-pr-comment-1.ts} (74%) rename src/assets/scripts/{deleted-assets-pr-comment.js => deleted-assets-pr-comment.ts} (76%) rename src/assets/scripts/{find-orphaned-assets.js => find-orphaned-assets.ts} (95%) rename src/assets/scripts/{list-image-sizes.js => list-image-sizes.ts} (62%) rename src/assets/scripts/{validate-asset-images.js => validate-asset-images.ts} (90%) rename src/assets/tests/{static-assets.js => static-assets.ts} (98%) diff --git a/package-lock.json b/package-lock.json index 44c695262210..601463118080 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,7 @@ "express": "4.21.1", "express-rate-limit": "7.4.0", "fastest-levenshtein": "1.0.16", - "file-type": "19.4.1", + "file-type": "19.6.0", "flat": "^6.0.1", "github-slugger": "^2.0.0", "glob": "11.0.0", @@ -6880,12 +6880,13 @@ } }, "node_modules/file-type": { - "version": "19.4.1", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-19.4.1.tgz", - "integrity": "sha512-RuWzwF2L9tCHS76KR/Mdh+DwJZcFCzrhrPXpOw6MlEfl/o31fjpTikzcKlYuyeV7e7ftdCGVJTNOCzkYD/aLbw==", + "version": "19.6.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-19.6.0.tgz", + "integrity": "sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==", + "license": "MIT", "dependencies": { "get-stream": "^9.0.1", - "strtok3": "^8.1.0", + "strtok3": "^9.0.1", "token-types": "^6.0.0", "uint8array-extras": "^1.3.0" }, @@ -11546,9 +11547,10 @@ } }, "node_modules/peek-readable": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.1.4.tgz", - "integrity": "sha512-E7mY2VmKqw9jYuXrSWGHFuPCW2SLQenzXLF3amGaY6lXXg4/b3gj5HVM7h8ZjCO/nZS9ICs0Cz285+32FvNd/A==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.3.1.tgz", + "integrity": "sha512-GVlENSDW6KHaXcd9zkZltB7tCLosKB/4Hg0fqBJkAoBgYG2Tn1xtMgXtSUuMU9AK/gCm/tTdT8mgAeF4YNeeqw==", + "license": "MIT", "engines": { "node": ">=14.16" }, @@ -13836,12 +13838,13 @@ "license": "MIT" }, "node_modules/strtok3": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-8.1.0.tgz", - "integrity": "sha512-ExzDvHYPj6F6QkSNe/JxSlBxTh3OrI6wrAIz53ulxo1c4hBJ1bT9C/JrAthEKHWG9riVH3Xzg7B03Oxty6S2Lw==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-9.0.1.tgz", + "integrity": "sha512-ERPW+XkvX9W2A+ov07iy+ZFJpVdik04GhDA4eVogiG9hpC97Kem2iucyzhFxbFRvQ5o2UckFtKZdp1hkGvnrEw==", + "license": "MIT", "dependencies": { "@tokenizer/token": "^0.3.0", - "peek-readable": "^5.1.4" + "peek-readable": "^5.3.1" }, "engines": { "node": ">=16" diff --git a/package.json b/package.json index 569dfee80c0b..e44d8053a5de 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "delete-orphan-translation-files": "tsx src/workflows/delete-orphan-translation-files.ts", "deleted-features-pr-comment": "tsx src/data-directory/scripts/deleted-features-pr-comment.ts", "dev": "cross-env npm start", - "find-orphaned-assets": "node src/assets/scripts/find-orphaned-assets.js", + "find-orphaned-assets": "tsx src/assets/scripts/find-orphaned-assets.ts", "find-orphaned-features": "tsx src/data-directory/scripts/find-orphaned-features/index.ts", "find-past-built-pr": "tsx src/workflows/find-past-built-pr.ts", "find-unused-variables": "tsx src/content-linter/scripts/find-unsed-variables.ts", @@ -259,7 +259,7 @@ "express": "4.21.1", "express-rate-limit": "7.4.0", "fastest-levenshtein": "1.0.16", - "file-type": "19.4.1", + "file-type": "19.6.0", "flat": "^6.0.1", "github-slugger": "^2.0.0", "glob": "11.0.0", diff --git a/src/assets/lib/image-density.d.ts b/src/assets/lib/image-density.d.ts new file mode 100644 index 000000000000..dbcbd318b1a3 --- /dev/null +++ b/src/assets/lib/image-density.d.ts @@ -0,0 +1 @@ +export const IMAGE_DENSITY: Record diff --git a/src/assets/scripts/deleted-assets-pr-comment-1.js b/src/assets/scripts/deleted-assets-pr-comment-1.ts similarity index 74% rename from src/assets/scripts/deleted-assets-pr-comment-1.js rename to src/assets/scripts/deleted-assets-pr-comment-1.ts index 0adc48c7264d..206143821033 100755 --- a/src/assets/scripts/deleted-assets-pr-comment-1.js +++ b/src/assets/scripts/deleted-assets-pr-comment-1.ts @@ -18,14 +18,19 @@ // [end-readme] import { program } from 'commander' -import main from './deleted-assets-pr-comment.js' +import main from './deleted-assets-pr-comment' program .description('If applicable, print a snippet of Markdown about deleted assets') - .arguments('owner repo base_sha head_sha', 'Simulate what the Actions workflow does') + .arguments('owner repo base_sha head_sha') .parse(process.argv) -const opts = program.opts() -const args = program.args +type MainArgs = { + owner: string + repo: string + baseSHA: string + headSHA: string +} +const opts = program.opts() as MainArgs -console.log(await main(...args, { ...opts })) +console.log(await main(opts)) diff --git a/src/assets/scripts/deleted-assets-pr-comment.js b/src/assets/scripts/deleted-assets-pr-comment.ts similarity index 76% rename from src/assets/scripts/deleted-assets-pr-comment.js rename to src/assets/scripts/deleted-assets-pr-comment.ts index 3aa9352478f1..561fb461b0c6 100755 --- a/src/assets/scripts/deleted-assets-pr-comment.js +++ b/src/assets/scripts/deleted-assets-pr-comment.ts @@ -13,16 +13,22 @@ if (!GITHUB_TOKEN) { // When this file is invoked directly from action as opposed to being imported if (import.meta.url.endsWith(process.argv[1])) { const owner = context.repo.owner - const repo = context.payload.repository.name - const baseSHA = context.payload.pull_request.base.sha - const headSHA = context.payload.pull_request.head.sha + const repo = context.payload.repository?.name || '' + const baseSHA = context.payload.pull_request?.base.sha + const headSHA = context.payload.pull_request?.head.sha - const markdown = await main(owner, repo, baseSHA, headSHA) + const markdown = await main({ owner, repo, baseSHA, headSHA }) core.setOutput('markdown', markdown) } -async function main(owner, repo, baseSHA, headSHA) { - const octokit = github.getOctokit(GITHUB_TOKEN) +type MainArgs = { + owner: string + repo: string + baseSHA: string + headSHA: string +} +async function main({ owner, repo, baseSHA, headSHA }: MainArgs) { + const octokit = github.getOctokit(GITHUB_TOKEN as string) // get the list of file changes from the PR const response = await octokit.rest.repos.compareCommitsWithBasehead({ owner, @@ -32,6 +38,10 @@ async function main(owner, repo, baseSHA, headSHA) { const { files } = response.data + if (!files) { + throw new Error('No files found in the PR') + } + const oldFilenames = [] for (const file of files) { const { filename, status } = file diff --git a/src/assets/scripts/find-orphaned-assets.js b/src/assets/scripts/find-orphaned-assets.ts similarity index 95% rename from src/assets/scripts/find-orphaned-assets.js rename to src/assets/scripts/find-orphaned-assets.ts index bf266fe17312..07885edf5f38 100755 --- a/src/assets/scripts/find-orphaned-assets.js +++ b/src/assets/scripts/find-orphaned-assets.ts @@ -32,7 +32,7 @@ const EXCEPTIONS = new Set([ 'assets/images/site/apple-touch-icon-76x76.png', ]) -function isExceptionPath(imagePath) { +function isExceptionPath(imagePath: string) { // We also check for .DS_Store because any macOS user that has opened // a folder with images will have this on disk. It won't get added // to git anyway thanks to our .DS_Store. @@ -53,9 +53,15 @@ program .option('--exclude-translations', "Don't search in translations/") .parse(process.argv) -main(program.opts(), program.args) +type MainOptions = { + json: boolean + verbose: boolean + exit: boolean + excludeTranslations: boolean +} +main(program.opts()) -async function main(opts) { +async function main(opts: MainOptions) { const { json, verbose, exit, excludeTranslations } = opts const walkOptions = { @@ -164,7 +170,7 @@ async function main(opts) { } } -function getTotalDiskSize(filePaths) { +function getTotalDiskSize(filePaths: Set) { let sum = 0 for (const filePath of filePaths) { sum += fs.statSync(filePath).size diff --git a/src/assets/scripts/list-image-sizes.js b/src/assets/scripts/list-image-sizes.ts similarity index 62% rename from src/assets/scripts/list-image-sizes.js rename to src/assets/scripts/list-image-sizes.ts index 974c748dd021..e343dacdd4b1 100755 --- a/src/assets/scripts/list-image-sizes.js +++ b/src/assets/scripts/list-image-sizes.ts @@ -10,32 +10,26 @@ import { fileURLToPath } from 'url' import path from 'path' import walk from 'walk-sync' import sharp from 'sharp' -import { chain } from 'lodash-es' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const imagesPath = path.join(__dirname, '../assets/images') const imagesExtensions = ['.jpg', '.jpeg', '.png', '.gif'] -const files = chain(walk(imagesPath, { directories: false })).filter((relativePath) => { +const files = walk(imagesPath, { directories: false }).filter((relativePath) => { return imagesExtensions.includes(path.extname(relativePath.toLowerCase())) }) -const infos = await Promise.all( +const images = await Promise.all( files.map(async (relativePath) => { const fullPath = path.join(imagesPath, relativePath) const image = sharp(fullPath) const { width, height } = await image.metadata() - const size = width * height + const size = (width || 0) * (height || 0) return { relativePath, width, height, size } }), ) -const images = files - .map((relativePath, i) => { - return { relativePath, ...infos[i] } +images + .sort((a, b) => b.size - a.size) + .forEach((image) => { + const { relativePath, width, height } = image + console.log(`${width} x ${height} - ${relativePath}`) }) - .orderBy('size', 'desc') - .value() - -images.forEach((image) => { - const { relativePath, width, height } = image - console.log(`${width} x ${height} - ${relativePath}`) -}) diff --git a/src/assets/scripts/validate-asset-images.js b/src/assets/scripts/validate-asset-images.ts similarity index 90% rename from src/assets/scripts/validate-asset-images.js rename to src/assets/scripts/validate-asset-images.ts index 1481ca3b407c..4e9052488b1d 100755 --- a/src/assets/scripts/validate-asset-images.js +++ b/src/assets/scripts/validate-asset-images.ts @@ -19,6 +19,7 @@ import path from 'path' import { program } from 'commander' import chalk from 'chalk' import cheerio from 'cheerio' +// @ts-ignore see https://github.com/sindresorhus/file-type/issues/652 import { fileTypeFromFile } from 'file-type' import walk from 'walk-sync' import isSVG from 'is-svg' @@ -43,7 +44,7 @@ const EXPECT = { '.ico': 'image/x-icon', '.pdf': 'application/pdf', '.webp': 'image/webp', -} +} as Record const CRITICAL = 'critical' const WARNING = 'warning' @@ -56,7 +57,7 @@ program main(program.opts()) -async function main(opts) { +async function main(opts: { dryRun: boolean; verbose: boolean }) { let errors = 0 const files = walk(ASSETS_ROOT, { includeBasePath: true, directories: false }).filter( @@ -71,7 +72,11 @@ async function main(opts) { ) }, ) - const results = (await Promise.all(files.map(checkFile))).filter(Boolean) + const results = (await Promise.all(files.map(checkFile))).filter(Boolean) as [ + level: string, + filePath: string, + error: string, + ][] for (const [level, filePath, error] of results) { console.log( level === CRITICAL ? chalk.red(level) : chalk.yellow(level), @@ -94,7 +99,7 @@ async function main(opts) { process.exitCode = errors } -async function checkFile(filePath) { +async function checkFile(filePath: string) { const ext = path.extname(filePath) const { size } = await fs.stat(filePath) @@ -113,7 +118,7 @@ async function checkFile(filePath) { } try { checkSVGContent(content) - } catch (error) { + } catch (error: any) { return [CRITICAL, filePath, error.message] } } else if (EXPECT[ext]) { @@ -135,15 +140,15 @@ async function checkFile(filePath) { // All is well. Nothing to complain about. } -function checkSVGContent(content) { +function checkSVGContent(content: string) { const $ = cheerio.load(content) const disallowedTagNames = new Set(['script', 'object', 'iframe', 'embed']) $('*').each((i, element) => { - const { tagName } = element + const { tagName } = $(element).get(0) if (disallowedTagNames.has(tagName)) { throw new Error(`contains a <${tagName}> tag`) } - for (const key in element.attribs) { + for (const key in $(element).get(0).attribs) { // Looks for suspicious event handlers on tags. // For example `