зеркало из https://github.com/github/docs.git
Typescript src/assets (#53110)
This commit is contained in:
Родитель
0a72ea7794
Коммит
d35127dfa9
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export const IMAGE_DENSITY: Record<string, string>
|
|
@ -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))
|
|
@ -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
|
|
@ -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<string>) {
|
||||
let sum = 0
|
||||
for (const filePath of filePaths) {
|
||||
sum += fs.statSync(filePath).size
|
|
@ -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}`)
|
||||
})
|
|
@ -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<string, string>
|
||||
|
||||
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 `<path oNload="alert(1)"" d="M28 0l4.59 4.59-9.76`
|
||||
// We don't need to do a case-sensitive regex here because cheerio
|
|
@ -6,7 +6,7 @@ import { describe, expect, test, vi } from 'vitest'
|
|||
import { get } from '#src/tests/helpers/e2etest.js'
|
||||
import { checkCachingHeaders } from '#src/tests/helpers/caching-headers.js'
|
||||
|
||||
function getNextStaticAsset(directory) {
|
||||
function getNextStaticAsset(directory: string) {
|
||||
const root = path.join('.next', 'static', directory)
|
||||
const files = fs.readdirSync(root)
|
||||
if (!files.length) throw new Error(`Can't find any files in ${root}`)
|
|
@ -1,11 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
|
@ -30,9 +26,5 @@
|
|||
"docs-internal-data",
|
||||
"src/code-scanning/scripts/generate-code-scanning-query-list.ts"
|
||||
],
|
||||
"include": [
|
||||
"*.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
]
|
||||
"include": ["*.d.ts", "**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче