@gitpod/sdk
Version:
Public TypeScript SDK for Ona.
742 lines (741 loc) • 35.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Environment = exports.EnvironmentClient = exports.WriteFileMode = void 0;
const protobuf_1 = require("@bufbuild/protobuf");
const wkt_1 = require("@bufbuild/protobuf/wkt");
const environment_pb_1 = require("../gitpod/v1/environment_pb");
const event_pb_1 = require("../gitpod/v1/event_pb");
const identity_pb_1 = require("../gitpod/v1/identity_pb");
const project_pb_1 = require("../gitpod/v1/project_pb");
const pagination_pb_1 = require("../gitpod/v1/pagination_pb");
const runner_configuration_pb_1 = require("../gitpod/v1/runner_configuration_pb");
const runner_pb_1 = require("../gitpod/v1/runner_pb");
const usage_pb_1 = require("../gitpod/v1/usage_pb");
const environmentopts_pb_1 = require("../supervisor/v1/environmentopts_pb");
Object.defineProperty(exports, "WriteFileMode", { enumerable: true, get: function () { return environmentopts_pb_1.WriteMode; } });
const errors_1 = require("./errors");
const logging_1 = require("./logging");
const agents_1 = require("./agents");
const defaultPageSize = 100;
const defaultProjectUsageLookbackMs = 90 * 24 * 60 * 60 * 1000;
class EnvironmentClient {
constructor(client) {
this.client = client;
}
async create(options) {
const operation = "environments.create";
if (!options.contextUrl) {
throw new errors_1.ValidationError(operation, "context URL is required");
}
(0, logging_1.debug)(this.client.logger, "creating environment", {
operation,
context_url: safeURLForLog(options.contextUrl),
name: options.name,
});
const resolved = await this.resolveContext(options.contextUrl, options.signal);
let environmentClassId = firstRecommendedEnvironmentClass(resolved);
let runnerId = "";
let selectionSource = "recommended";
if (!environmentClassId) {
const selected = await this.defaultEnvironmentClass(options.signal);
environmentClassId = selected.environmentClassId;
runnerId = selected.runnerId;
selectionSource = "default";
}
if (!runnerId) {
runnerId = await this.environmentClassRunnerID(environmentClassId, options.signal);
}
await this.requireSCMAuthentication(runnerId, options.contextUrl, options.signal);
const projectId = await this.preferredProjectID(resolved.projectIds, options.signal);
if (projectId) {
(0, logging_1.debug)(this.client.logger, "creating environment from project", {
operation,
project_id: projectId,
project_candidates: resolved.projectIds.length,
});
const created = await this.createFromProject(projectId, resolved, options.name, options.signal);
return await this.environment(created, true, options.signal);
}
(0, logging_1.debug)(this.client.logger, "selected environment class", {
operation,
environment_class_id: environmentClassId,
selection_source: selectionSource,
recommended_count: resolved.recommendedEnvironmentClasses.length,
});
const created = await this.createFromContextURL(options.contextUrl, environmentClassId, options.name, options.signal);
return await this.environment(created, true, options.signal);
}
async createScratch(options = {}) {
const operation = "environments.create_scratch";
const selected = await this.defaultEnvironmentClass(options.signal);
const response = await this.client.services.environment.createEnvironment((0, protobuf_1.create)(environment_pb_1.CreateEnvironmentRequestSchema, {
spec: (0, protobuf_1.create)(environment_pb_1.EnvironmentSpecSchema, {
desiredPhase: environment_pb_1.EnvironmentPhase.RUNNING,
machine: (0, protobuf_1.create)(environment_pb_1.EnvironmentSpec_MachineSchema, { class: selected.environmentClassId }),
}),
name: options.name,
}), { signal: options.signal }).catch((error) => {
throw (0, errors_1.mapError)(operation, error);
});
if (!response.environment) {
throw new errors_1.CapabilityUnavailableError(operation, "create response did not include an environment");
}
return await this.environment(handleFor(response.environment), true, options.signal);
}
async get(environmentId, options = {}) {
const handle = await this.getHandle(environmentId, options.signal);
return new Environment(this.client, this, handle);
}
async *list(options = {}) {
const operation = "environments.list";
const identity = await this.client.services.identity.getAuthenticatedIdentity((0, protobuf_1.create)(identity_pb_1.GetAuthenticatedIdentityRequestSchema), { signal: options.signal }).catch((error) => {
throw (0, errors_1.mapError)(operation, error);
});
const creatorId = identity.subject?.id;
if (!creatorId) {
throw new errors_1.CapabilityUnavailableError(operation, "authenticated identity did not include a subject ID");
}
let token = "";
let count = 0;
for (;;) {
(0, logging_1.debug)(this.client.logger, "listing environments", {
operation,
creator_id: creatorId,
page_size: options.pageSize ?? defaultPageSize,
has_page_token: token !== "",
});
const response = await this.client.services.environment.listEnvironments((0, protobuf_1.create)(environment_pb_1.ListEnvironmentsRequestSchema, {
pagination: (0, protobuf_1.create)(pagination_pb_1.PaginationRequestSchema, {
pageSize: options.pageSize ?? defaultPageSize,
token,
}),
filter: (0, protobuf_1.create)(environment_pb_1.ListEnvironmentsRequest_FilterSchema, {
creatorIds: [creatorId],
roles: [environment_pb_1.EnvironmentRole.DEFAULT],
archivalStatus: environment_pb_1.ListEnvironmentsRequest_ArchivalStatus.ALL,
}),
}), { signal: options.signal }).catch((error) => {
throw (0, errors_1.mapError)(operation, error);
});
for (const environment of response.environments) {
count++;
yield new Environment(this.client, this, handleFor(environment));
}
const nextToken = response.pagination?.nextToken ?? "";
(0, logging_1.debug)(this.client.logger, "listed environment page", {
operation,
creator_id: creatorId,
page_count: response.environments.length,
total_count: count,
has_next_page: nextToken !== "",
});
if (!nextToken) {
break;
}
token = nextToken;
}
}
async stop(environmentId, options = {}) {
const operation = "environments.stop";
if (!environmentId) {
throw new errors_1.ValidationError(operation, "environment ID is required");
}
(0, logging_1.debug)(this.client.logger, "stopping environment", { operation, environment_id: environmentId });
await this.client.services.environment.stopEnvironment((0, protobuf_1.create)(environment_pb_1.StopEnvironmentRequestSchema, { environmentId }), { signal: options.signal }).catch((error) => {
throw (0, errors_1.mapError)(operation, error);
});
await this.waitForEnvironment(environmentId, operation, options.signal, (environment) => {
const phase = environment.status?.phase;
return phase === environment_pb_1.EnvironmentPhase.STOPPED || phase === environment_pb_1.EnvironmentPhase.DELETED;
});
}
async delete(environmentId, options = {}) {
const operation = "environments.delete";
if (!environmentId) {
throw new errors_1.ValidationError(operation, "environment ID is required");
}
(0, logging_1.debug)(this.client.logger, "deleting environment", {
operation,
environment_id: environmentId,
force: options.force === true,
});
await this.client.services.environment.deleteEnvironment((0, protobuf_1.create)(environment_pb_1.DeleteEnvironmentRequestSchema, {
environmentId,
force: options.force === true,
}), { signal: options.signal }).catch((error) => {
throw (0, errors_1.mapError)(operation, error);
});
(0, logging_1.debug)(this.client.logger, "environment deleted", {
operation,
environment_id: environmentId,
force: options.force === true,
});
}
async start(environmentId, options = {}) {
const operation = "environments.start";
if (!environmentId) {
throw new errors_1.ValidationError(operation, "environment ID is required");
}
(0, logging_1.debug)(this.client.logger, "starting environment", { operation, environment_id: environmentId });
await this.client.services.environment.startEnvironment((0, protobuf_1.create)(environment_pb_1.StartEnvironmentRequestSchema, { environmentId }), { signal: options.signal }).catch((error) => {
throw (0, errors_1.mapError)(operation, error);
});
const environment = await this.waitForEnvironment(environmentId, "environments.wait_running", options.signal, (current) => {
return current.status?.phase === environment_pb_1.EnvironmentPhase.RUNNING;
});
return new Environment(this.client, this, handleFor(environment));
}
async getHandle(environmentId, signal) {
const operation = "environments.get";
if (!environmentId) {
throw new errors_1.ValidationError(operation, "environment ID is required");
}
const response = await this.client.services.environment.getEnvironment((0, protobuf_1.create)(environment_pb_1.GetEnvironmentRequestSchema, { environmentId }), { signal }).catch((error) => {
throw (0, errors_1.mapError)(operation, error);
});
if (!response.environment) {
throw new errors_1.NotFoundError(operation, `environment ${environmentId} was not returned`);
}
return handleFor(response.environment);
}
async environment(handle, waitForRunning, signal) {
if (waitForRunning) {
const latest = await this.waitForEnvironment(handle.id, "environments.wait_running", signal, (environment) => {
return environment.status?.phase === environment_pb_1.EnvironmentPhase.RUNNING;
});
handle.environment = latest;
}
return new Environment(this.client, this, handle);
}
async waitForEnvironment(environmentId, operation, signal, done) {
let current = (await this.getHandle(environmentId, signal)).environment;
logEnvironmentPhase(this.client, operation, current);
const failure = environmentFailureError(operation, current);
if (failure) {
throw failure;
}
if (done(current)) {
return current;
}
if (operation === "environments.wait_running" && isTerminalBeforeRunning(current.status?.phase)) {
throw new errors_1.UnavailableError(operation, `environment ${environmentId} reached ${environmentPhaseLabel(current.status?.phase)} before running`);
}
const events = this.client.services.event.watchEvents((0, protobuf_1.create)(event_pb_1.WatchEventsRequestSchema, {
scope: { case: "organization", value: true },
resourceTypeFilters: [
(0, protobuf_1.create)(event_pb_1.WatchEventsRequest_ResourceTypeFilterSchema, {
resourceType: event_pb_1.ResourceType.ENVIRONMENT,
resourceIds: [environmentId],
}),
],
}), { signal });
for await (const event of events) {
if (event.resourceId !== environmentId || event.resourceType !== event_pb_1.ResourceType.ENVIRONMENT) {
continue;
}
current = (await this.getHandle(environmentId, signal)).environment;
logEnvironmentPhase(this.client, operation, current);
const eventFailure = environmentFailureError(operation, current);
if (eventFailure) {
throw eventFailure;
}
if (done(current)) {
return current;
}
if (operation === "environments.wait_running" && isTerminalBeforeRunning(current.status?.phase)) {
throw new errors_1.UnavailableError(operation, `environment ${environmentId} reached ${environmentPhaseLabel(current.status?.phase)} before running`);
}
}
throw new errors_1.UnavailableError(operation, `environment ${environmentId} event stream ended before the target phase`);
}
async resolveContext(contextUrl, signal) {
const operation = "scm.resolve_context";
(0, logging_1.debug)(this.client.logger, "resolving context URL", {
operation,
context_url: safeURLForLog(contextUrl),
});
const response = await this.client.services.runner.parseContextURL((0, protobuf_1.create)(runner_pb_1.ParseContextURLRequestSchema, { contextUrl }), { signal }).catch((error) => {
throw (0, errors_1.mapError)(operation, error);
});
(0, logging_1.debug)(this.client.logger, "resolved context URL", {
operation,
context_url: safeURLForLog(response.originalContextUrl),
scm_id: response.scmId,
git_host: response.git?.host,
git_owner: response.git?.owner,
git_repo: response.git?.repo,
project_count: response.projectIds.length,
recommended_environment_class_count: response.recommendedEnvironmentClasses.length,
});
return response;
}
async requireSCMAuthentication(runnerId, contextUrl, signal) {
const operation = "scm.check_authentication_for_host";
const host = hostFromURL(contextUrl);
const response = await this.client.services.runner.checkAuthenticationForHost((0, protobuf_1.create)(runner_pb_1.CheckAuthenticationForHostRequestSchema, { runnerId, host }), { signal }).catch((error) => {
throw (0, errors_1.mapError)(operation, error);
});
if (!response.authenticated) {
(0, logging_1.debug)(this.client.logger, "SCM authentication required", {
operation,
runner_id: runnerId,
host,
scm_id: response.scmId,
scm_name: response.scmName,
supports_oauth2: response.supportsOauth2 !== undefined,
supports_pat: response.supportsPat !== undefined || response.patSupported,
});
throw new errors_1.AuthenticationRequiredError(operation, `authentication required for ${host}`);
}
(0, logging_1.debug)(this.client.logger, "SCM authentication available", {
operation,
runner_id: runnerId,
host,
scm_id: response.scmId,
scm_name: response.scmName,
});
}
async defaultEnvironmentClass(signal) {
const operation = "environments.default_class";
const response = await this.client.services.environment.listEnvironmentClasses((0, protobuf_1.create)(runner_configuration_pb_1.ListEnvironmentClassesRequestSchema, {
pagination: (0, protobuf_1.create)(pagination_pb_1.PaginationRequestSchema, { pageSize: defaultPageSize }),
filter: (0, protobuf_1.create)(runner_configuration_pb_1.ListEnvironmentClassesRequest_FilterSchema, {
enabled: true,
canCreateEnvironments: true,
}),
}), { signal }).catch((error) => {
throw (0, errors_1.mapError)(operation, error);
});
const selected = response.environmentClasses.find((environmentClass) => environmentClass.id);
if (!selected) {
throw new errors_1.CapabilityUnavailableError(operation, "no environment class available for creating environments");
}
(0, logging_1.debug)(this.client.logger, "selected default environment class", {
operation,
environment_class_id: selected.id,
runner_id: selected.runnerId,
});
return { environmentClassId: selected.id, runnerId: selected.runnerId };
}
async environmentClassRunnerID(environmentClassId, signal) {
const operation = "environments.environment_class_runner";
const response = await this.client.services.runnerConfiguration.getEnvironmentClass((0, protobuf_1.create)(runner_configuration_pb_1.GetEnvironmentClassRequestSchema, { environmentClassId }), { signal }).catch((error) => {
throw (0, errors_1.mapError)(operation, error);
});
const runnerId = response.environmentClass?.runnerId ?? "";
if (!runnerId) {
throw new errors_1.CapabilityUnavailableError(operation, `environment class ${environmentClassId} did not include a runner ID`);
}
return runnerId;
}
async preferredProjectID(projectIds, signal) {
const operation = "environments.preferred_project";
const candidates = new Set(projectIds.filter((projectId) => projectId !== ""));
const fallback = candidates.values().next().value ?? "";
if (candidates.size <= 1) {
return fallback;
}
const now = new Date();
const response = await this.client.services.usage.getTopProjects((0, protobuf_1.create)(usage_pb_1.GetTopProjectsRequestSchema, {
pagination: (0, protobuf_1.create)(pagination_pb_1.PaginationRequestSchema, { pageSize: defaultPageSize }),
dateRange: (0, protobuf_1.create)(usage_pb_1.DateRangeSchema, {
startTime: (0, wkt_1.timestampFromDate)(new Date(now.getTime() - defaultProjectUsageLookbackMs)),
endTime: (0, wkt_1.timestampFromDate)(now),
}),
}), { signal }).catch(() => undefined);
for (const project of response?.projects ?? []) {
if (candidates.has(project.projectId)) {
(0, logging_1.debug)(this.client.logger, "selected project for context URL", {
operation,
project_id: project.projectId,
selection_source: "usage",
candidate_count: candidates.size,
});
return project.projectId;
}
}
(0, logging_1.debug)(this.client.logger, "selected project for context URL", {
operation,
project_id: fallback,
selection_source: "first_match",
candidate_count: candidates.size,
});
return fallback;
}
async createFromContextURL(contextUrl, environmentClass, name, signal) {
const operation = "environments.create_from_context_url";
const response = await this.client.services.environment.createEnvironment((0, protobuf_1.create)(environment_pb_1.CreateEnvironmentRequestSchema, {
spec: (0, protobuf_1.create)(environment_pb_1.EnvironmentSpecSchema, {
desiredPhase: environment_pb_1.EnvironmentPhase.RUNNING,
machine: environmentClass ? (0, protobuf_1.create)(environment_pb_1.EnvironmentSpec_MachineSchema, { class: environmentClass }) : undefined,
content: (0, protobuf_1.create)(environment_pb_1.EnvironmentSpec_ContentSchema, {
initializer: (0, protobuf_1.create)(environment_pb_1.EnvironmentInitializerSchema, {
specs: [
(0, protobuf_1.create)(environment_pb_1.EnvironmentInitializer_SpecSchema, {
spec: {
case: "contextUrl",
value: (0, protobuf_1.create)(environment_pb_1.ContextURLInitializerSchema, { url: contextUrl }),
},
}),
],
}),
}),
}),
name,
}), { signal }).catch((error) => {
throw (0, errors_1.mapError)(operation, error);
});
if (!response.environment) {
throw new errors_1.CapabilityUnavailableError(operation, "create response did not include an environment");
}
return handleFor(response.environment);
}
async createFromProject(projectId, resolved, name, signal) {
const operation = "environments.create_from_project";
const gitRemoteURI = await this.projectRemoteURIForParsedContext(projectId, resolved.git, signal);
const response = await this.client.services.environment.createEnvironmentFromProject((0, protobuf_1.create)(environment_pb_1.CreateEnvironmentFromProjectRequestSchema, {
projectId,
spec: environmentSpecFromProject(resolved, gitRemoteURI),
name,
}), { signal }).catch((error) => {
throw (0, errors_1.mapError)(operation, error);
});
if (!response.environment) {
throw new errors_1.CapabilityUnavailableError(operation, "create-from-project response did not include an environment");
}
return handleFor(response.environment);
}
async projectRemoteURIForParsedContext(projectId, gitContext, signal) {
const fallback = gitContext?.cloneUrl ?? "";
if (!projectId || !fallback) {
return fallback;
}
const response = await this.client.services.project.getProject((0, protobuf_1.create)(project_pb_1.GetProjectRequestSchema, { projectId }), { signal }).catch(() => undefined);
const target = normalizedRepositoryIdentifier(fallback);
for (const spec of response?.project?.initializer?.specs ?? []) {
const remoteURI = spec.spec.case === "git" ? spec.spec.value.remoteUri : "";
if (remoteURI && normalizedRepositoryIdentifier(remoteURI) === target) {
return remoteURI;
}
}
return fallback;
}
}
exports.EnvironmentClient = EnvironmentClient;
class Environment {
constructor(client, environmentClient, handle) {
this.client = client;
this.environmentClient = environmentClient;
this.handle = handle;
}
id() {
return this.handle.id;
}
proto() {
return this.handle.environment;
}
workspaceDir() {
return this.handle.environment.status?.devcontainer?.remoteWorkspaceFolder
|| this.handle.environment.status?.content?.contentLocationInMachine
|| "";
}
async refresh(options = {}) {
const next = await this.environmentClient.getHandle(this.id(), options.signal);
this.handle.environment = next.environment;
return this.handle.environment;
}
async runCommand(options) {
const operation = "environments.run_command";
if (!options.command) {
throw new errors_1.ValidationError(operation, "command is required");
}
const ops = await this.opsClient(options.signal);
const operationId = randomID();
(0, logging_1.debug)(this.client.logger, "running command in environment", {
operation,
environment_id: this.id(),
operation_id: operationId,
working_directory: options.workingDirectory,
timeout_seconds: options.timeoutSeconds,
command_bytes: options.command.length,
});
const response = await ops.exec((0, protobuf_1.create)(environmentopts_pb_1.ExecRequestSchema, {
operationId,
command: options.command,
workingDirectory: options.workingDirectory ?? "",
timeoutSeconds: options.timeoutSeconds ?? 0,
}), { signal: options.signal }).catch((error) => {
throw this.mapOpsError("ops.run_command", error);
});
(0, logging_1.debug)(this.client.logger, "environment command finished", {
operation,
environment_id: this.id(),
operation_id: operationId,
exit_code: response.exitCode,
stdout_bytes: response.stdout.length,
stderr_bytes: response.stderr.length,
});
return {
environmentId: this.id(),
exitCode: response.exitCode,
stdout: response.stdout,
stderr: response.stderr,
};
}
async readFile(path, options = {}) {
const operation = "environments.read_file";
if (!path) {
throw new errors_1.ValidationError(operation, "path is required");
}
const ops = await this.opsClient(options.signal);
const response = await ops.readFile((0, protobuf_1.create)(environmentopts_pb_1.ReadFileRequestSchema, {
path,
offset: toBigInt(options.offset),
length: toBigInt(options.length),
}), { signal: options.signal }).catch((error) => {
throw this.mapOpsError("ops.read_file", error);
});
return response;
}
async writeFile(path, content, options = {}) {
const operation = "environments.write_file";
if (!path) {
throw new errors_1.ValidationError(operation, "path is required");
}
const data = typeof content === "string" ? new TextEncoder().encode(content) : content;
const ops = await this.opsClient(options.signal);
const response = await ops.writeFile((0, protobuf_1.create)(environmentopts_pb_1.WriteFileRequestSchema, {
path,
content: data,
mode: options.mode ?? environmentopts_pb_1.WriteMode.UNSPECIFIED,
}), { signal: options.signal }).catch((error) => {
throw this.mapOpsError("ops.write_file", error);
});
return response;
}
async gitChanges(options = {}) {
const status = await this.gitStatus(options.signal);
const files = [];
const ops = await this.opsClient(options.signal);
for (const file of status.changedFiles) {
const diff = await ops.getGitDiff((0, protobuf_1.create)(environmentopts_pb_1.GetGitDiffRequestSchema, {
path: file.path,
unified: options.unified ?? 0,
baseRef: options.baseRef ?? "",
}), { signal: options.signal }).catch((error) => {
throw this.mapOpsError("ops.git_diff", error);
});
files.push({ file, diff });
}
return { status, files };
}
startCodex(options) {
return (0, agents_1.startCodexInEnvironment)(this.client, this.id(), options);
}
async gitStatus(signal) {
const ops = await this.opsClient(signal);
const response = await ops.getGitStatus((0, protobuf_1.create)(environmentopts_pb_1.GetGitStatusRequestSchema), { signal }).catch((error) => {
throw this.mapOpsError("ops.git_status", error);
});
if (!response.status) {
throw new errors_1.CapabilityUnavailableError("ops.git_status", "git status response did not include status");
}
return response.status;
}
async opsClient(signal) {
if (this.ops) {
return this.ops;
}
const operation = "ops.for_environment";
const environment = (await this.environmentClient.getHandle(this.id(), signal)).environment;
const opsUrl = environment.status?.environmentUrls?.ops ?? "";
if (!opsUrl) {
throw new errors_1.CapabilityUnavailableError(operation, "environment ops URL is not available");
}
const token = await this.client.services.environment.createEnvironmentAccessToken((0, protobuf_1.create)(environment_pb_1.CreateEnvironmentAccessTokenRequestSchema, { environmentId: this.id() }), { signal }).catch((error) => {
throw (0, errors_1.mapError)(operation, error);
});
if (!token.accessToken) {
throw new errors_1.CapabilityUnavailableError(operation, "environment access token response did not include an access token");
}
this.ops = this.client.opsClient(opsUrl, token.accessToken);
return this.ops;
}
mapOpsError(operation, error) {
if (isEnvironmentOpsReachabilityError(error)) {
const opsUrl = this.handle.environment.status?.environmentUrls?.ops ?? "";
return new errors_1.EnvironmentUnreachableError(operation, `cannot reach environment ${this.id()} ops service at ${safeURLForLog(opsUrl)}: ${(0, errors_1.messageOf)(error)}`, error);
}
return (0, errors_1.mapError)(operation, error);
}
}
exports.Environment = Environment;
function handleFor(environment) {
return { id: environment.id, environment };
}
function environmentSpecFromProject(resolved, gitRemoteURI) {
const initializer = gitInitializerFromParsedContext(resolved.git, gitRemoteURI);
if (!initializer) {
return undefined;
}
return (0, protobuf_1.create)(environment_pb_1.EnvironmentSpecSchema, {
content: (0, protobuf_1.create)(environment_pb_1.EnvironmentSpec_ContentSchema, {
initializer: (0, protobuf_1.create)(environment_pb_1.EnvironmentInitializerSchema, {
specs: [
(0, protobuf_1.create)(environment_pb_1.EnvironmentInitializer_SpecSchema, {
spec: { case: "git", value: initializer },
}),
],
}),
}),
});
}
function gitInitializerFromParsedContext(gitContext, remoteURI) {
if (!gitContext) {
return undefined;
}
const resolvedRemoteURI = remoteURI || gitContext.cloneUrl;
if (!resolvedRemoteURI) {
return undefined;
}
let target = gitContext.commit;
let targetMode = environment_pb_1.GitInitializer_CloneTargetMode.REMOTE_COMMIT;
if (!target) {
target = gitContext.tag;
targetMode = environment_pb_1.GitInitializer_CloneTargetMode.REMOTE_TAG;
}
if (!target) {
target = gitContext.branch;
targetMode = environment_pb_1.GitInitializer_CloneTargetMode.REMOTE_BRANCH;
}
if (!target) {
return undefined;
}
return (0, protobuf_1.create)(environment_pb_1.GitInitializerSchema, {
remoteUri: resolvedRemoteURI,
targetMode,
cloneTarget: target,
checkoutLocation: gitContext.repo,
upstreamRemoteUri: gitContext.upstreamRemoteUrl,
});
}
function firstRecommendedEnvironmentClass(resolved) {
return resolved.recommendedEnvironmentClasses.find((environmentClass) => environmentClass !== "") ?? "";
}
function hostFromURL(rawURL) {
try {
const parsed = new URL(rawURL);
if (!parsed.host) {
throw new Error("context URL must include a host");
}
return parsed.host;
}
catch (error) {
throw new errors_1.ValidationError("environments.require_scm_authentication", `parse context URL: ${(0, errors_1.messageOf)(error)}`, error);
}
}
function normalizedRepositoryIdentifier(raw) {
const trimmed = raw.trim().replace(/\/+$/, "").replace(/\.git$/, "");
if (trimmed.startsWith("git@")) {
const withoutPrefix = trimmed.slice("git@".length);
const separator = withoutPrefix.indexOf(":");
if (separator >= 0) {
return `${withoutPrefix.slice(0, separator).toLowerCase()}/${withoutPrefix.slice(separator + 1).replace(/\/+$/, "").replace(/\.git$/, "")}`;
}
}
try {
const parsed = new URL(trimmed);
return `${parsed.host.toLowerCase()}/${parsed.pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/, "")}`;
}
catch {
return trimmed;
}
}
function environmentFailureError(operation, environment) {
const failures = environment.status?.failureMessage ?? [];
if (failures.length === 0) {
return undefined;
}
return new errors_1.EnvironmentPolicyError(operation, `environment ${environment.id} failed: ${failures.join("; ")}`);
}
function isTerminalBeforeRunning(phase) {
return phase === environment_pb_1.EnvironmentPhase.STOPPED || phase === environment_pb_1.EnvironmentPhase.DELETED;
}
function environmentPhaseLabel(phase) {
switch (phase) {
case environment_pb_1.EnvironmentPhase.CREATING:
return "creating";
case environment_pb_1.EnvironmentPhase.STARTING:
return "starting";
case environment_pb_1.EnvironmentPhase.RUNNING:
return "running";
case environment_pb_1.EnvironmentPhase.UPDATING:
return "updating";
case environment_pb_1.EnvironmentPhase.STOPPING:
return "stopping";
case environment_pb_1.EnvironmentPhase.STOPPED:
return "stopped";
case environment_pb_1.EnvironmentPhase.DELETING:
return "deleting";
case environment_pb_1.EnvironmentPhase.DELETED:
return "deleted";
default:
return "unspecified";
}
}
function logEnvironmentPhase(client, operation, environment) {
(0, logging_1.debug)(client.logger, "environment phase changed", {
operation,
environment_id: environment.id,
phase: environmentPhaseLabel(environment.status?.phase),
});
}
function toBigInt(value) {
if (value === undefined) {
return BigInt(0);
}
return typeof value === "bigint" ? value : BigInt(value);
}
function randomID() {
if (globalThis.crypto?.randomUUID) {
return globalThis.crypto.randomUUID();
}
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (char) => {
const value = Math.floor(Math.random() * 16);
return (char === "x" ? value : (value & 0x3) | 0x8).toString(16);
});
}
function isEnvironmentOpsReachabilityError(error) {
const message = (0, errors_1.messageOf)(error).toLowerCase();
return message.includes("deadline exceeded")
|| message.includes("unavailable")
|| message.includes("failed to fetch")
|| message.includes("fetch failed")
|| message.includes("networkerror")
|| message.includes("no such host")
|| message.includes("name not resolved")
|| message.includes("enotfound")
|| message.includes("econnrefused")
|| message.includes("connection refused")
|| message.includes("network is unreachable")
|| message.includes("timeout")
|| message.includes("timed out")
|| message.includes("i/o timeout");
}
function safeURLForLog(rawURL) {
const trimmed = rawURL.trim();
if (!trimmed) {
return "";
}
try {
const parsed = new URL(trimmed);
parsed.username = "";
parsed.password = "";
parsed.search = "";
parsed.hash = "";
return parsed.toString();
}
catch {
return trimmed;
}
}