@shipfox/api-runners 12.1.0 → 12.3.0
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/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +35 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +24 -2
- package/dist/config.js.map +1 -1
- package/dist/core/errors.d.ts +4 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +6 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/rate-limit.d.ts.map +1 -1
- package/dist/core/runner-control-sessions.d.ts.map +1 -1
- package/dist/core/runner-control-sessions.js +37 -11
- package/dist/core/runner-control-sessions.js.map +1 -1
- package/dist/core/runner-instances.d.ts +1 -0
- package/dist/core/runner-instances.d.ts.map +1 -1
- package/dist/core/runner-instances.js.map +1 -1
- package/dist/core/runner-labels.d.ts +11 -0
- package/dist/core/runner-labels.d.ts.map +1 -0
- package/dist/core/runner-labels.js +41 -0
- package/dist/core/runner-labels.js.map +1 -0
- package/dist/core/runner-sessions.d.ts.map +1 -1
- package/dist/core/runner-sessions.js +5 -1
- package/dist/core/runner-sessions.js.map +1 -1
- package/dist/db/db.d.ts +64 -64
- package/dist/db/ephemeral-registration-tokens.d.ts.map +1 -1
- package/dist/db/ephemeral-registration-tokens.js +12 -2
- package/dist/db/ephemeral-registration-tokens.js.map +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/reservations.d.ts.map +1 -1
- package/dist/db/reservations.js +36 -10
- package/dist/db/reservations.js.map +1 -1
- package/dist/db/runner-instances.d.ts +4 -0
- package/dist/db/runner-instances.d.ts.map +1 -1
- package/dist/db/runner-instances.js +16 -5
- package/dist/db/runner-instances.js.map +1 -1
- package/dist/db/runner-sessions.d.ts.map +1 -1
- package/dist/db/runner-sessions.js +33 -15
- package/dist/db/runner-sessions.js.map +1 -1
- package/dist/db/schema/admin-command-results.d.ts +1 -1
- package/dist/db/schema/ephemeral-registration-tokens.d.ts +1 -1
- package/dist/db/schema/manual-registration-tokens.d.ts +1 -1
- package/dist/db/schema/outbox.d.ts +1 -1
- package/dist/db/schema/pending-job-executions.d.ts +3 -3
- package/dist/db/schema/provisioner-capability-snapshots.d.ts +3 -3
- package/dist/db/schema/provisioner-tokens.d.ts +2 -2
- package/dist/db/schema/rate-limits.d.ts +1 -1
- package/dist/db/schema/reservations.d.ts +3 -3
- package/dist/db/schema/runner-activation-tokens.d.ts +1 -1
- package/dist/db/schema/runner-control-sessions.d.ts +2 -2
- package/dist/db/schema/runner-instances.d.ts +4 -4
- package/dist/db/schema/runner-instances.d.ts.map +1 -1
- package/dist/db/schema/runner-instances.js +1 -0
- package/dist/db/schema/runner-instances.js.map +1 -1
- package/dist/db/schema/runner-sessions.d.ts +4 -4
- package/dist/db/schema/running-job-executions.d.ts +5 -5
- package/dist/metrics/index.d.ts +2 -1
- package/dist/metrics/index.d.ts.map +1 -1
- package/dist/metrics/index.js +1 -1
- package/dist/metrics/index.js.map +1 -1
- package/dist/metrics/instance.d.ts +13 -8
- package/dist/metrics/instance.d.ts.map +1 -1
- package/dist/metrics/instance.js +8 -0
- package/dist/metrics/instance.js.map +1 -1
- package/dist/metrics/service.d.ts.map +1 -1
- package/dist/metrics/service.js +20 -4
- package/dist/metrics/service.js.map +1 -1
- package/dist/presentation/dto/admin-provisioner-token.d.ts +1 -1
- package/dist/presentation/dto/admin-provisioner-token.d.ts.map +1 -1
- package/dist/presentation/dto/admin-runner-instances.d.ts +2 -2
- package/dist/presentation/dto/admin-runner-instances.d.ts.map +1 -1
- package/dist/presentation/routes/poll-demand.d.ts.map +1 -1
- package/dist/presentation/routes/poll-demand.js +12 -6
- package/dist/presentation/routes/poll-demand.js.map +1 -1
- package/dist/presentation/routes/rate-limit.d.ts.map +1 -1
- package/dist/presentation/routes/register.d.ts.map +1 -1
- package/dist/presentation/routes/register.js +9 -1
- package/dist/presentation/routes/register.js.map +1 -1
- package/dist/presentation/routes/report-runner-instances.d.ts.map +1 -1
- package/dist/presentation/routes/report-runner-instances.js +1 -0
- package/dist/presentation/routes/report-runner-instances.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0004_huge_skreet.sql +1 -0
- package/drizzle/meta/0004_snapshot.json +2194 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +9 -9
- package/src/config.test.ts +81 -0
- package/src/config.ts +39 -2
- package/src/core/errors.ts +9 -0
- package/src/core/index.ts +1 -0
- package/src/core/runner-control-sessions.test.ts +404 -0
- package/src/core/runner-control-sessions.ts +51 -11
- package/src/core/runner-instances.ts +1 -0
- package/src/core/runner-labels.test.ts +53 -0
- package/src/core/runner-labels.ts +51 -0
- package/src/core/runner-sessions.test.ts +34 -1
- package/src/core/runner-sessions.ts +8 -1
- package/src/db/ephemeral-registration-tokens.ts +15 -1
- package/src/db/index.ts +1 -0
- package/src/db/reservations.test.ts +537 -0
- package/src/db/reservations.ts +116 -17
- package/src/db/runner-instances.test.ts +276 -1
- package/src/db/runner-instances.ts +39 -3
- package/src/db/runner-sessions.ts +39 -15
- package/src/db/schema/runner-instances.ts +3 -0
- package/src/metrics/index.ts +2 -0
- package/src/metrics/instance.ts +18 -0
- package/src/metrics/service.test.ts +99 -0
- package/src/metrics/service.ts +24 -4
- package/src/presentation/routes/late-runner-enrollment.test.ts +230 -0
- package/src/presentation/routes/poll-demand.test.ts +116 -3
- package/src/presentation/routes/poll-demand.ts +18 -6
- package/src/presentation/routes/register.test.ts +116 -2
- package/src/presentation/routes/register.ts +7 -0
- package/src/presentation/routes/report-runner-instances.test.ts +33 -0
- package/src/presentation/routes/report-runner-instances.ts +1 -0
- package/src/presentation/routes/runner-enrollment.test.ts +65 -5
- package/src/temporal/activities/maintenance-activities.test.ts +24 -31
- package/test/env.ts +1 -0
- package/test/fixtures/late-runner-enrollment.ts +103 -0
- package/test/index.ts +5 -0
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/routes/register.ts"],"sourcesContent":["import type {AuthInterModuleClient} from '@shipfox/api-auth-dto/inter-module';\nimport {registerRunnerBodySchema, registerRunnerResponseSchema} from '@shipfox/api-runners-dto';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {\n EmptyRunnerLabelsError,\n RegistrationTokenConsumedError,\n RegistrationTokenExpiredError,\n} from '#core/errors.js';\nimport {registerRunnerSession} from '#core/runner-sessions.js';\nimport {getRunnerContext} from '#presentation/auth/index.js';\nimport {createEphemeralRegisterRateLimitPreHandler} from './rate-limit.js';\n\nexport function createRegisterRoute(auth: AuthInterModuleClient) {\n return defineRoute({\n method: 'POST',\n path: '/register',\n description: 'Exchange a runner registration token for a runner session token',\n schema: {\n body: registerRunnerBodySchema,\n response: {\n 200: registerRunnerResponseSchema,\n },\n },\n preHandler: createEphemeralRegisterRateLimitPreHandler(),\n errorHandler: (error, request) => {\n if (error instanceof EmptyRunnerLabelsError) {\n throw new ClientError(error.message, 'empty-runner-labels', {status: 400});\n }\n if (error instanceof RegistrationTokenConsumedError) {\n const runner = getRunnerContext(request);\n if (runner.kind === 'ephemeral') {\n request.log.warn(\n {\n ephemeralTokenId: error.ephemeralTokenId,\n provisionerId: runner.provisionerId,\n },\n 'Ephemeral registration token reuse rejected',\n );\n }\n throw new ClientError(\n 'Registration token has already been consumed',\n 'registration-token-consumed',\n {\n status: 409,\n },\n );\n }\n if (error instanceof RegistrationTokenExpiredError) {\n throw new ClientError('Registration token has expired', 'registration-token-expired', {\n status: 401,\n });\n }\n throw error;\n },\n handler: async (request) => {\n const runner = getRunnerContext(request);\n const result = await registerRunnerSession({\n auth,\n credential:\n runner.kind === 'manual'\n ? {\n kind: 'manual',\n registrationTokenId: runner.registrationTokenId,\n workspaceId: runner.workspaceId,\n }\n : runner,\n labels: request.body.labels,\n toolCapabilities: request.body.capabilities ?? null,\n });\n\n return {\n session_token: result.sessionToken,\n session_id: result.session.id,\n mode: result.mode,\n max_claims: result.maxClaims,\n };\n },\n });\n}\n"],"names":["registerRunnerBodySchema","registerRunnerResponseSchema","ClientError","defineRoute","EmptyRunnerLabelsError","RegistrationTokenConsumedError","RegistrationTokenExpiredError","registerRunnerSession","getRunnerContext","createEphemeralRegisterRateLimitPreHandler","createRegisterRoute","auth","method","path","description","schema","body","response","preHandler","errorHandler","error","request","message","status","runner","kind","log","warn","ephemeralTokenId","provisionerId","handler","result","credential","registrationTokenId","workspaceId","
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/routes/register.ts"],"sourcesContent":["import type {AuthInterModuleClient} from '@shipfox/api-auth-dto/inter-module';\nimport {registerRunnerBodySchema, registerRunnerResponseSchema} from '@shipfox/api-runners-dto';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {\n EmptyRunnerLabelsError,\n RegistrationTokenConsumedError,\n RegistrationTokenExpiredError,\n RunnerLabelsReservedError,\n} from '#core/errors.js';\nimport {registerRunnerSession} from '#core/runner-sessions.js';\nimport {getRunnerContext} from '#presentation/auth/index.js';\nimport {createEphemeralRegisterRateLimitPreHandler} from './rate-limit.js';\n\nexport function createRegisterRoute(auth: AuthInterModuleClient) {\n return defineRoute({\n method: 'POST',\n path: '/register',\n description: 'Exchange a runner registration token for a runner session token',\n schema: {\n body: registerRunnerBodySchema,\n response: {\n 200: registerRunnerResponseSchema,\n },\n },\n preHandler: createEphemeralRegisterRateLimitPreHandler(),\n errorHandler: (error, request) => {\n if (error instanceof RunnerLabelsReservedError) {\n throw new ClientError(error.message, 'runner-labels-reserved', {\n details: {labels: error.labels},\n status: 400,\n });\n }\n if (error instanceof EmptyRunnerLabelsError) {\n throw new ClientError(error.message, 'empty-runner-labels', {status: 400});\n }\n if (error instanceof RegistrationTokenConsumedError) {\n const runner = getRunnerContext(request);\n if (runner.kind === 'ephemeral') {\n request.log.warn(\n {\n ephemeralTokenId: error.ephemeralTokenId,\n provisionerId: runner.provisionerId,\n },\n 'Ephemeral registration token reuse rejected',\n );\n }\n throw new ClientError(\n 'Registration token has already been consumed',\n 'registration-token-consumed',\n {\n status: 409,\n },\n );\n }\n if (error instanceof RegistrationTokenExpiredError) {\n throw new ClientError('Registration token has expired', 'registration-token-expired', {\n status: 401,\n });\n }\n throw error;\n },\n handler: async (request) => {\n const runner = getRunnerContext(request);\n const result = await registerRunnerSession({\n auth,\n credential:\n runner.kind === 'manual'\n ? {\n kind: 'manual',\n registrationTokenId: runner.registrationTokenId,\n workspaceId: runner.workspaceId,\n }\n : runner,\n labels: request.body.labels,\n toolCapabilities: request.body.capabilities ?? null,\n });\n\n return {\n session_token: result.sessionToken,\n session_id: result.session.id,\n mode: result.mode,\n max_claims: result.maxClaims,\n };\n },\n });\n}\n"],"names":["registerRunnerBodySchema","registerRunnerResponseSchema","ClientError","defineRoute","EmptyRunnerLabelsError","RegistrationTokenConsumedError","RegistrationTokenExpiredError","RunnerLabelsReservedError","registerRunnerSession","getRunnerContext","createEphemeralRegisterRateLimitPreHandler","createRegisterRoute","auth","method","path","description","schema","body","response","preHandler","errorHandler","error","request","message","details","labels","status","runner","kind","log","warn","ephemeralTokenId","provisionerId","handler","result","credential","registrationTokenId","workspaceId","toolCapabilities","capabilities","session_token","sessionToken","session_id","session","id","mode","max_claims","maxClaims"],"mappings":"AACA,SAAQA,wBAAwB,EAAEC,4BAA4B,QAAO,2BAA2B;AAChG,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SACEC,sBAAsB,EACtBC,8BAA8B,EAC9BC,6BAA6B,EAC7BC,yBAAyB,QACpB,kBAAkB;AACzB,SAAQC,qBAAqB,QAAO,2BAA2B;AAC/D,SAAQC,gBAAgB,QAAO,8BAA8B;AAC7D,SAAQC,0CAA0C,QAAO,kBAAkB;AAE3E,OAAO,SAASC,oBAAoBC,IAA2B;IAC7D,OAAOT,YAAY;QACjBU,QAAQ;QACRC,MAAM;QACNC,aAAa;QACbC,QAAQ;YACNC,MAAMjB;YACNkB,UAAU;gBACR,KAAKjB;YACP;QACF;QACAkB,YAAYT;QACZU,cAAc,CAACC,OAAOC;YACpB,IAAID,iBAAiBd,2BAA2B;gBAC9C,MAAM,IAAIL,YAAYmB,MAAME,OAAO,EAAE,0BAA0B;oBAC7DC,SAAS;wBAACC,QAAQJ,MAAMI,MAAM;oBAAA;oBAC9BC,QAAQ;gBACV;YACF;YACA,IAAIL,iBAAiBjB,wBAAwB;gBAC3C,MAAM,IAAIF,YAAYmB,MAAME,OAAO,EAAE,uBAAuB;oBAACG,QAAQ;gBAAG;YAC1E;YACA,IAAIL,iBAAiBhB,gCAAgC;gBACnD,MAAMsB,SAASlB,iBAAiBa;gBAChC,IAAIK,OAAOC,IAAI,KAAK,aAAa;oBAC/BN,QAAQO,GAAG,CAACC,IAAI,CACd;wBACEC,kBAAkBV,MAAMU,gBAAgB;wBACxCC,eAAeL,OAAOK,aAAa;oBACrC,GACA;gBAEJ;gBACA,MAAM,IAAI9B,YACR,gDACA,+BACA;oBACEwB,QAAQ;gBACV;YAEJ;YACA,IAAIL,iBAAiBf,+BAA+B;gBAClD,MAAM,IAAIJ,YAAY,kCAAkC,8BAA8B;oBACpFwB,QAAQ;gBACV;YACF;YACA,MAAML;QACR;QACAY,SAAS,OAAOX;YACd,MAAMK,SAASlB,iBAAiBa;YAChC,MAAMY,SAAS,MAAM1B,sBAAsB;gBACzCI;gBACAuB,YACER,OAAOC,IAAI,KAAK,WACZ;oBACEA,MAAM;oBACNQ,qBAAqBT,OAAOS,mBAAmB;oBAC/CC,aAAaV,OAAOU,WAAW;gBACjC,IACAV;gBACNF,QAAQH,QAAQL,IAAI,CAACQ,MAAM;gBAC3Ba,kBAAkBhB,QAAQL,IAAI,CAACsB,YAAY,IAAI;YACjD;YAEA,OAAO;gBACLC,eAAeN,OAAOO,YAAY;gBAClCC,YAAYR,OAAOS,OAAO,CAACC,EAAE;gBAC7BC,MAAMX,OAAOW,IAAI;gBACjBC,YAAYZ,OAAOa,SAAS;YAC9B;QACF;IACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report-runner-instances.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/report-runner-instances.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,0BAA0B,
|
|
1
|
+
{"version":3,"file":"report-runner-instances.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/report-runner-instances.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,0BAA0B,iDA+BrC,CAAC"}
|
|
@@ -16,6 +16,7 @@ export const reportRunnerInstancesRoute = defineRoute({
|
|
|
16
16
|
handler: async (request)=>{
|
|
17
17
|
const context = requireProvisionerContext(request);
|
|
18
18
|
const result = await reportRunnerInstances({
|
|
19
|
+
scope: context.scope,
|
|
19
20
|
workspaceId: context.scope === 'workspace' ? context.workspaceId : null,
|
|
20
21
|
provisionerId: context.provisionerTokenId,
|
|
21
22
|
events: request.body.events.map((event)=>({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/routes/report-runner-instances.ts"],"sourcesContent":["import {requireProvisionerContext} from '@shipfox/api-auth-context';\nimport {\n reportRunnerInstancesBodySchema,\n reportRunnerInstancesResponseSchema,\n} from '@shipfox/api-runners-dto';\nimport {defineRoute} from '@shipfox/node-fastify';\nimport {reportRunnerInstances} from '#core/index.js';\nimport {toReportRunnerInstancesResponseDto} from '#presentation/dto/index.js';\n\nexport const reportRunnerInstancesRoute = defineRoute({\n method: 'POST',\n path: '/runner-instances/report',\n description: 'Report provisioned runner lifecycle state',\n schema: {\n body: reportRunnerInstancesBodySchema,\n response: {\n 200: reportRunnerInstancesResponseSchema,\n },\n },\n handler: async (request) => {\n const context = requireProvisionerContext(request);\n const result = await reportRunnerInstances({\n workspaceId: context.scope === 'workspace' ? context.workspaceId : null,\n provisionerId: context.provisionerTokenId,\n events: request.body.events.map((event) => ({\n providerRunnerId: event.provider_runner_id,\n reservationId: event.reservation_id ?? null,\n templateKey: event.template_key ?? null,\n labels: event.labels,\n state: event.state,\n reason: event.reason ?? null,\n runnerSessionId: event.runner_session_id ?? null,\n providerKind: event.provider_kind ?? null,\n reportedAt: new Date(event.reported_at),\n })),\n });\n\n return toReportRunnerInstancesResponseDto(result);\n },\n});\n"],"names":["requireProvisionerContext","reportRunnerInstancesBodySchema","reportRunnerInstancesResponseSchema","defineRoute","reportRunnerInstances","toReportRunnerInstancesResponseDto","reportRunnerInstancesRoute","method","path","description","schema","body","response","handler","request","context","result","
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/routes/report-runner-instances.ts"],"sourcesContent":["import {requireProvisionerContext} from '@shipfox/api-auth-context';\nimport {\n reportRunnerInstancesBodySchema,\n reportRunnerInstancesResponseSchema,\n} from '@shipfox/api-runners-dto';\nimport {defineRoute} from '@shipfox/node-fastify';\nimport {reportRunnerInstances} from '#core/index.js';\nimport {toReportRunnerInstancesResponseDto} from '#presentation/dto/index.js';\n\nexport const reportRunnerInstancesRoute = defineRoute({\n method: 'POST',\n path: '/runner-instances/report',\n description: 'Report provisioned runner lifecycle state',\n schema: {\n body: reportRunnerInstancesBodySchema,\n response: {\n 200: reportRunnerInstancesResponseSchema,\n },\n },\n handler: async (request) => {\n const context = requireProvisionerContext(request);\n const result = await reportRunnerInstances({\n scope: context.scope,\n workspaceId: context.scope === 'workspace' ? context.workspaceId : null,\n provisionerId: context.provisionerTokenId,\n events: request.body.events.map((event) => ({\n providerRunnerId: event.provider_runner_id,\n reservationId: event.reservation_id ?? null,\n templateKey: event.template_key ?? null,\n labels: event.labels,\n state: event.state,\n reason: event.reason ?? null,\n runnerSessionId: event.runner_session_id ?? null,\n providerKind: event.provider_kind ?? null,\n reportedAt: new Date(event.reported_at),\n })),\n });\n\n return toReportRunnerInstancesResponseDto(result);\n },\n});\n"],"names":["requireProvisionerContext","reportRunnerInstancesBodySchema","reportRunnerInstancesResponseSchema","defineRoute","reportRunnerInstances","toReportRunnerInstancesResponseDto","reportRunnerInstancesRoute","method","path","description","schema","body","response","handler","request","context","result","scope","workspaceId","provisionerId","provisionerTokenId","events","map","event","providerRunnerId","provider_runner_id","reservationId","reservation_id","templateKey","template_key","labels","state","reason","runnerSessionId","runner_session_id","providerKind","provider_kind","reportedAt","Date","reported_at"],"mappings":"AAAA,SAAQA,yBAAyB,QAAO,4BAA4B;AACpE,SACEC,+BAA+B,EAC/BC,mCAAmC,QAC9B,2BAA2B;AAClC,SAAQC,WAAW,QAAO,wBAAwB;AAClD,SAAQC,qBAAqB,QAAO,iBAAiB;AACrD,SAAQC,kCAAkC,QAAO,6BAA6B;AAE9E,OAAO,MAAMC,6BAA6BH,YAAY;IACpDI,QAAQ;IACRC,MAAM;IACNC,aAAa;IACbC,QAAQ;QACNC,MAAMV;QACNW,UAAU;YACR,KAAKV;QACP;IACF;IACAW,SAAS,OAAOC;QACd,MAAMC,UAAUf,0BAA0Bc;QAC1C,MAAME,SAAS,MAAMZ,sBAAsB;YACzCa,OAAOF,QAAQE,KAAK;YACpBC,aAAaH,QAAQE,KAAK,KAAK,cAAcF,QAAQG,WAAW,GAAG;YACnEC,eAAeJ,QAAQK,kBAAkB;YACzCC,QAAQP,QAAQH,IAAI,CAACU,MAAM,CAACC,GAAG,CAAC,CAACC,QAAW,CAAA;oBAC1CC,kBAAkBD,MAAME,kBAAkB;oBAC1CC,eAAeH,MAAMI,cAAc,IAAI;oBACvCC,aAAaL,MAAMM,YAAY,IAAI;oBACnCC,QAAQP,MAAMO,MAAM;oBACpBC,OAAOR,MAAMQ,KAAK;oBAClBC,QAAQT,MAAMS,MAAM,IAAI;oBACxBC,iBAAiBV,MAAMW,iBAAiB,IAAI;oBAC5CC,cAAcZ,MAAMa,aAAa,IAAI;oBACrCC,YAAY,IAAIC,KAAKf,MAAMgB,WAAW;gBACxC,CAAA;QACF;QAEA,OAAOlC,mCAAmCW;IAC5C;AACF,GAAG"}
|