feat: migrated packages to ts

This commit is contained in:
Haoyu Xu
2025-05-02 02:27:42 +08:00
parent 0af0c785d4
commit 8f6f537c81
111 changed files with 3166 additions and 1155 deletions

3
.gitignore vendored
View File

@@ -138,4 +138,5 @@ _*.json
assets/*
temp/*
.turbo
data/*
data/*
release

10
.vscode/launch.json vendored
View File

@@ -1,5 +1,6 @@
{
"configurations": [
{
"name": "Launch Chrome",
"request": "launch",
@@ -110,6 +111,15 @@
"request": "launch",
"command": "pnpm run deploy",
"cwd": "${workspaceFolder}"
},{
"type": "bun",
"name": "Run Script: build",
"request": "launch",
"env": {
"mode": "build"
},
"program": "index.ts",
"cwd": "${workspaceFolder}"
},
]
}

View File

@@ -3,6 +3,9 @@ import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import baseConfig from '@aklive2d/eslint-config'
import { tsConfig } from '@aklive2d/eslint-config'
import tseslint from 'typescript-eslint'
import globals from 'globals'
/** @type {import('eslint').Config} */
export default [
@@ -27,4 +30,21 @@ export default [
],
},
},
...tsConfig,
{
files: ['**/*.js', '**/*.ts'],
languageOptions: {
ecmaVersion: 2022,
globals: {
...globals.browser,
},
},
rules: {
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_' },
],
},
},
]

View File

@@ -5,9 +5,9 @@
"type": "module",
"scripts": {
"dev:directory": "vite --clearScreen false",
"build": "mode=build node runner.js",
"build": "mode=build bun runner.ts",
"preview:directory": "vite preview",
"lint": "eslint \"src/**/*.js\" \"src/**/*.jsx\" && stylelint \"src/**/*.css\" \"src/**/*.scss\" && prettier --check ."
"lint": "eslint && stylelint \"src/**/*.css\" \"src/**/*.scss\" && prettier --check ."
},
"dependencies": {
"react": "^19.0.0",
@@ -28,6 +28,11 @@
"@aklive2d/module": "workspace:*",
"@aklive2d/prettier-config": "workspace:*"
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript-eslint": ">=8.31.1",
"typescript": ">=5.8.2"
},
"devDependencies": {
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",

View File

@@ -1,11 +1,15 @@
import path from 'node:path'
import { DIST_DIR } from '@aklive2d/showcase'
import { build as viteBuild } from 'vite'
import { envParser } from '@aklive2d/libs'
import { envParser, file } from '@aklive2d/libs'
const build = async (namesToBuild) => {
const build = async (namesToBuild: string[]) => {
if (!namesToBuild.length) {
// skip as directory can only build
// when all operators are built
await viteBuild()
const releaseDir = path.resolve(DIST_DIR)
file.rmdir(releaseDir)
}
}
@@ -18,7 +22,7 @@ async function main() {
default: [],
},
})
await build(name)
await build(name as string[])
}
main()

View File

@@ -192,7 +192,7 @@ export default function Home() {
>
{officialUpdate.dates
.reduce((acc, cur) => {
const op = officialUpdate[cur]
const op = officialUpdate.info[cur]
return [...acc, ...op]
}, [])
.slice(

View File

@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2024",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2024", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["**/*"],
"exclude": ["spine-ts/**/*"]
}

View File

@@ -8,12 +8,12 @@ import { copyDirectoryData } from '@aklive2d/vite-helpers'
// https://vite.dev/config/
export default defineConfig(async () => {
const dataDir = path.resolve(import.meta.dirname, config.dir_name.data)
const publicDir = path.resolve(showcaseDirs.DIST_DIR)
await copyDirectoryData({ dataDir, publicDir })
const releaseDir = path.resolve(showcaseDirs.DIST_DIR)
await copyDirectoryData({ dataDir, publicDir: releaseDir })
return {
envDir: dataDir,
plugins: [react()],
publicDir,
publicDir: releaseDir,
resolve: {
alias: {
'@': path.resolve('./src'),
@@ -22,7 +22,12 @@ export default defineConfig(async () => {
},
build: {
emptyOutDir: false,
outDir: publicDir,
outDir: path.resolve(
import.meta.dirname,
'..',
'..',
config.dir_name.dist
),
rollupOptions: {
output: {
entryFileNames: `${config.directory.assets_dir}/[name]-[hash:8].js`,

View File

@@ -1,18 +1,15 @@
import eslint from '@eslint/js'
import { tsConfig } from '@aklive2d/eslint-config'
import tseslint from 'typescript-eslint'
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
import globals from 'globals'
/** @type {import('eslint').Config} */
export default tseslint.config(
eslint.configs.recommended,
tseslint.configs.recommended,
eslintPluginPrettierRecommended,
...tsConfig,
{
ignores: ['dist', 'spine-ts'],
},
{
files: ['**/*.{js,jsx}', '**/*.{ts,tsx}'],
files: ['**/*.js', '**/*.ts'],
languageOptions: {
ecmaVersion: 2022,
globals: {

View File

@@ -1,3 +1,4 @@
import { Player } from './src/player.ts'
import { Player, PlayerConfig } from './src/player.ts'
export { Player }
export type { PlayerConfig }

View File

@@ -6,13 +6,15 @@
"scripts": {
"lint": "eslint && prettier --check ."
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript-eslint": ">=8.31.1",
"typescript": ">=5.8.2"
},
"devDependencies": {
"@aklive2d/postcss-config": "workspace:*",
"@aklive2d/prettier-config": "workspace:*",
"@aklive2d/stylelint-config": "workspace:*",
"eslint-plugin-prettier": "^5.2.6",
"globals": "^16.0.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.31.1"
"@aklive2d/eslint-config": "workspace:*"
}
}

View File

@@ -1,3 +1,29 @@
import baseConfig from '@aklive2d/eslint-config'
import { tsConfig } from '@aklive2d/eslint-config'
import tseslint from 'typescript-eslint'
import globals from 'globals'
/** @type {import('eslint').Config} */
export default [...baseConfig, { ignores: ['src/libs/*'] }]
export default tseslint.config(
...baseConfig,
...tsConfig,
{
ignores: ['dist', 'spine-ts'],
},
{
files: ['**/*.js', '**/*.ts'],
languageOptions: {
ecmaVersion: 2022,
globals: {
...globals.browser,
},
},
rules: {
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_' },
],
},
}
)

View File

@@ -12,5 +12,5 @@ export const DIST_DIR = path.resolve(
import.meta.dirname,
'..',
'..',
config.dir_name.dist
config.app.showcase.release
)

View File

@@ -3,12 +3,17 @@
"private": true,
"version": "0.0.0",
"type": "module",
"main": "index.js",
"main": "index.ts",
"scripts": {
"dev:showcase": "vite --clearScreen false",
"build": "mode=build node runner.js",
"build": "mode=build bun runner.ts",
"preview:showcase": "vite preview",
"lint": "eslint \"src/**/*.js\" && stylelint \"**/*.css\" && prettier --check ."
"lint": "eslint && prettier --check ."
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript-eslint": ">=8.31.1",
"typescript": ">=5.8.2"
},
"devDependencies": {
"vite": "^6.1.5",

View File

@@ -5,7 +5,7 @@ import { envParser, file } from '@aklive2d/libs'
import { copyShowcaseData, copyProjectJSON } from '@aklive2d/vite-helpers'
import * as dirs from './index.js'
const build = async (namesToBuild) => {
const build = async (namesToBuild: string[]) => {
const names = !namesToBuild.length ? Object.keys(operators) : namesToBuild
console.log('Generating assets for', names.length, 'operators')
for (const name of names) {
@@ -32,7 +32,7 @@ async function main() {
default: [],
},
})
await build(name)
await build(name as string[])
}
main()

View File

@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2024",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2024", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["**/*"],
"exclude": ["spine-ts/**/*"]
}

102
bun.lock
View File

@@ -4,12 +4,14 @@
"": {
"name": "aklive2d",
"devDependencies": {
"@types/jsdom": "^21.1.7",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^9.25.1",
"http-server": "^14.1.1",
"prettier": "^3.5.3",
"stylelint": "^16.19.1",
"turbo": "^2.5.2",
"typescript": "5.8.2",
},
},
"apps/directory": {
@@ -46,18 +48,25 @@
"sass": "^1.84.0",
"vite": "^6.1.5",
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
},
"apps/module": {
"name": "@aklive2d/module",
"version": "0.0.0",
"devDependencies": {
"@aklive2d/eslint-config": "workspace:*",
"@aklive2d/postcss-config": "workspace:*",
"@aklive2d/prettier-config": "workspace:*",
"@aklive2d/stylelint-config": "workspace:*",
"eslint-plugin-prettier": "^5.2.6",
"globals": "^16.0.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.31.1",
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
},
"apps/showcase": {
@@ -76,6 +85,11 @@
"@aklive2d/vite-helpers": "workspace:*",
"vite": "^6.1.5",
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
},
"packages/assets": {
"name": "@aklive2d/assets",
@@ -89,6 +103,11 @@
"@aklive2d/operator": "workspace:*",
"@aklive2d/prettier-config": "workspace:*",
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
},
"packages/background": {
"name": "@aklive2d/background",
@@ -101,6 +120,11 @@
"@aklive2d/prettier-config": "workspace:*",
"sharp": "^0.33.5",
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
},
"packages/charword-table": {
"name": "@aklive2d/charword-table",
@@ -113,6 +137,11 @@
"@aklive2d/operator": "workspace:*",
"@aklive2d/prettier-config": "workspace:*",
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
},
"packages/config": {
"name": "@aklive2d/config",
@@ -122,6 +151,11 @@
"@aklive2d/libs": "workspace:*",
"@aklive2d/prettier-config": "workspace:*",
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
},
"packages/downloader": {
"name": "@aklive2d/downloader",
@@ -140,8 +174,10 @@
"@eslint/js": "^9.19.0",
"eslint": "^9.19.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.3",
"globals": "^15.14.0",
"eslint-plugin-prettier": "^5.2.6",
"globals": "^16.0.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.31.1",
},
},
"packages/libs": {
@@ -155,6 +191,15 @@
"yauzl-promise": "^4.0.0",
"yazl": "^3.3.1",
},
"devDependencies": {
"@types/yauzl-promise": "^4.0.1",
"@types/yazl": "^2.4.6",
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
},
"packages/music": {
"name": "@aklive2d/music",
@@ -166,6 +211,11 @@
"@aklive2d/libs": "workspace:*",
"@aklive2d/prettier-config": "workspace:*",
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
},
"packages/official-info": {
"name": "@aklive2d/official-info",
@@ -177,6 +227,11 @@
"@aklive2d/prettier-config": "workspace:*",
"jsdom": "^26.0.0",
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
},
"packages/operator": {
"name": "@aklive2d/operator",
@@ -189,6 +244,11 @@
"@aklive2d/prettier-config": "workspace:*",
"yaml": "^2.7.0",
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
},
"packages/postcss-config": {
"name": "@aklive2d/postcss-config",
@@ -202,6 +262,7 @@
"version": "0.0.0",
"peerDependencies": {
"prettier": ">=3.0.0",
"typescript": ">=5.8.2",
},
},
"packages/project-json": {
@@ -217,6 +278,11 @@
"@aklive2d/operator": "workspace:*",
"@aklive2d/prettier-config": "workspace:*",
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
},
"packages/stylelint-config": {
"name": "@aklive2d/stylelint-config",
@@ -240,6 +306,11 @@
"@aklive2d/operator": "workspace:*",
"@aklive2d/prettier-config": "workspace:*",
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
},
"packages/wrangler": {
"name": "@aklive2d/wrangler",
@@ -256,6 +327,11 @@
"@aklive2d/showcase": "workspace:*",
"p-throttle": "^7.0.0",
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript": ">=5.8.2",
"typescript-eslint": ">=8.31.1",
},
},
},
"trustedDependencies": [
@@ -597,6 +673,8 @@
"@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
"@types/jsdom": ["@types/jsdom@21.1.7", "", { "dependencies": { "@types/node": "*", "@types/tough-cookie": "*", "parse5": "^7.0.0" } }, "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/node": ["@types/node@22.15.3", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw=="],
@@ -605,6 +683,12 @@
"@types/react-dom": ["@types/react-dom@19.1.3", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-rJXC08OG0h3W6wDMFxQrZF00Kq6qQvw0djHRdzl3U5DnIERz0MRce3WVc7IS6JYBwtaP/DwYtRRjVlvivNveKg=="],
"@types/tough-cookie": ["@types/tough-cookie@4.0.5", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="],
"@types/yauzl-promise": ["@types/yauzl-promise@4.0.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-qYEC3rJwqiJpdQ9b+bPNeuSY0c3JUM8vIuDy08qfuVN7xHm3ZDsHn2kGphUIB0ruEXrPGNXZ64nMUcu4fDjViQ=="],
"@types/yazl": ["@types/yazl@2.4.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-/ifFjQtcKaoZOjl5NNCQRR0fAKafB3Foxd7J/WvFPTMea46zekapcR30uzkwIkKAAuq5T6d0dkwz754RFH27hg=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.31.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.31.1", "@typescript-eslint/type-utils": "8.31.1", "@typescript-eslint/utils": "8.31.1", "@typescript-eslint/visitor-keys": "8.31.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-oUlH4h1ABavI4F0Xnl8/fOtML/eu8nI2A1nYd+f+55XI0BLu+RIqKoCiZKNo6DtqZBEQm5aNKA20G3Z5w3R6GQ=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.31.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.31.1", "@typescript-eslint/types": "8.31.1", "@typescript-eslint/typescript-estree": "8.31.1", "@typescript-eslint/visitor-keys": "8.31.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q=="],
@@ -919,7 +1003,7 @@
"global-prefix": ["global-prefix@3.0.0", "", { "dependencies": { "ini": "^1.3.5", "kind-of": "^6.0.2", "which": "^1.3.1" } }, "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg=="],
"globals": ["globals@15.15.0", "", {}, "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg=="],
"globals": ["globals@16.0.0", "", {}, "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A=="],
"globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
@@ -1443,7 +1527,7 @@
"typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="],
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="],
"typescript-eslint": ["typescript-eslint@8.31.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.31.1", "@typescript-eslint/parser": "8.31.1", "@typescript-eslint/utils": "8.31.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-j6DsEotD/fH39qKzXTQRwYYWlt7D+0HmfpOK+DVhwJOFLcdmn92hq3mBb7HlKJHbjjI/gTOqEcc9d6JfpFf/VA=="],
@@ -1509,7 +1593,7 @@
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
"@aklive2d/module/globals": ["globals@16.0.0", "", {}, "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A=="],
"@aklive2d/eslint-config/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"@commitlint/config-validator/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],

View File

@@ -1,37 +1,42 @@
{
"private": true,
"scripts": {
"build": "turbo run build",
"dev:showcase": "turbo run dev:showcase --ui tui",
"preview:showcase": "turbo run preview:showcase --ui tui",
"dev:directory": "turbo run dev:directory --ui tui",
"preview:directory": "turbo run preview:directory --ui tui",
"preview": "http-server ./dist",
"lint": "turbo run lint",
"update": "turbo run update",
"init": "turbo run init",
"download:game": "turbo run download:game",
"build:cleanup": "turbo run build:cleanup"
},
"devDependencies": {
"cz-conventional-changelog": "^3.3.0",
"eslint": "^9.25.1",
"http-server": "^14.1.1",
"prettier": "^3.5.3",
"stylelint": "^16.19.1",
"turbo": "^2.5.2"
},
"name": "aklive2d",
"type": "module",
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
},
"packageManager": "bun@1.2.11",
"workspaces": ["packages/*", "apps/*"],
"trustedDependencies": [
"@parcel/watcher",
"@swc/core"
]
"private": true,
"scripts": {
"build": "turbo run build",
"dev:showcase": "turbo run dev:showcase --ui tui",
"preview:showcase": "turbo run preview:showcase --ui tui",
"dev:directory": "turbo run dev:directory --ui tui",
"preview:directory": "turbo run preview:directory --ui tui",
"preview": "http-server ./dist",
"lint": "turbo run lint",
"update": "turbo run update",
"init": "turbo run init",
"download:game": "turbo run download:game",
"build:cleanup": "turbo run build:cleanup"
},
"devDependencies": {
"@types/jsdom": "^21.1.7",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^9.25.1",
"http-server": "^14.1.1",
"prettier": "^3.5.3",
"stylelint": "^16.19.1",
"turbo": "^2.5.2",
"typescript": "5.8.2"
},
"name": "aklive2d",
"type": "module",
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
},
"packageManager": "bun@1.2.11",
"workspaces": [
"packages/*",
"apps/*"
],
"trustedDependencies": [
"@parcel/watcher",
"@swc/core"
]
}

View File

@@ -1,3 +1,27 @@
import baseConfig from '@aklive2d/eslint-config'
import { tsConfig } from '@aklive2d/eslint-config'
import tseslint from 'typescript-eslint'
import globals from 'globals'
/** @type {import('eslint').Config} */
export default [...baseConfig]
export default tseslint.config(
...tsConfig,
{
ignores: ['dist', 'data'],
},
{
files: ['**/*.js', '**/*.ts'],
languageOptions: {
ecmaVersion: 2022,
globals: {
...globals.node,
},
},
rules: {
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_' },
],
},
}
)

View File

@@ -1,12 +1,13 @@
import path from 'node:path'
import config from '@aklive2d/config'
import { yaml } from '@aklive2d/libs'
import type { Config } from './types.ts'
export const DIST_DIR = path.resolve(import.meta.dirname, config.dir_name.dist)
export const CONFIG_PATH = path.resolve(
import.meta.dirname,
config.module.assets.config_yaml
)
const selfConfig = yaml.read(CONFIG_PATH)
const selfConfig: Config = yaml.read(CONFIG_PATH)
export default selfConfig

View File

@@ -1,9 +1,9 @@
import path from 'node:path'
import { file } from '@aklive2d/libs'
import config from '@aklive2d/config'
import { DIST_DIR } from '../index.js'
import { DIST_DIR } from '../index.ts'
export default async (packageDir) => {
export default async (packageDir: string) => {
const copyQueue = [
{
fn: file.symlink,

View File

@@ -1,22 +1,23 @@
import path from 'node:path'
import { file } from '@aklive2d/libs'
import { unzipDownload } from '@aklive2d/downloader'
import { unzipDownload, type UnzipDownloadItem } from '@aklive2d/downloader'
import { getOperatorId, getOperatorAlternativeId } from '@aklive2d/operator'
import { mapping } from '@aklive2d/music'
import config from '../index.js'
import config from '../index.ts'
import type { UpdateList, ItemToDownload, AbInfosItem } from '../types.ts'
export default async (dataDir) => {
const pidSet = new Set()
const versionRes = await fetch(
export default async (dataDir: string) => {
const pidSet: Set<string> = new Set()
const versionRes: Response = await fetch(
'https://ak-conf.hypergryph.com/config/prod/official/Android/version'
)
const version = (await versionRes.json()).resVersion
const lpacksRes = await fetch(
const version: string = (await versionRes.json()).resVersion
const lpacksRes: Response = await fetch(
`https://ak.hycdn.cn/assetbundle/official/Android/assets/${version}/hot_update_list.json`
)
const updateList = await lpacksRes.json()
const itemToDownload = new Set(config.item_to_download)
updateList.abInfos.map((item) => {
const updateList: UpdateList = await lpacksRes.json()
const itemToDownload: Set<ItemToDownload> = new Set(config.item_to_download)
updateList.abInfos.map((item: AbInfosItem) => {
if (item.name.includes(config.dynchars)) {
const id = getOperatorId(item.name).replace('.ab', '')
itemToDownload.add(id)
@@ -32,12 +33,12 @@ export default async (dataDir) => {
const itemToDownloadRegExp = new RegExp(
`(.*)(${Array.from(itemToDownload).join('|')})(.*)`
)
updateList.abInfos.map((item) => {
updateList.abInfos.map((item: AbInfosItem) => {
if (itemToDownloadRegExp.test(item.name)) {
item.pid && pidSet.add(item.pid)
if (item.pid) pidSet.add(item.pid)
}
})
const lpacksToDownload = []
const lpacksToDownload: UnzipDownloadItem[] = []
pidSet.forEach((item) => {
lpacksToDownload.push({
name: item,

View File

@@ -1,7 +1,7 @@
{
"name": "@aklive2d/assets",
"version": "0.0.0",
"main": "index.js",
"main": "index.ts",
"type": "module",
"license": "MIT",
"dependencies": {
@@ -13,10 +13,15 @@
"@aklive2d/operator": "workspace:*",
"@aklive2d/music": "workspace:*"
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript-eslint": ">=8.31.1",
"typescript": ">=5.8.2"
},
"scripts": {
"build": "mode=build node runner.js",
"download:game": "mode=download node runner.js",
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check .",
"build": "mode=build bun runner.ts",
"download:game": "mode=download bun runner.ts",
"lint": "eslint && prettier --check .",
"build:cleanup": "rm -rf ./dist"
}
}

View File

@@ -1,8 +1,8 @@
import path from 'node:path'
import { envParser } from '@aklive2d/libs'
import config from '@aklive2d/config'
import build from './libs/build.js'
import download from './libs/download.js'
import build from './libs/build.ts'
import download from './libs/download.ts'
const packageDir = path.resolve(import.meta.dirname, '..')
const dataDir = path.resolve(import.meta.dirname, config.dir_name.data)

View File

@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2024",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2024", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["**/*"],
"exclude": ["dist/**/*", "data/**/*"]
}

35
packages/assets/types.ts Normal file
View File

@@ -0,0 +1,35 @@
export type Config = {
dynchars: string
item_to_download: ItemToDownload[]
additional_regex: string[]
}
export type ItemToDownload = string
export type UpdateList = {
versionId: string
abInfos: AbInfosItem[]
manifestName: string
manifestVersion: string
packInfos: PackInfosItem[]
}
export type AbInfosItem = {
name: string
hash: string
md5: string
totalSize: number
abSize: number
cid: number
cat?: number
pid?: string
}
export type PackInfosItem = {
name: string
hash: string
md5: string
totalSize: number
abSize: number
cid: number
}

View File

@@ -1,3 +1,27 @@
import baseConfig from '@aklive2d/eslint-config'
import { tsConfig } from '@aklive2d/eslint-config'
import tseslint from 'typescript-eslint'
import globals from 'globals'
/** @type {import('eslint').Config} */
export default [...baseConfig]
export default tseslint.config(
...tsConfig,
{
ignores: ['dist', 'data'],
},
{
files: ['**/*.js', '**/*.ts'],
languageOptions: {
ecmaVersion: 2022,
globals: {
...globals.node,
},
},
rules: {
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_' },
],
},
}
)

View File

@@ -66,12 +66,18 @@ export const build = async () => {
return err
}
const composite = async (filenamePrefix, fileExt) => {
const composite = async (filenamePrefix: string, fileExt: string) => {
const image = sharp(
path.join(EXTRACTED_DIR, `${filenamePrefix}_left${fileExt}`)
)
const metadata = await image.metadata()
if (!metadata.width || !metadata.height) {
throw new Error(
`Width and height metadata for ${filenamePrefix}_left${fileExt} is not found.`
)
}
image
.resize(2 * metadata.width, metadata.height, {
kernel: sharp.kernel.nearest,

View File

@@ -2,7 +2,7 @@
"name": "@aklive2d/background",
"private": true,
"version": "0.0.0",
"main": "index.js",
"main": "index.ts",
"type": "module",
"dependencies": {
"sharp": "^0.33.5",
@@ -12,9 +12,14 @@
"@aklive2d/eslint-config": "workspace:*",
"@aklive2d/prettier-config": "workspace:*"
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript-eslint": ">=8.31.1",
"typescript": ">=5.8.2"
},
"scripts": {
"build": "mode=build node runner.js",
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check .",
"build": "mode=build bun runner.js",
"lint": "eslint && prettier --check .",
"build:cleanup": "rm -rf ./dist ./data"
}
}

View File

@@ -1,4 +1,4 @@
import { build } from './index.js'
import { build } from './index.ts'
import { envParser, error } from '@aklive2d/libs'
async function main() {

View File

@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2024",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2024", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["**/*"],
"exclude": ["dist/**/*", "data/**/*"]
}

View File

@@ -1,3 +1,27 @@
import baseConfig from '@aklive2d/eslint-config'
import { tsConfig } from '@aklive2d/eslint-config'
import tseslint from 'typescript-eslint'
import globals from 'globals'
/** @type {import('eslint').Config} */
export default [...baseConfig]
export default tseslint.config(
...tsConfig,
{
ignores: ['dist', 'data'],
},
{
files: ['**/*.js', '**/*.ts'],
languageOptions: {
ecmaVersion: 2022,
globals: {
...globals.node,
},
},
rules: {
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_' },
],
},
}
)

View File

@@ -7,17 +7,26 @@ import operators, {
getOperatorAlternativeId,
OPERATOR_SOURCE_FOLDER,
} from '@aklive2d/operator'
import type {
Region,
CharwordTableJson,
OperatorCharwordTable,
CharwordTable,
VoiceRegionObject,
InfoRegionObject,
} from './types.ts'
// zh_TW uses an older version of charword_table.json
// zh_TW is removed
const REGIONS = ['zh_CN', 'en_US', 'ja_JP', 'ko_KR']
const REGIONS: Region[] = ['zh_CN', 'en_US', 'ja_JP', 'ko_KR']
const REGION_URLS = {
zh_CN: 'Kengxxiao/ArknightsGameData',
en_US: 'Kengxxiao/ArknightsGameData_YoStar',
ja_JP: 'Kengxxiao/ArknightsGameData_YoStar',
ko_KR: 'Kengxxiao/ArknightsGameData_YoStar',
}
const DEFAULT_REGION = REGIONS[0]
const DEFAULT_REGION: Region = REGIONS[0]
export const defaultRegion = DEFAULT_REGION.replace('_', '-')
const NICKNAME = {
zh_CN: '博士',
@@ -34,22 +43,18 @@ const AUTO_UPDATE_FOLDER = path.resolve(
import.meta.dirname,
config.dir_name.auto_update
)
const CHARWORD_TABLE_FILE = path.resolve(
AUTO_UPDATE_FOLDER,
config.module.charword_table.charword_table_json
)
const CHARWORD_TABLE = JSON.parse(file.readSync(CHARWORD_TABLE_FILE)) || {}
const DIST_DIR = path.resolve(import.meta.dirname, config.dir_name.dist)
export const lookup = (operatorName) => {
const lookup = (operatorName: string, charwordTable: CharwordTable) => {
const operatorId = getOperatorId(operators[operatorName].filename)
const operatorBlock = CHARWORD_TABLE[operatorId]
const operatorBlock = charwordTable[operatorId]
return operatorBlock.ref
? CHARWORD_TABLE[operatorBlock.alternativeId]
? charwordTable[operatorBlock.alternativeId]
: operatorBlock
}
const getDistDir = (name) => {
const getDistDir = (name: string) => {
return path.join(
DIST_DIR,
name,
@@ -57,27 +62,37 @@ const getDistDir = (name) => {
)
}
export const getLangs = (name, voiceJson = null) => {
voiceJson = voiceJson
? voiceJson
: JSON.parse(file.readSync(getDistDir(name)))
const voiceLangs = Object.keys(voiceJson.voiceLangs['zh-CN'])
const subtitleLangs = Object.keys(voiceJson.subtitleLangs)
export const getLangs = (
name: string,
voiceJson: OperatorCharwordTable | null = null
) => {
let data: OperatorCharwordTable
if (!voiceJson) {
const text = file.readSync(getDistDir(name))
if (!text) throw new Error(`File not found: ${getDistDir(name)}`)
data = JSON.parse(text)
} else {
data = voiceJson
}
const voiceLangs = Object.keys(data.voiceLangs['zh-CN'])
const subtitleLangs = Object.keys(data.subtitleLangs)
return { voiceLangs, subtitleLangs }
}
export const build = async (namesToBuild) => {
const err = []
export const build = async (namesToBuild: string[]) => {
const err: string[] = []
const names = !namesToBuild.length ? Object.keys(operators) : namesToBuild
console.log('Generating charword_table for', names.length, 'operators')
await updateFn(true)
const charwordTable = await updateFn(true)
for (const name of names) {
const charwordTableLookup = lookup(name)
const voiceJson = {}
const charwordTableLookup = lookup(name, charwordTable)
const voiceJson = {} as OperatorCharwordTable
voiceJson.voiceLangs = {}
voiceJson.subtitleLangs = {}
const subtitleInfo = Object.keys(charwordTableLookup.info)
let voiceList = {}
const subtitleInfo = Object.keys(charwordTableLookup.info) as Region[]
const voiceList = {} as {
[key: string]: string[]
}
subtitleInfo.forEach((item) => {
if (Object.keys(charwordTableLookup.info[item]).length > 0) {
const key = item.replace('_', '-')
@@ -104,7 +119,7 @@ export const build = async (namesToBuild) => {
)
}
})
let voiceLangs = []
let voiceLangs = [] as string[]
try {
voiceLangs = getLangs(name, voiceJson).voiceLangs
@@ -161,36 +176,57 @@ const updateFn = async (isLocalOnly = false) => {
(acc, cur) => ({ ...acc, [cur]: {} }),
{}
)
const charwordTable = {} as CharwordTable
OPERATOR_IDS.forEach((id) => {
CHARWORD_TABLE[id] = {
charwordTable[id] = {
alternativeId: getOperatorAlternativeId(id),
voice: structuredClone(regionObject),
info: structuredClone(regionObject),
voice: structuredClone(regionObject) as VoiceRegionObject,
info: structuredClone(regionObject) as InfoRegionObject,
infile: false,
ref: false,
}
})
await load(DEFAULT_REGION, isLocalOnly)
await load(DEFAULT_REGION, charwordTable, isLocalOnly)
await Promise.all(
REGIONS.slice(1).map(async (region) => {
await load(region, isLocalOnly)
await load(region, charwordTable, isLocalOnly)
})
)
return charwordTable
}
const load = async (region, isLocalOnly = false) => {
const load = async (
region: Region,
charwordTable: CharwordTable,
isLocalOnly = false
) => {
const basename = `charword_table_${region}`
const filename = file
.readdirSync(AUTO_UPDATE_FOLDER)
.filter((item) => item.startsWith(`charword_table_${region}`))[0]
const localFilePath = path.join(AUTO_UPDATE_FOLDER, filename)
const data = isLocalOnly
? JSON.parse(file.readSync(localFilePath))
: await download(
region,
path.join(path.dirname(localFilePath), `${basename}.json`)
)
let data: CharwordTableJson
const getOnlineData = async () => {
return await download(
region,
path.join(path.dirname(localFilePath), `${basename}.json`)
)
}
if (isLocalOnly) {
const text = file.readSync(localFilePath)
if (!text) {
data = await getOnlineData()
} else {
data = JSON.parse(text)
}
} else {
data = await getOnlineData()
}
// put voice actor info into charword_table
for (const [id, element] of Object.entries(CHARWORD_TABLE)) {
for (const [id, element] of Object.entries(charwordTable)) {
let operatorId = id
let useAlternativeId = false
if (typeof data.voiceLangDict[operatorId] === 'undefined') {
@@ -236,7 +272,7 @@ const load = async (region, isLocalOnly = false) => {
// put voice lines into charword_table
Object.values(data.charWords).forEach((item) => {
const operatorInfo = Object.values(CHARWORD_TABLE).filter(
const operatorInfo = Object.values(charwordTable).filter(
(element) => element.info[region][item.wordKey]
)
if (operatorInfo.length > 0) {
@@ -256,9 +292,14 @@ const load = async (region, isLocalOnly = false) => {
}
}
})
return charwordTable
}
const download = async (region, targetFilePath) => {
const download = async (
region: keyof typeof REGION_URLS,
targetFilePath: string
) => {
return await githubDownload(
`https://api.github.com/repos/${REGION_URLS[region]}/commits?path=${region}/gamedata/excel/charword_table.json`,
`https://raw.githubusercontent.com/${REGION_URLS[region]}/master/${region}/gamedata/excel/charword_table.json`,

View File

@@ -2,7 +2,7 @@
"name": "@aklive2d/charword-table",
"private": true,
"version": "0.0.0",
"main": "index.js",
"main": "index.ts",
"type": "module",
"dependencies": {
"@aklive2d/libs": "workspace:*",
@@ -12,10 +12,15 @@
"@aklive2d/eslint-config": "workspace:*",
"@aklive2d/prettier-config": "workspace:*"
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript-eslint": ">=8.31.1",
"typescript": ">=5.8.2"
},
"scripts": {
"update": "mode=update node runner.js",
"build": "mode=build node runner.js",
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check .",
"update": "mode=update bun runner.ts",
"build": "mode=build bun runner.ts",
"lint": "eslint && prettier --check .",
"build:cleanup": "rm -rf ./dist"
}
}

View File

@@ -1,8 +1,8 @@
import { build, update } from './index.js'
import { build, update } from './index.ts'
import { envParser, error } from '@aklive2d/libs'
async function main() {
let err = []
let err: string[] = []
const { mode, name } = envParser.parse({
mode: {
type: 'string',
@@ -17,7 +17,7 @@ async function main() {
})
switch (mode) {
case 'build':
err = await build(name)
err = await build(name as string[])
break
case 'update':
await update()

View File

@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2024",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2024", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["**/*"],
"exclude": ["dist/**/*", "data/**/*"]
}

View File

@@ -0,0 +1,139 @@
export type Region = 'zh_CN' | 'en_US' | 'ja_JP' | 'ko_KR'
interface CharWordBase {
wordKey: string
charId: string
voiceId: string
voiceText: string
}
interface CharWord extends CharWordBase {
charWordId: string
voiceTitle: string
voiceIndex: number
voiceType: string
unlockType: string
unlockParam: unknown
lockDescription: string
placeType: string
voiceAsset: string
}
type VoiceLangInfo = {
wordkey: string
voiceLangType: string
cvName: string[]
voicePath: string | null
}
type CharVoiceLangDict = {
wordkeys: string[]
charId: string
dict: Record<string, VoiceLangInfo>
}
type VoiceLangType = {
name: string
groupType: string
}
type VoiceLangGroupType = {
name: string
members: string[]
}
type TimestampCharSet = {
timestamp: number
charSet: string[]
}
type TimeData = {
timeType: string
interval: {
startTs: number
endTs: number
}
}
type FesVoiceData = {
showType: string
timeData: TimeData[]
}
type FesVoiceWeight = {
showType: string
weight: number
priority: number
}
type ExtraVoiceConfig = {
voiceId: string
validVoiceLang: string[]
}
export type CharwordTableJson = {
charWords: Record<string, CharWord>
charExtraWords: Record<string, CharWordBase>
voiceLangDict: Record<string, CharVoiceLangDict>
defaultLangType: string
newTagList: string[]
voiceLangTypeDict: Record<string, VoiceLangType>
voiceLangGroupTypeDict: Record<string, VoiceLangGroupType>
charDefaultTypeDict: Record<string, string>
startTimeWithTypeDict: Record<string, TimestampCharSet[]>
displayGroupTypeList: string[]
displayTypeList: string[]
playVoiceRange: string
fesVoiceData: Record<string, FesVoiceData>
fesVoiceWeight: Record<string, FesVoiceWeight>
extraVoiceConfigData: Record<string, ExtraVoiceConfig>
}
export type OperatorCharwordTable = {
voiceLangs: {
[languageCode: string]: {
[voiceLangType: string]: string[]
}
}
subtitleLangs: {
[languageCode: string]: {
[voiceKey: 'default' | string]: {
[voiceId: string]: {
title: string
text: string
}
}
}
}
}
export type VoiceRegionObject = {
// eslint-disable-next-line
[region in Region]: {
[wordkey: string]: {
[voiceId: string]: {
title: string
text: string
}
}
}
}
export type InfoRegionObject = {
// eslint-disable-next-line
[region in Region]: {
[wordkey: string]: {
[voiceLangType: string]: string[]
}
}
}
export type CharwordTable = {
[name: string]: {
alternativeId: string
voice: VoiceRegionObject
info: InfoRegionObject
infile: boolean
ref: boolean
}
}

View File

@@ -49,6 +49,7 @@ app:
showcase:
public: public
assets: assets
release: release
dir_name:
data: data
dist: dist

View File

@@ -1,3 +1,27 @@
import baseConfig from '@aklive2d/eslint-config'
import { tsConfig } from '@aklive2d/eslint-config'
import tseslint from 'typescript-eslint'
import globals from 'globals'
/** @type {import('eslint').Config} */
export default [...baseConfig]
export default tseslint.config(
...tsConfig,
{
ignores: ['dist', 'data'],
},
{
files: ['**/*.js', '**/*.ts'],
languageOptions: {
ecmaVersion: 2022,
globals: {
...globals.node,
},
},
rules: {
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_' },
],
},
}
)

View File

@@ -1,4 +0,0 @@
import path from 'node:path'
import { yaml } from '@aklive2d/libs'
export default yaml.read(path.resolve(import.meta.dirname, 'config.yaml'))

9
packages/config/index.ts Normal file
View File

@@ -0,0 +1,9 @@
import path from 'node:path'
import { yaml } from '@aklive2d/libs'
import type { Config } from './types'
const config: Config = yaml.read(
path.resolve(import.meta.dirname, 'config.yaml')
)
export default config

View File

@@ -1,7 +1,7 @@
{
"name": "@aklive2d/config",
"version": "0.0.0",
"main": "index.js",
"main": "index.ts",
"type": "module",
"license": "MIT",
"dependencies": {
@@ -9,7 +9,12 @@
"@aklive2d/eslint-config": "workspace:*",
"@aklive2d/prettier-config": "workspace:*"
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript-eslint": ">=8.31.1",
"typescript": ">=5.8.2"
},
"scripts": {
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check ."
"lint": "eslint && prettier --check ."
}
}

View File

@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2024",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2024", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["**/*"],
"exclude": ["dist/**/*", "data/**/*"]
}

103
packages/config/types.ts Normal file
View File

@@ -0,0 +1,103 @@
export type Config = {
site_id: string
total_size: number
akassets: {
project_name: string
url: string
}
insight: {
id: string
url: string
}
module: {
assets: {
config_yaml: string
background: string
music: string
charword_table: string
project_json: string
}
background: {
operator_bg_png: string
}
charword_table: {
charword_table_json: string
}
music: {
music_table_json: string
display_meta_table_json: string
audio_data_json: string
}
official_info: {
official_info_json: string
}
operator: {
operator: string
config: string
template_yaml: string
config_yaml: string
portraits: string
logos_assets: string
logos: string
directory_assets: string
MonoBehaviour: string
Texture2D: string
title: {
'zh-CN': string
'en-US': string
}
}
project_json: {
project_json: string
preview_jpg: string
template_yaml: string
}
wrangler: {
index_json: string
}
vite_helpers: {
config_json: string
}
}
app: {
showcase: {
public: string
assets: string
release: string
}
}
dir_name: {
data: string
dist: string
extracted: string
auto_update: string
voice: {
main: string
sub: {
name: string
lang: string
lookup_region: string
}[]
}
}
directory: {
assets_dir: string
title: string
voice: string
error: {
files: {
key: string
paddings: {
left: string
right: string
top: string
bottom: string
}
}[]
voice: {
file: string
target: string
}
}
}
}

View File

@@ -1,3 +1,27 @@
import baseConfig from '@aklive2d/eslint-config'
import { tsConfig } from '@aklive2d/eslint-config'
import tseslint from 'typescript-eslint'
import globals from 'globals'
/** @type {import('eslint').Config} */
export default [...baseConfig]
export default tseslint.config(
...tsConfig,
{
ignores: ['dist', 'data'],
},
{
files: ['**/*.js', '**/*.ts'],
languageOptions: {
ecmaVersion: 2022,
globals: {
...globals.node,
},
},
rules: {
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_' },
],
},
}
)

View File

@@ -5,7 +5,16 @@ import { pipeline } from 'node:stream/promises'
import pThrottle from 'p-throttle'
import { file as fileLib } from '@aklive2d/libs'
export const githubDownload = async (history_url, raw_url, filepath) => {
export type UnzipDownloadItem = {
name: string
url: string
}
export const githubDownload = async (
history_url: string,
raw_url: string,
filepath: string
) => {
const historyResponse = await fetch(history_url)
const historyData = await historyResponse.json()
const lastCommit = historyData[0]
@@ -21,7 +30,8 @@ export const githubDownload = async (history_url, raw_url, filepath) => {
if (fileLib.exists(filepath)) {
console.log(`${basename} is the latest version.`)
return JSON.parse(fileLib.readSync(filepath))
const content = fileLib.readSync(filepath)
return content ? JSON.parse(content) : null
}
const response = await fetch(raw_url)
const data = await response.json()
@@ -39,9 +49,12 @@ export const githubDownload = async (history_url, raw_url, filepath) => {
}
export const unzipDownload = async (
filesToDownload,
targetDir,
opts = {
filesToDownload: UnzipDownloadItem[],
targetDir: string,
opts: {
defaultRegex: RegExp | null
matchRegExps: RegExp[]
} = {
defaultRegex: null,
matchRegExps: [],
}
@@ -52,7 +65,7 @@ export const unzipDownload = async (
interval: 1000,
})
while (retry.length > 0) {
const newRetry = []
const newRetry: UnzipDownloadItem[] = []
await Promise.all(
retry.map(
throttle(async (item) => {
@@ -72,7 +85,9 @@ export const unzipDownload = async (
})
.catch((err) => {
console.error(err)
return null
})
if (!zip) return
try {
for await (const entry of zip) {
if (

View File

@@ -2,7 +2,7 @@
"name": "@aklive2d/downloader",
"private": true,
"version": "0.0.0",
"main": "index.js",
"main": "index.ts",
"type": "module",
"dependencies": {
"@aklive2d/eslint-config": "workspace:*",
@@ -11,6 +11,6 @@
"p-throttle": "^7.0.0"
},
"scripts": {
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check ."
"lint": "eslint && prettier --check ."
}
}

View File

@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2024",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2024", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["**/*"],
"exclude": ["dist/**/*", "data/**/*"]
}

View File

@@ -1,6 +1,7 @@
import js from '@eslint/js'
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
import globals from 'globals'
import tsConfig from './ts.js'
export default [
{ ignores: ['dist'] },
@@ -24,3 +25,5 @@ export default [
},
eslintPluginPrettierRecommended,
]
export { tsConfig }

View File

@@ -0,0 +1,4 @@
import eslint from '@eslint/js'
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
export default [eslint.configs.recommended, eslintPluginPrettierRecommended]

View File

@@ -1,17 +1,16 @@
{
"name": "@aklive2d/eslint-config",
"version": "0.0.0",
"main": "index.js",
"type": "module",
"license": "MIT",
"dependencies": {
"@eslint/js": "^9.19.0",
"eslint": "^9.19.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.3",
"globals": "^15.14.0"
},
"publishConfig": {
"access": "public"
}
"name": "@aklive2d/eslint-config",
"version": "0.0.0",
"main": "index.js",
"type": "module",
"license": "MIT",
"dependencies": {
"@eslint/js": "^9.19.0",
"eslint": "^9.19.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.6",
"globals": "^16.0.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.31.1"
}
}

View File

@@ -0,0 +1,10 @@
import eslint from '@eslint/js'
import tseslint from 'typescript-eslint'
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
/** @type {import('eslint').Config} */
export default tseslint.config(
eslint.configs.recommended,
tseslint.configs.recommended,
eslintPluginPrettierRecommended
)

View File

@@ -1,3 +1,27 @@
import baseConfig from '@aklive2d/eslint-config'
import { tsConfig } from '@aklive2d/eslint-config'
import tseslint from 'typescript-eslint'
import globals from 'globals'
/** @type {import('eslint').Config} */
export default [...baseConfig]
export default tseslint.config(
...tsConfig,
{
ignores: ['dist', 'data'],
},
{
files: ['**/*.js', '**/*.ts'],
languageOptions: {
ecmaVersion: 2022,
globals: {
...globals.node,
},
},
rules: {
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_' },
],
},
}
)

View File

@@ -1,13 +0,0 @@
import * as file from './libs/file.js'
import * as yaml from './libs/yaml.js'
import * as env from './libs/env.js'
import * as error from './libs/error.js'
import * as alphaComposite from './libs/alpha_composite.js'
import * as envParser from './libs/env_parser.js'
export { file }
export { yaml }
export { env }
export { error }
export { alphaComposite }
export { envParser }

13
packages/libs/index.ts Normal file
View File

@@ -0,0 +1,13 @@
import * as file from './libs/file.ts'
import * as yaml from './libs/yaml.ts'
import * as env from './libs/env.ts'
import * as error from './libs/error.ts'
import * as alphaComposite from './libs/alpha_composite.ts'
import * as envParser from './libs/env_parser.ts'
export { file }
export { yaml }
export { env }
export { error }
export { alphaComposite }
export { envParser }

View File

@@ -1,7 +1,19 @@
import sharp from 'sharp'
import path from 'node:path'
export const process = async (filename, maskFilename, extractedDir) => {
export type Rect = {
x: number
y: number
w: number
h: number
rotate: number
}
export const process = async (
filename: string,
maskFilename: string,
extractedDir: string
) => {
const image = sharp(path.join(extractedDir, filename)).removeAlpha()
const imageMeta = await image.metadata()
const imageBuffer = await image.toBuffer()
@@ -13,7 +25,22 @@ export const process = async (filename, maskFilename, extractedDir) => {
return sharp(imageBuffer).joinChannel(mask).toBuffer()
}
export const crop = async (buffer, rect) => {
export const crop = async (
buffer:
| Buffer
| ArrayBuffer
| Uint8Array
| Uint8ClampedArray
| Int8Array
| Uint16Array
| Int16Array
| Uint32Array
| Int32Array
| Float32Array
| Float64Array
| string,
rect: Rect
) => {
const left = rect.y
const top = rect.x
const width = rect.h
@@ -28,7 +55,7 @@ export const crop = async (buffer, rect) => {
return await sharp(newImage).rotate(rotate).toBuffer()
}
export const toBuffer = async (filename, extractedDir) => {
export const toBuffer = async (filename: string, extractedDir: string) => {
const file = path.join(extractedDir, filename)
const { data, info } = await sharp(file)
.raw()
@@ -36,7 +63,7 @@ export const toBuffer = async (filename, extractedDir) => {
const { width, height, channels } = info
const pixelArray = new Uint8ClampedArray(data.buffer)
for (let i = 0; i < pixelArray.length; i += 4) {
let alpha = pixelArray[i + 3] / 255
const alpha = pixelArray[i + 3] / 255
pixelArray[i + 0] = pixelArray[i + 0] * alpha
pixelArray[i + 1] = pixelArray[i + 1] * alpha
pixelArray[i + 2] = pixelArray[i + 2] * alpha

View File

@@ -1,4 +1,9 @@
export function generate(values) {
export function generate(
values: {
key: string
value: string
}[]
) {
return values
.map((value) => {
return `VITE_${value.key.toUpperCase()}=${value.value}`

View File

@@ -1,28 +1,40 @@
import process from 'node:process'
export const parse = (args) => {
type Args = {
[name: string]: {
type?: string
default?: unknown
multiple?: boolean
short?: string
}
}
export const parse = (args: Args) => {
const envVars = process.env
const argKeys = Object.keys(args)
const values = {}
const values: Record<string, unknown | unknown[]> = {}
argKeys.map((key) => {
let noInput = false
let value,
type = args[key].type || 'string',
let value: unknown
const type = args[key].type || 'string',
defaultVal = args[key].default,
multiple = args[key].multiple || false,
short = args[key].short
value = envVars[key] || envVars[short]
value = short
? envVars[short]
? envVars[short]
: envVars[key]
: envVars[key]
if (!value) noInput = true
value = noInput ? defaultVal : value
if (noInput) {
values[key] = value
} else {
value = multiple ? value.split(',') : value
const cValue = multiple ? (value as string).split(',') : value
if (multiple) {
values[key] = []
value.map((item) => {
values[key].push(typeCast(type, item))
})
values[key] = (cValue as string[]).map((item: string) =>
typeCast(type, item)
)
} else {
values[key] = typeCast(type, value)
}
@@ -31,7 +43,7 @@ export const parse = (args) => {
return values
}
const typeCast = (type, value) => {
const typeCast = (type: 'number' | 'boolean' | string, value: unknown) => {
switch (type) {
case 'number':
return Number(value)

View File

@@ -1,4 +1,4 @@
export const handle = (err) => {
export const handle = (err: string[]) => {
if (err.length > 0) {
const str = `${err.length} error${err.length > 1 ? 's were' : ' was'} found:\n${err.join('\n')}`
throw new Error(str)

View File

@@ -3,52 +3,61 @@ import path from 'node:path'
import yauzl from 'yauzl-promise'
import yazl from 'yazl'
export async function write(content, filePath) {
export async function write(
content: string | NodeJS.ArrayBufferView,
filePath: string
) {
mkdir(path.dirname(filePath))
return await fsP.writeFile(filePath, content, { flag: 'w' })
}
export function writeSync(content, filePath) {
export function writeSync(
content: string | NodeJS.ArrayBufferView,
filePath: string
) {
mkdir(path.dirname(filePath))
return fs.writeFileSync(filePath, content, { flag: 'w' })
}
export async function read(filePath, encoding = 'utf8') {
return await fsP.readFile(filePath, encoding, { flag: 'r' })
export async function read(
filePath: string,
encoding: BufferEncoding = 'utf8'
) {
return await fsP.readFile(filePath, { encoding, flag: 'r' })
}
export function readSync(filePath, encoding = 'utf8') {
export function readSync(filePath: string, encoding: BufferEncoding = 'utf8') {
if (exists(filePath)) {
return fs.readFileSync(filePath, encoding, { flag: 'r' })
return fs.readFileSync(filePath, { encoding, flag: 'r' })
}
return null
}
export function exists(filePath) {
export function exists(filePath: string) {
return fs.existsSync(filePath)
}
export function rmdir(dir) {
export function rmdir(dir: string) {
if (exists(dir)) {
fs.rmSync(dir, { recursive: true })
}
}
export function rm(dir) {
export function rm(dir: string) {
if (exists(dir)) {
fs.rmSync(dir, { recursive: true })
}
}
export function mkdir(dir) {
export function mkdir(dir: string) {
if (!exists(dir)) {
fs.mkdirSync(dir, { recursive: true })
}
}
export async function copy(
sourcePath,
targetPath,
sourcePath: string,
targetPath: string,
mode = fs.constants.COPYFILE_FICLONE
) {
if (!exists(sourcePath)) {
@@ -60,8 +69,8 @@ export async function copy(
}
export async function copyDir(
sourcePath,
targetPath,
sourcePath: string,
targetPath: string,
mode = fs.constants.COPYFILE_FICLONE
) {
if (!exists(sourcePath)) {
@@ -72,11 +81,11 @@ export async function copyDir(
return await fsP.cp(sourcePath, targetPath, { recursive: true, mode })
}
export function appendSync(content, filePath) {
export function appendSync(content: string | Uint8Array, filePath: string) {
return fs.appendFileSync(filePath, content, 'utf8')
}
export function readdirSync(dir) {
export function readdirSync(dir: string) {
if (!exists(dir)) {
console.warn(`Source ${dir} does not exist.`)
return []
@@ -84,7 +93,7 @@ export function readdirSync(dir) {
return fs.readdirSync(dir)
}
export function fileTypeSync(dir) {
export function fileTypeSync(dir: string) {
if (!exists(dir)) {
console.warn(`Source ${dir} does not exist.`)
return null
@@ -92,7 +101,7 @@ export function fileTypeSync(dir) {
return fs.statSync(dir).isDirectory() ? 'dir' : 'file'
}
export const symlink = (source, target) => {
export const symlink = (source: string, target: string) => {
if (!exists(source)) {
console.warn(`Source ${source} does not exist.`)
return
@@ -105,14 +114,14 @@ export const symlink = (source, target) => {
fs.symlinkSync(relative, target)
}
export const symlinkAll = (source, target) => {
export const symlinkAll = (source: string, target: string) => {
const files = readdirSync(source)
files.map((file) => {
symlink(path.join(source, file), path.join(target, file))
})
}
export const mv = (source, target) => {
export const mv = (source: string, target: string) => {
if (!exists(source)) {
console.warn(`Source file ${source} does not exist.`)
return
@@ -125,8 +134,8 @@ export const mv = (source, target) => {
}
export const cpSync = (
source,
target,
source: string,
target: string,
opts = {
dereference: false,
}
@@ -146,7 +155,7 @@ export const cpSync = (
})
}
export const relative = (source, target) => {
export const relative = (source: string, target: string) => {
if (!exists(source)) {
console.warn(`Source file ${source} does not exist.`)
return
@@ -154,7 +163,7 @@ export const relative = (source, target) => {
return path.relative(source, target)
}
export const size = (source) => {
export const size = (source: string) => {
if (!exists(source)) {
console.warn(`Source file ${source} does not exist.`)
return

View File

@@ -1,12 +1,17 @@
import fs from 'node:fs'
import path from 'node:path'
import { parse } from 'yaml'
import type { Tags, ScalarTag, SchemaOptions, CollectionTag } from 'yaml'
export function read(file_dir, customTags = []) {
const include = {
identify: (value) => value.startsWith('!include'),
export function read(
file_dir: string,
customTags: ScalarTag[] | CollectionTag[] = []
) {
const include: ScalarTag = {
identify: (value: unknown) =>
typeof value === 'string' && value.startsWith('!include'),
tag: '!include',
resolve(str) {
resolve(str: string) {
const dir = path.resolve(path.dirname(file_dir), str)
const data = read(dir)
return data
@@ -15,5 +20,5 @@ export function read(file_dir, customTags = []) {
const file = fs.readFileSync(file_dir, 'utf8')
return parse(file, {
customTags: [include, ...customTags],
})
} as SchemaOptions)
}

View File

@@ -2,7 +2,7 @@
"name": "@aklive2d/libs",
"private": true,
"version": "0.0.0",
"main": "index.js",
"main": "index.ts",
"type": "module",
"dependencies": {
"@aklive2d/eslint-config": "workspace:*",
@@ -12,7 +12,16 @@
"yauzl-promise": "^4.0.0",
"yazl": "^3.3.1"
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript-eslint": ">=8.31.1",
"typescript": ">=5.8.2"
},
"scripts": {
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check ."
"lint": "eslint && prettier --check ."
},
"devDependencies": {
"@types/yauzl-promise": "^4.0.1",
"@types/yazl": "^2.4.6"
}
}

View File

@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2024",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2024", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["libs/**/*"],
"exclude": ["dist/**/*", "data/**/*"]
}

View File

@@ -1,3 +1,27 @@
import baseConfig from '@aklive2d/eslint-config'
import { tsConfig } from '@aklive2d/eslint-config'
import tseslint from 'typescript-eslint'
import globals from 'globals'
/** @type {import('eslint').Config} */
export default [...baseConfig]
export default tseslint.config(
...tsConfig,
{
ignores: ['dist', 'data'],
},
{
files: ['**/*.js', '**/*.ts'],
languageOptions: {
ecmaVersion: 2022,
globals: {
...globals.node,
},
},
rules: {
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_' },
],
},
}
)

View File

@@ -2,6 +2,16 @@ import path from 'node:path'
import { file } from '@aklive2d/libs'
import { githubDownload } from '@aklive2d/downloader'
import config from '@aklive2d/config'
import type {
DisplayMetaTable,
AudioDataTable,
MusicTable,
MusicFileMapping,
MusicFiles,
MusicItem,
MusicDataItem,
MusicMapping,
} from './types.ts'
const AUTO_UPDATE_FOLDER = path.resolve(
import.meta.dirname,
@@ -22,12 +32,12 @@ const download = async () => {
AUTO_UPDATE_FOLDER,
config.module.music.audio_data_json
)
const metaTable = await githubDownload(
const metaTable: DisplayMetaTable = await githubDownload(
`https://api.github.com/repos/Kengxxiao/ArknightsGameData/commits?path=zh_CN/gamedata/excel/display_meta_table.json`,
`https://raw.githubusercontent.com/Kengxxiao/ArknightsGameData/master/zh_CN/gamedata/excel/display_meta_table.json`,
display_meta_table_json
)
const audioDataTable = await githubDownload(
const audioDataTable: AudioDataTable = await githubDownload(
`https://api.github.com/repos/Kengxxiao/ArknightsGameData/commits?path=zh_CN/gamedata/excel/audio_data.json`,
`https://raw.githubusercontent.com/Kengxxiao/ArknightsGameData/master/zh_CN/gamedata/excel/audio_data.json`,
audio_data_json
@@ -40,14 +50,17 @@ const download = async () => {
const generateMapping = () => {
const musicFolder = DATA_DIR
const musicTable = JSON.parse(file.readSync(MUSIC_TABLE_JSON))
const musicFileMapping = {}
const musicFiles = []
const musicTableContent = file.readSync(MUSIC_TABLE_JSON)
const musicTable: MusicTable = musicTableContent
? JSON.parse(musicTableContent)
: null
const musicFileMapping: MusicFileMapping = {}
const musicFiles: MusicFiles = []
if (!musicTable) return
if (!musicTable) throw new Error('Music table not found')
for (const item of musicTable) {
const key = `${item.id}.png`
musicFileMapping[key] = {}
musicFileMapping[key] = {} as MusicItem
if (item.intro) {
const filename = `${item.intro.split('/').pop()}.ogg`
@@ -78,34 +91,37 @@ const generateMapping = () => {
}
}
export const mapping = generateMapping()
export const mapping: MusicMapping = generateMapping()
export const update = async () => {
const { metaTable, audioDataTable } = await download()
const musicTable = audioDataTable.musics
const musicBank = audioDataTable.bgmBanks
const musicBankAlias = audioDataTable.bankAlias
const musicData = metaTable.homeBackgroundData.homeBgDataList.reduce(
(acc, cur) => {
const musicData: MusicDataItem[] =
metaTable.homeBackgroundData.homeBgDataList.reduce((acc, cur) => {
acc.push({
id: cur.bgId,
musicId: cur.bgMusicId,
})
return acc
},
[]
)
}, [] as MusicDataItem[])
const list = []
for (const item of musicData) {
let bankName = musicTable.find((el) => item.musicId === el.id).bank
const bankItem = musicTable.find((el) => item.musicId === el.id)
if (typeof bankItem === 'undefined')
console.warn(`No music found for id ${item.musicId}`)
let bankName = bankItem!.bank
if (typeof musicBankAlias[bankName] !== 'undefined') {
bankName = musicBankAlias[bankName]
}
const obj = musicBank.find((el) => bankName === el.name)
if (typeof obj === 'undefined')
console.warn(`No bank found for name ${bankName}`)
list.push({
id: item.id,
intro: obj.intro,
loop: obj.loop,
intro: obj!.intro,
loop: obj!.loop,
})
}
list.push({

View File

@@ -2,7 +2,7 @@
"name": "@aklive2d/music",
"private": true,
"version": "0.0.0",
"main": "index.js",
"main": "index.ts",
"type": "module",
"dependencies": {
"@aklive2d/libs": "workspace:*",
@@ -11,9 +11,14 @@
"@aklive2d/eslint-config": "workspace:*",
"@aklive2d/prettier-config": "workspace:*"
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript-eslint": ">=8.31.1",
"typescript": ">=5.8.2"
},
"scripts": {
"update": "mode=update node runner.js",
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check .",
"update": "mode=update bun runner.ts",
"lint": "eslint && prettier --check .",
"build:cleanup": "rm -rf ./data"
}
}

View File

@@ -1,5 +1,5 @@
import { envParser } from '@aklive2d/libs'
import { update } from './index.js'
import { update } from './index.ts'
async function main() {
const { mode } = envParser.parse({

View File

@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2024",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2024", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["**/*"],
"exclude": ["dist/**/*", "data/**/*"]
}

295
packages/music/types.ts Normal file
View File

@@ -0,0 +1,295 @@
export type MusicMapping = {
musicFiles: MusicFiles
musicFileMapping: MusicFileMapping
}
export type MusicItem = {
id?: string
intro: string | null
loop: string | null
}
export type MusicTable = MusicItem[]
export type MusicFileMapping = Record<string, MusicItem>
export type MusicFile = {
filename: string
source: string
}
export type MusicFiles = MusicFile[]
export type DisplayMetaTable = {
playerAvatarData: {
defaultAvatarId: string
avatarList: {
avatarId: string
avatarType: string
avatarIdSort: number
avatarIdDesc: string
avatarItemName: string
avatarItemDesc: string
avatarItemUsage: string
obtainApproach: string
dynAvatarId: null | unknown
}[]
avatarTypeData: {
[key: string]: {
avatarType: string
typeName: string
sortId: number
avatarIdList: string[]
}
}
}
homeBackgroundData: {
defaultBackgroundId: string
defaultThemeId: string
homeBgDataList: {
bgId: string
bgSortId: number
bgStartTime: number
bgName: string
bgMusicId: string
bgDes: string
bgUsage: string
obtainApproach: string
unlockDesList: string[]
}[]
themeList: {
id: string
type: string
sortId: number
startTime: number
tmName: string
tmDes: string
tmUsage: string
obtainApproach: string
unlockDesList: string[]
isLimitObtain: boolean
hideWhenLimit: boolean
rarity: string
}[]
backgroundLimitData: {
[key: string]: {
bgId: string
limitInfos: {
limitInfoId: string
startTime: Date
endTime: Date
invalidObtainDesc: string
displayAfterEndTime: boolean
}[]
}
}
themeLimitData: {
[key: string]: {
id: string
limitInfos: {
startTime: Date
endTime: Date
invalidObtainDesc: string
}[]
}
}
defaultBgMusicId: string
themeStartTime: Date
}
nameCardV2Data: {
fixedModuleData: {
[key: string]: {
id: string
type: string
}
}
removableModuleData: {
[key: string]: {
sortId: number
subType: string
name: string
id: string
type: string
}
}
skinData: {
[key: string]: {
id: string
name: string
type: string
sortId: number
isSpTheme: boolean
defaultShowDetail: boolean
themeName: string
themeEnName: string
skinStartTime: number
skinDesc: string
usageDesc: string
skinApproach: string
unlockConditionCnt: number
unlockDescList: unknown[]
fixedModuleList: string[]
rarity: string
skinTmplCnt: number
canChangeTmpl: boolean
isTimeLimit: boolean
timeLimitInfoList: unknown[]
}
}
consts: {
defaultNameCardSkinId: string
canUidHide: boolean
removableModuleMaxCount: number
}
}
mailArchiveData: {
mailArchiveInfoDict: {
[key: string]: {
id: string
type: string
sortId: number
displayReceiveTs: number
year: number
dateDelta: number
senderId: string
title: string
content: string
rewardList: {
id: string
count: number
type: string
}[]
}
}
constData: {
funcOpenTs: number
}
}
mailSenderData: {
senderDict: {
[key: string]: {
senderId: string
senderName: string
avatarId: string
}
}
}
emoticonData: {
emojiDataDict: {
[key: string]: {
id: string
type: string
sortId: number
picId: string
desc: string
}
}
emoticonThemeDataDict: { [key: string]: string[] }
}
storyVariantData: {
[key: string]: {
plotTaskId: string
spStoryId: string
storyId: string
priority: number
startTime: number
endTime: number
template: string
param: string[]
}
}
}
export type AudioDataTable = {
bgmBanks: {
intro: string
loop: string
volume: number
crossfade: number
delay: number
fadeStyleId: null | string
name: string
}[]
soundFXBanks: {
sounds: {
asset: string
weight: number
important: boolean
is2D: boolean
delay: number
minPitch: number
maxPitch: number
minVolume: number
maxVolume: number
ignoreTimeScale: boolean
}[]
maxSoundAllowed: number
popOldest: boolean
customMixerGroup: string
loop: boolean
name: string
mixerDesc: null | string
}[]
soundFXCtrlBanks: {
targetBank: string
ctrlStop: boolean
ctrlStopFadetime: number
name: string
}[]
snapshotBanks: {
targetSnapshot: string
hookSoundFxBank: string
delay: number
duration: number
name: string
}[]
battleVoice: {
crossfade: number
minTimeDeltaForEnemyEncounter: number
minSpCostForImportantPassiveSkill: number
voiceTypeOptions: {
voiceType: string
priority: number
overlapIfSamePriority: boolean
cooldown: number
delay: number
}[]
}
musics: {
id: string
name: string
bank: string
}[]
duckings: {
bank: string
volume: number
fadeTime: number
delay: number
fadeStyleId: null | string
}[]
fadeStyles: {
styleName: string
fadeinTime: number
fadeoutTime: number
fadeinType: string
fadeoutType: string
}[]
soundFxVoiceLang: {
[key: string]: {
[key: string]: {
JP: string
CN_MANDARIN: string
KR: string
EN?: string
}
}
}
bankAlias: {
[key: string]: string
}
}
export type MusicDataItem = {
id: string
musicId: string
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,27 @@
import baseConfig from '@aklive2d/eslint-config'
import { tsConfig } from '@aklive2d/eslint-config'
import tseslint from 'typescript-eslint'
import globals from 'globals'
/** @type {import('eslint').Config} */
export default [...baseConfig]
export default tseslint.config(
...tsConfig,
{
ignores: ['dist', 'data'],
},
{
files: ['**/*.js', '**/*.ts'],
languageOptions: {
ecmaVersion: 2022,
globals: {
...globals.node,
},
},
rules: {
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_' },
],
},
}
)

View File

@@ -2,6 +2,13 @@ import jsdom from 'jsdom'
import path from 'node:path'
import { file } from '@aklive2d/libs'
import config from '@aklive2d/config'
import type {
OfficialArray,
OfficialInfo,
OfficialOperatorInfo,
OfficialInfoMapping,
OfficialInfoOperatorConfig,
} from './types'
const AUTO_UPDATE_FOLDER = path.resolve(
import.meta.dirname,
@@ -18,22 +25,24 @@ export const update = async () => {
const dom = new jsdom.JSDOM(html_text)
const scripts = dom.window.document.body.querySelectorAll('script')
let data
let data: OfficialArray | null = null
scripts.forEach((e) => {
if (e.textContent.includes('干员晋升')) {
if (e.textContent?.includes('干员晋升')) {
data = JSON.parse(
e.textContent
.replace('self.__next_f.push([1,"c:', '')
.replace('\\n"])', '')
.replaceAll('\\', '')
)
) as OfficialArray
}
})
const rows = data[0][3].initialData
if (!data) throw new Error('No data found')
const rows = (data as OfficialArray)[0][3].initialData
const dict = {
const dict: OfficialInfo = {
length: rows.length,
dates: [],
info: {},
}
let current_displayTime = rows[0].displayTime
@@ -42,22 +51,22 @@ export const update = async () => {
for (const row of rows) {
const displayTime = row.displayTime
if (displayTime !== current_displayTime) {
dict[current_displayTime] = current_block
dict.info[current_displayTime] = current_block
dict.dates.push(current_displayTime)
current_displayTime = row.displayTime
current_block = []
}
current_block.push(get_row(row))
}
dict[current_displayTime] = current_block
dict.info[current_displayTime] = current_block
dict.dates.push(current_displayTime)
file.writeSync(JSON.stringify(dict, null, 4), OFFICIAL_INFO_JSON)
}
const get_row = (row) => {
const get_row = (row: OfficialOperatorInfo): OfficialInfoOperatorConfig => {
const type = row.type
let codename_zhCN, item_type
let codename_zhCN, item_type: 'operator' | 'skin'
switch (type) {
case 0:
codename_zhCN = row.charName
@@ -82,18 +91,18 @@ const get_row = (row) => {
}
const generateMapping = () => {
const mapping = {}
const data = JSON.parse(file.readSync(OFFICIAL_INFO_JSON))
if (!data) return
Object.keys(data).forEach((key) => {
if (typeof data[key] === 'object') {
data[key].forEach((operator) => {
mapping[operator.id] = {
date: key,
...operator,
}
})
}
const mapping: OfficialInfoMapping = {}
const content = file.readSync(OFFICIAL_INFO_JSON)
if (!content) throw new Error('Failed to read official info JSON')
const data: OfficialInfo = JSON.parse(content)
if (!data) throw new Error('Failed to parse official info JSON')
Object.keys(data.info).forEach((date) => {
data.info[date].forEach((operator) => {
mapping[operator.id] = {
date,
...operator,
}
})
})
return mapping
}

View File

@@ -2,7 +2,7 @@
"name": "@aklive2d/official-info",
"private": true,
"version": "0.0.0",
"main": "index.js",
"main": "index.ts",
"type": "module",
"dependencies": {
"jsdom": "^26.0.0",
@@ -11,8 +11,13 @@
"@aklive2d/eslint-config": "workspace:*",
"@aklive2d/prettier-config": "workspace:*"
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript-eslint": ">=8.31.1",
"typescript": ">=5.8.2"
},
"scripts": {
"update": "mode=update node runner.js",
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check ."
"update": "mode=update bun runner.ts",
"lint": "eslint && prettier --check ."
}
}

View File

@@ -1,5 +1,5 @@
import { envParser } from '@aklive2d/libs'
import { update } from './index.js'
import { update } from './index.ts'
async function main() {
const { mode } = envParser.parse({

View File

@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2024",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2024", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["**/*"],
"exclude": ["dist/**/*", "data/**/*"]
}

View File

@@ -0,0 +1,48 @@
export type OfficialOperatorInfo = {
cid: string
charName: string
suitName: string
codename: string
type: number
displayTime: string
portraitSrc: string
}
export type OfficialDataArray = [
'$',
string,
null,
{
initialData: OfficialOperatorInfo[]
},
]
type UnrelatedDataArray = ['$', string, null, unknown]
export type OfficialArray = [OfficialDataArray, UnrelatedDataArray]
export type OfficialInfo = {
length: number
dates: string[]
info: {
[date: string]: OfficialInfoOperatorConfig[]
}
}
export interface OfficialInfoOperatorConfig {
codename: {
'zh-CN': string
'en-US': string
}
type: 'operator' | 'skin'
link: string
id: string
}
export interface OperatorConfig extends OfficialInfoOperatorConfig {
date: string
}
export type OfficialInfoMapping = {
[id: string]: OperatorConfig
}

View File

@@ -1,3 +1,5 @@
dist
data
auto_update
auto_update
config
config.yaml

View File

@@ -61,3 +61,7 @@ yu: !include config/yu.yaml
logos_radiant_serenity: !include config/logos_radiant_serenity.yaml
muelsyse_golden_reverie: !include config/muelsyse_golden_reverie.yaml
ines_melodic_flutter: !include config/ines_melodic_flutter.yaml
wisadel_supernova: !include config/wisadel_supernova.yaml
archetto_glory_of_the_devout: !include config/archetto_glory_of_the_devout.yaml
kroos_moonlit_voyage: !include config/kroos_moonlit_voyage.yaml
exusiai_the_new_covenant: !include config/exusiai_the_new_covenant.yaml

View File

@@ -0,0 +1,13 @@
filename: dyn_illust_char_332_archet_sale#14
logo: logo_Laterano
fallback_name: char_332_archet_sale#14
viewport_left: 0
viewport_right: 0
viewport_top: 0
viewport_bottom: 0
invert_filter: true
codename:
zh-CN: 至虔者荣光 · 空弦
en-US: Glory of the Devout / Archetto
use_json: false
official_id: '202504953'

View File

@@ -0,0 +1,13 @@
filename: dyn_illust_char_1041_angel2
logo: logo_penguin
fallback_name: char_1041_angel2_2
viewport_left: 0
viewport_right: 0
viewport_top: 0
viewport_bottom: 0
invert_filter: true
codename:
zh-CN: 新约能天使
en-US: Exusiai the New Covenant
use_json: false
official_id: '202504941'

View File

@@ -0,0 +1,13 @@
filename: dyn_illust_char_124_kroos_sale#14
logo: logo_reserve1
fallback_name: char_124_kroos_sale#14
viewport_left: 0
viewport_right: 0
viewport_top: 0
viewport_bottom: 0
invert_filter: true
codename:
zh-CN: 星月漂流记 · 克洛丝
en-US: Moonlit Voyage / Kroos
use_json: false
official_id: '202504992'

View File

@@ -0,0 +1,13 @@
filename: dyn_illust_char_1035_wisdel_sale#14
logo: logo_babel
fallback_name: char_1035_wisdel_sale#14
viewport_left: 0
viewport_right: 0
viewport_top: 0
viewport_bottom: 0
invert_filter: true
codename:
zh-CN: 超新星 · 维什戴尔
en-US: Supernova / Wisadel
use_json: false
official_id: '202504974'

View File

@@ -1,3 +1,27 @@
import baseConfig from '@aklive2d/eslint-config'
import { tsConfig } from '@aklive2d/eslint-config'
import tseslint from 'typescript-eslint'
import globals from 'globals'
/** @type {import('eslint').Config} */
export default [...baseConfig]
export default tseslint.config(
...tsConfig,
{
ignores: ['dist', 'data'],
},
{
files: ['**/*.js', '**/*.ts'],
languageOptions: {
ecmaVersion: 2022,
globals: {
...globals.node,
},
},
rules: {
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_' },
],
},
}
)

View File

@@ -3,19 +3,20 @@ import { stringify } from 'yaml'
import { yaml, file, alphaComposite } from '@aklive2d/libs'
import config from '@aklive2d/config'
import { mapping as officialInfoMapping } from '@aklive2d/official-info'
import type { Config, PortraitHub, PortraitJson, AssetsJson } from './types.ts'
export const CONFIG_PATH = path.resolve(
import.meta.dirname,
config.module.operator.config_yaml
)
const CONFIG = yaml.read(CONFIG_PATH)
const CONFIG: Config = yaml.read(CONFIG_PATH)
export const OPERATOR_SOURCE_FOLDER = path.resolve(
import.meta.dirname,
config.dir_name.data
)
const DIST_DIR = path.join(import.meta.dirname, config.dir_name.dist)
const getVoiceFolders = (name) => {
const getVoiceFolders = (name: string) => {
return config.dir_name.voice.sub.map((sub) =>
path.join(
OPERATOR_SOURCE_FOLDER,
@@ -26,7 +27,7 @@ const getVoiceFolders = (name) => {
)
}
const getExtractedFolder = (name) => {
const getExtractedFolder = (name: string) => {
return path.join(OPERATOR_SOURCE_FOLDER, name, config.dir_name.extracted)
}
@@ -34,11 +35,11 @@ const getConfigFolder = () => {
return path.join(import.meta.dirname, config.module.operator.config)
}
const getDistFolder = (name) => {
const getDistFolder = (name: string) => {
return path.join(DIST_DIR, config.module.operator.operator, name)
}
export const has = (name) => {
export const has = (name: string) => {
return Object.keys(operators).includes(name)
}
@@ -67,7 +68,7 @@ const generateMapping = () => {
return CONFIG
}
const copyVoices = (name) => {
const copyVoices = (name: string) => {
file.symlinkAll(
path.join(OPERATOR_SOURCE_FOLDER, name, config.dir_name.voice.main),
path.join(DIST_DIR, config.dir_name.voice.main, name)
@@ -85,15 +86,15 @@ const operators = generateMapping()
export default operators
export function getOperatorId(name, matcher = '$2$3$4') {
export function getOperatorId(name: string, matcher = '$2$3$4') {
return name.replace(/^(.*)(char_[\d]+)(_[A-Za-z0-9]+)(|_.*)$/g, matcher)
}
export const getOperatorAlternativeId = (id) => {
export const getOperatorAlternativeId = (id: string) => {
return getOperatorId(id, '$2$3')
}
export const build = async (namesToBuild) => {
export const build = async (namesToBuild: string[]) => {
const names = !namesToBuild.length ? Object.keys(operators) : namesToBuild
console.log('Generating assets for', names.length, 'operators')
for (const name of names) {
@@ -103,7 +104,7 @@ export const build = async (namesToBuild) => {
copyLogos()
}
const generateAssets = async (name) => {
const generateAssets = async (name: string) => {
const extractedDir = getExtractedFolder(name)
const outDir = getDistFolder(name)
file.rmdir(outDir)
@@ -134,31 +135,35 @@ const generateAssets = async (name) => {
OPERATOR_SOURCE_FOLDER,
config.module.operator.portraits
)
const portraitHub = JSON.parse(
file.readSync(
path.join(
portraitDir,
config.module.operator.MonoBehaviour,
'portrait_hub.json'
)
const portraitHubContent = file.readSync(
path.join(
portraitDir,
config.module.operator.MonoBehaviour,
'portrait_hub.json'
)
)
if (!portraitHubContent) throw new Error('portrait_hub.json not found')
const portraitHub: PortraitHub = JSON.parse(portraitHubContent)
const fallback_name_lowerCase = fallback_name.toLowerCase()
const portraitAtlas = portraitHub._sprites.find(
const portraitItem = portraitHub._sprites.find(
(item) => item.name.toLowerCase() === fallback_name_lowerCase
).atlas
const portraitJson = JSON.parse(
file.readSync(
path.join(
portraitDir,
config.module.operator.MonoBehaviour,
`portraits#${portraitAtlas}.json`
)
)
if (!portraitItem) throw new Error(`portrait ${fallback_name} not found`)
const portraitAtlas = portraitItem.atlas
const portraitJsonText = file.readSync(
path.join(
portraitDir,
config.module.operator.MonoBehaviour,
`portraits#${portraitAtlas}.json`
)
)
if (!portraitJsonText)
throw new Error(`portrait ${fallback_name} json not found`)
const portraitJson: PortraitJson = JSON.parse(portraitJsonText)
const item = portraitJson._sprites.find(
(item) => item.name.toLowerCase() === fallback_name_lowerCase
)
if (!item) throw new Error(`portrait ${fallback_name} not found`)
const rect = {
...item.rect,
rotate: item.rotate,
@@ -186,15 +191,18 @@ const generateAssets = async (name) => {
}
export const generateAssetsJson = async (
filename,
extractedDir,
targetDir,
_opts = {
filename: string,
extractedDir: string,
targetDir: string,
_opts: {
useJSON?: boolean
useSymLink?: boolean
} = {
useJSON: false,
useSymLink: true,
}
) => {
const assetsJson = []
const assetsJson: AssetsJson = []
let skelFilename
if (_opts.useJSON) {
@@ -206,6 +214,8 @@ export const generateAssetsJson = async (
const atlasPath = path.join(extractedDir, atlasFilename)
let atlas = await file.read(atlasPath)
const matches = atlas.match(new RegExp(/(.*).png/g))
if (!matches)
throw new Error(`No matches found in atlas file ${atlasFilename}`)
for (const item of matches) {
let buffer
const alphaCompositeFilename = `${path.parse(item).name}[alpha].png`
@@ -236,17 +246,19 @@ export const generateAssetsJson = async (
const dir = path.join(targetDir, item.filename)
if (item.content) {
file.writeSync(item.content, dir)
} else {
} else if (item.path) {
if (_opts.useSymLink) {
file.symlink(item.path, dir)
} else {
file.cpSync(item.path, dir)
}
} else {
throw new Error(`Invalid asset item: ${item}`)
}
})
}
export const init = (name, id) => {
export const init = (name: string, id: string) => {
const voiceFolders = getVoiceFolders(name)
const extractedFolder = getExtractedFolder(name)
const operatorConfigFolder = getConfigFolder()

View File

@@ -2,7 +2,7 @@
"name": "@aklive2d/operator",
"private": true,
"version": "0.0.0",
"main": "index.js",
"main": "index.ts",
"type": "module",
"dependencies": {
"yaml": "^2.7.0",
@@ -12,10 +12,15 @@
"@aklive2d/eslint-config": "workspace:*",
"@aklive2d/prettier-config": "workspace:*"
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript-eslint": ">=8.31.1",
"typescript": ">=5.8.2"
},
"scripts": {
"build": "mode=build node runner.js",
"init": "mode=init node runner.js",
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check .",
"build": "mode=build bun runner.ts",
"init": "mode=init bun runner.ts",
"lint": "eslint && prettier --check .",
"build:cleanup": "rm -rf ./dist ./data"
}
}

View File

@@ -1,5 +1,11 @@
import { envParser } from '@aklive2d/libs'
import { build, init } from './index.js'
import { build, init } from './index.ts'
type Args = {
mode: string
name: string[]
id: string
}
async function main() {
const { mode, name, id } = envParser.parse({
@@ -16,7 +22,7 @@ async function main() {
id: {
type: 'string',
},
})
}) as Args
switch (mode) {
case 'build':
await build(name)

View File

@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2024",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2024", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["**/*"],
"exclude": ["dist/**/*", "data/**/*"]
}

View File

@@ -0,0 +1,93 @@
export type Codename = { 'zh-CN': string; 'en-US': string }
export interface OperatorConfig {
filename: string
logo: string
fallback_name: string
viewport_left: number
viewport_right: number
viewport_top: number
viewport_bottom: number
invert_filter: boolean
codename: Codename
use_json: boolean
official_id: string
title: string
type: 'operator' | 'skin'
link: string
id: string
date: string
}
export type Config = {
[name: string]: OperatorConfig
}
type FileIDPathID = {
m_FileID: number
m_PathID: number
}
export type PortraitHub = {
m_GameObject: FileIDPathID
m_Enabled: number
m_Script: FileIDPathID
m_Name: string
_sprites: {
name: string
atlas: number
}[]
_atlases: string[]
_inputSpriteDir: string
_outputAtlasDir: string
_rootAtlasName: string
_spriteSize: {
width: number
height: number
}
_cntPerAtlas: number
_maxAtlasSize: number
}
type SignedItem = {
name: string
guid: string
md5: string
}
export type PortraitJson = {
m_GameObject: FileIDPathID
m_Enabled: number
m_Script: FileIDPathID
m_Name: string
_sprites: {
name: string
guid: string
atlas: number
rect: {
x: number
y: number
w: number
h: number
}
rotate: number
}[]
_atlas: {
index: number
texture: FileIDPathID
alpha: FileIDPathID
size: number
}
_index: number
_sign: {
m_sprites: SignedItem[]
m_atlases: SignedItem[]
m_alphas: SignedItem[]
}
}
export type AssetsJson = {
filename: string
path?: string
content?: string | Buffer<ArrayBufferLike> | Buffer
}[]

View File

@@ -1,14 +1,11 @@
{
"name": "@aklive2d/postcss-config",
"private": true,
"version": "0.0.0",
"main": "index.js",
"type": "module",
"license": "MIT",
"dependencies": {
"postcss": "^8.5.1"
},
"publishConfig": {
"access": "public"
}
"name": "@aklive2d/postcss-config",
"private": true,
"version": "0.0.0",
"main": "index.js",
"type": "module",
"license": "MIT",
"dependencies": {
"postcss": "^8.5.1"
}
}

View File

@@ -1,14 +1,9 @@
// prettier.config.js, .prettierrc.js, prettier.config.mjs, or .prettierrc.mjs
/**
* @see https://prettier.io/docs/en/configuration.html
* @type {import("prettier").Config}
*/
const config = {
trailingComma: "es5",
trailingComma: 'es5',
tabWidth: 4,
semi: false,
singleQuote: true,
};
}
export default config;
export default config

View File

@@ -1,11 +1,12 @@
{
"name": "@aklive2d/prettier-config",
"private": true,
"version": "0.0.0",
"license": "MIT",
"main": "index.js",
"type": "module",
"peerDependencies": {
"prettier": ">=3.0.0"
}
"name": "@aklive2d/prettier-config",
"private": true,
"version": "0.0.0",
"license": "MIT",
"main": "index.js",
"type": "module",
"peerDependencies": {
"prettier": ">=3.0.0",
"typescript": ">=5.8.2"
}
}

View File

@@ -1,3 +1,27 @@
import baseConfig from '@aklive2d/eslint-config'
import { tsConfig } from '@aklive2d/eslint-config'
import tseslint from 'typescript-eslint'
import globals from 'globals'
/** @type {import('eslint').Config} */
export default [...baseConfig]
export default tseslint.config(
...tsConfig,
{
ignores: ['dist', 'data'],
},
{
files: ['**/*.js', '**/*.ts'],
languageOptions: {
ecmaVersion: 2022,
globals: {
...globals.node,
},
},
rules: {
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_' },
],
},
}
)

View File

@@ -1,15 +1,22 @@
import path from 'node:path'
import Matcher from './libs/content_processor.js'
import Matcher from './libs/content_processor.ts'
import { yaml, file } from '@aklive2d/libs'
import config from '@aklive2d/config'
import operators, { OPERATOR_SOURCE_FOLDER } from '@aklive2d/operator'
import { files as backgrounds } from '@aklive2d/background'
import { mapping as musics } from '@aklive2d/music'
import { getLangs } from '@aklive2d/charword-table'
import type { ScalarTag } from 'yaml'
import type {
Assets,
TemplateYAML,
ProjectJSON,
ProjectJSONProperty,
} from './types.ts'
const DIST_DIR = path.join(import.meta.dirname, config.dir_name.dist)
export const build = (namesToBuild) => {
export const build = (namesToBuild: string[]) => {
const err = []
const names = !namesToBuild.length ? Object.keys(operators) : namesToBuild
console.log('Generating assets for', names.length, 'operators')
@@ -43,15 +50,15 @@ export const build = (namesToBuild) => {
return err
}
const getDistDir = (name) => {
const getDistDir = (name: string) => {
return path.join(DIST_DIR, name)
}
const getDefaultPath = (name) => {
const getDefaultPath = (name: string) => {
return path.join(OPERATOR_SOURCE_FOLDER, name)
}
const getPath = (name) => {
const getPath = (name: string) => {
// if exists, do not use the template
const defaultPath = path.join(
getDefaultPath(name),
@@ -67,7 +74,15 @@ const getPath = (name) => {
}
}
const process = ({ name, data, template }) => {
const process = ({
name,
data,
template,
}: {
name: string
data: ProjectJSON
template: TemplateYAML
}) => {
return {
...data,
description: template.description,
@@ -79,12 +94,14 @@ const process = ({ name, data, template }) => {
...getProperties(template),
},
},
}
} as ProjectJSON
}
const getProperties = (template) => {
const getProperties = (template: TemplateYAML) => {
const properties = template.properties
const output = {}
const output = {} as {
[key: string]: ProjectJSONProperty
}
for (let i = 0; i < properties.length; i++) {
output[properties[i].key] = {
index: i,
@@ -95,13 +112,15 @@ const getProperties = (template) => {
return output
}
const load = async (name, assets) => {
const load = async (name: string, assets: Assets) => {
// load json from file
let data = JSON.parse(await file.read(getPath(name)))
let data = JSON.parse(await file.read(getPath(name))) as ProjectJSON
const matcher = new Matcher('~{', '}', operators[name], {
...assets,
...(() => {
const output = {}
const output = {} as {
[key: string]: { label: string; value: string }[]
}
for (const [key, value] of Object.entries(assets)) {
output[`${key}Options`] = value.map((b) => {
return {
@@ -114,20 +133,21 @@ const load = async (name, assets) => {
})(),
})
const match = {
identify: (value) => value.startsWith('!match'),
identify: (value: unknown) =>
typeof value === 'string' && value.startsWith('!match'),
tag: '!match',
resolve(str) {
resolve(str: string) {
matcher.content = str
return matcher.result
},
}
} as ScalarTag
const template = yaml.read(
path.resolve(
import.meta.dirname,
config.module.project_json.template_yaml
),
[match]
)
) as TemplateYAML
data = process({
name,
data,

View File

@@ -1,11 +1,20 @@
import type { OperatorConfig, Codename } from '@aklive2d/operator/types'
import type { Assets } from '../types.ts'
export default class Matcher {
#start
#end
#reExp
#config
#assets
content: string = ''
constructor(start, end, config, assets) {
constructor(
start: string,
end: string,
config: OperatorConfig,
assets: Assets
) {
this.#start = start
this.#end = end
this.#reExp = new RegExp(`${start}.+?${end}`, 'g')
@@ -40,27 +49,50 @@ class Evalable {
#config
#assets
constructor(config, assets) {
constructor(config: OperatorConfig, assets: Assets) {
this.#config = config
this.#assets = assets
}
split(location, varName, separator) {
return this.#step(location, varName).split(separator)
split(location: 'config' | 'assets', varName: string, separator: string) {
return (this.#step(location, varName) as string).split(separator)
}
var(location, varName) {
var(location: 'config' | 'assets', varName: string) {
return this.#step(location, varName)
}
#step(location, varName) {
let content = this.#config
#step(
location: 'config' | 'assets',
varName: string
):
| OperatorConfig
| Assets
| string
| number
| boolean
| Codename
| string[] {
let content:
| OperatorConfig
| Assets
| string
| number
| boolean
| Codename
| string[] = this.#config
if (location === 'assets') content = this.#assets
varName.split('->').forEach((item) => {
try {
content = content[item]
} catch (e) {
throw new Error(`Cannot step ${varName}.`, e)
if (location === 'config') {
content = (content as OperatorConfig)[
item as keyof OperatorConfig
]
} else {
content = (content as Assets)[item as keyof Assets]
}
} catch (e: unknown) {
throw new Error(`Cannot step ${varName}. ${e}`)
}
})
return content

View File

@@ -1,7 +1,7 @@
{
"name": "@aklive2d/project-json",
"version": "0.0.0",
"main": "index.js",
"main": "index.ts",
"type": "module",
"license": "MIT",
"dependencies": {
@@ -14,8 +14,13 @@
"@aklive2d/music": "workspace:*",
"@aklive2d/prettier-config": "workspace:*"
},
"peerDependencies": {
"globals": ">=16.0.0",
"typescript-eslint": ">=8.31.1",
"typescript": ">=5.8.2"
},
"scripts": {
"build": "mode=build node runner.js",
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check ."
"build": "mode=build bun runner.ts",
"lint": "eslint && prettier --check ."
}
}

View File

@@ -14,7 +14,7 @@ async function main() {
multiple: true,
default: [],
},
})
}) as { mode: string; name: string[] }
switch (mode) {
case 'build':
err = build(name)

View File

@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2024",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2024", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["**/*"],
"exclude": ["dist/**/*", "data/**/*"]
}

View File

@@ -0,0 +1,66 @@
export type Assets = {
backgrounds: string[]
voiceLangs: string[]
subtitleLangs: string[]
music: string[]
}
export interface Property {
text: string
type?: 'bool' | 'file' | 'slider' | 'combo' | 'textinput'
value?: boolean | string
condition?: string
fraction?: boolean
max?: number
min?: number
step?: number
precision?: number
options?: string[]
fileType?: 'video'
}
export interface ProjectJSONProperty extends Property {
index: number
order: number
}
interface Template {
description: string
localization: {
'en-us': Record<string, string>
'zh-chs': Record<string, string>
}
}
export interface TemplateYAML extends Template {
properties: {
key: string
value: Property
}[]
}
interface TemplateJSON extends Template {
properties: {
[key: string]: ProjectJSONProperty
}
}
export interface ProjectJSONBase {
contentrating: string
description: string
file: string
preview: string
ratingsex: string
ratingviolence: string
snapshotformat: number
snapshotoverlay: string
tags: string[]
title: string
type: string
version: number
workshopid: string
}
export interface ProjectJSON extends ProjectJSONBase {
general: TemplateJSON
}

View File

@@ -1,15 +1,12 @@
{
"name": "@aklive2d/stylelint-config",
"private": true,
"version": "0.0.0",
"license": "MIT",
"main": "index.js",
"type": "module",
"dependencies": {
"stylelint-config-standard-scss": "^14.0.0",
"stylelint-prettier": "^5.0.3"
},
"publishConfig": {
"access": "public"
}
"name": "@aklive2d/stylelint-config",
"private": true,
"version": "0.0.0",
"license": "MIT",
"main": "index.js",
"type": "module",
"dependencies": {
"stylelint-config-standard-scss": "^14.0.0",
"stylelint-prettier": "^5.0.3"
}
}

Some files were not shown because too many files have changed in this diff Show More