ras-stack 0.34.1 → 0.36.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 +10 -0
- package/dist/posthog/server.d.ts +39 -0
- package/dist/posthog/server.js +230 -2
- package/dist/posthog/server.js.map +1 -1
- package/dist/preview/cli.js +5 -0
- package/dist/preview/cli.js.map +1 -1
- package/dist/preview/dokploy-cli.d.ts +2 -0
- package/dist/preview/dokploy-cli.js +46 -0
- package/dist/preview/dokploy-cli.js.map +1 -0
- package/dist/preview/dokploy.d.ts +17 -0
- package/dist/preview/dokploy.js +61 -5
- package/dist/preview/dokploy.js.map +1 -1
- package/dist/server/rpc.d.ts +3 -2
- package/dist/server/rpc.js +11 -5
- package/dist/server/rpc.js.map +1 -1
- package/package.json +21 -1
package/README.md
CHANGED
|
@@ -87,6 +87,16 @@ Start with the narrowest public entrypoint that owns the repeated mechanic:
|
|
|
87
87
|
| Compiler, lint, CI, release, and preview mechanics | `ras-stack/config/*`, `actions/*`, `.github/workflows/*`, `ras-stack/preview/*` | Triggers, permissions, services, deployment, and verification |
|
|
88
88
|
| Generated repository policy and adoption checks | `ras policy` | Which policies apply and every declared exception |
|
|
89
89
|
|
|
90
|
+
## Dokploy previews 🚀
|
|
91
|
+
|
|
92
|
+
Three reusable workflows provide the standard pull-request preview lifecycle:
|
|
93
|
+
|
|
94
|
+
- `build-dokploy-preview.yml` builds commit-specific images without exposing secrets to forks.
|
|
95
|
+
- `deploy-dokploy-preview.yml` publishes, resolves, deploys, reports, and removes previews.
|
|
96
|
+
- `prune-dokploy-previews.yml` cleans up applications and images left behind by interrupted runs.
|
|
97
|
+
|
|
98
|
+
Applications supply only their package, application prefix, domain, port, environment template, and optional product hook. The shared `DOKPLOY_URL`, `DOKPLOY_API_KEY`, and staging-only `DOKPLOY_ENVIRONMENT_ID` secrets can be configured once at organization level. See [Repository tooling](docs/repository-tooling.md) for the caller contract, private-registry options, and lifecycle hooks.
|
|
99
|
+
|
|
90
100
|
## Guides 📚
|
|
91
101
|
|
|
92
102
|
| Guide | What it covers |
|
package/dist/posthog/server.d.ts
CHANGED
|
@@ -1,4 +1,43 @@
|
|
|
1
1
|
import type { PostHog, PostHogOptions } from 'posthog-node';
|
|
2
|
+
import type { RpcErrorContext, RpcLogger } from '../server/rpc.js';
|
|
2
3
|
import type { PostHogEnvironment } from './config.js';
|
|
4
|
+
export type PostHogLogValue = string | number | boolean | null | PostHogLogValue[];
|
|
5
|
+
export type PostHogLogRecord = {
|
|
6
|
+
body: string;
|
|
7
|
+
severityText?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
|
8
|
+
timestamp?: number;
|
|
9
|
+
attributes?: Record<string, unknown>;
|
|
10
|
+
};
|
|
11
|
+
export type ManagedPostHogServerTelemetryOptions = {
|
|
12
|
+
environment: PostHogEnvironment | undefined;
|
|
13
|
+
serviceName: string;
|
|
14
|
+
serviceVersion?: string;
|
|
15
|
+
deploymentEnvironment?: string;
|
|
16
|
+
clientOptions?: Omit<PostHogOptions, 'host'>;
|
|
17
|
+
onError?: (error: unknown) => void;
|
|
18
|
+
};
|
|
19
|
+
export type PostHogServerTelemetry = ReturnType<typeof createManagedPostHogServerTelemetry>;
|
|
20
|
+
export type PostHogRpcLoggerOptions = {
|
|
21
|
+
logError?: (error: unknown, context: RpcErrorContext) => void;
|
|
22
|
+
resolveAuthenticatedDistinctId?: (request: Request) => string | undefined | Promise<string | undefined>;
|
|
23
|
+
allowAnonymousDistinctId?: boolean;
|
|
24
|
+
fallbackDistinctId?: string;
|
|
25
|
+
};
|
|
26
|
+
type PostHogShutdownProcess = {
|
|
27
|
+
pid: number;
|
|
28
|
+
on(signal: 'SIGINT' | 'SIGTERM', listener: () => void): void;
|
|
29
|
+
off(signal: 'SIGINT' | 'SIGTERM', listener: () => void): void;
|
|
30
|
+
kill(pid: number, signal: 'SIGINT' | 'SIGTERM'): void;
|
|
31
|
+
};
|
|
3
32
|
export declare function createPostHogServerClient(environment: PostHogEnvironment | undefined, options?: Omit<PostHogOptions, 'host'>): Promise<PostHog | undefined>;
|
|
4
33
|
export declare function shutdownPostHogServerClient(client: PostHog | undefined, timeoutMs?: number): Promise<void>;
|
|
34
|
+
export declare function createManagedPostHogServerTelemetry(options: ManagedPostHogServerTelemetryOptions): {
|
|
35
|
+
start(): Promise<void>;
|
|
36
|
+
capture(distinctId: string, event: string, properties?: Record<string, unknown>): Promise<void>;
|
|
37
|
+
exception(error: unknown, distinctId?: string, properties?: Record<string, unknown>): Promise<void>;
|
|
38
|
+
log(record: PostHogLogRecord): Promise<void>;
|
|
39
|
+
shutdown(timeoutMs?: number): Promise<void>;
|
|
40
|
+
};
|
|
41
|
+
export declare function createPostHogRpcLogger(telemetry: PostHogServerTelemetry, options?: PostHogRpcLoggerOptions): RpcLogger;
|
|
42
|
+
export declare function installPostHogServerTelemetryShutdown(telemetry: Pick<PostHogServerTelemetry, 'shutdown'>, target?: PostHogShutdownProcess): () => void;
|
|
43
|
+
export {};
|
package/dist/posthog/server.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { postHogRequestContext } from './request.js';
|
|
1
2
|
export async function createPostHogServerClient(environment, options = {}) {
|
|
2
3
|
if (!environment)
|
|
3
4
|
return undefined;
|
|
@@ -9,9 +10,236 @@ export async function createPostHogServerClient(environment, options = {}) {
|
|
|
9
10
|
});
|
|
10
11
|
}
|
|
11
12
|
export async function shutdownPostHogServerClient(client, timeoutMs = 10_000) {
|
|
12
|
-
|
|
13
|
-
throw new Error('timeoutMs must be a non-negative integer');
|
|
13
|
+
assertShutdownTimeout(timeoutMs);
|
|
14
14
|
// oxlint-disable-next-line no-underscore-dangle -- posthog-node's async shutdown API is named `_shutdown`.
|
|
15
15
|
await client?._shutdown(timeoutMs);
|
|
16
16
|
}
|
|
17
|
+
export function createManagedPostHogServerTelemetry(options) {
|
|
18
|
+
let client;
|
|
19
|
+
let logProvider;
|
|
20
|
+
let closed = false;
|
|
21
|
+
const report = (error) => {
|
|
22
|
+
try {
|
|
23
|
+
options.onError?.(error);
|
|
24
|
+
}
|
|
25
|
+
catch { }
|
|
26
|
+
};
|
|
27
|
+
const getClient = () => {
|
|
28
|
+
if (closed || !options.environment)
|
|
29
|
+
return undefined;
|
|
30
|
+
if (!client) {
|
|
31
|
+
const pending = createPostHogServerClient(options.environment, options.clientOptions);
|
|
32
|
+
client = pending;
|
|
33
|
+
void pending.catch(() => {
|
|
34
|
+
if (client === pending)
|
|
35
|
+
client = undefined;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return client;
|
|
39
|
+
};
|
|
40
|
+
const getLogProvider = () => {
|
|
41
|
+
if (closed || !options.environment)
|
|
42
|
+
return undefined;
|
|
43
|
+
if (!logProvider) {
|
|
44
|
+
const pending = createPostHogLogProvider(options);
|
|
45
|
+
logProvider = pending;
|
|
46
|
+
void pending.catch(() => {
|
|
47
|
+
if (logProvider === pending)
|
|
48
|
+
logProvider = undefined;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return logProvider;
|
|
52
|
+
};
|
|
53
|
+
const safely = async (work) => {
|
|
54
|
+
if (closed || !options.environment)
|
|
55
|
+
return;
|
|
56
|
+
try {
|
|
57
|
+
await work();
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
report(error);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
return {
|
|
64
|
+
async start() {
|
|
65
|
+
await safely(async () => {
|
|
66
|
+
await Promise.all([getClient(), getLogProvider()]);
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
async capture(distinctId, event, properties) {
|
|
70
|
+
await safely(async () => {
|
|
71
|
+
const value = await getClient();
|
|
72
|
+
value?.capture({ distinctId, event, ...(properties ? { properties } : {}) });
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
async exception(error, distinctId = 'server', properties) {
|
|
76
|
+
await safely(async () => {
|
|
77
|
+
const value = await getClient();
|
|
78
|
+
value?.captureException(error, distinctId, properties);
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
async log(record) {
|
|
82
|
+
await safely(async () => {
|
|
83
|
+
const provider = await getLogProvider();
|
|
84
|
+
const attributes = boundedAttributes(record.attributes);
|
|
85
|
+
provider?.getLogger(options.serviceName).emit({
|
|
86
|
+
body: boundedString(record.body),
|
|
87
|
+
...(record.severityText ? { severityText: record.severityText } : {}),
|
|
88
|
+
...(record.timestamp === undefined ? {} : { timestamp: record.timestamp }),
|
|
89
|
+
...(attributes ? { attributes } : {}),
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
async shutdown(timeoutMs = 10_000) {
|
|
94
|
+
if (closed)
|
|
95
|
+
return;
|
|
96
|
+
assertShutdownTimeout(timeoutMs);
|
|
97
|
+
closed = true;
|
|
98
|
+
const results = await Promise.allSettled([
|
|
99
|
+
client?.then((value) => shutdownPostHogServerClient(value, timeoutMs)),
|
|
100
|
+
logProvider?.then((value) => shutdownPostHogLogProvider(value, timeoutMs)),
|
|
101
|
+
]);
|
|
102
|
+
for (const result of results)
|
|
103
|
+
if (result.status === 'rejected')
|
|
104
|
+
report(result.reason);
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
export function createPostHogRpcLogger(telemetry, options = {}) {
|
|
109
|
+
return async (error, context, request) => {
|
|
110
|
+
try {
|
|
111
|
+
options.logError?.(error, context);
|
|
112
|
+
}
|
|
113
|
+
catch { }
|
|
114
|
+
let authenticatedDistinctId;
|
|
115
|
+
if (request && options.resolveAuthenticatedDistinctId) {
|
|
116
|
+
try {
|
|
117
|
+
authenticatedDistinctId = await options.resolveAuthenticatedDistinctId(request);
|
|
118
|
+
}
|
|
119
|
+
catch { }
|
|
120
|
+
}
|
|
121
|
+
const correlation = request
|
|
122
|
+
? postHogRequestContext(request, {
|
|
123
|
+
...(authenticatedDistinctId ? { authenticatedDistinctId } : {}),
|
|
124
|
+
...(options.allowAnonymousDistinctId === undefined ? {} : { allowAnonymousDistinctId: options.allowAnonymousDistinctId }),
|
|
125
|
+
})
|
|
126
|
+
: { properties: {} };
|
|
127
|
+
const distinctId = correlation.distinctId ?? options.fallbackDistinctId ?? 'server';
|
|
128
|
+
const properties = {
|
|
129
|
+
...correlation.properties,
|
|
130
|
+
...(context.method ? { request_method: context.method } : {}),
|
|
131
|
+
...(context.path ? { request_path: context.path } : {}),
|
|
132
|
+
};
|
|
133
|
+
await Promise.all([
|
|
134
|
+
telemetry.exception(error, distinctId, properties),
|
|
135
|
+
telemetry.log({
|
|
136
|
+
body: 'server function failed',
|
|
137
|
+
severityText: 'error',
|
|
138
|
+
attributes: { ...properties, posthogDistinctId: distinctId },
|
|
139
|
+
}),
|
|
140
|
+
]);
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
export function installPostHogServerTelemetryShutdown(telemetry, target = process) {
|
|
144
|
+
let shuttingDown = false;
|
|
145
|
+
const listeners = new Map();
|
|
146
|
+
const remove = () => {
|
|
147
|
+
for (const [signal, listener] of listeners)
|
|
148
|
+
target.off(signal, listener);
|
|
149
|
+
listeners.clear();
|
|
150
|
+
};
|
|
151
|
+
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
152
|
+
const listener = () => {
|
|
153
|
+
if (shuttingDown)
|
|
154
|
+
return;
|
|
155
|
+
shuttingDown = true;
|
|
156
|
+
void telemetry
|
|
157
|
+
.shutdown()
|
|
158
|
+
.catch(() => undefined)
|
|
159
|
+
.finally(() => {
|
|
160
|
+
remove();
|
|
161
|
+
target.kill(target.pid, signal);
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
listeners.set(signal, listener);
|
|
165
|
+
target.on(signal, listener);
|
|
166
|
+
}
|
|
167
|
+
return remove;
|
|
168
|
+
}
|
|
169
|
+
async function createPostHogLogProvider(options) {
|
|
170
|
+
if (!options.environment)
|
|
171
|
+
return undefined;
|
|
172
|
+
const [{ OTLPLogExporter }, { resourceFromAttributes }, { BatchLogRecordProcessor, LoggerProvider }] = await Promise.all([
|
|
173
|
+
import('@opentelemetry/exporter-logs-otlp-http'),
|
|
174
|
+
import('@opentelemetry/resources'),
|
|
175
|
+
import('@opentelemetry/sdk-logs'),
|
|
176
|
+
]);
|
|
177
|
+
const exporter = new OTLPLogExporter({
|
|
178
|
+
url: `${options.environment.host.replace(/\/$/, '')}/i/v1/logs`,
|
|
179
|
+
headers: { Authorization: `Bearer ${options.environment.projectToken}` },
|
|
180
|
+
});
|
|
181
|
+
return new LoggerProvider({
|
|
182
|
+
resource: resourceFromAttributes({
|
|
183
|
+
'service.name': options.serviceName,
|
|
184
|
+
...(options.serviceVersion ? { 'service.version': options.serviceVersion } : {}),
|
|
185
|
+
...(options.deploymentEnvironment ? { 'deployment.environment': options.deploymentEnvironment } : {}),
|
|
186
|
+
}),
|
|
187
|
+
processors: [new BatchLogRecordProcessor({ exporter })],
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
function boundedAttributes(attributes) {
|
|
191
|
+
if (!attributes)
|
|
192
|
+
return undefined;
|
|
193
|
+
return Object.fromEntries(Object.entries(attributes)
|
|
194
|
+
.slice(0, 64)
|
|
195
|
+
.map(([key, value]) => [key.slice(0, 128), boundedLogValue(value, 0)])
|
|
196
|
+
.filter((entry) => entry[1] !== undefined));
|
|
197
|
+
}
|
|
198
|
+
function boundedLogValue(value, depth) {
|
|
199
|
+
if (value === null || typeof value === 'number' || typeof value === 'boolean')
|
|
200
|
+
return value;
|
|
201
|
+
if (typeof value === 'string')
|
|
202
|
+
return boundedString(value);
|
|
203
|
+
if (depth >= 3)
|
|
204
|
+
return '[Truncated]';
|
|
205
|
+
if (Array.isArray(value)) {
|
|
206
|
+
return value
|
|
207
|
+
.slice(0, 32)
|
|
208
|
+
.map((entry) => boundedLogValue(entry, depth + 1))
|
|
209
|
+
.filter((entry) => entry !== undefined);
|
|
210
|
+
}
|
|
211
|
+
if (value === undefined)
|
|
212
|
+
return undefined;
|
|
213
|
+
try {
|
|
214
|
+
return boundedString(JSON.stringify(value));
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return '[Unserializable value]';
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function boundedString(value) {
|
|
221
|
+
return value.slice(0, 2_048);
|
|
222
|
+
}
|
|
223
|
+
async function shutdownPostHogLogProvider(provider, timeoutMs) {
|
|
224
|
+
if (!provider)
|
|
225
|
+
return;
|
|
226
|
+
let timeout;
|
|
227
|
+
try {
|
|
228
|
+
await Promise.race([
|
|
229
|
+
provider.shutdown(),
|
|
230
|
+
new Promise((_resolve, reject) => {
|
|
231
|
+
timeout = setTimeout(() => reject(new Error(`PostHog log shutdown timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
232
|
+
timeout.unref?.();
|
|
233
|
+
}),
|
|
234
|
+
]);
|
|
235
|
+
}
|
|
236
|
+
finally {
|
|
237
|
+
if (timeout)
|
|
238
|
+
clearTimeout(timeout);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function assertShutdownTimeout(timeoutMs) {
|
|
242
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 0)
|
|
243
|
+
throw new Error('timeoutMs must be a non-negative integer');
|
|
244
|
+
}
|
|
17
245
|
//# sourceMappingURL=server.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/posthog/server.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,WAA2C,EAC3C,OAAO,GAAiC,EAAE;IAE1C,IAAI,CAAC,WAAW;QAAE,OAAO,SAAS,CAAA;IAClC,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAA;IAChD,OAAO,IAAI,OAAO,CAAC,WAAW,CAAC,YAAY,EAAE;QAC3C,IAAI,EAAE,WAAW,CAAC,IAAI;QACtB,0BAA0B,EAAE,IAAI;QAChC,GAAG,OAAO;KACX,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAAC,MAA2B,EAAE,SAAS,GAAG,MAAM;IAC/F,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,IAAI,SAAS,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAA;IAClH,2GAA2G;IAC3G,MAAM,MAAM,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;AACpC,CAAC","sourcesContent":["import type { PostHog, PostHogOptions } from 'posthog-node'\nimport type { PostHogEnvironment } from './config.js'\n\nexport async function createPostHogServerClient(\n environment: PostHogEnvironment | undefined,\n options: Omit<PostHogOptions, 'host'> = {},\n): Promise<PostHog | undefined> {\n if (!environment) return undefined\n const { PostHog } = await import('posthog-node')\n return new PostHog(environment.projectToken, {\n host: environment.host,\n enableExceptionAutocapture: true,\n ...options,\n })\n}\n\nexport async function shutdownPostHogServerClient(client: PostHog | undefined, timeoutMs = 10_000) {\n if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 0) throw new Error('timeoutMs must be a non-negative integer')\n // oxlint-disable-next-line no-underscore-dangle -- posthog-node's async shutdown API is named `_shutdown`.\n await client?._shutdown(timeoutMs)\n}\n"]}
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/posthog/server.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PostHog, PostHogOptions } from 'posthog-node'\nimport type { RpcErrorContext, RpcLogger } from '../server/rpc.js'\nimport type { PostHogEnvironment } from './config.js'\nimport { postHogRequestContext } from './request.js'\n\ntype LogProvider = InstanceType<(typeof import('@opentelemetry/sdk-logs'))['LoggerProvider']>\n\nexport type PostHogLogValue = string | number | boolean | null | PostHogLogValue[]\n\nexport type PostHogLogRecord = {\n body: string\n severityText?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'\n timestamp?: number\n attributes?: Record<string, unknown>\n}\n\nexport type ManagedPostHogServerTelemetryOptions = {\n environment: PostHogEnvironment | undefined\n serviceName: string\n serviceVersion?: string\n deploymentEnvironment?: string\n clientOptions?: Omit<PostHogOptions, 'host'>\n onError?: (error: unknown) => void\n}\n\nexport type PostHogServerTelemetry = ReturnType<typeof createManagedPostHogServerTelemetry>\n\nexport type PostHogRpcLoggerOptions = {\n logError?: (error: unknown, context: RpcErrorContext) => void\n resolveAuthenticatedDistinctId?: (request: Request) => string | undefined | Promise<string | undefined>\n allowAnonymousDistinctId?: boolean\n fallbackDistinctId?: string\n}\n\ntype PostHogShutdownProcess = {\n pid: number\n on(signal: 'SIGINT' | 'SIGTERM', listener: () => void): void\n off(signal: 'SIGINT' | 'SIGTERM', listener: () => void): void\n kill(pid: number, signal: 'SIGINT' | 'SIGTERM'): void\n}\n\nexport async function createPostHogServerClient(\n environment: PostHogEnvironment | undefined,\n options: Omit<PostHogOptions, 'host'> = {},\n): Promise<PostHog | undefined> {\n if (!environment) return undefined\n const { PostHog } = await import('posthog-node')\n return new PostHog(environment.projectToken, {\n host: environment.host,\n enableExceptionAutocapture: true,\n ...options,\n })\n}\n\nexport async function shutdownPostHogServerClient(client: PostHog | undefined, timeoutMs = 10_000) {\n assertShutdownTimeout(timeoutMs)\n // oxlint-disable-next-line no-underscore-dangle -- posthog-node's async shutdown API is named `_shutdown`.\n await client?._shutdown(timeoutMs)\n}\n\nexport function createManagedPostHogServerTelemetry(options: ManagedPostHogServerTelemetryOptions) {\n let client: Promise<PostHog | undefined> | undefined\n let logProvider: Promise<LogProvider | undefined> | undefined\n let closed = false\n\n const report = (error: unknown) => {\n try {\n options.onError?.(error)\n } catch {}\n }\n\n const getClient = () => {\n if (closed || !options.environment) return undefined\n if (!client) {\n const pending = createPostHogServerClient(options.environment, options.clientOptions)\n client = pending\n void pending.catch(() => {\n if (client === pending) client = undefined\n })\n }\n return client\n }\n\n const getLogProvider = () => {\n if (closed || !options.environment) return undefined\n if (!logProvider) {\n const pending = createPostHogLogProvider(options)\n logProvider = pending\n void pending.catch(() => {\n if (logProvider === pending) logProvider = undefined\n })\n }\n return logProvider\n }\n\n const safely = async (work: () => void | Promise<void>) => {\n if (closed || !options.environment) return\n try {\n await work()\n } catch (error) {\n report(error)\n }\n }\n\n return {\n async start() {\n await safely(async () => {\n await Promise.all([getClient(), getLogProvider()])\n })\n },\n async capture(distinctId: string, event: string, properties?: Record<string, unknown>) {\n await safely(async () => {\n const value = await getClient()\n value?.capture({ distinctId, event, ...(properties ? { properties } : {}) })\n })\n },\n async exception(error: unknown, distinctId = 'server', properties?: Record<string, unknown>) {\n await safely(async () => {\n const value = await getClient()\n value?.captureException(error, distinctId, properties)\n })\n },\n async log(record: PostHogLogRecord) {\n await safely(async () => {\n const provider = await getLogProvider()\n const attributes = boundedAttributes(record.attributes)\n provider?.getLogger(options.serviceName).emit({\n body: boundedString(record.body),\n ...(record.severityText ? { severityText: record.severityText } : {}),\n ...(record.timestamp === undefined ? {} : { timestamp: record.timestamp }),\n ...(attributes ? { attributes } : {}),\n })\n })\n },\n async shutdown(timeoutMs = 10_000) {\n if (closed) return\n assertShutdownTimeout(timeoutMs)\n closed = true\n const results = await Promise.allSettled([\n client?.then((value) => shutdownPostHogServerClient(value, timeoutMs)),\n logProvider?.then((value) => shutdownPostHogLogProvider(value, timeoutMs)),\n ])\n for (const result of results) if (result.status === 'rejected') report(result.reason)\n },\n }\n}\n\nexport function createPostHogRpcLogger(telemetry: PostHogServerTelemetry, options: PostHogRpcLoggerOptions = {}): RpcLogger {\n return async (error, context, request) => {\n try {\n options.logError?.(error, context)\n } catch {}\n let authenticatedDistinctId: string | undefined\n if (request && options.resolveAuthenticatedDistinctId) {\n try {\n authenticatedDistinctId = await options.resolveAuthenticatedDistinctId(request)\n } catch {}\n }\n const correlation = request\n ? postHogRequestContext(request, {\n ...(authenticatedDistinctId ? { authenticatedDistinctId } : {}),\n ...(options.allowAnonymousDistinctId === undefined ? {} : { allowAnonymousDistinctId: options.allowAnonymousDistinctId }),\n })\n : { properties: {} }\n const distinctId = correlation.distinctId ?? options.fallbackDistinctId ?? 'server'\n const properties = {\n ...correlation.properties,\n ...(context.method ? { request_method: context.method } : {}),\n ...(context.path ? { request_path: context.path } : {}),\n }\n await Promise.all([\n telemetry.exception(error, distinctId, properties),\n telemetry.log({\n body: 'server function failed',\n severityText: 'error',\n attributes: { ...properties, posthogDistinctId: distinctId },\n }),\n ])\n }\n}\n\nexport function installPostHogServerTelemetryShutdown(\n telemetry: Pick<PostHogServerTelemetry, 'shutdown'>,\n target: PostHogShutdownProcess = process,\n) {\n let shuttingDown = false\n const listeners = new Map<'SIGINT' | 'SIGTERM', () => void>()\n const remove = () => {\n for (const [signal, listener] of listeners) target.off(signal, listener)\n listeners.clear()\n }\n for (const signal of ['SIGINT', 'SIGTERM'] as const) {\n const listener = () => {\n if (shuttingDown) return\n shuttingDown = true\n void telemetry\n .shutdown()\n .catch(() => undefined)\n .finally(() => {\n remove()\n target.kill(target.pid, signal)\n })\n }\n listeners.set(signal, listener)\n target.on(signal, listener)\n }\n return remove\n}\n\nasync function createPostHogLogProvider(options: ManagedPostHogServerTelemetryOptions): Promise<LogProvider | undefined> {\n if (!options.environment) return undefined\n const [{ OTLPLogExporter }, { resourceFromAttributes }, { BatchLogRecordProcessor, LoggerProvider }] = await Promise.all([\n import('@opentelemetry/exporter-logs-otlp-http'),\n import('@opentelemetry/resources'),\n import('@opentelemetry/sdk-logs'),\n ])\n const exporter = new OTLPLogExporter({\n url: `${options.environment.host.replace(/\\/$/, '')}/i/v1/logs`,\n headers: { Authorization: `Bearer ${options.environment.projectToken}` },\n })\n return new LoggerProvider({\n resource: resourceFromAttributes({\n 'service.name': options.serviceName,\n ...(options.serviceVersion ? { 'service.version': options.serviceVersion } : {}),\n ...(options.deploymentEnvironment ? { 'deployment.environment': options.deploymentEnvironment } : {}),\n }),\n processors: [new BatchLogRecordProcessor({ exporter })],\n })\n}\n\nfunction boundedAttributes(attributes: Record<string, unknown> | undefined) {\n if (!attributes) return undefined\n return Object.fromEntries(\n Object.entries(attributes)\n .slice(0, 64)\n .map(([key, value]) => [key.slice(0, 128), boundedLogValue(value, 0)])\n .filter((entry): entry is [string, PostHogLogValue] => entry[1] !== undefined),\n )\n}\n\nfunction boundedLogValue(value: unknown, depth: number): PostHogLogValue | undefined {\n if (value === null || typeof value === 'number' || typeof value === 'boolean') return value\n if (typeof value === 'string') return boundedString(value)\n if (depth >= 3) return '[Truncated]'\n if (Array.isArray(value)) {\n return value\n .slice(0, 32)\n .map((entry) => boundedLogValue(entry, depth + 1))\n .filter((entry): entry is PostHogLogValue => entry !== undefined)\n }\n if (value === undefined) return undefined\n try {\n return boundedString(JSON.stringify(value))\n } catch {\n return '[Unserializable value]'\n }\n}\n\nfunction boundedString(value: string) {\n return value.slice(0, 2_048)\n}\n\nasync function shutdownPostHogLogProvider(provider: LogProvider | undefined, timeoutMs: number) {\n if (!provider) return\n let timeout: ReturnType<typeof setTimeout> | undefined\n try {\n await Promise.race([\n provider.shutdown(),\n new Promise<never>((_resolve, reject) => {\n timeout = setTimeout(() => reject(new Error(`PostHog log shutdown timed out after ${timeoutMs}ms`)), timeoutMs)\n timeout.unref?.()\n }),\n ])\n } finally {\n if (timeout) clearTimeout(timeout)\n }\n}\n\nfunction assertShutdownTimeout(timeoutMs: number) {\n if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 0) throw new Error('timeoutMs must be a non-negative integer')\n}\n"]}
|
package/dist/preview/cli.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { previewStatusFromEnvironment } from './environment.js';
|
|
2
|
+
import { runDokployPreviewCli } from './dokploy-cli.js';
|
|
2
3
|
import { reportPreviewStatus } from './github.js';
|
|
3
4
|
export async function runPreviewCli(arguments_) {
|
|
5
|
+
if (arguments_[0] === 'dokploy') {
|
|
6
|
+
await runDokployPreviewCli(arguments_.slice(1));
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
4
9
|
const { options, status } = previewStatusFromEnvironment(arguments_[0]);
|
|
5
10
|
await reportPreviewStatus(options, status);
|
|
6
11
|
console.log(`Preview status set to ${status.state}`);
|
package/dist/preview/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/preview/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,4BAA4B,EAAE,MAAM,kBAAkB,CAAA;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEjD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,UAAoB;IACtD,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,4BAA4B,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAA;IACvE,MAAM,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IAC1C,OAAO,CAAC,GAAG,CAAC,yBAAyB,MAAM,CAAC,KAAK,EAAE,CAAC,CAAA;AACtD,CAAC","sourcesContent":["import { previewStatusFromEnvironment } from './environment.js'\nimport { reportPreviewStatus } from './github.js'\n\nexport async function runPreviewCli(arguments_: string[]): Promise<void> {\n const { options, status } = previewStatusFromEnvironment(arguments_[0])\n await reportPreviewStatus(options, status)\n console.log(`Preview status set to ${status.state}`)\n}\n"]}
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/preview/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,4BAA4B,EAAE,MAAM,kBAAkB,CAAA;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEjD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,UAAoB;IACtD,IAAI,UAAU,CAAC,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;QAChC,MAAM,oBAAoB,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;QAC/C,OAAM;IACR,CAAC;IACD,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,4BAA4B,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAA;IACvE,MAAM,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IAC1C,OAAO,CAAC,GAAG,CAAC,yBAAyB,MAAM,CAAC,KAAK,EAAE,CAAC,CAAA;AACtD,CAAC","sourcesContent":["import { previewStatusFromEnvironment } from './environment.js'\nimport { runDokployPreviewCli } from './dokploy-cli.js'\nimport { reportPreviewStatus } from './github.js'\n\nexport async function runPreviewCli(arguments_: string[]): Promise<void> {\n if (arguments_[0] === 'dokploy') {\n await runDokployPreviewCli(arguments_.slice(1))\n return\n }\n const { options, status } = previewStatusFromEnvironment(arguments_[0])\n await reportPreviewStatus(options, status)\n console.log(`Preview status set to ${status.state}`)\n}\n"]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { appendFileSync } from 'node:fs';
|
|
3
|
+
import { dokployPreviewFromEnvironment, pullRequestNumber } from './dokploy.js';
|
|
4
|
+
export async function runDokployPreviewCli(arguments_, environment = process.env) {
|
|
5
|
+
const command = arguments_[0];
|
|
6
|
+
const { config, manager } = dokployPreviewFromEnvironment(environment);
|
|
7
|
+
if (command === 'deploy') {
|
|
8
|
+
const prNumber = pullRequestNumber(required(environment, 'PR_NUMBER'));
|
|
9
|
+
const preview = await manager.deploy({
|
|
10
|
+
prNumber,
|
|
11
|
+
image: required(environment, 'PREVIEW_IMAGE'),
|
|
12
|
+
environment: renderPreviewEnvironment(required(environment, 'PREVIEW_ENVIRONMENT'), prNumber),
|
|
13
|
+
...(config.registry ? { registry: config.registry } : {}),
|
|
14
|
+
});
|
|
15
|
+
if (environment.GITHUB_OUTPUT)
|
|
16
|
+
appendFileSync(environment.GITHUB_OUTPUT, `preview-url=${preview.url}\n`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (command === 'delete') {
|
|
20
|
+
const prNumber = pullRequestNumber(required(environment, 'PR_NUMBER'));
|
|
21
|
+
console.log((await manager.delete(prNumber)) ? `Deleted ${config.applicationPrefix}-pr-${prNumber}` : `No preview for pr-${prNumber}`);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (command === 'prune') {
|
|
25
|
+
const open = new Set((environment.OPEN_PR_NUMBERS ?? '').split(/\s+/).filter(Boolean).map(pullRequestNumber));
|
|
26
|
+
for (const prNumber of await manager.prune(open))
|
|
27
|
+
console.log(`Deleted ${config.applicationPrefix}-pr-${prNumber}`);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
throw new Error('usage: ras preview dokploy <deploy|delete|prune>');
|
|
31
|
+
}
|
|
32
|
+
export function renderPreviewEnvironment(template, prNumber) {
|
|
33
|
+
return template
|
|
34
|
+
.replaceAll('{{PR_NUMBER}}', pullRequestNumber(prNumber))
|
|
35
|
+
.replaceAll('{{RANDOM_HEX_32}}', () => randomBytes(32).toString('hex'));
|
|
36
|
+
}
|
|
37
|
+
function required(environment, name) {
|
|
38
|
+
const value = optional(environment, name);
|
|
39
|
+
if (!value)
|
|
40
|
+
throw new Error(`${name} is required`);
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
function optional(environment, name) {
|
|
44
|
+
return environment[name]?.trim() || undefined;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=dokploy-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dokploy-cli.js","sourceRoot":"","sources":["../../src/preview/dokploy-cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AACxC,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;IAEtE,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,iBAAiB,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAAA;QACtE,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC;YACnC,QAAQ;YACR,KAAK,EAAE,QAAQ,CAAC,WAAW,EAAE,eAAe,CAAC;YAC7C,WAAW,EAAE,wBAAwB,CAAC,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,EAAE,QAAQ,CAAC;YAC7F,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC1D,CAAC,CAAA;QACF,IAAI,WAAW,CAAC,aAAa;YAAE,cAAc,CAAC,WAAW,CAAC,aAAa,EAAE,eAAe,OAAO,CAAC,GAAG,IAAI,CAAC,CAAA;QACxG,OAAM;IACR,CAAC;IACD,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,iBAAiB,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAAA;QACtE,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,MAAM,CAAC,iBAAiB,OAAO,QAAQ,EAAE,CAAC,CAAC,CAAC,qBAAqB,QAAQ,EAAE,CAAC,CAAA;QACtI,OAAM;IACR,CAAC;IACD,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAA;QAC7G,KAAK,MAAM,QAAQ,IAAI,MAAM,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,OAAO,CAAC,GAAG,CAAC,WAAW,MAAM,CAAC,iBAAiB,OAAO,QAAQ,EAAE,CAAC,CAAA;QACnH,OAAM;IACR,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAA;AACrE,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,QAAgB,EAAE,QAAgB;IACzE,OAAO,QAAQ;SACZ,UAAU,CAAC,eAAe,EAAE,iBAAiB,CAAC,QAAQ,CAAC,CAAC;SACxD,UAAU,CAAC,mBAAmB,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAA;AAC3E,CAAC;AAED,SAAS,QAAQ,CAAC,WAA8B,EAAE,IAAY;IAC5D,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC,CAAA;IACzC,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,cAAc,CAAC,CAAA;IAClD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAS,QAAQ,CAAC,WAA8B,EAAE,IAAY;IAC5D,OAAO,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,SAAS,CAAA;AAC/C,CAAC","sourcesContent":["import { randomBytes } from 'node:crypto'\nimport { appendFileSync } from 'node:fs'\nimport { dokployPreviewFromEnvironment, pullRequestNumber } from './dokploy.js'\n\nexport async function runDokployPreviewCli(arguments_: string[], environment: NodeJS.ProcessEnv = process.env) {\n const command = arguments_[0]\n const { config, manager } = dokployPreviewFromEnvironment(environment)\n\n if (command === 'deploy') {\n const prNumber = pullRequestNumber(required(environment, 'PR_NUMBER'))\n const preview = await manager.deploy({\n prNumber,\n image: required(environment, 'PREVIEW_IMAGE'),\n environment: renderPreviewEnvironment(required(environment, 'PREVIEW_ENVIRONMENT'), prNumber),\n ...(config.registry ? { registry: config.registry } : {}),\n })\n if (environment.GITHUB_OUTPUT) appendFileSync(environment.GITHUB_OUTPUT, `preview-url=${preview.url}\\n`)\n return\n }\n if (command === 'delete') {\n const prNumber = pullRequestNumber(required(environment, 'PR_NUMBER'))\n console.log((await manager.delete(prNumber)) ? `Deleted ${config.applicationPrefix}-pr-${prNumber}` : `No preview for pr-${prNumber}`)\n return\n }\n if (command === 'prune') {\n const open = new Set((environment.OPEN_PR_NUMBERS ?? '').split(/\\s+/).filter(Boolean).map(pullRequestNumber))\n for (const prNumber of await manager.prune(open)) console.log(`Deleted ${config.applicationPrefix}-pr-${prNumber}`)\n return\n }\n throw new Error('usage: ras preview dokploy <deploy|delete|prune>')\n}\n\nexport function renderPreviewEnvironment(template: string, prNumber: string) {\n return template\n .replaceAll('{{PR_NUMBER}}', pullRequestNumber(prNumber))\n .replaceAll('{{RANDOM_HEX_32}}', () => randomBytes(32).toString('hex'))\n}\n\nfunction required(environment: NodeJS.ProcessEnv, name: string) {\n const value = optional(environment, name)\n if (!value) throw new Error(`${name} is required`)\n return value\n}\n\nfunction optional(environment: NodeJS.ProcessEnv, name: string) {\n return environment[name]?.trim() || undefined\n}\n"]}
|
|
@@ -2,6 +2,22 @@ export type DokployApplication = {
|
|
|
2
2
|
applicationId: string;
|
|
3
3
|
name: string;
|
|
4
4
|
};
|
|
5
|
+
export declare function dokployPreviewFromEnvironment(environment?: NodeJS.ProcessEnv): {
|
|
6
|
+
config: {
|
|
7
|
+
url: string;
|
|
8
|
+
apiKey: string;
|
|
9
|
+
environmentId: string;
|
|
10
|
+
applicationPrefix: string;
|
|
11
|
+
domain: string;
|
|
12
|
+
port: number;
|
|
13
|
+
healthPath: string | undefined;
|
|
14
|
+
registry?: {
|
|
15
|
+
username: string;
|
|
16
|
+
password: string;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
manager: DokployPreviewManager;
|
|
20
|
+
};
|
|
5
21
|
export type DokployClientOptions = {
|
|
6
22
|
url: string;
|
|
7
23
|
apiKey: string;
|
|
@@ -18,6 +34,7 @@ export declare class DokployClient {
|
|
|
18
34
|
api<T = unknown>(procedure: string, options?: {
|
|
19
35
|
query?: Record<string, string>;
|
|
20
36
|
body?: unknown;
|
|
37
|
+
signal?: AbortSignal;
|
|
21
38
|
}): Promise<T>;
|
|
22
39
|
applications(): Promise<DokployApplication[]>;
|
|
23
40
|
application(name: string): Promise<DokployApplication | undefined>;
|
package/dist/preview/dokploy.js
CHANGED
|
@@ -1,3 +1,38 @@
|
|
|
1
|
+
export function dokployPreviewFromEnvironment(environment = process.env) {
|
|
2
|
+
const username = optionalEnvironment(environment, 'PREVIEW_REGISTRY_USERNAME');
|
|
3
|
+
const password = optionalEnvironment(environment, 'PREVIEW_REGISTRY_PASSWORD');
|
|
4
|
+
if (Boolean(username) !== Boolean(password))
|
|
5
|
+
throw new Error('preview registry username and password must be configured together');
|
|
6
|
+
const port = Number(requiredEnvironment(environment, 'PREVIEW_PORT'));
|
|
7
|
+
if (!Number.isInteger(port) || port < 1 || port > 65_535)
|
|
8
|
+
throw new Error('PREVIEW_PORT must be a valid port');
|
|
9
|
+
const applicationPrefix = requiredEnvironment(environment, 'PREVIEW_APPLICATION_PREFIX');
|
|
10
|
+
if (!/^[a-z\d](?:[a-z\d-]*[a-z\d])?$/.test(applicationPrefix))
|
|
11
|
+
throw new Error('PREVIEW_APPLICATION_PREFIX must be a slug');
|
|
12
|
+
const domain = requiredEnvironment(environment, 'PREVIEW_DOMAIN');
|
|
13
|
+
previewHostname(`pr-1.${domain}`);
|
|
14
|
+
const config = {
|
|
15
|
+
url: requiredEnvironment(environment, 'DOKPLOY_URL'),
|
|
16
|
+
apiKey: requiredEnvironment(environment, 'DOKPLOY_API_KEY'),
|
|
17
|
+
environmentId: requiredEnvironment(environment, 'DOKPLOY_ENVIRONMENT_ID'),
|
|
18
|
+
applicationPrefix,
|
|
19
|
+
domain,
|
|
20
|
+
port,
|
|
21
|
+
healthPath: optionalEnvironment(environment, 'PREVIEW_HEALTH_PATH'),
|
|
22
|
+
...(username && password ? { registry: { username, password } } : {}),
|
|
23
|
+
};
|
|
24
|
+
const client = new DokployClient(config);
|
|
25
|
+
return {
|
|
26
|
+
config,
|
|
27
|
+
manager: new DokployPreviewManager({
|
|
28
|
+
client,
|
|
29
|
+
applicationName: (prNumber) => `${applicationPrefix}-pr-${prNumber}`,
|
|
30
|
+
hostname: (prNumber) => `pr-${prNumber}.${domain}`,
|
|
31
|
+
port,
|
|
32
|
+
...(config.healthPath ? { healthPath: config.healthPath } : {}),
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
1
36
|
export class DokployClient {
|
|
2
37
|
options;
|
|
3
38
|
request;
|
|
@@ -21,6 +56,7 @@ export class DokployClient {
|
|
|
21
56
|
'x-api-key': this.options.apiKey,
|
|
22
57
|
...(options.body === undefined ? {} : { 'content-type': 'application/json' }),
|
|
23
58
|
},
|
|
59
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
24
60
|
...(options.body === undefined ? {} : { body: JSON.stringify(options.body) }),
|
|
25
61
|
});
|
|
26
62
|
const text = await response.text();
|
|
@@ -140,10 +176,21 @@ export class DokployPreviewManager {
|
|
|
140
176
|
// Polling is intentionally sequential; each response determines whether another request is needed.
|
|
141
177
|
// oxlint-disable-next-line no-await-in-loop
|
|
142
178
|
await this.pause(this.options.pollIntervalMs ?? 5_000);
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
179
|
+
let applicationStatus;
|
|
180
|
+
try {
|
|
181
|
+
// Polling is intentionally sequential; each response determines whether another request is needed.
|
|
182
|
+
// oxlint-disable-next-line no-await-in-loop
|
|
183
|
+
const details = await this.options.client.api('application.one', {
|
|
184
|
+
query: { applicationId },
|
|
185
|
+
signal: AbortSignal.timeout(Math.max(1, deadline - this.now())),
|
|
186
|
+
});
|
|
187
|
+
applicationStatus = details.applicationStatus;
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
if (error instanceof DOMException && error.name === 'TimeoutError')
|
|
191
|
+
break;
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
147
194
|
if (applicationStatus === 'done')
|
|
148
195
|
return;
|
|
149
196
|
if (applicationStatus === 'error')
|
|
@@ -158,7 +205,7 @@ export class DokployPreviewManager {
|
|
|
158
205
|
try {
|
|
159
206
|
// Polling is intentionally sequential; each response determines whether another request is needed.
|
|
160
207
|
// oxlint-disable-next-line no-await-in-loop
|
|
161
|
-
const response = await this.request(url);
|
|
208
|
+
const response = await this.request(url, { signal: AbortSignal.timeout(Math.max(1, deadline - this.now())) });
|
|
162
209
|
if (response.status === 200)
|
|
163
210
|
return;
|
|
164
211
|
lastFailure = `status ${response.status}`;
|
|
@@ -189,4 +236,13 @@ function previewApplicationPrNumber(name, applicationName) {
|
|
|
189
236
|
const prNumber = match?.[1];
|
|
190
237
|
return prNumber && applicationName(prNumber) === name ? prNumber : undefined;
|
|
191
238
|
}
|
|
239
|
+
function requiredEnvironment(environment, name) {
|
|
240
|
+
const value = optionalEnvironment(environment, name);
|
|
241
|
+
if (!value)
|
|
242
|
+
throw new Error(`${name} is required`);
|
|
243
|
+
return value;
|
|
244
|
+
}
|
|
245
|
+
function optionalEnvironment(environment, name) {
|
|
246
|
+
return environment[name]?.trim() || undefined;
|
|
247
|
+
}
|
|
192
248
|
//# sourceMappingURL=dokploy.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dokploy.js","sourceRoot":"","sources":["../../src/preview/dokploy.ts"],"names":[],"mappings":"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","sourcesContent":["export type DokployApplication = { applicationId: string; name: string }\n\nexport type DokployClientOptions = {\n url: string\n apiKey: string\n environmentId: string\n fetch?: typeof fetch\n log?: (message: string) => void\n}\n\nexport class DokployClient {\n private readonly request: typeof fetch\n private readonly log: (message: string) => void\n\n constructor(private readonly options: DokployClientOptions) {\n this.request = options.fetch ?? fetch\n this.log = options.log ?? console.log\n }\n\n get environmentId() {\n return this.options.environmentId\n }\n\n async api<T = unknown>(procedure: string, options: { query?: Record<string, string>; body?: unknown } = {}): Promise<T> {\n const url = new URL(`${this.options.url.replace(/\\/$/, '')}/api/${procedure}`)\n for (const [key, value] of Object.entries(options.query ?? {})) url.searchParams.set(key, value)\n this.log(`→ ${procedure}`)\n const response = await this.request(url, {\n method: options.body === undefined ? 'GET' : 'POST',\n headers: {\n 'x-api-key': this.options.apiKey,\n ...(options.body === undefined ? {} : { 'content-type': 'application/json' }),\n },\n ...(options.body === undefined ? {} : { body: JSON.stringify(options.body) }),\n })\n const text = await response.text()\n if (!response.ok) throw new Error(`${procedure} failed with ${response.status}: ${text.slice(0, 500)}`)\n if (!text) return undefined as T\n try {\n return JSON.parse(text) as T\n } catch {\n throw new Error(`${procedure} returned ${response.status} with a non-JSON body: ${text.slice(0, 200)}`)\n }\n }\n\n async applications() {\n const environment = await this.api<{ applications?: DokployApplication[] } | undefined>('environment.one', {\n query: { environmentId: this.options.environmentId },\n })\n if (!environment) throw new Error('environment.one returned an empty response')\n return environment.applications ?? []\n }\n\n async application(name: string) {\n return (await this.applications()).find((application) => application.name === name)\n }\n}\n\nexport type DokployPreviewOptions = {\n client: DokployClient\n applicationName: (prNumber: string) => string\n hostname: (prNumber: string) => string\n port: number\n healthPath?: string\n deploymentTimeoutMs?: number\n healthTimeoutMs?: number\n pollIntervalMs?: number\n fetch?: typeof fetch\n sleep?: (milliseconds: number) => Promise<void>\n now?: () => number\n log?: (message: string) => void\n}\n\nexport type DeployPreviewOptions = {\n prNumber: string\n image: string\n environment: string\n registry?: { username: string; password: string }\n configure?: (context: { applicationId: string; client: DokployClient; host: string }) => void | Promise<void>\n}\n\nexport class DokployPreviewManager {\n private readonly request: typeof fetch\n private readonly pause: (milliseconds: number) => Promise<void>\n private readonly log: (message: string) => void\n private readonly now: () => number\n\n constructor(private readonly options: DokployPreviewOptions) {\n this.request = options.fetch ?? fetch\n this.pause = options.sleep ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)))\n this.log = options.log ?? console.log\n this.now = options.now ?? Date.now\n }\n\n async deploy(options: DeployPreviewOptions) {\n const prNumber = pullRequestNumber(options.prNumber)\n const name = this.options.applicationName(prNumber)\n const host = previewHostname(this.options.hostname(prNumber))\n let application = await this.options.client.application(name)\n if (!application) {\n await this.options.client.api('application.create', {\n body: { name, appName: name, environmentId: this.options.client.environmentId },\n })\n application = await this.options.client.application(name)\n if (!application) throw new Error(`Dokploy did not report ${name} after creating it`)\n }\n const applicationId = application.applicationId\n const details = await this.options.client.api<{ domains?: { host: string }[] } | undefined>('application.one', {\n query: { applicationId },\n })\n if (!details?.domains?.some((domain) => domain.host === host)) {\n await this.options.client.api('domain.create', {\n body: {\n applicationId,\n host,\n path: '/',\n port: this.options.port,\n https: true,\n certificateType: 'letsencrypt',\n domainType: 'application',\n },\n })\n }\n await options.configure?.({ applicationId, client: this.options.client, host })\n await this.options.client.api('application.saveDockerProvider', {\n body: {\n applicationId,\n dockerImage: options.image,\n username: options.registry?.username ?? null,\n password: options.registry?.password ?? null,\n registryUrl: options.registry ? options.image.split('/')[0] : null,\n },\n })\n await this.options.client.api('application.saveEnvironment', {\n body: { applicationId, env: options.environment, buildArgs: null, buildSecrets: null, createEnvFile: false },\n })\n await this.options.client.api('application.deploy', { body: { applicationId } })\n await this.waitForDeployment(applicationId)\n const url = `https://${host}`\n await this.waitForHealth(new URL(this.options.healthPath ?? '/api/health', url).toString())\n this.log(`Preview ready at ${url}`)\n return { applicationId, host, url }\n }\n\n async delete(prNumber: string, beforeDelete?: (application: DokployApplication | undefined) => void | Promise<void>) {\n const name = this.options.applicationName(pullRequestNumber(prNumber))\n const application = await this.options.client.application(name)\n await beforeDelete?.(application)\n if (!application) return false\n await this.options.client.api('application.delete', { body: { applicationId: application.applicationId } })\n return true\n }\n\n async prune(\n openPullRequests: ReadonlySet<string>,\n beforeDelete?: (prNumber: string, application: DokployApplication) => void | Promise<void>,\n ) {\n const deleted: string[] = []\n for (const application of await this.options.client.applications()) {\n const prNumber = previewApplicationPrNumber(application.name, this.options.applicationName)\n if (!prNumber || openPullRequests.has(prNumber)) continue\n // Dokploy mutations are intentionally ordered to avoid overwhelming one environment.\n // oxlint-disable-next-line no-await-in-loop\n await beforeDelete?.(prNumber, application)\n // oxlint-disable-next-line no-await-in-loop\n await this.options.client.api('application.delete', { body: { applicationId: application.applicationId } })\n deleted.push(prNumber)\n }\n return deleted\n }\n\n private async waitForDeployment(applicationId: string) {\n const deadline = this.now() + (this.options.deploymentTimeoutMs ?? 600_000)\n while (this.now() < deadline) {\n // Polling is intentionally sequential; each response determines whether another request is needed.\n // oxlint-disable-next-line no-await-in-loop\n await this.pause(this.options.pollIntervalMs ?? 5_000)\n // oxlint-disable-next-line no-await-in-loop\n const { applicationStatus } = await this.options.client.api<{ applicationStatus: string }>('application.one', {\n query: { applicationId },\n })\n if (applicationStatus === 'done') return\n if (applicationStatus === 'error') throw new Error('Dokploy reported a failed deployment')\n }\n throw new Error('Timed out waiting for the Dokploy deployment to finish')\n }\n\n private async waitForHealth(url: string) {\n const deadline = this.now() + (this.options.healthTimeoutMs ?? 300_000)\n let lastFailure = 'no response'\n while (this.now() < deadline) {\n try {\n // Polling is intentionally sequential; each response determines whether another request is needed.\n // oxlint-disable-next-line no-await-in-loop\n const response = await this.request(url)\n if (response.status === 200) return\n lastFailure = `status ${response.status}`\n } catch (error) {\n lastFailure = error instanceof Error ? error.message : String(error)\n }\n // oxlint-disable-next-line no-await-in-loop\n await this.pause(this.options.pollIntervalMs ?? 5_000)\n }\n throw new Error(`Timed out waiting for ${url} (${lastFailure})`)\n }\n}\n\nexport function pullRequestNumber(value: string) {\n if (!/^\\d+$/.test(value)) throw new Error('pull request number must contain only digits')\n return value\n}\n\nexport function previewHostname(value: string) {\n const parsed = new URL(`https://${value}`)\n if (parsed.hostname !== value || parsed.port || parsed.username || parsed.password || parsed.pathname !== '/') {\n throw new Error('preview hostname must be a bare hostname')\n }\n return value\n}\n\nfunction previewApplicationPrNumber(name: string, applicationName: (prNumber: string) => string) {\n const match = /^(\\d+)$/.exec(name.match(/(\\d+)$/)?.[1] ?? '')\n const prNumber = match?.[1]\n return prNumber && applicationName(prNumber) === name ? prNumber : undefined\n}\n"]}
|
|
1
|
+
{"version":3,"file":"dokploy.js","sourceRoot":"","sources":["../../src/preview/dokploy.ts"],"names":[],"mappings":"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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"]}
|
package/dist/server/rpc.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
export type
|
|
1
|
+
export type RpcErrorContext = {
|
|
2
2
|
method?: string;
|
|
3
3
|
path?: string;
|
|
4
|
-
}
|
|
4
|
+
};
|
|
5
|
+
export type RpcLogger = (error: unknown, context: RpcErrorContext, request?: Request) => void | Promise<void>;
|
|
5
6
|
export type RpcOptions = {
|
|
6
7
|
getRequest?: () => Request;
|
|
7
8
|
requireMutation?: (request: Request) => void;
|
package/dist/server/rpc.js
CHANGED
|
@@ -6,7 +6,11 @@ export function createRpc(options = {}) {
|
|
|
6
6
|
catch (error) {
|
|
7
7
|
if (error instanceof Response)
|
|
8
8
|
throw new Error((await error.text()) || `request failed (${error.status})`, { cause: error });
|
|
9
|
-
|
|
9
|
+
const request = resolveRequest(options.getRequest);
|
|
10
|
+
try {
|
|
11
|
+
await options.logError?.(error, requestContext(request), request);
|
|
12
|
+
}
|
|
13
|
+
catch { }
|
|
10
14
|
throw error;
|
|
11
15
|
}
|
|
12
16
|
}
|
|
@@ -20,13 +24,15 @@ export function createRpc(options = {}) {
|
|
|
20
24
|
}
|
|
21
25
|
return { rpc, mutationRpc };
|
|
22
26
|
}
|
|
23
|
-
function
|
|
27
|
+
function resolveRequest(getRequest) {
|
|
24
28
|
try {
|
|
25
|
-
|
|
26
|
-
return request ? { method: request.method, path: new URL(request.url).pathname } : {};
|
|
29
|
+
return getRequest?.();
|
|
27
30
|
}
|
|
28
31
|
catch {
|
|
29
|
-
return
|
|
32
|
+
return undefined;
|
|
30
33
|
}
|
|
31
34
|
}
|
|
35
|
+
function requestContext(request) {
|
|
36
|
+
return request ? { method: request.method, path: new URL(request.url).pathname } : {};
|
|
37
|
+
}
|
|
32
38
|
//# sourceMappingURL=rpc.js.map
|
package/dist/server/rpc.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc.js","sourceRoot":"","sources":["../../src/server/rpc.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"rpc.js","sourceRoot":"","sources":["../../src/server/rpc.ts"],"names":[],"mappings":"AASA,MAAM,UAAU,SAAS,CAAC,OAAO,GAAe,EAAE;IAChD,KAAK,UAAU,GAAG,CAAI,IAA0B;QAC9C,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,EAAE,CAAA;QACrB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,QAAQ;gBAAE,MAAM,IAAI,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,mBAAmB,KAAK,CAAC,MAAM,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAA;YAC5H,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;YAClD,IAAI,CAAC;gBACH,MAAM,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAA;YACnE,CAAC;YAAC,MAAM,CAAC,CAAA,CAAC;YACV,MAAM,KAAK,CAAA;QACb,CAAC;IACH,CAAC;IAED,SAAS,WAAW,CAAI,IAA0B,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,EAAE,EAAE;QAClF,OAAO,GAAG,CAAC,GAAG,EAAE;YACd,IAAI,CAAC,OAAO;gBAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAA;YAChE,OAAO,CAAC,eAAe,EAAE,CAAC,OAAO,CAAC,CAAA;YAClC,OAAO,IAAI,EAAE,CAAA;QACf,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,CAAA;AAC7B,CAAC;AAED,SAAS,cAAc,CAAC,UAAuC;IAC7D,IAAI,CAAC;QACH,OAAO,UAAU,EAAE,EAAE,CAAA;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,OAA4B;IAClD,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;AACvF,CAAC","sourcesContent":["export type RpcErrorContext = { method?: string; path?: string }\nexport type RpcLogger = (error: unknown, context: RpcErrorContext, request?: Request) => void | Promise<void>\n\nexport type RpcOptions = {\n getRequest?: () => Request\n requireMutation?: (request: Request) => void\n logError?: RpcLogger\n}\n\nexport function createRpc(options: RpcOptions = {}) {\n async function rpc<T>(work: () => Promise<T> | T): Promise<T> {\n try {\n return await work()\n } catch (error) {\n if (error instanceof Response) throw new Error((await error.text()) || `request failed (${error.status})`, { cause: error })\n const request = resolveRequest(options.getRequest)\n try {\n await options.logError?.(error, requestContext(request), request)\n } catch {}\n throw error\n }\n }\n\n function mutationRpc<T>(work: () => Promise<T> | T, request = options.getRequest?.()) {\n return rpc(() => {\n if (!request) throw new Error('mutation request is unavailable')\n options.requireMutation?.(request)\n return work()\n })\n }\n\n return { rpc, mutationRpc }\n}\n\nfunction resolveRequest(getRequest: (() => Request) | undefined) {\n try {\n return getRequest?.()\n } catch {\n return undefined\n }\n}\n\nfunction requestContext(request: Request | undefined): RpcErrorContext {\n return request ? { method: request.method, path: new URL(request.url).pathname } : {}\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ras-stack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.0",
|
|
4
4
|
"description": "Composable full-stack primitives shared across Richard Solomou's applications.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"authentication",
|
|
@@ -156,6 +156,10 @@
|
|
|
156
156
|
},
|
|
157
157
|
"devDependencies": {
|
|
158
158
|
"@changesets/cli": "^2.31.1",
|
|
159
|
+
"@opentelemetry/api-logs": "^0.221.0",
|
|
160
|
+
"@opentelemetry/exporter-logs-otlp-http": "^0.221.0",
|
|
161
|
+
"@opentelemetry/resources": "^2.10.0",
|
|
162
|
+
"@opentelemetry/sdk-logs": "^0.221.0",
|
|
159
163
|
"@playwright/test": "1.62.1",
|
|
160
164
|
"@posthog/react": "^1.10.3",
|
|
161
165
|
"@tanstack/react-query": "^5.101.4",
|
|
@@ -183,6 +187,10 @@
|
|
|
183
187
|
"vitest": "^4.1.10"
|
|
184
188
|
},
|
|
185
189
|
"peerDependencies": {
|
|
190
|
+
"@opentelemetry/api-logs": "^0.221.0",
|
|
191
|
+
"@opentelemetry/exporter-logs-otlp-http": "^0.221.0",
|
|
192
|
+
"@opentelemetry/resources": "^2.10.0",
|
|
193
|
+
"@opentelemetry/sdk-logs": "^0.221.0",
|
|
186
194
|
"@posthog/react": ">=1 <2",
|
|
187
195
|
"@tanstack/react-query": ">=5 <6",
|
|
188
196
|
"@tanstack/react-start": ">=1 <2",
|
|
@@ -197,6 +205,18 @@
|
|
|
197
205
|
"tus-js-client": ">=4 <5"
|
|
198
206
|
},
|
|
199
207
|
"peerDependenciesMeta": {
|
|
208
|
+
"@opentelemetry/api-logs": {
|
|
209
|
+
"optional": true
|
|
210
|
+
},
|
|
211
|
+
"@opentelemetry/exporter-logs-otlp-http": {
|
|
212
|
+
"optional": true
|
|
213
|
+
},
|
|
214
|
+
"@opentelemetry/resources": {
|
|
215
|
+
"optional": true
|
|
216
|
+
},
|
|
217
|
+
"@opentelemetry/sdk-logs": {
|
|
218
|
+
"optional": true
|
|
219
|
+
},
|
|
200
220
|
"@posthog/react": {
|
|
201
221
|
"optional": true
|
|
202
222
|
},
|