refactor: update import statements to use node built-in modules

chore: update lerna configuration formatting

chore: bump version in package.json to 6.0.0

fix: modify build scripts for improved readability and consistency

feat: enhance translation scripts with better error handling and progress tracking

chore: improve plugin creation script for better user experience

style: format svgo configuration for consistency
This commit is contained in:
Harvey Zack committed 2025-08-03 18:34:30 +08:00
1 parent 3540cd0cce
commit fa755b5f61
11 files changed
+509 -496

No files matched your search

+3 -3
View File
@@ -1,5 +1,5 @@
{ {
"packages": ["packages/*"], "packages": ["packages/*"],
"version": "independent", "version": "independent",
"npmClient": "yarn" "npmClient": "yarn"
} }
+5 -6
View File
@@ -1,7 +1,7 @@
{ {
"name": "artplayer-packages", "name": "artplayer-packages",
"type": "module", "type": "module",
"version": "5.2.0", "version": "6.0.0",
"private": true, "private": true,
"description": "ArtPlayer.js is a modern and full featured HTML5 video player", "description": "ArtPlayer.js is a modern and full featured HTML5 video player",
"workspaces": [ "workspaces": [
@@ -25,17 +25,17 @@
"node": ">= 20.0.0" "node": ">= 20.0.0"
}, },
"scripts": { "scripts": {
"build:all": "npm run build all && npm run build:i18n && npm run build:ts && npm run build:docs", "bootstrap": "npx lerna link",
"lint": "npx eslint packages/*/{src,types,package.json} --fix", "postinstall": "npx lerna run prepare",
"build:ts": "node ./scripts/build-ts.js", "build:ts": "node ./scripts/build-ts.js",
"build:docs": "node ./scripts/build-docs.js", "build:docs": "node ./scripts/build-docs.js",
"build:i18n": "node ./scripts/build-i18n.js", "build:i18n": "node ./scripts/build-i18n.js",
"trans:docs": "node ./scripts/trans-docs.js", "trans:docs": "node ./scripts/trans-docs.js",
"create:plugin": "node ./scripts/plugin/create.js", "create:plugin": "node ./scripts/plugin/create.js",
"lint": "npx eslint packages/*/{src,types,package.json} --fix",
"dev": "npx cross-env NODE_ENV=development node ./scripts/dev.js", "dev": "npx cross-env NODE_ENV=development node ./scripts/dev.js",
"build": "npx cross-env NODE_ENV=production node ./scripts/build.js", "build": "npx cross-env NODE_ENV=production node ./scripts/build.js",
"postinstall": "npx lerna run prepare", "build:all": "npm run build all && npm run build:i18n && npm run build:ts && npm run build:docs"
"bootstrap": "npx lerna link"
}, },
"browserslist": "last 1 Chrome version", "browserslist": "last 1 Chrome version",
"dependencies": { "dependencies": {
@@ -52,7 +52,6 @@
"dotenv": "^17.2.1", "dotenv": "^17.2.1",
"eslint": "^9.32.0", "eslint": "^9.32.0",
"glob": "^11.0.3", "glob": "^11.0.3",
"globals": "^16.3.0",
"lerna": "^8.2.3", "lerna": "^8.2.3",
"prompts": "^2.4.2", "prompts": "^2.4.2",
"servor": "^4.0.2", "servor": "^4.0.2",
+6 -5
View File
@@ -1,8 +1,9 @@
import spawn from 'cross-spawn'; import process from 'node:process'
import spawn from 'cross-spawn'
const proc = spawn('npm', ['run', 'build'], { const proc = spawn('npm', ['run', 'build'], {
cwd: './packages/artplayer-vitepress/', cwd: './packages/artplayer-vitepress/',
}); })
proc.stdout.pipe(process.stdout); proc.stdout.pipe(process.stdout)
proc.stderr.pipe(process.stderr); proc.stderr.pipe(process.stderr)
+37 -36
View File
@@ -1,45 +1,46 @@
import fs from 'fs'; import fs from 'node:fs'
import path from 'path'; import path from 'node:path'
import { glob } from 'glob'; import { Parcel } from '@parcel/core'
import { Parcel } from '@parcel/core'; import { glob } from 'glob'
const basePath = 'packages/artplayer'; const basePath = 'packages/artplayer'
const entries = glob.sync(path.join(basePath, 'src/i18n/*.js'), { const entries = glob.sync(path.join(basePath, 'src/i18n/*.js'), {
ignore: [path.join(basePath, 'src/i18n/index.js'), path.join(basePath, 'src/i18n/zh-cn.js')], ignore: [path.join(basePath, 'src/i18n/index.js'), path.join(basePath, 'src/i18n/zh-cn.js')],
}); })
const packagePath = path.join(basePath, 'package.json'); const packagePath = path.join(basePath, 'package.json')
const backupPackagePath = path.join(basePath, 'package_backup.json'); const backupPackagePath = path.join(basePath, 'package_backup.json');
(async function build() { (async function build() {
fs.renameSync(packagePath, backupPackagePath); fs.renameSync(packagePath, backupPackagePath)
const bundler = new Parcel({ const bundler = new Parcel({
entries: entries, entries,
defaultConfig: '@parcel/config-default', defaultConfig: '@parcel/config-default',
mode: 'production', mode: 'production',
defaultTargetOptions: { defaultTargetOptions: {
distDir: 'packages/artplayer/dist/i18n', distDir: 'packages/artplayer/dist/i18n',
outputFormat: 'global', outputFormat: 'global',
sourceMaps: false, sourceMaps: false,
isLibrary: true, isLibrary: true,
engines: { engines: {
browsers: ['last 1 Chrome version'], browsers: ['last 1 Chrome version'],
}, },
}, },
env: { env: {
NODE_ENV: 'production', NODE_ENV: 'production',
}, },
}); })
try { try {
const { bundleGraph, buildTime } = await bundler.run(); const { bundleGraph, buildTime } = await bundler.run()
const bundles = bundleGraph.getBundles(); const bundles = bundleGraph.getBundles()
console.log(`✨ Built [i18n] ${bundles.length} bundles in ${buildTime}ms`); console.log(`✨ Built [i18n] ${bundles.length} bundles in ${buildTime}ms`)
fs.renameSync(backupPackagePath, packagePath); fs.renameSync(backupPackagePath, packagePath)
} catch (err) { }
console.log(err.diagnostics); catch (err) {
fs.renameSync(backupPackagePath, packagePath); console.log(err.diagnostics)
} fs.renameSync(backupPackagePath, packagePath)
})(); }
})()
+126 -124
View File
@@ -1,161 +1,163 @@
import fs from 'fs'; import fs from 'node:fs'
import cpy from 'cpy'; import path from 'node:path'
import path from 'path'; import process from 'node:process'
import prompts from 'prompts'; import { Parcel } from '@parcel/core'
import { Parcel } from '@parcel/core'; import cpy from 'cpy'
import { getProjects } from './utils.js'; import prompts from 'prompts'
import { getProjects } from './utils.js'
const projects = getProjects(); const projects = getProjects()
const compiledPath = path.resolve('docs/compiled'); const compiledPath = path.resolve('docs/compiled')
function compressString(input) { function compressString(input) {
return input return input
.replace(/\s*(<[^>]+>)\s*/g, '$1') .replace(/\s*(<[^>]+>)\s*/g, '$1')
.replace(/(\r\n|\n|\r)/gm, ' ') .replace(/(\r\n|\n|\r)/g, ' ')
.replace(/\s+/g, ' ') .replace(/\s+/g, ' ')
.trim(); .trim()
} }
async function build(name, targetName) { async function build(name, targetName) {
const projectPackageJson = path.join(projects[name], 'package.json'); const projectPackageJson = path.join(projects[name], 'package.json')
const { version } = JSON.parse(fs.readFileSync(projectPackageJson, 'utf-8')); const { version } = JSON.parse(fs.readFileSync(projectPackageJson, 'utf-8'))
process.chdir(projects[name]); process.chdir(projects[name])
const targets = { const targets = {
main: { main: {
context: 'browser', context: 'browser',
distDir: path.join(projects[name], 'dist'), distDir: path.join(projects[name], 'dist'),
sourceMap: false, sourceMap: false,
outputFormat: 'global', outputFormat: 'global',
isLibrary: true, isLibrary: true,
engines: { engines: {
browsers: 'last 1 Chrome version', browsers: 'last 1 Chrome version',
}, },
}, },
legacy: { legacy: {
context: 'browser', context: 'browser',
distDir: path.join(projects[name], 'dist'), distDir: path.join(projects[name], 'dist'),
sourceMap: false, sourceMap: false,
outputFormat: 'global', outputFormat: 'global',
isLibrary: true, isLibrary: true,
engines: { engines: {
browsers: 'IE 11', browsers: 'IE 11',
}, },
}, },
esm: { esm: {
context: 'browser', context: 'browser',
distDir: path.join(projects[name], 'dist'), distDir: path.join(projects[name], 'dist'),
sourceMap: true, sourceMap: true,
outputFormat: 'esmodule', outputFormat: 'esmodule',
isLibrary: true, isLibrary: true,
engines: { engines: {
browsers: 'last 1 Chrome version', browsers: 'last 1 Chrome version',
}, },
}, },
}; }
const names = { const names = {
main: name, main: name,
legacy: `${name}.legacy`, legacy: `${name}.legacy`,
esm: `${name}.esm`, esm: `${name}.esm`,
}; }
const entryFile = path.join(projects[name], 'src/index.js'); const entryFile = path.join(projects[name], 'src/index.js')
const bundler = new Parcel({ const bundler = new Parcel({
entries: entryFile, entries: entryFile,
defaultConfig: '@parcel/config-default', defaultConfig: '@parcel/config-default',
mode: 'production', mode: 'production',
targets: { targets: {
[targetName]: targets[targetName], [targetName]: targets[targetName],
}, },
env: { env: {
NODE_ENV: 'production', NODE_ENV: 'production',
}, },
}); })
const banner = ` const banner = `
/*! /*!
* ${name}.js v${version} * ${name}.js v${version}
* Github: https://github.com/zhw2590582/ArtPlayer * Github: https://github.com/zhw2590582/ArtPlayer
* (c) 2017-${new Date().getFullYear()} Harvey Zack * (c) 2017-${new Date().getFullYear()} Harvey Zack
* Released under the MIT License. * Released under the MIT License.
*/ */
`; `
const { bundleGraph, buildTime } = await bundler.run(); const { bundleGraph, buildTime } = await bundler.run()
const bundles = bundleGraph.getBundles(); const bundles = bundleGraph.getBundles()
const filePath = path.join(projects[name], 'dist/index.js'); const filePath = path.join(projects[name], 'dist/index.js')
const newFilePath = path.join(projects[name], `dist/${names[targetName]}.js`); const newFilePath = path.join(projects[name], `dist/${names[targetName]}.js`)
const code = fs.readFileSync(filePath, 'utf-8'); const code = fs.readFileSync(filePath, 'utf-8')
fs.writeFileSync(filePath, banner + compressString(code)); fs.writeFileSync(filePath, banner + compressString(code))
fs.renameSync(filePath, newFilePath); fs.renameSync(filePath, newFilePath)
const size = fs.statSync(newFilePath).size / 1024; const size = fs.statSync(newFilePath).size / 1024
await cpy(newFilePath, compiledPath); await cpy(newFilePath, compiledPath)
if (targetName === 'esm') { if (targetName === 'esm') {
const jsFile = path.join(projects[name], `dist/${names.esm}.js`); const jsFile = path.join(projects[name], `dist/${names.esm}.js`)
const oldMapName = 'index.js.map'; const oldMapName = 'index.js.map'
const newMapName = `${names.esm}.js.map`; const newMapName = `${names.esm}.js.map`
const oldMapPath = path.join(projects[name], 'dist', oldMapName); const oldMapPath = path.join(projects[name], 'dist', oldMapName)
const newMapPath = path.join(projects[name], 'dist', newMapName); const newMapPath = path.join(projects[name], 'dist', newMapName)
if (fs.existsSync(oldMapPath)) { if (fs.existsSync(oldMapPath)) {
fs.renameSync(oldMapPath, newMapPath); fs.renameSync(oldMapPath, newMapPath)
}
let jsContent = fs.readFileSync(jsFile, 'utf8');
jsContent = jsContent.replace(/\/\/# sourceMappingURL=.*\.js\.map/, `\n//# sourceMappingURL=${newMapName}`);
fs.writeFileSync(jsFile, jsContent);
await cpy(newMapPath, compiledPath);
await cpy(newFilePath, compiledPath);
} }
console.log( let jsContent = fs.readFileSync(jsFile, 'utf8')
`✨ Built@${targetName} ${name}@${version}`, jsContent = jsContent.replace(/\/\/# sourceMappingURL=.*\.js\.map/, `\n//# sourceMappingURL=${newMapName}`)
`Bundles@${bundles.length}`, fs.writeFileSync(jsFile, jsContent)
`Time@${buildTime}ms`, await cpy(newMapPath, compiledPath)
`Size@${size.toFixed(2)}kb`, await cpy(newFilePath, compiledPath)
); }
console.log(
`✨ Built@${targetName} ${name}@${version}`,
`Bundles@${bundles.length}`,
`Time@${buildTime}ms`,
`Size@${size.toFixed(2)}kb`,
)
} }
async function runBuild() { async function runBuild() {
if (process.argv.pop() === 'all') { if (process.argv.pop() === 'all') {
const bundles = Object.keys(projects).map((name) => async () => { const bundles = Object.keys(projects).map(name => async () => {
await build(name, 'main'); await build(name, 'main')
await build(name, 'legacy'); await build(name, 'legacy')
await build(name, 'esm'); await build(name, 'esm')
}); })
await runPromisesInSeries(bundles); await runPromisesInSeries(bundles)
console.log('✅ Finished building all packages!'); console.log('✅ Finished building all packages!')
} else { }
const response = await prompts({ else {
type: 'select', const response = await prompts({
name: 'value', type: 'select',
message: 'Which project do you want to build?', name: 'value',
choices: Object.keys(projects).map((name) => ({ message: 'Which project do you want to build?',
title: name, choices: Object.keys(projects).map(name => ({
value: name, title: name,
})), value: name,
initial: 0, })),
}); initial: 0,
})
if (response.value) { if (response.value) {
await build(response.value, 'main'); await build(response.value, 'main')
await build(response.value, 'legacy'); await build(response.value, 'legacy')
await build(response.value, 'esm'); await build(response.value, 'esm')
}
} }
}
} }
function runPromisesInSeries(ps) { function runPromisesInSeries(ps) {
return ps.reduce((p, next) => p.then(next), Promise.resolve()); return ps.reduce((p, next) => p.then(next), Promise.resolve())
} }
runBuild().catch((error) => { runBuild().catch((error) => {
console.error('❌ Build script encountered an error:', error); console.error('❌ Build script encountered an error:', error)
}); })
+78 -76
View File
@@ -1,92 +1,94 @@
import path from 'path'; import path from 'node:path'
import servor from 'servor'; import process from 'node:process'
import prompts from 'prompts'; import { fileURLToPath } from 'node:url'
import { Parcel } from '@parcel/core'; import { Parcel } from '@parcel/core'
import { fileURLToPath } from 'url'; import prompts from 'prompts'
import { getProjects } from './utils.js'; import servor from 'servor'
import openBrowser from 'servor/utils/openBrowser.js'; import openBrowser from 'servor/utils/openBrowser.js'
import { getProjects } from './utils.js'
const projects = getProjects(); const projects = getProjects()
async function develop(name) { async function develop(name) {
const projectPath = projects[name]; const projectPath = projects[name]
const uncompiledPath = path.resolve(`docs/uncompiled/${name}`); const uncompiledPath = path.resolve(`docs/uncompiled/${name}`)
const entryFile = path.join(projectPath, 'src/index.js'); const entryFile = path.join(projectPath, 'src/index.js')
let isOpenBrowser = false; let isOpenBrowser = false
const { url } = await servor({ const { url } = await servor({
root: 'docs', root: 'docs',
fallback: 'index.html', fallback: 'index.html',
reload: true, reload: true,
port: 8082, port: 8082,
}); })
process.chdir(projectPath); process.chdir(projectPath)
const bundler = new Parcel({ const bundler = new Parcel({
entries: entryFile, entries: entryFile,
defaultConfig: '@parcel/config-default', defaultConfig: '@parcel/config-default',
mode: 'development', mode: 'development',
targets: { targets: {
main: { main: {
distDir: uncompiledPath, distDir: uncompiledPath,
outputFormat: 'global', outputFormat: 'global',
isLibrary: true, isLibrary: true,
engines: { engines: {
browsers: ['last 1 Chrome version'], browsers: ['last 1 Chrome version'],
},
},
}, },
env: { },
NODE_ENV: 'development', },
}, env: {
additionalReporters: [ NODE_ENV: 'development',
{ },
packageName: '@parcel/reporter-cli', additionalReporters: [
resolveFrom: fileURLToPath(import.meta.url), {
}, packageName: '@parcel/reporter-cli',
], resolveFrom: fileURLToPath(import.meta.url),
}); },
],
})
bundler.watch((error, event) => { bundler.watch((error, event) => {
if (error) { if (error) {
console.error(`[${name}] ❌ Build error:`, error); console.error(`[${name}] ❌ Build error:`, error)
return; return
} }
if (event.type === 'buildSuccess') { if (event.type === 'buildSuccess') {
const bundles = event.bundleGraph.getBundles(); const bundles = event.bundleGraph.getBundles()
console.log(`[${name}] ✅ Built ${bundles.length} bundles in ${event.buildTime}ms`); console.log(`[${name}] ✅ Built ${bundles.length} bundles in ${event.buildTime}ms`)
if (!isOpenBrowser) { if (!isOpenBrowser) {
isOpenBrowser = true; isOpenBrowser = true
openBrowser(url); openBrowser(url)
} }
} }
if (event.type === 'buildFailure') { if (event.type === 'buildFailure') {
console.error(`[${name}] ❌ Build failure:`, event.diagnostics); console.error(`[${name}] ❌ Build failure:`, event.diagnostics)
} }
}); })
} }
(async () => { (async () => {
try { try {
const response = await prompts({ const response = await prompts({
type: 'select', type: 'select',
name: 'value', name: 'value',
message: 'Which project do you want to develop?', message: 'Which project do you want to develop?',
choices: Object.keys(projects).map((name) => ({ choices: Object.keys(projects).map(name => ({
title: name, title: name,
value: name, value: name,
})), })),
initial: 0, initial: 0,
}); })
if (response.value) { if (response.value) {
await develop(response.value); await develop(response.value)
}
} catch (error) {
console.error(`❌ Prompt error: ${error.message}`);
} }
})(); }
catch (error) {
console.error(`❌ Prompt error: ${error.message}`)
}
})()
+54 -52
View File
@@ -1,81 +1,83 @@
import fs from 'fs'; import fs from 'node:fs'
import path from 'path'; import path from 'node:path'
import { fileURLToPath } from 'url'; import process from 'node:process'
import { fileURLToPath } from 'node:url'
// Get the current file path // Get the current file path
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename)
// Get the plugin name from command line arguments // Get the plugin name from command line arguments
const args = process.argv.slice(2); const args = process.argv.slice(2)
const pluginName = args[0]; const pluginName = args[0]
if (!pluginName) { if (!pluginName) {
console.error('Please provide a plugin name, e.g., npm run create:plugin somePluginName'); console.error('Please provide a plugin name, e.g., npm run create:plugin somePluginName')
process.exit(1); process.exit(1)
} }
// Validate plugin name // Validate plugin name
const pluginNameRegex = /^[a-z-]+$/; const pluginNameRegex = /^[a-z-]+$/
if (!pluginNameRegex.test(pluginName)) { if (!pluginNameRegex.test(pluginName)) {
console.error('Invalid plugin name. Only lowercase letters and hyphens are allowed.'); console.error('Invalid plugin name. Only lowercase letters and hyphens are allowed.')
process.exit(1); process.exit(1)
} }
// Convert plugin name to export name (PascalCase) // Convert plugin name to export name (PascalCase)
const exportName = pluginName const exportName = pluginName
.split('-') .split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(''); .join('')
const exportNameFull = `artplayerPlugin${exportName}`; const exportNameFull = `artplayerPlugin${exportName}`
// Define paths // Define paths
const templateDir = path.join(__dirname, 'template'); const templateDir = path.join(__dirname, 'template')
const destDir = path.join(__dirname, '../../packages', `artplayer-plugin-${pluginName}`); const destDir = path.join(__dirname, '../../packages', `artplayer-plugin-${pluginName}`)
const exampleDir = path.join(__dirname, '../../docs/assets/example'); const exampleDir = path.join(__dirname, '../../docs/assets/example')
const exampleFileName = `${pluginName.replace(/-/g, '.')}.js`; const exampleFileName = `${pluginName.replace(/-/g, '.')}.js`
const exampleFile = path.join(exampleDir, exampleFileName); const exampleFile = path.join(exampleDir, exampleFileName)
// Check if a plugin with the same name already exists // Check if a plugin with the same name already exists
if (fs.existsSync(destDir)) { if (fs.existsSync(destDir)) {
console.error(`Plugin ${pluginName} already exists. Please choose another name.`); console.error(`Plugin ${pluginName} already exists. Please choose another name.`)
process.exit(1); process.exit(1)
} }
// Create the destination directory // Create the destination directory
fs.mkdirSync(destDir, { recursive: true }); fs.mkdirSync(destDir, { recursive: true })
// Read files from the template directory and copy them to the destination directory // Read files from the template directory and copy them to the destination directory
function copyTemplateFiles(templateDir, destDir) { function copyTemplateFiles(templateDir, destDir) {
const files = fs.readdirSync(templateDir); const files = fs.readdirSync(templateDir)
files.forEach((file) => { files.forEach((file) => {
const templateFilePath = path.join(templateDir, file); const templateFilePath = path.join(templateDir, file)
let destFilePath = path.join(destDir, file); let destFilePath = path.join(destDir, file)
const stats = fs.statSync(templateFilePath); const stats = fs.statSync(templateFilePath)
if (stats.isDirectory()) { if (stats.isDirectory()) {
fs.mkdirSync(destFilePath, { recursive: true }); fs.mkdirSync(destFilePath, { recursive: true })
copyTemplateFiles(templateFilePath, destFilePath); copyTemplateFiles(templateFilePath, destFilePath)
} else { }
// Replace placeholders in the file name else {
destFilePath = destFilePath.replace(/{{name}}/g, pluginName); // Replace placeholders in the file name
destFilePath = destFilePath.replace(/\{\{name\}\}/g, pluginName)
let content = fs.readFileSync(templateFilePath, 'utf8'); let content = fs.readFileSync(templateFilePath, 'utf8')
// Replace placeholders in the file content // Replace placeholders in the file content
content = content.replace(/{{name}}/g, pluginName); content = content.replace(/\{\{name\}\}/g, pluginName)
content = content.replace(/{{export}}/g, exportNameFull); content = content.replace(/\{\{export\}\}/g, exportNameFull)
fs.writeFileSync(destFilePath, content); fs.writeFileSync(destFilePath, content)
} }
}); })
} }
// Create the example file // Create the example file
function createExampleFile() { function createExampleFile() {
const exampleContent = ` const exampleContent = `
// npm i artplayer-plugin-${pluginName} // npm i artplayer-plugin-${pluginName}
// import ${exportNameFull} from 'artplayer-plugin-${pluginName}'; // import ${exportNameFull} from 'artplayer-plugin-${pluginName}';
@@ -88,18 +90,18 @@ var art = new Artplayer({
}), }),
], ],
}); });
`.trim(); `.trim()
if (!fs.existsSync(exampleDir)) { if (!fs.existsSync(exampleDir)) {
fs.mkdirSync(exampleDir, { recursive: true }); fs.mkdirSync(exampleDir, { recursive: true })
} }
fs.writeFileSync(exampleFile, exampleContent); fs.writeFileSync(exampleFile, exampleContent)
console.log(`Example file created at ${exampleFile}`); console.log(`Example file created at ${exampleFile}`)
} }
// Start copying template files and creating example file // Start copying template files and creating example file
copyTemplateFiles(templateDir, destDir); copyTemplateFiles(templateDir, destDir)
createExampleFile(); createExampleFile()
console.log(`Plugin ${pluginName} has been successfully created at ${destDir}`); console.log(`Plugin ${pluginName} has been successfully created at ${destDir}`)
+22 -22
View File
@@ -1,24 +1,24 @@
{ {
"license": "MIT", "name": "artplayer-plugin-{{name}}",
"version": "1.0.0", "version": "1.0.0",
"name": "artplayer-plugin-{{name}}", "description": "{{name}} plugin for ArtPlayer",
"types": "types/artplayer-plugin-{{name}}.d.ts", "author": "Harvey Zack <laozhaochaguan@gmail.com>",
"description": "{{name}} plugin for ArtPlayer", "license": "MIT",
"main": "dist/artplayer-plugin-{{name}}.js", "homepage": "https://artplayer.org",
"legacy": "dist/artplayer-plugin-{{name}}.legacy.js", "repository": {
"author": "Harvey Zack <laozhaochaguan@gmail.com>", "type": "git",
"browserslist": "last 1 Chrome version", "url": "git+https://github.com/zhw2590582/ArtPlayer.git"
"homepage": "https://artplayer.org", },
"repository": { "bugs": {
"type": "git", "url": "https://github.com/zhw2590582/ArtPlayer/issues"
"url": "git+https://github.com/zhw2590582/ArtPlayer.git" },
}, "keywords": [
"bugs": { "html5",
"url": "https://github.com/zhw2590582/ArtPlayer/issues" "video",
}, "player"
"keywords": [ ],
"html5", "main": "dist/artplayer-plugin-{{name}}.js",
"video", "types": "types/artplayer-plugin-{{name}}.d.ts",
"player" "legacy": "dist/artplayer-plugin-{{name}}.legacy.js",
] "browserslist": "last 1 Chrome version"
} }
+143 -139
View File
@@ -1,171 +1,175 @@
import fs from 'fs'; import fs from 'node:fs'
import cpy from 'cpy'; import path from 'node:path'
import path from 'path'; import process from 'node:process'
import { glob } from 'glob'; import cpy from 'cpy'
import { removeDir } from './utils.js'; import dotenv from 'dotenv'
import dotenv from 'dotenv'; import { glob } from 'glob'
import { removeDir } from './utils.js'
dotenv.config(); dotenv.config()
// 配置常量 // 配置常量
const MAX_RETRIES = 3; const MAX_RETRIES = 3
const RETRY_DELAY = 1000; // 1秒 const RETRY_DELAY = 1000 // 1秒
const MAX_CHARS_PER_REQUEST = 128000; const MAX_CHARS_PER_REQUEST = 128000
const BASE_PATH = 'packages/artplayer-vitepress/docs'; const BASE_PATH = 'packages/artplayer-vitepress/docs'
const EN_PATH = 'packages/artplayer-vitepress/docs/en'; const EN_PATH = 'packages/artplayer-vitepress/docs/en'
const COPY_DIRS = ['advanced', 'component', 'plugin', 'start']; const COPY_DIRS = ['advanced', 'component', 'plugin', 'start']
function splitMarkdown(text, maxChars) { function splitMarkdown(text, maxChars) {
const parts = []; const parts = []
let currentPart = ''; let currentPart = ''
let inCodeBlock = false; let inCodeBlock = false
let inSpecialBlock = false; let inSpecialBlock = false
const lines = text.split('\n'); const lines = text.split('\n')
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
const line = lines[i]; const line = lines[i]
// 检测代码块 // 检测代码块
if (line.trim().startsWith('```')) { if (line.trim().startsWith('```')) {
inCodeBlock = !inCodeBlock; inCodeBlock = !inCodeBlock
}
// 检测特殊块
if (line.trim().startsWith(':::')) {
inSpecialBlock = !inSpecialBlock;
}
// 添加当前行到当前部分
currentPart += line + '\n';
// 检测是否达到字符限制
if (!inCodeBlock && !inSpecialBlock && currentPart.length > maxChars && i < lines.length - 1) {
parts.push(currentPart);
currentPart = '';
}
} }
// 添加最后一部分 // 检测特殊块
if (currentPart !== '') { if (line.trim().startsWith(':::')) {
parts.push(currentPart); inSpecialBlock = !inSpecialBlock
} }
return parts; // 添加当前行到当前部分
currentPart += `${line}\n`
// 检测是否达到字符限制
if (!inCodeBlock && !inSpecialBlock && currentPart.length > maxChars && i < lines.length - 1) {
parts.push(currentPart)
currentPart = ''
}
}
// 添加最后一部分
if (currentPart !== '') {
parts.push(currentPart)
}
return parts
} }
async function fetchWithRetry(url, options, retries = MAX_RETRIES) { async function fetchWithRetry(url, options, retries = MAX_RETRIES) {
try { try {
const response = await fetch(url, { const response = await fetch(url, {
...options, ...options,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
...options.headers, ...options.headers,
}, },
}); })
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`); throw new Error(`HTTP error! status: ${response.status}`)
}
return await response.json();
} catch (error) {
if (retries > 0) {
console.log(`Retrying... (${MAX_RETRIES - retries + 1}/${MAX_RETRIES})`);
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY));
return fetchWithRetry(url, options, retries - 1);
}
throw error;
} }
return await response.json()
}
catch (error) {
if (retries > 0) {
console.log(`Retrying... (${MAX_RETRIES - retries + 1}/${MAX_RETRIES})`)
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY))
return fetchWithRetry(url, options, retries - 1)
}
throw error
}
} }
const translateContent = async (content, targetLanguage) => { async function translateContent(content, targetLanguage) {
try { try {
const TRANSLATE_URL = 'https://api.deepseek.com/chat/completions'; const TRANSLATE_URL = 'https://api.deepseek.com/chat/completions'
const response = await fetchWithRetry(TRANSLATE_URL, { const response = await fetchWithRetry(TRANSLATE_URL, {
headers: { headers: {
Authorization: `Bearer ${process.env.DEEPL_API_KEY}`, Authorization: `Bearer ${process.env.DEEPL_API_KEY}`,
}, },
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
stream: false, stream: false,
model: 'deepseek-chat', model: 'deepseek-chat',
messages: [ messages: [
{ {
role: 'user', role: 'user',
content: `The following text is written in VitePress's extended Markdown syntax, Please translate it into ${targetLanguage}, and keep the Markdown format and don't add your explanation:\n\n${content}`, content: `The following text is written in VitePress's extended Markdown syntax, Please translate it into ${targetLanguage}, and keep the Markdown format and don't add your explanation:\n\n${content}`,
}, },
], ],
}), }),
}); })
return response.choices[0].message.content; return response.choices[0].message.content
} catch (error) { }
console.error(`Translation failed: ${error.message}`); catch (error) {
return content; // 返回原文作为回退 console.error(`Translation failed: ${error.message}`)
} return content // 返回原文作为回退
}; }
}
class ProgressTracker { class ProgressTracker {
constructor(totalParts) { constructor(totalParts) {
this.totalParts = totalParts; this.totalParts = totalParts
this.translatedPartsCount = 0; this.translatedPartsCount = 0
} }
update() { update() {
this.translatedPartsCount++; this.translatedPartsCount++
const progressPercentage = ((this.translatedPartsCount / this.totalParts) * 100).toFixed(2); const progressPercentage = ((this.translatedPartsCount / this.totalParts) * 100).toFixed(2)
console.log(`Overall progress: ${progressPercentage}% (${this.translatedPartsCount}/${this.totalParts})`); console.log(`Overall progress: ${progressPercentage}% (${this.translatedPartsCount}/${this.totalParts})`)
} }
} }
const translateMarkdownFiles = async (filePaths, targetLanguage, maxCharsPerRequest) => { async function translateMarkdownFiles(filePaths, targetLanguage, maxCharsPerRequest) {
// 先计算总共有多少部分需要翻译 // 先计算总共有多少部分需要翻译
let totalParts = 0; let totalParts = 0
for (const filePath of filePaths) { for (const filePath of filePaths) {
const content = fs.readFileSync(filePath, 'utf-8'); const content = fs.readFileSync(filePath, 'utf-8')
totalParts += splitMarkdown(content, maxCharsPerRequest).length; totalParts += splitMarkdown(content, maxCharsPerRequest).length
}
console.log(`Starting translation of ${filePaths.length} files, total ${totalParts} parts.`)
const progressTracker = new ProgressTracker(totalParts)
for (const filePath of filePaths) {
console.log(`Translating: ${path.relative(EN_PATH, filePath)}`)
const content = fs.readFileSync(filePath, 'utf-8')
const parts = splitMarkdown(content, maxCharsPerRequest)
const translatedParts = []
for (const part of parts) {
const translatedPart = await translateContent(part, targetLanguage)
translatedParts.push(translatedPart)
progressTracker.update()
} }
console.log(`Starting translation of ${filePaths.length} files, total ${totalParts} parts.`); const translatedContent = translatedParts.join('\n')
const progressTracker = new ProgressTracker(totalParts); fs.writeFileSync(filePath, translatedContent)
}
for (const filePath of filePaths) { }
console.log(`Translating: ${path.relative(EN_PATH, filePath)}`);
const content = fs.readFileSync(filePath, 'utf-8');
const parts = splitMarkdown(content, maxCharsPerRequest);
const translatedParts = [];
for (const part of parts) {
const translatedPart = await translateContent(part, targetLanguage);
translatedParts.push(translatedPart);
progressTracker.update();
}
const translatedContent = translatedParts.join('\n');
fs.writeFileSync(filePath, translatedContent);
}
};
async function setupDirectoryStructure() { async function setupDirectoryStructure() {
// 清理并创建目录结构 // 清理并创建目录结构
removeDir(EN_PATH); removeDir(EN_PATH)
await cpy(path.resolve(BASE_PATH, 'index.md'), path.resolve(EN_PATH)); await cpy(path.resolve(BASE_PATH, 'index.md'), path.resolve(EN_PATH))
// 并行复制所有目录 // 并行复制所有目录
await Promise.all( await Promise.all(
COPY_DIRS.map((dir) => cpy(path.resolve(BASE_PATH, dir), path.resolve(EN_PATH, dir), { flat: true })), COPY_DIRS.map(dir => cpy(path.resolve(BASE_PATH, dir), path.resolve(EN_PATH, dir), { flat: true })),
); )
} }
(async () => { (async () => {
try { try {
await setupDirectoryStructure(); await setupDirectoryStructure()
const mdsPaths = glob.sync(path.resolve(EN_PATH, '**/*.md')); const mdsPaths = glob.sync(path.resolve(EN_PATH, '**/*.md'))
await translateMarkdownFiles(mdsPaths, 'English', MAX_CHARS_PER_REQUEST); await translateMarkdownFiles(mdsPaths, 'English', MAX_CHARS_PER_REQUEST)
console.log('Translation completed successfully!'); console.log('Translation completed successfully!')
} catch (error) { }
console.error('Translation failed:', error); catch (error) {
process.exit(1); console.error('Translation failed:', error)
} process.exit(1)
})(); }
})()
+26 -24
View File
@@ -1,30 +1,32 @@
import fs from 'fs'; import fs from 'node:fs'
import path from 'path'; import path from 'node:path'
import { glob } from 'glob'; import process from 'node:process'
import { glob } from 'glob'
export function getProjects() { export function getProjects() {
return glob return glob
.sync('packages/*') .sync('packages/*')
.sort() .sort()
.filter((item) => !item.endsWith('artplayer-template') && !item.endsWith('artplayer-vitepress')) .filter(item => !item.endsWith('artplayer-template') && !item.endsWith('artplayer-vitepress'))
.reduce((result, item) => { .reduce((result, item) => {
const name = item.split(/\/|\\/g).pop(); const name = item.split(/\/|\\/g).pop()
result[name] = path.resolve(process.cwd(), item); result[name] = path.resolve(process.cwd(), item)
return result; return result
}, {}); }, {})
} }
export function removeDir(dir) { export function removeDir(dir) {
if (fs.existsSync(dir)) { if (fs.existsSync(dir)) {
const files = fs.readdirSync(dir); const files = fs.readdirSync(dir)
files.forEach((file) => { files.forEach((file) => {
const curPath = path.join(dir, file); const curPath = path.join(dir, file)
if (fs.statSync(curPath).isDirectory()) { if (fs.statSync(curPath).isDirectory()) {
removeDir(curPath); removeDir(curPath)
} else { }
fs.unlinkSync(curPath); else {
} fs.unlinkSync(curPath)
}); }
fs.rmdirSync(dir); })
} fs.rmdirSync(dir)
}
} }
+9 -9
View File
@@ -1,12 +1,12 @@
{ {
"plugins": [ "plugins": [
{ {
"name": "preset-default", "name": "preset-default",
"params": { "params": {
"overrides": { "overrides": {
"removeViewBox": false "removeViewBox": false
}
}
} }
] }
}
]
} }