@ebay-dt/bonfire-node 0.2.0-alpha.7 → 0.2.0-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/deployment-provider.d.ts +1 -0
- package/dist/deployment-provider.d.ts.map +1 -1
- package/dist/deployment-provider.js +1 -0
- package/dist/deployment-provider.js.map +1 -1
- package/dist/git-repository.d.ts +2 -1
- package/dist/git-repository.d.ts.map +1 -1
- package/dist/git-repository.js +17 -0
- package/dist/git-repository.js.map +1 -1
- package/dist/lifecycle/deployment-worktree.d.ts +35 -0
- package/dist/lifecycle/deployment-worktree.d.ts.map +1 -0
- package/dist/lifecycle/deployment-worktree.js +302 -0
- package/dist/lifecycle/deployment-worktree.js.map +1 -0
- package/dist/lifecycle/local-prototype-lifecycle.d.ts +2 -0
- package/dist/lifecycle/local-prototype-lifecycle.d.ts.map +1 -1
- package/dist/lifecycle/local-prototype-lifecycle.js +157 -52
- package/dist/lifecycle/local-prototype-lifecycle.js.map +1 -1
- 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;
|
|
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"]}
|
|
@@ -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"]}
|
package/dist/git-repository.d.ts
CHANGED
|
@@ -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;
|
|
@@ -69,6 +69,7 @@ export declare function prepareIdentityBranch(repository: GitRepository, options
|
|
|
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
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>;
|
|
72
73
|
export declare function pushBranch(repository: GitRepository, remote: string, branch: string): Promise<GitPushResult>;
|
|
73
74
|
export declare function createGitRepository(options: GitRepositoryOptions): GitRepository;
|
|
74
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 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 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"]}
|
package/dist/git-repository.js
CHANGED
|
@@ -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";
|
|
@@ -373,6 +378,18 @@ export async function restorePathFromRef(repository, ref, pathspec) {
|
|
|
373
378
|
validatedPathspec,
|
|
374
379
|
]);
|
|
375
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
|
+
}
|
|
376
393
|
export async function pushBranch(repository, remote, branch) {
|
|
377
394
|
assertValidBranchName(remote, "remote");
|
|
378
395
|
assertValidBranchName(branch, "branch");
|
|
@@ -1 +1 @@
|
|
|
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 | \"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 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 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"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type GitCommitIdentity, type GitRepository } from "../git-repository.js";
|
|
2
|
+
export type DeploymentWorktreeOptions = {
|
|
3
|
+
remote: string;
|
|
4
|
+
primaryBranch: string;
|
|
5
|
+
sourceSha: string;
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
commitIdentity?: GitCommitIdentity;
|
|
8
|
+
};
|
|
9
|
+
export type DeploymentWorktreeErrorKind = "invalid-source" | "preflight-conflict" | "preflight-failure" | "publication-conflict" | "publication-rejected" | "publication-failure" | "cleanup-failure";
|
|
10
|
+
type DeploymentPhase = "preflight" | "publication";
|
|
11
|
+
type DeploymentStep = "fetch" | "directory" | "marker" | "worktree-add" | "source-verify" | "merge" | "metadata-read" | "metadata-write" | "metadata-commit" | "push" | "cleanup";
|
|
12
|
+
/** Log only finite classifications; Git/OS messages may contain paths or credentials. */
|
|
13
|
+
export declare function reportDeploymentIssue(phase: DeploymentPhase, step: DeploymentStep, error: unknown): void;
|
|
14
|
+
export declare class DeploymentWorktreeError extends Error {
|
|
15
|
+
readonly kind: DeploymentWorktreeErrorKind;
|
|
16
|
+
readonly cleanupFailed: boolean;
|
|
17
|
+
readonly operationError: unknown;
|
|
18
|
+
readonly cause: unknown;
|
|
19
|
+
constructor(kind: DeploymentWorktreeErrorKind, options?: {
|
|
20
|
+
cleanupFailed?: boolean;
|
|
21
|
+
operationError?: unknown;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export declare function preflightDeploymentSource(repository: GitRepository, options: DeploymentWorktreeOptions): Promise<void>;
|
|
25
|
+
export type DeploymentPublicationResult = {
|
|
26
|
+
attempts: 1 | 2;
|
|
27
|
+
cleanupFailed: boolean;
|
|
28
|
+
};
|
|
29
|
+
export declare function publishDeploymentSource(repository: GitRepository, options: DeploymentWorktreeOptions, prepare: (workspace: {
|
|
30
|
+
path: string;
|
|
31
|
+
repository: GitRepository;
|
|
32
|
+
}) => Promise<void>): Promise<DeploymentPublicationResult>;
|
|
33
|
+
export declare function deploymentWorktreeRelativePath(repositoryRoot: string, workspacePath: string): string;
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=deployment-worktree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deployment-worktree.d.ts","sourceRoot":"","sources":["../../src/lifecycle/deployment-worktree.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAK9B,MAAM,MAAM,yBAAyB,GAAG;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,iBAAiB,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,2BAA2B,GACnC,gBAAgB,GAChB,oBAAoB,GACpB,mBAAmB,GACnB,sBAAsB,GACtB,sBAAsB,GACtB,qBAAqB,GACrB,iBAAiB,CAAC;AAEtB,KAAK,eAAe,GAAG,WAAW,GAAG,aAAa,CAAC;AACnD,KAAK,cAAc,GACf,OAAO,GACP,WAAW,GACX,QAAQ,GACR,cAAc,GACd,eAAe,GACf,OAAO,GACP,eAAe,GACf,gBAAgB,GAChB,iBAAiB,GACjB,MAAM,GACN,SAAS,CAAC;AAEd,yFAAyF;AACzF,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,OAAO,QAiBf;AAED,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,IAAI,EAAE,2BAA2B,CAAC;IAC3C,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAExB,YACE,IAAI,EAAE,2BAA2B,EACjC,OAAO,GAAE;QAAE,aAAa,CAAC,EAAE,OAAO,CAAC;QAAC,cAAc,CAAC,EAAE,OAAO,CAAA;KAAO,EAQpE;CACF;AA+ND,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,aAAa,EACzB,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,IAAI,CAAC,CAcf;AAED,MAAM,MAAM,2BAA2B,GAAG;IACxC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC;IAChB,aAAa,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,aAAa,EACzB,OAAO,EAAE,yBAAyB,EAClC,OAAO,EAAE,CAAC,SAAS,EAAE;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,aAAa,CAAC;CAC3B,KAAK,OAAO,CAAC,IAAI,CAAC,GAClB,OAAO,CAAC,2BAA2B,CAAC,CAgEtC;AAED,wBAAgB,8BAA8B,CAC5C,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,MAAM,UAGtB","sourcesContent":["import { lstat, mkdtemp, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join, relative, sep } from \"node:path\";\nimport {\n createGitRepository,\n GitRepositoryError,\n type GitCommitIdentity,\n type GitRepository,\n} from \"../git-repository.js\";\n\nconst OWNERSHIP_MARKER = \"bonfire deployment workspace\\n\";\nconst SOURCE_SHA_PATTERN = /^[0-9a-f]{40,64}$/u;\n\nexport type DeploymentWorktreeOptions = {\n remote: string;\n primaryBranch: string;\n sourceSha: string;\n timeoutMs?: number;\n commitIdentity?: GitCommitIdentity;\n};\n\nexport type DeploymentWorktreeErrorKind =\n | \"invalid-source\"\n | \"preflight-conflict\"\n | \"preflight-failure\"\n | \"publication-conflict\"\n | \"publication-rejected\"\n | \"publication-failure\"\n | \"cleanup-failure\";\n\ntype DeploymentPhase = \"preflight\" | \"publication\";\ntype DeploymentStep =\n | \"fetch\"\n | \"directory\"\n | \"marker\"\n | \"worktree-add\"\n | \"source-verify\"\n | \"merge\"\n | \"metadata-read\"\n | \"metadata-write\"\n | \"metadata-commit\"\n | \"push\"\n | \"cleanup\";\n\n/** Log only finite classifications; Git/OS messages may contain paths or credentials. */\nexport function reportDeploymentIssue(\n phase: DeploymentPhase,\n step: DeploymentStep,\n error: unknown,\n) {\n const kind =\n error instanceof GitRepositoryError\n ? error.kind\n : error instanceof DeploymentWorktreeError\n ? error.kind\n : step.startsWith(\"metadata-\") ||\n step === \"directory\" ||\n step === \"marker\"\n ? \"filesystem-failure\"\n : \"unexpected\";\n console.warn(\"[bonfire] Deployment workspace attention\", {\n phase,\n step,\n kind,\n });\n}\n\nexport class DeploymentWorktreeError extends Error {\n readonly kind: DeploymentWorktreeErrorKind;\n readonly cleanupFailed: boolean;\n readonly operationError: unknown;\n readonly cause: unknown;\n\n constructor(\n kind: DeploymentWorktreeErrorKind,\n options: { cleanupFailed?: boolean; operationError?: unknown } = {},\n ) {\n super(`Deployment workspace operation failed: ${kind}`);\n this.name = \"DeploymentWorktreeError\";\n this.kind = kind;\n this.cleanupFailed = options.cleanupFailed ?? false;\n this.operationError = options.operationError;\n this.cause = options.operationError;\n }\n}\n\nfunction withCleanupFailure(error: unknown): DeploymentWorktreeError {\n if (error instanceof DeploymentWorktreeError) {\n return new DeploymentWorktreeError(error.kind, {\n cleanupFailed: true,\n operationError: error.operationError ?? error,\n });\n }\n return new DeploymentWorktreeError(\"cleanup-failure\", {\n cleanupFailed: true,\n operationError: error,\n });\n}\n\ntype DeploymentWorktree = {\n path: string;\n repository: GitRepository;\n cleanup(): Promise<void>;\n};\n\ntype DeploymentWorkspaceOperation<Value> = (\n workspace: DeploymentWorktree,\n) => Promise<Value>;\n\nfunction assertBranchName(value: string, option: string) {\n if (\n value.length === 0 ||\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 TypeError(`Invalid ${option}`);\n }\n}\n\nfunction assertSourceSha(sourceSha: string) {\n if (!SOURCE_SHA_PATTERN.test(sourceSha)) {\n throw new DeploymentWorktreeError(\"invalid-source\");\n }\n}\n\nfunction remotePrimaryRef(options: DeploymentWorktreeOptions) {\n return `refs/remotes/${options.remote}/${options.primaryBranch}`;\n}\n\nfunction mapMergeError(\n error: unknown,\n phase: \"preflight\" | \"publication\",\n): unknown {\n if (!(error instanceof GitRepositoryError)) return error;\n if (error.kind === \"conflict\") {\n return new DeploymentWorktreeError(`${phase}-conflict`);\n }\n if (phase === \"publication\") {\n if (error.kind === \"non-fast-forward\") {\n return new DeploymentWorktreeError(\"publication-rejected\");\n }\n return new DeploymentWorktreeError(\"publication-failure\");\n }\n return error;\n}\n\nasync function createDeploymentWorktree(\n repository: GitRepository,\n options: DeploymentWorktreeOptions,\n phase: DeploymentPhase,\n): Promise<DeploymentWorktree> {\n assertBranchName(options.remote, \"remote\");\n assertBranchName(options.primaryBranch, \"primary branch\");\n assertSourceSha(options.sourceSha);\n\n try {\n await repository.fetch(options.remote, options.primaryBranch);\n } catch (error) {\n reportDeploymentIssue(phase, \"fetch\", error);\n throw new DeploymentWorktreeError(`${phase}-failure`, {\n operationError: error,\n });\n }\n let operationDirectory: string;\n try {\n operationDirectory = await mkdtemp(join(tmpdir(), \"bonfire-deployment-\"));\n } catch (error) {\n reportDeploymentIssue(phase, \"directory\", error);\n throw new DeploymentWorktreeError(`${phase}-failure`, {\n operationError: error,\n });\n }\n const workspacePath = join(operationDirectory, \"workspace\");\n const markerPath = join(operationDirectory, \"OWNER\");\n\n let worktreeAdded = false;\n let step: DeploymentStep = \"marker\";\n try {\n await writeFile(markerPath, OWNERSHIP_MARKER, { flag: \"wx\" });\n step = \"worktree-add\";\n await repository.raw([\n \"worktree\",\n \"add\",\n \"--detach\",\n workspacePath,\n remotePrimaryRef(options),\n ]);\n worktreeAdded = true;\n\n const workspaceRepository = createGitRepository({\n repositoryRoot: workspacePath,\n timeoutMs: options.timeoutMs,\n commitIdentity: options.commitIdentity,\n });\n\n step = \"source-verify\";\n await workspaceRepository.raw([\n \"cat-file\",\n \"-e\",\n `${options.sourceSha}^{commit}`,\n ]);\n\n return {\n path: workspacePath,\n repository: workspaceRepository,\n async cleanup() {\n try {\n const marker = await readFile(markerPath, \"utf8\");\n const stats = await lstat(workspacePath);\n if (marker !== OWNERSHIP_MARKER || stats.isSymbolicLink()) {\n throw new DeploymentWorktreeError(\"cleanup-failure\");\n }\n await repository.raw([\n \"worktree\",\n \"remove\",\n \"--force\",\n workspacePath,\n ]);\n await rm(operationDirectory, { recursive: true, force: false });\n } catch (error) {\n reportDeploymentIssue(phase, \"cleanup\", error);\n if (error instanceof DeploymentWorktreeError) throw error;\n throw new DeploymentWorktreeError(\"cleanup-failure\", {\n operationError: error,\n });\n }\n },\n };\n } catch (error) {\n reportDeploymentIssue(phase, step, error);\n let cleanupFailed = false;\n if (worktreeAdded) {\n try {\n await repository.raw([\"worktree\", \"remove\", \"--force\", workspacePath]);\n } catch (cleanupError) {\n cleanupFailed = true;\n reportDeploymentIssue(phase, \"cleanup\", cleanupError);\n }\n }\n try {\n await rm(operationDirectory, { recursive: true, force: true });\n } catch (cleanupError) {\n cleanupFailed = true;\n reportDeploymentIssue(phase, \"cleanup\", cleanupError);\n }\n throw new DeploymentWorktreeError(`${phase}-failure`, {\n cleanupFailed,\n operationError: error,\n });\n }\n}\n\nasync function mergeSource(\n workspace: DeploymentWorktree,\n sourceSha: string,\n phase: \"preflight\" | \"publication\",\n) {\n try {\n await workspace.repository.merge([\n sourceSha,\n \"--no-ff\",\n \"-m\",\n `merge: deploy ${sourceSha.slice(0, 12)}`,\n ]);\n } catch (error) {\n reportDeploymentIssue(phase, \"merge\", error);\n const mapped = mapMergeError(error, phase);\n throw mapped instanceof DeploymentWorktreeError\n ? mapped\n : new DeploymentWorktreeError(`${phase}-failure`, {\n operationError: error,\n });\n }\n}\n\nasync function withDeploymentWorktree<Value>(\n repository: GitRepository,\n options: DeploymentWorktreeOptions,\n operation: DeploymentWorkspaceOperation<Value>,\n phase: DeploymentPhase,\n): Promise<{ value: Value; cleanupFailed: boolean }> {\n const workspace = await createDeploymentWorktree(repository, options, phase);\n let value: Value;\n try {\n value = await operation(workspace);\n } catch (error) {\n // Preserve the operation's failure while making cleanup attention\n // observable to callers.\n try {\n await workspace.cleanup();\n } catch {\n throw withCleanupFailure(error);\n }\n throw error;\n }\n\n try {\n await workspace.cleanup();\n return { value, cleanupFailed: false };\n } catch {\n return { value, cleanupFailed: true };\n }\n}\n\nexport async function preflightDeploymentSource(\n repository: GitRepository,\n options: DeploymentWorktreeOptions,\n): Promise<void> {\n const result = await withDeploymentWorktree(\n repository,\n options,\n async (workspace) => {\n await mergeSource(workspace, options.sourceSha, \"preflight\");\n },\n \"preflight\",\n );\n if (result.cleanupFailed) {\n throw new DeploymentWorktreeError(\"cleanup-failure\", {\n cleanupFailed: true,\n });\n }\n}\n\nexport type DeploymentPublicationResult = {\n attempts: 1 | 2;\n cleanupFailed: boolean;\n};\n\nexport async function publishDeploymentSource(\n repository: GitRepository,\n options: DeploymentWorktreeOptions,\n prepare: (workspace: {\n path: string;\n repository: GitRepository;\n }) => Promise<void>,\n): Promise<DeploymentPublicationResult> {\n let cleanupFailed = false;\n for (const attempt of [1, 2] as const) {\n try {\n const result = await withDeploymentWorktree(\n repository,\n options,\n async (workspace) => {\n await mergeSource(workspace, options.sourceSha, \"publication\");\n await prepare({\n path: workspace.path,\n repository: workspace.repository,\n });\n try {\n await workspace.repository.raw([\n \"push\",\n options.remote,\n `HEAD:refs/heads/${options.primaryBranch}`,\n ]);\n } catch (error) {\n reportDeploymentIssue(\"publication\", \"push\", error);\n const mapped = mapMergeError(error, \"publication\");\n throw mapped instanceof DeploymentWorktreeError\n ? mapped\n : new DeploymentWorktreeError(\"publication-failure\", {\n operationError: error,\n });\n }\n },\n \"publication\",\n );\n cleanupFailed ||= result.cleanupFailed;\n return { attempts: attempt, cleanupFailed };\n } catch (error) {\n cleanupFailed ||=\n error instanceof DeploymentWorktreeError && error.cleanupFailed;\n if (\n !(error instanceof DeploymentWorktreeError) ||\n error.kind !== \"publication-rejected\" ||\n attempt === 2\n ) {\n if (cleanupFailed) {\n if (error instanceof DeploymentWorktreeError) {\n if (!error.cleanupFailed) {\n throw new DeploymentWorktreeError(error.kind, {\n cleanupFailed: true,\n operationError: error.operationError ?? error,\n });\n }\n } else {\n throw new DeploymentWorktreeError(\"publication-failure\", {\n cleanupFailed: true,\n operationError: error,\n });\n }\n }\n throw error;\n }\n }\n }\n\n throw new DeploymentWorktreeError(\"publication-rejected\", {\n cleanupFailed,\n });\n}\n\nexport function deploymentWorktreeRelativePath(\n repositoryRoot: string,\n workspacePath: string,\n) {\n return relative(repositoryRoot, workspacePath).split(sep).join(\"/\");\n}\n"]}
|