diff --git a/README.md b/README.md index df7a93513..6648e8533 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,17 @@ Hydro 用户群:1085853538

更新日志(点击展开)

+### Hydro 4.13.2 / UI 4.52.0 +- a11y: 优化网页测性能测试输出顺序 +- ui: 使用新 markdown 编辑器 +- core: 优化计分板逻辑 +- ui: 将缓存移至 IndexedDB +- core: 转写 migration 为服务 +- ui: 优化 safari 浏览器兼容 +- core: 修复题目 maintainer 字段 +- judge: 支持 pretest 中使用文件 IO +- vjudge: codeforces: 添加检查是否提交成功 + ### Hydro 4.13.0 / UI 4.51.0 - ui: 添加 sentry - core&ui: 支持显示当前编译器版本 diff --git a/build/version.ts b/build/version.ts new file mode 100644 index 000000000..1e1c6feb0 --- /dev/null +++ b/build/version.ts @@ -0,0 +1,55 @@ +import { execSync } from 'child_process'; +import * as fs from 'fs'; +import semver from 'semver'; +import { getWorkspaces } from './utils'; + +const ignore = ['@hydrooj/eslint-config']; +const lowest = '18.0.0'; +const processed = {}; + +function addRange(range: string, hint: string) { + const minVersion = semver.minVersion(range); + if (semver.gt(minVersion!, lowest)) { + console.log(`${hint} wants ${minVersion}`); + execSync(`yarn why ${hint}`, { stdio: 'inherit' }); + } +} + +function calcPackage(name: string) { + if (processed[name]) return; + processed[name] = true; + let jsonPath = ''; + try { + jsonPath = require.resolve(`${name}/package.json`); + } catch (e) { + jsonPath = `${require.resolve(name).split(name)[0] + name}/package.json`; + } + try { + const json = JSON.parse(fs.readFileSync(jsonPath, 'utf-8')); + if (json.engines?.node) addRange(json.engines.node, name); + const deps = json.dependencies || {}; + for (const dep in deps) { + calcPackage(dep); + } + } catch (e) { + console.error(`failed to process ${name}`); + } +} + +(async () => { + let folders = await getWorkspaces(); + if (process.argv[2]) { + folders = folders.filter((p) => p.startsWith(process.argv[2])); + } + + for (const name of folders) { + try { + // eslint-disable-next-line import/no-dynamic-require + const meta = require(`../${name}/package.json`); + if (!ignore.includes(meta.name)) calcPackage(meta.name); + } catch (e) { + console.error(e); + } + } + console.log(lowest); +})().catch((e) => process.exit(1)); diff --git a/framework/eslint-config/package.json b/framework/eslint-config/package.json index 23df59fd1..4a2424644 100644 --- a/framework/eslint-config/package.json +++ b/framework/eslint-config/package.json @@ -1,18 +1,18 @@ { "name": "@hydrooj/eslint-config", - "version": "1.0.11", + "version": "1.0.12", "license": "MIT", "main": "react.yaml", "dependencies": { "@stylistic/eslint-plugin": "^2.3.0", - "@typescript-eslint/eslint-plugin": "^7.15.0", - "@typescript-eslint/parser": "^7.15.0", + "@typescript-eslint/eslint-plugin": "^7.16.0", + "@typescript-eslint/parser": "^7.16.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^18.0.0", "eslint-plugin-github": "^5.0.1", "eslint-plugin-import": "2.29.1", "eslint-plugin-jsx-a11y": "^6.9.0", - "eslint-plugin-react": "^7.34.3", + "eslint-plugin-react": "^7.34.4", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-simple-import-sort": "^12.1.1" }, diff --git a/framework/framework/package.json b/framework/framework/package.json index 80e7f873b..ad68c6b3a 100644 --- a/framework/framework/package.json +++ b/framework/framework/package.json @@ -16,7 +16,7 @@ "koa-router": "^12.0.1", "lodash": "^4.17.21", "moment-timezone": "^0.5.45", - "path-to-regexp": "^7.0.0", + "path-to-regexp": "^7.1.0", "sanitize-filename": "^1.6.3", "saslprep": "^1.0.3", "shorty.js": "^1.0.1", diff --git a/package.json b/package.json index da0a7c30b..366851de9 100644 --- a/package.json +++ b/package.json @@ -51,14 +51,14 @@ "fs-extra": "^11.2.0", "globby": "^14.0.2", "inspectpack": "^4.7.1", - "mongodb-memory-server": "^9.4.0", + "mongodb-memory-server": "^9.4.1", "nyc": "^17.0.0", "ora": "^8.0.1", - "package-json": "^10.0.0", + "package-json": "^10.0.1", "semver": "^7.6.2", "simple-git": "^3.25.0", "supertest": "^7.0.0", "typescript": "5.3.3", - "wtfnode": "^0.9.2" + "wtfnode": "^0.9.3" } } diff --git a/packages/hydrojudge/package.json b/packages/hydrojudge/package.json index 741bbe263..c11a715a5 100644 --- a/packages/hydrojudge/package.json +++ b/packages/hydrojudge/package.json @@ -1,7 +1,7 @@ { "name": "@hydrooj/hydrojudge", "bin": "bin/hydrojudge.js", - "version": "3.3.15", + "version": "3.3.16", "main": "./src/index.ts", "author": "undefined ", "repository": "https://github.com/hydro-dev/Hydro.git", diff --git a/packages/hydrojudge/src/judge/run.ts b/packages/hydrojudge/src/judge/run.ts index b677137fc..6e487f83d 100644 --- a/packages/hydrojudge/src/judge/run.ts +++ b/packages/hydrojudge/src/judge/run.ts @@ -18,6 +18,7 @@ export const judge = async (ctx: JudgeTask) => { // Allow 2x limits for better debugging time: parseTimeMS(ctx.config.time || '1s') * 2, memory: parseMemoryMB(ctx.config.memory || '128m'), + filename: ctx.config.filename, addressSpaceLimit: address_space_limit, processLimit: process_limit, }, diff --git a/packages/hydrojudge/src/sandbox.ts b/packages/hydrojudge/src/sandbox.ts index e91d5ccc6..9b00b523f 100644 --- a/packages/hydrojudge/src/sandbox.ts +++ b/packages/hydrojudge/src/sandbox.ts @@ -74,7 +74,7 @@ function parseArgs(execute: string): string[] { function proc(params: Parameter): Cmd { const copyOut = supportOptional - ? params.copyOut + ? (params.copyOut || []) : (params.copyOut || []).map((i) => (i.endsWith('?') ? i.substring(0, i.length - 1) : i)); const stdioLimit = parseMemoryMB(getConfig('stdio_size')); const stdioSize = params.cacheStdoutAndStderr ? stdioLimit : 4; @@ -82,7 +82,7 @@ function proc(params: Parameter): Cmd { if (params.cacheStdoutAndStderr) { copyOutCached.push('stdout', 'stderr'); if (params.filename) copyOutCached.push(`${params.filename}.out?`); - } + } else if (params.filename) copyOut.push(`${params.filename}.out${supportOptional ? '?' : ''}`); const copyIn = { ...(params.copyIn || {}) }; const stdin = params.stdin || { content: '' }; if (params.filename) copyIn[`${params.filename}.in`] = stdin; diff --git a/packages/hydrooj/locales/zh.yaml b/packages/hydrooj/locales/zh.yaml index 39d5b48e2..78c0ed539 100644 --- a/packages/hydrooj/locales/zh.yaml +++ b/packages/hydrooj/locales/zh.yaml @@ -636,6 +636,7 @@ Quit Scratchpad: 退出在线编程模式 Quote: 引用 Rank: 排名 ranking: 排名 +Ranking: 排名 Read record codes after accept: 题目通过后读取记录的代码 Recalculates nSubmit and nAccept in problem status.: 重新计算每道题目的 AC 量和提交量 Recommended: 推荐 diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 9ca9a8c07..26b2c36c7 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "4.13.1", + "version": "4.13.2", "bin": "bin/hydrooj.js", "main": "src/plugin-api", "module": "src/plugin-api", @@ -12,10 +12,10 @@ }, "preferUnplugged": true, "dependencies": { - "@aws-sdk/client-s3": "3.609.0", - "@aws-sdk/lib-storage": "3.609.0", - "@aws-sdk/s3-presigned-post": "3.609.0", - "@aws-sdk/s3-request-presigner": "3.609.0", + "@aws-sdk/client-s3": "3.614.0", + "@aws-sdk/lib-storage": "3.614.0", + "@aws-sdk/s3-presigned-post": "3.614.0", + "@aws-sdk/s3-request-presigner": "3.614.0", "@graphql-tools/schema": "^10.0.4", "@hydrooj/framework": "workspace:^", "@hydrooj/utils": "workspace:^", @@ -34,7 +34,7 @@ "koa-proxies": "^0.12.4", "koa-static-cache": "^5.1.4", "lodash": "^4.17.21", - "lru-cache": "^10.3.1", + "lru-cache": "^10.4.3", "mime-types": "^2.1.35", "moment-timezone": "^0.5.45", "mongodb": "^5.9.2", diff --git a/packages/hydrooj/src/service/migration.ts b/packages/hydrooj/src/service/migration.ts index 176968ab5..d2bd8384a 100644 --- a/packages/hydrooj/src/service/migration.ts +++ b/packages/hydrooj/src/service/migration.ts @@ -14,16 +14,19 @@ declare module '../context' { export default class MigrationService extends Service { private channels: Record = {}; + private called = false; constructor(ctx: Context) { super(ctx, 'migration', true); } async registerChannel(name: string, s: MigrationScript[]) { + if (this.called) logger.warn('MigrationService.registerChannel: called after doUpgrade'); this.channels[name] = s; } async doUpgrade() { + this.called = true; for (const channel in this.channels) { const name = `db.ver${channel === 'hydrooj' ? '' : `-${channel}`}`; let dbVer = system.get(name) ?? 0; diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 41c36f66f..2a374d212 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/ui-default", - "version": "4.51.4", + "version": "4.52.0", "author": "undefined ", "license": "AGPL-3.0", "main": "index.ts", @@ -30,8 +30,8 @@ "@fontsource/source-code-pro": "^5.0.18", "@fontsource/ubuntu-mono": "^5.0.20", "@hydrooj/utils": "workspace:^", - "@sentry/browser": "^8.15.0", - "@sentry/webpack-plugin": "^2.20.1", + "@sentry/browser": "^8.17.0", + "@sentry/webpack-plugin": "^2.21.1", "@simplewebauthn/browser": "7.4.0", "@svgr/webpack": "^8.1.0", "@types/gulp-if": "^0.0.34", @@ -50,7 +50,7 @@ "@types/vscode": "^1.91.0", "@types/webpack-env": "^1.18.5", "@vscode/codicons": "^0.0.36", - "ajv": "^8.16.0", + "ajv": "^8.17.1", "allotment": "^1.20.2", "autoprefixer": "^10.4.19", "browser-update": "^3.3.53", @@ -67,7 +67,7 @@ "echarts": "^5.5.1", "emojis-keywords": "2.0.0", "emojis-list": "2.1.0", - "esbuild-loader": "^4.2.0", + "esbuild-loader": "^4.2.1", "fancy-log": "^2.0.0", "flatpickr": "^4.6.13", "graphiql": "^2.4.7", @@ -133,7 +133,7 @@ "vinyl-buffer": "^1.0.1", "vscode": "npm:@codingame/monaco-vscode-api@^7.0.5", "web-streams-polyfill": "^4.0.0", - "webpack": "^5.92.1", + "webpack": "^5.93.0", "webpack-bundle-analyzer": "^4.10.2", "webpack-dev-server": "^5.0.4", "webpack-manifest-plugin": "^5.0.0", diff --git a/packages/ui-default/pages/problem_files.page.tsx b/packages/ui-default/pages/problem_files.page.tsx index 885a62a33..93682098e 100644 --- a/packages/ui-default/pages/problem_files.page.tsx +++ b/packages/ui-default/pages/problem_files.page.tsx @@ -161,7 +161,7 @@ const page = new NamedPage('problem_files', () => { const style = { fontFamily: 'var(--code-font-family)' }; - return
+ return
{!preview ? <>
@@ -229,7 +229,7 @@ const page = new NamedPage('problem_files', () => { {i18n('New filename(s)')} - + {selectedFiles.map((file, index) => {file} {newNames[index]} diff --git a/packages/vjudge/package.json b/packages/vjudge/package.json index 283f9ecf8..4f6b6efc1 100644 --- a/packages/vjudge/package.json +++ b/packages/vjudge/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/vjudge", - "version": "1.9.7", + "version": "1.9.8", "description": "Submit problems to remote oj", "main": "./src/index.ts", "repository": "https://github.com/hydro-dev/Hydro.git", diff --git a/packages/vjudge/src/providers/codeforces.ts b/packages/vjudge/src/providers/codeforces.ts index 25637a82d..c82be9138 100644 --- a/packages/vjudge/src/providers/codeforces.ts +++ b/packages/vjudge/src/providers/codeforces.ts @@ -270,12 +270,20 @@ export default class CodeforcesProvider extends BasicFetcher implements IBasicPr }); } + async readLatestSubmission(contestId = '') { + const { document } = await this.html(contestId ? `/gym/${contestId}/my` : '/problemset/status?my=on'); + this.csrf = document.querySelector('meta[name="X-Csrf-Token"]').getAttribute('content'); + const submission = document.querySelector('[data-submission-id]').getAttribute('data-submission-id'); + return submission; + } + async submitProblem(id: string, lang: string, code: string, info, next, end) { const programTypeId = lang.includes('codeforces.') ? lang.split('codeforces.')[1] : '54'; const [type, contestId, problemId] = parseProblemId(id); const endpoint = type === 'GYM' ? `/gym/${contestId}/submit` : `/problemset/submit/${contestId}/${problemId}`; + const latestSubmission = await this.readLatestSubmission(type === 'GYM' ? contestId : ''); const [csrf, ftaa, bfaa] = await this.getCsrfToken(endpoint); // TODO check submit time to ensure submission const { text: submit, redirects } = await this.post(`${endpoint}?csrf_token=${csrf}`).send({ @@ -298,16 +306,13 @@ export default class CodeforcesProvider extends BasicFetcher implements IBasicPr end({ status: STATUS.STATUS_SYSTEM_ERROR, message }); return null; } - if (redirects.length === 0 || !redirects.toString().includes('my')) { + const submission = await this.readLatestSubmission(type === 'GYM' ? contestId : ''); + if (submission === latestSubmission || redirects.length === 0 || !redirects.toString().includes('my')) { // Submit failed so the request is not redirected - end({ status: STATUS.STATUS_SYSTEM_ERROR, message: 'Submit failed' }); + // eslint-disable-next-line max-len + end({ status: STATUS.STATUS_SYSTEM_ERROR, message: 'Submit failed. Check service status or use better network to avoid rejection by server protection.' }); return null; } - const { document } = await this.html(type !== 'GYM' - ? '/problemset/status?my=on' - : `/gym/${contestId}/my`); - this.csrf = document.querySelector('meta[name="X-Csrf-Token"]').getAttribute('content'); - const submission = document.querySelector('[data-submission-id]').getAttribute('data-submission-id'); return type !== 'GYM' ? submission : `${contestId}#${submission}`; }