@covro/covro 0.2.1-dev.4 → 0.2.1-dev.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/README.zh-CN.md +3 -1
- package/dist/server/http.js +16 -0
- package/dist/server/http.js.map +2 -2
- package/dist/server/project-clones.js +296 -0
- package/dist/server/project-clones.js.map +7 -0
- package/dist/server/service.js +20 -0
- package/dist/server/service.js.map +2 -2
- package/dist/server/workspaces.js +6 -2
- package/dist/server/workspaces.js.map +2 -2
- package/dist/shared/validation.js +5 -0
- package/dist/shared/validation.js.map +2 -2
- package/dist/web/assets/{abnfDiagram-WEYOIA3C-DTTcFQGL.js → abnfDiagram-WEYOIA3C-CwLX554E.js} +1 -1
- package/dist/web/assets/{arc-K2pKIwfu.js → arc-DAaDyyqc.js} +1 -1
- package/dist/web/assets/{architecture-WOLXFQ4H-DrxN_1CG.js → architecture-WOLXFQ4H-y6MnEqN0.js} +1 -1
- package/dist/web/assets/{architectureDiagram-ZHVYKT53-CyyCrVAb.js → architectureDiagram-ZHVYKT53-_B_4k-Pp.js} +1 -1
- package/dist/web/assets/{blockDiagram-RJAE7QA3-C8vl8QXS.js → blockDiagram-RJAE7QA3-C9jZ7FHy.js} +1 -1
- package/dist/web/assets/{c4Diagram-XDPPPE54-BtnIITFN.js → c4Diagram-XDPPPE54-DQAtMvqN.js} +1 -1
- package/dist/web/assets/channel-ByzMCsOB.js +1 -0
- package/dist/web/assets/{chunk-2E7WHVMD-BssauSch.js → chunk-2E7WHVMD-Bu427yab.js} +1 -1
- package/dist/web/assets/{chunk-4EMNKR2T-BZOo1RWg.js → chunk-4EMNKR2T-BrGrBG7B.js} +1 -1
- package/dist/web/assets/{chunk-55XCU76K-D8ZWazMQ.js → chunk-55XCU76K-l4f0OZWA.js} +1 -1
- package/dist/web/assets/{chunk-7S6XSTFO-C8WqPzUl.js → chunk-7S6XSTFO-D7ixF313.js} +1 -1
- package/dist/web/assets/{chunk-AQDTSKZB-Coo6Axpg.js → chunk-AQDTSKZB-Cesd-BF2.js} +1 -1
- package/dist/web/assets/{chunk-DSNLSHJZ-5TwmEPWR.js → chunk-DSNLSHJZ-B7dim5uT.js} +1 -1
- package/dist/web/assets/{chunk-HAGFOTED-Dgtj-Fc_.js → chunk-HAGFOTED-B-f2I7TA.js} +1 -1
- package/dist/web/assets/{chunk-HNE4Q36P-COoS4UPL.js → chunk-HNE4Q36P-CoIvaeX6.js} +1 -1
- package/dist/web/assets/{chunk-NQOKRVIF-tPhNkBBj.js → chunk-NQOKRVIF-BybRxR7n.js} +1 -1
- package/dist/web/assets/{chunk-UHW7PFBP-BLk_BkCl.js → chunk-UHW7PFBP-CV5WG4tq.js} +1 -1
- package/dist/web/assets/{chunk-VQ5W62QI-HBeSVTU0.js → chunk-VQ5W62QI-BO4AjXi1.js} +1 -1
- package/dist/web/assets/{chunk-WQQL4CDK-LmZUm8If.js → chunk-WQQL4CDK-CADNDGXO.js} +1 -1
- package/dist/web/assets/{chunk-XXDRQBXY-49rKU8Nk.js → chunk-XXDRQBXY-C6QZT7KW.js} +1 -1
- package/dist/web/assets/{chunk-YZO67HFG-B_TgpaEG.js → chunk-YZO67HFG-Cm6eGReX.js} +1 -1
- package/dist/web/assets/{chunk-ZAUX2LTX-f7-DSOrY.js → chunk-ZAUX2LTX-CUBoGbKg.js} +1 -1
- package/dist/web/assets/{chunk-ZCHMQSOR-QhJOh7x0.js → chunk-ZCHMQSOR-B0ImOOtq.js} +1 -1
- package/dist/web/assets/{classDiagram-v2-4MYWR3UA-CwF2HuoZ.js → classDiagram-v2-4MYWR3UA-D5NWZ9vm.js} +1 -1
- package/dist/web/assets/{cose-bilkent-JH36ORCC-CLvKOlx5.js → cose-bilkent-JH36ORCC-BzckEyjA.js} +1 -1
- package/dist/web/assets/{cynefin-EF2NZ3EQ-BNRVavZN.js → cynefin-EF2NZ3EQ-Da_J6-Jb.js} +1 -1
- package/dist/web/assets/{cynefinDiagram-WF24UDRP-DEd83kgI.js → cynefinDiagram-WF24UDRP-b4g_B1A-.js} +1 -1
- package/dist/web/assets/{dagre-NK4GIZVX-C340UEI3.js → dagre-NK4GIZVX-89H2mfs9.js} +1 -1
- package/dist/web/assets/{diagram-CBK3MWTC-CuN7ncUm.js → diagram-CBK3MWTC-CW4f_P0l.js} +1 -1
- package/dist/web/assets/{diagram-I5XIIV2D-D9mGAfA7.js → diagram-I5XIIV2D-Cv5Qndgc.js} +1 -1
- package/dist/web/assets/{diagram-ODIL2UCW-CXtBv4bI.js → diagram-ODIL2UCW-LrXxeoCt.js} +1 -1
- package/dist/web/assets/{diagram-PFBEJUXE-B8qP5nGT.js → diagram-PFBEJUXE-uJV2PooB.js} +1 -1
- package/dist/web/assets/{diagram-RJA3I4VV-BVrzsw2b.js → diagram-RJA3I4VV-DlY0PGRs.js} +1 -1
- package/dist/web/assets/{diagram-TYUNFSNR-Djfine3D.js → diagram-TYUNFSNR-B1KoXBEE.js} +1 -1
- package/dist/web/assets/{dist-D8kDnYqD.js → dist-B4eX4BCP.js} +1 -1
- package/dist/web/assets/{ebnfDiagram-V4P3TTPT-C69Oiekp.js → ebnfDiagram-V4P3TTPT-xCvQVbdj.js} +1 -1
- package/dist/web/assets/{elk-QONTBCTQ-Bd3BP1h1.js → elk-QONTBCTQ-Cl8Hu-Uy.js} +1 -1
- package/dist/web/assets/{erDiagram-KYQIGQAA-Dxcl5jVl.js → erDiagram-KYQIGQAA-Xp-AvSwE.js} +1 -1
- package/dist/web/assets/{eventmodeling-K75KTNOO-Do8p535i.js → eventmodeling-K75KTNOO-C0truZGU.js} +1 -1
- package/dist/web/assets/flowDiagram-IUGLLWBU-T67jQ-T_.js +1 -0
- package/dist/web/assets/{ganttDiagram-R7GPNTJY-MBGUZbVy.js → ganttDiagram-R7GPNTJY-rPaYp-D7.js} +1 -1
- package/dist/web/assets/{gitGraph-VSP46ZUC-CHxRIbkg.js → gitGraph-VSP46ZUC-Bgfpho41.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-PDYL7XHY-DbW0GeW4.js → gitGraphDiagram-PDYL7XHY-kDcmuv6k.js} +1 -1
- package/dist/web/assets/{index-Cz3AOJOw.js → index-3DUP8YCe.js} +11 -11
- package/dist/web/assets/{index-DaMLk_sl.css → index-B7tnf28H.css} +1 -1
- package/dist/web/assets/{info-OHQRW6UA-K8BF1xZb.js → info-OHQRW6UA-CAWg5k9R.js} +1 -1
- package/dist/web/assets/{infoDiagram-MVDRKAX5-QaWV5-SY.js → infoDiagram-MVDRKAX5-Cd08ebhn.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-HPEHV2GE-DpVz_sC0.js → ishikawaDiagram-HPEHV2GE-tAXBLt9k.js} +1 -1
- package/dist/web/assets/{journeyDiagram-BOSHW7O7-S9MzozV7.js → journeyDiagram-BOSHW7O7-ZgnWqXkg.js} +1 -1
- package/dist/web/assets/{kanban-definition-FNSA4HC5-BiU8Ibw-.js → kanban-definition-FNSA4HC5-BUjtjd-h.js} +1 -1
- package/dist/web/assets/{line-B1KaX5ms.js → line-DG48HRVK.js} +1 -1
- package/dist/web/assets/{linear-Cg1dwAtU.js → linear-CqnXVCVw.js} +1 -1
- package/dist/web/assets/{mermaid-parser.core-hzYHYxfI.js → mermaid-parser.core-gIvgB9WK.js} +3 -3
- package/dist/web/assets/{mermaid.core-N8xYYvXy.js → mermaid.core-C_R7Y1XB.js} +4 -4
- package/dist/web/assets/{mindmap-definition-6OY57UG3-BmIQCrEd.js → mindmap-definition-6OY57UG3-CwUEEl_y.js} +1 -1
- package/dist/web/assets/{packet-JDAUHWVQ-DhOTX9De.js → packet-JDAUHWVQ-BrXmpRQ3.js} +1 -1
- package/dist/web/assets/{pegDiagram-ECIUJNBX-CVfE6yUu.js → pegDiagram-ECIUJNBX-lLApO85Q.js} +1 -1
- package/dist/web/assets/{pie-XZMESJXO-BayfKVL8.js → pie-XZMESJXO-C_8kR5WX.js} +1 -1
- package/dist/web/assets/{pieDiagram-GNMPY74T-ClbMRve3.js → pieDiagram-GNMPY74T-CawGmJZn.js} +1 -1
- package/dist/web/assets/{quadrantDiagram-D57K4MC3-DFp-P_EZ.js → quadrantDiagram-D57K4MC3-Dg6Yq3s_.js} +1 -1
- package/dist/web/assets/{radar-ABXABTNO-Bt1EAsxC.js → radar-ABXABTNO-Bs1xY09D.js} +1 -1
- package/dist/web/assets/{railroad-I3PHUGI6-CSos2Rm9.js → railroad-I3PHUGI6-Bw_qS6e3.js} +1 -1
- package/dist/web/assets/{railroad-abnf-LNEFI6M7-CebY-3Kh.js → railroad-abnf-LNEFI6M7-CQr33CuI.js} +1 -1
- package/dist/web/assets/{railroad-ebnf-DWYD2UWJ-DpA6-TBs.js → railroad-ebnf-DWYD2UWJ-BY9nk5pg.js} +1 -1
- package/dist/web/assets/{railroad-peg-JR7BVNR7-Bg3QPvi5.js → railroad-peg-JR7BVNR7-CcFsAEI4.js} +1 -1
- package/dist/web/assets/{railroadDiagram-Y72NRBL7-BM4RAgqS.js → railroadDiagram-Y72NRBL7-rkZbchmc.js} +1 -1
- package/dist/web/assets/{requirementDiagram-EQYSW5RA-CqP9bCS_.js → requirementDiagram-EQYSW5RA-CSOcijj5.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-SY24JHO5-BhS8f6-k.js → sankeyDiagram-SY24JHO5-E0uo0rRp.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-BLEHNHQU-p3C1DgeC.js → sequenceDiagram-BLEHNHQU-BKWAAcRu.js} +1 -1
- package/dist/web/assets/{src-9XJAFibc.js → src-F6L8HcEC.js} +1 -1
- package/dist/web/assets/{stateDiagram-v2-A6IESKWZ-DVVy8chY.js → stateDiagram-v2-A6IESKWZ-BBk2tpPh.js} +1 -1
- package/dist/web/assets/{swimlanes-B3ENRKFQ-CtMPO6Gw.js → swimlanes-B3ENRKFQ-CAUrhGwF.js} +1 -1
- package/dist/web/assets/swimlanesDiagram-AYQRV4AK-BCnJymLL.js +8 -0
- package/dist/web/assets/{timeline-definition-JXYKGLGB-DuTqpjLQ.js → timeline-definition-JXYKGLGB-D0FWj9fa.js} +1 -1
- package/dist/web/assets/{treeView-D4JQ5SDB-qpOZEphu.js → treeView-D4JQ5SDB-CkJOmf93.js} +1 -1
- package/dist/web/assets/{treemap-SAJKECNS-Dm6e3TpG.js → treemap-SAJKECNS-BSFsardB.js} +1 -1
- package/dist/web/assets/{usecaseDiagram-WPOLBNNX-DoCSdm5y.js → usecaseDiagram-WPOLBNNX-B2U-2o4g.js} +1 -1
- package/dist/web/assets/{vennDiagram-Q3Y3IDCW-qzd4yvh5.js → vennDiagram-Q3Y3IDCW-BijTH0fT.js} +1 -1
- package/dist/web/assets/{wardley-7MLQ67FV-CkcgIQgQ.js → wardley-7MLQ67FV-DDOa5RzL.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-IVAIDKWA-CE6OR9hA.js → wardleyDiagram-IVAIDKWA-mbb5GvW5.js} +1 -1
- package/dist/web/assets/{xychartDiagram-4URNHDU4-OrFXHVc6.js → xychartDiagram-4URNHDU4-DHd60D_Y.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +3 -3
- package/dist/web/assets/channel-CDExoPVe.js +0 -1
- package/dist/web/assets/flowDiagram-IUGLLWBU-X85uzgrG.js +0 -1
- package/dist/web/assets/swimlanesDiagram-AYQRV4AK-BRXJWgnO.js +0 -8
package/README.md
CHANGED
|
@@ -105,7 +105,9 @@ pnpm build
|
|
|
105
105
|
pnpm start -- --config ./config.local.json
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
Open **<http://127.0.0.1:8787>** and click **+** beside **未关联项目** (Unassigned) in the sidebar to send a task without choosing a project.
|
|
108
|
+
Open **<http://127.0.0.1:8787>** and click **+** beside **未关联项目** (Unassigned) in the sidebar to send a task without choosing a project.
|
|
109
|
+
|
|
110
|
+
To work in a project directory, click **添加项目** (Add project), then choose an existing host directory or **克隆 Git 仓库** (Clone Git repository). For cloning, enter an HTTPS/SSH repository address, an absolute destination on the host, and an optional project name. Git must be available to the service and uses the host’s existing credentials. Follow progress or cancel in the dialog; once imported, open the project and send your first task. [Clone requirements and recovery](docs/project-clone.md).
|
|
109
111
|
|
|
110
112
|
Unassigned conversations use separate directories under `<dataDir>/workspaces/`, allocated on the first send. Their files are retained until you explicitly remove them; archiving does not delete files. See [conversation workspaces](docs/workspaces.md) for directory, recovery, and backup details.
|
|
111
113
|
|
package/README.zh-CN.md
CHANGED
|
@@ -105,7 +105,9 @@ pnpm build
|
|
|
105
105
|
pnpm start -- --config ./config.local.json
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
打开 **<http://127.0.0.1:8787>**,点击侧栏 **未关联项目** 标题右侧的 **+**
|
|
108
|
+
打开 **<http://127.0.0.1:8787>**,点击侧栏 **未关联项目** 标题右侧的 **+** 即可直接发送任务,无需选择项目。
|
|
109
|
+
|
|
110
|
+
如需操作项目目录,点击 **添加项目**,选择已有目录,或选择 **克隆 Git 仓库**,填写 HTTPS/SSH 仓库地址、主机上的绝对目标目录和可选项目名称。服务需要能找到 Git,并使用主机已有的认证。弹窗可查看进度、取消克隆;导入完成后打开项目并发送第一条任务。[克隆要求与失败恢复](docs/project-clone.md)。
|
|
109
111
|
|
|
110
112
|
未关联项目的会话在首次发送时分配 `<dataDir>/workspaces/` 下的独立目录。文件持续保留,归档不会删除文件;目录、恢复与备份规则见[会话工作目录](docs/workspaces.md)。
|
|
111
113
|
|
package/dist/server/http.js
CHANGED
|
@@ -156,6 +156,22 @@ function createApp({
|
|
|
156
156
|
const op = await service.createThread(null, await parse(c, schemas.createThreadSchema));
|
|
157
157
|
return c.json(op, status(op));
|
|
158
158
|
});
|
|
159
|
+
app.post(
|
|
160
|
+
"/api/project-clones",
|
|
161
|
+
async (c) => c.json(service.cloneProject(await parse(c, schemas.cloneProjectSchema)), 202)
|
|
162
|
+
);
|
|
163
|
+
app.get(
|
|
164
|
+
"/api/project-clones/:cloneId",
|
|
165
|
+
(c) => c.json(service.projectClones.get(id(c.req.param("cloneId"))))
|
|
166
|
+
);
|
|
167
|
+
app.post("/api/project-clones/:cloneId/cancel", async (c) => {
|
|
168
|
+
await parse(c, schemas.removeProjectSchema);
|
|
169
|
+
return c.json(service.projectClones.cancel(id(c.req.param("cloneId"))));
|
|
170
|
+
});
|
|
171
|
+
app.post("/api/project-clones/:cloneId/register", async (c) => {
|
|
172
|
+
await parse(c, schemas.removeProjectSchema);
|
|
173
|
+
return c.json(service.projectClones.retryRegistration(id(c.req.param("cloneId"))));
|
|
174
|
+
});
|
|
159
175
|
app.get(
|
|
160
176
|
"/api/projects/:projectId/runtime-settings",
|
|
161
177
|
async (c) => c.json(await service.readRuntimeSettings(id(c.req.param("projectId"))))
|
package/dist/server/http.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/server/http.ts"],
|
|
4
|
-
"sourcesContent": ["import { createHash } from 'node:crypto';\nimport { Hono } from 'hono';\nimport { bodyLimit } from 'hono/body-limit';\nimport { serveStatic } from '@hono/node-server/serve-static';\nimport { upgradeWebSocket, type HttpBindings } from '@hono/node-server';\nimport type { ZodType } from 'zod';\nimport { ZodError } from 'zod';\nimport { AppError } from '../shared/errors.js';\nimport { IMAGE_REQUEST_BYTES } from '../shared/images.js';\nimport * as schemas from '../shared/validation.js';\nimport type { Operation } from '../shared/types.js';\nimport type { AppConfig } from './config.js';\nimport { ConfigStore } from './config-store.js';\nimport { createAuthenticator, type Authenticator, type Identity } from './auth.js';\nimport type { GatewayService } from './service.js';\nimport { socketHandlers } from './websocket.js';\nimport { z } from 'zod';\nimport { releaseChannels, type UpdateController } from '../shared/updates.js';\nimport { settingsSchema, updateRequestSchema } from '../updates/manager.js';\nimport { startShellSchema } from '../shared/shell.js';\nexport interface HttpDependencies {\n config: AppConfig;\n service: GatewayService;\n staticDir: string;\n authenticate?: Authenticator;\n updates?: UpdateController;\n}\nexport function createApp({\n config,\n service,\n staticDir,\n authenticate = createAuthenticator(config),\n updates,\n}: HttpDependencies) {\n const app = new Hono<{\n Bindings: HttpBindings;\n Variables: { identity: Identity; operationId?: string };\n }>();\n app.onError((error, c) => {\n const failure =\n error instanceof AppError\n ? error\n : error instanceof ZodError || error instanceof SyntaxError\n ? new AppError('VALIDATION_FAILED', 'Invalid request data', 400)\n : new AppError('INTERNAL_ERROR', 'An internal error occurred', 500);\n return c.json({ error: failure.toJSON(c.get('operationId')) }, failure.status as 400);\n });\n app.get('/api/health', (c) => c.json({ ok: true }));\n app.use('*', async (c, next) => {\n c.set('identity', await authenticate(c.req.raw, c.req.path === '/ws'));\n await next();\n });\n app.use('/api/*', (c, next) =>\n bodyLimit({\n maxSize: /^\\/api\\/threads\\/[^/]+\\/(turns|steer)$/.test(c.req.path)\n ? IMAGE_REQUEST_BYTES\n : 512 * 1024,\n onError: () => {\n throw new AppError('BODY_TOO_LARGE', 'Request body exceeds limit', 413);\n },\n })(c, next),\n );\n app.use('/api/*', async (c, next) => {\n c.header('Cache-Control', 'no-store');\n if (\n service.isInMaintenance() &&\n !['GET', 'HEAD', 'OPTIONS'].includes(c.req.method) &&\n !c.req.path.startsWith('/api/updates')\n )\n throw new AppError('UPDATE_MAINTENANCE', '\u6B63\u5728\u5347\u7EA7\uFF0C\u8BF7\u7B49\u5F85\u670D\u52A1\u91CD\u65B0\u8FDE\u63A5', 503);\n await next();\n });\n if (updates) {\n app.get('/api/updates', async (c) => c.json(await updates.status()));\n app.post('/api/updates/check', async (c) => {\n const input = z\n .object({ channel: z.enum(releaseChannels).optional() })\n .strict()\n .parse(await c.req.json());\n return c.json(await updates.check(input.channel));\n });\n app.post('/api/updates/settings', async (c) =>\n c.json(await updates.configure(settingsSchema.parse(await c.req.json()))),\n );\n app.post('/api/updates/run', async (c) =>\n c.json(await updates.start(updateRequestSchema.parse(await c.req.json())), 202),\n );\n }\n const id = (value: string) => schemas.idSchema.parse(value);\n const parse = async <T>(\n c: { req: { json: () => Promise<unknown> }; set: (key: 'operationId', value: string) => void },\n schema: ZodType<T>,\n ): Promise<T> => {\n const value: unknown = await c.req.json();\n if (\n value &&\n typeof value === 'object' &&\n 'clientOperationId' in value &&\n typeof value.clientOperationId === 'string'\n )\n c.set('operationId', value.clientOperationId);\n return schema.parse(value);\n };\n const status = (op: Operation) =>\n op.status === 'dispatching' || op.status === 'accepted' ? (202 as const) : (200 as const);\n const configuration = new ConfigStore(config);\n app.use('/api/config*', async (c, next) => {\n c.header('Cache-Control', 'no-store');\n await next();\n });\n app.get('/api/config', async (c) => c.json(await configuration.snapshot()));\n app.post('/api/config', async (c) => c.json(await configuration.save(await c.req.json())));\n app.post('/api/config/import', async (c) =>\n c.json(await configuration.previewImport(await c.req.json())),\n );\n app.get('/api/config/export', async (c) => {\n c.header('Content-Disposition', 'attachment; filename=\"codex-web-config.json\"');\n return c.json(await configuration.export());\n });\n app.get('/api/history-cache-context', async (c) => {\n const identity = c.get('identity');\n if (config.auth.mode !== 'local' && !identity.subject)\n throw new AppError('UNAUTHENTICATED', '\u65E0\u6CD5\u786E\u8BA4\u7F13\u5B58\u6240\u5C5E\u7528\u6237', 401);\n const projects = await service.listProjects();\n // A restart, user change or project/permission change creates a new namespace.\n // The subject is hashed on the server and is never exposed to browser storage.\n const scope = createHash('sha256')\n .update(\n JSON.stringify([\n service.events.serverEpoch,\n service.getSystem().codexEpoch,\n config.auth.mode,\n identity.subject ?? 'local',\n projects.map((project) => [project.id, project.rootPath, project.available]),\n ]),\n )\n .digest('hex');\n return c.json({ scope });\n });\n app.get('/api/system', (c) => c.json(service.getSystem()));\n app.get('/api/shell', (c) =>\n c.json(\n service.shell.snapshot(\n c.req.query('epoch'),\n z.coerce\n .number()\n .int()\n .min(0)\n .max(Number.MAX_SAFE_INTEGER)\n .default(0)\n .parse(c.req.query('after')),\n ),\n ),\n );\n app.post('/api/projects/:projectId/shell', async (c) => {\n const op = await service.startShell(\n id(c.req.param('projectId')),\n await parse(c, startShellSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/shell/:commandId/interrupt', async (c) => {\n const op = await service.interruptShell(\n id(c.req.param('commandId')),\n await parse(c, schemas.operationSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/system/compatibility/confirm', async (c) => {\n const op = await service.confirmCompatibility(\n await parse(c, schemas.confirmCompatibilitySchema),\n );\n return c.json(op, status(op));\n });\n app.get('/api/projects', async (c) => c.json({ data: await service.listProjects() }));\n app.get('/api/unassigned/runtime-settings', async (c) =>\n c.json(await service.readRuntimeSettings(null)),\n );\n app.post('/api/unassigned/model-defaults', async (c) => {\n const op = await service.saveModelDefaults(\n null,\n await parse(c, schemas.saveModelDefaultsSchema),\n );\n return c.json(op, status(op));\n });\n app.get('/api/unassigned/threads', async (c) =>\n c.json(\n await service.listThreadSummaries(\n null,\n c.req.query('cursor') === undefined\n ? undefined\n : schemas.cursorSchema.parse(c.req.query('cursor')),\n schemas.archivedQuerySchema.parse(c.req.query('archived')),\n ),\n ),\n );\n app.post('/api/unassigned/threads', async (c) => {\n const op = await service.createThread(null, await parse(c, schemas.createThreadSchema));\n return c.json(op, status(op));\n });\n app.get('/api/projects/:projectId/runtime-settings', async (c) =>\n c.json(await service.readRuntimeSettings(id(c.req.param('projectId')))),\n );\n app.post('/api/projects/:projectId/model-defaults', async (c) => {\n const op = await service.saveModelDefaults(\n id(c.req.param('projectId')),\n await parse(c, schemas.saveModelDefaultsSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/projects', async (c) =>\n c.json(service.addProject(await parse(c, schemas.createProjectSchema))),\n );\n app.post('/api/projects/:projectId/rename', async (c) =>\n c.json(\n service.renameProject(\n id(c.req.param('projectId')),\n await parse(c, schemas.renameProjectSchema),\n ),\n ),\n );\n app.post('/api/projects/:projectId/remove', async (c) => {\n await parse(c, schemas.removeProjectSchema);\n return c.json(service.removeProject(id(c.req.param('projectId'))));\n });\n app.get('/api/projects/:projectId/threads', async (c) =>\n c.json(\n await service.listThreadSummaries(\n id(c.req.param('projectId')),\n c.req.query('cursor') === undefined\n ? undefined\n : schemas.cursorSchema.parse(c.req.query('cursor')),\n schemas.archivedQuerySchema.parse(c.req.query('archived')),\n ),\n ),\n );\n app.get('/api/threads', async (c) =>\n c.json(\n await service.listThreads(\n id(c.req.query('projectId') ?? ''),\n c.req.query('cursor') === undefined\n ? undefined\n : schemas.cursorSchema.parse(c.req.query('cursor')),\n schemas.archivedQuerySchema.parse(c.req.query('archived')),\n ),\n ),\n );\n app.post('/api/threads/:threadId/archive', async (c) => {\n return c.json(\n await service.setThreadArchived(\n id(c.req.param('threadId')),\n await parse(c, schemas.archiveThreadSchema),\n ),\n );\n });\n app.get('/api/threads/:threadId', async (c) =>\n c.json(await service.getThread(id(c.req.param('threadId')))),\n );\n app.get('/api/threads/:threadId/history', async (c) =>\n c.json(await service.getThreadHistory(id(c.req.param('threadId')), c.req.query('cursor'))),\n );\n app.get('/api/threads/:threadId/metadata', async (c) =>\n c.json(await service.getThreadMetadata(id(c.req.param('threadId')))),\n );\n app.get('/api/threads/:threadId/export', async (c) => {\n const exported = await service.exportThread(id(c.req.param('threadId')));\n c.header('Content-Type', 'text/markdown; charset=utf-8');\n c.header('Content-Disposition', `attachment; filename=\"${exported.filename}\"`);\n c.header('Cache-Control', 'no-store');\n c.header('X-Content-Type-Options', 'nosniff');\n c.header('X-Export-Completeness', exported.partial ? 'partial' : 'saved-history');\n return c.body(exported.markdown);\n });\n app.get('/api/threads/:threadId/images', async (c) => {\n c.header('Cache-Control', 'private, no-store');\n c.header('X-Content-Type-Options', 'nosniff');\n c.header('Cross-Origin-Resource-Policy', 'same-origin');\n c.header('Content-Security-Policy', \"default-src 'none'; sandbox\");\n const image = await service.getThreadImage(id(c.req.param('threadId')), c.req.query('path'));\n return c.body(image.bytes, 200, {\n 'Content-Type': image.contentType,\n 'Content-Length': String(image.bytes.length),\n });\n });\n app.get('/api/threads/:threadId/markdown', async (c) => {\n c.header('Cache-Control', 'private, no-store');\n c.header('X-Content-Type-Options', 'nosniff');\n c.header('Cross-Origin-Resource-Policy', 'same-origin');\n return c.json(\n await service.getThreadMarkdown(id(c.req.param('threadId')), c.req.query('path')),\n );\n });\n app.get('/api/threads/:threadId/json', async (c) => {\n c.header('Cache-Control', 'private, no-store');\n c.header('X-Content-Type-Options', 'nosniff');\n c.header('Cross-Origin-Resource-Policy', 'same-origin');\n return c.json(await service.getThreadJson(id(c.req.param('threadId')), c.req.query('path')));\n });\n app.get('/api/operations/:operationId', (c) =>\n c.json(service.getOperation(id(c.req.param('operationId')))),\n );\n app.post('/api/projects/:projectId/threads', async (c) => {\n const op = await service.createThread(\n id(c.req.param('projectId')),\n await parse(c, schemas.createThreadSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/resume', async (c) => {\n const op = await service.resumeThread(\n id(c.req.param('threadId')),\n await parse(c, schemas.resumeThreadSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/fork', async (c) => {\n const op = await service.forkThread(\n id(c.req.param('threadId')),\n await parse(c, schemas.forkThreadSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/rename', async (c) => {\n const op = await service.renameThread(\n id(c.req.param('threadId')),\n await parse(c, schemas.renameThreadSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/turns', async (c) => {\n const op = await service.startTurn(\n id(c.req.param('threadId')),\n await parse(c, schemas.startTurnSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/review', async (c) => {\n const op = await service.startReview(\n id(c.req.param('threadId')),\n await parse(c, schemas.startReviewSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/steer', async (c) => {\n const op = await service.steer(\n id(c.req.param('threadId')),\n await parse(c, schemas.steerSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/compact', async (c) => {\n const op = await service.compactThread(\n id(c.req.param('threadId')),\n await parse(c, schemas.operationSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/interrupt', async (c) => {\n const op = await service.interrupt(\n id(c.req.param('threadId')),\n await parse(c, schemas.interruptSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/actions/:actionId/resolve', async (c) => {\n const op = await service.resolveAction(\n id(c.req.param('actionId')),\n await parse(c, schemas.resolveActionSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/operations/:operationId/reconcile', async (c) => {\n const op = await service.reconcileOperation(\n id(c.req.param('operationId')),\n await parse(c, schemas.reconcileSchema),\n );\n return c.json(op, status(op));\n });\n app.get(\n '/ws',\n upgradeWebSocket((c) =>\n socketHandlers(service, config, c.get('identity'), c.req.query('overview') === '1'),\n ),\n );\n app.all('/api/*', () => {\n throw new AppError('NOT_FOUND', 'API route not found', 404);\n });\n app.all('/api', () => {\n throw new AppError('NOT_FOUND', 'API route not found', 404);\n });\n app.all('/ws/*', () => {\n throw new AppError('NOT_FOUND', 'WebSocket route not found', 404);\n });\n app.use('*', async (c, next) => {\n if (!c.req.path.startsWith('/assets/')) c.header('Cache-Control', 'no-store');\n c.header('X-Content-Type-Options', 'nosniff');\n c.header('Referrer-Policy', 'same-origin');\n c.header(\n 'Content-Security-Policy',\n // Remote Markdown images load directly in the browser, including HTTPS redirects.\n // Keep scripts, connections and all other resource policies restricted as before.\n \"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'\",\n );\n await next();\n });\n app.get('*', serveStatic({ root: staticDir }));\n app.get('*', async (c, next) => {\n if (!c.req.header('accept')?.includes('text/html') || /\\.[^/]+$/.test(c.req.path))\n return c.json({ error: { code: 'NOT_FOUND', message: 'Resource not found' } }, 404);\n return serveStatic({ path: `${staticDir}/index.html` })(c, next);\n });\n app.notFound((c) => c.json({ error: { code: 'NOT_FOUND', message: 'Resource not found' } }, 404));\n return app;\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { createHash } from 'node:crypto';\nimport { Hono } from 'hono';\nimport { bodyLimit } from 'hono/body-limit';\nimport { serveStatic } from '@hono/node-server/serve-static';\nimport { upgradeWebSocket, type HttpBindings } from '@hono/node-server';\nimport type { ZodType } from 'zod';\nimport { ZodError } from 'zod';\nimport { AppError } from '../shared/errors.js';\nimport { IMAGE_REQUEST_BYTES } from '../shared/images.js';\nimport * as schemas from '../shared/validation.js';\nimport type { Operation } from '../shared/types.js';\nimport type { AppConfig } from './config.js';\nimport { ConfigStore } from './config-store.js';\nimport { createAuthenticator, type Authenticator, type Identity } from './auth.js';\nimport type { GatewayService } from './service.js';\nimport { socketHandlers } from './websocket.js';\nimport { z } from 'zod';\nimport { releaseChannels, type UpdateController } from '../shared/updates.js';\nimport { settingsSchema, updateRequestSchema } from '../updates/manager.js';\nimport { startShellSchema } from '../shared/shell.js';\nexport interface HttpDependencies {\n config: AppConfig;\n service: GatewayService;\n staticDir: string;\n authenticate?: Authenticator;\n updates?: UpdateController;\n}\nexport function createApp({\n config,\n service,\n staticDir,\n authenticate = createAuthenticator(config),\n updates,\n}: HttpDependencies) {\n const app = new Hono<{\n Bindings: HttpBindings;\n Variables: { identity: Identity; operationId?: string };\n }>();\n app.onError((error, c) => {\n const failure =\n error instanceof AppError\n ? error\n : error instanceof ZodError || error instanceof SyntaxError\n ? new AppError('VALIDATION_FAILED', 'Invalid request data', 400)\n : new AppError('INTERNAL_ERROR', 'An internal error occurred', 500);\n return c.json({ error: failure.toJSON(c.get('operationId')) }, failure.status as 400);\n });\n app.get('/api/health', (c) => c.json({ ok: true }));\n app.use('*', async (c, next) => {\n c.set('identity', await authenticate(c.req.raw, c.req.path === '/ws'));\n await next();\n });\n app.use('/api/*', (c, next) =>\n bodyLimit({\n maxSize: /^\\/api\\/threads\\/[^/]+\\/(turns|steer)$/.test(c.req.path)\n ? IMAGE_REQUEST_BYTES\n : 512 * 1024,\n onError: () => {\n throw new AppError('BODY_TOO_LARGE', 'Request body exceeds limit', 413);\n },\n })(c, next),\n );\n app.use('/api/*', async (c, next) => {\n c.header('Cache-Control', 'no-store');\n if (\n service.isInMaintenance() &&\n !['GET', 'HEAD', 'OPTIONS'].includes(c.req.method) &&\n !c.req.path.startsWith('/api/updates')\n )\n throw new AppError('UPDATE_MAINTENANCE', '\u6B63\u5728\u5347\u7EA7\uFF0C\u8BF7\u7B49\u5F85\u670D\u52A1\u91CD\u65B0\u8FDE\u63A5', 503);\n await next();\n });\n if (updates) {\n app.get('/api/updates', async (c) => c.json(await updates.status()));\n app.post('/api/updates/check', async (c) => {\n const input = z\n .object({ channel: z.enum(releaseChannels).optional() })\n .strict()\n .parse(await c.req.json());\n return c.json(await updates.check(input.channel));\n });\n app.post('/api/updates/settings', async (c) =>\n c.json(await updates.configure(settingsSchema.parse(await c.req.json()))),\n );\n app.post('/api/updates/run', async (c) =>\n c.json(await updates.start(updateRequestSchema.parse(await c.req.json())), 202),\n );\n }\n const id = (value: string) => schemas.idSchema.parse(value);\n const parse = async <T>(\n c: { req: { json: () => Promise<unknown> }; set: (key: 'operationId', value: string) => void },\n schema: ZodType<T>,\n ): Promise<T> => {\n const value: unknown = await c.req.json();\n if (\n value &&\n typeof value === 'object' &&\n 'clientOperationId' in value &&\n typeof value.clientOperationId === 'string'\n )\n c.set('operationId', value.clientOperationId);\n return schema.parse(value);\n };\n const status = (op: Operation) =>\n op.status === 'dispatching' || op.status === 'accepted' ? (202 as const) : (200 as const);\n const configuration = new ConfigStore(config);\n app.use('/api/config*', async (c, next) => {\n c.header('Cache-Control', 'no-store');\n await next();\n });\n app.get('/api/config', async (c) => c.json(await configuration.snapshot()));\n app.post('/api/config', async (c) => c.json(await configuration.save(await c.req.json())));\n app.post('/api/config/import', async (c) =>\n c.json(await configuration.previewImport(await c.req.json())),\n );\n app.get('/api/config/export', async (c) => {\n c.header('Content-Disposition', 'attachment; filename=\"codex-web-config.json\"');\n return c.json(await configuration.export());\n });\n app.get('/api/history-cache-context', async (c) => {\n const identity = c.get('identity');\n if (config.auth.mode !== 'local' && !identity.subject)\n throw new AppError('UNAUTHENTICATED', '\u65E0\u6CD5\u786E\u8BA4\u7F13\u5B58\u6240\u5C5E\u7528\u6237', 401);\n const projects = await service.listProjects();\n // A restart, user change or project/permission change creates a new namespace.\n // The subject is hashed on the server and is never exposed to browser storage.\n const scope = createHash('sha256')\n .update(\n JSON.stringify([\n service.events.serverEpoch,\n service.getSystem().codexEpoch,\n config.auth.mode,\n identity.subject ?? 'local',\n projects.map((project) => [project.id, project.rootPath, project.available]),\n ]),\n )\n .digest('hex');\n return c.json({ scope });\n });\n app.get('/api/system', (c) => c.json(service.getSystem()));\n app.get('/api/shell', (c) =>\n c.json(\n service.shell.snapshot(\n c.req.query('epoch'),\n z.coerce\n .number()\n .int()\n .min(0)\n .max(Number.MAX_SAFE_INTEGER)\n .default(0)\n .parse(c.req.query('after')),\n ),\n ),\n );\n app.post('/api/projects/:projectId/shell', async (c) => {\n const op = await service.startShell(\n id(c.req.param('projectId')),\n await parse(c, startShellSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/shell/:commandId/interrupt', async (c) => {\n const op = await service.interruptShell(\n id(c.req.param('commandId')),\n await parse(c, schemas.operationSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/system/compatibility/confirm', async (c) => {\n const op = await service.confirmCompatibility(\n await parse(c, schemas.confirmCompatibilitySchema),\n );\n return c.json(op, status(op));\n });\n app.get('/api/projects', async (c) => c.json({ data: await service.listProjects() }));\n app.get('/api/unassigned/runtime-settings', async (c) =>\n c.json(await service.readRuntimeSettings(null)),\n );\n app.post('/api/unassigned/model-defaults', async (c) => {\n const op = await service.saveModelDefaults(\n null,\n await parse(c, schemas.saveModelDefaultsSchema),\n );\n return c.json(op, status(op));\n });\n app.get('/api/unassigned/threads', async (c) =>\n c.json(\n await service.listThreadSummaries(\n null,\n c.req.query('cursor') === undefined\n ? undefined\n : schemas.cursorSchema.parse(c.req.query('cursor')),\n schemas.archivedQuerySchema.parse(c.req.query('archived')),\n ),\n ),\n );\n app.post('/api/unassigned/threads', async (c) => {\n const op = await service.createThread(null, await parse(c, schemas.createThreadSchema));\n return c.json(op, status(op));\n });\n app.post('/api/project-clones', async (c) =>\n c.json(service.cloneProject(await parse(c, schemas.cloneProjectSchema)), 202),\n );\n app.get('/api/project-clones/:cloneId', (c) =>\n c.json(service.projectClones.get(id(c.req.param('cloneId')))),\n );\n app.post('/api/project-clones/:cloneId/cancel', async (c) => {\n await parse(c, schemas.removeProjectSchema);\n return c.json(service.projectClones.cancel(id(c.req.param('cloneId'))));\n });\n app.post('/api/project-clones/:cloneId/register', async (c) => {\n await parse(c, schemas.removeProjectSchema);\n return c.json(service.projectClones.retryRegistration(id(c.req.param('cloneId'))));\n });\n app.get('/api/projects/:projectId/runtime-settings', async (c) =>\n c.json(await service.readRuntimeSettings(id(c.req.param('projectId')))),\n );\n app.post('/api/projects/:projectId/model-defaults', async (c) => {\n const op = await service.saveModelDefaults(\n id(c.req.param('projectId')),\n await parse(c, schemas.saveModelDefaultsSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/projects', async (c) =>\n c.json(service.addProject(await parse(c, schemas.createProjectSchema))),\n );\n app.post('/api/projects/:projectId/rename', async (c) =>\n c.json(\n service.renameProject(\n id(c.req.param('projectId')),\n await parse(c, schemas.renameProjectSchema),\n ),\n ),\n );\n app.post('/api/projects/:projectId/remove', async (c) => {\n await parse(c, schemas.removeProjectSchema);\n return c.json(service.removeProject(id(c.req.param('projectId'))));\n });\n app.get('/api/projects/:projectId/threads', async (c) =>\n c.json(\n await service.listThreadSummaries(\n id(c.req.param('projectId')),\n c.req.query('cursor') === undefined\n ? undefined\n : schemas.cursorSchema.parse(c.req.query('cursor')),\n schemas.archivedQuerySchema.parse(c.req.query('archived')),\n ),\n ),\n );\n app.get('/api/threads', async (c) =>\n c.json(\n await service.listThreads(\n id(c.req.query('projectId') ?? ''),\n c.req.query('cursor') === undefined\n ? undefined\n : schemas.cursorSchema.parse(c.req.query('cursor')),\n schemas.archivedQuerySchema.parse(c.req.query('archived')),\n ),\n ),\n );\n app.post('/api/threads/:threadId/archive', async (c) => {\n return c.json(\n await service.setThreadArchived(\n id(c.req.param('threadId')),\n await parse(c, schemas.archiveThreadSchema),\n ),\n );\n });\n app.get('/api/threads/:threadId', async (c) =>\n c.json(await service.getThread(id(c.req.param('threadId')))),\n );\n app.get('/api/threads/:threadId/history', async (c) =>\n c.json(await service.getThreadHistory(id(c.req.param('threadId')), c.req.query('cursor'))),\n );\n app.get('/api/threads/:threadId/metadata', async (c) =>\n c.json(await service.getThreadMetadata(id(c.req.param('threadId')))),\n );\n app.get('/api/threads/:threadId/export', async (c) => {\n const exported = await service.exportThread(id(c.req.param('threadId')));\n c.header('Content-Type', 'text/markdown; charset=utf-8');\n c.header('Content-Disposition', `attachment; filename=\"${exported.filename}\"`);\n c.header('Cache-Control', 'no-store');\n c.header('X-Content-Type-Options', 'nosniff');\n c.header('X-Export-Completeness', exported.partial ? 'partial' : 'saved-history');\n return c.body(exported.markdown);\n });\n app.get('/api/threads/:threadId/images', async (c) => {\n c.header('Cache-Control', 'private, no-store');\n c.header('X-Content-Type-Options', 'nosniff');\n c.header('Cross-Origin-Resource-Policy', 'same-origin');\n c.header('Content-Security-Policy', \"default-src 'none'; sandbox\");\n const image = await service.getThreadImage(id(c.req.param('threadId')), c.req.query('path'));\n return c.body(image.bytes, 200, {\n 'Content-Type': image.contentType,\n 'Content-Length': String(image.bytes.length),\n });\n });\n app.get('/api/threads/:threadId/markdown', async (c) => {\n c.header('Cache-Control', 'private, no-store');\n c.header('X-Content-Type-Options', 'nosniff');\n c.header('Cross-Origin-Resource-Policy', 'same-origin');\n return c.json(\n await service.getThreadMarkdown(id(c.req.param('threadId')), c.req.query('path')),\n );\n });\n app.get('/api/threads/:threadId/json', async (c) => {\n c.header('Cache-Control', 'private, no-store');\n c.header('X-Content-Type-Options', 'nosniff');\n c.header('Cross-Origin-Resource-Policy', 'same-origin');\n return c.json(await service.getThreadJson(id(c.req.param('threadId')), c.req.query('path')));\n });\n app.get('/api/operations/:operationId', (c) =>\n c.json(service.getOperation(id(c.req.param('operationId')))),\n );\n app.post('/api/projects/:projectId/threads', async (c) => {\n const op = await service.createThread(\n id(c.req.param('projectId')),\n await parse(c, schemas.createThreadSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/resume', async (c) => {\n const op = await service.resumeThread(\n id(c.req.param('threadId')),\n await parse(c, schemas.resumeThreadSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/fork', async (c) => {\n const op = await service.forkThread(\n id(c.req.param('threadId')),\n await parse(c, schemas.forkThreadSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/rename', async (c) => {\n const op = await service.renameThread(\n id(c.req.param('threadId')),\n await parse(c, schemas.renameThreadSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/turns', async (c) => {\n const op = await service.startTurn(\n id(c.req.param('threadId')),\n await parse(c, schemas.startTurnSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/review', async (c) => {\n const op = await service.startReview(\n id(c.req.param('threadId')),\n await parse(c, schemas.startReviewSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/steer', async (c) => {\n const op = await service.steer(\n id(c.req.param('threadId')),\n await parse(c, schemas.steerSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/compact', async (c) => {\n const op = await service.compactThread(\n id(c.req.param('threadId')),\n await parse(c, schemas.operationSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/interrupt', async (c) => {\n const op = await service.interrupt(\n id(c.req.param('threadId')),\n await parse(c, schemas.interruptSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/actions/:actionId/resolve', async (c) => {\n const op = await service.resolveAction(\n id(c.req.param('actionId')),\n await parse(c, schemas.resolveActionSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/operations/:operationId/reconcile', async (c) => {\n const op = await service.reconcileOperation(\n id(c.req.param('operationId')),\n await parse(c, schemas.reconcileSchema),\n );\n return c.json(op, status(op));\n });\n app.get(\n '/ws',\n upgradeWebSocket((c) =>\n socketHandlers(service, config, c.get('identity'), c.req.query('overview') === '1'),\n ),\n );\n app.all('/api/*', () => {\n throw new AppError('NOT_FOUND', 'API route not found', 404);\n });\n app.all('/api', () => {\n throw new AppError('NOT_FOUND', 'API route not found', 404);\n });\n app.all('/ws/*', () => {\n throw new AppError('NOT_FOUND', 'WebSocket route not found', 404);\n });\n app.use('*', async (c, next) => {\n if (!c.req.path.startsWith('/assets/')) c.header('Cache-Control', 'no-store');\n c.header('X-Content-Type-Options', 'nosniff');\n c.header('Referrer-Policy', 'same-origin');\n c.header(\n 'Content-Security-Policy',\n // Remote Markdown images load directly in the browser, including HTTPS redirects.\n // Keep scripts, connections and all other resource policies restricted as before.\n \"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'\",\n );\n await next();\n });\n app.get('*', serveStatic({ root: staticDir }));\n app.get('*', async (c, next) => {\n if (!c.req.header('accept')?.includes('text/html') || /\\.[^/]+$/.test(c.req.path))\n return c.json({ error: { code: 'NOT_FOUND', message: 'Resource not found' } }, 404);\n return serveStatic({ path: `${staticDir}/index.html` })(c, next);\n });\n app.notFound((c) => c.json({ error: { code: 'NOT_FOUND', message: 'Resource not found' } }, 404));\n return app;\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,kBAAkB;AAC3B,SAAS,YAAY;AACrB,SAAS,iBAAiB;AAC1B,SAAS,mBAAmB;AAC5B,SAAS,wBAA2C;AAEpD,SAAS,gBAAgB;AACzB,SAAS,gBAAgB;AACzB,SAAS,2BAA2B;AACpC,YAAY,aAAa;AAGzB,SAAS,mBAAmB;AAC5B,SAAS,2BAA8D;AAEvE,SAAS,sBAAsB;AAC/B,SAAS,SAAS;AAClB,SAAS,uBAA8C;AACvD,SAAS,gBAAgB,2BAA2B;AACpD,SAAS,wBAAwB;AAQ1B,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe,oBAAoB,MAAM;AAAA,EACzC;AACF,GAAqB;AACnB,QAAM,MAAM,IAAI,KAGb;AACH,MAAI,QAAQ,CAAC,OAAO,MAAM;AACxB,UAAM,UACJ,iBAAiB,WACb,QACA,iBAAiB,YAAY,iBAAiB,cAC5C,IAAI,SAAS,qBAAqB,wBAAwB,GAAG,IAC7D,IAAI,SAAS,kBAAkB,8BAA8B,GAAG;AACxE,WAAO,EAAE,KAAK,EAAE,OAAO,QAAQ,OAAO,EAAE,IAAI,aAAa,CAAC,EAAE,GAAG,QAAQ,MAAa;AAAA,EACtF,CAAC;AACD,MAAI,IAAI,eAAe,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,KAAK,CAAC,CAAC;AAClD,MAAI,IAAI,KAAK,OAAO,GAAG,SAAS;AAC9B,MAAE,IAAI,YAAY,MAAM,aAAa,EAAE,IAAI,KAAK,EAAE,IAAI,SAAS,KAAK,CAAC;AACrE,UAAM,KAAK;AAAA,EACb,CAAC;AACD,MAAI;AAAA,IAAI;AAAA,IAAU,CAAC,GAAG,SACpB,UAAU;AAAA,MACR,SAAS,yCAAyC,KAAK,EAAE,IAAI,IAAI,IAC7D,sBACA,MAAM;AAAA,MACV,SAAS,MAAM;AACb,cAAM,IAAI,SAAS,kBAAkB,8BAA8B,GAAG;AAAA,MACxE;AAAA,IACF,CAAC,EAAE,GAAG,IAAI;AAAA,EACZ;AACA,MAAI,IAAI,UAAU,OAAO,GAAG,SAAS;AACnC,MAAE,OAAO,iBAAiB,UAAU;AACpC,QACE,QAAQ,gBAAgB,KACxB,CAAC,CAAC,OAAO,QAAQ,SAAS,EAAE,SAAS,EAAE,IAAI,MAAM,KACjD,CAAC,EAAE,IAAI,KAAK,WAAW,cAAc;AAErC,YAAM,IAAI,SAAS,sBAAsB,wFAAkB,GAAG;AAChE,UAAM,KAAK;AAAA,EACb,CAAC;AACD,MAAI,SAAS;AACX,QAAI,IAAI,gBAAgB,OAAO,MAAM,EAAE,KAAK,MAAM,QAAQ,OAAO,CAAC,CAAC;AACnE,QAAI,KAAK,sBAAsB,OAAO,MAAM;AAC1C,YAAM,QAAQ,EACX,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,SAAS,EAAE,CAAC,EACtD,OAAO,EACP,MAAM,MAAM,EAAE,IAAI,KAAK,CAAC;AAC3B,aAAO,EAAE,KAAK,MAAM,QAAQ,MAAM,MAAM,OAAO,CAAC;AAAA,IAClD,CAAC;AACD,QAAI;AAAA,MAAK;AAAA,MAAyB,OAAO,MACvC,EAAE,KAAK,MAAM,QAAQ,UAAU,eAAe,MAAM,MAAM,EAAE,IAAI,KAAK,CAAC,CAAC,CAAC;AAAA,IAC1E;AACA,QAAI;AAAA,MAAK;AAAA,MAAoB,OAAO,MAClC,EAAE,KAAK,MAAM,QAAQ,MAAM,oBAAoB,MAAM,MAAM,EAAE,IAAI,KAAK,CAAC,CAAC,GAAG,GAAG;AAAA,IAChF;AAAA,EACF;AACA,QAAM,KAAK,CAAC,UAAkB,QAAQ,SAAS,MAAM,KAAK;AAC1D,QAAM,QAAQ,OACZ,GACA,WACe;AACf,UAAM,QAAiB,MAAM,EAAE,IAAI,KAAK;AACxC,QACE,SACA,OAAO,UAAU,YACjB,uBAAuB,SACvB,OAAO,MAAM,sBAAsB;AAEnC,QAAE,IAAI,eAAe,MAAM,iBAAiB;AAC9C,WAAO,OAAO,MAAM,KAAK;AAAA,EAC3B;AACA,QAAM,SAAS,CAAC,OACd,GAAG,WAAW,iBAAiB,GAAG,WAAW,aAAc,MAAiB;AAC9E,QAAM,gBAAgB,IAAI,YAAY,MAAM;AAC5C,MAAI,IAAI,gBAAgB,OAAO,GAAG,SAAS;AACzC,MAAE,OAAO,iBAAiB,UAAU;AACpC,UAAM,KAAK;AAAA,EACb,CAAC;AACD,MAAI,IAAI,eAAe,OAAO,MAAM,EAAE,KAAK,MAAM,cAAc,SAAS,CAAC,CAAC;AAC1E,MAAI,KAAK,eAAe,OAAO,MAAM,EAAE,KAAK,MAAM,cAAc,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,CAAC,CAAC;AACzF,MAAI;AAAA,IAAK;AAAA,IAAsB,OAAO,MACpC,EAAE,KAAK,MAAM,cAAc,cAAc,MAAM,EAAE,IAAI,KAAK,CAAC,CAAC;AAAA,EAC9D;AACA,MAAI,IAAI,sBAAsB,OAAO,MAAM;AACzC,MAAE,OAAO,uBAAuB,8CAA8C;AAC9E,WAAO,EAAE,KAAK,MAAM,cAAc,OAAO,CAAC;AAAA,EAC5C,CAAC;AACD,MAAI,IAAI,8BAA8B,OAAO,MAAM;AACjD,UAAM,WAAW,EAAE,IAAI,UAAU;AACjC,QAAI,OAAO,KAAK,SAAS,WAAW,CAAC,SAAS;AAC5C,YAAM,IAAI,SAAS,mBAAmB,gEAAc,GAAG;AACzD,UAAM,WAAW,MAAM,QAAQ,aAAa;AAG5C,UAAM,QAAQ,WAAW,QAAQ,EAC9B;AAAA,MACC,KAAK,UAAU;AAAA,QACb,QAAQ,OAAO;AAAA,QACf,QAAQ,UAAU,EAAE;AAAA,QACpB,OAAO,KAAK;AAAA,QACZ,SAAS,WAAW;AAAA,QACpB,SAAS,IAAI,CAAC,YAAY,CAAC,QAAQ,IAAI,QAAQ,UAAU,QAAQ,SAAS,CAAC;AAAA,MAC7E,CAAC;AAAA,IACH,EACC,OAAO,KAAK;AACf,WAAO,EAAE,KAAK,EAAE,MAAM,CAAC;AAAA,EACzB,CAAC;AACD,MAAI,IAAI,eAAe,CAAC,MAAM,EAAE,KAAK,QAAQ,UAAU,CAAC,CAAC;AACzD,MAAI;AAAA,IAAI;AAAA,IAAc,CAAC,MACrB,EAAE;AAAA,MACA,QAAQ,MAAM;AAAA,QACZ,EAAE,IAAI,MAAM,OAAO;AAAA,QACnB,EAAE,OACC,OAAO,EACP,IAAI,EACJ,IAAI,CAAC,EACL,IAAI,OAAO,gBAAgB,EAC3B,QAAQ,CAAC,EACT,MAAM,EAAE,IAAI,MAAM,OAAO,CAAC;AAAA,MAC/B;AAAA,IACF;AAAA,EACF;AACA,MAAI,KAAK,kCAAkC,OAAO,MAAM;AACtD,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,GAAG,EAAE,IAAI,MAAM,WAAW,CAAC;AAAA,MAC3B,MAAM,MAAM,GAAG,gBAAgB;AAAA,IACjC;AACA,WAAO,EAAE,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,EAC9B,CAAC;AACD,MAAI,KAAK,mCAAmC,OAAO,MAAM;AACvD,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,GAAG,EAAE,IAAI,MAAM,WAAW,CAAC;AAAA,MAC3B,MAAM,MAAM,GAAG,QAAQ,eAAe;AAAA,IACxC;AACA,WAAO,EAAE,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,EAC9B,CAAC;AACD,MAAI,KAAK,qCAAqC,OAAO,MAAM;AACzD,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,MAAM,MAAM,GAAG,QAAQ,0BAA0B;AAAA,IACnD;AACA,WAAO,EAAE,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,EAC9B,CAAC;AACD,MAAI,IAAI,iBAAiB,OAAO,MAAM,EAAE,KAAK,EAAE,MAAM,MAAM,QAAQ,aAAa,EAAE,CAAC,CAAC;AACpF,MAAI;AAAA,IAAI;AAAA,IAAoC,OAAO,MACjD,EAAE,KAAK,MAAM,QAAQ,oBAAoB,IAAI,CAAC;AAAA,EAChD;AACA,MAAI,KAAK,kCAAkC,OAAO,MAAM;AACtD,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB;AAAA,MACA,MAAM,MAAM,GAAG,QAAQ,uBAAuB;AAAA,IAChD;AACA,WAAO,EAAE,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,EAC9B,CAAC;AACD,MAAI;AAAA,IAAI;AAAA,IAA2B,OAAO,MACxC,EAAE;AAAA,MACA,MAAM,QAAQ;AAAA,QACZ;AAAA,QACA,EAAE,IAAI,MAAM,QAAQ,MAAM,SACtB,SACA,QAAQ,aAAa,MAAM,EAAE,IAAI,MAAM,QAAQ,CAAC;AAAA,QACpD,QAAQ,oBAAoB,MAAM,EAAE,IAAI,MAAM,UAAU,CAAC;AAAA,MAC3D;AAAA,IACF;AAAA,EACF;AACA,MAAI,KAAK,2BAA2B,OAAO,MAAM;AAC/C,UAAM,KAAK,MAAM,QAAQ,aAAa,MAAM,MAAM,MAAM,GAAG,QAAQ,kBAAkB,CAAC;AACtF,WAAO,EAAE,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,EAC9B,CAAC;AACD,MAAI;AAAA,IAAK;AAAA,IAAuB,OAAO,MACrC,EAAE,KAAK,QAAQ,aAAa,MAAM,MAAM,GAAG,QAAQ,kBAAkB,CAAC,GAAG,GAAG;AAAA,EAC9E;AACA,MAAI;AAAA,IAAI;AAAA,IAAgC,CAAC,MACvC,EAAE,KAAK,QAAQ,cAAc,IAAI,GAAG,EAAE,IAAI,MAAM,SAAS,CAAC,CAAC,CAAC;AAAA,EAC9D;AACA,MAAI,KAAK,uCAAuC,OAAO,MAAM;AAC3D,UAAM,MAAM,GAAG,QAAQ,mBAAmB;AAC1C,WAAO,EAAE,KAAK,QAAQ,cAAc,OAAO,GAAG,EAAE,IAAI,MAAM,SAAS,CAAC,CAAC,CAAC;AAAA,EACxE,CAAC;AACD,MAAI,KAAK,yCAAyC,OAAO,MAAM;AAC7D,UAAM,MAAM,GAAG,QAAQ,mBAAmB;AAC1C,WAAO,EAAE,KAAK,QAAQ,cAAc,kBAAkB,GAAG,EAAE,IAAI,MAAM,SAAS,CAAC,CAAC,CAAC;AAAA,EACnF,CAAC;AACD,MAAI;AAAA,IAAI;AAAA,IAA6C,OAAO,MAC1D,EAAE,KAAK,MAAM,QAAQ,oBAAoB,GAAG,EAAE,IAAI,MAAM,WAAW,CAAC,CAAC,CAAC;AAAA,EACxE;AACA,MAAI,KAAK,2CAA2C,OAAO,MAAM;AAC/D,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,GAAG,EAAE,IAAI,MAAM,WAAW,CAAC;AAAA,MAC3B,MAAM,MAAM,GAAG,QAAQ,uBAAuB;AAAA,IAChD;AACA,WAAO,EAAE,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,EAC9B,CAAC;AACD,MAAI;AAAA,IAAK;AAAA,IAAiB,OAAO,MAC/B,EAAE,KAAK,QAAQ,WAAW,MAAM,MAAM,GAAG,QAAQ,mBAAmB,CAAC,CAAC;AAAA,EACxE;AACA,MAAI;AAAA,IAAK;AAAA,IAAmC,OAAO,MACjD,EAAE;AAAA,MACA,QAAQ;AAAA,QACN,GAAG,EAAE,IAAI,MAAM,WAAW,CAAC;AAAA,QAC3B,MAAM,MAAM,GAAG,QAAQ,mBAAmB;AAAA,MAC5C;AAAA,IACF;AAAA,EACF;AACA,MAAI,KAAK,mCAAmC,OAAO,MAAM;AACvD,UAAM,MAAM,GAAG,QAAQ,mBAAmB;AAC1C,WAAO,EAAE,KAAK,QAAQ,cAAc,GAAG,EAAE,IAAI,MAAM,WAAW,CAAC,CAAC,CAAC;AAAA,EACnE,CAAC;AACD,MAAI;AAAA,IAAI;AAAA,IAAoC,OAAO,MACjD,EAAE;AAAA,MACA,MAAM,QAAQ;AAAA,QACZ,GAAG,EAAE,IAAI,MAAM,WAAW,CAAC;AAAA,QAC3B,EAAE,IAAI,MAAM,QAAQ,MAAM,SACtB,SACA,QAAQ,aAAa,MAAM,EAAE,IAAI,MAAM,QAAQ,CAAC;AAAA,QACpD,QAAQ,oBAAoB,MAAM,EAAE,IAAI,MAAM,UAAU,CAAC;AAAA,MAC3D;AAAA,IACF;AAAA,EACF;AACA,MAAI;AAAA,IAAI;AAAA,IAAgB,OAAO,MAC7B,EAAE;AAAA,MACA,MAAM,QAAQ;AAAA,QACZ,GAAG,EAAE,IAAI,MAAM,WAAW,KAAK,EAAE;AAAA,QACjC,EAAE,IAAI,MAAM,QAAQ,MAAM,SACtB,SACA,QAAQ,aAAa,MAAM,EAAE,IAAI,MAAM,QAAQ,CAAC;AAAA,QACpD,QAAQ,oBAAoB,MAAM,EAAE,IAAI,MAAM,UAAU,CAAC;AAAA,MAC3D;AAAA,IACF;AAAA,EACF;AACA,MAAI,KAAK,kCAAkC,OAAO,MAAM;AACtD,WAAO,EAAE;AAAA,MACP,MAAM,QAAQ;AAAA,QACZ,GAAG,EAAE,IAAI,MAAM,UAAU,CAAC;AAAA,QAC1B,MAAM,MAAM,GAAG,QAAQ,mBAAmB;AAAA,MAC5C;AAAA,IACF;AAAA,EACF,CAAC;AACD,MAAI;AAAA,IAAI;AAAA,IAA0B,OAAO,MACvC,EAAE,KAAK,MAAM,QAAQ,UAAU,GAAG,EAAE,IAAI,MAAM,UAAU,CAAC,CAAC,CAAC;AAAA,EAC7D;AACA,MAAI;AAAA,IAAI;AAAA,IAAkC,OAAO,MAC/C,EAAE,KAAK,MAAM,QAAQ,iBAAiB,GAAG,EAAE,IAAI,MAAM,UAAU,CAAC,GAAG,EAAE,IAAI,MAAM,QAAQ,CAAC,CAAC;AAAA,EAC3F;AACA,MAAI;AAAA,IAAI;AAAA,IAAmC,OAAO,MAChD,EAAE,KAAK,MAAM,QAAQ,kBAAkB,GAAG,EAAE,IAAI,MAAM,UAAU,CAAC,CAAC,CAAC;AAAA,EACrE;AACA,MAAI,IAAI,iCAAiC,OAAO,MAAM;AACpD,UAAM,WAAW,MAAM,QAAQ,aAAa,GAAG,EAAE,IAAI,MAAM,UAAU,CAAC,CAAC;AACvE,MAAE,OAAO,gBAAgB,8BAA8B;AACvD,MAAE,OAAO,uBAAuB,yBAAyB,SAAS,QAAQ,GAAG;AAC7E,MAAE,OAAO,iBAAiB,UAAU;AACpC,MAAE,OAAO,0BAA0B,SAAS;AAC5C,MAAE,OAAO,yBAAyB,SAAS,UAAU,YAAY,eAAe;AAChF,WAAO,EAAE,KAAK,SAAS,QAAQ;AAAA,EACjC,CAAC;AACD,MAAI,IAAI,iCAAiC,OAAO,MAAM;AACpD,MAAE,OAAO,iBAAiB,mBAAmB;AAC7C,MAAE,OAAO,0BAA0B,SAAS;AAC5C,MAAE,OAAO,gCAAgC,aAAa;AACtD,MAAE,OAAO,2BAA2B,6BAA6B;AACjE,UAAM,QAAQ,MAAM,QAAQ,eAAe,GAAG,EAAE,IAAI,MAAM,UAAU,CAAC,GAAG,EAAE,IAAI,MAAM,MAAM,CAAC;AAC3F,WAAO,EAAE,KAAK,MAAM,OAAO,KAAK;AAAA,MAC9B,gBAAgB,MAAM;AAAA,MACtB,kBAAkB,OAAO,MAAM,MAAM,MAAM;AAAA,IAC7C,CAAC;AAAA,EACH,CAAC;AACD,MAAI,IAAI,mCAAmC,OAAO,MAAM;AACtD,MAAE,OAAO,iBAAiB,mBAAmB;AAC7C,MAAE,OAAO,0BAA0B,SAAS;AAC5C,MAAE,OAAO,gCAAgC,aAAa;AACtD,WAAO,EAAE;AAAA,MACP,MAAM,QAAQ,kBAAkB,GAAG,EAAE,IAAI,MAAM,UAAU,CAAC,GAAG,EAAE,IAAI,MAAM,MAAM,CAAC;AAAA,IAClF;AAAA,EACF,CAAC;AACD,MAAI,IAAI,+BAA+B,OAAO,MAAM;AAClD,MAAE,OAAO,iBAAiB,mBAAmB;AAC7C,MAAE,OAAO,0BAA0B,SAAS;AAC5C,MAAE,OAAO,gCAAgC,aAAa;AACtD,WAAO,EAAE,KAAK,MAAM,QAAQ,cAAc,GAAG,EAAE,IAAI,MAAM,UAAU,CAAC,GAAG,EAAE,IAAI,MAAM,MAAM,CAAC,CAAC;AAAA,EAC7F,CAAC;AACD,MAAI;AAAA,IAAI;AAAA,IAAgC,CAAC,MACvC,EAAE,KAAK,QAAQ,aAAa,GAAG,EAAE,IAAI,MAAM,aAAa,CAAC,CAAC,CAAC;AAAA,EAC7D;AACA,MAAI,KAAK,oCAAoC,OAAO,MAAM;AACxD,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,GAAG,EAAE,IAAI,MAAM,WAAW,CAAC;AAAA,MAC3B,MAAM,MAAM,GAAG,QAAQ,kBAAkB;AAAA,IAC3C;AACA,WAAO,EAAE,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,EAC9B,CAAC;AACD,MAAI,KAAK,iCAAiC,OAAO,MAAM;AACrD,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,GAAG,EAAE,IAAI,MAAM,UAAU,CAAC;AAAA,MAC1B,MAAM,MAAM,GAAG,QAAQ,kBAAkB;AAAA,IAC3C;AACA,WAAO,EAAE,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,EAC9B,CAAC;AACD,MAAI,KAAK,+BAA+B,OAAO,MAAM;AACnD,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,GAAG,EAAE,IAAI,MAAM,UAAU,CAAC;AAAA,MAC1B,MAAM,MAAM,GAAG,QAAQ,gBAAgB;AAAA,IACzC;AACA,WAAO,EAAE,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,EAC9B,CAAC;AACD,MAAI,KAAK,iCAAiC,OAAO,MAAM;AACrD,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,GAAG,EAAE,IAAI,MAAM,UAAU,CAAC;AAAA,MAC1B,MAAM,MAAM,GAAG,QAAQ,kBAAkB;AAAA,IAC3C;AACA,WAAO,EAAE,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,EAC9B,CAAC;AACD,MAAI,KAAK,gCAAgC,OAAO,MAAM;AACpD,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,GAAG,EAAE,IAAI,MAAM,UAAU,CAAC;AAAA,MAC1B,MAAM,MAAM,GAAG,QAAQ,eAAe;AAAA,IACxC;AACA,WAAO,EAAE,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,EAC9B,CAAC;AACD,MAAI,KAAK,iCAAiC,OAAO,MAAM;AACrD,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,GAAG,EAAE,IAAI,MAAM,UAAU,CAAC;AAAA,MAC1B,MAAM,MAAM,GAAG,QAAQ,iBAAiB;AAAA,IAC1C;AACA,WAAO,EAAE,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,EAC9B,CAAC;AACD,MAAI,KAAK,gCAAgC,OAAO,MAAM;AACpD,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,GAAG,EAAE,IAAI,MAAM,UAAU,CAAC;AAAA,MAC1B,MAAM,MAAM,GAAG,QAAQ,WAAW;AAAA,IACpC;AACA,WAAO,EAAE,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,EAC9B,CAAC;AACD,MAAI,KAAK,kCAAkC,OAAO,MAAM;AACtD,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,GAAG,EAAE,IAAI,MAAM,UAAU,CAAC;AAAA,MAC1B,MAAM,MAAM,GAAG,QAAQ,eAAe;AAAA,IACxC;AACA,WAAO,EAAE,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,EAC9B,CAAC;AACD,MAAI,KAAK,oCAAoC,OAAO,MAAM;AACxD,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,GAAG,EAAE,IAAI,MAAM,UAAU,CAAC;AAAA,MAC1B,MAAM,MAAM,GAAG,QAAQ,eAAe;AAAA,IACxC;AACA,WAAO,EAAE,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,EAC9B,CAAC;AACD,MAAI,KAAK,kCAAkC,OAAO,MAAM;AACtD,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,GAAG,EAAE,IAAI,MAAM,UAAU,CAAC;AAAA,MAC1B,MAAM,MAAM,GAAG,QAAQ,mBAAmB;AAAA,IAC5C;AACA,WAAO,EAAE,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,EAC9B,CAAC;AACD,MAAI,KAAK,0CAA0C,OAAO,MAAM;AAC9D,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,GAAG,EAAE,IAAI,MAAM,aAAa,CAAC;AAAA,MAC7B,MAAM,MAAM,GAAG,QAAQ,eAAe;AAAA,IACxC;AACA,WAAO,EAAE,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,EAC9B,CAAC;AACD,MAAI;AAAA,IACF;AAAA,IACA;AAAA,MAAiB,CAAC,MAChB,eAAe,SAAS,QAAQ,EAAE,IAAI,UAAU,GAAG,EAAE,IAAI,MAAM,UAAU,MAAM,GAAG;AAAA,IACpF;AAAA,EACF;AACA,MAAI,IAAI,UAAU,MAAM;AACtB,UAAM,IAAI,SAAS,aAAa,uBAAuB,GAAG;AAAA,EAC5D,CAAC;AACD,MAAI,IAAI,QAAQ,MAAM;AACpB,UAAM,IAAI,SAAS,aAAa,uBAAuB,GAAG;AAAA,EAC5D,CAAC;AACD,MAAI,IAAI,SAAS,MAAM;AACrB,UAAM,IAAI,SAAS,aAAa,6BAA6B,GAAG;AAAA,EAClE,CAAC;AACD,MAAI,IAAI,KAAK,OAAO,GAAG,SAAS;AAC9B,QAAI,CAAC,EAAE,IAAI,KAAK,WAAW,UAAU,EAAG,GAAE,OAAO,iBAAiB,UAAU;AAC5E,MAAE,OAAO,0BAA0B,SAAS;AAC5C,MAAE,OAAO,mBAAmB,aAAa;AACzC,MAAE;AAAA,MACA;AAAA;AAAA;AAAA,MAGA;AAAA,IACF;AACA,UAAM,KAAK;AAAA,EACb,CAAC;AACD,MAAI,IAAI,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC,CAAC;AAC7C,MAAI,IAAI,KAAK,OAAO,GAAG,SAAS;AAC9B,QAAI,CAAC,EAAE,IAAI,OAAO,QAAQ,GAAG,SAAS,WAAW,KAAK,WAAW,KAAK,EAAE,IAAI,IAAI;AAC9E,aAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,SAAS,qBAAqB,EAAE,GAAG,GAAG;AACpF,WAAO,YAAY,EAAE,MAAM,GAAG,SAAS,cAAc,CAAC,EAAE,GAAG,IAAI;AAAA,EACjE,CAAC;AACD,MAAI,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,SAAS,qBAAqB,EAAE,GAAG,GAAG,CAAC;AAChG,SAAO;AACT;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { accessSync, constants, lstatSync, readdirSync, realpathSync } from "node:fs";
|
|
4
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
5
|
+
import { AppError } from "../shared/errors.js";
|
|
6
|
+
import { cloneProjectSchema } from "../shared/validation.js";
|
|
7
|
+
import { containsPath } from "./projects.js";
|
|
8
|
+
const active = (job) => ["cloning", "cancelling"].includes(job.view.status);
|
|
9
|
+
const hasControl = (value) => [...value].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127);
|
|
10
|
+
function validateRepository(repository) {
|
|
11
|
+
let valid = false;
|
|
12
|
+
if (!hasControl(repository) && !/[\s\\]/.test(repository)) {
|
|
13
|
+
if (/^(https|ssh):\/\//.test(repository)) {
|
|
14
|
+
try {
|
|
15
|
+
const url = new URL(repository);
|
|
16
|
+
valid = Boolean(
|
|
17
|
+
url.hostname && !url.hostname.startsWith("-") && url.pathname.length > 1 && !url.password && !url.search && !url.hash && (url.protocol === "ssh:" ? /^(?:[a-zA-Z0-9_][\w.-]*)?$/.test(url.username) : !url.username)
|
|
18
|
+
);
|
|
19
|
+
} catch {
|
|
20
|
+
}
|
|
21
|
+
} else {
|
|
22
|
+
valid = /^(?:[a-zA-Z0-9_][\w.-]*@)?[a-zA-Z0-9][\w.-]*:[^/:\s][^\s]*$/.test(repository);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (!valid)
|
|
26
|
+
throw new AppError(
|
|
27
|
+
"INVALID_REPOSITORY",
|
|
28
|
+
"\u8BF7\u8F93\u5165 HTTPS \u6216 SSH Git \u4ED3\u5E93\u5730\u5740\uFF1B\u8BF7\u4F7F\u7528\u4E3B\u673A\u5DF2\u6709\u7684\u8BA4\u8BC1\uFF0C\u4E0D\u8981\u5728\u5730\u5740\u4E2D\u586B\u5199\u5BC6\u7801\u3001\u4EE4\u724C\u6216\u67E5\u8BE2\u53C2\u6570",
|
|
29
|
+
400
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
function targetPath(path) {
|
|
33
|
+
if (!isAbsolute(path) || hasControl(path) || resolve(path) === dirname(resolve(path)))
|
|
34
|
+
throw new AppError("INVALID_PROJECT_PATH", "\u8BF7\u586B\u5199\u670D\u52A1\u5668\u4E0A\u7684\u7EDD\u5BF9\u76EE\u6807\u76EE\u5F55", 400);
|
|
35
|
+
try {
|
|
36
|
+
const target = join(realpathSync(dirname(resolve(path))), basename(resolve(path)));
|
|
37
|
+
accessSync(dirname(target), constants.W_OK | constants.X_OK);
|
|
38
|
+
return target;
|
|
39
|
+
} catch {
|
|
40
|
+
throw new AppError(
|
|
41
|
+
"CLONE_PARENT_UNAVAILABLE",
|
|
42
|
+
"\u76EE\u6807\u76EE\u5F55\u7684\u7236\u76EE\u5F55\u5FC5\u987B\u5DF2\u5B58\u5728\uFF0C\u4E14\u670D\u52A1\u9700\u8981\u5199\u5165\u548C\u8FDB\u5165\u6743\u9650",
|
|
43
|
+
409
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function checkEmpty(path) {
|
|
48
|
+
try {
|
|
49
|
+
const stat = lstatSync(path);
|
|
50
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || readdirSync(path).length)
|
|
51
|
+
throw new AppError(
|
|
52
|
+
"CLONE_TARGET_EXISTS",
|
|
53
|
+
"\u76EE\u6807\u76EE\u5F55\u5DF2\u5B58\u5728\u4E14\u975E\u7A7A\uFF0C\u6216\u4E0D\u662F\u666E\u901A\u76EE\u5F55\uFF1B\u8BF7\u9009\u62E9\u65B0\u7684\u7A7A\u76EE\u5F55\uFF0C\u4E0D\u4F1A\u8986\u76D6\u5DF2\u6709\u6587\u4EF6",
|
|
54
|
+
409
|
|
55
|
+
);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (error.code === "ENOENT") return;
|
|
58
|
+
if (error instanceof AppError) throw error;
|
|
59
|
+
throw new AppError(
|
|
60
|
+
"CLONE_TARGET_UNAVAILABLE",
|
|
61
|
+
"\u65E0\u6CD5\u68C0\u67E5\u76EE\u6807\u76EE\u5F55\uFF0C\u8BF7\u68C0\u67E5\u670D\u52A1\u5668\u8DEF\u5F84\u548C\u8BBF\u95EE\u6743\u9650",
|
|
62
|
+
409
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function failure(output, missingGit) {
|
|
67
|
+
if (missingGit) return "\u670D\u52A1\u5668\u672A\u5B89\u88C5 Git\uFF0C\u6216\u670D\u52A1\u7684 PATH \u4E2D\u627E\u4E0D\u5230 Git\u3002\u5B89\u88C5\u5E76\u914D\u7F6E\u540E\u91CD\u8BD5\u3002";
|
|
68
|
+
if (/authentication failed|could not read username|permission denied|publickey|terminal prompts disabled|credential|access denied|401|403/i.test(
|
|
69
|
+
output
|
|
70
|
+
))
|
|
71
|
+
return "Git \u8BA4\u8BC1\u5931\u8D25\u6216\u6CA1\u6709\u4ED3\u5E93\u8BBF\u95EE\u6743\u9650\u3002\u8BF7\u5728\u8FD0\u884C\u670D\u52A1\u7684\u4E3B\u673A\u4E0A\u914D\u7F6E Git \u51ED\u636E\u6216 SSH \u5BC6\u94A5\u540E\u91CD\u8BD5\u3002";
|
|
72
|
+
if (/host key verification failed/i.test(output))
|
|
73
|
+
return "SSH \u4E3B\u673A\u9A8C\u8BC1\u5931\u8D25\u3002\u8BF7\u5728\u8FD0\u884C\u670D\u52A1\u7684\u4E3B\u673A\u4E0A\u786E\u8BA4\u5E76\u914D\u7F6E\u4ED3\u5E93\u4E3B\u673A\u7684 SSH \u4FE1\u4EFB\u540E\u91CD\u8BD5\u3002";
|
|
74
|
+
if (/repository .*not found|does not appear to be a git repository|not a repository|404/i.test(
|
|
75
|
+
output
|
|
76
|
+
))
|
|
77
|
+
return "\u4ED3\u5E93\u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u8BBF\u95EE\u3002\u8BF7\u68C0\u67E5\u4ED3\u5E93\u5730\u5740\u548C\u4E3B\u673A\u4E0A\u7684 Git \u8BBF\u95EE\u6743\u9650\u3002";
|
|
78
|
+
if (/could not resolve|failed to connect|unable to access|connection|timed out|certificate/i.test(
|
|
79
|
+
output
|
|
80
|
+
))
|
|
81
|
+
return "\u65E0\u6CD5\u8FDE\u63A5 Git \u4ED3\u5E93\u3002\u8BF7\u68C0\u67E5\u670D\u52A1\u5668\u7F51\u7EDC\u3001\u4EE3\u7406\u548C\u8BC1\u4E66\u914D\u7F6E\u540E\u91CD\u8BD5\u3002";
|
|
82
|
+
if (/already exists and is not an empty directory/i.test(output))
|
|
83
|
+
return "\u76EE\u6807\u76EE\u5F55\u5DF2\u5305\u542B\u6587\u4EF6\uFF0CGit \u5DF2\u505C\u6B62\u514B\u9686\uFF1B\u4E0D\u4F1A\u8986\u76D6\u6216\u5220\u9664\u5DF2\u6709\u6587\u4EF6\u3002";
|
|
84
|
+
if (/no space left|disk quota/i.test(output))
|
|
85
|
+
return "\u670D\u52A1\u5668\u78C1\u76D8\u7A7A\u95F4\u4E0D\u8DB3\uFF0C\u8BF7\u91CA\u653E\u7A7A\u95F4\u540E\u4F7F\u7528\u65B0\u7684\u7A7A\u76EE\u5F55\u91CD\u8BD5\u3002";
|
|
86
|
+
return "Git \u514B\u9686\u5931\u8D25\u3002\u8BF7\u68C0\u67E5\u4ED3\u5E93\u5730\u5740\u3001\u4E3B\u673A\u4E0A\u7684 Git \u8BA4\u8BC1\u3001\u76EE\u5F55\u6743\u9650\u548C\u78C1\u76D8\u7A7A\u95F4\u540E\u91CD\u8BD5\u3002";
|
|
87
|
+
}
|
|
88
|
+
class ProjectClones {
|
|
89
|
+
constructor(register, registered) {
|
|
90
|
+
this.register = register;
|
|
91
|
+
this.registered = registered;
|
|
92
|
+
}
|
|
93
|
+
register;
|
|
94
|
+
registered;
|
|
95
|
+
jobs = /* @__PURE__ */ new Map();
|
|
96
|
+
closing = false;
|
|
97
|
+
get hasActive() {
|
|
98
|
+
return [...this.jobs.values()].some(active);
|
|
99
|
+
}
|
|
100
|
+
assertIdlePath(path) {
|
|
101
|
+
if (!this.hasActive) return;
|
|
102
|
+
let canonical = resolve(path);
|
|
103
|
+
try {
|
|
104
|
+
canonical = realpathSync(path);
|
|
105
|
+
} catch {
|
|
106
|
+
try {
|
|
107
|
+
canonical = join(realpathSync(dirname(canonical)), basename(canonical));
|
|
108
|
+
} catch {
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if ([...this.jobs.values()].some(
|
|
112
|
+
(job) => active(job) && (containsPath(job.view.path, canonical) || containsPath(canonical, job.view.path))
|
|
113
|
+
))
|
|
114
|
+
throw new AppError("CLONE_IN_PROGRESS", "\u8BE5\u76EE\u5F55\u6B63\u5728\u514B\u9686\uFF0C\u8BF7\u7B49\u5F85\u5B8C\u6210\u6216\u53D6\u6D88\u540E\u518D\u64CD\u4F5C", 409);
|
|
115
|
+
}
|
|
116
|
+
start(input) {
|
|
117
|
+
const parsed = cloneProjectSchema.parse(input);
|
|
118
|
+
validateRepository(parsed.repository);
|
|
119
|
+
const fingerprint = createHash("sha256").update(JSON.stringify(parsed)).digest("hex");
|
|
120
|
+
const previous = this.jobs.get(parsed.clientOperationId);
|
|
121
|
+
if (previous) {
|
|
122
|
+
if (previous.fingerprint !== fingerprint)
|
|
123
|
+
throw new AppError("CLONE_ID_CONFLICT", "\u8BE5\u64CD\u4F5C ID \u5DF2\u7528\u4E8E\u5176\u4ED6\u514B\u9686\u8BF7\u6C42", 409);
|
|
124
|
+
return this.get(previous.view.id);
|
|
125
|
+
}
|
|
126
|
+
if (this.closing) throw new AppError("GATEWAY_STOPPING", "\u670D\u52A1\u6B63\u5728\u505C\u6B62\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5", 503);
|
|
127
|
+
const path = targetPath(parsed.path);
|
|
128
|
+
this.assertIdlePath(path);
|
|
129
|
+
if (this.registered(path))
|
|
130
|
+
throw new AppError("PROJECT_EXISTS", "\u8BE5\u76EE\u5F55\u5DF2\u6DFB\u52A0\u4E3A\u9879\u76EE\uFF0C\u8BF7\u4F7F\u7528\u5DF2\u6709\u9879\u76EE\u6216\u9009\u62E9\u5176\u4ED6\u76EE\u5F55", 409);
|
|
131
|
+
checkEmpty(path);
|
|
132
|
+
if ([...this.jobs.values()].filter(active).length >= 4)
|
|
133
|
+
throw new AppError("CLONE_LIMIT", "\u5DF2\u6709\u591A\u4E2A\u4ED3\u5E93\u6B63\u5728\u514B\u9686\uFF0C\u8BF7\u7B49\u5F85\u5B8C\u6210\u540E\u91CD\u8BD5", 409);
|
|
134
|
+
if (this.jobs.size >= 100) {
|
|
135
|
+
const oldest = [...this.jobs].find(([, job2]) => !active(job2));
|
|
136
|
+
if (oldest) this.jobs.delete(oldest[0]);
|
|
137
|
+
}
|
|
138
|
+
const job = {
|
|
139
|
+
view: {
|
|
140
|
+
id: parsed.clientOperationId,
|
|
141
|
+
path,
|
|
142
|
+
name: parsed.name,
|
|
143
|
+
status: "cloning",
|
|
144
|
+
progress: "\u6B63\u5728\u8FDE\u63A5\u4ED3\u5E93\u2026"
|
|
145
|
+
},
|
|
146
|
+
fingerprint,
|
|
147
|
+
done: Promise.resolve()
|
|
148
|
+
};
|
|
149
|
+
this.jobs.set(job.view.id, job);
|
|
150
|
+
job.done = this.run(job, parsed.repository);
|
|
151
|
+
return this.get(job.view.id);
|
|
152
|
+
}
|
|
153
|
+
get(id) {
|
|
154
|
+
const job = this.jobs.get(id);
|
|
155
|
+
if (!job)
|
|
156
|
+
throw new AppError(
|
|
157
|
+
"CLONE_NOT_FOUND",
|
|
158
|
+
"\u672A\u627E\u5230\u514B\u9686\u8BB0\u5F55\uFF1B\u670D\u52A1\u91CD\u542F\u540E\u8BF7\u68C0\u67E5\u76EE\u6807\u76EE\u5F55\uFF0C\u518D\u6DFB\u52A0\u5DF2\u6709\u76EE\u5F55\u6216\u4F7F\u7528\u65B0\u7684\u7A7A\u76EE\u5F55\u91CD\u8BD5",
|
|
159
|
+
404
|
|
160
|
+
);
|
|
161
|
+
return structuredClone(job.view);
|
|
162
|
+
}
|
|
163
|
+
cancel(id) {
|
|
164
|
+
const job = this.jobs.get(id);
|
|
165
|
+
this.get(id);
|
|
166
|
+
if (job && active(job) && job.view.status !== "cancelling") {
|
|
167
|
+
job.view.status = "cancelling";
|
|
168
|
+
job.view.progress = "\u6B63\u5728\u53D6\u6D88\uFF0C\u7B49\u5F85 Git \u9000\u51FA\u2026";
|
|
169
|
+
this.signal(job, "SIGTERM");
|
|
170
|
+
}
|
|
171
|
+
return this.get(id);
|
|
172
|
+
}
|
|
173
|
+
retryRegistration(id) {
|
|
174
|
+
const job = this.jobs.get(id);
|
|
175
|
+
this.get(id);
|
|
176
|
+
if (job?.view.status === "registration-failed") this.finishRegistration(job);
|
|
177
|
+
return this.get(id);
|
|
178
|
+
}
|
|
179
|
+
finishRegistration(job) {
|
|
180
|
+
try {
|
|
181
|
+
job.view.project = this.register({ path: job.view.path, name: job.view.name });
|
|
182
|
+
job.view.status = "completed";
|
|
183
|
+
job.view.progress = "\u514B\u9686\u5B8C\u6210\uFF0C\u9879\u76EE\u5DF2\u6DFB\u52A0";
|
|
184
|
+
delete job.view.error;
|
|
185
|
+
} catch {
|
|
186
|
+
job.view.status = "registration-failed";
|
|
187
|
+
job.view.progress = "\u514B\u9686\u5DF2\u5B8C\u6210\uFF0C\u9879\u76EE\u5C1A\u672A\u6DFB\u52A0";
|
|
188
|
+
job.view.error = "\u9879\u76EE\u6CE8\u518C\u5931\u8D25\u3002\u5DF2\u514B\u9686\u7684\u6587\u4EF6\u4FDD\u7559\u5728\u76EE\u6807\u76EE\u5F55\uFF1B\u53EF\u91CD\u8BD5\u6DFB\u52A0\u9879\u76EE\uFF0C\u65E0\u9700\u518D\u6B21\u514B\u9686\uFF0C\u4E5F\u53EF\u901A\u8FC7\u201C\u5DF2\u6709\u76EE\u5F55\u201D\u6DFB\u52A0\u3002";
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
signal(job, signal) {
|
|
192
|
+
if (!job.child?.pid) return;
|
|
193
|
+
try {
|
|
194
|
+
if (process.platform === "win32") job.child.kill(signal);
|
|
195
|
+
else process.kill(-job.child.pid, signal);
|
|
196
|
+
} catch {
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async run(job, repository) {
|
|
200
|
+
let output = "";
|
|
201
|
+
let pending = "";
|
|
202
|
+
let missingGit = false;
|
|
203
|
+
let timedOut = false;
|
|
204
|
+
const sshCommand = process.env.GIT_SSH_COMMAND ?? (process.env.GIT_SSH ? `'${process.env.GIT_SSH.replaceAll("'", "'\\''")}'` : "ssh");
|
|
205
|
+
const child = spawn(
|
|
206
|
+
"git",
|
|
207
|
+
[
|
|
208
|
+
"-c",
|
|
209
|
+
"protocol.allow=never",
|
|
210
|
+
"-c",
|
|
211
|
+
"protocol.https.allow=always",
|
|
212
|
+
"-c",
|
|
213
|
+
"protocol.ssh.allow=always",
|
|
214
|
+
"clone",
|
|
215
|
+
"--progress",
|
|
216
|
+
"--",
|
|
217
|
+
repository,
|
|
218
|
+
job.view.path
|
|
219
|
+
],
|
|
220
|
+
{
|
|
221
|
+
detached: process.platform !== "win32",
|
|
222
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
223
|
+
env: {
|
|
224
|
+
...process.env,
|
|
225
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
226
|
+
GCM_INTERACTIVE: "never",
|
|
227
|
+
GIT_SSH_COMMAND: `${sshCommand} -oBatchMode=yes -oStrictHostKeyChecking=yes`,
|
|
228
|
+
LC_ALL: "C"
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
job.child = child;
|
|
233
|
+
const read = (chunk) => {
|
|
234
|
+
output = (output + chunk.toString()).slice(-16384);
|
|
235
|
+
pending = (pending + chunk.toString()).slice(-4096);
|
|
236
|
+
const lines = pending.split(/[\r\n]/);
|
|
237
|
+
pending = lines.pop() ?? "";
|
|
238
|
+
for (const line of lines) {
|
|
239
|
+
const match = /^(?:remote: )?(Receiving objects|Resolving deltas|Updating files|Checking out files|Counting objects|Compressing objects):\s+(\d{1,3})%/.exec(
|
|
240
|
+
line
|
|
241
|
+
);
|
|
242
|
+
if (!match || job.view.status !== "cloning") continue;
|
|
243
|
+
const labels = {
|
|
244
|
+
"Receiving objects": "\u6B63\u5728\u63A5\u6536\u5BF9\u8C61",
|
|
245
|
+
"Resolving deltas": "\u6B63\u5728\u89E3\u6790\u5DEE\u5F02",
|
|
246
|
+
"Updating files": "\u6B63\u5728\u68C0\u51FA\u6587\u4EF6",
|
|
247
|
+
"Checking out files": "\u6B63\u5728\u68C0\u51FA\u6587\u4EF6",
|
|
248
|
+
"Counting objects": "\u6B63\u5728\u7EDF\u8BA1\u5BF9\u8C61",
|
|
249
|
+
"Compressing objects": "\u6B63\u5728\u538B\u7F29\u5BF9\u8C61"
|
|
250
|
+
};
|
|
251
|
+
job.view.progress = labels[match[1]];
|
|
252
|
+
job.view.percent = Math.min(100, Number(match[2]));
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
child.stdout?.on("data", read);
|
|
256
|
+
child.stderr?.on("data", read);
|
|
257
|
+
const timeout = setTimeout(() => {
|
|
258
|
+
timedOut = true;
|
|
259
|
+
this.cancel(job.view.id);
|
|
260
|
+
}, 30 * 6e4);
|
|
261
|
+
timeout.unref();
|
|
262
|
+
const escalation = setInterval(() => {
|
|
263
|
+
if (job.view.status === "cancelling") this.signal(job, "SIGKILL");
|
|
264
|
+
}, 3e3);
|
|
265
|
+
escalation.unref();
|
|
266
|
+
const code = await new Promise((done) => {
|
|
267
|
+
child.once("error", (error) => {
|
|
268
|
+
missingGit = error.code === "ENOENT";
|
|
269
|
+
});
|
|
270
|
+
child.once("close", done);
|
|
271
|
+
});
|
|
272
|
+
clearTimeout(timeout);
|
|
273
|
+
clearInterval(escalation);
|
|
274
|
+
job.child = void 0;
|
|
275
|
+
delete job.view.percent;
|
|
276
|
+
if (job.view.status === "cancelling") {
|
|
277
|
+
job.view.status = timedOut ? "failed" : "cancelled";
|
|
278
|
+
job.view.progress = timedOut ? "\u514B\u9686\u8D85\u65F6\uFF0C\u5DF2\u505C\u6B62" : "\u5DF2\u53D6\u6D88\u514B\u9686";
|
|
279
|
+
job.view.error = `${timedOut ? "\u514B\u9686\u8D85\u8FC7 30 \u5206\u949F\uFF0C\u5DF2\u505C\u6B62\u3002" : ""}\u672A\u6DFB\u52A0\u9879\u76EE\uFF0C\u4E5F\u672A\u5220\u9664\u4EFB\u4F55\u6587\u4EF6\u3002\u76EE\u6807\u76EE\u5F55\u53EF\u80FD\u6B8B\u7559\u6587\u4EF6\uFF0C\u8BF7\u68C0\u67E5\u540E\u4F7F\u7528\u65B0\u7684\u7A7A\u76EE\u5F55\u91CD\u8BD5\u3002`;
|
|
280
|
+
} else if (code !== 0) {
|
|
281
|
+
job.view.status = "failed";
|
|
282
|
+
job.view.progress = "\u514B\u9686\u5931\u8D25";
|
|
283
|
+
job.view.error = `${failure(output, missingGit)} \u672A\u6DFB\u52A0\u9879\u76EE\uFF1B\u76EE\u6807\u76EE\u5F55\u53EF\u80FD\u6B8B\u7559\u6587\u4EF6\uFF0C\u8BF7\u68C0\u67E5\u540E\u4F7F\u7528\u65B0\u7684\u7A7A\u76EE\u5F55\u91CD\u8BD5\u3002`;
|
|
284
|
+
} else this.finishRegistration(job);
|
|
285
|
+
}
|
|
286
|
+
async close() {
|
|
287
|
+
this.closing = true;
|
|
288
|
+
for (const job of this.jobs.values()) if (active(job)) this.cancel(job.view.id);
|
|
289
|
+
await Promise.all([...this.jobs.values()].map((job) => job.done));
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
export {
|
|
293
|
+
ProjectClones,
|
|
294
|
+
validateRepository
|
|
295
|
+
};
|
|
296
|
+
//# sourceMappingURL=project-clones.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/server/project-clones.ts"],
|
|
4
|
+
"sourcesContent": ["import { spawn, type ChildProcess } from 'node:child_process';\nimport { createHash } from 'node:crypto';\nimport { accessSync, constants, lstatSync, readdirSync, realpathSync } from 'node:fs';\nimport { basename, dirname, isAbsolute, join, resolve } from 'node:path';\nimport { AppError } from '../shared/errors.js';\nimport type {\n CloneProjectInput,\n CreateProjectInput,\n Project,\n ProjectClone,\n} from '../shared/types.js';\nimport { cloneProjectSchema } from '../shared/validation.js';\nimport { containsPath } from './projects.js';\n\ninterface Job {\n view: ProjectClone;\n fingerprint: string;\n child?: ChildProcess;\n done: Promise<void>;\n}\nconst active = (job: Job) => ['cloning', 'cancelling'].includes(job.view.status);\nconst hasControl = (value: string) =>\n [...value].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127);\n\n/** Only remote Git transports; never pass a local path, option, or remote helper to Git. */\nexport function validateRepository(repository: string): void {\n let valid = false;\n if (!hasControl(repository) && !/[\\s\\\\]/.test(repository)) {\n if (/^(https|ssh):\\/\\//.test(repository)) {\n try {\n const url = new URL(repository);\n valid = Boolean(\n url.hostname &&\n !url.hostname.startsWith('-') &&\n url.pathname.length > 1 &&\n !url.password &&\n !url.search &&\n !url.hash &&\n (url.protocol === 'ssh:'\n ? /^(?:[a-zA-Z0-9_][\\w.-]*)?$/.test(url.username)\n : !url.username),\n );\n } catch {\n /* Report a fixed message, never the supplied URL. */\n }\n } else {\n valid = /^(?:[a-zA-Z0-9_][\\w.-]*@)?[a-zA-Z0-9][\\w.-]*:[^/:\\s][^\\s]*$/.test(repository);\n }\n }\n if (!valid)\n throw new AppError(\n 'INVALID_REPOSITORY',\n '\u8BF7\u8F93\u5165 HTTPS \u6216 SSH Git \u4ED3\u5E93\u5730\u5740\uFF1B\u8BF7\u4F7F\u7528\u4E3B\u673A\u5DF2\u6709\u7684\u8BA4\u8BC1\uFF0C\u4E0D\u8981\u5728\u5730\u5740\u4E2D\u586B\u5199\u5BC6\u7801\u3001\u4EE4\u724C\u6216\u67E5\u8BE2\u53C2\u6570',\n 400,\n );\n}\n\nfunction targetPath(path: string): string {\n if (!isAbsolute(path) || hasControl(path) || resolve(path) === dirname(resolve(path)))\n throw new AppError('INVALID_PROJECT_PATH', '\u8BF7\u586B\u5199\u670D\u52A1\u5668\u4E0A\u7684\u7EDD\u5BF9\u76EE\u6807\u76EE\u5F55', 400);\n try {\n const target = join(realpathSync(dirname(resolve(path))), basename(resolve(path)));\n accessSync(dirname(target), constants.W_OK | constants.X_OK);\n return target;\n } catch {\n throw new AppError(\n 'CLONE_PARENT_UNAVAILABLE',\n '\u76EE\u6807\u76EE\u5F55\u7684\u7236\u76EE\u5F55\u5FC5\u987B\u5DF2\u5B58\u5728\uFF0C\u4E14\u670D\u52A1\u9700\u8981\u5199\u5165\u548C\u8FDB\u5165\u6743\u9650',\n 409,\n );\n }\n}\n\nfunction checkEmpty(path: string): void {\n try {\n const stat = lstatSync(path);\n if (!stat.isDirectory() || stat.isSymbolicLink() || readdirSync(path).length)\n throw new AppError(\n 'CLONE_TARGET_EXISTS',\n '\u76EE\u6807\u76EE\u5F55\u5DF2\u5B58\u5728\u4E14\u975E\u7A7A\uFF0C\u6216\u4E0D\u662F\u666E\u901A\u76EE\u5F55\uFF1B\u8BF7\u9009\u62E9\u65B0\u7684\u7A7A\u76EE\u5F55\uFF0C\u4E0D\u4F1A\u8986\u76D6\u5DF2\u6709\u6587\u4EF6',\n 409,\n );\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return;\n if (error instanceof AppError) throw error;\n throw new AppError(\n 'CLONE_TARGET_UNAVAILABLE',\n '\u65E0\u6CD5\u68C0\u67E5\u76EE\u6807\u76EE\u5F55\uFF0C\u8BF7\u68C0\u67E5\u670D\u52A1\u5668\u8DEF\u5F84\u548C\u8BBF\u95EE\u6743\u9650',\n 409,\n );\n }\n}\n\nfunction failure(output: string, missingGit: boolean): string {\n if (missingGit) return '\u670D\u52A1\u5668\u672A\u5B89\u88C5 Git\uFF0C\u6216\u670D\u52A1\u7684 PATH \u4E2D\u627E\u4E0D\u5230 Git\u3002\u5B89\u88C5\u5E76\u914D\u7F6E\u540E\u91CD\u8BD5\u3002';\n if (\n /authentication failed|could not read username|permission denied|publickey|terminal prompts disabled|credential|access denied|401|403/i.test(\n output,\n )\n )\n return 'Git \u8BA4\u8BC1\u5931\u8D25\u6216\u6CA1\u6709\u4ED3\u5E93\u8BBF\u95EE\u6743\u9650\u3002\u8BF7\u5728\u8FD0\u884C\u670D\u52A1\u7684\u4E3B\u673A\u4E0A\u914D\u7F6E Git \u51ED\u636E\u6216 SSH \u5BC6\u94A5\u540E\u91CD\u8BD5\u3002';\n if (/host key verification failed/i.test(output))\n return 'SSH \u4E3B\u673A\u9A8C\u8BC1\u5931\u8D25\u3002\u8BF7\u5728\u8FD0\u884C\u670D\u52A1\u7684\u4E3B\u673A\u4E0A\u786E\u8BA4\u5E76\u914D\u7F6E\u4ED3\u5E93\u4E3B\u673A\u7684 SSH \u4FE1\u4EFB\u540E\u91CD\u8BD5\u3002';\n if (\n /repository .*not found|does not appear to be a git repository|not a repository|404/i.test(\n output,\n )\n )\n return '\u4ED3\u5E93\u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u8BBF\u95EE\u3002\u8BF7\u68C0\u67E5\u4ED3\u5E93\u5730\u5740\u548C\u4E3B\u673A\u4E0A\u7684 Git \u8BBF\u95EE\u6743\u9650\u3002';\n if (\n /could not resolve|failed to connect|unable to access|connection|timed out|certificate/i.test(\n output,\n )\n )\n return '\u65E0\u6CD5\u8FDE\u63A5 Git \u4ED3\u5E93\u3002\u8BF7\u68C0\u67E5\u670D\u52A1\u5668\u7F51\u7EDC\u3001\u4EE3\u7406\u548C\u8BC1\u4E66\u914D\u7F6E\u540E\u91CD\u8BD5\u3002';\n if (/already exists and is not an empty directory/i.test(output))\n return '\u76EE\u6807\u76EE\u5F55\u5DF2\u5305\u542B\u6587\u4EF6\uFF0CGit \u5DF2\u505C\u6B62\u514B\u9686\uFF1B\u4E0D\u4F1A\u8986\u76D6\u6216\u5220\u9664\u5DF2\u6709\u6587\u4EF6\u3002';\n if (/no space left|disk quota/i.test(output))\n return '\u670D\u52A1\u5668\u78C1\u76D8\u7A7A\u95F4\u4E0D\u8DB3\uFF0C\u8BF7\u91CA\u653E\u7A7A\u95F4\u540E\u4F7F\u7528\u65B0\u7684\u7A7A\u76EE\u5F55\u91CD\u8BD5\u3002';\n return 'Git \u514B\u9686\u5931\u8D25\u3002\u8BF7\u68C0\u67E5\u4ED3\u5E93\u5730\u5740\u3001\u4E3B\u673A\u4E0A\u7684 Git \u8BA4\u8BC1\u3001\u76EE\u5F55\u6743\u9650\u548C\u78C1\u76D8\u7A7A\u95F4\u540E\u91CD\u8BD5\u3002';\n}\n\n/** Transient jobs never persist repository URLs, credentials, or raw Git output. */\nexport class ProjectClones {\n private readonly jobs = new Map<string, Job>();\n private closing = false;\n constructor(\n private readonly register: (input: CreateProjectInput) => Project,\n private readonly registered: (path: string) => boolean,\n ) {}\n\n get hasActive(): boolean {\n return [...this.jobs.values()].some(active);\n }\n\n assertIdlePath(path: string): void {\n if (!this.hasActive) return;\n let canonical = resolve(path);\n try {\n canonical = realpathSync(path);\n } catch {\n try {\n canonical = join(realpathSync(dirname(canonical)), basename(canonical));\n } catch {\n /* Normal registration validation reports unavailable paths. */\n }\n }\n if (\n [...this.jobs.values()].some(\n (job) =>\n active(job) &&\n (containsPath(job.view.path, canonical) || containsPath(canonical, job.view.path)),\n )\n )\n throw new AppError('CLONE_IN_PROGRESS', '\u8BE5\u76EE\u5F55\u6B63\u5728\u514B\u9686\uFF0C\u8BF7\u7B49\u5F85\u5B8C\u6210\u6216\u53D6\u6D88\u540E\u518D\u64CD\u4F5C', 409);\n }\n\n start(input: CloneProjectInput): ProjectClone {\n const parsed = cloneProjectSchema.parse(input);\n validateRepository(parsed.repository);\n const fingerprint = createHash('sha256').update(JSON.stringify(parsed)).digest('hex');\n const previous = this.jobs.get(parsed.clientOperationId);\n if (previous) {\n if (previous.fingerprint !== fingerprint)\n throw new AppError('CLONE_ID_CONFLICT', '\u8BE5\u64CD\u4F5C ID \u5DF2\u7528\u4E8E\u5176\u4ED6\u514B\u9686\u8BF7\u6C42', 409);\n return this.get(previous.view.id);\n }\n if (this.closing) throw new AppError('GATEWAY_STOPPING', '\u670D\u52A1\u6B63\u5728\u505C\u6B62\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5', 503);\n const path = targetPath(parsed.path);\n this.assertIdlePath(path);\n if (this.registered(path))\n throw new AppError('PROJECT_EXISTS', '\u8BE5\u76EE\u5F55\u5DF2\u6DFB\u52A0\u4E3A\u9879\u76EE\uFF0C\u8BF7\u4F7F\u7528\u5DF2\u6709\u9879\u76EE\u6216\u9009\u62E9\u5176\u4ED6\u76EE\u5F55', 409);\n checkEmpty(path);\n if ([...this.jobs.values()].filter(active).length >= 4)\n throw new AppError('CLONE_LIMIT', '\u5DF2\u6709\u591A\u4E2A\u4ED3\u5E93\u6B63\u5728\u514B\u9686\uFF0C\u8BF7\u7B49\u5F85\u5B8C\u6210\u540E\u91CD\u8BD5', 409);\n // Bound transient history without evicting a running process.\n if (this.jobs.size >= 100) {\n const oldest = [...this.jobs].find(([, job]) => !active(job));\n if (oldest) this.jobs.delete(oldest[0]);\n }\n const job: Job = {\n view: {\n id: parsed.clientOperationId,\n path,\n name: parsed.name,\n status: 'cloning',\n progress: '\u6B63\u5728\u8FDE\u63A5\u4ED3\u5E93\u2026',\n },\n fingerprint,\n done: Promise.resolve(),\n };\n this.jobs.set(job.view.id, job);\n job.done = this.run(job, parsed.repository);\n return this.get(job.view.id);\n }\n\n get(id: string): ProjectClone {\n const job = this.jobs.get(id);\n if (!job)\n throw new AppError(\n 'CLONE_NOT_FOUND',\n '\u672A\u627E\u5230\u514B\u9686\u8BB0\u5F55\uFF1B\u670D\u52A1\u91CD\u542F\u540E\u8BF7\u68C0\u67E5\u76EE\u6807\u76EE\u5F55\uFF0C\u518D\u6DFB\u52A0\u5DF2\u6709\u76EE\u5F55\u6216\u4F7F\u7528\u65B0\u7684\u7A7A\u76EE\u5F55\u91CD\u8BD5',\n 404,\n );\n return structuredClone(job.view);\n }\n\n cancel(id: string): ProjectClone {\n const job = this.jobs.get(id);\n this.get(id);\n if (job && active(job) && job.view.status !== 'cancelling') {\n job.view.status = 'cancelling';\n job.view.progress = '\u6B63\u5728\u53D6\u6D88\uFF0C\u7B49\u5F85 Git \u9000\u51FA\u2026';\n this.signal(job, 'SIGTERM');\n }\n return this.get(id);\n }\n\n retryRegistration(id: string): ProjectClone {\n const job = this.jobs.get(id);\n this.get(id);\n if (job?.view.status === 'registration-failed') this.finishRegistration(job);\n return this.get(id);\n }\n\n private finishRegistration(job: Job): void {\n try {\n job.view.project = this.register({ path: job.view.path, name: job.view.name });\n job.view.status = 'completed';\n job.view.progress = '\u514B\u9686\u5B8C\u6210\uFF0C\u9879\u76EE\u5DF2\u6DFB\u52A0';\n delete job.view.error;\n } catch {\n job.view.status = 'registration-failed';\n job.view.progress = '\u514B\u9686\u5DF2\u5B8C\u6210\uFF0C\u9879\u76EE\u5C1A\u672A\u6DFB\u52A0';\n job.view.error =\n '\u9879\u76EE\u6CE8\u518C\u5931\u8D25\u3002\u5DF2\u514B\u9686\u7684\u6587\u4EF6\u4FDD\u7559\u5728\u76EE\u6807\u76EE\u5F55\uFF1B\u53EF\u91CD\u8BD5\u6DFB\u52A0\u9879\u76EE\uFF0C\u65E0\u9700\u518D\u6B21\u514B\u9686\uFF0C\u4E5F\u53EF\u901A\u8FC7\u201C\u5DF2\u6709\u76EE\u5F55\u201D\u6DFB\u52A0\u3002';\n }\n }\n\n private signal(job: Job, signal: NodeJS.Signals): void {\n if (!job.child?.pid) return;\n try {\n if (process.platform === 'win32') job.child.kill(signal);\n else process.kill(-job.child.pid, signal);\n } catch {\n /* Process may have already exited. */\n }\n }\n\n private async run(job: Job, repository: string): Promise<void> {\n let output = '';\n let pending = '';\n let missingGit = false;\n let timedOut = false;\n const sshCommand =\n process.env.GIT_SSH_COMMAND ??\n (process.env.GIT_SSH ? `'${process.env.GIT_SSH.replaceAll(\"'\", \"'\\\\''\")}'` : 'ssh');\n const child = spawn(\n 'git',\n [\n '-c',\n 'protocol.allow=never',\n '-c',\n 'protocol.https.allow=always',\n '-c',\n 'protocol.ssh.allow=always',\n 'clone',\n '--progress',\n '--',\n repository,\n job.view.path,\n ],\n {\n detached: process.platform !== 'win32',\n stdio: ['ignore', 'pipe', 'pipe'],\n env: {\n ...process.env,\n GIT_TERMINAL_PROMPT: '0',\n GCM_INTERACTIVE: 'never',\n GIT_SSH_COMMAND: `${sshCommand} -oBatchMode=yes -oStrictHostKeyChecking=yes`,\n LC_ALL: 'C',\n },\n },\n );\n job.child = child;\n const read = (chunk: Buffer) => {\n output = (output + chunk.toString()).slice(-16_384);\n pending = (pending + chunk.toString()).slice(-4096);\n const lines = pending.split(/[\\r\\n]/);\n pending = lines.pop() ?? '';\n for (const line of lines) {\n const match =\n /^(?:remote: )?(Receiving objects|Resolving deltas|Updating files|Checking out files|Counting objects|Compressing objects):\\s+(\\d{1,3})%/.exec(\n line,\n );\n if (!match || job.view.status !== 'cloning') continue;\n const labels: Record<string, string> = {\n 'Receiving objects': '\u6B63\u5728\u63A5\u6536\u5BF9\u8C61',\n 'Resolving deltas': '\u6B63\u5728\u89E3\u6790\u5DEE\u5F02',\n 'Updating files': '\u6B63\u5728\u68C0\u51FA\u6587\u4EF6',\n 'Checking out files': '\u6B63\u5728\u68C0\u51FA\u6587\u4EF6',\n 'Counting objects': '\u6B63\u5728\u7EDF\u8BA1\u5BF9\u8C61',\n 'Compressing objects': '\u6B63\u5728\u538B\u7F29\u5BF9\u8C61',\n };\n job.view.progress = labels[match[1]!]!;\n job.view.percent = Math.min(100, Number(match[2]));\n }\n };\n child.stdout?.on('data', read);\n child.stderr?.on('data', read);\n const timeout = setTimeout(() => {\n timedOut = true;\n this.cancel(job.view.id);\n }, 30 * 60_000);\n timeout.unref();\n const escalation = setInterval(() => {\n if (job.view.status === 'cancelling') this.signal(job, 'SIGKILL');\n }, 3000);\n escalation.unref();\n const code = await new Promise<number | null>((done) => {\n child.once('error', (error: NodeJS.ErrnoException) => {\n missingGit = error.code === 'ENOENT';\n });\n child.once('close', done);\n });\n clearTimeout(timeout);\n clearInterval(escalation);\n job.child = undefined;\n delete job.view.percent;\n if (job.view.status === 'cancelling') {\n job.view.status = timedOut ? 'failed' : 'cancelled';\n job.view.progress = timedOut ? '\u514B\u9686\u8D85\u65F6\uFF0C\u5DF2\u505C\u6B62' : '\u5DF2\u53D6\u6D88\u514B\u9686';\n job.view.error = `${timedOut ? '\u514B\u9686\u8D85\u8FC7 30 \u5206\u949F\uFF0C\u5DF2\u505C\u6B62\u3002' : ''}\u672A\u6DFB\u52A0\u9879\u76EE\uFF0C\u4E5F\u672A\u5220\u9664\u4EFB\u4F55\u6587\u4EF6\u3002\u76EE\u6807\u76EE\u5F55\u53EF\u80FD\u6B8B\u7559\u6587\u4EF6\uFF0C\u8BF7\u68C0\u67E5\u540E\u4F7F\u7528\u65B0\u7684\u7A7A\u76EE\u5F55\u91CD\u8BD5\u3002`;\n } else if (code !== 0) {\n job.view.status = 'failed';\n job.view.progress = '\u514B\u9686\u5931\u8D25';\n job.view.error = `${failure(output, missingGit)} \u672A\u6DFB\u52A0\u9879\u76EE\uFF1B\u76EE\u6807\u76EE\u5F55\u53EF\u80FD\u6B8B\u7559\u6587\u4EF6\uFF0C\u8BF7\u68C0\u67E5\u540E\u4F7F\u7528\u65B0\u7684\u7A7A\u76EE\u5F55\u91CD\u8BD5\u3002`;\n } else this.finishRegistration(job);\n }\n\n async close(): Promise<void> {\n this.closing = true;\n for (const job of this.jobs.values()) if (active(job)) this.cancel(job.view.id);\n await Promise.all([...this.jobs.values()].map((job) => job.done));\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["job"]
|
|
7
|
+
}
|
package/dist/server/service.js
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
import { EventHub } from "./events.js";
|
|
17
17
|
import { TurnUsageStore } from "./db/turn-usage.js";
|
|
18
18
|
import { Projection, isActive, isTerminal } from "./projection.js";
|
|
19
|
+
import { ProjectClones } from "./project-clones.js";
|
|
19
20
|
import { renderThreadExport } from "./thread-export.js";
|
|
20
21
|
import { readLocalImage } from "./images.js";
|
|
21
22
|
import { temporaryFileRoots } from "./local-files.js";
|
|
@@ -32,6 +33,15 @@ class GatewayService {
|
|
|
32
33
|
this.projects,
|
|
33
34
|
options.workspacesRoot
|
|
34
35
|
);
|
|
36
|
+
this.projectClones = new ProjectClones(
|
|
37
|
+
(input) => {
|
|
38
|
+
this.workspaces.assertProjectDirectory(input.path);
|
|
39
|
+
const project = this.projects.add(input);
|
|
40
|
+
this.events.publishProjects(this.projects.list());
|
|
41
|
+
return project;
|
|
42
|
+
},
|
|
43
|
+
(path) => this.projects.list().some((project) => project.canonicalRoot === path)
|
|
44
|
+
);
|
|
35
45
|
this.codex = options.codex;
|
|
36
46
|
this.shell = new ShellService(this.codex);
|
|
37
47
|
this.events = new EventHub(options.limits?.eventBytes);
|
|
@@ -41,6 +51,7 @@ class GatewayService {
|
|
|
41
51
|
this.removeListener = this.codex.onEvent((event) => this.consume(event));
|
|
42
52
|
}
|
|
43
53
|
options;
|
|
54
|
+
projectClones;
|
|
44
55
|
shell;
|
|
45
56
|
events;
|
|
46
57
|
projection;
|
|
@@ -96,6 +107,7 @@ class GatewayService {
|
|
|
96
107
|
this.maintenance = value;
|
|
97
108
|
}
|
|
98
109
|
prepareUpdate() {
|
|
110
|
+
if (this.projectClones.hasActive) return { ready: false, reason: "\u7B49\u5F85\u9879\u76EE\u514B\u9686\u5B8C\u6210\u6216\u53D6\u6D88" };
|
|
99
111
|
if (this.shell.active()) return { ready: false, reason: "\u7B49\u5F85 Shell \u547D\u4EE4\u7ED3\u675F" };
|
|
100
112
|
if (this.inFlightOperations.size || this.resumes.size || this.pendingThreadCreations.size)
|
|
101
113
|
return { ready: false, reason: "\u7B49\u5F85\u6B63\u5728\u63D0\u4EA4\u6216\u6062\u590D\u7684\u64CD\u4F5C\u5B8C\u6210" };
|
|
@@ -110,10 +122,17 @@ class GatewayService {
|
|
|
110
122
|
}
|
|
111
123
|
addProject(input) {
|
|
112
124
|
this.workspaces.assertProjectDirectory(input.path);
|
|
125
|
+
this.projectClones.assertIdlePath(input.path);
|
|
113
126
|
const project = this.projects.add(input);
|
|
114
127
|
this.events.publishProjects(this.projects.list());
|
|
115
128
|
return project;
|
|
116
129
|
}
|
|
130
|
+
cloneProject(input) {
|
|
131
|
+
if (this.maintenance || !this.accepting)
|
|
132
|
+
throw new AppError("GATEWAY_STOPPING", "\u670D\u52A1\u6B63\u5728\u5347\u7EA7\u6216\u505C\u6B62\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5", 503);
|
|
133
|
+
this.workspaces.assertProjectDirectory(input.path);
|
|
134
|
+
return this.projectClones.start(input);
|
|
135
|
+
}
|
|
117
136
|
renameProject(projectId, input) {
|
|
118
137
|
const project = this.projects.rename(projectId, input);
|
|
119
138
|
this.events.publishProjects(this.projects.list());
|
|
@@ -855,6 +874,7 @@ class GatewayService {
|
|
|
855
874
|
}
|
|
856
875
|
async shutdown() {
|
|
857
876
|
this.accepting = false;
|
|
877
|
+
await this.projectClones.close();
|
|
858
878
|
this.store.recover();
|
|
859
879
|
this.projection.runtimeReset();
|
|
860
880
|
this.removeListener();
|