@covro/covro 0.2.1-dev.5 → 0.2.1-dev.7

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.
Files changed (133) hide show
  1. package/README.md +12 -4
  2. package/README.zh-CN.md +9 -1
  3. package/dist/backups/factory.js +22 -0
  4. package/dist/backups/factory.js.map +7 -0
  5. package/dist/backups/lock.js +22 -0
  6. package/dist/backups/lock.js.map +7 -0
  7. package/dist/backups/manager.js +170 -0
  8. package/dist/backups/manager.js.map +7 -0
  9. package/dist/backups/restore.js +188 -0
  10. package/dist/backups/restore.js.map +7 -0
  11. package/dist/backups/store.js +274 -0
  12. package/dist/backups/store.js.map +7 -0
  13. package/dist/cli/backup.js +84 -0
  14. package/dist/cli/backup.js.map +7 -0
  15. package/dist/cli/daemon.js +7 -0
  16. package/dist/cli/daemon.js.map +2 -2
  17. package/dist/cli/main.js +35 -5
  18. package/dist/cli/main.js.map +2 -2
  19. package/dist/cli/paths.js.map +2 -2
  20. package/dist/codex/auto-review.js +55 -0
  21. package/dist/codex/auto-review.js.map +2 -2
  22. package/dist/server/db/history.js +60 -0
  23. package/dist/server/db/history.js.map +7 -0
  24. package/dist/server/db/index.js +11 -1
  25. package/dist/server/db/index.js.map +2 -2
  26. package/dist/server/db/migrate.js +8 -18
  27. package/dist/server/db/migrate.js.map +2 -2
  28. package/dist/server/http.js +21 -3
  29. package/dist/server/http.js.map +2 -2
  30. package/dist/server/main.js +14 -3
  31. package/dist/server/main.js.map +2 -2
  32. package/dist/server/project-clones.js +53 -17
  33. package/dist/server/project-clones.js.map +2 -2
  34. package/dist/server/service.js +9 -5
  35. package/dist/server/service.js.map +2 -2
  36. package/dist/shared/backups.js +55 -0
  37. package/dist/shared/backups.js.map +7 -0
  38. package/dist/shared/database.js +15 -0
  39. package/dist/shared/database.js.map +7 -0
  40. package/dist/update-worker.cjs +934 -388
  41. package/dist/updates/files.js +2 -1
  42. package/dist/updates/files.js.map +2 -2
  43. package/dist/updates/transaction.js +3 -24
  44. package/dist/updates/transaction.js.map +2 -2
  45. package/dist/updates/worker.js +69 -19
  46. package/dist/updates/worker.js.map +2 -2
  47. package/dist/web/assets/{abnfDiagram-WEYOIA3C-CwLX554E.js → abnfDiagram-WEYOIA3C-Baw8Q2RL.js} +1 -1
  48. package/dist/web/assets/{arc-DAaDyyqc.js → arc-BvZHXzPr.js} +1 -1
  49. package/dist/web/assets/{architecture-WOLXFQ4H-y6MnEqN0.js → architecture-WOLXFQ4H-DrQ2OxuY.js} +1 -1
  50. package/dist/web/assets/{architectureDiagram-ZHVYKT53-_B_4k-Pp.js → architectureDiagram-ZHVYKT53-D96hX75H.js} +1 -1
  51. package/dist/web/assets/{blockDiagram-RJAE7QA3-C9jZ7FHy.js → blockDiagram-RJAE7QA3-Csce-aWb.js} +1 -1
  52. package/dist/web/assets/{c4Diagram-XDPPPE54-DQAtMvqN.js → c4Diagram-XDPPPE54-Dfu8ZQ6Y.js} +1 -1
  53. package/dist/web/assets/channel-DQhtAfM4.js +1 -0
  54. package/dist/web/assets/{chunk-2E7WHVMD-Bu427yab.js → chunk-2E7WHVMD-Vb63e-lu.js} +1 -1
  55. package/dist/web/assets/{chunk-4EMNKR2T-BrGrBG7B.js → chunk-4EMNKR2T-DrY1P7q3.js} +1 -1
  56. package/dist/web/assets/{chunk-55XCU76K-l4f0OZWA.js → chunk-55XCU76K-D70bjQ9E.js} +1 -1
  57. package/dist/web/assets/{chunk-7S6XSTFO-D7ixF313.js → chunk-7S6XSTFO-CfW90MUC.js} +1 -1
  58. package/dist/web/assets/{chunk-AQDTSKZB-Cesd-BF2.js → chunk-AQDTSKZB-u4KDDGBt.js} +1 -1
  59. package/dist/web/assets/{chunk-DSNLSHJZ-B7dim5uT.js → chunk-DSNLSHJZ-Cax3giJD.js} +1 -1
  60. package/dist/web/assets/{chunk-HAGFOTED-B-f2I7TA.js → chunk-HAGFOTED-BG9UMwzK.js} +1 -1
  61. package/dist/web/assets/{chunk-HNE4Q36P-CoIvaeX6.js → chunk-HNE4Q36P-BNxIfOgZ.js} +1 -1
  62. package/dist/web/assets/{chunk-NQOKRVIF-BybRxR7n.js → chunk-NQOKRVIF-DJ60e0Al.js} +1 -1
  63. package/dist/web/assets/{chunk-UHW7PFBP-CV5WG4tq.js → chunk-UHW7PFBP-C9gHYcjJ.js} +1 -1
  64. package/dist/web/assets/{chunk-VQ5W62QI-BO4AjXi1.js → chunk-VQ5W62QI-TyHPGVfQ.js} +1 -1
  65. package/dist/web/assets/{chunk-WQQL4CDK-CADNDGXO.js → chunk-WQQL4CDK-DeAauheh.js} +1 -1
  66. package/dist/web/assets/{chunk-XXDRQBXY-C6QZT7KW.js → chunk-XXDRQBXY-V-L20Tbi.js} +1 -1
  67. package/dist/web/assets/{chunk-YZO67HFG-Cm6eGReX.js → chunk-YZO67HFG-LV5QVBiU.js} +1 -1
  68. package/dist/web/assets/{chunk-ZAUX2LTX-CUBoGbKg.js → chunk-ZAUX2LTX-CBsBBk7L.js} +1 -1
  69. package/dist/web/assets/{chunk-ZCHMQSOR-B0ImOOtq.js → chunk-ZCHMQSOR-a2_9JHHn.js} +1 -1
  70. package/dist/web/assets/{classDiagram-v2-4MYWR3UA-D5NWZ9vm.js → classDiagram-v2-4MYWR3UA-77eiBCO3.js} +1 -1
  71. package/dist/web/assets/{cose-bilkent-JH36ORCC-BzckEyjA.js → cose-bilkent-JH36ORCC-DDP73pnY.js} +1 -1
  72. package/dist/web/assets/{cynefin-EF2NZ3EQ-Da_J6-Jb.js → cynefin-EF2NZ3EQ-onpnpyxc.js} +1 -1
  73. package/dist/web/assets/{cynefinDiagram-WF24UDRP-b4g_B1A-.js → cynefinDiagram-WF24UDRP-BmLplbnH.js} +1 -1
  74. package/dist/web/assets/{dagre-NK4GIZVX-89H2mfs9.js → dagre-NK4GIZVX-BhaMf_Gx.js} +1 -1
  75. package/dist/web/assets/{diagram-CBK3MWTC-CW4f_P0l.js → diagram-CBK3MWTC-BaalTqFt.js} +1 -1
  76. package/dist/web/assets/{diagram-I5XIIV2D-Cv5Qndgc.js → diagram-I5XIIV2D-4Tdshcyt.js} +1 -1
  77. package/dist/web/assets/{diagram-ODIL2UCW-LrXxeoCt.js → diagram-ODIL2UCW-SlEcZgod.js} +1 -1
  78. package/dist/web/assets/{diagram-PFBEJUXE-uJV2PooB.js → diagram-PFBEJUXE-CosCAXbh.js} +1 -1
  79. package/dist/web/assets/{diagram-RJA3I4VV-DlY0PGRs.js → diagram-RJA3I4VV-HQElVL4B.js} +1 -1
  80. package/dist/web/assets/{diagram-TYUNFSNR-B1KoXBEE.js → diagram-TYUNFSNR-BT2WDc21.js} +1 -1
  81. package/dist/web/assets/{dist-B4eX4BCP.js → dist-CrpGQKqw.js} +1 -1
  82. package/dist/web/assets/{ebnfDiagram-V4P3TTPT-xCvQVbdj.js → ebnfDiagram-V4P3TTPT-BhK6AM0_.js} +1 -1
  83. package/dist/web/assets/{elk-QONTBCTQ-Cl8Hu-Uy.js → elk-QONTBCTQ-CS7DfCnD.js} +1 -1
  84. package/dist/web/assets/{erDiagram-KYQIGQAA-Xp-AvSwE.js → erDiagram-KYQIGQAA-DxVnuqFK.js} +1 -1
  85. package/dist/web/assets/{eventmodeling-K75KTNOO-C0truZGU.js → eventmodeling-K75KTNOO-oC_YeomD.js} +1 -1
  86. package/dist/web/assets/flowDiagram-IUGLLWBU-Bspg0rQz.js +1 -0
  87. package/dist/web/assets/{ganttDiagram-R7GPNTJY-rPaYp-D7.js → ganttDiagram-R7GPNTJY-CquUpiE1.js} +1 -1
  88. package/dist/web/assets/{gitGraph-VSP46ZUC-Bgfpho41.js → gitGraph-VSP46ZUC-BTtGQYsh.js} +1 -1
  89. package/dist/web/assets/{gitGraphDiagram-PDYL7XHY-kDcmuv6k.js → gitGraphDiagram-PDYL7XHY-BsokGXYo.js} +1 -1
  90. package/dist/web/assets/index-B8tsymVC.js +147 -0
  91. package/dist/web/assets/{index-B7tnf28H.css → index-COSho7m2.css} +1 -1
  92. package/dist/web/assets/{info-OHQRW6UA-CAWg5k9R.js → info-OHQRW6UA-CvGN4gWh.js} +1 -1
  93. package/dist/web/assets/{infoDiagram-MVDRKAX5-Cd08ebhn.js → infoDiagram-MVDRKAX5-BF9Rp3BT.js} +1 -1
  94. package/dist/web/assets/{ishikawaDiagram-HPEHV2GE-tAXBLt9k.js → ishikawaDiagram-HPEHV2GE-DtD20ngU.js} +1 -1
  95. package/dist/web/assets/{journeyDiagram-BOSHW7O7-ZgnWqXkg.js → journeyDiagram-BOSHW7O7-gB16trHV.js} +1 -1
  96. package/dist/web/assets/{kanban-definition-FNSA4HC5-BUjtjd-h.js → kanban-definition-FNSA4HC5-D0aKOsE2.js} +1 -1
  97. package/dist/web/assets/{line-DG48HRVK.js → line-BR5dqPmF.js} +1 -1
  98. package/dist/web/assets/{linear-CqnXVCVw.js → linear-OsiQaPpK.js} +1 -1
  99. package/dist/web/assets/{mermaid-parser.core-gIvgB9WK.js → mermaid-parser.core-MEuwYVot.js} +3 -3
  100. package/dist/web/assets/{mermaid.core-C_R7Y1XB.js → mermaid.core-CVKnnz65.js} +4 -4
  101. package/dist/web/assets/{mindmap-definition-6OY57UG3-CwUEEl_y.js → mindmap-definition-6OY57UG3-5aHA-kPJ.js} +1 -1
  102. package/dist/web/assets/{packet-JDAUHWVQ-BrXmpRQ3.js → packet-JDAUHWVQ-uj48AF5r.js} +1 -1
  103. package/dist/web/assets/{pegDiagram-ECIUJNBX-lLApO85Q.js → pegDiagram-ECIUJNBX-BkVIHLBX.js} +1 -1
  104. package/dist/web/assets/{pie-XZMESJXO-C_8kR5WX.js → pie-XZMESJXO-D3hy3nD7.js} +1 -1
  105. package/dist/web/assets/{pieDiagram-GNMPY74T-CawGmJZn.js → pieDiagram-GNMPY74T-CBAqGMEo.js} +1 -1
  106. package/dist/web/assets/{quadrantDiagram-D57K4MC3-Dg6Yq3s_.js → quadrantDiagram-D57K4MC3-C35kvNRB.js} +1 -1
  107. package/dist/web/assets/{radar-ABXABTNO-Bs1xY09D.js → radar-ABXABTNO-3LWE6J2Y.js} +1 -1
  108. package/dist/web/assets/{railroad-I3PHUGI6-Bw_qS6e3.js → railroad-I3PHUGI6-B6Mc9Djl.js} +1 -1
  109. package/dist/web/assets/{railroad-abnf-LNEFI6M7-CQr33CuI.js → railroad-abnf-LNEFI6M7-DJnbwbpn.js} +1 -1
  110. package/dist/web/assets/{railroad-ebnf-DWYD2UWJ-BY9nk5pg.js → railroad-ebnf-DWYD2UWJ-BFXAvY4k.js} +1 -1
  111. package/dist/web/assets/{railroad-peg-JR7BVNR7-CcFsAEI4.js → railroad-peg-JR7BVNR7-JYGb2Oq5.js} +1 -1
  112. package/dist/web/assets/{railroadDiagram-Y72NRBL7-rkZbchmc.js → railroadDiagram-Y72NRBL7-uBIggR0w.js} +1 -1
  113. package/dist/web/assets/{requirementDiagram-EQYSW5RA-CSOcijj5.js → requirementDiagram-EQYSW5RA-CDWglrxJ.js} +1 -1
  114. package/dist/web/assets/{sankeyDiagram-SY24JHO5-E0uo0rRp.js → sankeyDiagram-SY24JHO5-BT16hnTl.js} +1 -1
  115. package/dist/web/assets/{sequenceDiagram-BLEHNHQU-BKWAAcRu.js → sequenceDiagram-BLEHNHQU-eFJ27rVk.js} +1 -1
  116. package/dist/web/assets/{src-F6L8HcEC.js → src-BlOCBHYS.js} +1 -1
  117. package/dist/web/assets/{stateDiagram-v2-A6IESKWZ-BBk2tpPh.js → stateDiagram-v2-A6IESKWZ-CK_s-2Qv.js} +1 -1
  118. package/dist/web/assets/{swimlanes-B3ENRKFQ-CAUrhGwF.js → swimlanes-B3ENRKFQ-WLMrk9Lm.js} +1 -1
  119. package/dist/web/assets/swimlanesDiagram-AYQRV4AK-DmjebfnI.js +8 -0
  120. package/dist/web/assets/{timeline-definition-JXYKGLGB-D0FWj9fa.js → timeline-definition-JXYKGLGB-D0-xZS_V.js} +1 -1
  121. package/dist/web/assets/{treeView-D4JQ5SDB-CkJOmf93.js → treeView-D4JQ5SDB-TTivyB8T.js} +1 -1
  122. package/dist/web/assets/{treemap-SAJKECNS-BSFsardB.js → treemap-SAJKECNS-DtU16wOO.js} +1 -1
  123. package/dist/web/assets/{usecaseDiagram-WPOLBNNX-B2U-2o4g.js → usecaseDiagram-WPOLBNNX-DLFybW0F.js} +1 -1
  124. package/dist/web/assets/{vennDiagram-Q3Y3IDCW-BijTH0fT.js → vennDiagram-Q3Y3IDCW-DRDuYw70.js} +1 -1
  125. package/dist/web/assets/{wardley-7MLQ67FV-DDOa5RzL.js → wardley-7MLQ67FV-tZ-5qrqv.js} +1 -1
  126. package/dist/web/assets/{wardleyDiagram-IVAIDKWA-mbb5GvW5.js → wardleyDiagram-IVAIDKWA-wRKeGCMl.js} +1 -1
  127. package/dist/web/assets/{xychartDiagram-4URNHDU4-DHd60D_Y.js → xychartDiagram-4URNHDU4-B5-k-DHF.js} +1 -1
  128. package/dist/web/index.html +4 -4
  129. package/package.json +10 -3
  130. package/dist/web/assets/channel-ByzMCsOB.js +0 -1
  131. package/dist/web/assets/flowDiagram-IUGLLWBU-T67jQ-T_.js +0 -1
  132. package/dist/web/assets/index-3DUP8YCe.js +0 -142
  133. package/dist/web/assets/swimlanesDiagram-AYQRV4AK-BCnJymLL.js +0 -8
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/server/main.ts"],
4
- "sourcesContent": ["import { fileURLToPath } from 'node:url';\nimport { join } from 'node:path';\nimport { existsSync, realpathSync } from 'node:fs';\nimport { serve } from '@hono/node-server';\nimport { WebSocketServer } from 'ws';\nimport { CodexAdapter } from '../codex/adapter.js';\nimport type { CodexClient } from '../codex/types.js';\nimport { loadConfig, type AppConfig } from './config.js';\nimport { acquireInstanceLock } from './instance-lock.js';\nimport { openDatabase } from './db/index.js';\nimport { migrateDatabase } from './db/migrate.js';\nimport { OperationStore } from './db/operations.js';\nimport { ProjectRegistry } from './projects.js';\nimport { GatewayService } from './service.js';\nimport { createApp } from './http.js';\nimport { startHeartbeat } from './websocket.js';\nimport { UpdateManager } from '../updates/manager.js';\nimport type { UpdateController } from '../shared/updates.js';\nexport async function startGateway(\n config: AppConfig,\n overrides: {\n codex?: CodexClient;\n staticDir?: string;\n migrationsDir?: string;\n updates?: UpdateController;\n maintenance?: boolean;\n } = {},\n) {\n const release = await acquireInstanceLock(config.dataDir);\n let closeDatabase: (() => void) | undefined;\n let codex: CodexClient | undefined;\n const updates =\n overrides.updates ??\n new UpdateManager(undefined, undefined, () => codex?.diagnostics().codexVersion ?? null);\n try {\n const db = openDatabase(join(config.dataDir, 'app.sqlite'));\n closeDatabase = db.close;\n const packagedMigrations = fileURLToPath(new URL('../drizzle', import.meta.url));\n migrateDatabase(\n db.database,\n overrides.migrationsDir ?? (existsSync(packagedMigrations) ? packagedMigrations : undefined),\n );\n codex = overrides.codex ?? new CodexAdapter(config.codex);\n const service = new GatewayService({\n store: new OperationStore(db.database),\n projects: new ProjectRegistry(config.projects, db.database),\n codex,\n workspacesRoot: join(config.dataDir, 'workspaces'),\n limits: config.limits,\n authMode: config.auth.mode,\n demo: config.demo,\n });\n service.setMaintenance(overrides.maintenance ?? false);\n try {\n await codex.start();\n } catch {\n /* Diagnostics remain available; never substitute a fixture. */\n }\n const wss = new WebSocketServer({ noServer: true, maxPayload: 8192 });\n const app = createApp({\n config,\n service,\n staticDir: overrides.staticDir ?? fileURLToPath(new URL('../web', import.meta.url)),\n updates,\n });\n const server = serve({\n fetch: app.fetch,\n hostname: config.host,\n port: config.port,\n websocket: { server: wss },\n });\n await new Promise<void>((resolveListening, reject) => {\n server.once('listening', resolveListening);\n server.once('error', reject);\n });\n const stopHeartbeat = startHeartbeat(wss);\n let closing: Promise<void> | undefined;\n const close = (): Promise<void> =>\n (closing ??= (async () => {\n stopHeartbeat();\n updates.close();\n const closed = new Promise<void>((resolveClosed) => server.close(() => resolveClosed()));\n for (const ws of wss.clients) {\n ws.close(1001, 'Gateway stopping');\n ws.terminate();\n }\n wss.close();\n try {\n await service.shutdown();\n } finally {\n if ('closeAllConnections' in server) server.closeAllConnections();\n await closed;\n try {\n db.close();\n } finally {\n await release();\n }\n }\n })());\n return { app, server, service, codex, close, updates };\n } catch (error) {\n updates.close();\n try {\n await codex?.stop();\n } finally {\n try {\n closeDatabase?.();\n } finally {\n await release();\n }\n }\n throw error;\n }\n}\nif (process.argv[1] && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {\n try {\n const config = await loadConfig();\n const runtime = await startGateway(config);\n console.log(`Codex Web UI listening on ${config.host}:${config.port}`);\n console.log(`Allowed origins: ${config.publicOrigin.join(', ')}`);\n const stop = () => {\n void runtime.close().then(\n () => {\n process.exitCode = 0;\n },\n () => {\n process.exitCode = 1;\n },\n );\n };\n process.once('SIGINT', stop);\n process.once('SIGTERM', stop);\n } catch (error) {\n console.error(error instanceof Error ? error.message : 'Startup failed');\n process.exitCode = 1;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { BackupManager } from '../backups/manager.js';\nimport { createBackupStore } from '../backups/factory.js';\nimport { assertNoPendingRestore } from '../backups/restore.js';\nimport type { BackupController } from '../shared/backups.js';\nimport { fileURLToPath } from 'node:url';\nimport { join } from 'node:path';\nimport { existsSync, realpathSync } from 'node:fs';\nimport { serve } from '@hono/node-server';\nimport { WebSocketServer } from 'ws';\nimport { CodexAdapter } from '../codex/adapter.js';\nimport type { CodexClient } from '../codex/types.js';\nimport { loadConfig, configSource, type AppConfig } from './config.js';\nimport { acquireInstanceLock } from './instance-lock.js';\nimport { openDatabase } from './db/index.js';\nimport { migrateDatabase } from './db/migrate.js';\nimport { OperationStore } from './db/operations.js';\nimport { ProjectRegistry } from './projects.js';\nimport { GatewayService } from './service.js';\nimport { createApp } from './http.js';\nimport { startHeartbeat } from './websocket.js';\nimport { UpdateManager } from '../updates/manager.js';\nimport type { UpdateController } from '../shared/updates.js';\nexport async function startGateway(\n config: AppConfig,\n overrides: {\n codex?: CodexClient;\n staticDir?: string;\n migrationsDir?: string;\n updates?: UpdateController;\n backups?: BackupController;\n maintenance?: boolean;\n } = {},\n) {\n const release = await acquireInstanceLock(config.dataDir);\n let closeDatabase: (() => void) | undefined;\n let codex: CodexClient | undefined;\n let backups: BackupController | undefined = overrides.backups;\n const updates =\n overrides.updates ??\n new UpdateManager(undefined, undefined, () => codex?.diagnostics().codexVersion ?? null);\n try {\n const source = configSource(config);\n const backupStore = source\n ? createBackupStore(source.path, config.dataDir, overrides.migrationsDir)\n : undefined;\n if (backupStore) await assertNoPendingRestore(backupStore.options.directory);\n const db = openDatabase(join(config.dataDir, 'app.sqlite'));\n closeDatabase = db.close;\n const packagedMigrations = fileURLToPath(new URL('../drizzle', import.meta.url));\n migrateDatabase(\n db.database,\n overrides.migrationsDir ?? (existsSync(packagedMigrations) ? packagedMigrations : undefined),\n );\n codex = overrides.codex ?? new CodexAdapter(config.codex);\n const service = new GatewayService({\n store: new OperationStore(db.database),\n projects: new ProjectRegistry(config.projects, db.database),\n codex,\n workspacesRoot: join(config.dataDir, 'workspaces'),\n limits: config.limits,\n authMode: config.auth.mode,\n demo: config.demo,\n });\n service.setMaintenance(overrides.maintenance ?? false);\n backups ??= backupStore ? new BackupManager(backupStore, service) : undefined;\n try {\n await codex.start();\n } catch {\n /* Diagnostics remain available; never substitute a fixture. */\n }\n const wss = new WebSocketServer({ noServer: true, maxPayload: 8192 });\n const app = createApp({\n config,\n service,\n staticDir: overrides.staticDir ?? fileURLToPath(new URL('../web', import.meta.url)),\n updates,\n backups,\n });\n const server = serve({\n fetch: app.fetch,\n hostname: config.host,\n port: config.port,\n websocket: { server: wss },\n });\n await new Promise<void>((resolveListening, reject) => {\n server.once('listening', resolveListening);\n server.once('error', reject);\n });\n const stopHeartbeat = startHeartbeat(wss);\n let closing: Promise<void> | undefined;\n const close = (): Promise<void> =>\n (closing ??= (async () => {\n stopHeartbeat();\n updates.close();\n const closed = new Promise<void>((resolveClosed) => server.close(() => resolveClosed()));\n for (const ws of wss.clients) {\n ws.close(1001, 'Gateway stopping');\n ws.terminate();\n }\n wss.close();\n try {\n await backups?.close();\n await service.shutdown();\n } finally {\n if ('closeAllConnections' in server) server.closeAllConnections();\n await closed;\n try {\n db.close();\n } finally {\n await release();\n }\n }\n })());\n return { app, server, service, codex, close, updates, backups };\n } catch (error) {\n updates.close();\n await backups?.close();\n try {\n await codex?.stop();\n } finally {\n try {\n closeDatabase?.();\n } finally {\n await release();\n }\n }\n throw error;\n }\n}\nif (process.argv[1] && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {\n try {\n const config = await loadConfig();\n const runtime = await startGateway(config);\n console.log(`Codex Web UI listening on ${config.host}:${config.port}`);\n console.log(`Allowed origins: ${config.publicOrigin.join(', ')}`);\n const stop = () => {\n void runtime.close().then(\n () => {\n process.exitCode = 0;\n },\n () => {\n process.exitCode = 1;\n },\n );\n };\n process.once('SIGINT', stop);\n process.once('SIGTERM', stop);\n } catch (error) {\n console.error(error instanceof Error ? error.message : 'Startup failed');\n process.exitCode = 1;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -64,26 +64,51 @@ function checkEmpty(path) {
64
64
  }
65
65
  }
66
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";
67
+ if (missingGit)
68
+ return {
69
+ code: "CLONE_GIT_MISSING",
70
+ message: "\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"
71
+ };
68
72
  if (/authentication failed|could not read username|permission denied|publickey|terminal prompts disabled|credential|access denied|401|403/i.test(
69
73
  output
70
74
  ))
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";
75
+ return {
76
+ code: "CLONE_AUTH_FAILED",
77
+ message: "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"
78
+ };
72
79
  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";
80
+ return {
81
+ code: "CLONE_HOST_KEY_FAILED",
82
+ message: "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"
83
+ };
74
84
  if (/repository .*not found|does not appear to be a git repository|not a repository|404/i.test(
75
85
  output
76
86
  ))
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";
87
+ return {
88
+ code: "CLONE_REPOSITORY_UNAVAILABLE",
89
+ message: "\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"
90
+ };
78
91
  if (/could not resolve|failed to connect|unable to access|connection|timed out|certificate/i.test(
79
92
  output
80
93
  ))
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";
94
+ return {
95
+ code: "CLONE_NETWORK_FAILED",
96
+ message: "\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"
97
+ };
82
98
  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";
99
+ return {
100
+ code: "CLONE_TARGET_NOT_EMPTY",
101
+ message: "\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"
102
+ };
84
103
  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";
104
+ return {
105
+ code: "CLONE_DISK_FULL",
106
+ message: "\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"
107
+ };
108
+ return {
109
+ code: "CLONE_FAILED",
110
+ message: "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"
111
+ };
87
112
  }
88
113
  class ProjectClones {
89
114
  constructor(register, registered) {
@@ -141,7 +166,8 @@ class ProjectClones {
141
166
  path,
142
167
  name: parsed.name,
143
168
  status: "cloning",
144
- progress: "\u6B63\u5728\u8FDE\u63A5\u4ED3\u5E93\u2026"
169
+ progress: "\u6B63\u5728\u8FDE\u63A5\u4ED3\u5E93\u2026",
170
+ progressStage: "connecting"
145
171
  },
146
172
  fingerprint,
147
173
  done: Promise.resolve()
@@ -166,6 +192,7 @@ class ProjectClones {
166
192
  if (job && active(job) && job.view.status !== "cancelling") {
167
193
  job.view.status = "cancelling";
168
194
  job.view.progress = "\u6B63\u5728\u53D6\u6D88\uFF0C\u7B49\u5F85 Git \u9000\u51FA\u2026";
195
+ job.view.progressStage = "cancelling";
169
196
  this.signal(job, "SIGTERM");
170
197
  }
171
198
  return this.get(id);
@@ -181,10 +208,14 @@ class ProjectClones {
181
208
  job.view.project = this.register({ path: job.view.path, name: job.view.name });
182
209
  job.view.status = "completed";
183
210
  job.view.progress = "\u514B\u9686\u5B8C\u6210\uFF0C\u9879\u76EE\u5DF2\u6DFB\u52A0";
211
+ job.view.progressStage = "completed";
212
+ delete job.view.errorCode;
184
213
  delete job.view.error;
185
214
  } catch {
186
215
  job.view.status = "registration-failed";
187
216
  job.view.progress = "\u514B\u9686\u5DF2\u5B8C\u6210\uFF0C\u9879\u76EE\u5C1A\u672A\u6DFB\u52A0";
217
+ job.view.progressStage = "registration_failed";
218
+ job.view.errorCode = "CLONE_REGISTRATION_FAILED";
188
219
  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
220
  }
190
221
  }
@@ -241,14 +272,14 @@ class ProjectClones {
241
272
  );
242
273
  if (!match || job.view.status !== "cloning") continue;
243
274
  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"
275
+ "Receiving objects": ["receiving", "\u6B63\u5728\u63A5\u6536\u5BF9\u8C61"],
276
+ "Resolving deltas": ["resolving", "\u6B63\u5728\u89E3\u6790\u5DEE\u5F02"],
277
+ "Updating files": ["checking_out", "\u6B63\u5728\u68C0\u51FA\u6587\u4EF6"],
278
+ "Checking out files": ["checking_out", "\u6B63\u5728\u68C0\u51FA\u6587\u4EF6"],
279
+ "Counting objects": ["counting", "\u6B63\u5728\u7EDF\u8BA1\u5BF9\u8C61"],
280
+ "Compressing objects": ["compressing", "\u6B63\u5728\u538B\u7F29\u5BF9\u8C61"]
250
281
  };
251
- job.view.progress = labels[match[1]];
282
+ [job.view.progressStage, job.view.progress] = labels[match[1]];
252
283
  job.view.percent = Math.min(100, Number(match[2]));
253
284
  }
254
285
  };
@@ -276,11 +307,16 @@ class ProjectClones {
276
307
  if (job.view.status === "cancelling") {
277
308
  job.view.status = timedOut ? "failed" : "cancelled";
278
309
  job.view.progress = timedOut ? "\u514B\u9686\u8D85\u65F6\uFF0C\u5DF2\u505C\u6B62" : "\u5DF2\u53D6\u6D88\u514B\u9686";
310
+ job.view.progressStage = timedOut ? "timed_out" : "cancelled";
311
+ job.view.errorCode = timedOut ? "CLONE_TIMED_OUT" : "CLONE_CANCELLED";
279
312
  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
313
  } else if (code !== 0) {
281
314
  job.view.status = "failed";
282
315
  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`;
316
+ job.view.progressStage = "failed";
317
+ const problem = failure(output, missingGit);
318
+ job.view.errorCode = problem.code;
319
+ job.view.error = `${problem.message} \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
320
  } else this.finishRegistration(job);
285
321
  }
286
322
  async close() {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
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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",
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(\n output: string,\n missingGit: boolean,\n): { code: NonNullable<ProjectClone['errorCode']>; message: string } {\n if (missingGit)\n return {\n code: 'CLONE_GIT_MISSING',\n message: '\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 };\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 {\n code: 'CLONE_AUTH_FAILED',\n message:\n '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 };\n if (/host key verification failed/i.test(output))\n return {\n code: 'CLONE_HOST_KEY_FAILED',\n message: '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 };\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 {\n code: 'CLONE_REPOSITORY_UNAVAILABLE',\n message: '\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 };\n if (\n /could not resolve|failed to connect|unable to access|connection|timed out|certificate/i.test(\n output,\n )\n )\n return {\n code: 'CLONE_NETWORK_FAILED',\n message: '\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 };\n if (/already exists and is not an empty directory/i.test(output))\n return {\n code: 'CLONE_TARGET_NOT_EMPTY',\n message: '\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 };\n if (/no space left|disk quota/i.test(output))\n return {\n code: 'CLONE_DISK_FULL',\n message: '\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 };\n return {\n code: 'CLONE_FAILED',\n message: '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\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 progressStage: 'connecting',\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 job.view.progressStage = 'cancelling';\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 job.view.progressStage = 'completed';\n delete job.view.errorCode;\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.progressStage = 'registration_failed';\n job.view.errorCode = 'CLONE_REGISTRATION_FAILED';\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, [NonNullable<ProjectClone['progressStage']>, string]> = {\n 'Receiving objects': ['receiving', '\u6B63\u5728\u63A5\u6536\u5BF9\u8C61'],\n 'Resolving deltas': ['resolving', '\u6B63\u5728\u89E3\u6790\u5DEE\u5F02'],\n 'Updating files': ['checking_out', '\u6B63\u5728\u68C0\u51FA\u6587\u4EF6'],\n 'Checking out files': ['checking_out', '\u6B63\u5728\u68C0\u51FA\u6587\u4EF6'],\n 'Counting objects': ['counting', '\u6B63\u5728\u7EDF\u8BA1\u5BF9\u8C61'],\n 'Compressing objects': ['compressing', '\u6B63\u5728\u538B\u7F29\u5BF9\u8C61'],\n };\n [job.view.progressStage, 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.progressStage = timedOut ? 'timed_out' : 'cancelled';\n job.view.errorCode = timedOut ? 'CLONE_TIMED_OUT' : 'CLONE_CANCELLED';\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.progressStage = 'failed';\n const problem = failure(output, missingGit);\n job.view.errorCode = problem.code;\n job.view.error = `${problem.message} \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
6
  "names": ["job"]
7
7
  }
@@ -64,6 +64,7 @@ class GatewayService {
64
64
  fileTemporaryRoots = temporaryFileRoots();
65
65
  accepting = true;
66
66
  maintenance = false;
67
+ maintenanceReason = "update";
67
68
  currentEpoch;
68
69
  confirmedCompatibility;
69
70
  archiveChanges = /* @__PURE__ */ new Set();
@@ -103,10 +104,14 @@ class GatewayService {
103
104
  isInMaintenance() {
104
105
  return this.maintenance;
105
106
  }
106
- setMaintenance(value) {
107
+ setMaintenance(value, reason = "update") {
107
108
  this.maintenance = value;
109
+ this.maintenanceReason = reason;
108
110
  }
109
- prepareUpdate() {
111
+ maintenanceError() {
112
+ return this.maintenanceReason === "backup" ? new AppError("BACKUP_MAINTENANCE", "\u6B63\u5728\u5907\u4EFD\uFF0C\u8BF7\u5B8C\u6210\u540E\u91CD\u8BD5\u3002", 503) : new AppError("UPDATE_MAINTENANCE", "\u6B63\u5728\u5347\u7EA7\uFF0C\u8BF7\u7B49\u5F85\u670D\u52A1\u91CD\u65B0\u8FDE\u63A5", 503);
113
+ }
114
+ prepareUpdate(reason = "update") {
110
115
  if (this.projectClones.hasActive) return { ready: false, reason: "\u7B49\u5F85\u9879\u76EE\u514B\u9686\u5B8C\u6210\u6216\u53D6\u6D88" };
111
116
  if (this.shell.active()) return { ready: false, reason: "\u7B49\u5F85 Shell \u547D\u4EE4\u7ED3\u675F" };
112
117
  if (this.inFlightOperations.size || this.resumes.size || this.pendingThreadCreations.size)
@@ -117,7 +122,7 @@ class GatewayService {
117
122
  (action) => ["pending", "responding"].includes(action.status)
118
123
  ))
119
124
  return { ready: false, reason: "\u7B49\u5F85\u6267\u884C\u4E2D\u7684\u4EFB\u52A1\u3001\u5BA1\u6279\u6216\u8F93\u5165\u7ED3\u675F" };
120
- this.maintenance = true;
125
+ this.setMaintenance(true, reason);
121
126
  return { ready: true };
122
127
  }
123
128
  addProject(input) {
@@ -882,8 +887,7 @@ class GatewayService {
882
887
  }
883
888
  async perform(kind, target, input, work) {
884
889
  if (!this.accepting) throw new AppError("CODEX_UNAVAILABLE", "\u670D\u52A1\u6B63\u5728\u5173\u95ED", 503);
885
- if (this.maintenance)
886
- throw new AppError("UPDATE_MAINTENANCE", "\u6B63\u5728\u5347\u7EA7\uFF0C\u8BF7\u7B49\u5F85\u670D\u52A1\u91CD\u65B0\u8FDE\u63A5", 503);
890
+ if (this.maintenance) throw this.maintenanceError();
887
891
  let accepted;
888
892
  try {
889
893
  accepted = this.store.accept(input.clientOperationId, kind, target, input);