@ebay-dt/bonfire-node 0.2.0-alpha.0 → 0.2.0-alpha.10

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 (32) hide show
  1. package/dist/deployment-provider.d.ts +1 -0
  2. package/dist/deployment-provider.d.ts.map +1 -1
  3. package/dist/deployment-provider.js +1 -0
  4. package/dist/deployment-provider.js.map +1 -1
  5. package/dist/git-repository.d.ts +3 -1
  6. package/dist/git-repository.d.ts.map +1 -1
  7. package/dist/git-repository.js +30 -0
  8. package/dist/git-repository.js.map +1 -1
  9. package/dist/github-cli.d.ts.map +1 -1
  10. package/dist/github-cli.js +3 -1
  11. package/dist/github-cli.js.map +1 -1
  12. package/dist/lifecycle/deployment-worktree.d.ts +35 -0
  13. package/dist/lifecycle/deployment-worktree.d.ts.map +1 -0
  14. package/dist/lifecycle/deployment-worktree.js +302 -0
  15. package/dist/lifecycle/deployment-worktree.js.map +1 -0
  16. package/dist/lifecycle/local-prototype-lifecycle.d.ts +2 -0
  17. package/dist/lifecycle/local-prototype-lifecycle.d.ts.map +1 -1
  18. package/dist/lifecycle/local-prototype-lifecycle.js +170 -54
  19. package/dist/lifecycle/local-prototype-lifecycle.js.map +1 -1
  20. package/dist/lifecycle/repository-summary.d.ts +2 -0
  21. package/dist/lifecycle/repository-summary.d.ts.map +1 -1
  22. package/dist/lifecycle/repository-summary.js +14 -4
  23. package/dist/lifecycle/repository-summary.js.map +1 -1
  24. package/dist/lifecycle/repository-synchronization.d.ts +1 -0
  25. package/dist/lifecycle/repository-synchronization.d.ts.map +1 -1
  26. package/dist/lifecycle/repository-synchronization.js +54 -10
  27. package/dist/lifecycle/repository-synchronization.js.map +1 -1
  28. package/dist/lifecycle/runtime-context.d.ts +2 -0
  29. package/dist/lifecycle/runtime-context.d.ts.map +1 -1
  30. package/dist/lifecycle/runtime-context.js +5 -1
  31. package/dist/lifecycle/runtime-context.js.map +1 -1
  32. package/package.json +2 -2
@@ -6,6 +6,7 @@ import type { DeploymentReadyState } from "@ebay-dt/bonfire-core";
6
6
  export type DeploymentProviderResult = {
7
7
  url: string;
8
8
  deploymentId: string;
9
+ commitSha: string;
9
10
  readyState: DeploymentReadyState;
10
11
  };
11
12
  /** Server-side transport boundary for creating an application deployment. */
@@ -1 +1 @@
1
- {"version":3,"file":"deployment-provider.d.ts","sourceRoot":"","sources":["../src/deployment-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAQlE;;;GAGG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,oBAAoB,CAAC;CAClC,CAAC;AAEF,6EAA6E;AAC7E,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,KAAK,EAAE;QACZ,aAAa,EAAE,MAAM,CAAC;QACtB,KAAK,EAAE,MAAM,CAAC;KACf,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAC;CACvC;AAED,MAAM,MAAM,+BAA+B,GAAG;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,2BAA2B,GACnC,SAAS,GACT,iBAAiB,GACjB,kBAAkB,GAClB,aAAa,GACb,uBAAuB,CAAC;AAE5B;;;GAGG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,IAAI,EAAE,2BAA2B,CAAC;IAE3C,YAAY,IAAI,EAAE,2BAA2B,EAI5C;CACF;AA8KD,wBAAgB,wBAAwB,CAAC,EACvC,QAAQ,EAAE,kBAAkB,EAC5B,SAAS,EAAE,iBAAiB,GAC7B,EAAE,+BAA+B,GAAG,kBAAkB,CA2CtD","sourcesContent":["import type { DeploymentReadyState } from \"@ebay-dt/bonfire-core\";\nimport { isDeploymentReadyState } from \"@ebay-dt/bonfire-core\";\nimport { isRecord } from \"./internal/utils.js\";\n\nconst DEFAULT_TIMEOUT_MS = 120_000;\nconst MAX_RESPONSE_STRING_LENGTH = 2_000;\nconst MAX_RESPONSE_URL_LENGTH = 8_192;\n\n/**\n * The normalized result returned by a deployment provider. `url` is the\n * deployment-root URL; the lifecycle service adds the active prototype route.\n */\nexport type DeploymentProviderResult = {\n url: string;\n deploymentId: string;\n readyState: DeploymentReadyState;\n};\n\n/** Server-side transport boundary for creating an application deployment. */\nexport interface DeploymentProvider {\n deploy(input: {\n applicationId: string;\n login: string;\n }): Promise<DeploymentProviderResult>;\n}\n\nexport type CreateDeploymentProviderOptions = {\n endpoint: string;\n timeoutMs?: number;\n};\n\nexport type DeploymentProviderErrorKind =\n | \"timeout\"\n | \"service-failure\"\n | \"invalid-response\"\n | \"invalid-url\"\n | \"invalid-configuration\";\n\n/**\n * Safe provider failure classification. It intentionally carries no response\n * body, endpoint credentials, or unbounded service diagnostics.\n */\nexport class DeploymentProviderError extends Error {\n readonly kind: DeploymentProviderErrorKind;\n\n constructor(kind: DeploymentProviderErrorKind) {\n super(\"Deployment provider request failed\");\n this.name = \"DeploymentProviderError\";\n this.kind = kind;\n }\n}\n\n/**\n * Internal DT deployment success wire shape. The service diagnostics are\n * validated at the transport boundary but are not exposed as client data.\n */\ntype DeploymentServiceSuccessResponse = {\n repository: string;\n ref: string;\n commitSha: string;\n sourceFileCount: number;\n project: string;\n deployed: true;\n deploymentId: string;\n url: string;\n readyState: DeploymentReadyState;\n};\n\n/** Internal DT deployment failure wire shape. */\ntype DeploymentServiceFailureResponse = {\n repository: string;\n ref?: string;\n commitSha?: string;\n project?: string;\n stage?: string;\n deployed: false;\n statusCode: number;\n details: string;\n};\n\ntype DeploymentServiceResponse =\n DeploymentServiceSuccessResponse | DeploymentServiceFailureResponse;\n\nfunction isBoundedString(\n value: unknown,\n options: { allowEmpty?: boolean; maxLength?: number } = {},\n): value is string {\n if (typeof value !== \"string\") return false;\n if (!options.allowEmpty && value.length === 0) return false;\n return value.length <= (options.maxLength ?? MAX_RESPONSE_STRING_LENGTH);\n}\n\nfunction validateEndpoint(value: unknown): string {\n if (\n typeof value !== \"string\" ||\n value.length === 0 ||\n value.trim() !== value\n ) {\n throw new DeploymentProviderError(\"invalid-configuration\");\n }\n\n let endpoint: URL;\n try {\n endpoint = new URL(value);\n } catch {\n throw new DeploymentProviderError(\"invalid-configuration\");\n }\n\n if (\n (endpoint.protocol !== \"http:\" && endpoint.protocol !== \"https:\") ||\n endpoint.username !== \"\" ||\n endpoint.password !== \"\" ||\n endpoint.search !== \"\" ||\n endpoint.hash !== \"\"\n ) {\n throw new DeploymentProviderError(\"invalid-configuration\");\n }\n\n return value;\n}\n\nfunction validateTimeout(value: unknown): number {\n if (\n value === undefined ||\n (typeof value === \"number\" &&\n Number.isFinite(value) &&\n Number.isInteger(value) &&\n value > 0)\n ) {\n return value ?? DEFAULT_TIMEOUT_MS;\n }\n\n throw new DeploymentProviderError(\"invalid-configuration\");\n}\n\nfunction parseDeploymentUrl(value: unknown): string | null {\n if (!isBoundedString(value, { maxLength: MAX_RESPONSE_URL_LENGTH })) {\n return null;\n }\n\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return null;\n }\n\n if (url.protocol !== \"https:\" || url.username !== \"\" || url.password !== \"\") {\n return null;\n }\n\n return url.toString().replace(/\\/$/, \"\");\n}\n\nfunction isDeploymentServiceSuccessResponse(\n value: unknown,\n): value is DeploymentServiceSuccessResponse {\n if (!isRecord(value)) return false;\n\n return (\n isBoundedString(value.repository) &&\n isBoundedString(value.ref) &&\n isBoundedString(value.commitSha) &&\n Number.isInteger(value.sourceFileCount) &&\n Number(value.sourceFileCount) >= 0 &&\n isBoundedString(value.project) &&\n value.deployed === true &&\n isBoundedString(value.deploymentId) &&\n isBoundedString(value.url, { maxLength: MAX_RESPONSE_URL_LENGTH }) &&\n isDeploymentReadyState(value.readyState)\n );\n}\n\nfunction isDeploymentServiceFailureResponse(\n value: unknown,\n): value is DeploymentServiceFailureResponse {\n if (!isRecord(value)) return false;\n\n const optionalStringFields = [\"ref\", \"commitSha\", \"project\", \"stage\"];\n return (\n isBoundedString(value.repository) &&\n value.deployed === false &&\n Number.isInteger(value.statusCode) &&\n Number(value.statusCode) >= 0 &&\n isBoundedString(value.details) &&\n optionalStringFields.every(\n (field) => !Object.hasOwn(value, field) || isBoundedString(value[field]),\n )\n );\n}\n\nasync function readResponse(response: Response): Promise<unknown> {\n try {\n return await response.json();\n } catch {\n throw new DeploymentProviderError(\"invalid-response\");\n }\n}\n\nfunction parseSuccessResponse(value: unknown): DeploymentProviderResult {\n if (!isDeploymentServiceSuccessResponse(value)) {\n throw new DeploymentProviderError(\"invalid-response\");\n }\n\n const url = parseDeploymentUrl(value.url);\n if (url === null) {\n throw new DeploymentProviderError(\"invalid-url\");\n }\n\n return {\n url,\n deploymentId: value.deploymentId,\n readyState: value.readyState,\n };\n}\n\nfunction parseFailureResponse(value: unknown): never {\n if (!isDeploymentServiceFailureResponse(value)) {\n throw new DeploymentProviderError(\"invalid-response\");\n }\n\n throw new DeploymentProviderError(\"service-failure\");\n}\n\nexport function createDeploymentProvider({\n endpoint: configuredEndpoint,\n timeoutMs: configuredTimeout,\n}: CreateDeploymentProviderOptions): DeploymentProvider {\n const endpoint = validateEndpoint(configuredEndpoint);\n const timeoutMs = validateTimeout(configuredTimeout);\n const fetchImplementation = globalThis.fetch;\n\n if (typeof fetchImplementation !== \"function\") {\n throw new DeploymentProviderError(\"invalid-configuration\");\n }\n\n return {\n async deploy(input) {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n\n let response: Response;\n try {\n response = await fetchImplementation(endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n applicationId: input.applicationId,\n login: input.login,\n }),\n cache: \"no-store\",\n signal: controller.signal,\n });\n } catch {\n if (controller.signal.aborted) {\n throw new DeploymentProviderError(\"timeout\");\n }\n throw new DeploymentProviderError(\"service-failure\");\n } finally {\n clearTimeout(timeout);\n }\n\n const payload = await readResponse(response);\n if (response.status === 201) {\n return parseSuccessResponse(payload);\n }\n\n return parseFailureResponse(payload);\n },\n };\n}\n"]}
1
+ {"version":3,"file":"deployment-provider.d.ts","sourceRoot":"","sources":["../src/deployment-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAQlE;;;GAGG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,oBAAoB,CAAC;CAClC,CAAC;AAEF,6EAA6E;AAC7E,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,KAAK,EAAE;QACZ,aAAa,EAAE,MAAM,CAAC;QACtB,KAAK,EAAE,MAAM,CAAC;KACf,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAC;CACvC;AAED,MAAM,MAAM,+BAA+B,GAAG;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,2BAA2B,GACnC,SAAS,GACT,iBAAiB,GACjB,kBAAkB,GAClB,aAAa,GACb,uBAAuB,CAAC;AAE5B;;;GAGG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,IAAI,EAAE,2BAA2B,CAAC;IAE3C,YAAY,IAAI,EAAE,2BAA2B,EAI5C;CACF;AA+KD,wBAAgB,wBAAwB,CAAC,EACvC,QAAQ,EAAE,kBAAkB,EAC5B,SAAS,EAAE,iBAAiB,GAC7B,EAAE,+BAA+B,GAAG,kBAAkB,CA2CtD","sourcesContent":["import type { DeploymentReadyState } from \"@ebay-dt/bonfire-core\";\nimport { isDeploymentReadyState } from \"@ebay-dt/bonfire-core\";\nimport { isRecord } from \"./internal/utils.js\";\n\nconst DEFAULT_TIMEOUT_MS = 120_000;\nconst MAX_RESPONSE_STRING_LENGTH = 2_000;\nconst MAX_RESPONSE_URL_LENGTH = 8_192;\n\n/**\n * The normalized result returned by a deployment provider. `url` is the\n * deployment-root URL; the lifecycle service adds the active prototype route.\n */\nexport type DeploymentProviderResult = {\n url: string;\n deploymentId: string;\n commitSha: string;\n readyState: DeploymentReadyState;\n};\n\n/** Server-side transport boundary for creating an application deployment. */\nexport interface DeploymentProvider {\n deploy(input: {\n applicationId: string;\n login: string;\n }): Promise<DeploymentProviderResult>;\n}\n\nexport type CreateDeploymentProviderOptions = {\n endpoint: string;\n timeoutMs?: number;\n};\n\nexport type DeploymentProviderErrorKind =\n | \"timeout\"\n | \"service-failure\"\n | \"invalid-response\"\n | \"invalid-url\"\n | \"invalid-configuration\";\n\n/**\n * Safe provider failure classification. It intentionally carries no response\n * body, endpoint credentials, or unbounded service diagnostics.\n */\nexport class DeploymentProviderError extends Error {\n readonly kind: DeploymentProviderErrorKind;\n\n constructor(kind: DeploymentProviderErrorKind) {\n super(\"Deployment provider request failed\");\n this.name = \"DeploymentProviderError\";\n this.kind = kind;\n }\n}\n\n/**\n * Internal DT deployment success wire shape. The service diagnostics are\n * validated at the transport boundary but are not exposed as client data.\n */\ntype DeploymentServiceSuccessResponse = {\n repository: string;\n ref: string;\n commitSha: string;\n sourceFileCount: number;\n project: string;\n deployed: true;\n deploymentId: string;\n url: string;\n readyState: DeploymentReadyState;\n};\n\n/** Internal DT deployment failure wire shape. */\ntype DeploymentServiceFailureResponse = {\n repository: string;\n ref?: string;\n commitSha?: string;\n project?: string;\n stage?: string;\n deployed: false;\n statusCode: number;\n details: string;\n};\n\ntype DeploymentServiceResponse =\n DeploymentServiceSuccessResponse | DeploymentServiceFailureResponse;\n\nfunction isBoundedString(\n value: unknown,\n options: { allowEmpty?: boolean; maxLength?: number } = {},\n): value is string {\n if (typeof value !== \"string\") return false;\n if (!options.allowEmpty && value.length === 0) return false;\n return value.length <= (options.maxLength ?? MAX_RESPONSE_STRING_LENGTH);\n}\n\nfunction validateEndpoint(value: unknown): string {\n if (\n typeof value !== \"string\" ||\n value.length === 0 ||\n value.trim() !== value\n ) {\n throw new DeploymentProviderError(\"invalid-configuration\");\n }\n\n let endpoint: URL;\n try {\n endpoint = new URL(value);\n } catch {\n throw new DeploymentProviderError(\"invalid-configuration\");\n }\n\n if (\n (endpoint.protocol !== \"http:\" && endpoint.protocol !== \"https:\") ||\n endpoint.username !== \"\" ||\n endpoint.password !== \"\" ||\n endpoint.search !== \"\" ||\n endpoint.hash !== \"\"\n ) {\n throw new DeploymentProviderError(\"invalid-configuration\");\n }\n\n return value;\n}\n\nfunction validateTimeout(value: unknown): number {\n if (\n value === undefined ||\n (typeof value === \"number\" &&\n Number.isFinite(value) &&\n Number.isInteger(value) &&\n value > 0)\n ) {\n return value ?? DEFAULT_TIMEOUT_MS;\n }\n\n throw new DeploymentProviderError(\"invalid-configuration\");\n}\n\nfunction parseDeploymentUrl(value: unknown): string | null {\n if (!isBoundedString(value, { maxLength: MAX_RESPONSE_URL_LENGTH })) {\n return null;\n }\n\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return null;\n }\n\n if (url.protocol !== \"https:\" || url.username !== \"\" || url.password !== \"\") {\n return null;\n }\n\n return url.toString().replace(/\\/$/, \"\");\n}\n\nfunction isDeploymentServiceSuccessResponse(\n value: unknown,\n): value is DeploymentServiceSuccessResponse {\n if (!isRecord(value)) return false;\n\n return (\n isBoundedString(value.repository) &&\n isBoundedString(value.ref) &&\n isBoundedString(value.commitSha) &&\n Number.isInteger(value.sourceFileCount) &&\n Number(value.sourceFileCount) >= 0 &&\n isBoundedString(value.project) &&\n value.deployed === true &&\n isBoundedString(value.deploymentId) &&\n isBoundedString(value.url, { maxLength: MAX_RESPONSE_URL_LENGTH }) &&\n isDeploymentReadyState(value.readyState)\n );\n}\n\nfunction isDeploymentServiceFailureResponse(\n value: unknown,\n): value is DeploymentServiceFailureResponse {\n if (!isRecord(value)) return false;\n\n const optionalStringFields = [\"ref\", \"commitSha\", \"project\", \"stage\"];\n return (\n isBoundedString(value.repository) &&\n value.deployed === false &&\n Number.isInteger(value.statusCode) &&\n Number(value.statusCode) >= 0 &&\n isBoundedString(value.details) &&\n optionalStringFields.every(\n (field) => !Object.hasOwn(value, field) || isBoundedString(value[field]),\n )\n );\n}\n\nasync function readResponse(response: Response): Promise<unknown> {\n try {\n return await response.json();\n } catch {\n throw new DeploymentProviderError(\"invalid-response\");\n }\n}\n\nfunction parseSuccessResponse(value: unknown): DeploymentProviderResult {\n if (!isDeploymentServiceSuccessResponse(value)) {\n throw new DeploymentProviderError(\"invalid-response\");\n }\n\n const url = parseDeploymentUrl(value.url);\n if (url === null) {\n throw new DeploymentProviderError(\"invalid-url\");\n }\n\n return {\n url,\n deploymentId: value.deploymentId,\n commitSha: value.commitSha,\n readyState: value.readyState,\n };\n}\n\nfunction parseFailureResponse(value: unknown): never {\n if (!isDeploymentServiceFailureResponse(value)) {\n throw new DeploymentProviderError(\"invalid-response\");\n }\n\n throw new DeploymentProviderError(\"service-failure\");\n}\n\nexport function createDeploymentProvider({\n endpoint: configuredEndpoint,\n timeoutMs: configuredTimeout,\n}: CreateDeploymentProviderOptions): DeploymentProvider {\n const endpoint = validateEndpoint(configuredEndpoint);\n const timeoutMs = validateTimeout(configuredTimeout);\n const fetchImplementation = globalThis.fetch;\n\n if (typeof fetchImplementation !== \"function\") {\n throw new DeploymentProviderError(\"invalid-configuration\");\n }\n\n return {\n async deploy(input) {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n\n let response: Response;\n try {\n response = await fetchImplementation(endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n applicationId: input.applicationId,\n login: input.login,\n }),\n cache: \"no-store\",\n signal: controller.signal,\n });\n } catch {\n if (controller.signal.aborted) {\n throw new DeploymentProviderError(\"timeout\");\n }\n throw new DeploymentProviderError(\"service-failure\");\n } finally {\n clearTimeout(timeout);\n }\n\n const payload = await readResponse(response);\n if (response.status === 201) {\n return parseSuccessResponse(payload);\n }\n\n return parseFailureResponse(payload);\n },\n };\n}\n"]}
@@ -114,6 +114,7 @@ function parseSuccessResponse(value) {
114
114
  return {
115
115
  url,
116
116
  deploymentId: value.deploymentId,
117
+ commitSha: value.commitSha,
117
118
  readyState: value.readyState,
118
119
  };
119
120
  }
@@ -1 +1 @@
1
- {"version":3,"file":"deployment-provider.js","sourceRoot":"","sources":["../src/deployment-provider.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DeploymentReadyState } from \"@ebay-dt/bonfire-core\";\nimport { isDeploymentReadyState } from \"@ebay-dt/bonfire-core\";\nimport { isRecord } from \"./internal/utils.js\";\n\nconst DEFAULT_TIMEOUT_MS = 120_000;\nconst MAX_RESPONSE_STRING_LENGTH = 2_000;\nconst MAX_RESPONSE_URL_LENGTH = 8_192;\n\n/**\n * The normalized result returned by a deployment provider. `url` is the\n * deployment-root URL; the lifecycle service adds the active prototype route.\n */\nexport type DeploymentProviderResult = {\n url: string;\n deploymentId: string;\n readyState: DeploymentReadyState;\n};\n\n/** Server-side transport boundary for creating an application deployment. */\nexport interface DeploymentProvider {\n deploy(input: {\n applicationId: string;\n login: string;\n }): Promise<DeploymentProviderResult>;\n}\n\nexport type CreateDeploymentProviderOptions = {\n endpoint: string;\n timeoutMs?: number;\n};\n\nexport type DeploymentProviderErrorKind =\n | \"timeout\"\n | \"service-failure\"\n | \"invalid-response\"\n | \"invalid-url\"\n | \"invalid-configuration\";\n\n/**\n * Safe provider failure classification. It intentionally carries no response\n * body, endpoint credentials, or unbounded service diagnostics.\n */\nexport class DeploymentProviderError extends Error {\n readonly kind: DeploymentProviderErrorKind;\n\n constructor(kind: DeploymentProviderErrorKind) {\n super(\"Deployment provider request failed\");\n this.name = \"DeploymentProviderError\";\n this.kind = kind;\n }\n}\n\n/**\n * Internal DT deployment success wire shape. The service diagnostics are\n * validated at the transport boundary but are not exposed as client data.\n */\ntype DeploymentServiceSuccessResponse = {\n repository: string;\n ref: string;\n commitSha: string;\n sourceFileCount: number;\n project: string;\n deployed: true;\n deploymentId: string;\n url: string;\n readyState: DeploymentReadyState;\n};\n\n/** Internal DT deployment failure wire shape. */\ntype DeploymentServiceFailureResponse = {\n repository: string;\n ref?: string;\n commitSha?: string;\n project?: string;\n stage?: string;\n deployed: false;\n statusCode: number;\n details: string;\n};\n\ntype DeploymentServiceResponse =\n DeploymentServiceSuccessResponse | DeploymentServiceFailureResponse;\n\nfunction isBoundedString(\n value: unknown,\n options: { allowEmpty?: boolean; maxLength?: number } = {},\n): value is string {\n if (typeof value !== \"string\") return false;\n if (!options.allowEmpty && value.length === 0) return false;\n return value.length <= (options.maxLength ?? MAX_RESPONSE_STRING_LENGTH);\n}\n\nfunction validateEndpoint(value: unknown): string {\n if (\n typeof value !== \"string\" ||\n value.length === 0 ||\n value.trim() !== value\n ) {\n throw new DeploymentProviderError(\"invalid-configuration\");\n }\n\n let endpoint: URL;\n try {\n endpoint = new URL(value);\n } catch {\n throw new DeploymentProviderError(\"invalid-configuration\");\n }\n\n if (\n (endpoint.protocol !== \"http:\" && endpoint.protocol !== \"https:\") ||\n endpoint.username !== \"\" ||\n endpoint.password !== \"\" ||\n endpoint.search !== \"\" ||\n endpoint.hash !== \"\"\n ) {\n throw new DeploymentProviderError(\"invalid-configuration\");\n }\n\n return value;\n}\n\nfunction validateTimeout(value: unknown): number {\n if (\n value === undefined ||\n (typeof value === \"number\" &&\n Number.isFinite(value) &&\n Number.isInteger(value) &&\n value > 0)\n ) {\n return value ?? DEFAULT_TIMEOUT_MS;\n }\n\n throw new DeploymentProviderError(\"invalid-configuration\");\n}\n\nfunction parseDeploymentUrl(value: unknown): string | null {\n if (!isBoundedString(value, { maxLength: MAX_RESPONSE_URL_LENGTH })) {\n return null;\n }\n\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return null;\n }\n\n if (url.protocol !== \"https:\" || url.username !== \"\" || url.password !== \"\") {\n return null;\n }\n\n return url.toString().replace(/\\/$/, \"\");\n}\n\nfunction isDeploymentServiceSuccessResponse(\n value: unknown,\n): value is DeploymentServiceSuccessResponse {\n if (!isRecord(value)) return false;\n\n return (\n isBoundedString(value.repository) &&\n isBoundedString(value.ref) &&\n isBoundedString(value.commitSha) &&\n Number.isInteger(value.sourceFileCount) &&\n Number(value.sourceFileCount) >= 0 &&\n isBoundedString(value.project) &&\n value.deployed === true &&\n isBoundedString(value.deploymentId) &&\n isBoundedString(value.url, { maxLength: MAX_RESPONSE_URL_LENGTH }) &&\n isDeploymentReadyState(value.readyState)\n );\n}\n\nfunction isDeploymentServiceFailureResponse(\n value: unknown,\n): value is DeploymentServiceFailureResponse {\n if (!isRecord(value)) return false;\n\n const optionalStringFields = [\"ref\", \"commitSha\", \"project\", \"stage\"];\n return (\n isBoundedString(value.repository) &&\n value.deployed === false &&\n Number.isInteger(value.statusCode) &&\n Number(value.statusCode) >= 0 &&\n isBoundedString(value.details) &&\n optionalStringFields.every(\n (field) => !Object.hasOwn(value, field) || isBoundedString(value[field]),\n )\n );\n}\n\nasync function readResponse(response: Response): Promise<unknown> {\n try {\n return await response.json();\n } catch {\n throw new DeploymentProviderError(\"invalid-response\");\n }\n}\n\nfunction parseSuccessResponse(value: unknown): DeploymentProviderResult {\n if (!isDeploymentServiceSuccessResponse(value)) {\n throw new DeploymentProviderError(\"invalid-response\");\n }\n\n const url = parseDeploymentUrl(value.url);\n if (url === null) {\n throw new DeploymentProviderError(\"invalid-url\");\n }\n\n return {\n url,\n deploymentId: value.deploymentId,\n readyState: value.readyState,\n };\n}\n\nfunction parseFailureResponse(value: unknown): never {\n if (!isDeploymentServiceFailureResponse(value)) {\n throw new DeploymentProviderError(\"invalid-response\");\n }\n\n throw new DeploymentProviderError(\"service-failure\");\n}\n\nexport function createDeploymentProvider({\n endpoint: configuredEndpoint,\n timeoutMs: configuredTimeout,\n}: CreateDeploymentProviderOptions): DeploymentProvider {\n const endpoint = validateEndpoint(configuredEndpoint);\n const timeoutMs = validateTimeout(configuredTimeout);\n const fetchImplementation = globalThis.fetch;\n\n if (typeof fetchImplementation !== \"function\") {\n throw new DeploymentProviderError(\"invalid-configuration\");\n }\n\n return {\n async deploy(input) {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n\n let response: Response;\n try {\n response = await fetchImplementation(endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n applicationId: input.applicationId,\n login: input.login,\n }),\n cache: \"no-store\",\n signal: controller.signal,\n });\n } catch {\n if (controller.signal.aborted) {\n throw new DeploymentProviderError(\"timeout\");\n }\n throw new DeploymentProviderError(\"service-failure\");\n } finally {\n clearTimeout(timeout);\n }\n\n const payload = await readResponse(response);\n if (response.status === 201) {\n return parseSuccessResponse(payload);\n }\n\n return parseFailureResponse(payload);\n },\n };\n}\n"]}
1
+ {"version":3,"file":"deployment-provider.js","sourceRoot":"","sources":["../src/deployment-provider.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DeploymentReadyState } from \"@ebay-dt/bonfire-core\";\nimport { isDeploymentReadyState } from \"@ebay-dt/bonfire-core\";\nimport { isRecord } from \"./internal/utils.js\";\n\nconst DEFAULT_TIMEOUT_MS = 120_000;\nconst MAX_RESPONSE_STRING_LENGTH = 2_000;\nconst MAX_RESPONSE_URL_LENGTH = 8_192;\n\n/**\n * The normalized result returned by a deployment provider. `url` is the\n * deployment-root URL; the lifecycle service adds the active prototype route.\n */\nexport type DeploymentProviderResult = {\n url: string;\n deploymentId: string;\n commitSha: string;\n readyState: DeploymentReadyState;\n};\n\n/** Server-side transport boundary for creating an application deployment. */\nexport interface DeploymentProvider {\n deploy(input: {\n applicationId: string;\n login: string;\n }): Promise<DeploymentProviderResult>;\n}\n\nexport type CreateDeploymentProviderOptions = {\n endpoint: string;\n timeoutMs?: number;\n};\n\nexport type DeploymentProviderErrorKind =\n | \"timeout\"\n | \"service-failure\"\n | \"invalid-response\"\n | \"invalid-url\"\n | \"invalid-configuration\";\n\n/**\n * Safe provider failure classification. It intentionally carries no response\n * body, endpoint credentials, or unbounded service diagnostics.\n */\nexport class DeploymentProviderError extends Error {\n readonly kind: DeploymentProviderErrorKind;\n\n constructor(kind: DeploymentProviderErrorKind) {\n super(\"Deployment provider request failed\");\n this.name = \"DeploymentProviderError\";\n this.kind = kind;\n }\n}\n\n/**\n * Internal DT deployment success wire shape. The service diagnostics are\n * validated at the transport boundary but are not exposed as client data.\n */\ntype DeploymentServiceSuccessResponse = {\n repository: string;\n ref: string;\n commitSha: string;\n sourceFileCount: number;\n project: string;\n deployed: true;\n deploymentId: string;\n url: string;\n readyState: DeploymentReadyState;\n};\n\n/** Internal DT deployment failure wire shape. */\ntype DeploymentServiceFailureResponse = {\n repository: string;\n ref?: string;\n commitSha?: string;\n project?: string;\n stage?: string;\n deployed: false;\n statusCode: number;\n details: string;\n};\n\ntype DeploymentServiceResponse =\n DeploymentServiceSuccessResponse | DeploymentServiceFailureResponse;\n\nfunction isBoundedString(\n value: unknown,\n options: { allowEmpty?: boolean; maxLength?: number } = {},\n): value is string {\n if (typeof value !== \"string\") return false;\n if (!options.allowEmpty && value.length === 0) return false;\n return value.length <= (options.maxLength ?? MAX_RESPONSE_STRING_LENGTH);\n}\n\nfunction validateEndpoint(value: unknown): string {\n if (\n typeof value !== \"string\" ||\n value.length === 0 ||\n value.trim() !== value\n ) {\n throw new DeploymentProviderError(\"invalid-configuration\");\n }\n\n let endpoint: URL;\n try {\n endpoint = new URL(value);\n } catch {\n throw new DeploymentProviderError(\"invalid-configuration\");\n }\n\n if (\n (endpoint.protocol !== \"http:\" && endpoint.protocol !== \"https:\") ||\n endpoint.username !== \"\" ||\n endpoint.password !== \"\" ||\n endpoint.search !== \"\" ||\n endpoint.hash !== \"\"\n ) {\n throw new DeploymentProviderError(\"invalid-configuration\");\n }\n\n return value;\n}\n\nfunction validateTimeout(value: unknown): number {\n if (\n value === undefined ||\n (typeof value === \"number\" &&\n Number.isFinite(value) &&\n Number.isInteger(value) &&\n value > 0)\n ) {\n return value ?? DEFAULT_TIMEOUT_MS;\n }\n\n throw new DeploymentProviderError(\"invalid-configuration\");\n}\n\nfunction parseDeploymentUrl(value: unknown): string | null {\n if (!isBoundedString(value, { maxLength: MAX_RESPONSE_URL_LENGTH })) {\n return null;\n }\n\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return null;\n }\n\n if (url.protocol !== \"https:\" || url.username !== \"\" || url.password !== \"\") {\n return null;\n }\n\n return url.toString().replace(/\\/$/, \"\");\n}\n\nfunction isDeploymentServiceSuccessResponse(\n value: unknown,\n): value is DeploymentServiceSuccessResponse {\n if (!isRecord(value)) return false;\n\n return (\n isBoundedString(value.repository) &&\n isBoundedString(value.ref) &&\n isBoundedString(value.commitSha) &&\n Number.isInteger(value.sourceFileCount) &&\n Number(value.sourceFileCount) >= 0 &&\n isBoundedString(value.project) &&\n value.deployed === true &&\n isBoundedString(value.deploymentId) &&\n isBoundedString(value.url, { maxLength: MAX_RESPONSE_URL_LENGTH }) &&\n isDeploymentReadyState(value.readyState)\n );\n}\n\nfunction isDeploymentServiceFailureResponse(\n value: unknown,\n): value is DeploymentServiceFailureResponse {\n if (!isRecord(value)) return false;\n\n const optionalStringFields = [\"ref\", \"commitSha\", \"project\", \"stage\"];\n return (\n isBoundedString(value.repository) &&\n value.deployed === false &&\n Number.isInteger(value.statusCode) &&\n Number(value.statusCode) >= 0 &&\n isBoundedString(value.details) &&\n optionalStringFields.every(\n (field) => !Object.hasOwn(value, field) || isBoundedString(value[field]),\n )\n );\n}\n\nasync function readResponse(response: Response): Promise<unknown> {\n try {\n return await response.json();\n } catch {\n throw new DeploymentProviderError(\"invalid-response\");\n }\n}\n\nfunction parseSuccessResponse(value: unknown): DeploymentProviderResult {\n if (!isDeploymentServiceSuccessResponse(value)) {\n throw new DeploymentProviderError(\"invalid-response\");\n }\n\n const url = parseDeploymentUrl(value.url);\n if (url === null) {\n throw new DeploymentProviderError(\"invalid-url\");\n }\n\n return {\n url,\n deploymentId: value.deploymentId,\n commitSha: value.commitSha,\n readyState: value.readyState,\n };\n}\n\nfunction parseFailureResponse(value: unknown): never {\n if (!isDeploymentServiceFailureResponse(value)) {\n throw new DeploymentProviderError(\"invalid-response\");\n }\n\n throw new DeploymentProviderError(\"service-failure\");\n}\n\nexport function createDeploymentProvider({\n endpoint: configuredEndpoint,\n timeoutMs: configuredTimeout,\n}: CreateDeploymentProviderOptions): DeploymentProvider {\n const endpoint = validateEndpoint(configuredEndpoint);\n const timeoutMs = validateTimeout(configuredTimeout);\n const fetchImplementation = globalThis.fetch;\n\n if (typeof fetchImplementation !== \"function\") {\n throw new DeploymentProviderError(\"invalid-configuration\");\n }\n\n return {\n async deploy(input) {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n\n let response: Response;\n try {\n response = await fetchImplementation(endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n applicationId: input.applicationId,\n login: input.login,\n }),\n cache: \"no-store\",\n signal: controller.signal,\n });\n } catch {\n if (controller.signal.aborted) {\n throw new DeploymentProviderError(\"timeout\");\n }\n throw new DeploymentProviderError(\"service-failure\");\n } finally {\n clearTimeout(timeout);\n }\n\n const payload = await readResponse(response);\n if (response.status === 201) {\n return parseSuccessResponse(payload);\n }\n\n return parseFailureResponse(payload);\n },\n };\n}\n"]}
@@ -37,7 +37,7 @@ export type GitCommitResult = GitCommitSnapshot | null;
37
37
  export type GitPushResult = {
38
38
  remoteStatus: "pushed" | "local-only";
39
39
  };
40
- export type GitRepositoryErrorKind = "missing-git" | "timeout" | "conflict" | "command-failure" | "malformed-output" | "invalid-configuration" | "unexpected";
40
+ export type GitRepositoryErrorKind = "missing-git" | "timeout" | "conflict" | "non-fast-forward" | "command-failure" | "malformed-output" | "invalid-configuration" | "unexpected";
41
41
  export declare class GitRepositoryError extends Error {
42
42
  readonly kind: GitRepositoryErrorKind;
43
43
  readonly operation: string;
@@ -68,6 +68,8 @@ export type PrepareIdentityBranchOptions = {
68
68
  export declare function prepareIdentityBranch(repository: GitRepository, options: PrepareIdentityBranchOptions): Promise<void>;
69
69
  export declare function commitPath(repository: GitRepository, pathspec: string, message: string): Promise<GitCommitResult>;
70
70
  export declare function discardPath(repository: GitRepository, pathspec: string): Promise<void>;
71
+ export declare function restorePathFromRef(repository: GitRepository, ref: string, pathspec: string): Promise<void>;
72
+ export declare function pushCommitToBranch(repository: GitRepository, remote: string, commit: string, branch: string): Promise<GitPushResult>;
71
73
  export declare function pushBranch(repository: GitRepository, remote: string, branch: string): Promise<GitPushResult>;
72
74
  export declare function createGitRepository(options: GitRepositoryOptions): GitRepository;
73
75
  //# sourceMappingURL=git-repository.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"git-repository.d.ts","sourceRoot":"","sources":["../src/git-repository.ts"],"names":[],"mappings":"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","sourcesContent":["import { isAbsolute } from \"node:path\";\nimport { simpleGit } from \"simple-git\";\nimport {\n asFailure,\n isNonEmptyString,\n isPositiveInteger,\n isRecord,\n} from \"./internal/utils.js\";\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst DEFAULT_MAX_CONCURRENT_PROCESSES = 1;\nconst DEFAULT_COMMIT_IDENTITY = {\n name: \"Bonfire\",\n email: \"bonfire@localhost\",\n};\nconst NOTHING_TO_COMMIT_MESSAGES = [\n \"nothing to commit\",\n \"nothing added to commit\",\n];\n\nexport type GitCommitIdentity = {\n name: string;\n email: string;\n};\n\nexport type GitRepositoryOptions = {\n repositoryRoot: string;\n timeoutMs?: number;\n maxConcurrentProcesses?: number;\n commitIdentity?: GitCommitIdentity;\n};\n\nexport type GitStatusFile = {\n path: string;\n index: string;\n workingDirectory: string;\n from?: string;\n};\n\nexport type GitStatusSnapshot = {\n current: string | null;\n files: readonly GitStatusFile[];\n clean: boolean;\n};\n\nexport type GitRemoteSnapshot = {\n name: string;\n fetchUrl: string | null;\n pushUrl: string | null;\n};\n\nexport type GitBranchSnapshot = {\n current: string | null;\n all: readonly string[];\n};\n\nexport type GitCommitSnapshot = {\n branch: string;\n commit: string;\n root: boolean;\n};\n\nexport type GitCommitResult = GitCommitSnapshot | null;\n\nexport type GitPushResult = {\n remoteStatus: \"pushed\" | \"local-only\";\n};\n\nexport type GitRepositoryErrorKind =\n | \"missing-git\"\n | \"timeout\"\n | \"conflict\"\n | \"command-failure\"\n | \"malformed-output\"\n | \"invalid-configuration\"\n | \"unexpected\";\n\nexport class GitRepositoryError extends Error {\n readonly kind: GitRepositoryErrorKind;\n readonly operation: string;\n\n constructor(kind: GitRepositoryErrorKind, operation: string) {\n super(`Git operation failed: ${operation}`);\n this.name = \"GitRepositoryError\";\n this.kind = kind;\n this.operation = operation;\n }\n}\n\nexport interface GitRepository {\n readonly repositoryRoot: string;\n currentBranch(): Promise<string>;\n status(pathspecs?: readonly string[]): Promise<GitStatusSnapshot>;\n remotes(): Promise<readonly GitRemoteSnapshot[]>;\n branches(): Promise<GitBranchSnapshot>;\n fetch(remote: string, branch?: string): Promise<void>;\n checkout(args: readonly string[]): Promise<void>;\n checkoutLocalBranch(branch: string): Promise<void>;\n merge(args: readonly string[]): Promise<void>;\n add(pathspecs: readonly string[]): Promise<void>;\n commit(\n message: string,\n pathspecs?: readonly string[],\n ): Promise<GitCommitResult>;\n push(\n remote: string,\n branch: string,\n options?: readonly string[],\n ): Promise<void>;\n restore(pathspecs: readonly string[]): Promise<void>;\n clean(mode: string, pathspecs: readonly string[]): Promise<void>;\n raw(args: readonly string[]): Promise<string>;\n}\n\nfunction classifyError(error: unknown): GitRepositoryErrorKind {\n if (error instanceof GitRepositoryError) return error.kind;\n\n const failure = asFailure(error);\n const message = typeof failure.message === \"string\" ? failure.message : \"\";\n const normalized = message.toLowerCase();\n\n if (failure.code === \"ENOENT\" || normalized.includes(\"enoent\")) {\n return \"missing-git\";\n }\n\n if (\n failure.code === \"ETIMEDOUT\" ||\n failure.killed === true ||\n failure.signal === \"SIGTERM\" ||\n normalized.includes(\"timed out\") ||\n normalized.includes(\"timeout\")\n ) {\n return \"timeout\";\n }\n\n if (\n normalized.includes(\"conflict\") ||\n normalized.includes(\"would be overwritten\") ||\n normalized.includes(\"unmerged paths\")\n ) {\n return \"conflict\";\n }\n\n if (isRecord(error) || error instanceof Error) return \"command-failure\";\n return \"unexpected\";\n}\n\nfunction normalizeError(operation: string, error: unknown) {\n if (error instanceof GitRepositoryError) return error;\n return new GitRepositoryError(classifyError(error), operation);\n}\n\nfunction assertValidPositiveInteger(value: number, option: string) {\n if (!isPositiveInteger(value)) {\n throw new GitRepositoryError(\"invalid-configuration\", option);\n }\n}\n\nfunction assertValidBranchName(value: string, option: string) {\n if (\n !isNonEmptyString(value) ||\n value.startsWith(\"-\") ||\n value.includes(\"\\0\") ||\n /\\s/u.test(value) ||\n value.includes(\"@{\") ||\n value.split(/[\\\\/]/u).some((segment) => segment === \"..\")\n ) {\n throw new GitRepositoryError(\"invalid-configuration\", option);\n }\n}\n\nfunction assertValidPathspec(pathspec: string) {\n if (\n !isNonEmptyString(pathspec) ||\n isAbsolute(pathspec) ||\n /^[A-Za-z]:[\\\\/]/u.test(pathspec) ||\n pathspec.startsWith(\"\\\\\") ||\n pathspec.startsWith(\"-\") ||\n pathspec.startsWith(\":\") ||\n pathspec.includes(\"\\0\") ||\n pathspec.includes(\"*\") ||\n pathspec.includes(\"?\") ||\n pathspec.includes(\"[\") ||\n pathspec.includes(\"]\") ||\n pathspec === \".\" ||\n pathspec.split(/[\\\\/]/u).some((segment) => segment === \"..\")\n ) {\n throw new GitRepositoryError(\"invalid-configuration\", \"pathspec\");\n }\n}\n\nfunction assertValidPathspecs(pathspecs: readonly string[]) {\n if (pathspecs.length === 0) {\n throw new GitRepositoryError(\"invalid-configuration\", \"pathspec\");\n }\n for (const pathspec of pathspecs) assertValidPathspec(pathspec);\n return [...pathspecs];\n}\n\nfunction assertValidArguments(args: readonly string[], operation: string) {\n if (\n args.some(\n (argument) => !isNonEmptyString(argument) || argument.includes(\"\\0\"),\n )\n ) {\n throw new GitRepositoryError(\"invalid-configuration\", operation);\n }\n return [...args];\n}\n\nfunction isNothingToCommit(error: unknown) {\n const failure = asFailure(error);\n const message = typeof failure.message === \"string\" ? failure.message : \"\";\n return NOTHING_TO_COMMIT_MESSAGES.some((needle) =>\n message.toLowerCase().includes(needle),\n );\n}\n\nclass SimpleGitRepository implements GitRepository {\n readonly repositoryRoot: string;\n readonly #git;\n\n constructor(options: Required<GitRepositoryOptions>) {\n this.repositoryRoot = options.repositoryRoot;\n this.#git = simpleGit(options.repositoryRoot, {\n baseDir: options.repositoryRoot,\n maxConcurrentProcesses: options.maxConcurrentProcesses,\n config: [\n `user.name=${options.commitIdentity.name}`,\n `user.email=${options.commitIdentity.email}`,\n ],\n timeout: { block: options.timeoutMs },\n });\n }\n\n async #execute<Value>(operation: string, action: () => Promise<Value>) {\n try {\n return await action();\n } catch (error) {\n throw normalizeError(operation, error);\n }\n }\n\n async currentBranch() {\n const result = await this.#execute(\"current-branch\", () =>\n this.#git.revparse([\"--abbrev-ref\", \"HEAD\"]),\n );\n if (!isNonEmptyString(result) || result.trim().length === 0) {\n throw new GitRepositoryError(\"malformed-output\", \"current-branch\");\n }\n return result.trim();\n }\n\n async status(pathspecs?: readonly string[]) {\n const validatedPathspecs =\n pathspecs === undefined ? undefined : assertValidPathspecs(pathspecs);\n const result = await this.#execute(\"status\", () =>\n validatedPathspecs === undefined\n ? this.#git.status()\n : this.#git.status([\"--\", ...validatedPathspecs]),\n );\n\n if (\n !isRecord(result) ||\n !Array.isArray(result.files) ||\n (typeof result.current !== \"string\" && result.current !== null) ||\n typeof result.isClean !== \"function\"\n ) {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n\n const files: GitStatusFile[] = [];\n for (const file of result.files) {\n if (!isRecord(file)) {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n if (\n typeof file.path !== \"string\" ||\n typeof file.index !== \"string\" ||\n typeof file.working_dir !== \"string\"\n ) {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n\n files.push({\n path: file.path,\n index: file.index,\n workingDirectory: file.working_dir,\n ...(typeof file.from === \"string\" ? { from: file.from } : {}),\n });\n }\n\n let clean: boolean;\n try {\n clean = result.isClean();\n } catch {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n if (typeof clean !== \"boolean\") {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n\n return {\n current: result.current,\n files,\n clean,\n } satisfies GitStatusSnapshot;\n }\n\n async remotes() {\n const result = await this.#execute(\"remotes\", () =>\n this.#git.getRemotes(true),\n );\n if (!Array.isArray(result)) {\n throw new GitRepositoryError(\"malformed-output\", \"remotes\");\n }\n\n const remotes: GitRemoteSnapshot[] = [];\n for (const remote of result) {\n if (!isRecord(remote) || typeof remote.name !== \"string\") {\n throw new GitRepositoryError(\"malformed-output\", \"remotes\");\n }\n const refs = remote.refs;\n if (!isRecord(refs)) {\n throw new GitRepositoryError(\"malformed-output\", \"remotes\");\n }\n if (typeof refs.fetch !== \"string\" || typeof refs.push !== \"string\") {\n throw new GitRepositoryError(\"malformed-output\", \"remotes\");\n }\n\n remotes.push({\n name: remote.name,\n fetchUrl: refs.fetch,\n pushUrl: refs.push,\n });\n }\n return remotes;\n }\n\n async branches() {\n const result = await this.#execute(\"branches\", () =>\n this.#git.branch([\"-a\"]),\n );\n if (\n !isRecord(result) ||\n !Array.isArray(result.all) ||\n (typeof result.current !== \"string\" && result.current !== null) ||\n !result.all.every((branch) => typeof branch === \"string\")\n ) {\n throw new GitRepositoryError(\"malformed-output\", \"branches\");\n }\n\n return {\n current: result.current,\n all: [...result.all],\n } satisfies GitBranchSnapshot;\n }\n\n async fetch(remote: string, branch?: string) {\n assertValidBranchName(remote, \"remote\");\n if (branch !== undefined) assertValidBranchName(branch, \"branch\");\n await this.#execute(\"fetch\", () =>\n branch === undefined\n ? this.#git.fetch(remote)\n : this.#git.fetch(remote, branch),\n );\n }\n\n async checkout(args: readonly string[]) {\n const validatedArgs = assertValidArguments(args, \"checkout\");\n await this.#execute(\"checkout\", () => this.#git.checkout(validatedArgs));\n }\n\n async checkoutLocalBranch(branch: string) {\n assertValidBranchName(branch, \"branch\");\n await this.#execute(\"checkout-local-branch\", () =>\n this.#git.checkoutLocalBranch(branch),\n );\n }\n\n async merge(args: readonly string[]) {\n const validatedArgs = assertValidArguments(args, \"merge\");\n await this.#execute(\"merge\", () => this.#git.merge(validatedArgs));\n }\n\n async add(pathspecs: readonly string[]) {\n const validatedPathspecs = assertValidPathspecs(pathspecs);\n await this.#execute(\"add\", () => this.#git.add(validatedPathspecs));\n }\n\n async commit(message: string, pathspecs?: readonly string[]) {\n if (!isNonEmptyString(message)) {\n throw new GitRepositoryError(\"invalid-configuration\", \"commit-message\");\n }\n const validatedPathspecs =\n pathspecs === undefined ? undefined : assertValidPathspecs(pathspecs);\n\n let result: unknown;\n try {\n result =\n validatedPathspecs === undefined\n ? await this.#git.commit(message)\n : await this.#git.commit(message, [...validatedPathspecs]);\n } catch (error) {\n if (isNothingToCommit(error)) return null;\n throw normalizeError(\"commit\", error);\n }\n if (\n !isRecord(result) ||\n typeof result.branch !== \"string\" ||\n typeof result.commit !== \"string\" ||\n typeof result.root !== \"boolean\"\n ) {\n throw new GitRepositoryError(\"malformed-output\", \"commit\");\n }\n\n return {\n branch: result.branch,\n commit: result.commit,\n root: result.root,\n } satisfies GitCommitSnapshot;\n }\n\n async push(remote: string, branch: string, options: readonly string[] = []) {\n assertValidBranchName(remote, \"remote\");\n assertValidBranchName(branch, \"branch\");\n const validatedOptions = assertValidArguments(options, \"push\");\n await this.#execute(\"push\", () =>\n this.#git.push(remote, branch, validatedOptions),\n );\n }\n\n async restore(pathspecs: readonly string[]) {\n const validatedPathspecs = assertValidPathspecs(pathspecs);\n await this.#execute(\"restore\", () =>\n this.#git.checkout([\"--\", ...validatedPathspecs]),\n );\n }\n\n async clean(mode: string, pathspecs: readonly string[]) {\n if (!/^[a-z]+$/u.test(mode)) {\n throw new GitRepositoryError(\"invalid-configuration\", \"clean-mode\");\n }\n const validatedPathspecs = assertValidPathspecs(pathspecs);\n await this.#execute(\"clean\", () =>\n this.#git.clean(mode, validatedPathspecs),\n );\n }\n\n async raw(args: readonly string[]) {\n const validatedArgs = assertValidArguments(args, \"raw\");\n const result = await this.#execute(\"raw\", () =>\n this.#git.raw(validatedArgs),\n );\n if (typeof result !== \"string\") {\n throw new GitRepositoryError(\"malformed-output\", \"raw\");\n }\n return result;\n }\n}\n\nexport type PrepareIdentityBranchOptions = {\n identityBranch: string;\n primaryBranch: string;\n remote: string;\n};\n\nexport async function prepareIdentityBranch(\n repository: GitRepository,\n options: PrepareIdentityBranchOptions,\n): Promise<void> {\n assertValidBranchName(options.identityBranch, \"identity-branch\");\n assertValidBranchName(options.primaryBranch, \"primary-branch\");\n assertValidBranchName(options.remote, \"remote\");\n\n const currentBranch = await repository.currentBranch();\n if (currentBranch === options.identityBranch) return;\n\n const status = await repository.status();\n if (!status.clean) {\n throw new GitRepositoryError(\"conflict\", \"prepare-identity-branch\");\n }\n\n const remotes = await repository.remotes();\n const configuredRemote = remotes.some(\n (remote) => remote.name === options.remote,\n );\n if (configuredRemote) {\n await repository.fetch(options.remote, options.primaryBranch);\n }\n\n const branches = await repository.branches();\n const hasLocalPrimary = branches.all.includes(options.primaryBranch);\n const remotePrimary = `remotes/${options.remote}/${options.primaryBranch}`;\n const hasRemotePrimary =\n configuredRemote && branches.all.includes(remotePrimary);\n if (!hasLocalPrimary && !hasRemotePrimary) {\n throw new GitRepositoryError(\"command-failure\", \"primary-branch\");\n }\n\n if (hasLocalPrimary) {\n await repository.checkout([options.primaryBranch]);\n } else {\n await repository.checkout([\n \"-b\",\n options.primaryBranch,\n \"--track\",\n `${options.remote}/${options.primaryBranch}`,\n ]);\n }\n if (configuredRemote) {\n await repository.merge([\n `${options.remote}/${options.primaryBranch}`,\n \"--ff-only\",\n ]);\n }\n\n if (branches.all.includes(options.identityBranch)) {\n await repository.checkout([options.identityBranch]);\n return;\n }\n\n const remoteBranch = `remotes/${options.remote}/${options.identityBranch}`;\n if (branches.all.includes(remoteBranch)) {\n await repository.checkout([\n \"-b\",\n options.identityBranch,\n \"--track\",\n `${options.remote}/${options.identityBranch}`,\n ]);\n return;\n }\n\n await repository.checkoutLocalBranch(options.identityBranch);\n}\n\nexport async function commitPath(\n repository: GitRepository,\n pathspec: string,\n message: string,\n): Promise<GitCommitResult> {\n const validatedPathspecs = assertValidPathspecs([pathspec]);\n await repository.add(validatedPathspecs);\n return repository.commit(message, validatedPathspecs);\n}\n\nexport async function discardPath(\n repository: GitRepository,\n pathspec: string,\n): Promise<void> {\n const validatedPathspec = assertValidPathspecs([pathspec])[0]!;\n await repository.raw([\"reset\", \"HEAD\", \"--\", validatedPathspec]);\n await repository.restore([validatedPathspec]);\n await repository.clean(\"fd\", [validatedPathspec]);\n}\n\nexport async function pushBranch(\n repository: GitRepository,\n remote: string,\n branch: string,\n): Promise<GitPushResult> {\n assertValidBranchName(remote, \"remote\");\n assertValidBranchName(branch, \"branch\");\n\n const hasRemote = (await repository.remotes()).some(\n (configuredRemote) => configuredRemote.name === remote,\n );\n if (!hasRemote) return { remoteStatus: \"local-only\" };\n\n await repository.push(remote, branch, [\"--set-upstream\"]);\n return { remoteStatus: \"pushed\" };\n}\n\nexport function createGitRepository(\n options: GitRepositoryOptions,\n): GitRepository {\n if (!isAbsolute(options.repositoryRoot)) {\n throw new GitRepositoryError(\"invalid-configuration\", \"repository-root\");\n }\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const maxConcurrentProcesses =\n options.maxConcurrentProcesses ?? DEFAULT_MAX_CONCURRENT_PROCESSES;\n const commitIdentity = options.commitIdentity ?? DEFAULT_COMMIT_IDENTITY;\n\n assertValidPositiveInteger(timeoutMs, \"timeout\");\n assertValidPositiveInteger(\n maxConcurrentProcesses,\n \"max-concurrent-processes\",\n );\n\n return new SimpleGitRepository({\n repositoryRoot: options.repositoryRoot,\n timeoutMs,\n maxConcurrentProcesses,\n commitIdentity,\n });\n}\n"]}
1
+ {"version":3,"file":"git-repository.d.ts","sourceRoot":"","sources":["../src/git-repository.ts"],"names":[],"mappings":"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","sourcesContent":["import { isAbsolute } from \"node:path\";\nimport { simpleGit } from \"simple-git\";\nimport {\n asFailure,\n isNonEmptyString,\n isPositiveInteger,\n isRecord,\n} from \"./internal/utils.js\";\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst DEFAULT_MAX_CONCURRENT_PROCESSES = 1;\nconst DEFAULT_COMMIT_IDENTITY = {\n name: \"Bonfire\",\n email: \"bonfire@localhost\",\n};\nconst NOTHING_TO_COMMIT_MESSAGES = [\n \"nothing to commit\",\n \"nothing added to commit\",\n];\n\nexport type GitCommitIdentity = {\n name: string;\n email: string;\n};\n\nexport type GitRepositoryOptions = {\n repositoryRoot: string;\n timeoutMs?: number;\n maxConcurrentProcesses?: number;\n commitIdentity?: GitCommitIdentity;\n};\n\nexport type GitStatusFile = {\n path: string;\n index: string;\n workingDirectory: string;\n from?: string;\n};\n\nexport type GitStatusSnapshot = {\n current: string | null;\n files: readonly GitStatusFile[];\n clean: boolean;\n};\n\nexport type GitRemoteSnapshot = {\n name: string;\n fetchUrl: string | null;\n pushUrl: string | null;\n};\n\nexport type GitBranchSnapshot = {\n current: string | null;\n all: readonly string[];\n};\n\nexport type GitCommitSnapshot = {\n branch: string;\n commit: string;\n root: boolean;\n};\n\nexport type GitCommitResult = GitCommitSnapshot | null;\n\nexport type GitPushResult = {\n remoteStatus: \"pushed\" | \"local-only\";\n};\n\nexport type GitRepositoryErrorKind =\n | \"missing-git\"\n | \"timeout\"\n | \"conflict\"\n | \"non-fast-forward\"\n | \"command-failure\"\n | \"malformed-output\"\n | \"invalid-configuration\"\n | \"unexpected\";\n\nexport class GitRepositoryError extends Error {\n readonly kind: GitRepositoryErrorKind;\n readonly operation: string;\n\n constructor(kind: GitRepositoryErrorKind, operation: string) {\n super(`Git operation failed: ${operation}`);\n this.name = \"GitRepositoryError\";\n this.kind = kind;\n this.operation = operation;\n }\n}\n\nexport interface GitRepository {\n readonly repositoryRoot: string;\n currentBranch(): Promise<string>;\n status(pathspecs?: readonly string[]): Promise<GitStatusSnapshot>;\n remotes(): Promise<readonly GitRemoteSnapshot[]>;\n branches(): Promise<GitBranchSnapshot>;\n fetch(remote: string, branch?: string): Promise<void>;\n checkout(args: readonly string[]): Promise<void>;\n checkoutLocalBranch(branch: string): Promise<void>;\n merge(args: readonly string[]): Promise<void>;\n add(pathspecs: readonly string[]): Promise<void>;\n commit(\n message: string,\n pathspecs?: readonly string[],\n ): Promise<GitCommitResult>;\n push(\n remote: string,\n branch: string,\n options?: readonly string[],\n ): Promise<void>;\n restore(pathspecs: readonly string[]): Promise<void>;\n clean(mode: string, pathspecs: readonly string[]): Promise<void>;\n raw(args: readonly string[]): Promise<string>;\n}\n\nfunction classifyError(error: unknown): GitRepositoryErrorKind {\n if (error instanceof GitRepositoryError) return error.kind;\n\n const failure = asFailure(error);\n const message = typeof failure.message === \"string\" ? failure.message : \"\";\n const normalized = message.toLowerCase();\n\n if (failure.code === \"ENOENT\" || normalized.includes(\"enoent\")) {\n return \"missing-git\";\n }\n\n if (\n failure.code === \"ETIMEDOUT\" ||\n failure.killed === true ||\n failure.signal === \"SIGTERM\" ||\n normalized.includes(\"timed out\") ||\n normalized.includes(\"timeout\")\n ) {\n return \"timeout\";\n }\n\n if (\n normalized.includes(\"conflict\") ||\n normalized.includes(\"would be overwritten\") ||\n normalized.includes(\"unmerged paths\")\n ) {\n return \"conflict\";\n }\n\n if (\n normalized.includes(\"non-fast-forward\") ||\n normalized.includes(\"fetch first\") ||\n normalized.includes(\"stale info\")\n ) {\n return \"non-fast-forward\";\n }\n\n if (isRecord(error) || error instanceof Error) return \"command-failure\";\n return \"unexpected\";\n}\n\nfunction normalizeError(operation: string, error: unknown) {\n if (error instanceof GitRepositoryError) return error;\n return new GitRepositoryError(classifyError(error), operation);\n}\n\nfunction assertValidPositiveInteger(value: number, option: string) {\n if (!isPositiveInteger(value)) {\n throw new GitRepositoryError(\"invalid-configuration\", option);\n }\n}\n\nfunction assertValidBranchName(value: string, option: string) {\n if (\n !isNonEmptyString(value) ||\n value.startsWith(\"-\") ||\n value.includes(\"\\0\") ||\n /\\s/u.test(value) ||\n value.includes(\"@{\") ||\n value.split(/[\\\\/]/u).some((segment) => segment === \"..\")\n ) {\n throw new GitRepositoryError(\"invalid-configuration\", option);\n }\n}\n\nfunction assertValidPathspec(pathspec: string) {\n if (\n !isNonEmptyString(pathspec) ||\n isAbsolute(pathspec) ||\n /^[A-Za-z]:[\\\\/]/u.test(pathspec) ||\n pathspec.startsWith(\"\\\\\") ||\n pathspec.startsWith(\"-\") ||\n pathspec.startsWith(\":\") ||\n pathspec.includes(\"\\0\") ||\n pathspec.includes(\"*\") ||\n pathspec.includes(\"?\") ||\n pathspec.includes(\"[\") ||\n pathspec.includes(\"]\") ||\n pathspec === \".\" ||\n pathspec.split(/[\\\\/]/u).some((segment) => segment === \"..\")\n ) {\n throw new GitRepositoryError(\"invalid-configuration\", \"pathspec\");\n }\n}\n\nfunction assertValidPathspecs(pathspecs: readonly string[]) {\n if (pathspecs.length === 0) {\n throw new GitRepositoryError(\"invalid-configuration\", \"pathspec\");\n }\n for (const pathspec of pathspecs) assertValidPathspec(pathspec);\n return [...pathspecs];\n}\n\nfunction assertValidArguments(args: readonly string[], operation: string) {\n if (\n args.some(\n (argument) => !isNonEmptyString(argument) || argument.includes(\"\\0\"),\n )\n ) {\n throw new GitRepositoryError(\"invalid-configuration\", operation);\n }\n return [...args];\n}\n\nfunction isNothingToCommit(error: unknown) {\n const failure = asFailure(error);\n const message = typeof failure.message === \"string\" ? failure.message : \"\";\n return NOTHING_TO_COMMIT_MESSAGES.some((needle) =>\n message.toLowerCase().includes(needle),\n );\n}\n\nclass SimpleGitRepository implements GitRepository {\n readonly repositoryRoot: string;\n readonly #git;\n\n constructor(options: Required<GitRepositoryOptions>) {\n this.repositoryRoot = options.repositoryRoot;\n this.#git = simpleGit(options.repositoryRoot, {\n baseDir: options.repositoryRoot,\n maxConcurrentProcesses: options.maxConcurrentProcesses,\n config: [\n `user.name=${options.commitIdentity.name}`,\n `user.email=${options.commitIdentity.email}`,\n ],\n timeout: { block: options.timeoutMs },\n });\n }\n\n async #execute<Value>(operation: string, action: () => Promise<Value>) {\n try {\n return await action();\n } catch (error) {\n throw normalizeError(operation, error);\n }\n }\n\n async currentBranch() {\n const result = await this.#execute(\"current-branch\", () =>\n this.#git.revparse([\"--abbrev-ref\", \"HEAD\"]),\n );\n if (!isNonEmptyString(result) || result.trim().length === 0) {\n throw new GitRepositoryError(\"malformed-output\", \"current-branch\");\n }\n return result.trim();\n }\n\n async status(pathspecs?: readonly string[]) {\n const validatedPathspecs =\n pathspecs === undefined ? undefined : assertValidPathspecs(pathspecs);\n const result = await this.#execute(\"status\", () =>\n validatedPathspecs === undefined\n ? this.#git.status()\n : this.#git.status([\"--\", ...validatedPathspecs]),\n );\n\n if (\n !isRecord(result) ||\n !Array.isArray(result.files) ||\n (typeof result.current !== \"string\" && result.current !== null) ||\n typeof result.isClean !== \"function\"\n ) {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n\n const files: GitStatusFile[] = [];\n for (const file of result.files) {\n if (!isRecord(file)) {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n if (\n typeof file.path !== \"string\" ||\n typeof file.index !== \"string\" ||\n typeof file.working_dir !== \"string\"\n ) {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n\n files.push({\n path: file.path,\n index: file.index,\n workingDirectory: file.working_dir,\n ...(typeof file.from === \"string\" ? { from: file.from } : {}),\n });\n }\n\n let clean: boolean;\n try {\n clean = result.isClean();\n } catch {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n if (typeof clean !== \"boolean\") {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n\n return {\n current: result.current,\n files,\n clean,\n } satisfies GitStatusSnapshot;\n }\n\n async remotes() {\n const result = await this.#execute(\"remotes\", () =>\n this.#git.getRemotes(true),\n );\n if (!Array.isArray(result)) {\n throw new GitRepositoryError(\"malformed-output\", \"remotes\");\n }\n\n const remotes: GitRemoteSnapshot[] = [];\n for (const remote of result) {\n if (!isRecord(remote) || typeof remote.name !== \"string\") {\n throw new GitRepositoryError(\"malformed-output\", \"remotes\");\n }\n const refs = remote.refs;\n if (!isRecord(refs)) {\n throw new GitRepositoryError(\"malformed-output\", \"remotes\");\n }\n if (typeof refs.fetch !== \"string\" || typeof refs.push !== \"string\") {\n throw new GitRepositoryError(\"malformed-output\", \"remotes\");\n }\n\n remotes.push({\n name: remote.name,\n fetchUrl: refs.fetch,\n pushUrl: refs.push,\n });\n }\n return remotes;\n }\n\n async branches() {\n const result = await this.#execute(\"branches\", () =>\n this.#git.branch([\"-a\"]),\n );\n if (\n !isRecord(result) ||\n !Array.isArray(result.all) ||\n (typeof result.current !== \"string\" && result.current !== null) ||\n !result.all.every((branch) => typeof branch === \"string\")\n ) {\n throw new GitRepositoryError(\"malformed-output\", \"branches\");\n }\n\n return {\n current: result.current,\n all: [...result.all],\n } satisfies GitBranchSnapshot;\n }\n\n async fetch(remote: string, branch?: string) {\n assertValidBranchName(remote, \"remote\");\n if (branch !== undefined) assertValidBranchName(branch, \"branch\");\n await this.#execute(\"fetch\", () =>\n branch === undefined\n ? this.#git.fetch(remote)\n : this.#git.fetch(remote, branch),\n );\n }\n\n async checkout(args: readonly string[]) {\n const validatedArgs = assertValidArguments(args, \"checkout\");\n await this.#execute(\"checkout\", () => this.#git.checkout(validatedArgs));\n }\n\n async checkoutLocalBranch(branch: string) {\n assertValidBranchName(branch, \"branch\");\n await this.#execute(\"checkout-local-branch\", () =>\n this.#git.checkoutLocalBranch(branch),\n );\n }\n\n async merge(args: readonly string[]) {\n const validatedArgs = assertValidArguments(args, \"merge\");\n await this.#execute(\"merge\", () => this.#git.merge(validatedArgs));\n }\n\n async add(pathspecs: readonly string[]) {\n const validatedPathspecs = assertValidPathspecs(pathspecs);\n await this.#execute(\"add\", () => this.#git.add(validatedPathspecs));\n }\n\n async commit(message: string, pathspecs?: readonly string[]) {\n if (!isNonEmptyString(message)) {\n throw new GitRepositoryError(\"invalid-configuration\", \"commit-message\");\n }\n const validatedPathspecs =\n pathspecs === undefined ? undefined : assertValidPathspecs(pathspecs);\n\n let result: unknown;\n try {\n result =\n validatedPathspecs === undefined\n ? await this.#git.commit(message)\n : await this.#git.commit(message, [...validatedPathspecs]);\n } catch (error) {\n if (isNothingToCommit(error)) return null;\n throw normalizeError(\"commit\", error);\n }\n if (\n !isRecord(result) ||\n typeof result.branch !== \"string\" ||\n typeof result.commit !== \"string\" ||\n typeof result.root !== \"boolean\"\n ) {\n throw new GitRepositoryError(\"malformed-output\", \"commit\");\n }\n\n return {\n branch: result.branch,\n commit: result.commit,\n root: result.root,\n } satisfies GitCommitSnapshot;\n }\n\n async push(remote: string, branch: string, options: readonly string[] = []) {\n assertValidBranchName(remote, \"remote\");\n assertValidBranchName(branch, \"branch\");\n const validatedOptions = assertValidArguments(options, \"push\");\n await this.#execute(\"push\", () =>\n this.#git.push(remote, branch, validatedOptions),\n );\n }\n\n async restore(pathspecs: readonly string[]) {\n const validatedPathspecs = assertValidPathspecs(pathspecs);\n await this.#execute(\"restore\", () =>\n this.#git.checkout([\"--\", ...validatedPathspecs]),\n );\n }\n\n async clean(mode: string, pathspecs: readonly string[]) {\n if (!/^[a-z]+$/u.test(mode)) {\n throw new GitRepositoryError(\"invalid-configuration\", \"clean-mode\");\n }\n const validatedPathspecs = assertValidPathspecs(pathspecs);\n await this.#execute(\"clean\", () =>\n this.#git.clean(mode, validatedPathspecs),\n );\n }\n\n async raw(args: readonly string[]) {\n const validatedArgs = assertValidArguments(args, \"raw\");\n const result = await this.#execute(\"raw\", () =>\n this.#git.raw(validatedArgs),\n );\n if (typeof result !== \"string\") {\n throw new GitRepositoryError(\"malformed-output\", \"raw\");\n }\n return result;\n }\n}\n\nexport type PrepareIdentityBranchOptions = {\n identityBranch: string;\n primaryBranch: string;\n remote: string;\n};\n\nexport async function prepareIdentityBranch(\n repository: GitRepository,\n options: PrepareIdentityBranchOptions,\n): Promise<void> {\n assertValidBranchName(options.identityBranch, \"identity-branch\");\n assertValidBranchName(options.primaryBranch, \"primary-branch\");\n assertValidBranchName(options.remote, \"remote\");\n\n const currentBranch = await repository.currentBranch();\n if (currentBranch === options.identityBranch) return;\n\n const status = await repository.status();\n if (!status.clean) {\n throw new GitRepositoryError(\"conflict\", \"prepare-identity-branch\");\n }\n\n const remotes = await repository.remotes();\n const configuredRemote = remotes.some(\n (remote) => remote.name === options.remote,\n );\n if (configuredRemote) {\n await repository.fetch(options.remote, options.primaryBranch);\n }\n\n const branches = await repository.branches();\n const hasLocalPrimary = branches.all.includes(options.primaryBranch);\n const remotePrimary = `remotes/${options.remote}/${options.primaryBranch}`;\n const hasRemotePrimary =\n configuredRemote && branches.all.includes(remotePrimary);\n if (!hasLocalPrimary && !hasRemotePrimary) {\n throw new GitRepositoryError(\"command-failure\", \"primary-branch\");\n }\n\n if (hasLocalPrimary) {\n await repository.checkout([options.primaryBranch]);\n } else {\n await repository.checkout([\n \"-b\",\n options.primaryBranch,\n \"--track\",\n `${options.remote}/${options.primaryBranch}`,\n ]);\n }\n if (configuredRemote) {\n await repository.merge([\n `${options.remote}/${options.primaryBranch}`,\n \"--ff-only\",\n ]);\n }\n\n if (branches.all.includes(options.identityBranch)) {\n await repository.checkout([options.identityBranch]);\n return;\n }\n\n const remoteBranch = `remotes/${options.remote}/${options.identityBranch}`;\n if (branches.all.includes(remoteBranch)) {\n await repository.checkout([\n \"-b\",\n options.identityBranch,\n \"--track\",\n `${options.remote}/${options.identityBranch}`,\n ]);\n return;\n }\n\n await repository.checkoutLocalBranch(options.identityBranch);\n}\n\nexport async function commitPath(\n repository: GitRepository,\n pathspec: string,\n message: string,\n): Promise<GitCommitResult> {\n const validatedPathspecs = assertValidPathspecs([pathspec]);\n await repository.add(validatedPathspecs);\n return repository.commit(message, validatedPathspecs);\n}\n\nexport async function discardPath(\n repository: GitRepository,\n pathspec: string,\n): Promise<void> {\n const validatedPathspec = assertValidPathspecs([pathspec])[0]!;\n await repository.raw([\"reset\", \"HEAD\", \"--\", validatedPathspec]);\n await repository.restore([validatedPathspec]);\n await repository.clean(\"fd\", [validatedPathspec]);\n}\n\nexport async function restorePathFromRef(\n repository: GitRepository,\n ref: string,\n pathspec: string,\n): Promise<void> {\n assertValidArguments([ref], \"restore-ref\");\n const validatedPathspec = assertValidPathspecs([pathspec])[0]!;\n await repository.raw([\n \"restore\",\n \"--source\",\n ref,\n \"--staged\",\n \"--worktree\",\n \"--\",\n validatedPathspec,\n ]);\n}\n\nexport async function pushCommitToBranch(\n repository: GitRepository,\n remote: string,\n commit: string,\n branch: string,\n): Promise<GitPushResult> {\n assertValidBranchName(remote, \"remote\");\n assertValidBranchName(branch, \"branch\");\n if (!/^[0-9a-f]{40,64}$/iu.test(commit)) {\n throw new GitRepositoryError(\"invalid-configuration\", \"commit\");\n }\n\n const hasRemote = (await repository.remotes()).some(\n (configuredRemote) => configuredRemote.name === remote,\n );\n if (!hasRemote) return { remoteStatus: \"local-only\" };\n\n await repository.raw([\"push\", remote, `${commit}:refs/heads/${branch}`]);\n return { remoteStatus: \"pushed\" };\n}\n\nexport async function pushBranch(\n repository: GitRepository,\n remote: string,\n branch: string,\n): Promise<GitPushResult> {\n assertValidBranchName(remote, \"remote\");\n assertValidBranchName(branch, \"branch\");\n\n const hasRemote = (await repository.remotes()).some(\n (configuredRemote) => configuredRemote.name === remote,\n );\n if (!hasRemote) return { remoteStatus: \"local-only\" };\n\n await repository.push(remote, branch, [\"--set-upstream\"]);\n return { remoteStatus: \"pushed\" };\n}\n\nexport function createGitRepository(\n options: GitRepositoryOptions,\n): GitRepository {\n if (!isAbsolute(options.repositoryRoot)) {\n throw new GitRepositoryError(\"invalid-configuration\", \"repository-root\");\n }\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const maxConcurrentProcesses =\n options.maxConcurrentProcesses ?? DEFAULT_MAX_CONCURRENT_PROCESSES;\n const commitIdentity = options.commitIdentity ?? DEFAULT_COMMIT_IDENTITY;\n\n assertValidPositiveInteger(timeoutMs, \"timeout\");\n assertValidPositiveInteger(\n maxConcurrentProcesses,\n \"max-concurrent-processes\",\n );\n\n return new SimpleGitRepository({\n repositoryRoot: options.repositoryRoot,\n timeoutMs,\n maxConcurrentProcesses,\n commitIdentity,\n });\n}\n"]}
@@ -42,6 +42,11 @@ function classifyError(error) {
42
42
  normalized.includes("unmerged paths")) {
43
43
  return "conflict";
44
44
  }
45
+ if (normalized.includes("non-fast-forward") ||
46
+ normalized.includes("fetch first") ||
47
+ normalized.includes("stale info")) {
48
+ return "non-fast-forward";
49
+ }
45
50
  if (isRecord(error) || error instanceof Error)
46
51
  return "command-failure";
47
52
  return "unexpected";
@@ -360,6 +365,31 @@ export async function discardPath(repository, pathspec) {
360
365
  await repository.restore([validatedPathspec]);
361
366
  await repository.clean("fd", [validatedPathspec]);
362
367
  }
368
+ export async function restorePathFromRef(repository, ref, pathspec) {
369
+ assertValidArguments([ref], "restore-ref");
370
+ const validatedPathspec = assertValidPathspecs([pathspec])[0];
371
+ await repository.raw([
372
+ "restore",
373
+ "--source",
374
+ ref,
375
+ "--staged",
376
+ "--worktree",
377
+ "--",
378
+ validatedPathspec,
379
+ ]);
380
+ }
381
+ export async function pushCommitToBranch(repository, remote, commit, branch) {
382
+ assertValidBranchName(remote, "remote");
383
+ assertValidBranchName(branch, "branch");
384
+ if (!/^[0-9a-f]{40,64}$/iu.test(commit)) {
385
+ throw new GitRepositoryError("invalid-configuration", "commit");
386
+ }
387
+ const hasRemote = (await repository.remotes()).some((configuredRemote) => configuredRemote.name === remote);
388
+ if (!hasRemote)
389
+ return { remoteStatus: "local-only" };
390
+ await repository.raw(["push", remote, `${commit}:refs/heads/${branch}`]);
391
+ return { remoteStatus: "pushed" };
392
+ }
363
393
  export async function pushBranch(repository, remote, branch) {
364
394
  assertValidBranchName(remote, "remote");
365
395
  assertValidBranchName(branch, "branch");
@@ -1 +1 @@
1
- {"version":3,"file":"git-repository.js","sourceRoot":"","sources":["../src/git-repository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EACL,SAAS,EACT,gBAAgB,EAChB,iBAAiB,EACjB,QAAQ,GACT,MAAM,qBAAqB,CAAC;AAE7B,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAClC,MAAM,gCAAgC,GAAG,CAAC,CAAC;AAC3C,MAAM,uBAAuB,GAAG;IAC9B,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,mBAAmB;CAC3B,CAAC;AACF,MAAM,0BAA0B,GAAG;IACjC,mBAAmB;IACnB,yBAAyB;CAC1B,CAAC;AA2DF,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAClC,IAAI,CAAyB;IAC7B,SAAS,CAAS;IAE3B,YAAY,IAA4B,EAAE,SAAiB;QACzD,KAAK,CAAC,yBAAyB,SAAS,EAAE,CAAC,CAAC;QAC5C,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;CACF;AA2BD,SAAS,aAAa,CAAC,KAAc;IACnC,IAAI,KAAK,YAAY,kBAAkB;QAAE,OAAO,KAAK,CAAC,IAAI,CAAC;IAE3D,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IACjC,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3E,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAEzC,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/D,OAAO,aAAa,CAAC;IACvB,CAAC;IAED,IACE,OAAO,CAAC,IAAI,KAAK,WAAW;QAC5B,OAAO,CAAC,MAAM,KAAK,IAAI;QACvB,OAAO,CAAC,MAAM,KAAK,SAAS;QAC5B,UAAU,CAAC,QAAQ,CAAC,WAAW,CAAC;QAChC,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,EAC9B,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IACE,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC;QAC/B,UAAU,CAAC,QAAQ,CAAC,sBAAsB,CAAC;QAC3C,UAAU,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EACrC,CAAC;QACD,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,IAAI,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,YAAY,KAAK;QAAE,OAAO,iBAAiB,CAAC;IACxE,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,SAAS,cAAc,CAAC,SAAiB,EAAE,KAAc;IACvD,IAAI,KAAK,YAAY,kBAAkB;QAAE,OAAO,KAAK,CAAC;IACtD,OAAO,IAAI,kBAAkB,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC,CAAC;AACjE,CAAC;AAED,SAAS,0BAA0B,CAAC,KAAa,EAAE,MAAc;IAC/D,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,kBAAkB,CAAC,uBAAuB,EAAE,MAAM,CAAC,CAAC;IAChE,CAAC;AACH,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAa,EAAE,MAAc;IAC1D,IACE,CAAC,gBAAgB,CAAC,KAAK,CAAC;QACxB,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;QACrB,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QACpB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;QACjB,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QACpB,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,IAAI,CAAC,EACzD,CAAC;QACD,MAAM,IAAI,kBAAkB,CAAC,uBAAuB,EAAE,MAAM,CAAC,CAAC;IAChE,CAAC;AACH,CAAC;AAED,SAAS,mBAAmB,CAAC,QAAgB;IAC3C,IACE,CAAC,gBAAgB,CAAC,QAAQ,CAAC;QAC3B,UAAU,CAAC,QAAQ,CAAC;QACpB,kBAAkB,CAAC,IAAI,CAAC,QAAQ,CAAC;QACjC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC;QACzB,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC;QACxB,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC;QACxB,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC;QACvB,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;QACtB,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;QACtB,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;QACtB,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;QACtB,QAAQ,KAAK,GAAG;QAChB,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,IAAI,CAAC,EAC5D,CAAC;QACD,MAAM,IAAI,kBAAkB,CAAC,uBAAuB,EAAE,UAAU,CAAC,CAAC;IACpE,CAAC;AACH,CAAC;AAED,SAAS,oBAAoB,CAAC,SAA4B;IACxD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,kBAAkB,CAAC,uBAAuB,EAAE,UAAU,CAAC,CAAC;IACpE,CAAC;IACD,KAAK,MAAM,QAAQ,IAAI,SAAS;QAAE,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IAChE,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;AACxB,CAAC;AAED,SAAS,oBAAoB,CAAC,IAAuB,EAAE,SAAiB;IACtE,IACE,IAAI,CAAC,IAAI,CACP,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CACrE,EACD,CAAC;QACD,MAAM,IAAI,kBAAkB,CAAC,uBAAuB,EAAE,SAAS,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;AACnB,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAc;IACvC,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IACjC,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3E,OAAO,0BAA0B,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAChD,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CACvC,CAAC;AACJ,CAAC;AAED,MAAM,mBAAmB;IACd,cAAc,CAAS;IACvB,IAAI,CAAC;IAEd,YAAY,OAAuC;QACjD,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC;QAC7C,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,cAAc,EAAE;YAC5C,OAAO,EAAE,OAAO,CAAC,cAAc;YAC/B,sBAAsB,EAAE,OAAO,CAAC,sBAAsB;YACtD,MAAM,EAAE;gBACN,aAAa,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE;gBAC1C,cAAc,OAAO,CAAC,cAAc,CAAC,KAAK,EAAE;aAC7C;YACD,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,SAAS,EAAE;SACtC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,QAAQ,CAAQ,SAAiB,EAAE,MAA4B;QACnE,IAAI,CAAC;YACH,OAAO,MAAM,MAAM,EAAE,CAAC;QACxB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,cAAc,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE,CACxD,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,CAC7C,CAAC;QACF,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5D,MAAM,IAAI,kBAAkB,CAAC,kBAAkB,EAAE,gBAAgB,CAAC,CAAC;QACrE,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,SAA6B;QACxC,MAAM,kBAAkB,GACtB,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC;QACxE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE,CAChD,kBAAkB,KAAK,SAAS;YAC9B,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YACpB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,GAAG,kBAAkB,CAAC,CAAC,CACpD,CAAC;QAEF,IACE,CAAC,QAAQ,CAAC,MAAM,CAAC;YACjB,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;YAC5B,CAAC,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,IAAI,MAAM,CAAC,OAAO,KAAK,IAAI,CAAC;YAC/D,OAAO,MAAM,CAAC,OAAO,KAAK,UAAU,EACpC,CAAC;YACD,MAAM,IAAI,kBAAkB,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;QAC7D,CAAC;QAED,MAAM,KAAK,GAAoB,EAAE,CAAC;QAClC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YAChC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBACpB,MAAM,IAAI,kBAAkB,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;YAC7D,CAAC;YACD,IACE,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ;gBAC7B,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ;gBAC9B,OAAO,IAAI,CAAC,WAAW,KAAK,QAAQ,EACpC,CAAC;gBACD,MAAM,IAAI,kBAAkB,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;YAC7D,CAAC;YAED,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,gBAAgB,EAAE,IAAI,CAAC,WAAW;gBAClC,GAAG,CAAC,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC9D,CAAC,CAAC;QACL,CAAC;QAED,IAAI,KAAc,CAAC;QACnB,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,kBAAkB,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;QAC7D,CAAC;QACD,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;YAC/B,MAAM,IAAI,kBAAkB,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;QAC7D,CAAC;QAED,OAAO;YACL,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,KAAK;YACL,KAAK;SACsB,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE,CACjD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAC3B,CAAC;QACF,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,kBAAkB,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC;QAC9D,CAAC;QAED,MAAM,OAAO,GAAwB,EAAE,CAAC;QACxC,KAAK,MAAM,MAAM,IAAI,MAAM,EAAE,CAAC;YAC5B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACzD,MAAM,IAAI,kBAAkB,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC;YAC9D,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;YACzB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBACpB,MAAM,IAAI,kBAAkB,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC;YAC9D,CAAC;YACD,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACpE,MAAM,IAAI,kBAAkB,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC;YAC9D,CAAC;YAED,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,QAAQ,EAAE,IAAI,CAAC,KAAK;gBACpB,OAAO,EAAE,IAAI,CAAC,IAAI;aACnB,CAAC,CAAC;QACL,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE,CAClD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CACzB,CAAC;QACF,IACE,CAAC,QAAQ,CAAC,MAAM,CAAC;YACjB,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC;YAC1B,CAAC,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,IAAI,MAAM,CAAC,OAAO,KAAK,IAAI,CAAC;YAC/D,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,MAAM,KAAK,QAAQ,CAAC,EACzD,CAAC;YACD,MAAM,IAAI,kBAAkB,CAAC,kBAAkB,EAAE,UAAU,CAAC,CAAC;QAC/D,CAAC;QAED,OAAO;YACL,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,GAAG,EAAE,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC;SACO,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,MAAc,EAAE,MAAe;QACzC,qBAAqB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACxC,IAAI,MAAM,KAAK,SAAS;YAAE,qBAAqB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAClE,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE,CAChC,MAAM,KAAK,SAAS;YAClB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;YACzB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,CACpC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAuB;QACpC,MAAM,aAAa,GAAG,oBAAoB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QAC7D,MAAM,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC;IAC3E,CAAC;IAED,KAAK,CAAC,mBAAmB,CAAC,MAAc;QACtC,qBAAqB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACxC,MAAM,IAAI,CAAC,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE,CAChD,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,CACtC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,IAAuB;QACjC,MAAM,aAAa,GAAG,oBAAoB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC1D,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC;IACrE,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,SAA4B;QACpC,MAAM,kBAAkB,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAC;QAC3D,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAC;IACtE,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,OAAe,EAAE,SAA6B;QACzD,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,kBAAkB,CAAC,uBAAuB,EAAE,gBAAgB,CAAC,CAAC;QAC1E,CAAC;QACD,MAAM,kBAAkB,GACtB,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC;QAExE,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM;gBACJ,kBAAkB,KAAK,SAAS;oBAC9B,CAAC,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;oBACjC,CAAC,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,GAAG,kBAAkB,CAAC,CAAC,CAAC;QACjE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,iBAAiB,CAAC,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;YAC1C,MAAM,cAAc,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QACxC,CAAC;QACD,IACE,CAAC,QAAQ,CAAC,MAAM,CAAC;YACjB,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ;YACjC,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ;YACjC,OAAO,MAAM,CAAC,IAAI,KAAK,SAAS,EAChC,CAAC;YACD,MAAM,IAAI,kBAAkB,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;QAC7D,CAAC;QAED,OAAO;YACL,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,IAAI,EAAE,MAAM,CAAC,IAAI;SACU,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAAc,EAAE,MAAc,EAAE,OAAO,GAAsB,EAAE;QACxE,qBAAqB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACxC,qBAAqB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACxC,MAAM,gBAAgB,GAAG,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC/D,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE,CAC/B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,CAAC,CACjD,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,SAA4B;QACxC,MAAM,kBAAkB,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAC;QAC3D,MAAM,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE,CAClC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,GAAG,kBAAkB,CAAC,CAAC,CAClD,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,IAAY,EAAE,SAA4B;QACpD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,kBAAkB,CAAC,uBAAuB,EAAE,YAAY,CAAC,CAAC;QACtE,CAAC;QACD,MAAM,kBAAkB,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAC;QAC3D,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE,CAChC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAC1C,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,IAAuB;QAC/B,MAAM,aAAa,GAAG,oBAAoB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACxD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,EAAE,CAC7C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAC7B,CAAC;QACF,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC/B,MAAM,IAAI,kBAAkB,CAAC,kBAAkB,EAAE,KAAK,CAAC,CAAC;QAC1D,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;CACF;AAQD,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,UAAyB,EACzB,OAAqC;IAErC,qBAAqB,CAAC,OAAO,CAAC,cAAc,EAAE,iBAAiB,CAAC,CAAC;IACjE,qBAAqB,CAAC,OAAO,CAAC,aAAa,EAAE,gBAAgB,CAAC,CAAC;IAC/D,qBAAqB,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAEhD,MAAM,aAAa,GAAG,MAAM,UAAU,CAAC,aAAa,EAAE,CAAC;IACvD,IAAI,aAAa,KAAK,OAAO,CAAC,cAAc;QAAE,OAAO;IAErD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,CAAC;IACzC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAClB,MAAM,IAAI,kBAAkB,CAAC,UAAU,EAAE,yBAAyB,CAAC,CAAC;IACtE,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC;IAC3C,MAAM,gBAAgB,GAAG,OAAO,CAAC,IAAI,CACnC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,OAAO,CAAC,MAAM,CAC3C,CAAC;IACF,IAAI,gBAAgB,EAAE,CAAC;QACrB,MAAM,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IAChE,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,QAAQ,EAAE,CAAC;IAC7C,MAAM,eAAe,GAAG,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IACrE,MAAM,aAAa,GAAG,WAAW,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;IAC3E,MAAM,gBAAgB,GACpB,gBAAgB,IAAI,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;IAC3D,IAAI,CAAC,eAAe,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1C,MAAM,IAAI,kBAAkB,CAAC,iBAAiB,EAAE,gBAAgB,CAAC,CAAC;IACpE,CAAC;IAED,IAAI,eAAe,EAAE,CAAC;QACpB,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC;IACrD,CAAC;SAAM,CAAC;QACN,MAAM,UAAU,CAAC,QAAQ,CAAC;YACxB,IAAI;YACJ,OAAO,CAAC,aAAa;YACrB,SAAS;YACT,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,aAAa,EAAE;SAC7C,CAAC,CAAC;IACL,CAAC;IACD,IAAI,gBAAgB,EAAE,CAAC;QACrB,MAAM,UAAU,CAAC,KAAK,CAAC;YACrB,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,aAAa,EAAE;YAC5C,WAAW;SACZ,CAAC,CAAC;IACL,CAAC;IAED,IAAI,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;QAClD,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC;QACpD,OAAO;IACT,CAAC;IAED,MAAM,YAAY,GAAG,WAAW,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAC3E,IAAI,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;QACxC,MAAM,UAAU,CAAC,QAAQ,CAAC;YACxB,IAAI;YACJ,OAAO,CAAC,cAAc;YACtB,SAAS;YACT,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,cAAc,EAAE;SAC9C,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IAED,MAAM,UAAU,CAAC,mBAAmB,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,UAAyB,EACzB,QAAgB,EAChB,OAAe;IAEf,MAAM,kBAAkB,GAAG,oBAAoB,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC5D,MAAM,UAAU,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IACzC,OAAO,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,UAAyB,EACzB,QAAgB;IAEhB,MAAM,iBAAiB,GAAG,oBAAoB,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC;IAC/D,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC;IACjE,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAC9C,MAAM,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,iBAAiB,CAAC,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,UAAyB,EACzB,MAAc,EACd,MAAc;IAEd,qBAAqB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACxC,qBAAqB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAExC,MAAM,SAAS,GAAG,CAAC,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CACjD,CAAC,gBAAgB,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,KAAK,MAAM,CACvD,CAAC;IACF,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC;IAEtD,MAAM,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAC1D,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,OAA6B;IAE7B,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,kBAAkB,CAAC,uBAAuB,EAAE,iBAAiB,CAAC,CAAC;IAC3E,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,kBAAkB,CAAC;IAC1D,MAAM,sBAAsB,GAC1B,OAAO,CAAC,sBAAsB,IAAI,gCAAgC,CAAC;IACrE,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,uBAAuB,CAAC;IAEzE,0BAA0B,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IACjD,0BAA0B,CACxB,sBAAsB,EACtB,0BAA0B,CAC3B,CAAC;IAEF,OAAO,IAAI,mBAAmB,CAAC;QAC7B,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,SAAS;QACT,sBAAsB;QACtB,cAAc;KACf,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { isAbsolute } from \"node:path\";\nimport { simpleGit } from \"simple-git\";\nimport {\n asFailure,\n isNonEmptyString,\n isPositiveInteger,\n isRecord,\n} from \"./internal/utils.js\";\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst DEFAULT_MAX_CONCURRENT_PROCESSES = 1;\nconst DEFAULT_COMMIT_IDENTITY = {\n name: \"Bonfire\",\n email: \"bonfire@localhost\",\n};\nconst NOTHING_TO_COMMIT_MESSAGES = [\n \"nothing to commit\",\n \"nothing added to commit\",\n];\n\nexport type GitCommitIdentity = {\n name: string;\n email: string;\n};\n\nexport type GitRepositoryOptions = {\n repositoryRoot: string;\n timeoutMs?: number;\n maxConcurrentProcesses?: number;\n commitIdentity?: GitCommitIdentity;\n};\n\nexport type GitStatusFile = {\n path: string;\n index: string;\n workingDirectory: string;\n from?: string;\n};\n\nexport type GitStatusSnapshot = {\n current: string | null;\n files: readonly GitStatusFile[];\n clean: boolean;\n};\n\nexport type GitRemoteSnapshot = {\n name: string;\n fetchUrl: string | null;\n pushUrl: string | null;\n};\n\nexport type GitBranchSnapshot = {\n current: string | null;\n all: readonly string[];\n};\n\nexport type GitCommitSnapshot = {\n branch: string;\n commit: string;\n root: boolean;\n};\n\nexport type GitCommitResult = GitCommitSnapshot | null;\n\nexport type GitPushResult = {\n remoteStatus: \"pushed\" | \"local-only\";\n};\n\nexport type GitRepositoryErrorKind =\n | \"missing-git\"\n | \"timeout\"\n | \"conflict\"\n | \"command-failure\"\n | \"malformed-output\"\n | \"invalid-configuration\"\n | \"unexpected\";\n\nexport class GitRepositoryError extends Error {\n readonly kind: GitRepositoryErrorKind;\n readonly operation: string;\n\n constructor(kind: GitRepositoryErrorKind, operation: string) {\n super(`Git operation failed: ${operation}`);\n this.name = \"GitRepositoryError\";\n this.kind = kind;\n this.operation = operation;\n }\n}\n\nexport interface GitRepository {\n readonly repositoryRoot: string;\n currentBranch(): Promise<string>;\n status(pathspecs?: readonly string[]): Promise<GitStatusSnapshot>;\n remotes(): Promise<readonly GitRemoteSnapshot[]>;\n branches(): Promise<GitBranchSnapshot>;\n fetch(remote: string, branch?: string): Promise<void>;\n checkout(args: readonly string[]): Promise<void>;\n checkoutLocalBranch(branch: string): Promise<void>;\n merge(args: readonly string[]): Promise<void>;\n add(pathspecs: readonly string[]): Promise<void>;\n commit(\n message: string,\n pathspecs?: readonly string[],\n ): Promise<GitCommitResult>;\n push(\n remote: string,\n branch: string,\n options?: readonly string[],\n ): Promise<void>;\n restore(pathspecs: readonly string[]): Promise<void>;\n clean(mode: string, pathspecs: readonly string[]): Promise<void>;\n raw(args: readonly string[]): Promise<string>;\n}\n\nfunction classifyError(error: unknown): GitRepositoryErrorKind {\n if (error instanceof GitRepositoryError) return error.kind;\n\n const failure = asFailure(error);\n const message = typeof failure.message === \"string\" ? failure.message : \"\";\n const normalized = message.toLowerCase();\n\n if (failure.code === \"ENOENT\" || normalized.includes(\"enoent\")) {\n return \"missing-git\";\n }\n\n if (\n failure.code === \"ETIMEDOUT\" ||\n failure.killed === true ||\n failure.signal === \"SIGTERM\" ||\n normalized.includes(\"timed out\") ||\n normalized.includes(\"timeout\")\n ) {\n return \"timeout\";\n }\n\n if (\n normalized.includes(\"conflict\") ||\n normalized.includes(\"would be overwritten\") ||\n normalized.includes(\"unmerged paths\")\n ) {\n return \"conflict\";\n }\n\n if (isRecord(error) || error instanceof Error) return \"command-failure\";\n return \"unexpected\";\n}\n\nfunction normalizeError(operation: string, error: unknown) {\n if (error instanceof GitRepositoryError) return error;\n return new GitRepositoryError(classifyError(error), operation);\n}\n\nfunction assertValidPositiveInteger(value: number, option: string) {\n if (!isPositiveInteger(value)) {\n throw new GitRepositoryError(\"invalid-configuration\", option);\n }\n}\n\nfunction assertValidBranchName(value: string, option: string) {\n if (\n !isNonEmptyString(value) ||\n value.startsWith(\"-\") ||\n value.includes(\"\\0\") ||\n /\\s/u.test(value) ||\n value.includes(\"@{\") ||\n value.split(/[\\\\/]/u).some((segment) => segment === \"..\")\n ) {\n throw new GitRepositoryError(\"invalid-configuration\", option);\n }\n}\n\nfunction assertValidPathspec(pathspec: string) {\n if (\n !isNonEmptyString(pathspec) ||\n isAbsolute(pathspec) ||\n /^[A-Za-z]:[\\\\/]/u.test(pathspec) ||\n pathspec.startsWith(\"\\\\\") ||\n pathspec.startsWith(\"-\") ||\n pathspec.startsWith(\":\") ||\n pathspec.includes(\"\\0\") ||\n pathspec.includes(\"*\") ||\n pathspec.includes(\"?\") ||\n pathspec.includes(\"[\") ||\n pathspec.includes(\"]\") ||\n pathspec === \".\" ||\n pathspec.split(/[\\\\/]/u).some((segment) => segment === \"..\")\n ) {\n throw new GitRepositoryError(\"invalid-configuration\", \"pathspec\");\n }\n}\n\nfunction assertValidPathspecs(pathspecs: readonly string[]) {\n if (pathspecs.length === 0) {\n throw new GitRepositoryError(\"invalid-configuration\", \"pathspec\");\n }\n for (const pathspec of pathspecs) assertValidPathspec(pathspec);\n return [...pathspecs];\n}\n\nfunction assertValidArguments(args: readonly string[], operation: string) {\n if (\n args.some(\n (argument) => !isNonEmptyString(argument) || argument.includes(\"\\0\"),\n )\n ) {\n throw new GitRepositoryError(\"invalid-configuration\", operation);\n }\n return [...args];\n}\n\nfunction isNothingToCommit(error: unknown) {\n const failure = asFailure(error);\n const message = typeof failure.message === \"string\" ? failure.message : \"\";\n return NOTHING_TO_COMMIT_MESSAGES.some((needle) =>\n message.toLowerCase().includes(needle),\n );\n}\n\nclass SimpleGitRepository implements GitRepository {\n readonly repositoryRoot: string;\n readonly #git;\n\n constructor(options: Required<GitRepositoryOptions>) {\n this.repositoryRoot = options.repositoryRoot;\n this.#git = simpleGit(options.repositoryRoot, {\n baseDir: options.repositoryRoot,\n maxConcurrentProcesses: options.maxConcurrentProcesses,\n config: [\n `user.name=${options.commitIdentity.name}`,\n `user.email=${options.commitIdentity.email}`,\n ],\n timeout: { block: options.timeoutMs },\n });\n }\n\n async #execute<Value>(operation: string, action: () => Promise<Value>) {\n try {\n return await action();\n } catch (error) {\n throw normalizeError(operation, error);\n }\n }\n\n async currentBranch() {\n const result = await this.#execute(\"current-branch\", () =>\n this.#git.revparse([\"--abbrev-ref\", \"HEAD\"]),\n );\n if (!isNonEmptyString(result) || result.trim().length === 0) {\n throw new GitRepositoryError(\"malformed-output\", \"current-branch\");\n }\n return result.trim();\n }\n\n async status(pathspecs?: readonly string[]) {\n const validatedPathspecs =\n pathspecs === undefined ? undefined : assertValidPathspecs(pathspecs);\n const result = await this.#execute(\"status\", () =>\n validatedPathspecs === undefined\n ? this.#git.status()\n : this.#git.status([\"--\", ...validatedPathspecs]),\n );\n\n if (\n !isRecord(result) ||\n !Array.isArray(result.files) ||\n (typeof result.current !== \"string\" && result.current !== null) ||\n typeof result.isClean !== \"function\"\n ) {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n\n const files: GitStatusFile[] = [];\n for (const file of result.files) {\n if (!isRecord(file)) {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n if (\n typeof file.path !== \"string\" ||\n typeof file.index !== \"string\" ||\n typeof file.working_dir !== \"string\"\n ) {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n\n files.push({\n path: file.path,\n index: file.index,\n workingDirectory: file.working_dir,\n ...(typeof file.from === \"string\" ? { from: file.from } : {}),\n });\n }\n\n let clean: boolean;\n try {\n clean = result.isClean();\n } catch {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n if (typeof clean !== \"boolean\") {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n\n return {\n current: result.current,\n files,\n clean,\n } satisfies GitStatusSnapshot;\n }\n\n async remotes() {\n const result = await this.#execute(\"remotes\", () =>\n this.#git.getRemotes(true),\n );\n if (!Array.isArray(result)) {\n throw new GitRepositoryError(\"malformed-output\", \"remotes\");\n }\n\n const remotes: GitRemoteSnapshot[] = [];\n for (const remote of result) {\n if (!isRecord(remote) || typeof remote.name !== \"string\") {\n throw new GitRepositoryError(\"malformed-output\", \"remotes\");\n }\n const refs = remote.refs;\n if (!isRecord(refs)) {\n throw new GitRepositoryError(\"malformed-output\", \"remotes\");\n }\n if (typeof refs.fetch !== \"string\" || typeof refs.push !== \"string\") {\n throw new GitRepositoryError(\"malformed-output\", \"remotes\");\n }\n\n remotes.push({\n name: remote.name,\n fetchUrl: refs.fetch,\n pushUrl: refs.push,\n });\n }\n return remotes;\n }\n\n async branches() {\n const result = await this.#execute(\"branches\", () =>\n this.#git.branch([\"-a\"]),\n );\n if (\n !isRecord(result) ||\n !Array.isArray(result.all) ||\n (typeof result.current !== \"string\" && result.current !== null) ||\n !result.all.every((branch) => typeof branch === \"string\")\n ) {\n throw new GitRepositoryError(\"malformed-output\", \"branches\");\n }\n\n return {\n current: result.current,\n all: [...result.all],\n } satisfies GitBranchSnapshot;\n }\n\n async fetch(remote: string, branch?: string) {\n assertValidBranchName(remote, \"remote\");\n if (branch !== undefined) assertValidBranchName(branch, \"branch\");\n await this.#execute(\"fetch\", () =>\n branch === undefined\n ? this.#git.fetch(remote)\n : this.#git.fetch(remote, branch),\n );\n }\n\n async checkout(args: readonly string[]) {\n const validatedArgs = assertValidArguments(args, \"checkout\");\n await this.#execute(\"checkout\", () => this.#git.checkout(validatedArgs));\n }\n\n async checkoutLocalBranch(branch: string) {\n assertValidBranchName(branch, \"branch\");\n await this.#execute(\"checkout-local-branch\", () =>\n this.#git.checkoutLocalBranch(branch),\n );\n }\n\n async merge(args: readonly string[]) {\n const validatedArgs = assertValidArguments(args, \"merge\");\n await this.#execute(\"merge\", () => this.#git.merge(validatedArgs));\n }\n\n async add(pathspecs: readonly string[]) {\n const validatedPathspecs = assertValidPathspecs(pathspecs);\n await this.#execute(\"add\", () => this.#git.add(validatedPathspecs));\n }\n\n async commit(message: string, pathspecs?: readonly string[]) {\n if (!isNonEmptyString(message)) {\n throw new GitRepositoryError(\"invalid-configuration\", \"commit-message\");\n }\n const validatedPathspecs =\n pathspecs === undefined ? undefined : assertValidPathspecs(pathspecs);\n\n let result: unknown;\n try {\n result =\n validatedPathspecs === undefined\n ? await this.#git.commit(message)\n : await this.#git.commit(message, [...validatedPathspecs]);\n } catch (error) {\n if (isNothingToCommit(error)) return null;\n throw normalizeError(\"commit\", error);\n }\n if (\n !isRecord(result) ||\n typeof result.branch !== \"string\" ||\n typeof result.commit !== \"string\" ||\n typeof result.root !== \"boolean\"\n ) {\n throw new GitRepositoryError(\"malformed-output\", \"commit\");\n }\n\n return {\n branch: result.branch,\n commit: result.commit,\n root: result.root,\n } satisfies GitCommitSnapshot;\n }\n\n async push(remote: string, branch: string, options: readonly string[] = []) {\n assertValidBranchName(remote, \"remote\");\n assertValidBranchName(branch, \"branch\");\n const validatedOptions = assertValidArguments(options, \"push\");\n await this.#execute(\"push\", () =>\n this.#git.push(remote, branch, validatedOptions),\n );\n }\n\n async restore(pathspecs: readonly string[]) {\n const validatedPathspecs = assertValidPathspecs(pathspecs);\n await this.#execute(\"restore\", () =>\n this.#git.checkout([\"--\", ...validatedPathspecs]),\n );\n }\n\n async clean(mode: string, pathspecs: readonly string[]) {\n if (!/^[a-z]+$/u.test(mode)) {\n throw new GitRepositoryError(\"invalid-configuration\", \"clean-mode\");\n }\n const validatedPathspecs = assertValidPathspecs(pathspecs);\n await this.#execute(\"clean\", () =>\n this.#git.clean(mode, validatedPathspecs),\n );\n }\n\n async raw(args: readonly string[]) {\n const validatedArgs = assertValidArguments(args, \"raw\");\n const result = await this.#execute(\"raw\", () =>\n this.#git.raw(validatedArgs),\n );\n if (typeof result !== \"string\") {\n throw new GitRepositoryError(\"malformed-output\", \"raw\");\n }\n return result;\n }\n}\n\nexport type PrepareIdentityBranchOptions = {\n identityBranch: string;\n primaryBranch: string;\n remote: string;\n};\n\nexport async function prepareIdentityBranch(\n repository: GitRepository,\n options: PrepareIdentityBranchOptions,\n): Promise<void> {\n assertValidBranchName(options.identityBranch, \"identity-branch\");\n assertValidBranchName(options.primaryBranch, \"primary-branch\");\n assertValidBranchName(options.remote, \"remote\");\n\n const currentBranch = await repository.currentBranch();\n if (currentBranch === options.identityBranch) return;\n\n const status = await repository.status();\n if (!status.clean) {\n throw new GitRepositoryError(\"conflict\", \"prepare-identity-branch\");\n }\n\n const remotes = await repository.remotes();\n const configuredRemote = remotes.some(\n (remote) => remote.name === options.remote,\n );\n if (configuredRemote) {\n await repository.fetch(options.remote, options.primaryBranch);\n }\n\n const branches = await repository.branches();\n const hasLocalPrimary = branches.all.includes(options.primaryBranch);\n const remotePrimary = `remotes/${options.remote}/${options.primaryBranch}`;\n const hasRemotePrimary =\n configuredRemote && branches.all.includes(remotePrimary);\n if (!hasLocalPrimary && !hasRemotePrimary) {\n throw new GitRepositoryError(\"command-failure\", \"primary-branch\");\n }\n\n if (hasLocalPrimary) {\n await repository.checkout([options.primaryBranch]);\n } else {\n await repository.checkout([\n \"-b\",\n options.primaryBranch,\n \"--track\",\n `${options.remote}/${options.primaryBranch}`,\n ]);\n }\n if (configuredRemote) {\n await repository.merge([\n `${options.remote}/${options.primaryBranch}`,\n \"--ff-only\",\n ]);\n }\n\n if (branches.all.includes(options.identityBranch)) {\n await repository.checkout([options.identityBranch]);\n return;\n }\n\n const remoteBranch = `remotes/${options.remote}/${options.identityBranch}`;\n if (branches.all.includes(remoteBranch)) {\n await repository.checkout([\n \"-b\",\n options.identityBranch,\n \"--track\",\n `${options.remote}/${options.identityBranch}`,\n ]);\n return;\n }\n\n await repository.checkoutLocalBranch(options.identityBranch);\n}\n\nexport async function commitPath(\n repository: GitRepository,\n pathspec: string,\n message: string,\n): Promise<GitCommitResult> {\n const validatedPathspecs = assertValidPathspecs([pathspec]);\n await repository.add(validatedPathspecs);\n return repository.commit(message, validatedPathspecs);\n}\n\nexport async function discardPath(\n repository: GitRepository,\n pathspec: string,\n): Promise<void> {\n const validatedPathspec = assertValidPathspecs([pathspec])[0]!;\n await repository.raw([\"reset\", \"HEAD\", \"--\", validatedPathspec]);\n await repository.restore([validatedPathspec]);\n await repository.clean(\"fd\", [validatedPathspec]);\n}\n\nexport async function pushBranch(\n repository: GitRepository,\n remote: string,\n branch: string,\n): Promise<GitPushResult> {\n assertValidBranchName(remote, \"remote\");\n assertValidBranchName(branch, \"branch\");\n\n const hasRemote = (await repository.remotes()).some(\n (configuredRemote) => configuredRemote.name === remote,\n );\n if (!hasRemote) return { remoteStatus: \"local-only\" };\n\n await repository.push(remote, branch, [\"--set-upstream\"]);\n return { remoteStatus: \"pushed\" };\n}\n\nexport function createGitRepository(\n options: GitRepositoryOptions,\n): GitRepository {\n if (!isAbsolute(options.repositoryRoot)) {\n throw new GitRepositoryError(\"invalid-configuration\", \"repository-root\");\n }\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const maxConcurrentProcesses =\n options.maxConcurrentProcesses ?? DEFAULT_MAX_CONCURRENT_PROCESSES;\n const commitIdentity = options.commitIdentity ?? DEFAULT_COMMIT_IDENTITY;\n\n assertValidPositiveInteger(timeoutMs, \"timeout\");\n assertValidPositiveInteger(\n maxConcurrentProcesses,\n \"max-concurrent-processes\",\n );\n\n return new SimpleGitRepository({\n repositoryRoot: options.repositoryRoot,\n timeoutMs,\n maxConcurrentProcesses,\n commitIdentity,\n });\n}\n"]}
1
+ {"version":3,"file":"git-repository.js","sourceRoot":"","sources":["../src/git-repository.ts"],"names":[],"mappings":"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","sourcesContent":["import { isAbsolute } from \"node:path\";\nimport { simpleGit } from \"simple-git\";\nimport {\n asFailure,\n isNonEmptyString,\n isPositiveInteger,\n isRecord,\n} from \"./internal/utils.js\";\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst DEFAULT_MAX_CONCURRENT_PROCESSES = 1;\nconst DEFAULT_COMMIT_IDENTITY = {\n name: \"Bonfire\",\n email: \"bonfire@localhost\",\n};\nconst NOTHING_TO_COMMIT_MESSAGES = [\n \"nothing to commit\",\n \"nothing added to commit\",\n];\n\nexport type GitCommitIdentity = {\n name: string;\n email: string;\n};\n\nexport type GitRepositoryOptions = {\n repositoryRoot: string;\n timeoutMs?: number;\n maxConcurrentProcesses?: number;\n commitIdentity?: GitCommitIdentity;\n};\n\nexport type GitStatusFile = {\n path: string;\n index: string;\n workingDirectory: string;\n from?: string;\n};\n\nexport type GitStatusSnapshot = {\n current: string | null;\n files: readonly GitStatusFile[];\n clean: boolean;\n};\n\nexport type GitRemoteSnapshot = {\n name: string;\n fetchUrl: string | null;\n pushUrl: string | null;\n};\n\nexport type GitBranchSnapshot = {\n current: string | null;\n all: readonly string[];\n};\n\nexport type GitCommitSnapshot = {\n branch: string;\n commit: string;\n root: boolean;\n};\n\nexport type GitCommitResult = GitCommitSnapshot | null;\n\nexport type GitPushResult = {\n remoteStatus: \"pushed\" | \"local-only\";\n};\n\nexport type GitRepositoryErrorKind =\n | \"missing-git\"\n | \"timeout\"\n | \"conflict\"\n | \"non-fast-forward\"\n | \"command-failure\"\n | \"malformed-output\"\n | \"invalid-configuration\"\n | \"unexpected\";\n\nexport class GitRepositoryError extends Error {\n readonly kind: GitRepositoryErrorKind;\n readonly operation: string;\n\n constructor(kind: GitRepositoryErrorKind, operation: string) {\n super(`Git operation failed: ${operation}`);\n this.name = \"GitRepositoryError\";\n this.kind = kind;\n this.operation = operation;\n }\n}\n\nexport interface GitRepository {\n readonly repositoryRoot: string;\n currentBranch(): Promise<string>;\n status(pathspecs?: readonly string[]): Promise<GitStatusSnapshot>;\n remotes(): Promise<readonly GitRemoteSnapshot[]>;\n branches(): Promise<GitBranchSnapshot>;\n fetch(remote: string, branch?: string): Promise<void>;\n checkout(args: readonly string[]): Promise<void>;\n checkoutLocalBranch(branch: string): Promise<void>;\n merge(args: readonly string[]): Promise<void>;\n add(pathspecs: readonly string[]): Promise<void>;\n commit(\n message: string,\n pathspecs?: readonly string[],\n ): Promise<GitCommitResult>;\n push(\n remote: string,\n branch: string,\n options?: readonly string[],\n ): Promise<void>;\n restore(pathspecs: readonly string[]): Promise<void>;\n clean(mode: string, pathspecs: readonly string[]): Promise<void>;\n raw(args: readonly string[]): Promise<string>;\n}\n\nfunction classifyError(error: unknown): GitRepositoryErrorKind {\n if (error instanceof GitRepositoryError) return error.kind;\n\n const failure = asFailure(error);\n const message = typeof failure.message === \"string\" ? failure.message : \"\";\n const normalized = message.toLowerCase();\n\n if (failure.code === \"ENOENT\" || normalized.includes(\"enoent\")) {\n return \"missing-git\";\n }\n\n if (\n failure.code === \"ETIMEDOUT\" ||\n failure.killed === true ||\n failure.signal === \"SIGTERM\" ||\n normalized.includes(\"timed out\") ||\n normalized.includes(\"timeout\")\n ) {\n return \"timeout\";\n }\n\n if (\n normalized.includes(\"conflict\") ||\n normalized.includes(\"would be overwritten\") ||\n normalized.includes(\"unmerged paths\")\n ) {\n return \"conflict\";\n }\n\n if (\n normalized.includes(\"non-fast-forward\") ||\n normalized.includes(\"fetch first\") ||\n normalized.includes(\"stale info\")\n ) {\n return \"non-fast-forward\";\n }\n\n if (isRecord(error) || error instanceof Error) return \"command-failure\";\n return \"unexpected\";\n}\n\nfunction normalizeError(operation: string, error: unknown) {\n if (error instanceof GitRepositoryError) return error;\n return new GitRepositoryError(classifyError(error), operation);\n}\n\nfunction assertValidPositiveInteger(value: number, option: string) {\n if (!isPositiveInteger(value)) {\n throw new GitRepositoryError(\"invalid-configuration\", option);\n }\n}\n\nfunction assertValidBranchName(value: string, option: string) {\n if (\n !isNonEmptyString(value) ||\n value.startsWith(\"-\") ||\n value.includes(\"\\0\") ||\n /\\s/u.test(value) ||\n value.includes(\"@{\") ||\n value.split(/[\\\\/]/u).some((segment) => segment === \"..\")\n ) {\n throw new GitRepositoryError(\"invalid-configuration\", option);\n }\n}\n\nfunction assertValidPathspec(pathspec: string) {\n if (\n !isNonEmptyString(pathspec) ||\n isAbsolute(pathspec) ||\n /^[A-Za-z]:[\\\\/]/u.test(pathspec) ||\n pathspec.startsWith(\"\\\\\") ||\n pathspec.startsWith(\"-\") ||\n pathspec.startsWith(\":\") ||\n pathspec.includes(\"\\0\") ||\n pathspec.includes(\"*\") ||\n pathspec.includes(\"?\") ||\n pathspec.includes(\"[\") ||\n pathspec.includes(\"]\") ||\n pathspec === \".\" ||\n pathspec.split(/[\\\\/]/u).some((segment) => segment === \"..\")\n ) {\n throw new GitRepositoryError(\"invalid-configuration\", \"pathspec\");\n }\n}\n\nfunction assertValidPathspecs(pathspecs: readonly string[]) {\n if (pathspecs.length === 0) {\n throw new GitRepositoryError(\"invalid-configuration\", \"pathspec\");\n }\n for (const pathspec of pathspecs) assertValidPathspec(pathspec);\n return [...pathspecs];\n}\n\nfunction assertValidArguments(args: readonly string[], operation: string) {\n if (\n args.some(\n (argument) => !isNonEmptyString(argument) || argument.includes(\"\\0\"),\n )\n ) {\n throw new GitRepositoryError(\"invalid-configuration\", operation);\n }\n return [...args];\n}\n\nfunction isNothingToCommit(error: unknown) {\n const failure = asFailure(error);\n const message = typeof failure.message === \"string\" ? failure.message : \"\";\n return NOTHING_TO_COMMIT_MESSAGES.some((needle) =>\n message.toLowerCase().includes(needle),\n );\n}\n\nclass SimpleGitRepository implements GitRepository {\n readonly repositoryRoot: string;\n readonly #git;\n\n constructor(options: Required<GitRepositoryOptions>) {\n this.repositoryRoot = options.repositoryRoot;\n this.#git = simpleGit(options.repositoryRoot, {\n baseDir: options.repositoryRoot,\n maxConcurrentProcesses: options.maxConcurrentProcesses,\n config: [\n `user.name=${options.commitIdentity.name}`,\n `user.email=${options.commitIdentity.email}`,\n ],\n timeout: { block: options.timeoutMs },\n });\n }\n\n async #execute<Value>(operation: string, action: () => Promise<Value>) {\n try {\n return await action();\n } catch (error) {\n throw normalizeError(operation, error);\n }\n }\n\n async currentBranch() {\n const result = await this.#execute(\"current-branch\", () =>\n this.#git.revparse([\"--abbrev-ref\", \"HEAD\"]),\n );\n if (!isNonEmptyString(result) || result.trim().length === 0) {\n throw new GitRepositoryError(\"malformed-output\", \"current-branch\");\n }\n return result.trim();\n }\n\n async status(pathspecs?: readonly string[]) {\n const validatedPathspecs =\n pathspecs === undefined ? undefined : assertValidPathspecs(pathspecs);\n const result = await this.#execute(\"status\", () =>\n validatedPathspecs === undefined\n ? this.#git.status()\n : this.#git.status([\"--\", ...validatedPathspecs]),\n );\n\n if (\n !isRecord(result) ||\n !Array.isArray(result.files) ||\n (typeof result.current !== \"string\" && result.current !== null) ||\n typeof result.isClean !== \"function\"\n ) {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n\n const files: GitStatusFile[] = [];\n for (const file of result.files) {\n if (!isRecord(file)) {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n if (\n typeof file.path !== \"string\" ||\n typeof file.index !== \"string\" ||\n typeof file.working_dir !== \"string\"\n ) {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n\n files.push({\n path: file.path,\n index: file.index,\n workingDirectory: file.working_dir,\n ...(typeof file.from === \"string\" ? { from: file.from } : {}),\n });\n }\n\n let clean: boolean;\n try {\n clean = result.isClean();\n } catch {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n if (typeof clean !== \"boolean\") {\n throw new GitRepositoryError(\"malformed-output\", \"status\");\n }\n\n return {\n current: result.current,\n files,\n clean,\n } satisfies GitStatusSnapshot;\n }\n\n async remotes() {\n const result = await this.#execute(\"remotes\", () =>\n this.#git.getRemotes(true),\n );\n if (!Array.isArray(result)) {\n throw new GitRepositoryError(\"malformed-output\", \"remotes\");\n }\n\n const remotes: GitRemoteSnapshot[] = [];\n for (const remote of result) {\n if (!isRecord(remote) || typeof remote.name !== \"string\") {\n throw new GitRepositoryError(\"malformed-output\", \"remotes\");\n }\n const refs = remote.refs;\n if (!isRecord(refs)) {\n throw new GitRepositoryError(\"malformed-output\", \"remotes\");\n }\n if (typeof refs.fetch !== \"string\" || typeof refs.push !== \"string\") {\n throw new GitRepositoryError(\"malformed-output\", \"remotes\");\n }\n\n remotes.push({\n name: remote.name,\n fetchUrl: refs.fetch,\n pushUrl: refs.push,\n });\n }\n return remotes;\n }\n\n async branches() {\n const result = await this.#execute(\"branches\", () =>\n this.#git.branch([\"-a\"]),\n );\n if (\n !isRecord(result) ||\n !Array.isArray(result.all) ||\n (typeof result.current !== \"string\" && result.current !== null) ||\n !result.all.every((branch) => typeof branch === \"string\")\n ) {\n throw new GitRepositoryError(\"malformed-output\", \"branches\");\n }\n\n return {\n current: result.current,\n all: [...result.all],\n } satisfies GitBranchSnapshot;\n }\n\n async fetch(remote: string, branch?: string) {\n assertValidBranchName(remote, \"remote\");\n if (branch !== undefined) assertValidBranchName(branch, \"branch\");\n await this.#execute(\"fetch\", () =>\n branch === undefined\n ? this.#git.fetch(remote)\n : this.#git.fetch(remote, branch),\n );\n }\n\n async checkout(args: readonly string[]) {\n const validatedArgs = assertValidArguments(args, \"checkout\");\n await this.#execute(\"checkout\", () => this.#git.checkout(validatedArgs));\n }\n\n async checkoutLocalBranch(branch: string) {\n assertValidBranchName(branch, \"branch\");\n await this.#execute(\"checkout-local-branch\", () =>\n this.#git.checkoutLocalBranch(branch),\n );\n }\n\n async merge(args: readonly string[]) {\n const validatedArgs = assertValidArguments(args, \"merge\");\n await this.#execute(\"merge\", () => this.#git.merge(validatedArgs));\n }\n\n async add(pathspecs: readonly string[]) {\n const validatedPathspecs = assertValidPathspecs(pathspecs);\n await this.#execute(\"add\", () => this.#git.add(validatedPathspecs));\n }\n\n async commit(message: string, pathspecs?: readonly string[]) {\n if (!isNonEmptyString(message)) {\n throw new GitRepositoryError(\"invalid-configuration\", \"commit-message\");\n }\n const validatedPathspecs =\n pathspecs === undefined ? undefined : assertValidPathspecs(pathspecs);\n\n let result: unknown;\n try {\n result =\n validatedPathspecs === undefined\n ? await this.#git.commit(message)\n : await this.#git.commit(message, [...validatedPathspecs]);\n } catch (error) {\n if (isNothingToCommit(error)) return null;\n throw normalizeError(\"commit\", error);\n }\n if (\n !isRecord(result) ||\n typeof result.branch !== \"string\" ||\n typeof result.commit !== \"string\" ||\n typeof result.root !== \"boolean\"\n ) {\n throw new GitRepositoryError(\"malformed-output\", \"commit\");\n }\n\n return {\n branch: result.branch,\n commit: result.commit,\n root: result.root,\n } satisfies GitCommitSnapshot;\n }\n\n async push(remote: string, branch: string, options: readonly string[] = []) {\n assertValidBranchName(remote, \"remote\");\n assertValidBranchName(branch, \"branch\");\n const validatedOptions = assertValidArguments(options, \"push\");\n await this.#execute(\"push\", () =>\n this.#git.push(remote, branch, validatedOptions),\n );\n }\n\n async restore(pathspecs: readonly string[]) {\n const validatedPathspecs = assertValidPathspecs(pathspecs);\n await this.#execute(\"restore\", () =>\n this.#git.checkout([\"--\", ...validatedPathspecs]),\n );\n }\n\n async clean(mode: string, pathspecs: readonly string[]) {\n if (!/^[a-z]+$/u.test(mode)) {\n throw new GitRepositoryError(\"invalid-configuration\", \"clean-mode\");\n }\n const validatedPathspecs = assertValidPathspecs(pathspecs);\n await this.#execute(\"clean\", () =>\n this.#git.clean(mode, validatedPathspecs),\n );\n }\n\n async raw(args: readonly string[]) {\n const validatedArgs = assertValidArguments(args, \"raw\");\n const result = await this.#execute(\"raw\", () =>\n this.#git.raw(validatedArgs),\n );\n if (typeof result !== \"string\") {\n throw new GitRepositoryError(\"malformed-output\", \"raw\");\n }\n return result;\n }\n}\n\nexport type PrepareIdentityBranchOptions = {\n identityBranch: string;\n primaryBranch: string;\n remote: string;\n};\n\nexport async function prepareIdentityBranch(\n repository: GitRepository,\n options: PrepareIdentityBranchOptions,\n): Promise<void> {\n assertValidBranchName(options.identityBranch, \"identity-branch\");\n assertValidBranchName(options.primaryBranch, \"primary-branch\");\n assertValidBranchName(options.remote, \"remote\");\n\n const currentBranch = await repository.currentBranch();\n if (currentBranch === options.identityBranch) return;\n\n const status = await repository.status();\n if (!status.clean) {\n throw new GitRepositoryError(\"conflict\", \"prepare-identity-branch\");\n }\n\n const remotes = await repository.remotes();\n const configuredRemote = remotes.some(\n (remote) => remote.name === options.remote,\n );\n if (configuredRemote) {\n await repository.fetch(options.remote, options.primaryBranch);\n }\n\n const branches = await repository.branches();\n const hasLocalPrimary = branches.all.includes(options.primaryBranch);\n const remotePrimary = `remotes/${options.remote}/${options.primaryBranch}`;\n const hasRemotePrimary =\n configuredRemote && branches.all.includes(remotePrimary);\n if (!hasLocalPrimary && !hasRemotePrimary) {\n throw new GitRepositoryError(\"command-failure\", \"primary-branch\");\n }\n\n if (hasLocalPrimary) {\n await repository.checkout([options.primaryBranch]);\n } else {\n await repository.checkout([\n \"-b\",\n options.primaryBranch,\n \"--track\",\n `${options.remote}/${options.primaryBranch}`,\n ]);\n }\n if (configuredRemote) {\n await repository.merge([\n `${options.remote}/${options.primaryBranch}`,\n \"--ff-only\",\n ]);\n }\n\n if (branches.all.includes(options.identityBranch)) {\n await repository.checkout([options.identityBranch]);\n return;\n }\n\n const remoteBranch = `remotes/${options.remote}/${options.identityBranch}`;\n if (branches.all.includes(remoteBranch)) {\n await repository.checkout([\n \"-b\",\n options.identityBranch,\n \"--track\",\n `${options.remote}/${options.identityBranch}`,\n ]);\n return;\n }\n\n await repository.checkoutLocalBranch(options.identityBranch);\n}\n\nexport async function commitPath(\n repository: GitRepository,\n pathspec: string,\n message: string,\n): Promise<GitCommitResult> {\n const validatedPathspecs = assertValidPathspecs([pathspec]);\n await repository.add(validatedPathspecs);\n return repository.commit(message, validatedPathspecs);\n}\n\nexport async function discardPath(\n repository: GitRepository,\n pathspec: string,\n): Promise<void> {\n const validatedPathspec = assertValidPathspecs([pathspec])[0]!;\n await repository.raw([\"reset\", \"HEAD\", \"--\", validatedPathspec]);\n await repository.restore([validatedPathspec]);\n await repository.clean(\"fd\", [validatedPathspec]);\n}\n\nexport async function restorePathFromRef(\n repository: GitRepository,\n ref: string,\n pathspec: string,\n): Promise<void> {\n assertValidArguments([ref], \"restore-ref\");\n const validatedPathspec = assertValidPathspecs([pathspec])[0]!;\n await repository.raw([\n \"restore\",\n \"--source\",\n ref,\n \"--staged\",\n \"--worktree\",\n \"--\",\n validatedPathspec,\n ]);\n}\n\nexport async function pushCommitToBranch(\n repository: GitRepository,\n remote: string,\n commit: string,\n branch: string,\n): Promise<GitPushResult> {\n assertValidBranchName(remote, \"remote\");\n assertValidBranchName(branch, \"branch\");\n if (!/^[0-9a-f]{40,64}$/iu.test(commit)) {\n throw new GitRepositoryError(\"invalid-configuration\", \"commit\");\n }\n\n const hasRemote = (await repository.remotes()).some(\n (configuredRemote) => configuredRemote.name === remote,\n );\n if (!hasRemote) return { remoteStatus: \"local-only\" };\n\n await repository.raw([\"push\", remote, `${commit}:refs/heads/${branch}`]);\n return { remoteStatus: \"pushed\" };\n}\n\nexport async function pushBranch(\n repository: GitRepository,\n remote: string,\n branch: string,\n): Promise<GitPushResult> {\n assertValidBranchName(remote, \"remote\");\n assertValidBranchName(branch, \"branch\");\n\n const hasRemote = (await repository.remotes()).some(\n (configuredRemote) => configuredRemote.name === remote,\n );\n if (!hasRemote) return { remoteStatus: \"local-only\" };\n\n await repository.push(remote, branch, [\"--set-upstream\"]);\n return { remoteStatus: \"pushed\" };\n}\n\nexport function createGitRepository(\n options: GitRepositoryOptions,\n): GitRepository {\n if (!isAbsolute(options.repositoryRoot)) {\n throw new GitRepositoryError(\"invalid-configuration\", \"repository-root\");\n }\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const maxConcurrentProcesses =\n options.maxConcurrentProcesses ?? DEFAULT_MAX_CONCURRENT_PROCESSES;\n const commitIdentity = options.commitIdentity ?? DEFAULT_COMMIT_IDENTITY;\n\n assertValidPositiveInteger(timeoutMs, \"timeout\");\n assertValidPositiveInteger(\n maxConcurrentProcesses,\n \"max-concurrent-processes\",\n );\n\n return new SimpleGitRepository({\n repositoryRoot: options.repositoryRoot,\n timeoutMs,\n maxConcurrentProcesses,\n commitIdentity,\n });\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"github-cli.d.ts","sourceRoot":"","sources":["../src/github-cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAGL,KAAK,sBAAsB,EAC5B,MAAM,qBAAqB,CAAC;AAsB7B,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,iBAAiB,GAAG,SAAS,CAAC;AAE7E,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IAEtC,YAAY,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,oBAAgC,EAIpE;CACF;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,sBAAsB,CAAC;CAClC,CAAC;AAEF,MAAM,WAAW,mBAAmB;IAClC,OAAO,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;IACpC,aAAa,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAC;CAC7C;AAwGD,qBAAa,eAAgB,YAAW,mBAAmB;;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAI1B,YAAY,OAAO,EAAE,sBAAsB,EAG1C;IAEK,OAAO,IAAI,OAAO,CAAC,eAAe,CAAC,CA+BxC;IAEK,aAAa,+BA6GlB;CACF","sourcesContent":["import {\n parseBonfireIdentity,\n type BonfireIdentity,\n} from \"@ebay-dt/bonfire-core\";\nimport {\n defaultGitHubCliProcessRunner,\n type GitHubCliChildProcess,\n type GitHubCliProcessRunner,\n} from \"./process-runner.js\";\n\nconst GH_EXECUTABLE = \"gh\";\nconst IDENTITY_TIMEOUT = 10_000;\nconst IDENTITY_MAX_BUFFER = 1024 * 1024;\nconst DEVICE_LOGIN_TIMEOUT = 10_000;\nconst ACTIVE_LOGIN_WINDOW = 10 * 60 * 1000;\nconst GH_LOGIN_CODE_LABEL_PATTERN = /\\bone[-\\s]time\\s+code\\b/i;\nconst GH_DEVICE_CODE_PATTERN = /\\b[A-Za-z0-9]+(?:-[A-Za-z0-9]+)+\\b/;\nconst ANSI_ESCAPE_PATTERN = new RegExp(\n `${String.fromCharCode(27)}\\\\[[0-?]*[ -/]*[@-~]`,\n \"g\",\n);\n\ntype GitHubCliFailure = {\n code?: unknown;\n killed?: unknown;\n signal?: unknown;\n stderr?: unknown;\n stdout?: unknown;\n};\n\nexport type GitHubCliErrorReason = \"missing\" | \"unauthenticated\" | \"unknown\";\n\nexport class GitHubCliError extends Error {\n readonly reason: GitHubCliErrorReason;\n\n constructor(message: string, reason: GitHubCliErrorReason = \"unknown\") {\n super(message);\n this.name = \"GitHubCliError\";\n this.reason = reason;\n }\n}\n\nexport type GitHubCliWebLogin = {\n code: string | null;\n deviceUrl: string;\n};\n\nexport type GitHubCliClientOptions = {\n hostname: string;\n process?: GitHubCliProcessRunner;\n};\n\nexport interface GitHubCliClientLike {\n getUser(): Promise<BonfireIdentity>;\n startWebLogin(): Promise<GitHubCliWebLogin>;\n}\n\ntype ActiveLogin = {\n child: GitHubCliChildProcess;\n code: string | null;\n settled: boolean;\n startedAt: number;\n promise: Promise<GitHubCliWebLogin>;\n};\n\nfunction asFailure(error: unknown): GitHubCliFailure {\n return error && typeof error === \"object\" ? (error as GitHubCliFailure) : {};\n}\n\nfunction outputFrom(value: unknown): string {\n return typeof value === \"string\" ? value : \"\";\n}\n\nfunction extractDeviceCode(output: string): string | null {\n const cleanOutput = output.replace(ANSI_ESCAPE_PATTERN, \"\").replace(/\\r/g, \"\");\n const lines = cleanOutput.split(\"\\n\");\n\n for (let index = 0; index < lines.length; index += 1) {\n const line = lines[index];\n if (line === undefined) continue;\n const label = line.match(GH_LOGIN_CODE_LABEL_PATTERN);\n if (!label || label.index === undefined) continue;\n\n const remainder = line.slice(label.index + label[0].length);\n const match =\n remainder.match(GH_DEVICE_CODE_PATTERN) ??\n lines[index + 1]?.match(GH_DEVICE_CODE_PATTERN);\n if (match) return match[0];\n }\n\n return null;\n}\n\nfunction projectIdentityFields(value: unknown) {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return value;\n }\n\n const record = value as Record<string, unknown>;\n return {\n login: record.login,\n ...(Object.hasOwn(record, \"name\") ? { name: record.name } : {}),\n avatar_url: record.avatar_url,\n };\n}\n\nfunction isUnauthenticatedOutput(output: string): boolean {\n const normalized = output.toLowerCase();\n return (\n normalized.includes(\"gh auth login\") ||\n normalized.includes(\"not logged into\") ||\n normalized.includes(\"authentication required\") ||\n normalized.includes(\"must authenticate\") ||\n /\\b(?:http(?:\\s+status)?\\s*)?401\\b/.test(normalized)\n );\n}\n\nfunction describeFailure(failure: GitHubCliFailure): string {\n if (failure.code === \"ENOENT\") {\n return \"GitHub CLI (`gh`) is not installed or is not available on PATH.\";\n }\n\n if (\n failure.code === \"ETIMEDOUT\" ||\n failure.killed === true ||\n failure.signal === \"SIGTERM\"\n ) {\n return \"GitHub CLI command timed out.\";\n }\n\n if (failure.code === \"ERR_CHILD_PROCESS_STDIO_MAXBUFFER\") {\n return \"GitHub CLI output exceeded the allowed limit.\";\n }\n\n return \"GitHub CLI command failed.\";\n}\n\nfunction commandFailure(error: unknown, hostname: string): GitHubCliError {\n const failure = asFailure(error);\n const output = `${outputFrom(failure.stdout)}\\n${outputFrom(failure.stderr)}`;\n const guidance = ` Run: gh auth login --hostname ${hostname}`;\n\n if (failure.code === \"ENOENT\") {\n return new GitHubCliError(\n `GitHub CLI (\\`gh\\`) is not installed or is not available on PATH.${guidance}`,\n \"missing\",\n );\n }\n\n if (isUnauthenticatedOutput(output)) {\n return new GitHubCliError(\n `GitHub CLI is not authenticated for ${hostname}.${guidance}`,\n \"unauthenticated\",\n );\n }\n\n return new GitHubCliError(`${describeFailure(failure)}${guidance}`);\n}\n\nexport class GitHubCliClient implements GitHubCliClientLike {\n readonly hostname: string;\n readonly #process: GitHubCliProcessRunner;\n #activeLogin: ActiveLogin | null = null;\n\n constructor(options: GitHubCliClientOptions) {\n this.hostname = options.hostname;\n this.#process = options.process ?? defaultGitHubCliProcessRunner;\n }\n\n async getUser(): Promise<BonfireIdentity> {\n const args = [\"api\", \"--hostname\", this.hostname, \"user\"];\n let result;\n\n try {\n result = await this.#process.execFile(GH_EXECUTABLE, args, {\n timeout: IDENTITY_TIMEOUT,\n maxBuffer: IDENTITY_MAX_BUFFER,\n });\n } catch (error) {\n throw commandFailure(error, this.hostname);\n }\n\n if (isUnauthenticatedOutput(`${result.stdout}\\n${result.stderr}`)) {\n throw commandFailure(result, this.hostname);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(result.stdout);\n } catch {\n throw new GitHubCliError(\n `GitHub CLI returned malformed user data. Run: gh auth login --hostname ${this.hostname}`,\n );\n }\n\n try {\n return parseBonfireIdentity(projectIdentityFields(parsed));\n } catch {\n throw new GitHubCliError(\"GitHub CLI returned an invalid user profile.\");\n }\n }\n\n async startWebLogin() {\n const activeLogin = this.#activeLogin;\n if (activeLogin) {\n const withinReuseWindow =\n Date.now() - activeLogin.startedAt < ACTIVE_LOGIN_WINDOW;\n if (\n !activeLogin.settled ||\n (activeLogin.code !== null && withinReuseWindow)\n ) {\n return activeLogin.promise;\n }\n\n try {\n activeLogin.child.kill(\"SIGTERM\");\n } catch {\n throw new GitHubCliError(\n \"The previous GitHub CLI login could not be terminated.\",\n );\n }\n this.#activeLogin = null;\n }\n\n const spawn = this.#process.spawn;\n if (!spawn) {\n throw new GitHubCliError(\"GitHub CLI device login is unavailable.\");\n }\n\n let child: GitHubCliChildProcess;\n try {\n child = spawn(\n GH_EXECUTABLE,\n [\n \"auth\",\n \"login\",\n \"--hostname\",\n this.hostname,\n \"--web\",\n \"--clipboard\",\n \"--git-protocol\",\n \"ssh\",\n \"--skip-ssh-key\",\n ],\n { stdio: [\"ignore\", \"pipe\", \"pipe\"] },\n );\n } catch (error) {\n throw commandFailure(error, this.hostname);\n }\n\n let resolveLogin!: (login: GitHubCliWebLogin) => void;\n let rejectLogin!: (error: GitHubCliError) => void;\n const promise = new Promise<GitHubCliWebLogin>((resolve, reject) => {\n resolveLogin = resolve;\n rejectLogin = reject;\n });\n const login: ActiveLogin = {\n child,\n code: null,\n settled: false,\n startedAt: Date.now(),\n promise,\n };\n this.#activeLogin = login;\n\n let output = \"\";\n let settled = false;\n let timer: ReturnType<typeof setTimeout>;\n\n const clearActiveLogin = () => {\n if (this.#activeLogin?.child === child) this.#activeLogin = null;\n };\n\n const settle = (code: string | null) => {\n if (settled) return;\n settled = true;\n login.code = code;\n login.settled = true;\n clearTimeout(timer);\n resolveLogin({\n code,\n deviceUrl: `https://${this.hostname}/login/device`,\n });\n };\n\n const fail = (error: unknown) => {\n clearActiveLogin();\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n rejectLogin(commandFailure(error, this.hostname));\n };\n\n const inspectOutput = (chunk: string | Uint8Array) => {\n output +=\n typeof chunk === \"string\" ? chunk : Buffer.from(chunk).toString(\"utf8\");\n const code = extractDeviceCode(output);\n if (code) settle(code);\n };\n\n timer = setTimeout(() => settle(null), DEVICE_LOGIN_TIMEOUT);\n timer.unref?.();\n child.stdout?.on(\"data\", inspectOutput);\n child.stderr?.on(\"data\", inspectOutput);\n child.once(\"error\", fail);\n child.once(\"close\", () => {\n if (!settled) settle(null);\n clearActiveLogin();\n });\n\n return promise;\n }\n}\n"]}
1
+ {"version":3,"file":"github-cli.d.ts","sourceRoot":"","sources":["../src/github-cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAGL,KAAK,sBAAsB,EAC5B,MAAM,qBAAqB,CAAC;AAsB7B,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,iBAAiB,GAAG,SAAS,CAAC;AAE7E,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IAEtC,YAAY,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,oBAAgC,EAIpE;CACF;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,sBAAsB,CAAC;CAClC,CAAC;AAEF,MAAM,WAAW,mBAAmB;IAClC,OAAO,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;IACpC,aAAa,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAC;CAC7C;AA0GD,qBAAa,eAAgB,YAAW,mBAAmB;;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAI1B,YAAY,OAAO,EAAE,sBAAsB,EAG1C;IAEK,OAAO,IAAI,OAAO,CAAC,eAAe,CAAC,CA+BxC;IAEK,aAAa,+BA6GlB;CACF","sourcesContent":["import {\n parseBonfireIdentity,\n type BonfireIdentity,\n} from \"@ebay-dt/bonfire-core\";\nimport {\n defaultGitHubCliProcessRunner,\n type GitHubCliChildProcess,\n type GitHubCliProcessRunner,\n} from \"./process-runner.js\";\n\nconst GH_EXECUTABLE = \"gh\";\nconst IDENTITY_TIMEOUT = 10_000;\nconst IDENTITY_MAX_BUFFER = 1024 * 1024;\nconst DEVICE_LOGIN_TIMEOUT = 10_000;\nconst ACTIVE_LOGIN_WINDOW = 10 * 60 * 1000;\nconst GH_LOGIN_CODE_LABEL_PATTERN = /\\bone[-\\s]time\\s+code\\b/i;\nconst GH_DEVICE_CODE_PATTERN = /\\b[A-Za-z0-9]+(?:-[A-Za-z0-9]+)+\\b/;\nconst ANSI_ESCAPE_PATTERN = new RegExp(\n `${String.fromCharCode(27)}\\\\[[0-?]*[ -/]*[@-~]`,\n \"g\",\n);\n\ntype GitHubCliFailure = {\n code?: unknown;\n killed?: unknown;\n signal?: unknown;\n stderr?: unknown;\n stdout?: unknown;\n};\n\nexport type GitHubCliErrorReason = \"missing\" | \"unauthenticated\" | \"unknown\";\n\nexport class GitHubCliError extends Error {\n readonly reason: GitHubCliErrorReason;\n\n constructor(message: string, reason: GitHubCliErrorReason = \"unknown\") {\n super(message);\n this.name = \"GitHubCliError\";\n this.reason = reason;\n }\n}\n\nexport type GitHubCliWebLogin = {\n code: string | null;\n deviceUrl: string;\n};\n\nexport type GitHubCliClientOptions = {\n hostname: string;\n process?: GitHubCliProcessRunner;\n};\n\nexport interface GitHubCliClientLike {\n getUser(): Promise<BonfireIdentity>;\n startWebLogin(): Promise<GitHubCliWebLogin>;\n}\n\ntype ActiveLogin = {\n child: GitHubCliChildProcess;\n code: string | null;\n settled: boolean;\n startedAt: number;\n promise: Promise<GitHubCliWebLogin>;\n};\n\nfunction asFailure(error: unknown): GitHubCliFailure {\n return error && typeof error === \"object\" ? (error as GitHubCliFailure) : {};\n}\n\nfunction outputFrom(value: unknown): string {\n return typeof value === \"string\" ? value : \"\";\n}\n\nfunction extractDeviceCode(output: string): string | null {\n const cleanOutput = output\n .replace(ANSI_ESCAPE_PATTERN, \"\")\n .replace(/\\r/g, \"\");\n const lines = cleanOutput.split(\"\\n\");\n\n for (let index = 0; index < lines.length; index += 1) {\n const line = lines[index];\n if (line === undefined) continue;\n const label = line.match(GH_LOGIN_CODE_LABEL_PATTERN);\n if (!label || label.index === undefined) continue;\n\n const remainder = line.slice(label.index + label[0].length);\n const match =\n remainder.match(GH_DEVICE_CODE_PATTERN) ??\n lines[index + 1]?.match(GH_DEVICE_CODE_PATTERN);\n if (match) return match[0];\n }\n\n return null;\n}\n\nfunction projectIdentityFields(value: unknown) {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return value;\n }\n\n const record = value as Record<string, unknown>;\n return {\n login: record.login,\n ...(Object.hasOwn(record, \"name\") ? { name: record.name } : {}),\n avatar_url: record.avatar_url,\n };\n}\n\nfunction isUnauthenticatedOutput(output: string): boolean {\n const normalized = output.toLowerCase();\n return (\n normalized.includes(\"gh auth login\") ||\n normalized.includes(\"not logged into\") ||\n normalized.includes(\"authentication required\") ||\n normalized.includes(\"must authenticate\") ||\n /\\b(?:http(?:\\s+status)?\\s*)?401\\b/.test(normalized)\n );\n}\n\nfunction describeFailure(failure: GitHubCliFailure): string {\n if (failure.code === \"ENOENT\") {\n return \"GitHub CLI (`gh`) is not installed or is not available on PATH.\";\n }\n\n if (\n failure.code === \"ETIMEDOUT\" ||\n failure.killed === true ||\n failure.signal === \"SIGTERM\"\n ) {\n return \"GitHub CLI command timed out.\";\n }\n\n if (failure.code === \"ERR_CHILD_PROCESS_STDIO_MAXBUFFER\") {\n return \"GitHub CLI output exceeded the allowed limit.\";\n }\n\n return \"GitHub CLI command failed.\";\n}\n\nfunction commandFailure(error: unknown, hostname: string): GitHubCliError {\n const failure = asFailure(error);\n const output = `${outputFrom(failure.stdout)}\\n${outputFrom(failure.stderr)}`;\n const guidance = ` Run: gh auth login --hostname ${hostname}`;\n\n if (failure.code === \"ENOENT\") {\n return new GitHubCliError(\n `GitHub CLI (\\`gh\\`) is not installed or is not available on PATH.${guidance}`,\n \"missing\",\n );\n }\n\n if (isUnauthenticatedOutput(output)) {\n return new GitHubCliError(\n `GitHub CLI is not authenticated for ${hostname}.${guidance}`,\n \"unauthenticated\",\n );\n }\n\n return new GitHubCliError(`${describeFailure(failure)}${guidance}`);\n}\n\nexport class GitHubCliClient implements GitHubCliClientLike {\n readonly hostname: string;\n readonly #process: GitHubCliProcessRunner;\n #activeLogin: ActiveLogin | null = null;\n\n constructor(options: GitHubCliClientOptions) {\n this.hostname = options.hostname;\n this.#process = options.process ?? defaultGitHubCliProcessRunner;\n }\n\n async getUser(): Promise<BonfireIdentity> {\n const args = [\"api\", \"--hostname\", this.hostname, \"user\"];\n let result;\n\n try {\n result = await this.#process.execFile(GH_EXECUTABLE, args, {\n timeout: IDENTITY_TIMEOUT,\n maxBuffer: IDENTITY_MAX_BUFFER,\n });\n } catch (error) {\n throw commandFailure(error, this.hostname);\n }\n\n if (isUnauthenticatedOutput(`${result.stdout}\\n${result.stderr}`)) {\n throw commandFailure(result, this.hostname);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(result.stdout);\n } catch {\n throw new GitHubCliError(\n `GitHub CLI returned malformed user data. Run: gh auth login --hostname ${this.hostname}`,\n );\n }\n\n try {\n return parseBonfireIdentity(projectIdentityFields(parsed));\n } catch {\n throw new GitHubCliError(\"GitHub CLI returned an invalid user profile.\");\n }\n }\n\n async startWebLogin() {\n const activeLogin = this.#activeLogin;\n if (activeLogin) {\n const withinReuseWindow =\n Date.now() - activeLogin.startedAt < ACTIVE_LOGIN_WINDOW;\n if (\n !activeLogin.settled ||\n (activeLogin.code !== null && withinReuseWindow)\n ) {\n return activeLogin.promise;\n }\n\n try {\n activeLogin.child.kill(\"SIGTERM\");\n } catch {\n throw new GitHubCliError(\n \"The previous GitHub CLI login could not be terminated.\",\n );\n }\n this.#activeLogin = null;\n }\n\n const spawn = this.#process.spawn;\n if (!spawn) {\n throw new GitHubCliError(\"GitHub CLI device login is unavailable.\");\n }\n\n let child: GitHubCliChildProcess;\n try {\n child = spawn(\n GH_EXECUTABLE,\n [\n \"auth\",\n \"login\",\n \"--hostname\",\n this.hostname,\n \"--web\",\n \"--clipboard\",\n \"--git-protocol\",\n \"ssh\",\n \"--skip-ssh-key\",\n ],\n { stdio: [\"ignore\", \"pipe\", \"pipe\"] },\n );\n } catch (error) {\n throw commandFailure(error, this.hostname);\n }\n\n let resolveLogin!: (login: GitHubCliWebLogin) => void;\n let rejectLogin!: (error: GitHubCliError) => void;\n const promise = new Promise<GitHubCliWebLogin>((resolve, reject) => {\n resolveLogin = resolve;\n rejectLogin = reject;\n });\n const login: ActiveLogin = {\n child,\n code: null,\n settled: false,\n startedAt: Date.now(),\n promise,\n };\n this.#activeLogin = login;\n\n let output = \"\";\n let settled = false;\n let timer: ReturnType<typeof setTimeout>;\n\n const clearActiveLogin = () => {\n if (this.#activeLogin?.child === child) this.#activeLogin = null;\n };\n\n const settle = (code: string | null) => {\n if (settled) return;\n settled = true;\n login.code = code;\n login.settled = true;\n clearTimeout(timer);\n resolveLogin({\n code,\n deviceUrl: `https://${this.hostname}/login/device`,\n });\n };\n\n const fail = (error: unknown) => {\n clearActiveLogin();\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n rejectLogin(commandFailure(error, this.hostname));\n };\n\n const inspectOutput = (chunk: string | Uint8Array) => {\n output +=\n typeof chunk === \"string\" ? chunk : Buffer.from(chunk).toString(\"utf8\");\n const code = extractDeviceCode(output);\n if (code) settle(code);\n };\n\n timer = setTimeout(() => settle(null), DEVICE_LOGIN_TIMEOUT);\n timer.unref?.();\n child.stdout?.on(\"data\", inspectOutput);\n child.stderr?.on(\"data\", inspectOutput);\n child.once(\"error\", fail);\n child.once(\"close\", () => {\n if (!settled) settle(null);\n clearActiveLogin();\n });\n\n return promise;\n }\n}\n"]}
@@ -23,7 +23,9 @@ function outputFrom(value) {
23
23
  return typeof value === "string" ? value : "";
24
24
  }
25
25
  function extractDeviceCode(output) {
26
- const cleanOutput = output.replace(ANSI_ESCAPE_PATTERN, "").replace(/\r/g, "");
26
+ const cleanOutput = output
27
+ .replace(ANSI_ESCAPE_PATTERN, "")
28
+ .replace(/\r/g, "");
27
29
  const lines = cleanOutput.split("\n");
28
30
  for (let index = 0; index < lines.length; index += 1) {
29
31
  const line = lines[index];