| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- import { existsSync } from 'node:fs'
- import { readFile } from 'node:fs/promises'
- import { dirname, join, resolve } from 'node:path'
- import { spawnSync } from 'node:child_process'
- import { fileURLToPath } from 'node:url'
- const scriptDir = dirname(fileURLToPath(import.meta.url))
- const sourceRoot = resolve(scriptDir, '..')
- const args = process.argv.slice(2)
- const offline = args.includes('--offline')
- const strictSocialPreview = args.includes('--strict-social-preview')
- const requiredFiles = [
- 'README.md',
- 'README.en.md',
- 'CONTRIBUTING.md',
- 'SECURITY.md',
- 'SUPPORT.md',
- 'CODE_OF_CONDUCT.md',
- 'ROADMAP.md',
- 'CHANGELOG.md',
- 'RELEASE_TEMPLATE.md',
- 'GITHUB_GROWTH_PLAYBOOK.md',
- 'GITHUB_GROWTH_AUDIT.md',
- 'GITHUB_SETTINGS_CHECKLIST.md',
- 'TRAFFIC_TRACKING.md',
- 'CONTENT_DISTRIBUTION_PLAN.md',
- '.github/FUNDING.yml',
- '.github/PULL_REQUEST_TEMPLATE.md',
- '.github/ISSUE_TEMPLATE/bug_report.yml',
- '.github/ISSUE_TEMPLATE/compatibility.yml',
- '.github/ISSUE_TEMPLATE/feature_request.yml',
- '.github/social-preview.svg',
- '.github/social-preview.png',
- 'docs/guide/compare.md',
- 'docs/en/guide/compare.md'
- ]
- const desiredTopics = [
- 'file-viewer',
- 'document-viewer',
- 'document-preview',
- 'file-preview',
- 'office-viewer',
- 'pdf-viewer',
- 'docx',
- 'pptx',
- 'xlsx',
- 'cad-viewer',
- 'dwg',
- 'dxf',
- 'vue',
- 'react',
- 'typescript',
- 'web-components',
- 'wasm',
- 'offline-first',
- 'self-hosted',
- 'private-deployment'
- ]
- const expectedDescription =
- 'Browser-native Office / PDF / CAD / archive viewer for internal web apps, with Vue, React, Svelte, jQuery, Web Components, and no server-side conversion.'
- const failures = []
- const pass = message => console.log(`ok - ${message}`)
- const fail = message => failures.push(message)
- const assertFile = file => {
- if (existsSync(resolve(sourceRoot, file))) {
- pass(`${file} exists`)
- } else {
- fail(`${file} is missing`)
- }
- }
- const readText = async file => readFile(resolve(sourceRoot, file), 'utf8')
- for (const file of requiredFiles) {
- assertFile(file)
- }
- const readme = await readText('README.md')
- const readmeEn = await readText('README.en.md')
- const demo = await readText('apps/viewer-demo/src/components/HelloWorld.vue')
- const playbook = await readText('GITHUB_GROWTH_PLAYBOOK.md')
- const requiredTextChecks = [
- [readme, '面向企业后台、内网和私有化系统的纯前端文件预览组件', 'Chinese README first-screen positioning'],
- [readme, '无需服务端转码', 'Chinese README no server-side conversion'],
- [readme, '当前内置 206+ 扩展名映射和 24 条预览链路', 'Chinese README format evidence'],
- [readme, '按场景选择入口', 'Chinese README scenario entrances'],
- [readme, '尤其需要真实业务文件来验证兼容性', 'Chinese README compatibility feedback'],
- [readmeEn, 'browser-native file viewer for private and internal web apps', 'English README first-screen positioning'],
- [readmeEn, 'without server-side conversion', 'English README no server-side conversion'],
- [readmeEn, '206+ extensions across 24 preview pipelines', 'English README format evidence'],
- [readmeEn, 'Choose By Scenario', 'English README scenario entrances'],
- [demo, '试试 Word 合同', 'Demo Word scenario shortcut'],
- [demo, '试试 DWG 图纸', 'Demo CAD scenario shortcut'],
- [demo, 'copySnippet', 'Demo copyable integration snippet'],
- [playbook, 'pnpm github:topics:update', 'Playbook has topics execution command'],
- [playbook, 'CONTENT_DISTRIBUTION_PLAN.md', 'Playbook links content distribution plan']
- ]
- for (const [source, needle, label] of requiredTextChecks) {
- if (source.includes(needle)) {
- pass(label)
- } else {
- fail(`${label} missing text: ${needle}`)
- }
- }
- const socialPreview = await readFile(resolve(sourceRoot, '.github/social-preview.png'))
- if (socialPreview.subarray(0, 8).toString('hex') !== '89504e470d0a1a0a') {
- fail('Social preview is not a PNG')
- } else {
- const width = socialPreview.readUInt32BE(16)
- const height = socialPreview.readUInt32BE(20)
- if (width === 1280 && height === 640) {
- pass('Social preview PNG is 1280x640')
- } else {
- fail(`Social preview PNG expected 1280x640, got ${width}x${height}`)
- }
- }
- const runGhJson = commandArgs => {
- const result = spawnSync('gh', commandArgs, {
- cwd: sourceRoot,
- encoding: 'utf8',
- stdio: 'pipe'
- })
- if (result.status !== 0) {
- const details = [result.stderr, result.stdout].filter(Boolean).join('\n')
- throw new Error(`Command failed: gh ${commandArgs.join(' ')}${details ? `\n${details}` : ''}`)
- }
- return JSON.parse(result.stdout || '{}')
- }
- if (!offline) {
- const remote = runGhJson([
- 'repo',
- 'view',
- 'flyfish-dev/file-viewer',
- '--json',
- 'description,homepageUrl,repositoryTopics'
- ])
- if (remote.description === expectedDescription) {
- pass('GitHub remote description matches growth positioning')
- } else {
- fail(`GitHub remote description mismatch: ${remote.description}`)
- }
- const remoteTopics = (remote.repositoryTopics || []).map(topic => topic.name).sort()
- const expectedTopics = [...desiredTopics].sort()
- if (JSON.stringify(remoteTopics) === JSON.stringify(expectedTopics)) {
- pass('GitHub remote topics match desired discovery set')
- } else {
- fail(`GitHub remote topics mismatch: ${remoteTopics.join(', ')}`)
- }
- if (remote.homepageUrl === 'https://file-viewer.app') {
- pass('GitHub remote homepage is file-viewer.app')
- } else {
- fail(`GitHub remote homepage mismatch: ${remote.homepageUrl}`)
- }
- const openGraph = runGhJson([
- 'api',
- 'graphql',
- '-F',
- 'owner=flyfish-dev',
- '-F',
- 'name=file-viewer',
- '-f',
- 'query=query($owner:String!, $name:String!) { repository(owner:$owner, name:$name) { usesCustomOpenGraphImage } }'
- ])
- const usesCustomOpenGraphImage = Boolean(openGraph.data?.repository?.usesCustomOpenGraphImage)
- if (usesCustomOpenGraphImage) {
- pass('GitHub remote custom social preview is configured')
- } else if (strictSocialPreview) {
- fail('GitHub remote custom social preview is not configured')
- } else {
- console.warn('warn - GitHub remote custom social preview is not configured; run with --strict-social-preview after uploading the image.')
- }
- }
- if (failures.length) {
- console.error('\nGitHub growth asset verification failed:')
- for (const failure of failures) {
- console.error(`- ${failure}`)
- }
- process.exit(1)
- }
- console.log('\nGitHub growth assets verified.')
|