@elench/testkit 0.1.25 → 0.1.27

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.
@@ -0,0 +1,104 @@
1
+ import type { AuthAdapter, HeaderBuilder, HttpSuiteConfig } from "../index";
2
+
3
+ export interface LocalDatabaseConfig {
4
+ provider: "local";
5
+ image?: string;
6
+ password?: string;
7
+ reset?: boolean;
8
+ template?: {
9
+ inputs?: string[];
10
+ };
11
+ user?: string;
12
+ }
13
+
14
+ export interface LifecycleConfig {
15
+ cmd: string;
16
+ cwd?: string;
17
+ testkitCmd?: string;
18
+ testkitCwd?: string;
19
+ }
20
+
21
+ export interface ServiceConfig {
22
+ database?: LocalDatabaseConfig;
23
+ databaseFrom?: string;
24
+ dependsOn?: string[];
25
+ discovery?: {
26
+ roots?: string[];
27
+ };
28
+ envFile?: string;
29
+ envFiles?: string[];
30
+ local?: {
31
+ baseUrl: string;
32
+ cwd?: string;
33
+ env?: Record<string, string>;
34
+ port?: number;
35
+ readyTimeoutMs?: number;
36
+ readyUrl: string;
37
+ start: string;
38
+ };
39
+ migrate?: LifecycleConfig;
40
+ seed?: LifecycleConfig;
41
+ }
42
+
43
+ export interface TestkitSetup {
44
+ profiles?: {
45
+ http?: Record<string, HttpSuiteConfig>;
46
+ };
47
+ services?: Record<string, ServiceConfig>;
48
+ telemetry?: {
49
+ enabled?: boolean;
50
+ endpoint?: string;
51
+ timeoutMs?: number;
52
+ tokenEnv?: string;
53
+ };
54
+ }
55
+
56
+ export declare function defineTestkitSetup<T extends TestkitSetup>(setup: T): T;
57
+ export declare function defineHttpProfile<T extends HttpSuiteConfig>(profile: T): T;
58
+ export declare function service<T extends ServiceConfig>(config: T): T;
59
+ export declare function localDatabase(options?: Omit<LocalDatabaseConfig, "provider">): LocalDatabaseConfig;
60
+ export declare function lifecycle(cmd: string, options?: Omit<LifecycleConfig, "cmd">): LifecycleConfig;
61
+ export declare function goService(options: ServiceConfig["local"] & {
62
+ command?: string;
63
+ entrypoint?: string;
64
+ envFiles?: string[];
65
+ readyPath?: string;
66
+ }): ServiceConfig;
67
+ export declare function nextService(options: ServiceConfig["local"] & {
68
+ envFiles?: string[];
69
+ }): ServiceConfig;
70
+ export declare function tsxService(options: ServiceConfig["local"] & {
71
+ entry?: string;
72
+ envFiles?: string[];
73
+ readyPath?: string;
74
+ }): ServiceConfig;
75
+ export declare function clerkSessionProfile(options?: {
76
+ apiBase?: string;
77
+ needsAuth?: boolean;
78
+ secretKeyEnv?: string;
79
+ }): HttpSuiteConfig;
80
+ export declare function jsonSessionProfile(options?: {
81
+ body?: (context: { env: Record<string, string> }) => unknown;
82
+ cookieName?: string;
83
+ headers?: Record<string, string>;
84
+ loginPath?: string;
85
+ passwordEnv?: string;
86
+ successStatus?: number;
87
+ usernameEnv?: string;
88
+ }): HttpSuiteConfig;
89
+
90
+ export declare function registerRepoSetup(setup: unknown): void;
91
+ export declare function getRepoSetup(): unknown;
92
+ export declare function clearRepoSetup(): void;
93
+ export declare function registerRuntimeContext(context: unknown): void;
94
+ export declare function getRuntimeContext(): unknown;
95
+ export declare function clearRuntimeContext(): void;
96
+ export declare const runtimeHttp: {
97
+ del(url: string, body?: unknown, params?: unknown): unknown;
98
+ file(data: unknown, filename?: string, contentType?: string): unknown;
99
+ get(url: string, params?: unknown): unknown;
100
+ patch(url: string, body?: unknown, params?: unknown): unknown;
101
+ post(url: string, body?: unknown, params?: unknown): unknown;
102
+ put(url: string, body?: unknown, params?: unknown): unknown;
103
+ };
104
+ export declare function runtimeJson<T = unknown>(response: { body: string }): T;
@@ -0,0 +1,292 @@
1
+ import {
2
+ clearRepoSetup,
3
+ clearRuntimeContext,
4
+ getRepoSetup,
5
+ getRuntimeContext,
6
+ getRuntimeEnv,
7
+ registerRepoSetup,
8
+ registerRuntimeContext,
9
+ runtimeHttp,
10
+ runtimeJson,
11
+ } from "./runtime.mjs";
12
+
13
+ export function defineTestkitSetup(setup) {
14
+ return setup || {};
15
+ }
16
+
17
+ export function defineHttpProfile(profile) {
18
+ return profile || {};
19
+ }
20
+
21
+ export function service(config) {
22
+ return config || {};
23
+ }
24
+
25
+ export function localDatabase(options = {}) {
26
+ return {
27
+ provider: "local",
28
+ ...options,
29
+ };
30
+ }
31
+
32
+ export function lifecycle(cmd, options = {}) {
33
+ return {
34
+ cmd,
35
+ cwd: options.cwd,
36
+ testkitCmd: options.testkitCmd,
37
+ testkitCwd: options.testkitCwd,
38
+ };
39
+ }
40
+
41
+ export function goService(options = {}) {
42
+ const cwd = options.cwd || ".";
43
+ const port = requiredNumber(options.port, "goService port");
44
+ const readyPath = options.readyPath || "/health";
45
+ const baseUrl = options.baseUrl || "http://127.0.0.1:{port}";
46
+
47
+ return {
48
+ ...service(options),
49
+ local: {
50
+ cwd,
51
+ start: options.start || defaultGoStartCommand(options),
52
+ port,
53
+ baseUrl,
54
+ readyUrl: options.readyUrl || `${baseUrl}${readyPath}`,
55
+ readyTimeoutMs: options.readyTimeoutMs,
56
+ env: options.env || {},
57
+ },
58
+ };
59
+ }
60
+
61
+ export function nextService(options = {}) {
62
+ const cwd = options.cwd || ".";
63
+ const port = requiredNumber(options.port, "nextService port");
64
+ const baseUrl = options.baseUrl || "http://127.0.0.1:{port}";
65
+
66
+ return {
67
+ ...service(options),
68
+ local: {
69
+ cwd,
70
+ start: options.start || "exec ./node_modules/.bin/next dev -p {port}",
71
+ port,
72
+ baseUrl,
73
+ readyUrl: options.readyUrl || baseUrl,
74
+ readyTimeoutMs: options.readyTimeoutMs,
75
+ env: options.env || {},
76
+ },
77
+ };
78
+ }
79
+
80
+ export function tsxService(options = {}) {
81
+ const cwd = options.cwd || ".";
82
+ const port = requiredNumber(options.port, "tsxService port");
83
+ const baseUrl = options.baseUrl || "http://127.0.0.1:{port}";
84
+ const entry = options.entry || "src/index.ts";
85
+
86
+ return {
87
+ ...service(options),
88
+ local: {
89
+ cwd,
90
+ start: options.start || `exec ./node_modules/.bin/tsx watch ${entry}`,
91
+ port,
92
+ baseUrl,
93
+ readyUrl: options.readyUrl || `${baseUrl}${options.readyPath || "/health"}`,
94
+ readyTimeoutMs: options.readyTimeoutMs,
95
+ env: options.env || {},
96
+ },
97
+ };
98
+ }
99
+
100
+ export function clerkSessionProfile(options = {}) {
101
+ const apiBase = options.apiBase || "https://api.clerk.com/v1";
102
+ const secretKeyEnv = options.secretKeyEnv || "CLERK_SECRET_KEY";
103
+ const needsAuth = options.needsAuth !== false;
104
+
105
+ return defineHttpProfile({
106
+ auth: {
107
+ setup() {
108
+ return resolveClerkSession({
109
+ apiBase,
110
+ secretKey: envValue(secretKeyEnv),
111
+ needsAuth,
112
+ });
113
+ },
114
+ headers(setupData) {
115
+ return getClerkAuthHeaders(setupData, { apiBase });
116
+ },
117
+ },
118
+ });
119
+ }
120
+
121
+ export function jsonSessionProfile(options = {}) {
122
+ const loginPath = options.loginPath || "/api/auth/login";
123
+ const cookieName = options.cookieName || "session";
124
+ const body =
125
+ typeof options.body === "function"
126
+ ? options.body
127
+ : () => ({
128
+ username: envValue(options.usernameEnv || "TESTKIT_USERNAME"),
129
+ password: envValue(options.passwordEnv || "TESTKIT_PASSWORD"),
130
+ });
131
+
132
+ return defineHttpProfile({
133
+ auth: {
134
+ setup({ env }) {
135
+ const requestBody = body({ env });
136
+ const res = runtimeHttp.post(`${env.BASE}${loginPath}`, JSON.stringify(requestBody), {
137
+ headers: {
138
+ "Content-Type": "application/json",
139
+ ...(env.routeParams || {}),
140
+ ...(options.headers || {}),
141
+ },
142
+ });
143
+
144
+ if (res.status !== (options.successStatus || 200)) {
145
+ throw new Error(`Login failed (${res.status}): ${res.body}`);
146
+ }
147
+
148
+ const cookie = res.cookies?.[cookieName]?.[0]?.value ?? null;
149
+ if (!cookie) {
150
+ throw new Error(`No ${cookieName} cookie returned from login`);
151
+ }
152
+
153
+ return {
154
+ cookie,
155
+ body: safeJson(res),
156
+ };
157
+ },
158
+ headers(session) {
159
+ if (!session?.cookie) return {};
160
+ return {
161
+ Cookie: `${cookieName}=${session.cookie}`,
162
+ };
163
+ },
164
+ },
165
+ });
166
+ }
167
+
168
+ export {
169
+ clearRepoSetup,
170
+ clearRuntimeContext,
171
+ getRepoSetup,
172
+ getRuntimeContext,
173
+ registerRepoSetup,
174
+ registerRuntimeContext,
175
+ runtimeHttp,
176
+ runtimeJson,
177
+ };
178
+
179
+ function defaultGoStartCommand(options) {
180
+ if (options.command) {
181
+ return `exec go run ${options.command}`;
182
+ }
183
+ if (options.entrypoint) {
184
+ return `exec go run ${options.entrypoint}`;
185
+ }
186
+ return "exec go run ./cmd/server";
187
+ }
188
+
189
+ function requiredNumber(value, label) {
190
+ if (!Number.isInteger(value) || value <= 0) {
191
+ throw new Error(`${label} must be a positive integer`);
192
+ }
193
+ return value;
194
+ }
195
+
196
+ function envValue(name) {
197
+ const env = getRuntimeEnv();
198
+ const value = env?.rawEnv?.[name] || env?.[name];
199
+ if (!value) {
200
+ throw new Error(`Missing required env var "${name}" for testkit setup`);
201
+ }
202
+ return value;
203
+ }
204
+
205
+ function safeJson(response) {
206
+ try {
207
+ return runtimeJson(response);
208
+ } catch {
209
+ return null;
210
+ }
211
+ }
212
+
213
+ function resolveClerkSession({ apiBase, secretKey, needsAuth }) {
214
+ if (!needsAuth) {
215
+ return { jwt: null };
216
+ }
217
+
218
+ const headers = { Authorization: `Bearer ${secretKey}` };
219
+ const usersRes = runtimeHttp.get(`${apiBase}/users?limit=1&order_by=-created_at`, {
220
+ headers,
221
+ });
222
+ if (usersRes.status !== 200) {
223
+ throw new Error(`Clerk list users failed (${usersRes.status}): ${usersRes.body}`);
224
+ }
225
+
226
+ const users = runtimeJson(usersRes);
227
+ if (!users.length) {
228
+ throw new Error("No Clerk users found for testkit Clerk auth profile");
229
+ }
230
+
231
+ const userId = users[0].id;
232
+ const sessionsRes = runtimeHttp.get(
233
+ `${apiBase}/sessions?user_id=${userId}&status=active&limit=1`,
234
+ { headers }
235
+ );
236
+ if (sessionsRes.status !== 200) {
237
+ throw new Error(`Clerk list sessions failed (${sessionsRes.status}): ${sessionsRes.body}`);
238
+ }
239
+
240
+ const sessions = runtimeJson(sessionsRes);
241
+ if (!sessions?.length) {
242
+ throw new Error("No active Clerk session found for testkit Clerk auth profile");
243
+ }
244
+
245
+ const sessionId = sessions[0].id;
246
+ const tokenRes = runtimeHttp.post(`${apiBase}/sessions/${sessionId}/tokens`, null, {
247
+ headers: {
248
+ ...headers,
249
+ "Content-Type": "application/json",
250
+ },
251
+ });
252
+ if (tokenRes.status !== 200) {
253
+ throw new Error(`Clerk create token failed (${tokenRes.status}): ${tokenRes.body}`);
254
+ }
255
+
256
+ return {
257
+ jwt: runtimeJson(tokenRes).jwt,
258
+ sessionId,
259
+ secretKey,
260
+ };
261
+ }
262
+
263
+ function getClerkAuthHeaders(setupData, { apiBase }) {
264
+ if (!setupData?.jwt) return {};
265
+
266
+ if (!setupData.sessionId || !setupData.secretKey) {
267
+ return {
268
+ Authorization: `Bearer ${setupData.jwt}`,
269
+ "Content-Type": "application/json",
270
+ };
271
+ }
272
+
273
+ const tokenRes = runtimeHttp.post(`${apiBase}/sessions/${setupData.sessionId}/tokens`, null, {
274
+ headers: {
275
+ Authorization: `Bearer ${setupData.secretKey}`,
276
+ "Content-Type": "application/json",
277
+ },
278
+ });
279
+ if (tokenRes.status !== 200) {
280
+ return {
281
+ Authorization: `Bearer ${setupData.jwt}`,
282
+ "Content-Type": "application/json",
283
+ };
284
+ }
285
+
286
+ const nextJwt = runtimeJson(tokenRes).jwt;
287
+ setupData.jwt = nextJwt;
288
+ return {
289
+ Authorization: `Bearer ${nextJwt}`,
290
+ "Content-Type": "application/json",
291
+ };
292
+ }
@@ -0,0 +1,79 @@
1
+ let activeSetup = null;
2
+ let activeRuntimeContext = null;
3
+
4
+ export function registerRepoSetup(setup) {
5
+ activeSetup = setup || null;
6
+ }
7
+
8
+ export function getRepoSetup() {
9
+ return activeSetup;
10
+ }
11
+
12
+ export function clearRepoSetup() {
13
+ activeSetup = null;
14
+ }
15
+
16
+ export function registerRuntimeContext(context) {
17
+ activeRuntimeContext = context || null;
18
+ }
19
+
20
+ export function getRuntimeContext() {
21
+ return activeRuntimeContext;
22
+ }
23
+
24
+ export function clearRuntimeContext() {
25
+ activeRuntimeContext = null;
26
+ }
27
+
28
+ export function getRuntimeHttp() {
29
+ const context = getRuntimeContext();
30
+ if (!context?.http) {
31
+ throw new Error("Testkit runtime HTTP client is not available in this context");
32
+ }
33
+ return context.http;
34
+ }
35
+
36
+ export function getRuntimeEnv() {
37
+ const context = getRuntimeContext();
38
+ if (!context?.env) {
39
+ throw new Error("Testkit runtime env is not available in this context");
40
+ }
41
+ return context.env;
42
+ }
43
+
44
+ export function runtimeJson(response) {
45
+ return JSON.parse(response.body);
46
+ }
47
+
48
+ export function resolveHttpProfile(name) {
49
+ if (!name) return null;
50
+
51
+ const setup = getRepoSetup();
52
+ if (!setup) return null;
53
+ const profile = setup?.profiles?.http?.[name];
54
+ if (!profile) {
55
+ throw new Error(`Unknown testkit HTTP profile "${name}"`);
56
+ }
57
+ return profile;
58
+ }
59
+
60
+ export const runtimeHttp = {
61
+ del(url, body, params) {
62
+ return getRuntimeHttp().del(url, body, params);
63
+ },
64
+ file(data, filename, contentType) {
65
+ return getRuntimeHttp().file(data, filename, contentType);
66
+ },
67
+ get(url, params) {
68
+ return getRuntimeHttp().get(url, params);
69
+ },
70
+ patch(url, body, params) {
71
+ return getRuntimeHttp().patch(url, body, params);
72
+ },
73
+ post(url, body, params) {
74
+ return getRuntimeHttp().post(url, body, params);
75
+ },
76
+ put(url, body, params) {
77
+ return getRuntimeHttp().put(url, body, params);
78
+ },
79
+ };
package/package.json CHANGED
@@ -1,11 +1,22 @@
1
1
  {
2
2
  "name": "@elench/testkit",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "description": "CLI for discovering and running local HTTP, DAL, and Playwright test suites",
5
5
  "type": "module",
6
+ "types": "./lib/index.d.ts",
6
7
  "exports": {
7
- ".": "./lib/index.mjs",
8
- "./runtime": "./lib/runtime/index.mjs",
8
+ ".": {
9
+ "types": "./lib/index.d.ts",
10
+ "default": "./lib/index.mjs"
11
+ },
12
+ "./setup": {
13
+ "types": "./lib/setup/index.d.ts",
14
+ "default": "./lib/setup/index.mjs"
15
+ },
16
+ "./runtime": {
17
+ "types": "./lib/runtime/index.d.ts",
18
+ "default": "./lib/runtime/index.mjs"
19
+ },
9
20
  "./package.json": "./package.json"
10
21
  },
11
22
  "bin": {