ras-stack 0.40.0 β 0.42.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 +3 -3
- package/dist/cli.js +0 -0
- package/dist/preview/dokploy-cli.d.ts +1 -1
- package/dist/preview/dokploy-cli.js +6 -6
- package/dist/preview/dokploy-cli.js.map +1 -1
- package/dist/preview/dokploy.d.ts +13 -3
- package/dist/preview/dokploy.js +109 -9
- package/dist/preview/dokploy.js.map +1 -1
- package/dist/preview/environment.d.ts +14 -2
- package/dist/preview/environment.js +13 -9
- package/dist/preview/environment.js.map +1 -1
- package/dist/preview/github.js +10 -6
- package/dist/preview/github.js.map +1 -1
- package/examples/full-stack/e2e/full-stack.spec.ts +9 -0
- package/examples/full-stack/pnpm-workspace.template.yaml +1 -0
- package/examples/full-stack/src/routes/index.tsx +4 -2
- package/package.json +19 -16
package/README.md
CHANGED
|
@@ -90,10 +90,10 @@ Start with the narrowest public entrypoint that owns the repeated mechanic:
|
|
|
90
90
|
To start from the production reference instead of assembling entrypoints individually:
|
|
91
91
|
|
|
92
92
|
```sh
|
|
93
|
-
pnpm
|
|
93
|
+
pnpm create ras-app my-app
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
-
The scaffold includes Better Auth, checked-in migrations, durable uploads, SMTP flows, a transactional realtime outbox, production health/lifecycle behavior, and its tests. It remains ordinary application code rather than a second framework API.
|
|
96
|
+
The `create-ras-app` command delegates to the same scaffold implementation available through `pnpm dlx ras-stack create my-app`. The scaffold includes Better Auth, checked-in migrations, durable uploads, SMTP flows, a transactional realtime outbox, production health/lifecycle behavior, and its tests. It remains ordinary application code rather than a second framework API.
|
|
97
97
|
|
|
98
98
|
## Dokploy previews π
|
|
99
99
|
|
|
@@ -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,
|
|
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.
|
|
107
107
|
|
|
108
108
|
## Guides π
|
|
109
109
|
|
package/dist/cli.js
CHANGED
|
File without changes
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export declare function runDokployPreviewCli(arguments_: string[], environment?: NodeJS.ProcessEnv): Promise<void>;
|
|
2
|
-
export declare function renderPreviewEnvironment(template: string, prNumber: string): string;
|
|
2
|
+
export declare function renderPreviewEnvironment(template: string, prNumber: string, previewUrl?: string): string;
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
|
-
import { appendFileSync } from 'node:fs';
|
|
3
2
|
import { dokployPreviewFromEnvironment, pullRequestNumber } from './dokploy.js';
|
|
4
3
|
export async function runDokployPreviewCli(arguments_, environment = process.env) {
|
|
5
4
|
const command = arguments_[0];
|
|
6
5
|
const { config, manager } = dokployPreviewFromEnvironment(environment);
|
|
7
6
|
if (command === 'deploy') {
|
|
8
7
|
const prNumber = pullRequestNumber(required(environment, 'PR_NUMBER'));
|
|
9
|
-
|
|
8
|
+
await manager.deploy({
|
|
10
9
|
prNumber,
|
|
11
10
|
image: required(environment, 'PREVIEW_IMAGE'),
|
|
12
|
-
environment: renderPreviewEnvironment(required(environment, 'PREVIEW_ENVIRONMENT'), prNumber),
|
|
11
|
+
environment: ({ url }) => renderPreviewEnvironment(required(environment, 'PREVIEW_ENVIRONMENT'), prNumber, url),
|
|
13
12
|
...(config.registry ? { registry: config.registry } : {}),
|
|
14
13
|
});
|
|
15
|
-
if (environment.GITHUB_OUTPUT)
|
|
16
|
-
appendFileSync(environment.GITHUB_OUTPUT, `preview-url=${preview.url}\n`);
|
|
17
14
|
return;
|
|
18
15
|
}
|
|
19
16
|
if (command === 'delete') {
|
|
@@ -29,9 +26,12 @@ export async function runDokployPreviewCli(arguments_, environment = process.env
|
|
|
29
26
|
}
|
|
30
27
|
throw new Error('usage: ras preview dokploy <deploy|delete|prune>');
|
|
31
28
|
}
|
|
32
|
-
export function renderPreviewEnvironment(template, prNumber) {
|
|
29
|
+
export function renderPreviewEnvironment(template, prNumber, previewUrl) {
|
|
30
|
+
if (template.includes('{{PREVIEW_URL}}') && !previewUrl)
|
|
31
|
+
throw new Error('preview URL is required to render PREVIEW_ENVIRONMENT');
|
|
33
32
|
return template
|
|
34
33
|
.replaceAll('{{PR_NUMBER}}', pullRequestNumber(prNumber))
|
|
34
|
+
.replaceAll('{{PREVIEW_URL}}', previewUrl ?? '')
|
|
35
35
|
.replaceAll('{{RANDOM_HEX_32}}', () => randomBytes(32).toString('hex'));
|
|
36
36
|
}
|
|
37
37
|
function required(environment, name) {
|
|
@@ -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,
|
|
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 { 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 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 })\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, 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"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export type DokployApplication = {
|
|
2
2
|
applicationId: string;
|
|
3
3
|
name: string;
|
|
4
|
+
serverId?: string | null;
|
|
4
5
|
};
|
|
5
6
|
export declare function dokployPreviewFromEnvironment(environment?: NodeJS.ProcessEnv): {
|
|
6
7
|
config: {
|
|
@@ -8,7 +9,7 @@ export declare function dokployPreviewFromEnvironment(environment?: NodeJS.Proce
|
|
|
8
9
|
apiKey: string;
|
|
9
10
|
environmentId: string;
|
|
10
11
|
applicationPrefix: string;
|
|
11
|
-
domain: string;
|
|
12
|
+
domain: string | undefined;
|
|
12
13
|
port: number;
|
|
13
14
|
healthPath: string | undefined;
|
|
14
15
|
registry?: {
|
|
@@ -38,11 +39,12 @@ export declare class DokployClient {
|
|
|
38
39
|
}): Promise<T>;
|
|
39
40
|
applications(): Promise<DokployApplication[]>;
|
|
40
41
|
application(name: string): Promise<DokployApplication | undefined>;
|
|
42
|
+
generatedDomain(appName: string, serverId?: string, existing?: string[]): Promise<string>;
|
|
41
43
|
}
|
|
42
44
|
export type DokployPreviewOptions = {
|
|
43
45
|
client: DokployClient;
|
|
44
46
|
applicationName: (prNumber: string) => string;
|
|
45
|
-
hostname
|
|
47
|
+
hostname?: (prNumber: string) => string;
|
|
46
48
|
port: number;
|
|
47
49
|
healthPath?: string;
|
|
48
50
|
deploymentTimeoutMs?: number;
|
|
@@ -52,11 +54,18 @@ export type DokployPreviewOptions = {
|
|
|
52
54
|
sleep?: (milliseconds: number) => Promise<void>;
|
|
53
55
|
now?: () => number;
|
|
54
56
|
log?: (message: string) => void;
|
|
57
|
+
onResolved?: (context: {
|
|
58
|
+
host: string;
|
|
59
|
+
url: string;
|
|
60
|
+
}) => void | Promise<void>;
|
|
55
61
|
};
|
|
56
62
|
export type DeployPreviewOptions = {
|
|
57
63
|
prNumber: string;
|
|
58
64
|
image: string;
|
|
59
|
-
environment: string
|
|
65
|
+
environment: string | ((context: {
|
|
66
|
+
host: string;
|
|
67
|
+
url: string;
|
|
68
|
+
}) => string);
|
|
60
69
|
registry?: {
|
|
61
70
|
username: string;
|
|
62
71
|
password: string;
|
|
@@ -65,6 +74,7 @@ export type DeployPreviewOptions = {
|
|
|
65
74
|
applicationId: string;
|
|
66
75
|
client: DokployClient;
|
|
67
76
|
host: string;
|
|
77
|
+
url: string;
|
|
68
78
|
}) => void | Promise<void>;
|
|
69
79
|
};
|
|
70
80
|
export declare class DokployPreviewManager {
|
package/dist/preview/dokploy.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { appendFileSync } from 'node:fs';
|
|
2
|
+
import { BlockList, isIP } from 'node:net';
|
|
1
3
|
export function dokployPreviewFromEnvironment(environment = process.env) {
|
|
4
|
+
const githubOutput = optionalEnvironment(environment, 'GITHUB_OUTPUT');
|
|
2
5
|
const username = optionalEnvironment(environment, 'PREVIEW_REGISTRY_USERNAME');
|
|
3
6
|
const password = optionalEnvironment(environment, 'PREVIEW_REGISTRY_PASSWORD');
|
|
4
7
|
if (Boolean(username) !== Boolean(password))
|
|
@@ -9,8 +12,9 @@ export function dokployPreviewFromEnvironment(environment = process.env) {
|
|
|
9
12
|
const applicationPrefix = requiredEnvironment(environment, 'PREVIEW_APPLICATION_PREFIX');
|
|
10
13
|
if (!/^[a-z\d](?:[a-z\d-]*[a-z\d])?$/.test(applicationPrefix))
|
|
11
14
|
throw new Error('PREVIEW_APPLICATION_PREFIX must be a slug');
|
|
12
|
-
const domain =
|
|
13
|
-
|
|
15
|
+
const domain = optionalEnvironment(environment, 'PREVIEW_DOMAIN');
|
|
16
|
+
if (domain)
|
|
17
|
+
previewHostname(`pr-1.${domain}`);
|
|
14
18
|
const config = {
|
|
15
19
|
url: requiredEnvironment(environment, 'DOKPLOY_URL'),
|
|
16
20
|
apiKey: requiredEnvironment(environment, 'DOKPLOY_API_KEY'),
|
|
@@ -27,9 +31,10 @@ export function dokployPreviewFromEnvironment(environment = process.env) {
|
|
|
27
31
|
manager: new DokployPreviewManager({
|
|
28
32
|
client,
|
|
29
33
|
applicationName: (prNumber) => `${applicationPrefix}-pr-${prNumber}`,
|
|
30
|
-
hostname: (prNumber) => `pr-${prNumber}.${domain}
|
|
34
|
+
...(domain ? { hostname: (prNumber) => `pr-${prNumber}.${domain}` } : {}),
|
|
31
35
|
port,
|
|
32
36
|
...(config.healthPath ? { healthPath: config.healthPath } : {}),
|
|
37
|
+
...(githubOutput ? { onResolved: ({ url }) => appendFileSync(githubOutput, `preview-url=${url}\n`) } : {}),
|
|
33
38
|
}),
|
|
34
39
|
};
|
|
35
40
|
}
|
|
@@ -82,6 +87,21 @@ export class DokployClient {
|
|
|
82
87
|
async application(name) {
|
|
83
88
|
return (await this.applications()).find((application) => application.name === name);
|
|
84
89
|
}
|
|
90
|
+
async generatedDomain(appName, serverId, existing = []) {
|
|
91
|
+
const serverIp = await this.api('domain.canGenerateTraefikMeDomains', {
|
|
92
|
+
query: { serverId: serverId ?? '' },
|
|
93
|
+
});
|
|
94
|
+
const publicIp = previewServerIp(serverIp);
|
|
95
|
+
const current = existing.find((host) => isCurrentGeneratedPreviewHostname(host, appName, publicIp));
|
|
96
|
+
if (current)
|
|
97
|
+
return current;
|
|
98
|
+
const generated = await this.api('domain.generateDomain', {
|
|
99
|
+
body: { appName, ...(serverId ? { serverId } : {}) },
|
|
100
|
+
});
|
|
101
|
+
if (typeof generated !== 'string')
|
|
102
|
+
throw new Error('domain.generateDomain returned an invalid hostname');
|
|
103
|
+
return generatedPreviewHostname(generated, publicIp, appName);
|
|
104
|
+
}
|
|
85
105
|
}
|
|
86
106
|
export class DokployPreviewManager {
|
|
87
107
|
options;
|
|
@@ -99,7 +119,6 @@ export class DokployPreviewManager {
|
|
|
99
119
|
async deploy(options) {
|
|
100
120
|
const prNumber = pullRequestNumber(options.prNumber);
|
|
101
121
|
const name = this.options.applicationName(prNumber);
|
|
102
|
-
const host = previewHostname(this.options.hostname(prNumber));
|
|
103
122
|
let application = await this.options.client.application(name);
|
|
104
123
|
if (!application) {
|
|
105
124
|
await this.options.client.api('application.create', {
|
|
@@ -113,6 +132,12 @@ export class DokployPreviewManager {
|
|
|
113
132
|
const details = await this.options.client.api('application.one', {
|
|
114
133
|
query: { applicationId },
|
|
115
134
|
});
|
|
135
|
+
const generated = !this.options.hostname;
|
|
136
|
+
const managedDomains = details?.domains?.filter((domain) => isGeneratedPreviewHostname(domain.host, generatedPreviewName)) ?? [];
|
|
137
|
+
const host = previewHostname(this.options.hostname
|
|
138
|
+
? this.options.hostname(prNumber)
|
|
139
|
+
: await this.options.client.generatedDomain(generatedPreviewName, application.serverId ?? undefined, managedDomains.map((domain) => domain.host)));
|
|
140
|
+
const url = `${generated ? 'http' : 'https'}://${host}`;
|
|
116
141
|
if (!details?.domains?.some((domain) => domain.host === host)) {
|
|
117
142
|
await this.options.client.api('domain.create', {
|
|
118
143
|
body: {
|
|
@@ -120,13 +145,14 @@ export class DokployPreviewManager {
|
|
|
120
145
|
host,
|
|
121
146
|
path: '/',
|
|
122
147
|
port: this.options.port,
|
|
123
|
-
https:
|
|
124
|
-
certificateType: 'letsencrypt',
|
|
148
|
+
https: !generated,
|
|
149
|
+
certificateType: generated ? 'none' : 'letsencrypt',
|
|
125
150
|
domainType: 'application',
|
|
126
151
|
},
|
|
127
152
|
});
|
|
128
153
|
}
|
|
129
|
-
await options.
|
|
154
|
+
await this.options.onResolved?.({ host, url });
|
|
155
|
+
await options.configure?.({ applicationId, client: this.options.client, host, url });
|
|
130
156
|
await this.options.client.api('application.saveDockerProvider', {
|
|
131
157
|
body: {
|
|
132
158
|
applicationId,
|
|
@@ -137,12 +163,23 @@ export class DokployPreviewManager {
|
|
|
137
163
|
},
|
|
138
164
|
});
|
|
139
165
|
await this.options.client.api('application.saveEnvironment', {
|
|
140
|
-
body: {
|
|
166
|
+
body: {
|
|
167
|
+
applicationId,
|
|
168
|
+
env: typeof options.environment === 'function' ? options.environment({ host, url }) : options.environment,
|
|
169
|
+
buildArgs: null,
|
|
170
|
+
buildSecrets: null,
|
|
171
|
+
createEnvFile: false,
|
|
172
|
+
},
|
|
141
173
|
});
|
|
142
174
|
await this.options.client.api('application.deploy', { body: { applicationId } });
|
|
143
175
|
await this.waitForDeployment(applicationId);
|
|
144
|
-
const url = `https://${host}`;
|
|
145
176
|
await this.waitForHealth(new URL(this.options.healthPath ?? '/api/health', url).toString());
|
|
177
|
+
for (const domain of managedDomains.filter((candidate) => candidate.host !== host)) {
|
|
178
|
+
if (!domain.domainId)
|
|
179
|
+
throw new Error(`Dokploy did not report an ID for generated domain ${domain.host}`);
|
|
180
|
+
// oxlint-disable-next-line no-await-in-loop
|
|
181
|
+
await this.options.client.api('domain.delete', { body: { domainId: domain.domainId } });
|
|
182
|
+
}
|
|
146
183
|
this.log(`Preview ready at ${url}`);
|
|
147
184
|
return { applicationId, host, url };
|
|
148
185
|
}
|
|
@@ -231,6 +268,69 @@ export function previewHostname(value) {
|
|
|
231
268
|
}
|
|
232
269
|
return value;
|
|
233
270
|
}
|
|
271
|
+
function generatedPreviewHostname(value, serverIp, appName) {
|
|
272
|
+
const host = previewHostname(value);
|
|
273
|
+
if (!isCurrentGeneratedPreviewHostname(host, appName, serverIp))
|
|
274
|
+
throw new Error('domain.generateDomain did not return an sslip.io hostname for the Dokploy server');
|
|
275
|
+
return host;
|
|
276
|
+
}
|
|
277
|
+
function isGeneratedPreviewHostname(host, appName) {
|
|
278
|
+
return new RegExp(`^${regularExpressionLiteral(appName.slice(0, 40))}-[a-f\\d]{6}-[a-f\\d-]+\\.sslip\\.io$`).test(host);
|
|
279
|
+
}
|
|
280
|
+
function isCurrentGeneratedPreviewHostname(host, appName, serverIp) {
|
|
281
|
+
return new RegExp(`^${regularExpressionLiteral(appName.slice(0, 40))}-[a-f\\d]{6}-${regularExpressionLiteral(encodedIp(serverIp))}\\.sslip\\.io$`).test(host);
|
|
282
|
+
}
|
|
283
|
+
function regularExpressionLiteral(value) {
|
|
284
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
285
|
+
}
|
|
286
|
+
function encodedIp(address) {
|
|
287
|
+
return address.replaceAll('.', '-').replaceAll(':', '-');
|
|
288
|
+
}
|
|
289
|
+
const generatedPreviewName = 'ras-preview';
|
|
290
|
+
const blockedPreviewAddresses = new BlockList();
|
|
291
|
+
for (const [address, prefix, family] of [
|
|
292
|
+
['0.0.0.0', 8, 'ipv4'],
|
|
293
|
+
['10.0.0.0', 8, 'ipv4'],
|
|
294
|
+
['100.64.0.0', 10, 'ipv4'],
|
|
295
|
+
['127.0.0.0', 8, 'ipv4'],
|
|
296
|
+
['169.254.0.0', 16, 'ipv4'],
|
|
297
|
+
['172.16.0.0', 12, 'ipv4'],
|
|
298
|
+
['192.0.0.0', 24, 'ipv4'],
|
|
299
|
+
['192.0.2.0', 24, 'ipv4'],
|
|
300
|
+
['192.88.99.0', 24, 'ipv4'],
|
|
301
|
+
['192.168.0.0', 16, 'ipv4'],
|
|
302
|
+
['198.18.0.0', 15, 'ipv4'],
|
|
303
|
+
['198.51.100.0', 24, 'ipv4'],
|
|
304
|
+
['203.0.113.0', 24, 'ipv4'],
|
|
305
|
+
['224.0.0.0', 4, 'ipv4'],
|
|
306
|
+
['240.0.0.0', 4, 'ipv4'],
|
|
307
|
+
['::', 128, 'ipv6'],
|
|
308
|
+
['::1', 128, 'ipv6'],
|
|
309
|
+
['64:ff9b:1::', 48, 'ipv6'],
|
|
310
|
+
['100::', 64, 'ipv6'],
|
|
311
|
+
['100:0:0:1::', 64, 'ipv6'],
|
|
312
|
+
['2001::', 23, 'ipv6'],
|
|
313
|
+
['2001:db8::', 32, 'ipv6'],
|
|
314
|
+
['2002::', 16, 'ipv6'],
|
|
315
|
+
['3fff::', 20, 'ipv6'],
|
|
316
|
+
['5f00::', 16, 'ipv6'],
|
|
317
|
+
['fc00::', 7, 'ipv6'],
|
|
318
|
+
['fe80::', 10, 'ipv6'],
|
|
319
|
+
['ff00::', 8, 'ipv6'],
|
|
320
|
+
]) {
|
|
321
|
+
blockedPreviewAddresses.addSubnet(address, prefix, family);
|
|
322
|
+
}
|
|
323
|
+
function previewServerIp(value) {
|
|
324
|
+
if (typeof value !== 'string')
|
|
325
|
+
throw new Error('Dokploy requires a public server IP before it can generate preview domains');
|
|
326
|
+
const address = value.trim().toLowerCase();
|
|
327
|
+
const version = isIP(address);
|
|
328
|
+
const family = version === 4 ? 'ipv4' : version === 6 ? 'ipv6' : undefined;
|
|
329
|
+
if (!family || address.startsWith('::ffff:') || blockedPreviewAddresses.check(address, family)) {
|
|
330
|
+
throw new Error('Dokploy requires a public server IP before it can generate preview domains');
|
|
331
|
+
}
|
|
332
|
+
return address;
|
|
333
|
+
}
|
|
234
334
|
function previewApplicationPrNumber(name, applicationName) {
|
|
235
335
|
const match = /^(\d+)$/.exec(name.match(/(\d+)$/)?.[1] ?? '');
|
|
236
336
|
const prNumber = match?.[1];
|
|
@@ -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 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"]}
|
|
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 = await this.api('domain.canGenerateTraefikMeDomains', {\n query: { serverId: serverId ?? '' },\n })\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<{ domains?: { domainId?: string; host: string }[] } | undefined>('application.one', {\n query: { applicationId },\n })\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 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,5 +1,17 @@
|
|
|
1
|
-
import type { GitHubPreviewOptions,
|
|
1
|
+
import type { GitHubPreviewOptions, PreviewStatusState } from './github.js';
|
|
2
2
|
export declare function previewStatusFromEnvironment(stateValue: string | undefined, environment?: NodeJS.ProcessEnv): {
|
|
3
3
|
options: GitHubPreviewOptions;
|
|
4
|
-
status:
|
|
4
|
+
status: {
|
|
5
|
+
state: "deleted";
|
|
6
|
+
prNumber: string;
|
|
7
|
+
};
|
|
8
|
+
} | {
|
|
9
|
+
options: GitHubPreviewOptions;
|
|
10
|
+
status: {
|
|
11
|
+
state: Exclude<PreviewStatusState, 'deleted'>;
|
|
12
|
+
prNumber: string;
|
|
13
|
+
sha: string;
|
|
14
|
+
previewUrl?: string;
|
|
15
|
+
runUrl?: string;
|
|
16
|
+
};
|
|
5
17
|
};
|
|
@@ -14,16 +14,20 @@ export function previewStatusFromEnvironment(stateValue, environment = process.e
|
|
|
14
14
|
...(checkName ? { checkName } : {}),
|
|
15
15
|
};
|
|
16
16
|
const prNumber = requiredEnvironment(environment, 'PR_NUMBER');
|
|
17
|
+
if (state === 'deleted')
|
|
18
|
+
return { options, status: { state, prNumber } };
|
|
17
19
|
const runUrl = optionalEnvironment(environment, 'RUN_URL');
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
20
|
+
const sha = requiredEnvironment(environment, 'COMMIT_SHA');
|
|
21
|
+
const previewUrl = optionalEnvironment(environment, 'PREVIEW_URL');
|
|
22
|
+
if (state === 'ready' && !previewUrl)
|
|
23
|
+
throw new Error('PREVIEW_URL is required');
|
|
24
|
+
const status = {
|
|
25
|
+
state,
|
|
26
|
+
prNumber,
|
|
27
|
+
sha,
|
|
28
|
+
...(previewUrl ? { previewUrl } : {}),
|
|
29
|
+
...(runUrl ? { runUrl } : {}),
|
|
30
|
+
};
|
|
27
31
|
return { options, status };
|
|
28
32
|
}
|
|
29
33
|
function requiredEnvironment(environment, name) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"environment.js","sourceRoot":"","sources":["../../src/preview/environment.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,GAAG,IAAI,GAAG,CAAqB,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAA;AAElG,MAAM,UAAU,4BAA4B,CAAC,UAA8B,EAAE,WAAW,GAAsB,OAAO,CAAC,GAAG;IACvH,IAAI,CAAC,UAAU,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAgC,CAAC,EAAE,CAAC;QACjE,MAAM,IAAI,KAAK,CAAC,qEAAqE,CAAC,CAAA;IACxF,CAAC;IACD,MAAM,KAAK,GAAG,UAAgC,CAAA;IAC9C,MAAM,IAAI,GAAG,mBAAmB,CAAC,WAAW,EAAE,cAAc,CAAC,CAAA;IAC7D,MAAM,SAAS,GAAG,mBAAmB,CAAC,WAAW,EAAE,oBAAoB,CAAC,CAAA;IACxE,MAAM,OAAO,GAAyB;QACpC,UAAU,EAAE,mBAAmB,CAAC,WAAW,EAAE,mBAAmB,CAAC;QACjE,KAAK,EAAE,mBAAmB,CAAC,WAAW,EAAE,UAAU,CAAC;QACnD,MAAM,EAAE,mBAAmB,CAAC,WAAW,EAAE,gBAAgB,CAAC;QAC1D,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzB,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACpC,CAAA;IACD,MAAM,QAAQ,GAAG,mBAAmB,CAAC,WAAW,EAAE,WAAW,CAAC,CAAA;IAC9D,MAAM,MAAM,GAAG,mBAAmB,CAAC,WAAW,EAAE,SAAS,CAAC,CAAA;IAC1D,MAAM,
|
|
1
|
+
{"version":3,"file":"environment.js","sourceRoot":"","sources":["../../src/preview/environment.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,GAAG,IAAI,GAAG,CAAqB,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAA;AAElG,MAAM,UAAU,4BAA4B,CAAC,UAA8B,EAAE,WAAW,GAAsB,OAAO,CAAC,GAAG;IACvH,IAAI,CAAC,UAAU,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAgC,CAAC,EAAE,CAAC;QACjE,MAAM,IAAI,KAAK,CAAC,qEAAqE,CAAC,CAAA;IACxF,CAAC;IACD,MAAM,KAAK,GAAG,UAAgC,CAAA;IAC9C,MAAM,IAAI,GAAG,mBAAmB,CAAC,WAAW,EAAE,cAAc,CAAC,CAAA;IAC7D,MAAM,SAAS,GAAG,mBAAmB,CAAC,WAAW,EAAE,oBAAoB,CAAC,CAAA;IACxE,MAAM,OAAO,GAAyB;QACpC,UAAU,EAAE,mBAAmB,CAAC,WAAW,EAAE,mBAAmB,CAAC;QACjE,KAAK,EAAE,mBAAmB,CAAC,WAAW,EAAE,UAAU,CAAC;QACnD,MAAM,EAAE,mBAAmB,CAAC,WAAW,EAAE,gBAAgB,CAAC;QAC1D,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzB,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACpC,CAAA;IACD,MAAM,QAAQ,GAAG,mBAAmB,CAAC,WAAW,EAAE,WAAW,CAAC,CAAA;IAC9D,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAA;IACxE,MAAM,MAAM,GAAG,mBAAmB,CAAC,WAAW,EAAE,SAAS,CAAC,CAAA;IAC1D,MAAM,GAAG,GAAG,mBAAmB,CAAC,WAAW,EAAE,YAAY,CAAC,CAAA;IAC1D,MAAM,UAAU,GAAG,mBAAmB,CAAC,WAAW,EAAE,aAAa,CAAC,CAAA;IAClE,IAAI,KAAK,KAAK,OAAO,IAAI,CAAC,UAAU;QAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAA;IAChF,MAAM,MAAM,GAAkB;QAC5B,KAAK;QACL,QAAQ;QACR,GAAG;QACH,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC9B,CAAA;IACD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAA;AAC5B,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":["import type { GitHubPreviewOptions, PreviewStatus, PreviewStatusState } from './github.js'\n\nconst states = new Set<PreviewStatusState>(['awaiting', 'building', 'ready', 'failed', 'deleted'])\n\nexport function previewStatusFromEnvironment(stateValue: string | undefined, environment: NodeJS.ProcessEnv = process.env) {\n if (!stateValue || !states.has(stateValue as PreviewStatusState)) {\n throw new Error('preview state must be awaiting, building, ready, failed, or deleted')\n }\n const state = stateValue as PreviewStatusState\n const note = optionalEnvironment(environment, 'PREVIEW_NOTE')\n const checkName = optionalEnvironment(environment, 'PREVIEW_CHECK_NAME')\n const options: GitHubPreviewOptions = {\n repository: requiredEnvironment(environment, 'GITHUB_REPOSITORY'),\n token: requiredEnvironment(environment, 'GH_TOKEN'),\n marker: requiredEnvironment(environment, 'PREVIEW_MARKER'),\n ...(note ? { note } : {}),\n ...(checkName ? { checkName } : {}),\n }\n const prNumber = requiredEnvironment(environment, 'PR_NUMBER')\n if (state === 'deleted') return { options, status: { state, prNumber } }\n const runUrl = optionalEnvironment(environment, 'RUN_URL')\n const sha = requiredEnvironment(environment, 'COMMIT_SHA')\n const previewUrl = optionalEnvironment(environment, 'PREVIEW_URL')\n if (state === 'ready' && !previewUrl) throw new Error('PREVIEW_URL is required')\n const status: PreviewStatus = {\n state,\n prNumber,\n sha,\n ...(previewUrl ? { previewUrl } : {}),\n ...(runUrl ? { runUrl } : {}),\n }\n return { options, status }\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"]}
|
package/dist/preview/github.js
CHANGED
|
@@ -8,7 +8,14 @@ export async function reportPreviewStatus(options, status) {
|
|
|
8
8
|
const request = options.fetch ?? fetch;
|
|
9
9
|
const api = (path, init) => github(request, options.token, `/repos/${repository}${path}`, init);
|
|
10
10
|
if (status.state !== 'deleted') {
|
|
11
|
-
|
|
11
|
+
const sha = commitSha(status.sha);
|
|
12
|
+
if (status.state === 'ready' && !status.previewUrl)
|
|
13
|
+
throw new Error('preview URL is required for ready preview status');
|
|
14
|
+
if (status.previewUrl)
|
|
15
|
+
webUrl(status.previewUrl, 'preview URL');
|
|
16
|
+
if (status.runUrl)
|
|
17
|
+
webUrl(status.runUrl, 'workflow run URL');
|
|
18
|
+
await updateCheck(api, options.checkName ?? 'PR preview deploy', sha, status);
|
|
12
19
|
}
|
|
13
20
|
const comments = await issueComments(api, prNumber);
|
|
14
21
|
const existing = comments.find((comment) => comment.body?.includes(marker));
|
|
@@ -51,15 +58,12 @@ function previewComment(options, status, previous) {
|
|
|
51
58
|
awaiting: `βΈοΈ The preview of \`${sha}\` is waiting for a maintainer to approve its build.${standing}`,
|
|
52
59
|
building: `π Deploying \`${sha}\`.${standing}`,
|
|
53
60
|
ready: `β
Preview is up to date with commit \`${sha}\`.`,
|
|
54
|
-
failed: `β Deploying commit \`${sha}\` failed${status.runUrl ? ` ([workflow run](${webUrl(status.runUrl, 'workflow run URL')}))` : ''}. The preview
|
|
61
|
+
failed: `β Deploying commit \`${sha}\` failed${status.runUrl ? ` ([workflow run](${webUrl(status.runUrl, 'workflow run URL')}))` : ''}. The preview may be stale or unavailable.`,
|
|
55
62
|
}[status.state];
|
|
56
|
-
if (!status.previewUrl)
|
|
57
|
-
throw new Error('preview URL is required for active preview status');
|
|
58
63
|
return [
|
|
59
64
|
options.marker,
|
|
60
65
|
heading,
|
|
61
|
-
'',
|
|
62
|
-
`Preview: ${webUrl(status.previewUrl, 'preview URL')}`,
|
|
66
|
+
...(status.previewUrl ? ['', `Preview: ${webUrl(status.previewUrl, 'preview URL')}`] : []),
|
|
63
67
|
...(options.note ? ['', options.note] : []),
|
|
64
68
|
].join('\n');
|
|
65
69
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/preview/github.ts"],"names":[],"mappings":"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","sourcesContent":["import { pullRequestNumber } from './dokploy.js'\n\nexport type PreviewStatusState = 'awaiting' | 'building' | 'ready' | 'failed' | 'deleted'\n\nexport type GitHubPreviewOptions = {\n repository: string\n token: string\n marker: string\n note?: string\n checkName?: string\n fetch?: typeof fetch\n}\n\nexport type PreviewStatus =\n | { state: 'deleted'; prNumber: string }\n | {\n state: Exclude<PreviewStatusState, 'deleted'>\n prNumber: string\n sha: string\n previewUrl?: string\n runUrl?: string\n }\n\ntype Check = { id: number }\ntype Comment = { id: number; body?: string }\n\nexport async function reportPreviewStatus(options: GitHubPreviewOptions, status: PreviewStatus) {\n const repository = githubRepository(options.repository)\n const marker = commentMarker(options.marker)\n if (!options.token) throw new Error('GitHub token is required')\n const prNumber = pullRequestNumber(status.prNumber)\n const request = options.fetch ?? fetch\n const api = <T>(path: string, init?: RequestInit) => github<T>(request, options.token, `/repos/${repository}${path}`, init)\n\n if (status.state !== 'deleted') {\n await updateCheck(api, options.checkName ?? 'PR preview deploy', commitSha(status.sha), status)\n }\n const comments = await issueComments(api, prNumber)\n const existing = comments.find((comment) => comment.body?.includes(marker))\n const body = previewComment({ ...options, marker }, status, existing?.body)\n if (existing) await api(`/issues/comments/${existing.id}`, jsonRequest('PATCH', { body }))\n else await api(`/issues/${prNumber}/comments`, jsonRequest('POST', { body }))\n}\n\nasync function updateCheck(\n api: <T>(path: string, init?: RequestInit) => Promise<T>,\n name: string,\n sha: string,\n status: Extract<PreviewStatus, { state: Exclude<PreviewStatusState, 'deleted'> }>,\n) {\n const checks = await api<{ check_runs: Check[] }>(`/commits/${sha}/check-runs?check_name=${encodeURIComponent(name)}&filter=latest`)\n const state = checkState(status.state)\n const body = {\n name,\n head_sha: sha,\n status: state.status,\n ...(state.conclusion ? { conclusion: state.conclusion } : {}),\n ...(status.runUrl ? { details_url: webUrl(status.runUrl, 'workflow run URL') } : {}),\n output: { title: name, summary: state.summary },\n }\n const existing = checks.check_runs[0]\n if (existing) await api(`/check-runs/${existing.id}`, jsonRequest('PATCH', body))\n else await api('/check-runs', jsonRequest('POST', body))\n}\n\nasync function issueComments(api: <T>(path: string, init?: RequestInit) => Promise<T>, prNumber: string, page = 1): Promise<Comment[]> {\n if (page > 10) throw new Error('preview comment lookup exceeded 1,000 comments')\n const comments = await api<Comment[]>(`/issues/${prNumber}/comments?per_page=100&page=${page}`)\n return comments.length < 100 ? comments : [...comments, ...(await issueComments(api, prNumber, page + 1))]\n}\n\nfunction previewComment(options: GitHubPreviewOptions, status: PreviewStatus, previous?: string) {\n if (status.state === 'deleted') return `${options.marker}\\nποΈ Preview deleted because this pull request was closed.`\n const sha = commitSha(status.sha).slice(0, 7)\n const standingSha = previous?.match(/up to date with commit `([0-9a-f]{7})`/)?.[1]\n const standing = standingSha ? ` The preview of \\`${standingSha}\\` stays up until it does.` : ''\n const heading = {\n awaiting: `βΈοΈ The preview of \\`${sha}\\` is waiting for a maintainer to approve its build.${standing}`,\n building: `π Deploying \\`${sha}\\`.${standing}`,\n ready: `β
Preview is up to date with commit \\`${sha}\\`.`,\n failed: `β Deploying commit \\`${sha}\\` failed${status.runUrl ? ` ([workflow run](${webUrl(status.runUrl, 'workflow run URL')}))` : ''}. The preview below may be stale or unavailable.`,\n }[status.state]\n if (!status.previewUrl) throw new Error('preview URL is required for active preview status')\n return [\n options.marker,\n heading,\n '',\n `Preview: ${webUrl(status.previewUrl, 'preview URL')}`,\n ...(options.note ? ['', options.note] : []),\n ].join('\\n')\n}\n\nfunction checkState(state: Exclude<PreviewStatusState, 'deleted'>) {\n return {\n awaiting: { status: 'queued', summary: 'The preview build is waiting for workflow approval.' },\n building: { status: 'in_progress', summary: 'A new preview version is deploying.' },\n ready: { status: 'completed', conclusion: 'success', summary: 'The preview is up to date.' },\n failed: { status: 'completed', conclusion: 'failure', summary: 'The preview deployment failed.' },\n }[state]\n}\n\nasync function github<T>(request: typeof fetch, token: string, path: string, init?: RequestInit): Promise<T> {\n const headers = new Headers(init?.headers)\n headers.set('accept', 'application/vnd.github+json')\n headers.set('authorization', `Bearer ${token}`)\n headers.set('x-github-api-version', '2022-11-28')\n const response = await request(`https://api.github.com${path}`, {\n ...init,\n headers,\n })\n const text = await response.text()\n if (!response.ok) throw new Error(`GitHub ${path.split('?')[0]} failed with ${response.status}: ${text.slice(0, 500)}`)\n return text ? (JSON.parse(text) as T) : (undefined as T)\n}\n\nfunction jsonRequest(method: 'POST' | 'PATCH', body: unknown): RequestInit {\n return { method, headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) }\n}\n\nfunction githubRepository(value: string) {\n if (!/^[a-z\\d][\\w.-]*\\/[a-z\\d][\\w.-]*$/i.test(value)) throw new Error('repository must use an owner/name identifier')\n return value\n}\n\nfunction commentMarker(value: string) {\n if (!/^<!-- [a-z\\d-]+ -->$/i.test(value)) throw new Error('preview marker must be a named HTML comment')\n return value\n}\n\nfunction webUrl(value: string, name: string) {\n const url = new URL(value)\n if (url.protocol !== 'https:' && url.protocol !== 'http:') throw new Error(`${name} must use HTTP or HTTPS`)\n return url.toString().replace(/\\/$/, '')\n}\n\nfunction commitSha(value: string | undefined) {\n if (!value || !/^[0-9a-f]{40}$/i.test(value)) throw new Error('commit SHA must contain 40 hexadecimal characters')\n return value\n}\n"]}
|
|
1
|
+
{"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/preview/github.ts"],"names":[],"mappings":"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","sourcesContent":["import { pullRequestNumber } from './dokploy.js'\n\nexport type PreviewStatusState = 'awaiting' | 'building' | 'ready' | 'failed' | 'deleted'\n\nexport type GitHubPreviewOptions = {\n repository: string\n token: string\n marker: string\n note?: string\n checkName?: string\n fetch?: typeof fetch\n}\n\nexport type PreviewStatus =\n | { state: 'deleted'; prNumber: string }\n | {\n state: Exclude<PreviewStatusState, 'deleted'>\n prNumber: string\n sha: string\n previewUrl?: string\n runUrl?: string\n }\n\ntype Check = { id: number }\ntype Comment = { id: number; body?: string }\n\nexport async function reportPreviewStatus(options: GitHubPreviewOptions, status: PreviewStatus) {\n const repository = githubRepository(options.repository)\n const marker = commentMarker(options.marker)\n if (!options.token) throw new Error('GitHub token is required')\n const prNumber = pullRequestNumber(status.prNumber)\n const request = options.fetch ?? fetch\n const api = <T>(path: string, init?: RequestInit) => github<T>(request, options.token, `/repos/${repository}${path}`, init)\n\n if (status.state !== 'deleted') {\n const sha = commitSha(status.sha)\n if (status.state === 'ready' && !status.previewUrl) throw new Error('preview URL is required for ready preview status')\n if (status.previewUrl) webUrl(status.previewUrl, 'preview URL')\n if (status.runUrl) webUrl(status.runUrl, 'workflow run URL')\n await updateCheck(api, options.checkName ?? 'PR preview deploy', sha, status)\n }\n const comments = await issueComments(api, prNumber)\n const existing = comments.find((comment) => comment.body?.includes(marker))\n const body = previewComment({ ...options, marker }, status, existing?.body)\n if (existing) await api(`/issues/comments/${existing.id}`, jsonRequest('PATCH', { body }))\n else await api(`/issues/${prNumber}/comments`, jsonRequest('POST', { body }))\n}\n\nasync function updateCheck(\n api: <T>(path: string, init?: RequestInit) => Promise<T>,\n name: string,\n sha: string,\n status: Extract<PreviewStatus, { state: Exclude<PreviewStatusState, 'deleted'> }>,\n) {\n const checks = await api<{ check_runs: Check[] }>(`/commits/${sha}/check-runs?check_name=${encodeURIComponent(name)}&filter=latest`)\n const state = checkState(status.state)\n const body = {\n name,\n head_sha: sha,\n status: state.status,\n ...(state.conclusion ? { conclusion: state.conclusion } : {}),\n ...(status.runUrl ? { details_url: webUrl(status.runUrl, 'workflow run URL') } : {}),\n output: { title: name, summary: state.summary },\n }\n const existing = checks.check_runs[0]\n if (existing) await api(`/check-runs/${existing.id}`, jsonRequest('PATCH', body))\n else await api('/check-runs', jsonRequest('POST', body))\n}\n\nasync function issueComments(api: <T>(path: string, init?: RequestInit) => Promise<T>, prNumber: string, page = 1): Promise<Comment[]> {\n if (page > 10) throw new Error('preview comment lookup exceeded 1,000 comments')\n const comments = await api<Comment[]>(`/issues/${prNumber}/comments?per_page=100&page=${page}`)\n return comments.length < 100 ? comments : [...comments, ...(await issueComments(api, prNumber, page + 1))]\n}\n\nfunction previewComment(options: GitHubPreviewOptions, status: PreviewStatus, previous?: string) {\n if (status.state === 'deleted') return `${options.marker}\\nποΈ Preview deleted because this pull request was closed.`\n const sha = commitSha(status.sha).slice(0, 7)\n const standingSha = previous?.match(/up to date with commit `([0-9a-f]{7})`/)?.[1]\n const standing = standingSha ? ` The preview of \\`${standingSha}\\` stays up until it does.` : ''\n const heading = {\n awaiting: `βΈοΈ The preview of \\`${sha}\\` is waiting for a maintainer to approve its build.${standing}`,\n building: `π Deploying \\`${sha}\\`.${standing}`,\n ready: `β
Preview is up to date with commit \\`${sha}\\`.`,\n failed: `β Deploying commit \\`${sha}\\` failed${status.runUrl ? ` ([workflow run](${webUrl(status.runUrl, 'workflow run URL')}))` : ''}. The preview may be stale or unavailable.`,\n }[status.state]\n return [\n options.marker,\n heading,\n ...(status.previewUrl ? ['', `Preview: ${webUrl(status.previewUrl, 'preview URL')}`] : []),\n ...(options.note ? ['', options.note] : []),\n ].join('\\n')\n}\n\nfunction checkState(state: Exclude<PreviewStatusState, 'deleted'>) {\n return {\n awaiting: { status: 'queued', summary: 'The preview build is waiting for workflow approval.' },\n building: { status: 'in_progress', summary: 'A new preview version is deploying.' },\n ready: { status: 'completed', conclusion: 'success', summary: 'The preview is up to date.' },\n failed: { status: 'completed', conclusion: 'failure', summary: 'The preview deployment failed.' },\n }[state]\n}\n\nasync function github<T>(request: typeof fetch, token: string, path: string, init?: RequestInit): Promise<T> {\n const headers = new Headers(init?.headers)\n headers.set('accept', 'application/vnd.github+json')\n headers.set('authorization', `Bearer ${token}`)\n headers.set('x-github-api-version', '2022-11-28')\n const response = await request(`https://api.github.com${path}`, {\n ...init,\n headers,\n })\n const text = await response.text()\n if (!response.ok) throw new Error(`GitHub ${path.split('?')[0]} failed with ${response.status}: ${text.slice(0, 500)}`)\n return text ? (JSON.parse(text) as T) : (undefined as T)\n}\n\nfunction jsonRequest(method: 'POST' | 'PATCH', body: unknown): RequestInit {\n return { method, headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) }\n}\n\nfunction githubRepository(value: string) {\n if (!/^[a-z\\d][\\w.-]*\\/[a-z\\d][\\w.-]*$/i.test(value)) throw new Error('repository must use an owner/name identifier')\n return value\n}\n\nfunction commentMarker(value: string) {\n if (!/^<!-- [a-z\\d-]+ -->$/i.test(value)) throw new Error('preview marker must be a named HTML comment')\n return value\n}\n\nfunction webUrl(value: string, name: string) {\n const url = new URL(value)\n if (url.protocol !== 'https:' && url.protocol !== 'http:') throw new Error(`${name} must use HTTP or HTTPS`)\n return url.toString().replace(/\\/$/, '')\n}\n\nfunction commitSha(value: string | undefined) {\n if (!value || !/^[0-9a-f]{40}$/i.test(value)) throw new Error('commit SHA must contain 40 hexadecimal characters')\n return value\n}\n"]}
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { expect, test } from '@playwright/test'
|
|
2
2
|
|
|
3
|
+
test('keeps authentication submission disabled before hydration', async ({ browser }) => {
|
|
4
|
+
const context = await browser.newContext({ javaScriptEnabled: false })
|
|
5
|
+
const page = await context.newPage()
|
|
6
|
+
await page.goto('/')
|
|
7
|
+
|
|
8
|
+
await expect(page.getByRole('button', { name: 'Create account', exact: true })).toBeDisabled()
|
|
9
|
+
await context.close()
|
|
10
|
+
})
|
|
11
|
+
|
|
3
12
|
test('signs up, authorizes resources, persists an upload, and receives an outbox publication', async ({ browser, request }, testInfo) => {
|
|
4
13
|
const run = Date.now()
|
|
5
14
|
const firstMessage = `first message ${run}`
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useMutation, useQueryClient, useSuspenseQuery } from '@tanstack/react-query'
|
|
2
2
|
import { createFileRoute } from '@tanstack/react-router'
|
|
3
3
|
import { useAuthAction } from 'ras-stack/auth/react'
|
|
4
|
-
import { useState } from 'react'
|
|
4
|
+
import { useEffect, useState } from 'react'
|
|
5
5
|
import { authClient } from '../client/auth'
|
|
6
6
|
import { snapshotQuery } from '../client/queries'
|
|
7
7
|
import { queryErrorMessage } from '../client/queryClient'
|
|
@@ -38,10 +38,12 @@ function Home() {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
function Authentication({ emailConfigured }: { emailConfigured: boolean }) {
|
|
41
|
+
const [hydrated, setHydrated] = useState(false)
|
|
41
42
|
const [mode, setMode] = useState<'sign-up' | 'sign-in'>('sign-up')
|
|
42
43
|
const [notice, setNotice] = useState('')
|
|
43
44
|
const queryClient = useQueryClient()
|
|
44
45
|
const action = useAuthAction()
|
|
46
|
+
useEffect(() => setHydrated(true), [])
|
|
45
47
|
return (
|
|
46
48
|
<form
|
|
47
49
|
onSubmit={async (event) => {
|
|
@@ -84,7 +86,7 @@ function Authentication({ emailConfigured }: { emailConfigured: boolean }) {
|
|
|
84
86
|
type="password"
|
|
85
87
|
/>
|
|
86
88
|
</label>
|
|
87
|
-
<button disabled={action.busy}>{mode === 'sign-up' ? 'Create account' : 'Sign in'}</button>
|
|
89
|
+
<button disabled={!hydrated || action.busy}>{mode === 'sign-up' ? 'Create account' : 'Sign in'}</button>
|
|
88
90
|
<button type="button" onClick={() => setMode(mode === 'sign-up' ? 'sign-in' : 'sign-up')}>
|
|
89
91
|
{mode === 'sign-up' ? 'Use an existing account' : 'Create a new account'}
|
|
90
92
|
</button>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ras-stack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.42.0",
|
|
4
4
|
"description": "Composable full-stack primitives shared across Richard Solomou's applications.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"authentication",
|
|
@@ -63,6 +63,10 @@
|
|
|
63
63
|
"types": "./dist/conformance/index.d.ts",
|
|
64
64
|
"default": "./dist/conformance/index.js"
|
|
65
65
|
},
|
|
66
|
+
"./create": {
|
|
67
|
+
"types": "./dist/create/index.d.ts",
|
|
68
|
+
"default": "./dist/create/index.js"
|
|
69
|
+
},
|
|
66
70
|
"./policy": {
|
|
67
71
|
"types": "./dist/policy/index.d.ts",
|
|
68
72
|
"default": "./dist/policy/index.js"
|
|
@@ -144,19 +148,6 @@
|
|
|
144
148
|
"publishConfig": {
|
|
145
149
|
"access": "public"
|
|
146
150
|
},
|
|
147
|
-
"scripts": {
|
|
148
|
-
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
149
|
-
"config:check": "tsc -p test/config-consumer/tsconfig.json && tsc -p test/config-consumer/tsconfig-bundler.json && tsc -p test/config-consumer/tsconfig-node-bundler.json && tsc -p test/config-consumer/tsconfig-library.json && oxlint --config config/oxlint/application.json --print-config > /dev/null && oxlint --config config/oxlint/tanstack.json --print-config > /dev/null",
|
|
150
|
-
"typecheck": "tsc --noEmit",
|
|
151
|
-
"lint": "oxlint --type-aware --deny-warnings .",
|
|
152
|
-
"format": "oxfmt --write .",
|
|
153
|
-
"format:check": "oxfmt --check .",
|
|
154
|
-
"test": "vitest run --config vitest.config.ts",
|
|
155
|
-
"package:check": "node scripts/checkPackedPackage.mjs",
|
|
156
|
-
"peer:floor": "node scripts/checkPeerFloor.mjs",
|
|
157
|
-
"check": "pnpm format:check && pnpm lint && pnpm config:check && pnpm typecheck && pnpm test && pnpm build && pnpm package:check && pnpm --filter @ras-stack/example-full-stack check && node dist/cli.js policy check",
|
|
158
|
-
"version-packages": "changeset version"
|
|
159
|
-
},
|
|
160
151
|
"dependencies": {
|
|
161
152
|
"@inquirer/confirm": "^6.1.1",
|
|
162
153
|
"@inquirer/select": "^5.2.1",
|
|
@@ -266,5 +257,17 @@
|
|
|
266
257
|
"engines": {
|
|
267
258
|
"node": ">=24 <25"
|
|
268
259
|
},
|
|
269
|
-
"
|
|
270
|
-
|
|
260
|
+
"scripts": {
|
|
261
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
262
|
+
"config:check": "tsc -p test/config-consumer/tsconfig.json && tsc -p test/config-consumer/tsconfig-bundler.json && tsc -p test/config-consumer/tsconfig-node-bundler.json && tsc -p test/config-consumer/tsconfig-library.json && oxlint --config config/oxlint/application.json --print-config > /dev/null && oxlint --config config/oxlint/tanstack.json --print-config > /dev/null",
|
|
263
|
+
"typecheck": "tsc --noEmit",
|
|
264
|
+
"lint": "oxlint --type-aware --deny-warnings .",
|
|
265
|
+
"format": "oxfmt --write .",
|
|
266
|
+
"format:check": "oxfmt --check .",
|
|
267
|
+
"test": "vitest run --config vitest.config.ts",
|
|
268
|
+
"package:check": "node scripts/checkPackedPackage.mjs",
|
|
269
|
+
"peer:floor": "node scripts/checkPeerFloor.mjs",
|
|
270
|
+
"check": "pnpm format:check && pnpm lint && pnpm config:check && pnpm typecheck && pnpm test && pnpm build && pnpm package:check && pnpm --filter @ras-stack/example-full-stack check && node dist/cli.js policy check",
|
|
271
|
+
"version-packages": "changeset version"
|
|
272
|
+
}
|
|
273
|
+
}
|