ras-stack 0.42.1 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/preview/cloudflare.d.ts +22 -0
- package/dist/preview/cloudflare.js +85 -0
- package/dist/preview/cloudflare.js.map +1 -0
- package/dist/preview/dokploy-cli.js +33 -2
- package/dist/preview/dokploy-cli.js.map +1 -1
- package/dist/preview/dokploy.d.ts +3 -0
- package/dist/preview/dokploy.js +21 -7
- package/dist/preview/dokploy.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -103,7 +103,7 @@ Three reusable workflows provide the standard pull-request preview lifecycle:
|
|
|
103
103
|
- `deploy-dokploy-preview.yml` publishes, resolves, deploys, reports, and removes previews.
|
|
104
104
|
- `prune-dokploy-previews.yml` cleans up applications and images left behind by interrupted runs.
|
|
105
105
|
|
|
106
|
-
Applications supply only their package, application prefix, port, environment template, and optional product hook. They can provide a custom HTTPS domain or let Dokploy generate an HTTP `sslip.io` address. 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.
|
|
106
|
+
Applications supply only their package, application prefix, port, environment template, and optional product hook. They can provide a custom HTTPS domain, optionally manage a prefixed Cloudflare-proxied record for origins that reject direct traffic, or let Dokploy generate an HTTP `sslip.io` address. 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.
|
|
107
107
|
|
|
108
108
|
## Guides 📚
|
|
109
109
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type CloudflarePreviewDnsOptions = {
|
|
2
|
+
apiToken: string;
|
|
3
|
+
zoneId: string;
|
|
4
|
+
fetch?: typeof fetch;
|
|
5
|
+
};
|
|
6
|
+
export declare class CloudflarePreviewDns {
|
|
7
|
+
private readonly options;
|
|
8
|
+
private readonly request;
|
|
9
|
+
private readonly baseUrl;
|
|
10
|
+
constructor(options: CloudflarePreviewDnsOptions);
|
|
11
|
+
upsert(input: {
|
|
12
|
+
hostname: string;
|
|
13
|
+
originIp: string;
|
|
14
|
+
owner: string;
|
|
15
|
+
}): Promise<void>;
|
|
16
|
+
delete(input: {
|
|
17
|
+
hostname: string;
|
|
18
|
+
owner: string;
|
|
19
|
+
}): Promise<void>;
|
|
20
|
+
private records;
|
|
21
|
+
private api;
|
|
22
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { isIP } from 'node:net';
|
|
2
|
+
import { previewHostname } from './dokploy.js';
|
|
3
|
+
export class CloudflarePreviewDns {
|
|
4
|
+
options;
|
|
5
|
+
request;
|
|
6
|
+
baseUrl;
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
if (!options.apiToken.trim())
|
|
10
|
+
throw new Error('Cloudflare API token is required');
|
|
11
|
+
if (!/^[a-f\d]{32}$/i.test(options.zoneId))
|
|
12
|
+
throw new Error('Cloudflare zone ID must contain 32 hexadecimal characters');
|
|
13
|
+
this.request = options.fetch ?? fetch;
|
|
14
|
+
this.baseUrl = `https://api.cloudflare.com/client/v4/zones/${options.zoneId}/dns_records`;
|
|
15
|
+
}
|
|
16
|
+
async upsert(input) {
|
|
17
|
+
const hostname = previewHostname(input.hostname);
|
|
18
|
+
if (isIP(input.originIp) !== 4)
|
|
19
|
+
throw new Error('Cloudflare preview DNS requires a public IPv4 origin');
|
|
20
|
+
const comment = ownerComment(input.owner);
|
|
21
|
+
const records = await this.records(hostname);
|
|
22
|
+
const addressRecords = records.filter((record) => ['A', 'AAAA', 'CNAME'].includes(record.type));
|
|
23
|
+
const managed = addressRecords.filter((record) => record.type === 'A' && record.comment === comment);
|
|
24
|
+
if (managed.length > 1 || addressRecords.some((record) => !managed.includes(record))) {
|
|
25
|
+
throw new Error(`Cloudflare DNS record ${hostname} is not owned by ras-stack`);
|
|
26
|
+
}
|
|
27
|
+
const desired = {
|
|
28
|
+
type: 'A',
|
|
29
|
+
name: hostname,
|
|
30
|
+
content: input.originIp,
|
|
31
|
+
proxied: true,
|
|
32
|
+
ttl: 1,
|
|
33
|
+
comment,
|
|
34
|
+
};
|
|
35
|
+
const current = managed[0];
|
|
36
|
+
if (!current) {
|
|
37
|
+
await this.api(this.baseUrl, { method: 'POST', body: desired });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (current.content === desired.content && current.proxied === desired.proxied)
|
|
41
|
+
return;
|
|
42
|
+
await this.api(`${this.baseUrl}/${current.id}`, { method: 'PUT', body: desired });
|
|
43
|
+
}
|
|
44
|
+
async delete(input) {
|
|
45
|
+
const hostname = previewHostname(input.hostname);
|
|
46
|
+
const comment = ownerComment(input.owner);
|
|
47
|
+
const records = await this.records(hostname);
|
|
48
|
+
const managed = records.filter((record) => record.type === 'A' && record.comment === comment);
|
|
49
|
+
if (managed.length > 1)
|
|
50
|
+
throw new Error(`Cloudflare DNS has multiple records owned by ${input.owner}`);
|
|
51
|
+
if (managed[0])
|
|
52
|
+
await this.api(`${this.baseUrl}/${managed[0].id}`, { method: 'DELETE' });
|
|
53
|
+
}
|
|
54
|
+
async records(hostname) {
|
|
55
|
+
const url = new URL(this.baseUrl);
|
|
56
|
+
url.searchParams.set('name', hostname);
|
|
57
|
+
url.searchParams.set('match', 'all');
|
|
58
|
+
return this.api(url.toString());
|
|
59
|
+
}
|
|
60
|
+
async api(url, options = {}) {
|
|
61
|
+
const response = await this.request(url, {
|
|
62
|
+
method: options.method ?? 'GET',
|
|
63
|
+
headers: {
|
|
64
|
+
authorization: `Bearer ${this.options.apiToken}`,
|
|
65
|
+
...(options.body === undefined ? {} : { 'content-type': 'application/json' }),
|
|
66
|
+
},
|
|
67
|
+
...(options.body === undefined ? {} : { body: JSON.stringify(options.body) }),
|
|
68
|
+
});
|
|
69
|
+
const payload = (await response.json());
|
|
70
|
+
if (!response.ok || !payload.success) {
|
|
71
|
+
const detail = payload.errors
|
|
72
|
+
?.map((error) => error.message)
|
|
73
|
+
.filter(Boolean)
|
|
74
|
+
.join('; ') || response.statusText;
|
|
75
|
+
throw new Error(`Cloudflare DNS request failed with ${response.status}: ${detail}`);
|
|
76
|
+
}
|
|
77
|
+
return payload.result;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function ownerComment(owner) {
|
|
81
|
+
if (!/^[a-z\d](?:[a-z\d-]*[a-z\d])?$/.test(owner))
|
|
82
|
+
throw new Error('Cloudflare preview DNS owner must be a slug');
|
|
83
|
+
return `ras-stack preview ${owner}`;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=cloudflare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../src/preview/cloudflare.ts"],"names":[],"mappings":"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","sourcesContent":["import { isIP } from 'node:net'\nimport { previewHostname } from './dokploy.js'\n\ntype CloudflareDnsRecord = {\n id: string\n type: string\n name: string\n content: string\n proxied?: boolean\n comment?: string | null\n}\n\ntype CloudflareResponse<T> = {\n success: boolean\n result: T\n errors?: { message?: string }[]\n}\n\nexport type CloudflarePreviewDnsOptions = {\n apiToken: string\n zoneId: string\n fetch?: typeof fetch\n}\n\nexport class CloudflarePreviewDns {\n private readonly request: typeof fetch\n private readonly baseUrl: string\n\n constructor(private readonly options: CloudflarePreviewDnsOptions) {\n if (!options.apiToken.trim()) throw new Error('Cloudflare API token is required')\n if (!/^[a-f\\d]{32}$/i.test(options.zoneId)) throw new Error('Cloudflare zone ID must contain 32 hexadecimal characters')\n this.request = options.fetch ?? fetch\n this.baseUrl = `https://api.cloudflare.com/client/v4/zones/${options.zoneId}/dns_records`\n }\n\n async upsert(input: { hostname: string; originIp: string; owner: string }) {\n const hostname = previewHostname(input.hostname)\n if (isIP(input.originIp) !== 4) throw new Error('Cloudflare preview DNS requires a public IPv4 origin')\n const comment = ownerComment(input.owner)\n const records = await this.records(hostname)\n const addressRecords = records.filter((record) => ['A', 'AAAA', 'CNAME'].includes(record.type))\n const managed = addressRecords.filter((record) => record.type === 'A' && record.comment === comment)\n if (managed.length > 1 || addressRecords.some((record) => !managed.includes(record))) {\n throw new Error(`Cloudflare DNS record ${hostname} is not owned by ras-stack`)\n }\n const desired = {\n type: 'A',\n name: hostname,\n content: input.originIp,\n proxied: true,\n ttl: 1,\n comment,\n } as const\n const current = managed[0]\n if (!current) {\n await this.api(this.baseUrl, { method: 'POST', body: desired })\n return\n }\n if (current.content === desired.content && current.proxied === desired.proxied) return\n await this.api(`${this.baseUrl}/${current.id}`, { method: 'PUT', body: desired })\n }\n\n async delete(input: { hostname: string; owner: string }) {\n const hostname = previewHostname(input.hostname)\n const comment = ownerComment(input.owner)\n const records = await this.records(hostname)\n const managed = records.filter((record) => record.type === 'A' && record.comment === comment)\n if (managed.length > 1) throw new Error(`Cloudflare DNS has multiple records owned by ${input.owner}`)\n if (managed[0]) await this.api(`${this.baseUrl}/${managed[0].id}`, { method: 'DELETE' })\n }\n\n private async records(hostname: string) {\n const url = new URL(this.baseUrl)\n url.searchParams.set('name', hostname)\n url.searchParams.set('match', 'all')\n return this.api<CloudflareDnsRecord[]>(url.toString())\n }\n\n private async api<T = unknown>(url: string, options: { method?: string; body?: unknown } = {}) {\n const response = await this.request(url, {\n method: options.method ?? 'GET',\n headers: {\n authorization: `Bearer ${this.options.apiToken}`,\n ...(options.body === undefined ? {} : { 'content-type': 'application/json' }),\n },\n ...(options.body === undefined ? {} : { body: JSON.stringify(options.body) }),\n })\n const payload = (await response.json()) as CloudflareResponse<T>\n if (!response.ok || !payload.success) {\n const detail =\n payload.errors\n ?.map((error) => error.message)\n .filter(Boolean)\n .join('; ') || response.statusText\n throw new Error(`Cloudflare DNS request failed with ${response.status}: ${detail}`)\n }\n return payload.result\n }\n}\n\nfunction ownerComment(owner: string) {\n if (!/^[a-z\\d](?:[a-z\\d-]*[a-z\\d])?$/.test(owner)) throw new Error('Cloudflare preview DNS owner must be a slug')\n return `ras-stack preview ${owner}`\n}\n"]}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { CloudflarePreviewDns } from './cloudflare.js';
|
|
2
3
|
import { dokployPreviewFromEnvironment, pullRequestNumber } from './dokploy.js';
|
|
3
4
|
export async function runDokployPreviewCli(arguments_, environment = process.env) {
|
|
4
5
|
const command = arguments_[0];
|
|
5
6
|
const { config, manager } = dokployPreviewFromEnvironment(environment);
|
|
7
|
+
const dns = cloudflarePreviewDnsFromEnvironment(environment, Boolean(config.domain));
|
|
6
8
|
if (command === 'deploy') {
|
|
7
9
|
const prNumber = pullRequestNumber(required(environment, 'PR_NUMBER'));
|
|
8
10
|
await manager.deploy({
|
|
@@ -10,22 +12,51 @@ export async function runDokployPreviewCli(arguments_, environment = process.env
|
|
|
10
12
|
image: required(environment, 'PREVIEW_IMAGE'),
|
|
11
13
|
environment: ({ url }) => renderPreviewEnvironment(required(environment, 'PREVIEW_ENVIRONMENT'), prNumber, url),
|
|
12
14
|
...(config.registry ? { registry: config.registry } : {}),
|
|
15
|
+
...(dns
|
|
16
|
+
? {
|
|
17
|
+
configure: async ({ client, host, serverId }) => dns.upsert({
|
|
18
|
+
hostname: host,
|
|
19
|
+
originIp: await client.publicIp(serverId),
|
|
20
|
+
owner: `${config.applicationPrefix}-pr-${prNumber}`,
|
|
21
|
+
}),
|
|
22
|
+
}
|
|
23
|
+
: {}),
|
|
13
24
|
});
|
|
14
25
|
return;
|
|
15
26
|
}
|
|
16
27
|
if (command === 'delete') {
|
|
17
28
|
const prNumber = pullRequestNumber(required(environment, 'PR_NUMBER'));
|
|
18
|
-
|
|
29
|
+
const owner = `${config.applicationPrefix}-pr-${prNumber}`;
|
|
30
|
+
const hostname = config.domain ? `${config.subdomainPrefix}-${prNumber}.${config.domain}` : undefined;
|
|
31
|
+
console.log((await manager.delete(prNumber, dns && hostname ? () => dns.delete({ hostname, owner }) : undefined))
|
|
32
|
+
? `Deleted ${owner}`
|
|
33
|
+
: `No preview for pr-${prNumber}`);
|
|
19
34
|
return;
|
|
20
35
|
}
|
|
21
36
|
if (command === 'prune') {
|
|
22
37
|
const open = new Set((environment.OPEN_PR_NUMBERS ?? '').split(/\s+/).filter(Boolean).map(pullRequestNumber));
|
|
23
|
-
for (const prNumber of await manager.prune(open
|
|
38
|
+
for (const prNumber of await manager.prune(open, dns && config.domain
|
|
39
|
+
? (candidate) => dns.delete({
|
|
40
|
+
hostname: `${config.subdomainPrefix}-${candidate}.${config.domain}`,
|
|
41
|
+
owner: `${config.applicationPrefix}-pr-${candidate}`,
|
|
42
|
+
})
|
|
43
|
+
: undefined))
|
|
24
44
|
console.log(`Deleted ${config.applicationPrefix}-pr-${prNumber}`);
|
|
25
45
|
return;
|
|
26
46
|
}
|
|
27
47
|
throw new Error('usage: ras preview dokploy <deploy|delete|prune>');
|
|
28
48
|
}
|
|
49
|
+
function cloudflarePreviewDnsFromEnvironment(environment, customDomain) {
|
|
50
|
+
const apiToken = optional(environment, 'CLOUDFLARE_API_TOKEN');
|
|
51
|
+
const zoneId = optional(environment, 'CLOUDFLARE_ZONE_ID');
|
|
52
|
+
if (Boolean(apiToken) !== Boolean(zoneId))
|
|
53
|
+
throw new Error('CLOUDFLARE_API_TOKEN and CLOUDFLARE_ZONE_ID must be configured together');
|
|
54
|
+
if (!apiToken || !zoneId)
|
|
55
|
+
return undefined;
|
|
56
|
+
if (!customDomain)
|
|
57
|
+
throw new Error('Cloudflare preview DNS requires PREVIEW_DOMAIN');
|
|
58
|
+
return new CloudflarePreviewDns({ apiToken, zoneId });
|
|
59
|
+
}
|
|
29
60
|
export function renderPreviewEnvironment(template, prNumber, previewUrl) {
|
|
30
61
|
if (template.includes('{{PREVIEW_URL}}') && !previewUrl)
|
|
31
62
|
throw new Error('preview URL is required to render PREVIEW_ENVIRONMENT');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dokploy-cli.js","sourceRoot":"","sources":["../../src/preview/dokploy-cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,6BAA6B,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAE/E,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,UAAoB,EAAE,WAAW,GAAsB,OAAO,CAAC,GAAG;IAC3G,MAAM,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC,CAAA;IAC7B,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,6BAA6B,CAAC,WAAW,CAAC,CAAA;
|
|
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 { CloudflarePreviewDns } from './cloudflare.js'\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 const dns = cloudflarePreviewDnsFromEnvironment(environment, Boolean(config.domain))\n\n if (command === 'deploy') {\n const prNumber = pullRequestNumber(required(environment, 'PR_NUMBER'))\n await manager.deploy({\n prNumber,\n image: required(environment, 'PREVIEW_IMAGE'),\n environment: ({ url }) => renderPreviewEnvironment(required(environment, 'PREVIEW_ENVIRONMENT'), prNumber, url),\n ...(config.registry ? { registry: config.registry } : {}),\n ...(dns\n ? {\n configure: async ({ client, host, serverId }) =>\n dns.upsert({\n hostname: host,\n originIp: await client.publicIp(serverId),\n owner: `${config.applicationPrefix}-pr-${prNumber}`,\n }),\n }\n : {}),\n })\n return\n }\n if (command === 'delete') {\n const prNumber = pullRequestNumber(required(environment, 'PR_NUMBER'))\n const owner = `${config.applicationPrefix}-pr-${prNumber}`\n const hostname = config.domain ? `${config.subdomainPrefix}-${prNumber}.${config.domain}` : undefined\n console.log(\n (await manager.delete(prNumber, dns && hostname ? () => dns.delete({ hostname, owner }) : undefined))\n ? `Deleted ${owner}`\n : `No preview for pr-${prNumber}`,\n )\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(\n open,\n dns && config.domain\n ? (candidate) =>\n dns.delete({\n hostname: `${config.subdomainPrefix}-${candidate}.${config.domain}`,\n owner: `${config.applicationPrefix}-pr-${candidate}`,\n })\n : undefined,\n ))\n console.log(`Deleted ${config.applicationPrefix}-pr-${prNumber}`)\n return\n }\n throw new Error('usage: ras preview dokploy <deploy|delete|prune>')\n}\n\nfunction cloudflarePreviewDnsFromEnvironment(environment: NodeJS.ProcessEnv, customDomain: boolean) {\n const apiToken = optional(environment, 'CLOUDFLARE_API_TOKEN')\n const zoneId = optional(environment, 'CLOUDFLARE_ZONE_ID')\n if (Boolean(apiToken) !== Boolean(zoneId)) throw new Error('CLOUDFLARE_API_TOKEN and CLOUDFLARE_ZONE_ID must be configured together')\n if (!apiToken || !zoneId) return undefined\n if (!customDomain) throw new Error('Cloudflare preview DNS requires PREVIEW_DOMAIN')\n return new CloudflarePreviewDns({ apiToken, zoneId })\n}\n\nexport function renderPreviewEnvironment(template: string, prNumber: string, previewUrl?: string) {\n if (template.includes('{{PREVIEW_URL}}') && !previewUrl) throw new Error('preview URL is required to render PREVIEW_ENVIRONMENT')\n return template\n .replaceAll('{{PR_NUMBER}}', pullRequestNumber(prNumber))\n .replaceAll('{{PREVIEW_URL}}', previewUrl ?? '')\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"]}
|
|
@@ -10,6 +10,7 @@ export declare function dokployPreviewFromEnvironment(environment?: NodeJS.Proce
|
|
|
10
10
|
environmentId: string;
|
|
11
11
|
applicationPrefix: string;
|
|
12
12
|
domain: string | undefined;
|
|
13
|
+
subdomainPrefix: string;
|
|
13
14
|
port: number;
|
|
14
15
|
healthPath: string | undefined;
|
|
15
16
|
registry?: {
|
|
@@ -40,6 +41,7 @@ export declare class DokployClient {
|
|
|
40
41
|
applications(): Promise<DokployApplication[]>;
|
|
41
42
|
application(name: string): Promise<DokployApplication | undefined>;
|
|
42
43
|
generatedDomain(appName: string, serverId?: string, existing?: string[]): Promise<string>;
|
|
44
|
+
publicIp(serverId?: string): Promise<string>;
|
|
43
45
|
}
|
|
44
46
|
export type DokployPreviewOptions = {
|
|
45
47
|
client: DokployClient;
|
|
@@ -75,6 +77,7 @@ export type DeployPreviewOptions = {
|
|
|
75
77
|
client: DokployClient;
|
|
76
78
|
host: string;
|
|
77
79
|
url: string;
|
|
80
|
+
serverId?: string;
|
|
78
81
|
}) => void | Promise<void>;
|
|
79
82
|
};
|
|
80
83
|
export declare class DokployPreviewManager {
|
package/dist/preview/dokploy.js
CHANGED
|
@@ -13,14 +13,18 @@ export function dokployPreviewFromEnvironment(environment = process.env) {
|
|
|
13
13
|
if (!/^[a-z\d](?:[a-z\d-]*[a-z\d])?$/.test(applicationPrefix))
|
|
14
14
|
throw new Error('PREVIEW_APPLICATION_PREFIX must be a slug');
|
|
15
15
|
const domain = optionalEnvironment(environment, 'PREVIEW_DOMAIN');
|
|
16
|
+
const subdomainPrefix = optionalEnvironment(environment, 'PREVIEW_SUBDOMAIN_PREFIX') ?? 'pr';
|
|
17
|
+
if (!/^[a-z\d](?:[a-z\d-]*[a-z\d])?$/.test(subdomainPrefix))
|
|
18
|
+
throw new Error('PREVIEW_SUBDOMAIN_PREFIX must be a slug');
|
|
16
19
|
if (domain)
|
|
17
|
-
previewHostname(
|
|
20
|
+
previewHostname(`${subdomainPrefix}-1.${domain}`);
|
|
18
21
|
const config = {
|
|
19
22
|
url: requiredEnvironment(environment, 'DOKPLOY_URL'),
|
|
20
23
|
apiKey: requiredEnvironment(environment, 'DOKPLOY_API_KEY'),
|
|
21
24
|
environmentId: requiredEnvironment(environment, 'DOKPLOY_ENVIRONMENT_ID'),
|
|
22
25
|
applicationPrefix,
|
|
23
26
|
domain,
|
|
27
|
+
subdomainPrefix,
|
|
24
28
|
port,
|
|
25
29
|
healthPath: optionalEnvironment(environment, 'PREVIEW_HEALTH_PATH'),
|
|
26
30
|
...(username && password ? { registry: { username, password } } : {}),
|
|
@@ -31,7 +35,7 @@ export function dokployPreviewFromEnvironment(environment = process.env) {
|
|
|
31
35
|
manager: new DokployPreviewManager({
|
|
32
36
|
client,
|
|
33
37
|
applicationName: (prNumber) => `${applicationPrefix}-pr-${prNumber}`,
|
|
34
|
-
...(domain ? { hostname: (prNumber) =>
|
|
38
|
+
...(domain ? { hostname: (prNumber) => `${subdomainPrefix}-${prNumber}.${domain}` } : {}),
|
|
35
39
|
port,
|
|
36
40
|
...(config.healthPath ? { healthPath: config.healthPath } : {}),
|
|
37
41
|
...(githubOutput ? { onResolved: ({ url }) => appendFileSync(githubOutput, `preview-url=${url}\n`) } : {}),
|
|
@@ -88,10 +92,7 @@ export class DokployClient {
|
|
|
88
92
|
return (await this.applications()).find((application) => application.name === name);
|
|
89
93
|
}
|
|
90
94
|
async generatedDomain(appName, serverId, existing = []) {
|
|
91
|
-
const
|
|
92
|
-
? await this.api('domain.canGenerateTraefikMeDomains', { query: { serverId } })
|
|
93
|
-
: await this.api('settings.getIp');
|
|
94
|
-
const publicIp = previewServerIp(serverIp);
|
|
95
|
+
const publicIp = await this.publicIp(serverId);
|
|
95
96
|
const current = existing.find((host) => isCurrentGeneratedPreviewHostname(host, appName, publicIp));
|
|
96
97
|
if (current)
|
|
97
98
|
return current;
|
|
@@ -102,6 +103,12 @@ export class DokployClient {
|
|
|
102
103
|
throw new Error('domain.generateDomain returned an invalid hostname');
|
|
103
104
|
return generatedPreviewHostname(generated, publicIp, appName);
|
|
104
105
|
}
|
|
106
|
+
async publicIp(serverId) {
|
|
107
|
+
const serverIp = serverId
|
|
108
|
+
? await this.api('domain.canGenerateTraefikMeDomains', { query: { serverId } })
|
|
109
|
+
: await this.api('settings.getIp');
|
|
110
|
+
return previewServerIp(serverIp);
|
|
111
|
+
}
|
|
105
112
|
}
|
|
106
113
|
export class DokployPreviewManager {
|
|
107
114
|
options;
|
|
@@ -150,7 +157,14 @@ export class DokployPreviewManager {
|
|
|
150
157
|
});
|
|
151
158
|
}
|
|
152
159
|
await this.options.onResolved?.({ host, url });
|
|
153
|
-
|
|
160
|
+
const serverId = details?.serverId || application.serverId || undefined;
|
|
161
|
+
await options.configure?.({
|
|
162
|
+
applicationId,
|
|
163
|
+
client: this.options.client,
|
|
164
|
+
host,
|
|
165
|
+
url,
|
|
166
|
+
...(serverId ? { serverId } : {}),
|
|
167
|
+
});
|
|
154
168
|
await this.options.client.api('application.saveDockerProvider', {
|
|
155
169
|
body: {
|
|
156
170
|
applicationId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dokploy.js","sourceRoot":"","sources":["../../src/preview/dokploy.ts"],"names":[],"mappings":"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","sourcesContent":["import { appendFileSync } from 'node:fs'\nimport { BlockList, isIP } from 'node:net'\n\nexport type DokployApplication = { applicationId: string; name: string; serverId?: string | null }\n\nexport function dokployPreviewFromEnvironment(environment: NodeJS.ProcessEnv = process.env) {\n const githubOutput = optionalEnvironment(environment, 'GITHUB_OUTPUT')\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 = optionalEnvironment(environment, 'PREVIEW_DOMAIN')\n if (domain) 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 ...(domain ? { hostname: (prNumber: string) => `pr-${prNumber}.${domain}` } : {}),\n port,\n ...(config.healthPath ? { healthPath: config.healthPath } : {}),\n ...(githubOutput ? { onResolved: ({ url }: { url: string }) => appendFileSync(githubOutput, `preview-url=${url}\\n`) } : {}),\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 async generatedDomain(appName: string, serverId?: string, existing: string[] = []) {\n const serverIp = serverId\n ? await this.api('domain.canGenerateTraefikMeDomains', { query: { serverId } })\n : await this.api('settings.getIp')\n const publicIp = previewServerIp(serverIp)\n const current = existing.find((host) => isCurrentGeneratedPreviewHostname(host, appName, publicIp))\n if (current) return current\n const generated = await this.api('domain.generateDomain', {\n body: { appName, ...(serverId ? { serverId } : {}) },\n })\n if (typeof generated !== 'string') throw new Error('domain.generateDomain returned an invalid hostname')\n return generatedPreviewHostname(generated, publicIp, appName)\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 onResolved?: (context: { host: string; url: string }) => void | Promise<void>\n}\n\nexport type DeployPreviewOptions = {\n prNumber: string\n image: string\n environment: string | ((context: { host: string; url: string }) => string)\n registry?: { username: string; password: string }\n configure?: (context: { applicationId: string; client: DokployClient; host: string; url: 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 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<\n { domains?: { domainId?: string; host: string }[]; serverId?: string | null } | undefined\n >('application.one', { query: { applicationId } })\n const generated = !this.options.hostname\n const managedDomains = details?.domains?.filter((domain) => isGeneratedPreviewHostname(domain.host, generatedPreviewName)) ?? []\n const host = previewHostname(\n this.options.hostname\n ? this.options.hostname(prNumber)\n : await this.options.client.generatedDomain(\n generatedPreviewName,\n details?.serverId ?? application.serverId ?? undefined,\n managedDomains.map((domain) => domain.host),\n ),\n )\n const url = `${generated ? 'http' : 'https'}://${host}`\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: !generated,\n certificateType: generated ? 'none' : 'letsencrypt',\n domainType: 'application',\n },\n })\n }\n await this.options.onResolved?.({ host, url })\n await options.configure?.({ applicationId, client: this.options.client, host, url })\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: {\n applicationId,\n env: typeof options.environment === 'function' ? options.environment({ host, url }) : options.environment,\n buildArgs: null,\n buildSecrets: null,\n createEnvFile: false,\n },\n })\n await this.options.client.api('application.deploy', { body: { applicationId } })\n await this.waitForDeployment(applicationId)\n await this.waitForHealth(new URL(this.options.healthPath ?? '/api/health', url).toString())\n for (const domain of managedDomains.filter((candidate) => candidate.host !== host)) {\n if (!domain.domainId) throw new Error(`Dokploy did not report an ID for generated domain ${domain.host}`)\n // oxlint-disable-next-line no-await-in-loop\n await this.options.client.api('domain.delete', { body: { domainId: domain.domainId } })\n }\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 generatedPreviewHostname(value: string, serverIp: string, appName: string) {\n const host = previewHostname(value)\n if (!isCurrentGeneratedPreviewHostname(host, appName, serverIp))\n throw new Error('domain.generateDomain did not return an sslip.io hostname for the Dokploy server')\n return host\n}\n\nfunction isGeneratedPreviewHostname(host: string, appName: string) {\n return new RegExp(`^${regularExpressionLiteral(appName.slice(0, 40))}-[a-f\\\\d]{6}-[a-f\\\\d-]+\\\\.sslip\\\\.io$`).test(host)\n}\n\nfunction isCurrentGeneratedPreviewHostname(host: string, appName: string, serverIp: string) {\n return new RegExp(\n `^${regularExpressionLiteral(appName.slice(0, 40))}-[a-f\\\\d]{6}-${regularExpressionLiteral(encodedIp(serverIp))}\\\\.sslip\\\\.io$`,\n ).test(host)\n}\n\nfunction regularExpressionLiteral(value: string) {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\nfunction encodedIp(address: string) {\n return address.replaceAll('.', '-').replaceAll(':', '-')\n}\n\nconst generatedPreviewName = 'ras-preview'\n\nconst blockedPreviewAddresses = new BlockList()\nfor (const [address, prefix, family] of [\n ['0.0.0.0', 8, 'ipv4'],\n ['10.0.0.0', 8, 'ipv4'],\n ['100.64.0.0', 10, 'ipv4'],\n ['127.0.0.0', 8, 'ipv4'],\n ['169.254.0.0', 16, 'ipv4'],\n ['172.16.0.0', 12, 'ipv4'],\n ['192.0.0.0', 24, 'ipv4'],\n ['192.0.2.0', 24, 'ipv4'],\n ['192.88.99.0', 24, 'ipv4'],\n ['192.168.0.0', 16, 'ipv4'],\n ['198.18.0.0', 15, 'ipv4'],\n ['198.51.100.0', 24, 'ipv4'],\n ['203.0.113.0', 24, 'ipv4'],\n ['224.0.0.0', 4, 'ipv4'],\n ['240.0.0.0', 4, 'ipv4'],\n ['::', 128, 'ipv6'],\n ['::1', 128, 'ipv6'],\n ['64:ff9b:1::', 48, 'ipv6'],\n ['100::', 64, 'ipv6'],\n ['100:0:0:1::', 64, 'ipv6'],\n ['2001::', 23, 'ipv6'],\n ['2001:db8::', 32, 'ipv6'],\n ['2002::', 16, 'ipv6'],\n ['3fff::', 20, 'ipv6'],\n ['5f00::', 16, 'ipv6'],\n ['fc00::', 7, 'ipv6'],\n ['fe80::', 10, 'ipv6'],\n ['ff00::', 8, 'ipv6'],\n] as const) {\n blockedPreviewAddresses.addSubnet(address, prefix, family)\n}\n\nfunction previewServerIp(value: unknown) {\n if (typeof value !== 'string') throw new Error('Dokploy requires a public server IP before it can generate preview domains')\n const address = value.trim().toLowerCase()\n const version = isIP(address)\n const family = version === 4 ? 'ipv4' : version === 6 ? 'ipv6' : undefined\n if (!family || address.startsWith('::ffff:') || blockedPreviewAddresses.check(address, family)) {\n throw new Error('Dokploy requires a public server IP before it can generate preview domains')\n }\n return address\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"]}
|
|
1
|
+
{"version":3,"file":"dokploy.js","sourceRoot":"","sources":["../../src/preview/dokploy.ts"],"names":[],"mappings":"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","sourcesContent":["import { appendFileSync } from 'node:fs'\nimport { BlockList, isIP } from 'node:net'\n\nexport type DokployApplication = { applicationId: string; name: string; serverId?: string | null }\n\nexport function dokployPreviewFromEnvironment(environment: NodeJS.ProcessEnv = process.env) {\n const githubOutput = optionalEnvironment(environment, 'GITHUB_OUTPUT')\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 = optionalEnvironment(environment, 'PREVIEW_DOMAIN')\n const subdomainPrefix = optionalEnvironment(environment, 'PREVIEW_SUBDOMAIN_PREFIX') ?? 'pr'\n if (!/^[a-z\\d](?:[a-z\\d-]*[a-z\\d])?$/.test(subdomainPrefix)) throw new Error('PREVIEW_SUBDOMAIN_PREFIX must be a slug')\n if (domain) previewHostname(`${subdomainPrefix}-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 subdomainPrefix,\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 ...(domain ? { hostname: (prNumber: string) => `${subdomainPrefix}-${prNumber}.${domain}` } : {}),\n port,\n ...(config.healthPath ? { healthPath: config.healthPath } : {}),\n ...(githubOutput ? { onResolved: ({ url }: { url: string }) => appendFileSync(githubOutput, `preview-url=${url}\\n`) } : {}),\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 async generatedDomain(appName: string, serverId?: string, existing: string[] = []) {\n const publicIp = await this.publicIp(serverId)\n const current = existing.find((host) => isCurrentGeneratedPreviewHostname(host, appName, publicIp))\n if (current) return current\n const generated = await this.api('domain.generateDomain', {\n body: { appName, ...(serverId ? { serverId } : {}) },\n })\n if (typeof generated !== 'string') throw new Error('domain.generateDomain returned an invalid hostname')\n return generatedPreviewHostname(generated, publicIp, appName)\n }\n\n async publicIp(serverId?: string) {\n const serverIp = serverId\n ? await this.api('domain.canGenerateTraefikMeDomains', { query: { serverId } })\n : await this.api('settings.getIp')\n return previewServerIp(serverIp)\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 onResolved?: (context: { host: string; url: string }) => void | Promise<void>\n}\n\nexport type DeployPreviewOptions = {\n prNumber: string\n image: string\n environment: string | ((context: { host: string; url: string }) => string)\n registry?: { username: string; password: string }\n configure?: (context: {\n applicationId: string\n client: DokployClient\n host: string\n url: string\n serverId?: string\n }) => 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 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<\n { domains?: { domainId?: string; host: string }[]; serverId?: string | null } | undefined\n >('application.one', { query: { applicationId } })\n const generated = !this.options.hostname\n const managedDomains = details?.domains?.filter((domain) => isGeneratedPreviewHostname(domain.host, generatedPreviewName)) ?? []\n const host = previewHostname(\n this.options.hostname\n ? this.options.hostname(prNumber)\n : await this.options.client.generatedDomain(\n generatedPreviewName,\n details?.serverId ?? application.serverId ?? undefined,\n managedDomains.map((domain) => domain.host),\n ),\n )\n const url = `${generated ? 'http' : 'https'}://${host}`\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: !generated,\n certificateType: generated ? 'none' : 'letsencrypt',\n domainType: 'application',\n },\n })\n }\n await this.options.onResolved?.({ host, url })\n const serverId = details?.serverId || application.serverId || undefined\n await options.configure?.({\n applicationId,\n client: this.options.client,\n host,\n url,\n ...(serverId ? { serverId } : {}),\n })\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: {\n applicationId,\n env: typeof options.environment === 'function' ? options.environment({ host, url }) : options.environment,\n buildArgs: null,\n buildSecrets: null,\n createEnvFile: false,\n },\n })\n await this.options.client.api('application.deploy', { body: { applicationId } })\n await this.waitForDeployment(applicationId)\n await this.waitForHealth(new URL(this.options.healthPath ?? '/api/health', url).toString())\n for (const domain of managedDomains.filter((candidate) => candidate.host !== host)) {\n if (!domain.domainId) throw new Error(`Dokploy did not report an ID for generated domain ${domain.host}`)\n // oxlint-disable-next-line no-await-in-loop\n await this.options.client.api('domain.delete', { body: { domainId: domain.domainId } })\n }\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 generatedPreviewHostname(value: string, serverIp: string, appName: string) {\n const host = previewHostname(value)\n if (!isCurrentGeneratedPreviewHostname(host, appName, serverIp))\n throw new Error('domain.generateDomain did not return an sslip.io hostname for the Dokploy server')\n return host\n}\n\nfunction isGeneratedPreviewHostname(host: string, appName: string) {\n return new RegExp(`^${regularExpressionLiteral(appName.slice(0, 40))}-[a-f\\\\d]{6}-[a-f\\\\d-]+\\\\.sslip\\\\.io$`).test(host)\n}\n\nfunction isCurrentGeneratedPreviewHostname(host: string, appName: string, serverIp: string) {\n return new RegExp(\n `^${regularExpressionLiteral(appName.slice(0, 40))}-[a-f\\\\d]{6}-${regularExpressionLiteral(encodedIp(serverIp))}\\\\.sslip\\\\.io$`,\n ).test(host)\n}\n\nfunction regularExpressionLiteral(value: string) {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\nfunction encodedIp(address: string) {\n return address.replaceAll('.', '-').replaceAll(':', '-')\n}\n\nconst generatedPreviewName = 'ras-preview'\n\nconst blockedPreviewAddresses = new BlockList()\nfor (const [address, prefix, family] of [\n ['0.0.0.0', 8, 'ipv4'],\n ['10.0.0.0', 8, 'ipv4'],\n ['100.64.0.0', 10, 'ipv4'],\n ['127.0.0.0', 8, 'ipv4'],\n ['169.254.0.0', 16, 'ipv4'],\n ['172.16.0.0', 12, 'ipv4'],\n ['192.0.0.0', 24, 'ipv4'],\n ['192.0.2.0', 24, 'ipv4'],\n ['192.88.99.0', 24, 'ipv4'],\n ['192.168.0.0', 16, 'ipv4'],\n ['198.18.0.0', 15, 'ipv4'],\n ['198.51.100.0', 24, 'ipv4'],\n ['203.0.113.0', 24, 'ipv4'],\n ['224.0.0.0', 4, 'ipv4'],\n ['240.0.0.0', 4, 'ipv4'],\n ['::', 128, 'ipv6'],\n ['::1', 128, 'ipv6'],\n ['64:ff9b:1::', 48, 'ipv6'],\n ['100::', 64, 'ipv6'],\n ['100:0:0:1::', 64, 'ipv6'],\n ['2001::', 23, 'ipv6'],\n ['2001:db8::', 32, 'ipv6'],\n ['2002::', 16, 'ipv6'],\n ['3fff::', 20, 'ipv6'],\n ['5f00::', 16, 'ipv6'],\n ['fc00::', 7, 'ipv6'],\n ['fe80::', 10, 'ipv6'],\n ['ff00::', 8, 'ipv6'],\n] as const) {\n blockedPreviewAddresses.addSubnet(address, prefix, family)\n}\n\nfunction previewServerIp(value: unknown) {\n if (typeof value !== 'string') throw new Error('Dokploy requires a public server IP before it can generate preview domains')\n const address = value.trim().toLowerCase()\n const version = isIP(address)\n const family = version === 4 ? 'ipv4' : version === 6 ? 'ipv6' : undefined\n if (!family || address.startsWith('::ffff:') || blockedPreviewAddresses.check(address, family)) {\n throw new Error('Dokploy requires a public server IP before it can generate preview domains')\n }\n return address\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"]}
|