chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:40:11 +08:00
commit 3b061d0e53
248 changed files with 40470 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env node
/**
* Equivalent to: npm run build --workspaces --if-present
*
* Reads the workspace list from root package.json, filters to those with a
* "build" script, and runs them all concurrently via parallelTask.
*/
import { readFileSync } from 'fs'
import { dirname, join } from 'path'
import { fileURLToPath } from 'url'
import { parallelTask } from './parallel-task.js'
const rootDir = join(dirname(fileURLToPath(import.meta.url)), '..')
const rootPkg = JSON.parse(readFileSync(join(rootDir, 'package.json'), 'utf-8'))
const tasks = rootPkg.workspaces
.map((ws) => {
const dir = join(rootDir, ws)
const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'))
return pkg.scripts?.build ? { label: pkg.name, command: 'npm run build', cwd: dir } : null
})
.filter(Boolean)
await parallelTask(tasks, { timeoutMs: 120_000 })
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env node
/**
* Full build pipeline. Equivalent to:
* npm run cleanup && npm run build --workspaces --if-present
* && npm run build:website -w @page-agent/website
* && npm run zip -w @page-agent/ext
*
* 1. cleanup
* 2. build everything in parallel (libs + website + extension)
*/
import chalk from 'chalk'
import { execSync } from 'child_process'
import { readFileSync } from 'fs'
import { dirname, join } from 'path'
import { fileURLToPath } from 'url'
import { parallelTask } from './parallel-task.js'
const rootDir = join(dirname(fileURLToPath(import.meta.url)), '..')
const rootPkg = JSON.parse(readFileSync(join(rootDir, 'package.json'), 'utf-8'))
// Step 1: cleanup
console.log(chalk.bgBlue.white.bold(' ▸ cleanup '))
execSync('npm run cleanup', { cwd: rootDir, stdio: 'inherit' })
// Step 2: build all in parallel
console.log(chalk.bgBlue.white.bold(' ▸ build '))
const tasks = rootPkg.workspaces
.map((ws) => {
const dir = join(rootDir, ws)
const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'))
return pkg.scripts?.build ? { label: pkg.name, command: 'npm run build', cwd: dir } : null
})
.filter(Boolean)
tasks.push(
{
label: '@page-agent/website',
command: 'npm run build:website',
cwd: join(rootDir, 'packages/website'),
},
{ label: '@page-agent/ext', command: 'npm run zip', cwd: join(rootDir, 'packages/extension') }
)
await parallelTask(tasks, { timeoutMs: 120_000 })
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env node
/**
* CI check script. Run locally before commit or in GitHub Actions.
*
* Usage:
* node scripts/ci.js # run all checks
* node scripts/ci.js --no-build # skip build step
*/
import chalk from 'chalk'
import { execSync } from 'child_process'
import { parallelTask } from './parallel-task.js'
const args = new Set(process.argv.slice(2))
const skipBuild = args.has('--no-build')
function run(label, command) {
console.log(chalk.bgBlue.white.bold(`${label} `))
execSync(command, { stdio: 'inherit' })
}
function isMainBranch() {
if (process.env.GITHUB_REF) return process.env.GITHUB_REF === 'refs/heads/main'
try {
return execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim() === 'main'
} catch {
return true
}
}
// 1. Commitlint — skip on main
if (isMainBranch()) {
console.log(chalk.dim(' ▸ commitlint (skipped on main)'))
} else {
const from = execSync('git merge-base origin/main HEAD', { encoding: 'utf-8' }).trim()
run('commitlint', `npx commitlint --from ${from} --to HEAD`)
}
// 2. Lint + Format + Typecheck + Test in parallel
console.log(chalk.bgBlue.white.bold(' ▸ lint + format + typecheck + test '))
await parallelTask(
[
{ label: 'lint', command: 'npm run lint' },
{ label: 'format', command: 'npx prettier --check .' },
{ label: 'typecheck', command: 'npm run typecheck' },
{ label: 'test', command: 'npm test' },
],
{ timeoutMs: 120_000 }
)
// 3. Build
if (skipBuild) {
console.log(chalk.dim(' ▸ build (skipped)'))
} else {
run('build', 'npm run build')
}
+115
View File
@@ -0,0 +1,115 @@
import chalk from 'chalk'
import { spawn } from 'child_process'
/**
* Run multiple shell commands in parallel with progress reporting.
*
* @param {{ label: string, command: string, cwd?: string }[]} tasks
* @param {{ timeoutMs?: number }} options - Default timeout 30s per task
* @returns {Promise<void>} Rejects (process.exit) if any task fails
*/
export async function parallelTask(tasks, options = {}) {
const { timeoutMs = 30_000 } = options
const total = tasks.length
const bgColors = [
chalk.bgCyan,
chalk.bgMagenta,
chalk.bgBlue,
chalk.bgYellow,
chalk.bgGreenBright,
]
const fgOnBg = [chalk.black, chalk.white, chalk.white, chalk.black, chalk.black]
let done = 0
let failed = 0
const spinner = ['◐', '◓', '◑', '◒']
let tick = 0
const printProgress = () => {
const running = total - done - failed
const s = spinner[tick++ % spinner.length]
const status = failed
? `${running} running, ${done} done, ${chalk.bgRed.white.bold(` ${failed} failed `)}`
: `${running} running, ${done} done`
process.stderr.write(`\r${chalk.bgCyan.black.bold(` ${s} ${done}/${total} `)} ${status} `)
}
const timer = setInterval(printProgress, 1000)
printProgress()
/** @type {{ label: string, output: string, exitCode: number | null, timedOut: boolean }[]} */
const results = await Promise.all(
tasks.map(
(task) =>
new Promise((resolve) => {
const chunks = /** @type {Buffer[]} */ ([])
const child = spawn('sh', ['-c', task.command], {
cwd: task.cwd,
env: { ...process.env, FORCE_COLOR: '1', NO_COLOR: '' },
stdio: ['ignore', 'pipe', 'pipe'],
})
child.stdout.on('data', (d) => chunks.push(d))
child.stderr.on('data', (d) => chunks.push(d))
const timeout = setTimeout(() => {
child.kill('SIGTERM')
}, timeoutMs)
child.on('close', (code, signal) => {
clearTimeout(timeout)
const timedOut = signal === 'SIGTERM'
const exitCode = timedOut ? 1 : code
if (exitCode === 0) done++
else failed++
resolve({
label: task.label,
output: Buffer.concat(chunks).toString(),
exitCode,
timedOut,
})
})
})
)
)
clearInterval(timer)
process.stderr.write('\r\x1b[K')
const failedTasks = /** @type {typeof results} */ ([])
for (let i = 0; i < results.length; i++) {
const r = results[i]
if (r.exitCode !== 0) {
failedTasks.push(r)
continue
}
const bg = bgColors[i % bgColors.length]
const fg = fgOnBg[i % fgOnBg.length]
const banner = bg(fg.bold(`${r.label} `))
console.log(`\n${banner}`)
if (r.output.trim()) process.stdout.write(r.output)
}
if (failedTasks.length) {
for (const r of failedTasks) {
const banner = chalk.bgRed(
chalk.white.bold(`${r.label} ${r.timedOut ? '· timed out' : '· failed'} `)
)
console.log(`\n${banner}`)
if (r.output.trim()) process.stdout.write(r.output)
}
const summary = failedTasks.map((t) => t.label).join(', ')
console.error(
`\n${chalk.bgRed.white.bold(`${failedTasks.length}/${total} failed: ${summary} `)}`
)
process.exit(1)
}
console.log(`\n${chalk.bgGreen.black.bold(` ✔ All ${total} tasks completed `)}`)
}
+30
View File
@@ -0,0 +1,30 @@
#!/usr/bin/env node
/**
* Restore package.json from the backup created by pre-publish.js,
* then clean up temporary files (backup, LICENSE, README.md).
*
* Usage: node ../../scripts/post-publish.js (from a package dir)
*/
import { existsSync, readFileSync, renameSync, rmSync } from 'fs'
import { join } from 'path'
const pkgPath = join(process.cwd(), 'package.json')
const bakPath = pkgPath + '.bak'
if (!existsSync(bakPath)) {
console.log(' No backup found, nothing to restore.')
process.exit(0)
}
const name = JSON.parse(readFileSync(pkgPath, 'utf-8')).name
renameSync(bakPath, pkgPath)
console.log(' ✓ package.json restored from backup')
rmSync(join(process.cwd(), 'LICENSE'), { force: true })
console.log(' ✓ LICENSE removed')
if (name === 'page-agent') {
rmSync(join(process.cwd(), 'README.md'), { force: true })
console.log(' ✓ README.md removed')
}
+42
View File
@@ -0,0 +1,42 @@
#!/usr/bin/env node
/**
* Backup package.json, then rewrite it for publish:
* - Promote `publishConfig` fields to top level
* - Remove `publishConfig` (npm doesn't need the wrapper)
* - Copy LICENSE (and README.md for the main package)
*
* Usage: node ../../scripts/pre-publish.js (from a package dir)
*/
import { copyFileSync, readFileSync, writeFileSync } from 'fs'
import { join } from 'path'
const pkgPath = join(process.cwd(), 'package.json')
const raw = readFileSync(pkgPath, 'utf-8')
const pkg = JSON.parse(raw)
const publishConfig = pkg.publishConfig
if (!publishConfig) {
console.log(' No publishConfig found, skipping manifest rewrite.')
process.exit(0)
}
// Backup the original file byte-for-byte
copyFileSync(pkgPath, pkgPath + '.bak')
console.log(' ✓ package.json backed up')
for (const [field, value] of Object.entries(publishConfig)) {
pkg[field] = value
}
delete pkg.publishConfig
writeFileSync(pkgPath, JSON.stringify(pkg, null, ' ') + '\n')
console.log(` ✓ Manifest rewritten for publish (${Object.keys(publishConfig).join(', ')})`)
const root = join(process.cwd(), '../..')
copyFileSync(join(root, 'LICENSE'), join(process.cwd(), 'LICENSE'))
console.log(' ✓ LICENSE copied')
if (pkg.name === 'page-agent') {
copyFileSync(join(root, 'README.md'), join(process.cwd(), 'README.md'))
console.log(' ✓ README.md copied')
}
+144
View File
@@ -0,0 +1,144 @@
#!/usr/bin/env node
/**
* Sync version from root package.json to all packages
*
* Usage:
* node scripts/sync-version.js # Sync current version from root
* node scripts/sync-version.js 0.1.0 # Set root version, then sync all packages
*/
import chalk from 'chalk'
import { existsSync, readFileSync, readdirSync, writeFileSync } from 'fs'
import { dirname, join } from 'path'
import { exit } from 'process'
import { fileURLToPath } from 'url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const rootDir = join(__dirname, '..')
const versionArg = process.argv[2]
// Read root package.json
const rootPkgPath = join(rootDir, 'package.json')
const rootPkg = JSON.parse(readFileSync(rootPkgPath, 'utf-8'))
const oldVersion = rootPkg.version
const newVersion = versionArg ?? rootPkg.version
if (!newVersion) {
console.log(chalk.yellow('⚠️ No version found in root package.json.\n'))
exit(1)
}
console.log(chalk.cyan.bold('\n📦 Syncing version\n'))
// Update root package.json if new version specified
if (versionArg) {
rootPkg.version = newVersion
writeFileSync(rootPkgPath, JSON.stringify(rootPkg, null, ' ') + '\n')
console.log(
chalk.green('✓') +
` ${chalk.bold('root')}: ${chalk.dim(oldVersion)}${chalk.yellow(newVersion)}`
)
} else {
console.log(chalk.dim(' root:') + ` ${chalk.yellow(newVersion)} ${chalk.dim('(source)')}`)
}
// Sync to all packages
const packagesDir = join(rootDir, 'packages')
const packages = readdirSync(packagesDir, { withFileTypes: true })
.filter((d) => d.isDirectory())
.map((d) => d.name)
let hasChanges = !!versionArg
/**
* Check if a dependency name is a page-agent internal package
*/
function isInternalPackage(name) {
return name === 'page-agent' || name.startsWith('@page-agent/')
}
/**
* Update internal package versions in dependencies object
* @returns {boolean} Whether any changes were made
*/
function updateInternalDeps(deps, newVersion) {
if (!deps) return false
let changed = false
for (const [name, version] of Object.entries(deps)) {
if (isInternalPackage(name) && version !== newVersion) {
deps[name] = newVersion
changed = true
}
}
return changed
}
for (const pkg of packages) {
const pkgPath = join(packagesDir, pkg, 'package.json')
if (!existsSync(pkgPath)) continue
const pkgJson = JSON.parse(readFileSync(pkgPath, 'utf-8'))
let pkgChanged = false
// Update package version
if (pkgJson.version !== newVersion) {
pkgJson.version = newVersion
pkgChanged = true
}
// Update internal dependencies (dependencies only, devDeps keep "*")
if (updateInternalDeps(pkgJson.dependencies, newVersion)) {
pkgChanged = true
}
if (!pkgChanged) {
console.log(chalk.dim(` ${pkgJson.name}: ${newVersion} (unchanged)`))
continue
}
writeFileSync(pkgPath, JSON.stringify(pkgJson, null, ' ') + '\n')
console.log(
chalk.green('✓') +
` ${chalk.bold(pkgJson.name)}: ${chalk.dim(oldVersion)}${chalk.yellow(newVersion)}`
)
hasChanges = true
}
// Update CDN URLs in documentation and source files
const CDN_DEMO_URL_OLD = `https://cdn.jsdelivr.net/npm/page-agent@${oldVersion}/dist/iife/page-agent.demo.js`
const CDN_DEMO_URL_NEW = `https://cdn.jsdelivr.net/npm/page-agent@${newVersion}/dist/iife/page-agent.demo.js`
const CDN_DEMO_CN_URL_OLD = `https://registry.npmmirror.com/page-agent/${oldVersion}/files/dist/iife/page-agent.demo.js`
const CDN_DEMO_CN_URL_NEW = `https://registry.npmmirror.com/page-agent/${newVersion}/files/dist/iife/page-agent.demo.js`
const filesToUpdateCdn = ['README.md', 'docs/README-zh.md', 'packages/website/src/constants.ts']
for (const relPath of filesToUpdateCdn) {
const filePath = join(rootDir, relPath)
if (!existsSync(filePath)) continue
let content = readFileSync(filePath, 'utf-8')
const original = content
content = content.replaceAll(CDN_DEMO_URL_OLD, CDN_DEMO_URL_NEW)
content = content.replaceAll(CDN_DEMO_CN_URL_OLD, CDN_DEMO_CN_URL_NEW)
if (content !== original) {
writeFileSync(filePath, content)
console.log(chalk.green('✓') + ` ${chalk.bold(relPath)}: CDN URLs updated`)
hasChanges = true
}
}
console.log(chalk.green.bold(`\n✓ Version synced: ${newVersion}\n`))
// Show git commands hint
if (hasChanges) {
const tagName = `v${newVersion}`
console.log(chalk.cyan.bold('📋 Next steps:\n'))
console.log(chalk.blueBright(`npm i`))
console.log(
chalk.blueBright(`git add . && git commit -m "chore(version): bump version to ${newVersion}"`)
)
console.log(chalk.blueBright(`git tag -a ${tagName} -m "${tagName}"`))
console.log(chalk.blueBright(`git push && git push origin ${tagName}\n`))
}