ras-stack 0.35.0 → 0.36.1
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/README.md +10 -0
- package/dist/preview/cli.js +5 -0
- package/dist/preview/cli.js.map +1 -1
- package/dist/preview/dokploy-cli.d.ts +2 -0
- package/dist/preview/dokploy-cli.js +46 -0
- package/dist/preview/dokploy-cli.js.map +1 -0
- package/dist/preview/dokploy.d.ts +17 -0
- package/dist/preview/dokploy.js +61 -5
- package/dist/preview/dokploy.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -87,6 +87,16 @@ Start with the narrowest public entrypoint that owns the repeated mechanic:
|
|
|
87
87
|
| Compiler, lint, CI, release, and preview mechanics | `ras-stack/config/*`, `actions/*`, `.github/workflows/*`, `ras-stack/preview/*` | Triggers, permissions, services, deployment, and verification |
|
|
88
88
|
| Generated repository policy and adoption checks | `ras policy` | Which policies apply and every declared exception |
|
|
89
89
|
|
|
90
|
+
## Dokploy previews 🚀
|
|
91
|
+
|
|
92
|
+
Three reusable workflows provide the standard pull-request preview lifecycle:
|
|
93
|
+
|
|
94
|
+
- `build-dokploy-preview.yml` builds commit-specific images without exposing secrets to forks.
|
|
95
|
+
- `deploy-dokploy-preview.yml` publishes, resolves, deploys, reports, and removes previews.
|
|
96
|
+
- `prune-dokploy-previews.yml` cleans up applications and images left behind by interrupted runs.
|
|
97
|
+
|
|
98
|
+
Applications supply only their package, application prefix, domain, port, environment template, and optional product hook. The shared `DOKPLOY_URL`, `DOKPLOY_API_KEY`, and staging-only `DOKPLOY_ENVIRONMENT_ID` secrets can be configured once at organization level. See [Repository tooling](docs/repository-tooling.md) for the caller contract, private-registry options, and lifecycle hooks.
|
|
99
|
+
|
|
90
100
|
## Guides 📚
|
|
91
101
|
|
|
92
102
|
| Guide | What it covers |
|
package/dist/preview/cli.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { previewStatusFromEnvironment } from './environment.js';
|
|
2
|
+
import { runDokployPreviewCli } from './dokploy-cli.js';
|
|
2
3
|
import { reportPreviewStatus } from './github.js';
|
|
3
4
|
export async function runPreviewCli(arguments_) {
|
|
5
|
+
if (arguments_[0] === 'dokploy') {
|
|
6
|
+
await runDokployPreviewCli(arguments_.slice(1));
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
4
9
|
const { options, status } = previewStatusFromEnvironment(arguments_[0]);
|
|
5
10
|
await reportPreviewStatus(options, status);
|
|
6
11
|
console.log(`Preview status set to ${status.state}`);
|
package/dist/preview/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/preview/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,4BAA4B,EAAE,MAAM,kBAAkB,CAAA;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEjD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,UAAoB;IACtD,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,4BAA4B,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAA;IACvE,MAAM,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IAC1C,OAAO,CAAC,GAAG,CAAC,yBAAyB,MAAM,CAAC,KAAK,EAAE,CAAC,CAAA;AACtD,CAAC","sourcesContent":["import { previewStatusFromEnvironment } from './environment.js'\nimport { reportPreviewStatus } from './github.js'\n\nexport async function runPreviewCli(arguments_: string[]): Promise<void> {\n const { options, status } = previewStatusFromEnvironment(arguments_[0])\n await reportPreviewStatus(options, status)\n console.log(`Preview status set to ${status.state}`)\n}\n"]}
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/preview/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,4BAA4B,EAAE,MAAM,kBAAkB,CAAA;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEjD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,UAAoB;IACtD,IAAI,UAAU,CAAC,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;QAChC,MAAM,oBAAoB,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;QAC/C,OAAM;IACR,CAAC;IACD,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,4BAA4B,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAA;IACvE,MAAM,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IAC1C,OAAO,CAAC,GAAG,CAAC,yBAAyB,MAAM,CAAC,KAAK,EAAE,CAAC,CAAA;AACtD,CAAC","sourcesContent":["import { previewStatusFromEnvironment } from './environment.js'\nimport { runDokployPreviewCli } from './dokploy-cli.js'\nimport { reportPreviewStatus } from './github.js'\n\nexport async function runPreviewCli(arguments_: string[]): Promise<void> {\n if (arguments_[0] === 'dokploy') {\n await runDokployPreviewCli(arguments_.slice(1))\n return\n }\n const { options, status } = previewStatusFromEnvironment(arguments_[0])\n await reportPreviewStatus(options, status)\n console.log(`Preview status set to ${status.state}`)\n}\n"]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { appendFileSync } from 'node:fs';
|
|
3
|
+
import { dokployPreviewFromEnvironment, pullRequestNumber } from './dokploy.js';
|
|
4
|
+
export async function runDokployPreviewCli(arguments_, environment = process.env) {
|
|
5
|
+
const command = arguments_[0];
|
|
6
|
+
const { config, manager } = dokployPreviewFromEnvironment(environment);
|
|
7
|
+
if (command === 'deploy') {
|
|
8
|
+
const prNumber = pullRequestNumber(required(environment, 'PR_NUMBER'));
|
|
9
|
+
const preview = await manager.deploy({
|
|
10
|
+
prNumber,
|
|
11
|
+
image: required(environment, 'PREVIEW_IMAGE'),
|
|
12
|
+
environment: renderPreviewEnvironment(required(environment, 'PREVIEW_ENVIRONMENT'), prNumber),
|
|
13
|
+
...(config.registry ? { registry: config.registry } : {}),
|
|
14
|
+
});
|
|
15
|
+
if (environment.GITHUB_OUTPUT)
|
|
16
|
+
appendFileSync(environment.GITHUB_OUTPUT, `preview-url=${preview.url}\n`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (command === 'delete') {
|
|
20
|
+
const prNumber = pullRequestNumber(required(environment, 'PR_NUMBER'));
|
|
21
|
+
console.log((await manager.delete(prNumber)) ? `Deleted ${config.applicationPrefix}-pr-${prNumber}` : `No preview for pr-${prNumber}`);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (command === 'prune') {
|
|
25
|
+
const open = new Set((environment.OPEN_PR_NUMBERS ?? '').split(/\s+/).filter(Boolean).map(pullRequestNumber));
|
|
26
|
+
for (const prNumber of await manager.prune(open))
|
|
27
|
+
console.log(`Deleted ${config.applicationPrefix}-pr-${prNumber}`);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
throw new Error('usage: ras preview dokploy <deploy|delete|prune>');
|
|
31
|
+
}
|
|
32
|
+
export function renderPreviewEnvironment(template, prNumber) {
|
|
33
|
+
return template
|
|
34
|
+
.replaceAll('{{PR_NUMBER}}', pullRequestNumber(prNumber))
|
|
35
|
+
.replaceAll('{{RANDOM_HEX_32}}', () => randomBytes(32).toString('hex'));
|
|
36
|
+
}
|
|
37
|
+
function required(environment, name) {
|
|
38
|
+
const value = optional(environment, name);
|
|
39
|
+
if (!value)
|
|
40
|
+
throw new Error(`${name} is required`);
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
function optional(environment, name) {
|
|
44
|
+
return environment[name]?.trim() || undefined;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=dokploy-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dokploy-cli.js","sourceRoot":"","sources":["../../src/preview/dokploy-cli.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from 'node:crypto'\nimport { appendFileSync } from 'node:fs'\nimport { dokployPreviewFromEnvironment, pullRequestNumber } from './dokploy.js'\n\nexport async function runDokployPreviewCli(arguments_: string[], environment: NodeJS.ProcessEnv = process.env) {\n const command = arguments_[0]\n const { config, manager } = dokployPreviewFromEnvironment(environment)\n\n if (command === 'deploy') {\n const prNumber = pullRequestNumber(required(environment, 'PR_NUMBER'))\n const preview = await manager.deploy({\n prNumber,\n image: required(environment, 'PREVIEW_IMAGE'),\n environment: renderPreviewEnvironment(required(environment, 'PREVIEW_ENVIRONMENT'), prNumber),\n ...(config.registry ? { registry: config.registry } : {}),\n })\n if (environment.GITHUB_OUTPUT) appendFileSync(environment.GITHUB_OUTPUT, `preview-url=${preview.url}\\n`)\n return\n }\n if (command === 'delete') {\n const prNumber = pullRequestNumber(required(environment, 'PR_NUMBER'))\n console.log((await manager.delete(prNumber)) ? `Deleted ${config.applicationPrefix}-pr-${prNumber}` : `No preview for pr-${prNumber}`)\n return\n }\n if (command === 'prune') {\n const open = new Set((environment.OPEN_PR_NUMBERS ?? '').split(/\\s+/).filter(Boolean).map(pullRequestNumber))\n for (const prNumber of await manager.prune(open)) console.log(`Deleted ${config.applicationPrefix}-pr-${prNumber}`)\n return\n }\n throw new Error('usage: ras preview dokploy <deploy|delete|prune>')\n}\n\nexport function renderPreviewEnvironment(template: string, prNumber: string) {\n return template\n .replaceAll('{{PR_NUMBER}}', pullRequestNumber(prNumber))\n .replaceAll('{{RANDOM_HEX_32}}', () => randomBytes(32).toString('hex'))\n}\n\nfunction required(environment: NodeJS.ProcessEnv, name: string) {\n const value = optional(environment, name)\n if (!value) throw new Error(`${name} is required`)\n return value\n}\n\nfunction optional(environment: NodeJS.ProcessEnv, name: string) {\n return environment[name]?.trim() || undefined\n}\n"]}
|
|
@@ -2,6 +2,22 @@ export type DokployApplication = {
|
|
|
2
2
|
applicationId: string;
|
|
3
3
|
name: string;
|
|
4
4
|
};
|
|
5
|
+
export declare function dokployPreviewFromEnvironment(environment?: NodeJS.ProcessEnv): {
|
|
6
|
+
config: {
|
|
7
|
+
url: string;
|
|
8
|
+
apiKey: string;
|
|
9
|
+
environmentId: string;
|
|
10
|
+
applicationPrefix: string;
|
|
11
|
+
domain: string;
|
|
12
|
+
port: number;
|
|
13
|
+
healthPath: string | undefined;
|
|
14
|
+
registry?: {
|
|
15
|
+
username: string;
|
|
16
|
+
password: string;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
manager: DokployPreviewManager;
|
|
20
|
+
};
|
|
5
21
|
export type DokployClientOptions = {
|
|
6
22
|
url: string;
|
|
7
23
|
apiKey: string;
|
|
@@ -18,6 +34,7 @@ export declare class DokployClient {
|
|
|
18
34
|
api<T = unknown>(procedure: string, options?: {
|
|
19
35
|
query?: Record<string, string>;
|
|
20
36
|
body?: unknown;
|
|
37
|
+
signal?: AbortSignal;
|
|
21
38
|
}): Promise<T>;
|
|
22
39
|
applications(): Promise<DokployApplication[]>;
|
|
23
40
|
application(name: string): Promise<DokployApplication | undefined>;
|
package/dist/preview/dokploy.js
CHANGED
|
@@ -1,3 +1,38 @@
|
|
|
1
|
+
export function dokployPreviewFromEnvironment(environment = process.env) {
|
|
2
|
+
const username = optionalEnvironment(environment, 'PREVIEW_REGISTRY_USERNAME');
|
|
3
|
+
const password = optionalEnvironment(environment, 'PREVIEW_REGISTRY_PASSWORD');
|
|
4
|
+
if (Boolean(username) !== Boolean(password))
|
|
5
|
+
throw new Error('preview registry username and password must be configured together');
|
|
6
|
+
const port = Number(requiredEnvironment(environment, 'PREVIEW_PORT'));
|
|
7
|
+
if (!Number.isInteger(port) || port < 1 || port > 65_535)
|
|
8
|
+
throw new Error('PREVIEW_PORT must be a valid port');
|
|
9
|
+
const applicationPrefix = requiredEnvironment(environment, 'PREVIEW_APPLICATION_PREFIX');
|
|
10
|
+
if (!/^[a-z\d](?:[a-z\d-]*[a-z\d])?$/.test(applicationPrefix))
|
|
11
|
+
throw new Error('PREVIEW_APPLICATION_PREFIX must be a slug');
|
|
12
|
+
const domain = requiredEnvironment(environment, 'PREVIEW_DOMAIN');
|
|
13
|
+
previewHostname(`pr-1.${domain}`);
|
|
14
|
+
const config = {
|
|
15
|
+
url: requiredEnvironment(environment, 'DOKPLOY_URL'),
|
|
16
|
+
apiKey: requiredEnvironment(environment, 'DOKPLOY_API_KEY'),
|
|
17
|
+
environmentId: requiredEnvironment(environment, 'DOKPLOY_ENVIRONMENT_ID'),
|
|
18
|
+
applicationPrefix,
|
|
19
|
+
domain,
|
|
20
|
+
port,
|
|
21
|
+
healthPath: optionalEnvironment(environment, 'PREVIEW_HEALTH_PATH'),
|
|
22
|
+
...(username && password ? { registry: { username, password } } : {}),
|
|
23
|
+
};
|
|
24
|
+
const client = new DokployClient(config);
|
|
25
|
+
return {
|
|
26
|
+
config,
|
|
27
|
+
manager: new DokployPreviewManager({
|
|
28
|
+
client,
|
|
29
|
+
applicationName: (prNumber) => `${applicationPrefix}-pr-${prNumber}`,
|
|
30
|
+
hostname: (prNumber) => `pr-${prNumber}.${domain}`,
|
|
31
|
+
port,
|
|
32
|
+
...(config.healthPath ? { healthPath: config.healthPath } : {}),
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
1
36
|
export class DokployClient {
|
|
2
37
|
options;
|
|
3
38
|
request;
|
|
@@ -21,6 +56,7 @@ export class DokployClient {
|
|
|
21
56
|
'x-api-key': this.options.apiKey,
|
|
22
57
|
...(options.body === undefined ? {} : { 'content-type': 'application/json' }),
|
|
23
58
|
},
|
|
59
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
24
60
|
...(options.body === undefined ? {} : { body: JSON.stringify(options.body) }),
|
|
25
61
|
});
|
|
26
62
|
const text = await response.text();
|
|
@@ -140,10 +176,21 @@ export class DokployPreviewManager {
|
|
|
140
176
|
// Polling is intentionally sequential; each response determines whether another request is needed.
|
|
141
177
|
// oxlint-disable-next-line no-await-in-loop
|
|
142
178
|
await this.pause(this.options.pollIntervalMs ?? 5_000);
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
179
|
+
let applicationStatus;
|
|
180
|
+
try {
|
|
181
|
+
// Polling is intentionally sequential; each response determines whether another request is needed.
|
|
182
|
+
// oxlint-disable-next-line no-await-in-loop
|
|
183
|
+
const details = await this.options.client.api('application.one', {
|
|
184
|
+
query: { applicationId },
|
|
185
|
+
signal: AbortSignal.timeout(Math.max(1, deadline - this.now())),
|
|
186
|
+
});
|
|
187
|
+
applicationStatus = details.applicationStatus;
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
if (error instanceof DOMException && error.name === 'TimeoutError')
|
|
191
|
+
break;
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
147
194
|
if (applicationStatus === 'done')
|
|
148
195
|
return;
|
|
149
196
|
if (applicationStatus === 'error')
|
|
@@ -158,7 +205,7 @@ export class DokployPreviewManager {
|
|
|
158
205
|
try {
|
|
159
206
|
// Polling is intentionally sequential; each response determines whether another request is needed.
|
|
160
207
|
// oxlint-disable-next-line no-await-in-loop
|
|
161
|
-
const response = await this.request(url);
|
|
208
|
+
const response = await this.request(url, { signal: AbortSignal.timeout(Math.max(1, deadline - this.now())) });
|
|
162
209
|
if (response.status === 200)
|
|
163
210
|
return;
|
|
164
211
|
lastFailure = `status ${response.status}`;
|
|
@@ -189,4 +236,13 @@ function previewApplicationPrNumber(name, applicationName) {
|
|
|
189
236
|
const prNumber = match?.[1];
|
|
190
237
|
return prNumber && applicationName(prNumber) === name ? prNumber : undefined;
|
|
191
238
|
}
|
|
239
|
+
function requiredEnvironment(environment, name) {
|
|
240
|
+
const value = optionalEnvironment(environment, name);
|
|
241
|
+
if (!value)
|
|
242
|
+
throw new Error(`${name} is required`);
|
|
243
|
+
return value;
|
|
244
|
+
}
|
|
245
|
+
function optionalEnvironment(environment, name) {
|
|
246
|
+
return environment[name]?.trim() || undefined;
|
|
247
|
+
}
|
|
192
248
|
//# sourceMappingURL=dokploy.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dokploy.js","sourceRoot":"","sources":["../../src/preview/dokploy.ts"],"names":[],"mappings":"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","sourcesContent":["export type DokployApplication = { applicationId: string; name: string }\n\nexport type DokployClientOptions = {\n url: string\n apiKey: string\n environmentId: string\n fetch?: typeof fetch\n log?: (message: string) => void\n}\n\nexport class DokployClient {\n private readonly request: typeof fetch\n private readonly log: (message: string) => void\n\n constructor(private readonly options: DokployClientOptions) {\n this.request = options.fetch ?? fetch\n this.log = options.log ?? console.log\n }\n\n get environmentId() {\n return this.options.environmentId\n }\n\n async api<T = unknown>(procedure: string, options: { query?: Record<string, string>; body?: unknown } = {}): Promise<T> {\n const url = new URL(`${this.options.url.replace(/\\/$/, '')}/api/${procedure}`)\n for (const [key, value] of Object.entries(options.query ?? {})) url.searchParams.set(key, value)\n this.log(`→ ${procedure}`)\n const response = await this.request(url, {\n method: options.body === undefined ? 'GET' : 'POST',\n headers: {\n 'x-api-key': this.options.apiKey,\n ...(options.body === undefined ? {} : { 'content-type': 'application/json' }),\n },\n ...(options.body === undefined ? {} : { body: JSON.stringify(options.body) }),\n })\n const text = await response.text()\n if (!response.ok) throw new Error(`${procedure} failed with ${response.status}: ${text.slice(0, 500)}`)\n if (!text) return undefined as T\n try {\n return JSON.parse(text) as T\n } catch {\n throw new Error(`${procedure} returned ${response.status} with a non-JSON body: ${text.slice(0, 200)}`)\n }\n }\n\n async applications() {\n const environment = await this.api<{ applications?: DokployApplication[] } | undefined>('environment.one', {\n query: { environmentId: this.options.environmentId },\n })\n if (!environment) throw new Error('environment.one returned an empty response')\n return environment.applications ?? []\n }\n\n async application(name: string) {\n return (await this.applications()).find((application) => application.name === name)\n }\n}\n\nexport type DokployPreviewOptions = {\n client: DokployClient\n applicationName: (prNumber: string) => string\n hostname: (prNumber: string) => string\n port: number\n healthPath?: string\n deploymentTimeoutMs?: number\n healthTimeoutMs?: number\n pollIntervalMs?: number\n fetch?: typeof fetch\n sleep?: (milliseconds: number) => Promise<void>\n now?: () => number\n log?: (message: string) => void\n}\n\nexport type DeployPreviewOptions = {\n prNumber: string\n image: string\n environment: string\n registry?: { username: string; password: string }\n configure?: (context: { applicationId: string; client: DokployClient; host: string }) => void | Promise<void>\n}\n\nexport class DokployPreviewManager {\n private readonly request: typeof fetch\n private readonly pause: (milliseconds: number) => Promise<void>\n private readonly log: (message: string) => void\n private readonly now: () => number\n\n constructor(private readonly options: DokployPreviewOptions) {\n this.request = options.fetch ?? fetch\n this.pause = options.sleep ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)))\n this.log = options.log ?? console.log\n this.now = options.now ?? Date.now\n }\n\n async deploy(options: DeployPreviewOptions) {\n const prNumber = pullRequestNumber(options.prNumber)\n const name = this.options.applicationName(prNumber)\n const host = previewHostname(this.options.hostname(prNumber))\n let application = await this.options.client.application(name)\n if (!application) {\n await this.options.client.api('application.create', {\n body: { name, appName: name, environmentId: this.options.client.environmentId },\n })\n application = await this.options.client.application(name)\n if (!application) throw new Error(`Dokploy did not report ${name} after creating it`)\n }\n const applicationId = application.applicationId\n const details = await this.options.client.api<{ domains?: { host: string }[] } | undefined>('application.one', {\n query: { applicationId },\n })\n if (!details?.domains?.some((domain) => domain.host === host)) {\n await this.options.client.api('domain.create', {\n body: {\n applicationId,\n host,\n path: '/',\n port: this.options.port,\n https: true,\n certificateType: 'letsencrypt',\n domainType: 'application',\n },\n })\n }\n await options.configure?.({ applicationId, client: this.options.client, host })\n await this.options.client.api('application.saveDockerProvider', {\n body: {\n applicationId,\n dockerImage: options.image,\n username: options.registry?.username ?? null,\n password: options.registry?.password ?? null,\n registryUrl: options.registry ? options.image.split('/')[0] : null,\n },\n })\n await this.options.client.api('application.saveEnvironment', {\n body: { applicationId, env: options.environment, buildArgs: null, buildSecrets: null, createEnvFile: false },\n })\n await this.options.client.api('application.deploy', { body: { applicationId } })\n await this.waitForDeployment(applicationId)\n const url = `https://${host}`\n await this.waitForHealth(new URL(this.options.healthPath ?? '/api/health', url).toString())\n this.log(`Preview ready at ${url}`)\n return { applicationId, host, url }\n }\n\n async delete(prNumber: string, beforeDelete?: (application: DokployApplication | undefined) => void | Promise<void>) {\n const name = this.options.applicationName(pullRequestNumber(prNumber))\n const application = await this.options.client.application(name)\n await beforeDelete?.(application)\n if (!application) return false\n await this.options.client.api('application.delete', { body: { applicationId: application.applicationId } })\n return true\n }\n\n async prune(\n openPullRequests: ReadonlySet<string>,\n beforeDelete?: (prNumber: string, application: DokployApplication) => void | Promise<void>,\n ) {\n const deleted: string[] = []\n for (const application of await this.options.client.applications()) {\n const prNumber = previewApplicationPrNumber(application.name, this.options.applicationName)\n if (!prNumber || openPullRequests.has(prNumber)) continue\n // Dokploy mutations are intentionally ordered to avoid overwhelming one environment.\n // oxlint-disable-next-line no-await-in-loop\n await beforeDelete?.(prNumber, application)\n // oxlint-disable-next-line no-await-in-loop\n await this.options.client.api('application.delete', { body: { applicationId: application.applicationId } })\n deleted.push(prNumber)\n }\n return deleted\n }\n\n private async waitForDeployment(applicationId: string) {\n const deadline = this.now() + (this.options.deploymentTimeoutMs ?? 600_000)\n while (this.now() < deadline) {\n // Polling is intentionally sequential; each response determines whether another request is needed.\n // oxlint-disable-next-line no-await-in-loop\n await this.pause(this.options.pollIntervalMs ?? 5_000)\n // oxlint-disable-next-line no-await-in-loop\n const { applicationStatus } = await this.options.client.api<{ applicationStatus: string }>('application.one', {\n query: { applicationId },\n })\n if (applicationStatus === 'done') return\n if (applicationStatus === 'error') throw new Error('Dokploy reported a failed deployment')\n }\n throw new Error('Timed out waiting for the Dokploy deployment to finish')\n }\n\n private async waitForHealth(url: string) {\n const deadline = this.now() + (this.options.healthTimeoutMs ?? 300_000)\n let lastFailure = 'no response'\n while (this.now() < deadline) {\n try {\n // Polling is intentionally sequential; each response determines whether another request is needed.\n // oxlint-disable-next-line no-await-in-loop\n const response = await this.request(url)\n if (response.status === 200) return\n lastFailure = `status ${response.status}`\n } catch (error) {\n lastFailure = error instanceof Error ? error.message : String(error)\n }\n // oxlint-disable-next-line no-await-in-loop\n await this.pause(this.options.pollIntervalMs ?? 5_000)\n }\n throw new Error(`Timed out waiting for ${url} (${lastFailure})`)\n }\n}\n\nexport function pullRequestNumber(value: string) {\n if (!/^\\d+$/.test(value)) throw new Error('pull request number must contain only digits')\n return value\n}\n\nexport function previewHostname(value: string) {\n const parsed = new URL(`https://${value}`)\n if (parsed.hostname !== value || parsed.port || parsed.username || parsed.password || parsed.pathname !== '/') {\n throw new Error('preview hostname must be a bare hostname')\n }\n return value\n}\n\nfunction previewApplicationPrNumber(name: string, applicationName: (prNumber: string) => string) {\n const match = /^(\\d+)$/.exec(name.match(/(\\d+)$/)?.[1] ?? '')\n const prNumber = match?.[1]\n return prNumber && applicationName(prNumber) === name ? prNumber : undefined\n}\n"]}
|
|
1
|
+
{"version":3,"file":"dokploy.js","sourceRoot":"","sources":["../../src/preview/dokploy.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,6BAA6B,CAAC,WAAW,GAAsB,OAAO,CAAC,GAAG;IACxF,MAAM,QAAQ,GAAG,mBAAmB,CAAC,WAAW,EAAE,2BAA2B,CAAC,CAAA;IAC9E,MAAM,QAAQ,GAAG,mBAAmB,CAAC,WAAW,EAAE,2BAA2B,CAAC,CAAA;IAC9E,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAA;IAClI,MAAM,IAAI,GAAG,MAAM,CAAC,mBAAmB,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC,CAAA;IACrE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAA;IAC9G,MAAM,iBAAiB,GAAG,mBAAmB,CAAC,WAAW,EAAE,4BAA4B,CAAC,CAAA;IACxF,IAAI,CAAC,gCAAgC,CAAC,IAAI,CAAC,iBAAiB,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAA;IAC3H,MAAM,MAAM,GAAG,mBAAmB,CAAC,WAAW,EAAE,gBAAgB,CAAC,CAAA;IACjE,eAAe,CAAC,QAAQ,MAAM,EAAE,CAAC,CAAA;IACjC,MAAM,MAAM,GAAG;QACb,GAAG,EAAE,mBAAmB,CAAC,WAAW,EAAE,aAAa,CAAC;QACpD,MAAM,EAAE,mBAAmB,CAAC,WAAW,EAAE,iBAAiB,CAAC;QAC3D,aAAa,EAAE,mBAAmB,CAAC,WAAW,EAAE,wBAAwB,CAAC;QACzE,iBAAiB;QACjB,MAAM;QACN,IAAI;QACJ,UAAU,EAAE,mBAAmB,CAAC,WAAW,EAAE,qBAAqB,CAAC;QACnE,GAAG,CAAC,QAAQ,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtE,CAAA;IACD,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,MAAM,CAAC,CAAA;IACxC,OAAO;QACL,MAAM;QACN,OAAO,EAAE,IAAI,qBAAqB,CAAC;YACjC,MAAM;YACN,eAAe,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,GAAG,iBAAiB,OAAO,QAAQ,EAAE;YACpE,QAAQ,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,MAAM,QAAQ,IAAI,MAAM,EAAE;YAClD,IAAI;YACJ,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAChE,CAAC;KACH,CAAA;AACH,CAAC;AAUD,MAAM,OAAO,aAAa;IAIK,OAAO;IAHnB,OAAO,CAAc;IACrB,GAAG,CAA2B;IAE/C,YAA6B,OAA6B;uBAA7B,OAAO;QAClC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,KAAK,CAAA;QACrC,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAA;IACvC,CAAC;IAED,IAAI,aAAa;QACf,OAAO,IAAI,CAAC,OAAO,CAAC,aAAa,CAAA;IACnC,CAAC;IAED,KAAK,CAAC,GAAG,CACP,SAAiB,EACjB,OAAO,GAA6E,EAAE;QAEtF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,SAAS,EAAE,CAAC,CAAA;QAC9E,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;YAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;QAChG,IAAI,CAAC,GAAG,CAAC,KAAK,SAAS,EAAE,CAAC,CAAA;QAC1B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE;YACvC,MAAM,EAAE,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM;YACnD,OAAO,EAAE;gBACP,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM;gBAChC,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC;aAC9E;YACD,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrD,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;SAC9E,CAAC,CAAA;QACF,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;QAClC,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,SAAS,gBAAgB,QAAQ,CAAC,MAAM,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAA;QACvG,IAAI,CAAC,IAAI;YAAE,OAAO,SAAc,CAAA;QAChC,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAM,CAAA;QAC9B,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,GAAG,SAAS,aAAa,QAAQ,CAAC,MAAM,0BAA0B,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAA;QACzG,CAAC;IACH,CAAC;IAED,KAAK,CAAC,YAAY;QAChB,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,GAAG,CAAsD,iBAAiB,EAAE;YACzG,KAAK,EAAE,EAAE,aAAa,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE;SACrD,CAAC,CAAA;QACF,IAAI,CAAC,WAAW;YAAE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAA;QAC/E,OAAO,WAAW,CAAC,YAAY,IAAI,EAAE,CAAA;IACvC,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,IAAY;QAC5B,OAAO,CAAC,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;IACrF,CAAC;CACF;AAyBD,MAAM,OAAO,qBAAqB;IAMH,OAAO;IALnB,OAAO,CAAc;IACrB,KAAK,CAAyC;IAC9C,GAAG,CAA2B;IAC9B,GAAG,CAAc;IAElC,YAA6B,OAA8B;uBAA9B,OAAO;QAClC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,KAAK,CAAA;QACrC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,CAAA;QAC7G,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAA;QACrC,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAA;IACpC,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,OAA6B;QACxC,MAAM,QAAQ,GAAG,iBAAiB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;QACpD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAA;QACnD,MAAM,IAAI,GAAG,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAA;QAC7D,IAAI,WAAW,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;QAC7D,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,oBAAoB,EAAE;gBAClD,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE;aAChF,CAAC,CAAA;YACF,WAAW,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;YACzD,IAAI,CAAC,WAAW;gBAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,IAAI,oBAAoB,CAAC,CAAA;QACvF,CAAC;QACD,MAAM,aAAa,GAAG,WAAW,CAAC,aAAa,CAAA;QAC/C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAA+C,iBAAiB,EAAE;YAC7G,KAAK,EAAE,EAAE,aAAa,EAAE;SACzB,CAAC,CAAA;QACF,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;YAC9D,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,eAAe,EAAE;gBAC7C,IAAI,EAAE;oBACJ,aAAa;oBACb,IAAI;oBACJ,IAAI,EAAE,GAAG;oBACT,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI;oBACvB,KAAK,EAAE,IAAI;oBACX,eAAe,EAAE,aAAa;oBAC9B,UAAU,EAAE,aAAa;iBAC1B;aACF,CAAC,CAAA;QACJ,CAAC;QACD,MAAM,OAAO,CAAC,SAAS,EAAE,CAAC,EAAE,aAAa,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;QAC/E,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,gCAAgC,EAAE;YAC9D,IAAI,EAAE;gBACJ,aAAa;gBACb,WAAW,EAAE,OAAO,CAAC,KAAK;gBAC1B,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,QAAQ,IAAI,IAAI;gBAC5C,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,QAAQ,IAAI,IAAI;gBAC5C,WAAW,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI;aACnE;SACF,CAAC,CAAA;QACF,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,6BAA6B,EAAE;YAC3D,IAAI,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,OAAO,CAAC,WAAW,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE;SAC7G,CAAC,CAAA;QACF,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,oBAAoB,EAAE,EAAE,IAAI,EAAE,EAAE,aAAa,EAAE,EAAE,CAAC,CAAA;QAChF,MAAM,IAAI,CAAC,iBAAiB,CAAC,aAAa,CAAC,CAAA;QAC3C,MAAM,GAAG,GAAG,WAAW,IAAI,EAAE,CAAA;QAC7B,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,IAAI,aAAa,EAAE,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAA;QAC3F,IAAI,CAAC,GAAG,CAAC,oBAAoB,GAAG,EAAE,CAAC,CAAA;QACnC,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,GAAG,EAAE,CAAA;IACrC,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,QAAgB,EAAE,YAAoF;QACjH,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAA;QACtE,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;QAC/D,MAAM,YAAY,EAAE,CAAC,WAAW,CAAC,CAAA;QACjC,IAAI,CAAC,WAAW;YAAE,OAAO,KAAK,CAAA;QAC9B,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,oBAAoB,EAAE,EAAE,IAAI,EAAE,EAAE,aAAa,EAAE,WAAW,CAAC,aAAa,EAAE,EAAE,CAAC,CAAA;QAC3G,OAAO,IAAI,CAAA;IACb,CAAC;IAED,KAAK,CAAC,KAAK,CACT,gBAAqC,EACrC,YAA0F;QAE1F,MAAM,OAAO,GAAa,EAAE,CAAA;QAC5B,KAAK,MAAM,WAAW,IAAI,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,EAAE,CAAC;YACnE,MAAM,QAAQ,GAAG,0BAA0B,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,CAAA;YAC3F,IAAI,CAAC,QAAQ,IAAI,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC;gBAAE,SAAQ;YACzD,qFAAqF;YACrF,4CAA4C;YAC5C,MAAM,YAAY,EAAE,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAA;YAC3C,4CAA4C;YAC5C,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,oBAAoB,EAAE,EAAE,IAAI,EAAE,EAAE,aAAa,EAAE,WAAW,CAAC,aAAa,EAAE,EAAE,CAAC,CAAA;YAC3G,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACxB,CAAC;QACD,OAAO,OAAO,CAAA;IAChB,CAAC;IAEO,KAAK,CAAC,iBAAiB,CAAC,aAAqB;QACnD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,mBAAmB,IAAI,OAAO,CAAC,CAAA;QAC3E,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;YAC7B,mGAAmG;YACnG,4CAA4C;YAC5C,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,cAAc,IAAI,KAAK,CAAC,CAAA;YACtD,IAAI,iBAAyB,CAAA;YAC7B,IAAI,CAAC;gBACH,mGAAmG;gBACnG,4CAA4C;gBAC5C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAgC,iBAAiB,EAAE;oBAC9F,KAAK,EAAE,EAAE,aAAa,EAAE;oBACxB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;iBAChE,CAAC,CAAA;gBACF,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAA;YAC/C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,KAAK,YAAY,YAAY,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc;oBAAE,MAAK;gBACzE,MAAM,KAAK,CAAA;YACb,CAAC;YACD,IAAI,iBAAiB,KAAK,MAAM;gBAAE,OAAM;YACxC,IAAI,iBAAiB,KAAK,OAAO;gBAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAA;QAC5F,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAA;IAC3E,CAAC;IAEO,KAAK,CAAC,aAAa,CAAC,GAAW;QACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,eAAe,IAAI,OAAO,CAAC,CAAA;QACvE,IAAI,WAAW,GAAG,aAAa,CAAA;QAC/B,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;YAC7B,IAAI,CAAC;gBACH,mGAAmG;gBACnG,4CAA4C;gBAC5C,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAA;gBAC7G,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG;oBAAE,OAAM;gBACnC,WAAW,GAAG,UAAU,QAAQ,CAAC,MAAM,EAAE,CAAA;YAC3C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,WAAW,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YACtE,CAAC;YACD,4CAA4C;YAC5C,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,cAAc,IAAI,KAAK,CAAC,CAAA;QACxD,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,yBAAyB,GAAG,KAAK,WAAW,GAAG,CAAC,CAAA;IAClE,CAAC;CACF;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAA;IACzF,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,WAAW,KAAK,EAAE,CAAC,CAAA;IAC1C,IAAI,MAAM,CAAC,QAAQ,KAAK,KAAK,IAAI,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,KAAK,GAAG,EAAE,CAAC;QAC9G,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAA;IAC7D,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAS,0BAA0B,CAAC,IAAY,EAAE,eAA6C;IAC7F,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;IAC7D,MAAM,QAAQ,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAA;IAC3B,OAAO,QAAQ,IAAI,eAAe,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAA;AAC9E,CAAC;AAED,SAAS,mBAAmB,CAAC,WAA8B,EAAE,IAAY;IACvE,MAAM,KAAK,GAAG,mBAAmB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAA;IACpD,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,cAAc,CAAC,CAAA;IAClD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAS,mBAAmB,CAAC,WAA8B,EAAE,IAAY;IACvE,OAAO,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,SAAS,CAAA;AAC/C,CAAC","sourcesContent":["export type DokployApplication = { applicationId: string; name: string }\n\nexport function dokployPreviewFromEnvironment(environment: NodeJS.ProcessEnv = process.env) {\n const username = optionalEnvironment(environment, 'PREVIEW_REGISTRY_USERNAME')\n const password = optionalEnvironment(environment, 'PREVIEW_REGISTRY_PASSWORD')\n if (Boolean(username) !== Boolean(password)) throw new Error('preview registry username and password must be configured together')\n const port = Number(requiredEnvironment(environment, 'PREVIEW_PORT'))\n if (!Number.isInteger(port) || port < 1 || port > 65_535) throw new Error('PREVIEW_PORT must be a valid port')\n const applicationPrefix = requiredEnvironment(environment, 'PREVIEW_APPLICATION_PREFIX')\n if (!/^[a-z\\d](?:[a-z\\d-]*[a-z\\d])?$/.test(applicationPrefix)) throw new Error('PREVIEW_APPLICATION_PREFIX must be a slug')\n const domain = requiredEnvironment(environment, 'PREVIEW_DOMAIN')\n previewHostname(`pr-1.${domain}`)\n const config = {\n url: requiredEnvironment(environment, 'DOKPLOY_URL'),\n apiKey: requiredEnvironment(environment, 'DOKPLOY_API_KEY'),\n environmentId: requiredEnvironment(environment, 'DOKPLOY_ENVIRONMENT_ID'),\n applicationPrefix,\n domain,\n port,\n healthPath: optionalEnvironment(environment, 'PREVIEW_HEALTH_PATH'),\n ...(username && password ? { registry: { username, password } } : {}),\n }\n const client = new DokployClient(config)\n return {\n config,\n manager: new DokployPreviewManager({\n client,\n applicationName: (prNumber) => `${applicationPrefix}-pr-${prNumber}`,\n hostname: (prNumber) => `pr-${prNumber}.${domain}`,\n port,\n ...(config.healthPath ? { healthPath: config.healthPath } : {}),\n }),\n }\n}\n\nexport type DokployClientOptions = {\n url: string\n apiKey: string\n environmentId: string\n fetch?: typeof fetch\n log?: (message: string) => void\n}\n\nexport class DokployClient {\n private readonly request: typeof fetch\n private readonly log: (message: string) => void\n\n constructor(private readonly options: DokployClientOptions) {\n this.request = options.fetch ?? fetch\n this.log = options.log ?? console.log\n }\n\n get environmentId() {\n return this.options.environmentId\n }\n\n async api<T = unknown>(\n procedure: string,\n options: { query?: Record<string, string>; body?: unknown; signal?: AbortSignal } = {},\n ): Promise<T> {\n const url = new URL(`${this.options.url.replace(/\\/$/, '')}/api/${procedure}`)\n for (const [key, value] of Object.entries(options.query ?? {})) url.searchParams.set(key, value)\n this.log(`→ ${procedure}`)\n const response = await this.request(url, {\n method: options.body === undefined ? 'GET' : 'POST',\n headers: {\n 'x-api-key': this.options.apiKey,\n ...(options.body === undefined ? {} : { 'content-type': 'application/json' }),\n },\n ...(options.signal ? { signal: options.signal } : {}),\n ...(options.body === undefined ? {} : { body: JSON.stringify(options.body) }),\n })\n const text = await response.text()\n if (!response.ok) throw new Error(`${procedure} failed with ${response.status}: ${text.slice(0, 500)}`)\n if (!text) return undefined as T\n try {\n return JSON.parse(text) as T\n } catch {\n throw new Error(`${procedure} returned ${response.status} with a non-JSON body: ${text.slice(0, 200)}`)\n }\n }\n\n async applications() {\n const environment = await this.api<{ applications?: DokployApplication[] } | undefined>('environment.one', {\n query: { environmentId: this.options.environmentId },\n })\n if (!environment) throw new Error('environment.one returned an empty response')\n return environment.applications ?? []\n }\n\n async application(name: string) {\n return (await this.applications()).find((application) => application.name === name)\n }\n}\n\nexport type DokployPreviewOptions = {\n client: DokployClient\n applicationName: (prNumber: string) => string\n hostname: (prNumber: string) => string\n port: number\n healthPath?: string\n deploymentTimeoutMs?: number\n healthTimeoutMs?: number\n pollIntervalMs?: number\n fetch?: typeof fetch\n sleep?: (milliseconds: number) => Promise<void>\n now?: () => number\n log?: (message: string) => void\n}\n\nexport type DeployPreviewOptions = {\n prNumber: string\n image: string\n environment: string\n registry?: { username: string; password: string }\n configure?: (context: { applicationId: string; client: DokployClient; host: string }) => void | Promise<void>\n}\n\nexport class DokployPreviewManager {\n private readonly request: typeof fetch\n private readonly pause: (milliseconds: number) => Promise<void>\n private readonly log: (message: string) => void\n private readonly now: () => number\n\n constructor(private readonly options: DokployPreviewOptions) {\n this.request = options.fetch ?? fetch\n this.pause = options.sleep ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)))\n this.log = options.log ?? console.log\n this.now = options.now ?? Date.now\n }\n\n async deploy(options: DeployPreviewOptions) {\n const prNumber = pullRequestNumber(options.prNumber)\n const name = this.options.applicationName(prNumber)\n const host = previewHostname(this.options.hostname(prNumber))\n let application = await this.options.client.application(name)\n if (!application) {\n await this.options.client.api('application.create', {\n body: { name, appName: name, environmentId: this.options.client.environmentId },\n })\n application = await this.options.client.application(name)\n if (!application) throw new Error(`Dokploy did not report ${name} after creating it`)\n }\n const applicationId = application.applicationId\n const details = await this.options.client.api<{ domains?: { host: string }[] } | undefined>('application.one', {\n query: { applicationId },\n })\n if (!details?.domains?.some((domain) => domain.host === host)) {\n await this.options.client.api('domain.create', {\n body: {\n applicationId,\n host,\n path: '/',\n port: this.options.port,\n https: true,\n certificateType: 'letsencrypt',\n domainType: 'application',\n },\n })\n }\n await options.configure?.({ applicationId, client: this.options.client, host })\n await this.options.client.api('application.saveDockerProvider', {\n body: {\n applicationId,\n dockerImage: options.image,\n username: options.registry?.username ?? null,\n password: options.registry?.password ?? null,\n registryUrl: options.registry ? options.image.split('/')[0] : null,\n },\n })\n await this.options.client.api('application.saveEnvironment', {\n body: { applicationId, env: options.environment, buildArgs: null, buildSecrets: null, createEnvFile: false },\n })\n await this.options.client.api('application.deploy', { body: { applicationId } })\n await this.waitForDeployment(applicationId)\n const url = `https://${host}`\n await this.waitForHealth(new URL(this.options.healthPath ?? '/api/health', url).toString())\n this.log(`Preview ready at ${url}`)\n return { applicationId, host, url }\n }\n\n async delete(prNumber: string, beforeDelete?: (application: DokployApplication | undefined) => void | Promise<void>) {\n const name = this.options.applicationName(pullRequestNumber(prNumber))\n const application = await this.options.client.application(name)\n await beforeDelete?.(application)\n if (!application) return false\n await this.options.client.api('application.delete', { body: { applicationId: application.applicationId } })\n return true\n }\n\n async prune(\n openPullRequests: ReadonlySet<string>,\n beforeDelete?: (prNumber: string, application: DokployApplication) => void | Promise<void>,\n ) {\n const deleted: string[] = []\n for (const application of await this.options.client.applications()) {\n const prNumber = previewApplicationPrNumber(application.name, this.options.applicationName)\n if (!prNumber || openPullRequests.has(prNumber)) continue\n // Dokploy mutations are intentionally ordered to avoid overwhelming one environment.\n // oxlint-disable-next-line no-await-in-loop\n await beforeDelete?.(prNumber, application)\n // oxlint-disable-next-line no-await-in-loop\n await this.options.client.api('application.delete', { body: { applicationId: application.applicationId } })\n deleted.push(prNumber)\n }\n return deleted\n }\n\n private async waitForDeployment(applicationId: string) {\n const deadline = this.now() + (this.options.deploymentTimeoutMs ?? 600_000)\n while (this.now() < deadline) {\n // Polling is intentionally sequential; each response determines whether another request is needed.\n // oxlint-disable-next-line no-await-in-loop\n await this.pause(this.options.pollIntervalMs ?? 5_000)\n let applicationStatus: string\n try {\n // Polling is intentionally sequential; each response determines whether another request is needed.\n // oxlint-disable-next-line no-await-in-loop\n const details = await this.options.client.api<{ applicationStatus: string }>('application.one', {\n query: { applicationId },\n signal: AbortSignal.timeout(Math.max(1, deadline - this.now())),\n })\n applicationStatus = details.applicationStatus\n } catch (error) {\n if (error instanceof DOMException && error.name === 'TimeoutError') break\n throw error\n }\n if (applicationStatus === 'done') return\n if (applicationStatus === 'error') throw new Error('Dokploy reported a failed deployment')\n }\n throw new Error('Timed out waiting for the Dokploy deployment to finish')\n }\n\n private async waitForHealth(url: string) {\n const deadline = this.now() + (this.options.healthTimeoutMs ?? 300_000)\n let lastFailure = 'no response'\n while (this.now() < deadline) {\n try {\n // Polling is intentionally sequential; each response determines whether another request is needed.\n // oxlint-disable-next-line no-await-in-loop\n const response = await this.request(url, { signal: AbortSignal.timeout(Math.max(1, deadline - this.now())) })\n if (response.status === 200) return\n lastFailure = `status ${response.status}`\n } catch (error) {\n lastFailure = error instanceof Error ? error.message : String(error)\n }\n // oxlint-disable-next-line no-await-in-loop\n await this.pause(this.options.pollIntervalMs ?? 5_000)\n }\n throw new Error(`Timed out waiting for ${url} (${lastFailure})`)\n }\n}\n\nexport function pullRequestNumber(value: string) {\n if (!/^\\d+$/.test(value)) throw new Error('pull request number must contain only digits')\n return value\n}\n\nexport function previewHostname(value: string) {\n const parsed = new URL(`https://${value}`)\n if (parsed.hostname !== value || parsed.port || parsed.username || parsed.password || parsed.pathname !== '/') {\n throw new Error('preview hostname must be a bare hostname')\n }\n return value\n}\n\nfunction previewApplicationPrNumber(name: string, applicationName: (prNumber: string) => string) {\n const match = /^(\\d+)$/.exec(name.match(/(\\d+)$/)?.[1] ?? '')\n const prNumber = match?.[1]\n return prNumber && applicationName(prNumber) === name ? prNumber : undefined\n}\n\nfunction requiredEnvironment(environment: NodeJS.ProcessEnv, name: string) {\n const value = optionalEnvironment(environment, name)\n if (!value) throw new Error(`${name} is required`)\n return value\n}\n\nfunction optionalEnvironment(environment: NodeJS.ProcessEnv, name: string) {\n return environment[name]?.trim() || undefined\n}\n"]}
|