@harperfast/integration-testing 0.2.0 → 0.3.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 +71 -5
- package/dist/harperLifecycle.d.ts +20 -0
- package/dist/harperLifecycle.js +71 -40
- package/dist/harperLifecycle.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/security/certGenUtils.d.ts +52 -0
- package/dist/security/certGenUtils.js +170 -0
- package/dist/security/certGenUtils.js.map +1 -0
- package/dist/security/crl/generate-test-certs.d.ts +34 -0
- package/dist/security/crl/generate-test-certs.js +81 -0
- package/dist/security/crl/generate-test-certs.js.map +1 -0
- package/dist/security/ocsp/generate-test-certs.d.ts +55 -0
- package/dist/security/ocsp/generate-test-certs.js +106 -0
- package/dist/security/ocsp/generate-test-certs.js.map +1 -0
- package/dist/security/ocspServer.d.ts +18 -0
- package/dist/security/ocspServer.js +132 -0
- package/dist/security/ocspServer.js.map +1 -0
- package/dist/securityServices.d.ts +49 -0
- package/dist/securityServices.js +120 -0
- package/dist/securityServices.js.map +1 -0
- package/dist/src/harperLifecycle.d.ts +156 -0
- package/dist/src/harperLifecycle.js +315 -0
- package/dist/src/harperLifecycle.js.map +1 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/loopbackAddressPool.d.ts +50 -0
- package/dist/src/loopbackAddressPool.js +337 -0
- package/dist/src/loopbackAddressPool.js.map +1 -0
- package/dist/src/run.d.ts +2 -0
- package/dist/src/run.js +94 -0
- package/dist/src/run.js.map +1 -0
- package/dist/src/security/certGenUtils.d.ts +52 -0
- package/dist/src/security/certGenUtils.js +170 -0
- package/dist/src/security/certGenUtils.js.map +1 -0
- package/dist/src/security/crl/generate-test-certs.d.ts +34 -0
- package/dist/src/security/crl/generate-test-certs.js +81 -0
- package/dist/src/security/crl/generate-test-certs.js.map +1 -0
- package/dist/src/security/ocsp/generate-test-certs.d.ts +55 -0
- package/dist/src/security/ocsp/generate-test-certs.js +106 -0
- package/dist/src/security/ocsp/generate-test-certs.js.map +1 -0
- package/dist/src/security/ocspServer.d.ts +18 -0
- package/dist/src/security/ocspServer.js +132 -0
- package/dist/src/security/ocspServer.js.map +1 -0
- package/dist/src/securityServices.d.ts +49 -0
- package/dist/src/securityServices.js +120 -0
- package/dist/src/securityServices.js.map +1 -0
- package/dist/src/targz.d.ts +6 -0
- package/dist/src/targz.js +20 -0
- package/dist/src/targz.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -97,30 +97,96 @@ Kills Harper, releases the loopback address back to the pool, and removes the in
|
|
|
97
97
|
|
|
98
98
|
Helper to POST an operation to the Operations API and assert HTTP 200.
|
|
99
99
|
|
|
100
|
-
### `
|
|
100
|
+
### `createHarperContext(name?)`
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
Creates a plain object satisfying `HarperTestContext`, for use outside `node:test` (e.g. Playwright worker fixtures). The `name` is optional and used for log directory naming.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
// Playwright example
|
|
106
|
+
const ctx = createHarperContext(`worker-${workerInfo.workerIndex}`);
|
|
107
|
+
await startHarper(ctx);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Types
|
|
111
|
+
|
|
112
|
+
There are four related TypeScript types — it helps to understand how they compose.
|
|
113
|
+
|
|
114
|
+
**`HarperContext`** is the instance data object stored at `ctx.harper` after `startHarper()` resolves. It describes a running Harper instance:
|
|
103
115
|
|
|
104
116
|
```ts
|
|
105
117
|
interface HarperContext {
|
|
106
|
-
dataRootDir: string; // Harper install directory
|
|
118
|
+
dataRootDir: string; // absolute path to the Harper install directory
|
|
107
119
|
admin: { username: string; password: string };
|
|
108
120
|
httpURL: string; // e.g. 'http://127.0.0.2:9926'
|
|
109
121
|
operationsAPIURL: string; // e.g. 'http://127.0.0.2:9925'
|
|
110
122
|
hostname: string; // e.g. '127.0.0.2'
|
|
111
123
|
process: ChildProcess;
|
|
112
|
-
logDir?: string;
|
|
124
|
+
logDir?: string; // set when HARPER_INTEGRATION_TEST_LOG_DIR is configured
|
|
125
|
+
startupOutput?: { stdout: string; stderr: string }; // captured startup output
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**`HarperTestContext`** is the minimal context shape accepted by `startHarper()`, `setupHarperWithFixture()`, `killHarper()`, and `teardownHarper()`. It is intentionally loose so it can be satisfied by a plain object or a `node:test` context:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
interface HarperTestContext {
|
|
133
|
+
name?: string; // used for log directory naming
|
|
134
|
+
harper?: Partial<HarperContext>; // populated by startHarper()
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**`StartedHarperTestContext`** is the same as `HarperTestContext` but with `harper` guaranteed to be a fully populated `HarperContext`. It is the return type of `startHarper()` and `setupHarperWithFixture()`, and the required parameter type of `killHarper()` and `teardownHarper()`:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
interface StartedHarperTestContext extends HarperTestContext {
|
|
142
|
+
harper: HarperContext;
|
|
113
143
|
}
|
|
114
144
|
```
|
|
115
145
|
|
|
146
|
+
**`ContextWithHarper`** is for `node:test` only. It extends both `SuiteContext` and `TestContext` from `node:test` with `harper: HarperContext`, so you can use it as the typed context parameter in a `suite()` callback:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
// node:test usage
|
|
150
|
+
suite('my suite', (ctx: ContextWithHarper) => {
|
|
151
|
+
before(async () => { await startHarper(ctx); });
|
|
152
|
+
after(async () => { await teardownHarper(ctx); });
|
|
153
|
+
|
|
154
|
+
test('example', async () => {
|
|
155
|
+
const res = await fetch(ctx.harper.httpURL);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
If you are not using `node:test`, use `createHarperContext()` to create a plain `HarperTestContext` instead.
|
|
161
|
+
|
|
116
162
|
### Server Log Capture
|
|
117
163
|
|
|
118
|
-
When `HARPER_INTEGRATION_TEST_LOG_DIR` is set, each Harper instance writes its logs to a per-suite subdirectory. Logs
|
|
164
|
+
When `HARPER_INTEGRATION_TEST_LOG_DIR` is set, each Harper instance writes its logs to a per-suite subdirectory. Logs are preserved for the lifetime of the log directory. In CI, combine with artifact upload steps that run on failure to capture logs from failing runs.
|
|
119
165
|
|
|
120
166
|
```sh
|
|
121
167
|
HARPER_INTEGRATION_TEST_LOG_DIR=/tmp/harper-test-logs npx harper-integration-test-run "integrationTests/**/*.test.ts"
|
|
122
168
|
```
|
|
123
169
|
|
|
170
|
+
Additionally, `startupOutput` on `HarperContext` provides the captured stdout/stderr from Harper startup for programmatic access (e.g. attaching to Playwright test results).
|
|
171
|
+
|
|
172
|
+
### `HarperStartupError`
|
|
173
|
+
|
|
174
|
+
When Harper fails to start or times out, a `HarperStartupError` is thrown. It extends `Error` and includes structured `stdout` and `stderr` properties for diagnostics:
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import { HarperStartupError } from '@harperfast/integration-testing';
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
await startHarper(ctx);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (error instanceof HarperStartupError) {
|
|
183
|
+
console.error('Startup failed:', error.message);
|
|
184
|
+
console.error('stdout:', error.stdout);
|
|
185
|
+
console.error('stderr:', error.stderr);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
124
190
|
### `targz(dirPath)`
|
|
125
191
|
|
|
126
192
|
Packs and compresses a directory into a base64-encoded tar.gz string. Useful for `deploy_component` Operations API calls.
|
|
@@ -30,6 +30,12 @@ export declare const OPERATIONS_API_PORT = 9925;
|
|
|
30
30
|
export declare const DEFAULT_ADMIN_USERNAME = "admin";
|
|
31
31
|
export declare const DEFAULT_ADMIN_PASSWORD = "Abc1234!";
|
|
32
32
|
export declare const DEFAULT_STARTUP_TIMEOUT_MS: number;
|
|
33
|
+
/**
|
|
34
|
+
* The runtime to use for running Harper during tests.
|
|
35
|
+
* Set via the HARPER_RUNTIME environment variable ('node' or 'bun').
|
|
36
|
+
* Defaults to 'node'.
|
|
37
|
+
*/
|
|
38
|
+
export declare const HARPER_RUNTIME: 'node' | 'bun';
|
|
33
39
|
/**
|
|
34
40
|
* Options for setting up a Harper instance.
|
|
35
41
|
*/
|
|
@@ -76,6 +82,11 @@ export interface HarperContext {
|
|
|
76
82
|
process: ChildProcess;
|
|
77
83
|
/** Absolute path to the log directory for this suite (only set when HARPER_INTEGRATION_TEST_LOG_DIR is configured) */
|
|
78
84
|
logDir?: string;
|
|
85
|
+
/** Captured stdout/stderr from Harper startup, up to the point it reported ready. */
|
|
86
|
+
startupOutput?: {
|
|
87
|
+
stdout: string;
|
|
88
|
+
stderr: string;
|
|
89
|
+
};
|
|
79
90
|
}
|
|
80
91
|
/**
|
|
81
92
|
* Test context interface with Harper instance details, for use with node:test.
|
|
@@ -89,6 +100,15 @@ export interface HarperContext {
|
|
|
89
100
|
export interface ContextWithHarper extends SuiteContext, TestContext {
|
|
90
101
|
harper: HarperContext;
|
|
91
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Error thrown when a Harper process fails to start or times out.
|
|
105
|
+
* Includes captured stdout and stderr for diagnostics.
|
|
106
|
+
*/
|
|
107
|
+
export declare class HarperStartupError extends Error {
|
|
108
|
+
stdout: string;
|
|
109
|
+
stderr: string;
|
|
110
|
+
constructor(message: string, stdout: string, stderr: string);
|
|
111
|
+
}
|
|
92
112
|
/**
|
|
93
113
|
* Sets up a Harper instance with a component pre-installed from a local directory.
|
|
94
114
|
*
|
package/dist/harperLifecycle.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn, ChildProcess } from 'node:child_process';
|
|
2
|
-
import { createWriteStream, existsSync
|
|
2
|
+
import { createWriteStream, existsSync } from 'node:fs';
|
|
3
3
|
import { join, basename } from 'node:path';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { mkdtemp, mkdir, rm, cp } from 'node:fs/promises';
|
|
@@ -19,13 +19,19 @@ export function createHarperContext(name) {
|
|
|
19
19
|
// Constants
|
|
20
20
|
const HTTP_PORT = 9926;
|
|
21
21
|
const HTTPS_PORT = 9927;
|
|
22
|
+
const MQTT_PORT = 1883;
|
|
23
|
+
const MQTTS_PORT = 8883;
|
|
22
24
|
export const OPERATIONS_API_PORT = 9925;
|
|
23
25
|
export const DEFAULT_ADMIN_USERNAME = 'admin';
|
|
24
26
|
export const DEFAULT_ADMIN_PASSWORD = 'Abc1234!';
|
|
25
|
-
export const DEFAULT_STARTUP_TIMEOUT_MS = process.env.HARPER_INTEGRATION_TEST_STARTUP_TIMEOUT_MS
|
|
26
|
-
? parseInt(process.env.HARPER_INTEGRATION_TEST_STARTUP_TIMEOUT_MS, 10)
|
|
27
|
-
: 30000;
|
|
27
|
+
export const DEFAULT_STARTUP_TIMEOUT_MS = parseInt(process.env.HARPER_INTEGRATION_TEST_STARTUP_TIMEOUT_MS || '', 10) || 60000;
|
|
28
28
|
const LOG_DIR = process.env.HARPER_INTEGRATION_TEST_LOG_DIR;
|
|
29
|
+
/**
|
|
30
|
+
* The runtime to use for running Harper during tests.
|
|
31
|
+
* Set via the HARPER_RUNTIME environment variable ('node' or 'bun').
|
|
32
|
+
* Defaults to 'node'.
|
|
33
|
+
*/
|
|
34
|
+
export const HARPER_RUNTIME = process.env.HARPER_RUNTIME || 'node';
|
|
29
35
|
/**
|
|
30
36
|
* Gets the path to the Harper CLI script.
|
|
31
37
|
*
|
|
@@ -65,6 +71,14 @@ function getHarperScript(harperBinPath) {
|
|
|
65
71
|
` - HARPER_INTEGRATION_TEST_INSTALL_SCRIPT environment variable\n` +
|
|
66
72
|
` - Install 'harper' as a dependency in your project`);
|
|
67
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Strips ANSI escape sequences (colors, bold, underline, cursor movement, etc.) from a string.
|
|
76
|
+
*/
|
|
77
|
+
// eslint-disable-next-line no-control-regex
|
|
78
|
+
const ANSI_REGEX = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nq-uy=><~]/g;
|
|
79
|
+
function stripAnsi(str) {
|
|
80
|
+
return str.replace(ANSI_REGEX, '');
|
|
81
|
+
}
|
|
68
82
|
/**
|
|
69
83
|
* Sanitizes a string for use as a filesystem directory name.
|
|
70
84
|
*/
|
|
@@ -74,17 +88,42 @@ function sanitizeForFilesystem(name) {
|
|
|
74
88
|
.replace(/_+/g, '_')
|
|
75
89
|
.substring(0, 100);
|
|
76
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Error thrown when a Harper process fails to start or times out.
|
|
93
|
+
* Includes captured stdout and stderr for diagnostics.
|
|
94
|
+
*/
|
|
95
|
+
export class HarperStartupError extends Error {
|
|
96
|
+
stdout;
|
|
97
|
+
stderr;
|
|
98
|
+
constructor(message, stdout, stderr) {
|
|
99
|
+
let fullMessage = message;
|
|
100
|
+
if (stdout) {
|
|
101
|
+
fullMessage += `\n\nstdout:\n${stdout}`;
|
|
102
|
+
}
|
|
103
|
+
if (stderr) {
|
|
104
|
+
fullMessage += `\n\nstderr:\n${stderr}`;
|
|
105
|
+
}
|
|
106
|
+
super(fullMessage);
|
|
107
|
+
this.name = 'HarperStartupError';
|
|
108
|
+
this.stdout = stdout;
|
|
109
|
+
this.stderr = stderr;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
77
112
|
/**
|
|
78
113
|
* Runs a Harper CLI command and captures output.
|
|
79
114
|
*
|
|
80
115
|
* When `logDir` is provided, stdout and stderr are also written to files
|
|
81
116
|
* (`stdout.log` and `stderr.log`) in that directory.
|
|
82
117
|
*
|
|
83
|
-
* @throws {
|
|
118
|
+
* @throws {HarperStartupError} If the command times out or exits with a non-zero status code
|
|
84
119
|
*/
|
|
85
|
-
function runHarperCommand({ args, env, completionMessage, logDir, harperBinPath, }) {
|
|
120
|
+
function runHarperCommand({ args, env, completionMessage, logDir, harperBinPath, timeoutMs, }) {
|
|
86
121
|
const harperScript = getHarperScript(harperBinPath);
|
|
87
|
-
const
|
|
122
|
+
const runtime = HARPER_RUNTIME;
|
|
123
|
+
const runtimeArgs = runtime === 'bun'
|
|
124
|
+
? [harperScript, ...args]
|
|
125
|
+
: ['--trace-warnings', '--force-node-api-uncaught-exceptions-policy=true', harperScript, ...args];
|
|
126
|
+
const proc = spawn(runtime, runtimeArgs, {
|
|
88
127
|
env: { ...process.env, ...env },
|
|
89
128
|
});
|
|
90
129
|
let stdoutStream;
|
|
@@ -93,32 +132,27 @@ function runHarperCommand({ args, env, completionMessage, logDir, harperBinPath,
|
|
|
93
132
|
stdoutStream = createWriteStream(join(logDir, 'stdout.log'));
|
|
94
133
|
stderrStream = createWriteStream(join(logDir, 'stderr.log'));
|
|
95
134
|
}
|
|
135
|
+
const effectiveTimeout = timeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
|
|
96
136
|
return new Promise((resolve, reject) => {
|
|
97
137
|
let stdout = '';
|
|
98
138
|
let stderr = '';
|
|
99
139
|
let timer = setTimeout(() => {
|
|
100
|
-
|
|
101
|
-
if (stdout) {
|
|
102
|
-
errorMessage += `\n\nstdout:\n${stdout}`;
|
|
103
|
-
}
|
|
104
|
-
if (stderr) {
|
|
105
|
-
errorMessage += `\n\nstderr:\n${stderr}`;
|
|
106
|
-
}
|
|
107
|
-
reject(errorMessage);
|
|
140
|
+
reject(new HarperStartupError(`Harper process timed out after ${effectiveTimeout}ms`, stdout, stderr));
|
|
108
141
|
proc.kill();
|
|
109
|
-
},
|
|
142
|
+
}, effectiveTimeout);
|
|
110
143
|
proc.stdout?.on('data', (data) => {
|
|
111
|
-
const dataString = data.toString();
|
|
112
|
-
stdoutStream?.write(
|
|
144
|
+
const dataString = stripAnsi(data.toString());
|
|
145
|
+
stdoutStream?.write(dataString);
|
|
113
146
|
if (completionMessage && dataString.includes(completionMessage)) {
|
|
114
147
|
clearTimeout(timer);
|
|
115
|
-
resolve(proc);
|
|
148
|
+
resolve({ process: proc, stdout, stderr });
|
|
116
149
|
}
|
|
117
150
|
stdout += dataString;
|
|
118
151
|
});
|
|
119
152
|
proc.stderr?.on('data', (data) => {
|
|
120
|
-
|
|
121
|
-
|
|
153
|
+
const dataString = stripAnsi(data.toString());
|
|
154
|
+
stderrStream?.write(dataString);
|
|
155
|
+
stderr += dataString;
|
|
122
156
|
});
|
|
123
157
|
proc.on('error', (error) => {
|
|
124
158
|
reject(error);
|
|
@@ -126,15 +160,12 @@ function runHarperCommand({ args, env, completionMessage, logDir, harperBinPath,
|
|
|
126
160
|
proc.on('exit', (statusCode, signal) => {
|
|
127
161
|
clearTimeout(timer);
|
|
128
162
|
if (statusCode === 0) {
|
|
129
|
-
resolve(proc);
|
|
163
|
+
resolve({ process: proc, stdout, stderr });
|
|
130
164
|
}
|
|
131
165
|
else {
|
|
132
|
-
|
|
166
|
+
const errorMessage = `Harper process failed with exit code/signal ${statusCode ?? signal}`;
|
|
133
167
|
stderrStream?.write(errorMessage);
|
|
134
|
-
|
|
135
|
-
errorMessage += `\n\nstderr:\n${stderr}`;
|
|
136
|
-
}
|
|
137
|
-
reject(errorMessage);
|
|
168
|
+
reject(new HarperStartupError(errorMessage, stdout, stderr));
|
|
138
169
|
}
|
|
139
170
|
stdoutStream?.end();
|
|
140
171
|
stderrStream?.end();
|
|
@@ -192,26 +223,18 @@ export async function startHarper(ctx, options) {
|
|
|
192
223
|
let logDir;
|
|
193
224
|
if (LOG_DIR) {
|
|
194
225
|
const suiteName = sanitizeForFilesystem(ctx.name || 'unknown');
|
|
195
|
-
|
|
226
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
227
|
+
logDir = join(LOG_DIR, `${suiteName}-${sanitizeForFilesystem(loopbackAddress)}-${timestamp}`);
|
|
196
228
|
await mkdir(logDir, { recursive: true });
|
|
197
229
|
}
|
|
198
230
|
// Point Harper's log directory to the suite log dir so hdb.log is preserved for upload
|
|
199
231
|
const config = { ...options?.config };
|
|
200
232
|
if (logDir) {
|
|
201
233
|
config.logging = { ...config.logging, root: logDir };
|
|
202
|
-
// Clean up log directory on successful exit — only keep logs when tests fail
|
|
203
|
-
process.on('exit', (code) => {
|
|
204
|
-
if (code === 0) {
|
|
205
|
-
try {
|
|
206
|
-
rmSync(logDir, { recursive: true, force: true });
|
|
207
|
-
}
|
|
208
|
-
catch { }
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
234
|
}
|
|
212
235
|
const args = [
|
|
213
236
|
`--ROOTPATH=${dataRootDir}`,
|
|
214
|
-
|
|
237
|
+
`--AUTHENTICATION_AUTHORIZELOCAL=true`,
|
|
215
238
|
`--HDB_ADMIN_USERNAME=${DEFAULT_ADMIN_USERNAME}`,
|
|
216
239
|
`--HDB_ADMIN_PASSWORD=${DEFAULT_ADMIN_PASSWORD}`,
|
|
217
240
|
'--THREADS_COUNT=1',
|
|
@@ -219,6 +242,8 @@ export async function startHarper(ctx, options) {
|
|
|
219
242
|
`--NODE_HOSTNAME=${loopbackAddress}`,
|
|
220
243
|
`--HTTP_PORT=${loopbackAddress}:${HTTP_PORT}`,
|
|
221
244
|
`--OPERATIONSAPI_NETWORK_PORT=${loopbackAddress}:${OPERATIONS_API_PORT}`,
|
|
245
|
+
`--MQTT_NETWORK_PORT=${loopbackAddress}:${MQTT_PORT}`,
|
|
246
|
+
`--MQTT_NETWORK_SECUREPORT=${loopbackAddress}:${MQTTS_PORT}`,
|
|
222
247
|
'--LOGGING_LEVEL=debug',
|
|
223
248
|
'--LOGGING_STDSTREAMS=false',
|
|
224
249
|
];
|
|
@@ -232,12 +257,13 @@ export async function startHarper(ctx, options) {
|
|
|
232
257
|
HARPER_SET_CONFIG: JSON.stringify(config),
|
|
233
258
|
...options?.env,
|
|
234
259
|
};
|
|
235
|
-
const
|
|
260
|
+
const result = await runHarperCommand({
|
|
236
261
|
args,
|
|
237
262
|
env: harperEnv,
|
|
238
263
|
completionMessage: 'successfully started',
|
|
239
264
|
logDir,
|
|
240
265
|
harperBinPath: options?.harperBinPath,
|
|
266
|
+
timeoutMs: options?.startupTimeoutMs,
|
|
241
267
|
});
|
|
242
268
|
ctx.harper = {
|
|
243
269
|
dataRootDir,
|
|
@@ -248,8 +274,9 @@ export async function startHarper(ctx, options) {
|
|
|
248
274
|
httpURL: `http://${loopbackAddress}:${HTTP_PORT}`,
|
|
249
275
|
operationsAPIURL: `http://${loopbackAddress}:${OPERATIONS_API_PORT}`,
|
|
250
276
|
hostname: loopbackAddress,
|
|
251
|
-
process:
|
|
277
|
+
process: result.process,
|
|
252
278
|
logDir,
|
|
279
|
+
startupOutput: { stdout: result.stdout, stderr: result.stderr },
|
|
253
280
|
};
|
|
254
281
|
return ctx;
|
|
255
282
|
}
|
|
@@ -258,6 +285,8 @@ export async function startHarper(ctx, options) {
|
|
|
258
285
|
* @param ctx
|
|
259
286
|
*/
|
|
260
287
|
export async function killHarper(ctx) {
|
|
288
|
+
if (!ctx.harper?.process)
|
|
289
|
+
return;
|
|
261
290
|
await new Promise((resolve) => {
|
|
262
291
|
let timer;
|
|
263
292
|
ctx.harper.process.on('exit', () => {
|
|
@@ -297,6 +326,8 @@ export async function killHarper(ctx) {
|
|
|
297
326
|
* ```
|
|
298
327
|
*/
|
|
299
328
|
export async function teardownHarper(ctx) {
|
|
329
|
+
if (!ctx.harper)
|
|
330
|
+
return;
|
|
300
331
|
await killHarper(ctx);
|
|
301
332
|
await releaseLoopbackAddress(ctx.harper.hostname);
|
|
302
333
|
// a few retries are typically necessary, might take a sec for a process to finish, especially since rocksdb may be flushing
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harperLifecycle.js","sourceRoot":"","sources":["../src/harperLifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"harperLifecycle.js","sourceRoot":"","sources":["../src/harperLifecycle.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { startHarper, setupHarperWithFixture, killHarper, teardownHarper, sendOperation, createHarperContext, OPERATIONS_API_PORT, DEFAULT_ADMIN_USERNAME, DEFAULT_ADMIN_PASSWORD, DEFAULT_STARTUP_TIMEOUT_MS, type StartHarperOptions, type HarperContext, type HarperTestContext, type StartedHarperTestContext, type ContextWithHarper, } from './harperLifecycle.ts';
|
|
1
|
+
export { startHarper, setupHarperWithFixture, killHarper, teardownHarper, sendOperation, createHarperContext, HarperStartupError, OPERATIONS_API_PORT, DEFAULT_ADMIN_USERNAME, DEFAULT_ADMIN_PASSWORD, DEFAULT_STARTUP_TIMEOUT_MS, HARPER_RUNTIME, type StartHarperOptions, type HarperContext, type HarperTestContext, type StartedHarperTestContext, type ContextWithHarper, } from './harperLifecycle.ts';
|
|
2
2
|
export { validateLoopbackAddressPool, getNextAvailableLoopbackAddress, releaseLoopbackAddress, releaseAllLoopbackAddressesForCurrentProcess, } from './loopbackAddressPool.ts';
|
|
3
3
|
export { targz } from './targz.ts';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { startHarper, setupHarperWithFixture, killHarper, teardownHarper, sendOperation, createHarperContext, OPERATIONS_API_PORT, DEFAULT_ADMIN_USERNAME, DEFAULT_ADMIN_PASSWORD, DEFAULT_STARTUP_TIMEOUT_MS, } from "./harperLifecycle.js";
|
|
1
|
+
export { startHarper, setupHarperWithFixture, killHarper, teardownHarper, sendOperation, createHarperContext, HarperStartupError, OPERATIONS_API_PORT, DEFAULT_ADMIN_USERNAME, DEFAULT_ADMIN_PASSWORD, DEFAULT_STARTUP_TIMEOUT_MS, HARPER_RUNTIME, } from "./harperLifecycle.js";
|
|
2
2
|
export { validateLoopbackAddressPool, getNextAvailableLoopbackAddress, releaseLoopbackAddress, releaseAllLoopbackAddressesForCurrentProcess, } from "./loopbackAddressPool.js";
|
|
3
3
|
export { targz } from "./targz.js";
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,WAAW,EACX,sBAAsB,EACtB,UAAU,EACV,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,mBAAmB,EACnB,sBAAsB,EACtB,sBAAsB,EACtB,0BAA0B,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,WAAW,EACX,sBAAsB,EACtB,UAAU,EACV,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACtB,sBAAsB,EACtB,0BAA0B,EAC1B,cAAc,GAMd,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACN,2BAA2B,EAC3B,+BAA+B,EAC/B,sBAAsB,EACtB,4CAA4C,GAC5C,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure Node.js X.509 certificate and CRL generation utilities
|
|
3
|
+
*
|
|
4
|
+
* Uses Node.js built-in webcrypto (Ed25519) + pkijs for ASN.1 encoding.
|
|
5
|
+
* No openssl CLI dependency — fully portable across macOS and Linux.
|
|
6
|
+
*/
|
|
7
|
+
import * as pkijs from 'pkijs';
|
|
8
|
+
declare const OCSP_SIGNING_OID = "1.3.6.1.5.5.7.3.9";
|
|
9
|
+
declare const CLIENT_AUTH_OID = "1.3.6.1.5.5.7.3.2";
|
|
10
|
+
export interface Ed25519KeyPair {
|
|
11
|
+
privateKey: CryptoKey;
|
|
12
|
+
publicKey: CryptoKey;
|
|
13
|
+
/** PKCS8 PEM string */
|
|
14
|
+
privateKeyPem: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function generateEd25519KeyPair(): Promise<Ed25519KeyPair>;
|
|
17
|
+
/** Convert a pkijs Certificate to PEM string */
|
|
18
|
+
export declare function certToPem(cert: pkijs.Certificate): string;
|
|
19
|
+
/** Convert a pkijs CertificateRevocationList to PEM string */
|
|
20
|
+
export declare function crlToPem(crl: pkijs.CertificateRevocationList): string;
|
|
21
|
+
interface CertOptions {
|
|
22
|
+
serialNumber: number;
|
|
23
|
+
subject: {
|
|
24
|
+
CN: string;
|
|
25
|
+
O?: string;
|
|
26
|
+
};
|
|
27
|
+
issuer: {
|
|
28
|
+
CN: string;
|
|
29
|
+
O?: string;
|
|
30
|
+
};
|
|
31
|
+
validDays: number;
|
|
32
|
+
issuerKey: CryptoKey;
|
|
33
|
+
subjectPublicKey: CryptoKey;
|
|
34
|
+
isCA?: boolean;
|
|
35
|
+
extensions?: pkijs.Extension[];
|
|
36
|
+
}
|
|
37
|
+
/** Create a signed Ed25519 X.509 v3 certificate */
|
|
38
|
+
export declare function createCertificate(opts: CertOptions): Promise<pkijs.Certificate>;
|
|
39
|
+
/** Create a CRL Distribution Points extension */
|
|
40
|
+
export declare function makeCRLDistributionPointsExt(url: string): pkijs.Extension;
|
|
41
|
+
/** Create an Authority Info Access extension with an OCSP URL */
|
|
42
|
+
export declare function makeOCSPAIAExt(url: string): pkijs.Extension;
|
|
43
|
+
/** Create an Extended Key Usage extension */
|
|
44
|
+
export declare function makeExtKeyUsageExt(oids: string[]): pkijs.Extension;
|
|
45
|
+
export { OCSP_SIGNING_OID, CLIENT_AUTH_OID };
|
|
46
|
+
/** Create a signed X.509v2 CRL */
|
|
47
|
+
export declare function createCRL(issuerCert: pkijs.Certificate, issuerKey: CryptoKey, revokedSerials: number[]): Promise<pkijs.CertificateRevocationList>;
|
|
48
|
+
/**
|
|
49
|
+
* Sign a pkijs BasicOCSPResponse with an Ed25519 key.
|
|
50
|
+
* Must be called after tbsResponseData.responses is populated.
|
|
51
|
+
*/
|
|
52
|
+
export declare function signBasicOCSPResponse(basicResponse: pkijs.BasicOCSPResponse, privateKey: CryptoKey): Promise<void>;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure Node.js X.509 certificate and CRL generation utilities
|
|
3
|
+
*
|
|
4
|
+
* Uses Node.js built-in webcrypto (Ed25519) + pkijs for ASN.1 encoding.
|
|
5
|
+
* No openssl CLI dependency — fully portable across macOS and Linux.
|
|
6
|
+
*/
|
|
7
|
+
import { webcrypto } from 'node:crypto';
|
|
8
|
+
import * as pkijs from 'pkijs';
|
|
9
|
+
import * as asn1js from 'asn1js';
|
|
10
|
+
// Ed25519 OID (RFC 8410)
|
|
11
|
+
const ED25519_OID = '1.3.101.112';
|
|
12
|
+
// Standard extension OIDs
|
|
13
|
+
const BASIC_CONSTRAINTS_OID = '2.5.29.19';
|
|
14
|
+
const CRL_DISTRIBUTION_POINTS_OID = '2.5.29.31';
|
|
15
|
+
const AUTHORITY_INFO_ACCESS_OID = '1.3.6.1.5.5.7.1.1';
|
|
16
|
+
const EXT_KEY_USAGE_OID = '2.5.29.37';
|
|
17
|
+
const OCSP_SIGNING_OID = '1.3.6.1.5.5.7.3.9';
|
|
18
|
+
const CLIENT_AUTH_OID = '1.3.6.1.5.5.7.3.2';
|
|
19
|
+
const OCSP_ACCESS_METHOD_OID = '1.3.6.1.5.5.7.48.1';
|
|
20
|
+
// Configure pkijs to use Node.js webcrypto
|
|
21
|
+
pkijs.setEngine('node', new pkijs.CryptoEngine({ name: 'node', crypto: webcrypto }));
|
|
22
|
+
export async function generateEd25519KeyPair() {
|
|
23
|
+
// Ed25519 is supported at runtime (Node 20+) but @types/node lacks dedicated types for it
|
|
24
|
+
const keyPair = (await webcrypto.subtle.generateKey({ name: 'Ed25519' }, true, [
|
|
25
|
+
'sign',
|
|
26
|
+
'verify',
|
|
27
|
+
]));
|
|
28
|
+
const pkcs8 = await webcrypto.subtle.exportKey('pkcs8', keyPair.privateKey);
|
|
29
|
+
const b64 = Buffer.from(pkcs8).toString('base64');
|
|
30
|
+
const lines = b64.match(/.{1,64}/g).join('\n');
|
|
31
|
+
const privateKeyPem = `-----BEGIN PRIVATE KEY-----\n${lines}\n-----END PRIVATE KEY-----\n`;
|
|
32
|
+
return { privateKey: keyPair.privateKey, publicKey: keyPair.publicKey, privateKeyPem };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Sign a pkijs Certificate or CertificateRevocationList with an Ed25519 key.
|
|
36
|
+
* Bypasses pkijs's sign() method which does not support Ed25519.
|
|
37
|
+
*/
|
|
38
|
+
async function signWithEd25519(obj, privateKey) {
|
|
39
|
+
const algId = new pkijs.AlgorithmIdentifier({ algorithmId: ED25519_OID });
|
|
40
|
+
obj.signature = algId;
|
|
41
|
+
obj.signatureAlgorithm = algId;
|
|
42
|
+
const tbsBer = obj.encodeTBS().toBER();
|
|
43
|
+
obj.tbsView = new Uint8Array(tbsBer);
|
|
44
|
+
const sig = await webcrypto.subtle.sign('Ed25519', privateKey, tbsBer);
|
|
45
|
+
obj.signatureValue = new asn1js.BitString({ valueHex: sig });
|
|
46
|
+
}
|
|
47
|
+
/** Convert a pkijs Certificate to PEM string */
|
|
48
|
+
export function certToPem(cert) {
|
|
49
|
+
const der = cert.toSchema(false).toBER();
|
|
50
|
+
const b64 = Buffer.from(der).toString('base64');
|
|
51
|
+
return `-----BEGIN CERTIFICATE-----\n${b64.match(/.{1,64}/g).join('\n')}\n-----END CERTIFICATE-----\n`;
|
|
52
|
+
}
|
|
53
|
+
/** Convert a pkijs CertificateRevocationList to PEM string */
|
|
54
|
+
export function crlToPem(crl) {
|
|
55
|
+
const der = crl.toSchema(false).toBER();
|
|
56
|
+
const b64 = Buffer.from(der).toString('base64');
|
|
57
|
+
return `-----BEGIN X509 CRL-----\n${b64.match(/.{1,64}/g).join('\n')}\n-----END X509 CRL-----\n`;
|
|
58
|
+
}
|
|
59
|
+
/** Create a signed Ed25519 X.509 v3 certificate */
|
|
60
|
+
export async function createCertificate(opts) {
|
|
61
|
+
const cert = new pkijs.Certificate();
|
|
62
|
+
cert.version = 2; // v3
|
|
63
|
+
cert.serialNumber = new asn1js.Integer({ value: opts.serialNumber });
|
|
64
|
+
const now = new Date();
|
|
65
|
+
cert.notBefore.value = now;
|
|
66
|
+
cert.notAfter.value = new Date(now.getTime() + opts.validDays * 24 * 60 * 60 * 1000);
|
|
67
|
+
// Build subject RDN
|
|
68
|
+
const buildRDN = (dn, target) => {
|
|
69
|
+
target.typesAndValues.push(new pkijs.AttributeTypeAndValue({
|
|
70
|
+
type: '2.5.4.3',
|
|
71
|
+
value: new asn1js.Utf8String({ value: dn.CN }),
|
|
72
|
+
}));
|
|
73
|
+
if (dn.O) {
|
|
74
|
+
target.typesAndValues.push(new pkijs.AttributeTypeAndValue({
|
|
75
|
+
type: '2.5.4.10',
|
|
76
|
+
value: new asn1js.Utf8String({ value: dn.O }),
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
buildRDN(opts.subject, cert.subject);
|
|
81
|
+
buildRDN(opts.issuer, cert.issuer);
|
|
82
|
+
// Import public key into SubjectPublicKeyInfo
|
|
83
|
+
const spki = await webcrypto.subtle.exportKey('spki', opts.subjectPublicKey);
|
|
84
|
+
const spkiAsn1 = asn1js.fromBER(spki);
|
|
85
|
+
cert.subjectPublicKeyInfo.fromSchema(spkiAsn1.result);
|
|
86
|
+
// Add basic constraints extension (always present)
|
|
87
|
+
cert.extensions = [];
|
|
88
|
+
const bc = new pkijs.BasicConstraints({ cA: opts.isCA === true });
|
|
89
|
+
cert.extensions.push(new pkijs.Extension({
|
|
90
|
+
extnID: BASIC_CONSTRAINTS_OID,
|
|
91
|
+
critical: true,
|
|
92
|
+
extnValue: bc.toSchema().toBER(),
|
|
93
|
+
}));
|
|
94
|
+
// Add caller-supplied extensions
|
|
95
|
+
if (opts.extensions) {
|
|
96
|
+
cert.extensions.push(...opts.extensions);
|
|
97
|
+
}
|
|
98
|
+
await signWithEd25519(cert, opts.issuerKey);
|
|
99
|
+
return cert;
|
|
100
|
+
}
|
|
101
|
+
/** Create a CRL Distribution Points extension */
|
|
102
|
+
export function makeCRLDistributionPointsExt(url) {
|
|
103
|
+
const dp = new pkijs.DistributionPoint({
|
|
104
|
+
distributionPoint: [new pkijs.GeneralName({ type: 6, value: url })],
|
|
105
|
+
});
|
|
106
|
+
const cdp = new pkijs.CRLDistributionPoints({ distributionPoints: [dp] });
|
|
107
|
+
return new pkijs.Extension({
|
|
108
|
+
extnID: CRL_DISTRIBUTION_POINTS_OID,
|
|
109
|
+
critical: false,
|
|
110
|
+
extnValue: cdp.toSchema().toBER(),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/** Create an Authority Info Access extension with an OCSP URL */
|
|
114
|
+
export function makeOCSPAIAExt(url) {
|
|
115
|
+
const desc = new pkijs.AccessDescription({
|
|
116
|
+
accessMethod: OCSP_ACCESS_METHOD_OID,
|
|
117
|
+
accessLocation: new pkijs.GeneralName({ type: 6, value: url }),
|
|
118
|
+
});
|
|
119
|
+
const aia = new pkijs.InfoAccess({ accessDescriptions: [desc] });
|
|
120
|
+
return new pkijs.Extension({
|
|
121
|
+
extnID: AUTHORITY_INFO_ACCESS_OID,
|
|
122
|
+
critical: false,
|
|
123
|
+
extnValue: aia.toSchema().toBER(),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/** Create an Extended Key Usage extension */
|
|
127
|
+
export function makeExtKeyUsageExt(oids) {
|
|
128
|
+
const seq = new asn1js.Sequence({
|
|
129
|
+
value: oids.map((oid) => new asn1js.ObjectIdentifier({ value: oid })),
|
|
130
|
+
});
|
|
131
|
+
return new pkijs.Extension({
|
|
132
|
+
extnID: EXT_KEY_USAGE_OID,
|
|
133
|
+
critical: false,
|
|
134
|
+
extnValue: seq.toBER(),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
export { OCSP_SIGNING_OID, CLIENT_AUTH_OID };
|
|
138
|
+
/** Create a signed X.509v2 CRL */
|
|
139
|
+
export async function createCRL(issuerCert, issuerKey, revokedSerials) {
|
|
140
|
+
const crl = new pkijs.CertificateRevocationList();
|
|
141
|
+
crl.version = 1; // CRLv2 = version field value 1
|
|
142
|
+
// Copy issuer from CA cert subject
|
|
143
|
+
crl.issuer = issuerCert.subject;
|
|
144
|
+
const now = new Date();
|
|
145
|
+
const next = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000);
|
|
146
|
+
crl.thisUpdate = new pkijs.Time({ type: 0, value: now });
|
|
147
|
+
crl.nextUpdate = new pkijs.Time({ type: 0, value: next });
|
|
148
|
+
if (revokedSerials.length > 0) {
|
|
149
|
+
crl.revokedCertificates = revokedSerials.map((serial) => new pkijs.RevokedCertificate({
|
|
150
|
+
userCertificate: new asn1js.Integer({ value: serial }),
|
|
151
|
+
revocationDate: new pkijs.Time({ type: 0, value: now }),
|
|
152
|
+
}));
|
|
153
|
+
}
|
|
154
|
+
await signWithEd25519(crl, issuerKey);
|
|
155
|
+
return crl;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Sign a pkijs BasicOCSPResponse with an Ed25519 key.
|
|
159
|
+
* Must be called after tbsResponseData.responses is populated.
|
|
160
|
+
*/
|
|
161
|
+
export async function signBasicOCSPResponse(basicResponse, privateKey) {
|
|
162
|
+
const algId = new pkijs.AlgorithmIdentifier({ algorithmId: ED25519_OID });
|
|
163
|
+
basicResponse.signatureAlgorithm = algId;
|
|
164
|
+
// Encode the TBS response data
|
|
165
|
+
const tbsDer = basicResponse.tbsResponseData.toSchema(true).toBER();
|
|
166
|
+
basicResponse.tbsResponseData.tbsView = new Uint8Array(tbsDer);
|
|
167
|
+
const sig = await webcrypto.subtle.sign('Ed25519', privateKey, tbsDer);
|
|
168
|
+
basicResponse.signature = new asn1js.BitString({ valueHex: sig });
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=certGenUtils.js.map
|