From f9ca2d9815eaf3537afe8c986d9123881aeafe51 Mon Sep 17 00:00:00 2001 From: CyC2018 <36260787+CyC2018@users.noreply.github.com> Date: Wed, 28 Oct 2026 02:05:32 +0800 Subject: [PATCH] Automate validated site publishing to Tencent Makers (#1475) --- .github/workflows/website.yml | 78 +++ website/.gitignore | 1 + website/README.md | 25 +- website/deploy/deploy.mjs | 113 ++++ website/deploy/deploy.test.mjs | 105 ++++ website/deploy/package-lock.json | 959 +++++++++++++++++++++++++++++++ website/deploy/package.json | 8 + website/refresh.py | 30 +- 8 files changed, 1307 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/website.yml create mode 100644 website/deploy/deploy.mjs create mode 100644 website/deploy/deploy.test.mjs create mode 100644 website/deploy/package-lock.json create mode 100644 website/deploy/package.json diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml new file mode 100644 index 00000000..d0d2b9cd --- /dev/null +++ b/.github/workflows/website.yml @@ -0,0 +1,78 @@ +name: 网站构建与发布 + +on: + push: + branches: [master] + pull_request: + paths: + - '.github/workflows/website.yml' + - 'README.md' + - 'CONTRIBUTING.md' + - 'notes/**' + - 'assets/**' + - 'docs/_media/**' + - 'website/**' + schedule: + # UTC; Beijing time 09:17 and 21:17. + - cron: '17 1,13 * * *' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: website-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + website: + runs-on: ubuntu-latest + timeout-minutes: 25 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + fetch-depth: 1 + ref: ${{ github.event_name == 'pull_request' && github.ref || 'master' }} + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: '3.12' + cache: pip + cache-dependency-path: website/requirements.txt + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: '22' + - name: 安装固定版本依赖 + run: | + python -m pip install -r website/requirements.txt + npm ci --prefix website/deploy --ignore-scripts + - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + with: + path: website/.cache/site-assets + key: site-webp-v1-${{ runner.os }}-${{ hashFiles('website/requirements.txt', 'website/builder/site_assets.py', 'notes/pics/**', 'assets/**') }} + restore-keys: site-webp-v1-${{ runner.os }}- + - name: 生成并校验网站与配图 + run: python website/refresh.py --from-checkout + - name: 检查搜索和发布保护 + run: | + python website/builder/test_site_assets.py + node website/builder/test_search.cjs + npm test --prefix website/deploy + - name: 发布网页和图片并校验正式域名 + if: github.repository == 'CyC2018/CS-Notes' && github.ref == 'refs/heads/master' && github.event_name != 'pull_request' + env: + EDGEONE_API_TOKEN: ${{ secrets.EDGEONE_API_TOKEN }} + run: node website/deploy/deploy.mjs + - name: 保留小型版本报告 + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: website-report-${{ github.run_id }}-${{ github.run_attempt }} + path: | + website/dist/build-info.json + website/dist/asset-manifest.json + website/dist-report.json + website/validation.json + website/deployment-report.json + retention-days: 7 + if-no-files-found: warn diff --git a/website/.gitignore b/website/.gitignore index 6854c1bc..ce9b4c25 100644 --- a/website/.gitignore +++ b/website/.gitignore @@ -9,6 +9,7 @@ /*-report.json /*-validation.json /validation.json +/deploy/node_modules/ /*.log /*.jpg /*.png diff --git a/website/README.md b/website/README.md index 7c6bca64..9884800c 100644 --- a/website/README.md +++ b/website/README.md @@ -26,11 +26,11 @@ refresh.py 使用独立浅层 Git 缓存读取最新 master,并生成源码快 发布时应把整个 website/dist(或内容位于 ZIP 根目录的压缩包)上传到静态托管,网页和图片一起上传。访客从网站域名读取图片,无需另建 COS/TOS 图床或替换一套对象存储链接。 -原文的头像、徽章、许可证图标和 GitHub 附件等 18 个外链继续沿用,依赖原服务可用性。构建生成的是静态文件;目前不包含每日定时配置、腾讯云上传代码或部署凭据。 +原文的头像、徽章、许可证图标和 GitHub 附件等 18 个外链继续沿用,依赖原服务可用性。构建生成的是静态文件,自动上传步骤与凭据配置见下文。 ## 验证 -Node.js 22+ 用于搜索与更新回归检查。生成后,在仓库根目录运行: +Node.js 22+ 用于搜索检查和自动发布。生成后,在仓库根目录运行: ```bash python3 website/builder/check.py @@ -42,3 +42,24 @@ python3 website/builder/verify_updates.py verify_updates.py 在临时副本中修改文章、替换配图、新增文章与图片,检查搜索索引更新,并模拟缺图、链接校验失败。测试结束自动清理副本,原始快照不改动。 页面模板保留手机分类导航、折叠目录、搜索和旧文章入口兼容;固定版本的浏览器依赖及许可证保存在 templates/vendor 中。 + +## 自动更新与发布 + +`.github/workflows/website.yml` 在 master 提交后、北京时间每天 09:17 和 21:17,以及手动 Run workflow 时运行。PR 仅验证合并后的源码,不读取部署凭据。CI 使用 `refresh.py --from-checkout` 从已检出的 Git 提交生成,源码中未提交的本机文件不会进入产物。 + +通过图片、引用和搜索检查后,使用腾讯云官方 Makers SDK 发布整个 website/dist(包含网页和图片)到现有 cs-notes 项目,固定项目 ID 和中国站,上传前核对项目名称。清单与正式站一致时检查正式文件并跳过重复发布;有变化时等待腾讯云部署 Success,再核对正式域名的版本清单及 11 个关键页面/资源的实际 SHA-256,覆盖原图和优化配图。 + +本机检查发布保护(不需要令牌、不会上传): + +```bash +npm ci --prefix website/deploy --ignore-scripts +npm test --prefix website/deploy +``` + +发布令牌只在仓库 Actions Secret `EDGEONE_API_TOKEN` 中配置,并仅传入可信 master 的发布步骤,不写入源码、网页、日志或报告。构建/校验失败时不会上传;发布后的域名检查失败会让任务失败,需查看部署记录后处理。删除令牌会停止后续发布,已经上线的网站不会被删除。 + +工作流只保留小型版本报告 7 天,不每天积累完整网站归档。图片优化缓存与产物不进入 Git。 + +GitHub 调度可能延迟;公开仓库连续 60 天没有活动时会自动停用定时工作流,届时需在 Actions 页面重新启用。可在 Actions 中禁用该工作流暂停自动更新。 + +参考:[Makers SDK](https://pages.edgeone.ai/document/sdk-overview)、[GitHub 定时规则](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#schedule)。 diff --git a/website/deploy/deploy.mjs b/website/deploy/deploy.mjs new file mode 100644 index 00000000..a0365dc8 --- /dev/null +++ b/website/deploy/deploy.mjs @@ -0,0 +1,113 @@ +// Publish only validated dist/ to the existing Tencent Cloud project. +import { createHash } from 'node:crypto'; +import { readFile, mkdir, writeFile, appendFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +const WEBSITE = fileURLToPath(new URL('../', import.meta.url)); + +export const PROJECT = { projectId: 'makers-s7dxi8u5vidy', name: 'cs-notes' }; +export const ORIGIN = 'https://cyc2018.xyz'; +const sha256 = bytes => createHash('sha256').update(bytes).digest('hex'); + +async function download(origin, path, revision, fetcher) { + const url = new URL(path.split('/').map(encodeURIComponent).join('/'), origin + '/'); + url.searchParams.set('release', revision); + const response = await fetcher(url, { signal: AbortSignal.timeout(30_000), redirect: 'error' }); + if (!response.ok) throw new Error(`HTTP ${response.status}: ${path}`); + return Buffer.from(await response.arrayBuffer()); +} + +export async function verifyPublished(manifestBytes, { fetcher = fetch, origin = ORIGIN } = {}) { + const manifest = JSON.parse(manifestBytes); + const revision = sha256(manifestBytes); + const remote = await download(origin, 'asset-manifest.json', revision, fetcher); + if (sha256(remote) !== revision) throw new Error('正式域名的版本清单尚未与构建一致。'); + const paths = ['index.html', 'build-info.json', 'README.md', 'site.css', 'site.js', + 'search.js', 'search-paths.js', manifest.searchIndex, 'notes/MySQL.md']; + const records = new Map(manifest.files.map(file => [file.path, file])); + for (const prefix of ['notes/pics/', 'optimized/']) { + const image = manifest.files.find(file => file.path.startsWith(prefix)); + if (!image) throw new Error(`构建缺少配图:${prefix}`); + paths.push(image.path); + } + // Check actual bytes as well as the manifest; an SPA fallback can also return HTTP 200. + await Promise.all(paths.map(async path => { + const record = records.get(path); + if (!record) throw new Error(`构建缺少关键文件:${path}`); + const bytes = await download(origin, path, revision, fetcher); + if (bytes.length !== record.bytes || sha256(bytes) !== record.sha256) { + throw new Error(`正式域名文件校验不一致:${path}`); + } + })); + return paths.length + 1; +} + +export async function deploySite({ directory, getClient, fetcher = fetch, + sleep = ms => new Promise(done => setTimeout(done, ms)), attempts = 12 }) { + const manifestBytes = await readFile(resolve(directory, 'asset-manifest.json')); + const manifest = JSON.parse(manifestBytes); + let unchanged = false; + try { + const remote = await download(ORIGIN, 'asset-manifest.json', sha256(manifestBytes), fetcher); + unchanged = sha256(remote) === sha256(manifestBytes); + } catch { /* A missing manifest should trigger a new deployment, never a false skip. */ } + let deployment; + if (!unchanged) { + const makers = await getClient(); + const project = await makers.projects.get({ projectId: PROJECT.projectId }); + if (project.projectId !== PROJECT.projectId || project.name !== PROJECT.name) { + throw new Error('腾讯云项目标识与预期不一致,已停止发布。'); + } + deployment = await makers.deployments.deploy({ + projectId: PROJECT.projectId, artifact: { directory: resolve(directory) }, + env: 'Production', wait: true, timeout: 600, pollInterval: 5, + onStatusChange: ({ deployment }) => console.log(`腾讯云部署状态:${deployment.status}`), + }); + if (deployment.status !== 'Success') { + throw new Error(`腾讯云部署未成功:${deployment.status}`); + } + } + let verifiedFiles; + for (let attempt = 0; attempt < attempts; attempt++) { + try { + verifiedFiles = await verifyPublished(manifestBytes, { fetcher }); + break; + } catch (error) { + if (attempt === attempts - 1) throw error; + await sleep(20_000); + } + } + return { projectId: PROJECT.projectId, origin: ORIGIN, + sourceCommit: manifest.sourceCommit, generatorHash: manifest.generatorHash, + manifestSha256: sha256(manifestBytes), + result: unchanged ? 'unchanged' : 'deployed', + ...(deployment ? { deploymentId: deployment.deploymentId } : {}), verifiedFiles }; +} + +if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) { + try { + const report = await deploySite({ directory: resolve(WEBSITE, 'dist'), getClient: async () => { + const token = process.env.EDGEONE_API_TOKEN; + if (!token) throw new Error('请在仓库 Actions Secrets 中配置 EDGEONE_API_TOKEN。'); + const { Makers } = await import('@edgeone/makers-sdk'); + // The account is on Tencent Cloud China, regardless of the runner's IP address. + return new Makers({ token, region: 'china' }); + } }); + await mkdir(WEBSITE, { recursive: true }); + await writeFile(resolve(WEBSITE, 'deployment-report.json'), JSON.stringify(report, null, 2) + '\n'); + console.log(JSON.stringify(report, null, 2)); + if (process.env.GITHUB_STEP_SUMMARY) { + await appendFile(process.env.GITHUB_STEP_SUMMARY, + `网站:${ORIGIN}\n\n结果:${report.result === 'unchanged' ? '版本一致,无需重复发布' : '已发布'}\n\n` + + `版本:\`${report.sourceCommit}\`;正式域名 ${report.verifiedFiles} 个关键文件校验通过。\n`); + } + } catch (error) { + // Never serialize SDK requests, headers, upload credentials or token-bearing preview URLs. + if (error?.name?.endsWith('Error') && error?.code) { + console.error(`腾讯云接口失败:${error.name} (${error.code});请检查令牌有效期与项目权限。`); + } else { + console.error(error instanceof Error ? error.message : '网站发布失败。'); + } + process.exitCode = 1; + } +} diff --git a/website/deploy/deploy.test.mjs b/website/deploy/deploy.test.mjs new file mode 100644 index 00000000..1e8bd1b7 --- /dev/null +++ b/website/deploy/deploy.test.mjs @@ -0,0 +1,105 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { createHash } from 'node:crypto'; +import { mkdtemp, writeFile, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join, resolve } from 'node:path'; +import { deploySite, PROJECT } from './deploy.mjs'; + +async function fixture(t) { + const directory = await mkdtemp(join(tmpdir(), 'cs-notes-deploy-')); + t.after(() => rm(directory, { recursive: true, force: true })); + const files = new Map(['index.html', 'build-info.json', 'README.md', 'site.css', 'site.js', + 'search.js', 'search-paths.js', 'search-index-example.json', 'notes/MySQL.md', + 'notes/pics/example.png', 'optimized/example.webp'] + .map(path => [path, Buffer.from(`validated contents: ${path}`)])); + const manifest = Buffer.from(JSON.stringify({ sourceCommit: 'source', generatorHash: 'generator', + searchIndex: 'search-index-example.json', files: [...files].map(([path, data]) => ({ + path, bytes: data.length, sha256: createHash('sha256').update(data).digest('hex'), + })) })); + files.set('asset-manifest.json', manifest); + await writeFile(join(directory, 'asset-manifest.json'), manifest); + return { directory, files, manifest }; +} + +test('每日检查:清单一致时不请求凭据、不重复发布,同时核对线上实际文件', async t => { + const { directory, files } = await fixture(t); + const requests = []; + const report = await deploySite({ directory, attempts: 1, + getClient: () => assert.fail('无变化时不应读取发布凭据'), + fetcher: async url => { + const path = decodeURIComponent(url.pathname.slice(1)); + requests.push(path); + return new Response(files.get(path)); + }, + }); + assert.equal(report.result, 'unchanged'); + assert.equal(report.verifiedFiles, 12); + assert.ok(requests.includes('notes/MySQL.md')); + assert.ok(requests.includes('notes/pics/example.png')); + assert.ok(requests.includes('optimized/example.webp')); +}); + +test('新版本只向固定项目发布 dist,等候 Success 后重试正式域名校验', async t => { + const { directory, files } = await fixture(t); + let published = false; + let firstVerification = true; + let retries = 0; + const report = await deploySite({ directory, attempts: 2, sleep: async () => { retries++; }, + fetcher: async url => { + if (!published) return new Response('{}'); + if (firstVerification) { firstVerification = false; return new Response('{}'); } + return new Response(files.get(decodeURIComponent(url.pathname.slice(1)))); + }, + getClient: async () => ({ + projects: { get: async input => { assert.deepEqual(input, { projectId: PROJECT.projectId }); return PROJECT; } }, + deployments: { deploy: async input => { + assert.equal(input.projectId, PROJECT.projectId); + assert.equal(input.env, 'Production'); + assert.equal(input.wait, true); + assert.deepEqual(input.artifact, { directory: resolve(directory) }); + published = true; + return { status: 'Success', deploymentId: 'deployment-test' }; + } }, + }), + }); + assert.equal(report.result, 'deployed'); + assert.equal(report.deploymentId, 'deployment-test'); + assert.equal(retries, 1); +}); + +test('项目身份不符时拒绝发布', async t => { + const { directory } = await fixture(t); + await assert.rejects(deploySite({ directory, fetcher: async () => new Response('{}'), + getClient: async () => ({ + projects: { get: async () => ({ ...PROJECT, name: 'other-site' }) }, + deployments: { deploy: () => assert.fail('不能向错误项目上传') }, + }), + }), /项目标识/); +}); + +test('腾讯云最终状态失败必须让任务失败', async t => { + const { directory } = await fixture(t); + await assert.rejects(deploySite({ directory, fetcher: async () => new Response('{}'), + getClient: async () => ({ projects: { get: async () => PROJECT }, + deployments: { deploy: async () => ({ status: 'Failed' }) } }), + }), /未成功:Failed/); +}); + +test('HTTP 200 的单页回退不能冒充真实资源', async t => { + const { directory, files } = await fixture(t); + await assert.rejects(deploySite({ directory, attempts: 1, + getClient: () => assert.fail('清单一致时不发布'), + fetcher: async url => new Response(url.pathname === '/asset-manifest.json' + ? files.get('asset-manifest.json') : 'SPA fallback'), + }), /文件校验不一致/); +}); + +test('网页正常但优化图片缺失时不能宣告发布成功', async t => { + const { directory, files } = await fixture(t); + await assert.rejects(deploySite({ directory, attempts: 1, + getClient: () => assert.fail('清单一致时不发布'), + fetcher: async url => new Response(url.pathname.endsWith('.webp') + ? 'SPA fallback' : files.get(decodeURIComponent(url.pathname.slice(1)))), + }), /文件校验不一致:optimized\/example.webp/); +}); diff --git a/website/deploy/package-lock.json b/website/deploy/package-lock.json new file mode 100644 index 00000000..3f31f4b5 --- /dev/null +++ b/website/deploy/package-lock.json @@ -0,0 +1,959 @@ +{ + "name": "cs-notes-deployment", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cs-notes-deployment", + "dependencies": { + "@edgeone/makers-sdk": "0.1.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@edgeone/makers-sdk": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@edgeone/makers-sdk/-/makers-sdk-0.1.0.tgz", + "integrity": "sha512-37gQjFzVcFCm82FTGLt2oubKjPEZCqrM3jY8pc4zdT794g0sr4KItrLCRqwkyFEUV3fgrX9JjXMnXBC+Gqkwow==", + "license": "MIT", + "dependencies": { + "cos-nodejs-sdk-v5": "^3.0.0", + "jszip": "^3.10.1", + "minimatch": "^10.2.6", + "yauzl": "^3.4.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "license": "Apache-2.0" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cos-fast-xml-parser": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cos-fast-xml-parser/-/cos-fast-xml-parser-1.0.0.tgz", + "integrity": "sha512-kOPJb1cuj+gc5E8jN5ekZn4rgQHaxYTLcQT+jmbHtlTNcNjv7wnOBEYH2uRA0pG3h8giot4z9h4WFLDuXATZwg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + }, + "engines": { + "node": ">= 9" + } + }, + "node_modules/cos-nodejs-sdk-v5": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cos-nodejs-sdk-v5/-/cos-nodejs-sdk-v5-3.0.0.tgz", + "integrity": "sha512-xUqiDdUxfEjfaWoyBA3qsSnlQVHPz13DRFErL+NliadBVUeeZhAPHGVa2inGpUvPTOs52ALIKkMbzL+inSqvXg==", + "license": "ISC", + "dependencies": { + "cos-fast-xml-parser": "^1.0.0", + "cos-request": "^1.3.0", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 9" + } + }, + "node_modules/cos-request": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/cos-request/-/cos-request-1.3.3.tgz", + "integrity": "sha512-zD7fKMAIMfJNHssx8VZE5mUQ67hs3QDi7S2BX7JuD8MzT1XYqEOAN42PJ0BtRhgutnn+8HiUF+Fup+j5zORasQ==", + "license": "Apache-2.0", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.5.6", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "^6.15.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~4.1.4", + "tunnel-agent": "^0.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "license": "MIT", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.6.tgz", + "integrity": "sha512-Ogz/E85h9tlfJzpI6TuFpGcHZFhLrb9Gw8wq9v40CxSCPnv7ahKr6Xgtkn0KYCDQJ8DNn5VoMO8EXr9V5PadyA==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "license": "MIT" + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "license": "MIT" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "license": "MIT", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/jszip": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.2.tgz", + "integrity": "sha512-3l+rb15IOWtUhU0H5MFqES/T6Kh7abYwjosBey/vD6hDt8zoEffkSC5Ws5SGtgVw3gBx2NEbhTeSW1+kWkpyTQ==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "license": "MIT" + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT" + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.17.0.tgz", + "integrity": "sha512-5LK5BZ2XDHMvcUJaf0CQ5HL3aVgKbpklvByC7qLSxYkfE8COEEbJg5xCHWCTqzqi0Gs4Jn5Mv5v3R38W6edscA==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "license": "MIT", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense" + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/verror/node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "license": "MIT" + }, + "node_modules/yauzl": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.4.0.tgz", + "integrity": "sha512-jIH9yLR9wqr0wOS0TpBvo/g/2UgZH5qePVbjgRliiF0BYvOZyaBknKsF+x9Iht0O6sqgnB93rCICdOZFecJuDw==", + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + }, + "engines": { + "node": ">=12" + } + } + } +} diff --git a/website/deploy/package.json b/website/deploy/package.json new file mode 100644 index 00000000..4c9a730e --- /dev/null +++ b/website/deploy/package.json @@ -0,0 +1,8 @@ +{ + "name": "cs-notes-deployment", + "private": true, + "type": "module", + "engines": { "node": ">=22" }, + "scripts": { "test": "node --test deploy.test.mjs" }, + "dependencies": { "@edgeone/makers-sdk": "0.1.0" } +} diff --git a/website/refresh.py b/website/refresh.py index f0601ca8..4efc12aa 100644 --- a/website/refresh.py +++ b/website/refresh.py @@ -1,5 +1,6 @@ """Read the latest public master and build locally; no write operation to GitHub or cloud.""" import json +import argparse import subprocess import sys import tarfile @@ -15,18 +16,27 @@ def git(*args): return subprocess.check_output(['git', '--git-dir=' + str(CACHE), *args], text=True).strip() -if not CACHE.exists(): - subprocess.run(['git', 'init', '--bare', str(CACHE)], check=True, stdout=subprocess.DEVNULL) - git('remote', 'add', 'origin', REPOSITORY) - git('config', 'credential.helper', '') - git('remote', 'set-url', '--push', 'origin', 'disabled://local-validation-read-only') -subprocess.run(['git', '--git-dir=' + str(CACHE), 'fetch', '--depth=1', 'origin', - '+refs/heads/master:refs/heads/master'], check=True) -commit = git('rev-parse', 'refs/heads/master') +parser = argparse.ArgumentParser(description='获取源文件并生成、校验静态站。') +parser.add_argument('--from-checkout', action='store_true', help='使用当前已提交的检出版本,供 CI 验证 PR 或发布已检出的主分支。') +args = parser.parse_args() +if args.from_checkout: + repository = BASE.parent + commit = subprocess.check_output(['git', '-C', str(repository), 'rev-parse', 'HEAD'], text=True).strip() + archive_command = ['git', '-C', str(repository), 'archive', commit] +else: + if not CACHE.exists(): + subprocess.run(['git', 'init', '--bare', str(CACHE)], check=True, stdout=subprocess.DEVNULL) + git('remote', 'add', 'origin', REPOSITORY) + git('config', 'credential.helper', '') + git('remote', 'set-url', '--push', 'origin', 'disabled://local-validation-read-only') + subprocess.run(['git', '--git-dir=' + str(CACHE), 'fetch', '--depth=1', 'origin', + '+refs/heads/master:refs/heads/master'], check=True) + commit = git('rev-parse', 'refs/heads/master') + archive_command = ['git', '--git-dir=' + str(CACHE), 'archive', commit] with tempfile.TemporaryDirectory(prefix='.snapshot-', dir=BASE) as temp: snapshot = Path(temp) / 'source' snapshot.mkdir() - process = subprocess.Popen(['git', '--git-dir=' + str(CACHE), 'archive', commit], stdout=subprocess.PIPE) + process = subprocess.Popen(archive_command, stdout=subprocess.PIPE) try: with tarfile.open(fileobj=process.stdout, mode='r|') as archive: archive.extractall(snapshot, filter='data') @@ -47,6 +57,6 @@ with tempfile.TemporaryDirectory(prefix='.snapshot-', dir=BASE) as temp: (BASE / 'source-version.json').write_text(json.dumps({ 'repository': REPOSITORY, 'ref': 'master', 'commit': commit, }, indent=2) + '\n') -print('已读取 GitHub 最新主分支:' + commit, flush=True) +print(('已读取当前检出的提交:' if args.from_checkout else '已读取 GitHub 最新主分支:') + commit, flush=True) subprocess.run([sys.executable, str(BASE / 'builder/build.py')], check=True) subprocess.run([sys.executable, str(BASE / 'builder/check.py')], check=True)