feat(task): replace built-in task adaptors with a sandboxed JS plugin system (#7076)

This commit is contained in:
Calcium-Ion
2026-08-29 18:51:57 +08:00
committed by GitHub
parent 7037ac15bd
commit eb48396d5f
336 changed files with 52333 additions and 6369 deletions
@@ -0,0 +1,116 @@
/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { describe, expect, test } from 'vitest'
import {
resolveLocalizedText,
type LocalizedTextValue,
} from '../localized-text'
type ResolveCase = {
name: string
value: LocalizedTextValue | null | undefined
language: string
expected: string
}
const KLING = {
en: 'Video generation via Kling API',
zh: '可灵视频生成',
'zh-TW': '可靈影片生成',
} as const
describe('resolveLocalizedText', () => {
test.each<ResolveCase>([
{
name: 'returns a bare string unchanged so legacy marketplace indexes still render',
value: 'Video generation via Kling API',
language: 'zhCN',
expected: 'Video generation via Kling API',
},
{
name: 'returns the exact BCP-47 tag when the map contains zh-TW',
value: KLING,
language: 'zh-TW',
expected: '可靈影片生成',
},
{
name: 'matches zh-TW case-insensitively when i18next language is zh-tw',
value: KLING,
language: 'zh-tw',
expected: '可靈影片生成',
},
{
name: 'maps the project i18next code zhTW onto the zh-TW map key',
value: KLING,
language: 'zhTW',
expected: '可靈影片生成',
},
{
name: 'falls back from zh-TW to the zh primary subtag when zh-TW is absent',
value: { en: KLING.en, zh: KLING.zh },
language: 'zh-TW',
expected: '可灵视频生成',
},
{
name: 'maps the project i18next code zhCN onto the zh primary subtag',
value: { en: KLING.en, zh: KLING.zh },
language: 'zhCN',
expected: '可灵视频生成',
},
{
name: 'falls back from en-US to en when only the primary tag exists',
value: { en: KLING.en, zh: KLING.zh },
language: 'en-US',
expected: 'Video generation via Kling API',
},
{
name: 'falls back to en when the requested language and its primary tag are absent',
value: { en: KLING.en, ja: 'Kling で動画生成' },
language: 'fr',
expected: 'Video generation via Kling API',
},
{
name: 'uses the first sorted key when en and the requested language are both absent',
value: { ja: 'Kling で動画生成', fr: 'Génération vidéo Kling' },
language: 'ru',
expected: 'Génération vidéo Kling',
},
{
name: 'returns an empty string when the value is null',
value: null,
language: 'en',
expected: '',
},
{
name: 'returns an empty string when the value is undefined',
value: undefined,
language: 'en',
expected: '',
},
{
name: 'returns an empty string when the map has no usable entries',
value: {},
language: 'zhCN',
expected: '',
},
])('$name', ({ value, language, expected }) => {
expect(resolveLocalizedText(value, language)).toBe(expected)
})
})
+2
View File
@@ -25,6 +25,7 @@ export type AdminCapabilities = AdminPermissionMatrix
export const ADMIN_PERMISSION_RESOURCES = {
CHANNEL: 'channel',
TASK_PLUGIN: 'task_plugin',
} as const
export const ADMIN_PERMISSION_ACTIONS = {
@@ -33,6 +34,7 @@ export const ADMIN_PERMISSION_ACTIONS = {
WRITE: 'write',
SENSITIVE_WRITE: 'sensitive_write',
SECRET_VIEW: 'secret_view',
BIND: 'bind',
} as const
// The role whose baseline grants are used as defaults in the permission editor.
+82
View File
@@ -0,0 +1,82 @@
/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
/**
* Plugin / marketplace copy that may be a bare string (legacy marketplace
* index) or a BCP-47 map. Gateway APIs always emit the map form.
*/
export type LocalizedTextValue = string | Record<string, string>
/**
* Resolve LocalizedText against an i18next language code.
*
* This project's `i18n.language` values are `en` / `zhCN` / `zhTW` / `fr` /
* `ru` / `ja` / `vi` (see `web/src/i18n/config.ts`). Backend keys are BCP-47
* (`en`, `zh`, `zh-TW`). Matching is case-insensitive and also accepts
* hyphenated tags (`zh-TW`, `en-US`) so callers can pass either shape.
*
* Fallback: exact tag → primary subtag → `en` → first key in sorted order → `''`.
*/
export function resolveLocalizedText(
value: LocalizedTextValue | undefined | null,
language: string
): string {
if (value == null) return ''
if (typeof value === 'string') return value
if (typeof value !== 'object' || Array.isArray(value)) return ''
const texts = new Map<string, string>()
for (const [key, text] of Object.entries(value)) {
if (typeof text !== 'string' || text.trim() === '') continue
const locale = key.trim().replaceAll('_', '-').toLowerCase()
if (!locale) continue
texts.set(locale, text)
}
if (texts.size === 0) return ''
for (const candidate of localeFallbackKeys(language)) {
const hit = texts.get(candidate)
if (hit !== undefined) return hit
}
const firstKey = [...texts.keys()].sort((left, right) =>
left.localeCompare(right)
)[0]
return firstKey ? (texts.get(firstKey) ?? '') : ''
}
function localeFallbackKeys(language: string): string[] {
const normalized = language.trim().replaceAll('_', '-').toLowerCase()
const keys: string[] = []
const add = (tag: string) => {
if (tag && !keys.includes(tag)) keys.push(tag)
}
add(normalized)
if (normalized === 'zhcn') add('zh-cn')
if (normalized === 'zhtw') add('zh-tw')
if (normalized.includes('-')) {
add(normalized.slice(0, normalized.indexOf('-')))
} else if (normalized === 'zhcn' || normalized === 'zhtw') {
add('zh')
}
add('en')
return keys
}