@harperfast/harper-pro 5.0.0-alpha.5 → 5.0.0-alpha.7
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/core/.github/workflows/integration-tests.yml +11 -0
- package/core/.github/workflows/notify-release-published.yaml +2 -2
- package/core/.github/workflows/unit-test.yml +2 -0
- package/core/bin/cliOperations.js +26 -68
- package/core/bin/harper.js +8 -13
- package/core/config/configUtils.js +11 -0
- package/core/integrationTests/deploy/deploy-from-github.test.ts +12 -2
- package/core/integrationTests/utils/harperLifecycle.ts +86 -15
- package/core/package.json +5 -7
- package/core/resources/DatabaseTransaction.ts +6 -1
- package/core/resources/RecordEncoder.ts +57 -3
- package/core/resources/RocksTransactionLogStore.ts +19 -5
- package/core/resources/Table.ts +103 -41
- package/core/resources/analytics/profile.ts +14 -7
- package/core/resources/auditStore.ts +28 -1
- package/core/security/impersonation.ts +160 -0
- package/core/security/role.js +9 -0
- package/core/server/operationsServer.ts +9 -0
- package/core/server/serverHelpers/serverHandlers.js +6 -1
- package/core/server/serverHelpers/serverUtilities.ts +1 -1
- package/core/unitTests/apiTests/analytics-test.mjs +1 -1
- package/core/unitTests/apiTests/graphql-querying-test.mjs +7 -9
- package/core/unitTests/apiTests/mqtt-test.mjs +2 -1
- package/core/unitTests/apiTests/setupTestApp.mjs +4 -5
- package/core/unitTests/config/configUtils.test.js +97 -0
- package/core/unitTests/resources/transaction.test.js +130 -0
- package/core/unitTests/security/impersonation.test.js +606 -0
- package/core/unitTests/security/role.test.js +57 -0
- package/core/unitTests/utility/operationPermissions.test.js +38 -1
- package/core/utility/hdbTerms.ts +1 -1
- package/core/utility/install/installer.js +18 -15
- package/core/utility/operationPermissions.ts +16 -0
- package/core/validation/role_validation.js +4 -8
- package/dist/core/bin/cliOperations.js +26 -68
- package/dist/core/bin/cliOperations.js.map +1 -1
- package/dist/core/bin/harper.js +8 -13
- package/dist/core/bin/harper.js.map +1 -1
- package/dist/core/config/configUtils.js +8 -0
- package/dist/core/config/configUtils.js.map +1 -1
- package/dist/core/resources/DatabaseTransaction.js +6 -1
- package/dist/core/resources/DatabaseTransaction.js.map +1 -1
- package/dist/core/resources/RecordEncoder.js +56 -4
- package/dist/core/resources/RecordEncoder.js.map +1 -1
- package/dist/core/resources/RocksTransactionLogStore.js +19 -5
- package/dist/core/resources/RocksTransactionLogStore.js.map +1 -1
- package/dist/core/resources/Table.js +94 -38
- package/dist/core/resources/Table.js.map +1 -1
- package/dist/core/resources/analytics/profile.js +13 -7
- package/dist/core/resources/analytics/profile.js.map +1 -1
- package/dist/core/resources/auditStore.js +29 -3
- package/dist/core/resources/auditStore.js.map +1 -1
- package/dist/core/security/impersonation.js +139 -0
- package/dist/core/security/impersonation.js.map +1 -0
- package/dist/core/security/role.js +8 -0
- package/dist/core/security/role.js.map +1 -1
- package/dist/core/server/operationsServer.js.map +1 -1
- package/dist/core/server/serverHelpers/serverHandlers.js +6 -1
- package/dist/core/server/serverHelpers/serverHandlers.js.map +1 -1
- package/dist/core/server/serverHelpers/serverUtilities.js +1 -1
- package/dist/core/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/core/utility/hdbTerms.js +1 -1
- package/dist/core/utility/hdbTerms.js.map +1 -1
- package/dist/core/utility/install/installer.js +17 -13
- package/dist/core/utility/install/installer.js.map +1 -1
- package/dist/core/utility/operationPermissions.js +15 -0
- package/dist/core/utility/operationPermissions.js.map +1 -1
- package/dist/core/validation/role_validation.js +4 -8
- package/dist/core/validation/role_validation.js.map +1 -1
- package/dist/replication/knownNodes.js +32 -8
- package/dist/replication/knownNodes.js.map +1 -1
- package/dist/replication/replicationConnection.js +117 -31
- package/dist/replication/replicationConnection.js.map +1 -1
- package/dist/replication/replicator.js +2 -2
- package/dist/replication/replicator.js.map +1 -1
- package/dist/replication/subscriptionManager.js +1 -26
- package/dist/replication/subscriptionManager.js.map +1 -1
- package/npm-shrinkwrap.json +503 -531
- package/package.json +5 -5
- package/replication/knownNodes.ts +48 -11
- package/replication/replicationConnection.ts +140 -33
- package/replication/replicator.ts +3 -3
- package/replication/subscriptionManager.ts +2 -30
- package/studio/web/assets/{index-CT88MT9s.js → index-Cd4R_BKr.js} +2 -2
- package/studio/web/assets/{index-CT88MT9s.js.map → index-Cd4R_BKr.js.map} +1 -1
- package/studio/web/assets/{index-BdvSVLGf.js → index-Cs_ZP9GJ.js} +2 -2
- package/studio/web/assets/{index-BdvSVLGf.js.map → index-Cs_ZP9GJ.js.map} +1 -1
- package/studio/web/assets/{index-C9FGGp3i.js → index-vG4Utcyg.js} +6 -6
- package/studio/web/assets/{index-C9FGGp3i.js.map → index-vG4Utcyg.js.map} +1 -1
- package/studio/web/assets/{index.lazy-Te-1e_Et.js → index.lazy-CqinvCXH.js} +2 -2
- package/studio/web/assets/{index.lazy-Te-1e_Et.js.map → index.lazy-CqinvCXH.js.map} +1 -1
- package/studio/web/assets/{profiler-BxnKkasB.js → profiler-hYHhy-KP.js} +2 -2
- package/studio/web/assets/{profiler-BxnKkasB.js.map → profiler-hYHhy-KP.js.map} +1 -1
- package/studio/web/assets/{react-redux-B82_Ptsp.js → react-redux-zvetwa5O.js} +2 -2
- package/studio/web/assets/{react-redux-B82_Ptsp.js.map → react-redux-zvetwa5O.js.map} +1 -1
- package/studio/web/assets/{startRecording-BwtmyzVD.js → startRecording-lV1Mo15A.js} +2 -2
- package/studio/web/assets/{startRecording-BwtmyzVD.js.map → startRecording-lV1Mo15A.js.map} +1 -1
- package/studio/web/index.html +1 -1
|
@@ -160,5 +160,16 @@ jobs:
|
|
|
160
160
|
name: harper-build-artifacts-node-${{ matrix.node-version }}
|
|
161
161
|
|
|
162
162
|
- name: Run Integration Test Shard ${{ matrix.shard }}
|
|
163
|
+
env:
|
|
164
|
+
HARPER_INTEGRATION_TEST_LOG_DIR: /tmp/harper-integration-test-logs
|
|
163
165
|
run: |
|
|
164
166
|
npm run test:integration -- --shard=${{ matrix.shard }}/4
|
|
167
|
+
|
|
168
|
+
- name: Upload Harper server logs
|
|
169
|
+
if: failure()
|
|
170
|
+
uses: actions/upload-artifact@v4
|
|
171
|
+
with:
|
|
172
|
+
name: harper-server-logs-node-${{ matrix.node-version }}-shard-${{ matrix.shard }}
|
|
173
|
+
path: /tmp/harper-integration-test-logs/
|
|
174
|
+
retention-days: 3
|
|
175
|
+
if-no-files-found: ignore
|
|
@@ -20,13 +20,13 @@ jobs:
|
|
|
20
20
|
payload: |
|
|
21
21
|
{
|
|
22
22
|
"channel": "#development-ci",
|
|
23
|
-
"text": "Harper
|
|
23
|
+
"text": "Harper ${{ github.event.release.tag_name }} GitHub release published",
|
|
24
24
|
"blocks": [
|
|
25
25
|
{
|
|
26
26
|
"type": "header",
|
|
27
27
|
"text": {
|
|
28
28
|
"type": "plain_text",
|
|
29
|
-
"text": "Harper
|
|
29
|
+
"text": "Harper ${{ github.event.release.tag_name }} release"
|
|
30
30
|
}
|
|
31
31
|
},
|
|
32
32
|
{
|
|
@@ -12,60 +12,6 @@ const { encode } = require('cbor-x');
|
|
|
12
12
|
const { getHdbPid } = require('../utility/processManagement/processManagement.js');
|
|
13
13
|
const { initConfig } = require('../config/configUtils.js');
|
|
14
14
|
|
|
15
|
-
const SUPPORTED_OPS = [
|
|
16
|
-
'describe_table',
|
|
17
|
-
'describe_all',
|
|
18
|
-
'describe_database',
|
|
19
|
-
'list_users',
|
|
20
|
-
'list_roles',
|
|
21
|
-
'drop_role',
|
|
22
|
-
'add_user',
|
|
23
|
-
'alter_user',
|
|
24
|
-
'drop_user',
|
|
25
|
-
'restart_service',
|
|
26
|
-
'restart',
|
|
27
|
-
'create_database',
|
|
28
|
-
'drop_database',
|
|
29
|
-
'create_table',
|
|
30
|
-
'drop_table',
|
|
31
|
-
'create_attribute',
|
|
32
|
-
'drop_attribute',
|
|
33
|
-
'search_by_id',
|
|
34
|
-
'insert',
|
|
35
|
-
'update',
|
|
36
|
-
'upsert',
|
|
37
|
-
'delete',
|
|
38
|
-
'search_by_value',
|
|
39
|
-
'csv_file_load',
|
|
40
|
-
'csv_url_load',
|
|
41
|
-
'add_component',
|
|
42
|
-
'deploy_component',
|
|
43
|
-
'package_component',
|
|
44
|
-
'drop_component',
|
|
45
|
-
'get_components',
|
|
46
|
-
'get_component_file',
|
|
47
|
-
'set_component_file',
|
|
48
|
-
'get_job',
|
|
49
|
-
'search_jobs_by_start_date',
|
|
50
|
-
'read_log',
|
|
51
|
-
'read_transaction_log',
|
|
52
|
-
'read_audit_log',
|
|
53
|
-
'delete_transaction_logs_before',
|
|
54
|
-
'purge_stream',
|
|
55
|
-
'delete_records_before',
|
|
56
|
-
'install_node_modules',
|
|
57
|
-
'set_configuration',
|
|
58
|
-
'get_configuration',
|
|
59
|
-
'create_authentication_tokens',
|
|
60
|
-
'refresh_operation_token',
|
|
61
|
-
'system_information',
|
|
62
|
-
'sql',
|
|
63
|
-
'get_status',
|
|
64
|
-
'set_status',
|
|
65
|
-
'clear_status',
|
|
66
|
-
'get_usage_licenses',
|
|
67
|
-
];
|
|
68
|
-
|
|
69
15
|
const OP_ALIASES = { deploy: 'deploy_component', package: 'package_component' };
|
|
70
16
|
|
|
71
17
|
module.exports = { cliOperations, buildRequest };
|
|
@@ -88,9 +34,7 @@ const PREPARE_OPERATION = {
|
|
|
88
34
|
function buildRequest() {
|
|
89
35
|
const req = {};
|
|
90
36
|
for (const arg of process.argv.slice(2)) {
|
|
91
|
-
if (
|
|
92
|
-
req.operation = arg;
|
|
93
|
-
} else if (OP_ALIASES.hasOwnProperty(arg)) {
|
|
37
|
+
if (OP_ALIASES.hasOwnProperty(arg)) {
|
|
94
38
|
req.operation = OP_ALIASES[arg];
|
|
95
39
|
} else if (arg.includes('=')) {
|
|
96
40
|
let [first, ...rest] = arg.split('=');
|
|
@@ -103,6 +47,9 @@ function buildRequest() {
|
|
|
103
47
|
}
|
|
104
48
|
|
|
105
49
|
req[first] = rest;
|
|
50
|
+
} else {
|
|
51
|
+
// operation should only be in the first arg
|
|
52
|
+
req.operation ??= arg;
|
|
106
53
|
}
|
|
107
54
|
}
|
|
108
55
|
|
|
@@ -143,12 +90,12 @@ async function cliOperations(req) {
|
|
|
143
90
|
initConfig();
|
|
144
91
|
if (!getHdbPid()) {
|
|
145
92
|
console.error('Harper Pro must be running to perform this operation');
|
|
146
|
-
process.exit();
|
|
93
|
+
process.exit(1);
|
|
147
94
|
}
|
|
148
95
|
|
|
149
96
|
if (!fs.existsSync(envMgr.get(terms.CONFIG_PARAMS.OPERATIONSAPI_NETWORK_DOMAINSOCKET))) {
|
|
150
97
|
console.error('No domain socket found, unable to perform this operation');
|
|
151
|
-
process.exit();
|
|
98
|
+
process.exit(1);
|
|
152
99
|
}
|
|
153
100
|
}
|
|
154
101
|
await PREPARE_OPERATION[req.operation]?.(req);
|
|
@@ -178,22 +125,33 @@ async function cliOperations(req) {
|
|
|
178
125
|
};
|
|
179
126
|
}
|
|
180
127
|
|
|
128
|
+
let responseLog;
|
|
181
129
|
if (req.json) {
|
|
182
|
-
|
|
130
|
+
responseLog = JSON.stringify(responseData, null, 2);
|
|
183
131
|
} else {
|
|
184
|
-
|
|
132
|
+
responseLog = YAML.stringify(responseData).trim();
|
|
185
133
|
}
|
|
186
134
|
|
|
135
|
+
const { statusCode } = response;
|
|
136
|
+
if (statusCode < 200 || (statusCode >= 300 && statusCode !== 304)) {
|
|
137
|
+
const errorPrefix = responseLog.startsWith('error:') ? '' : 'error: ';
|
|
138
|
+
console.error(`${errorPrefix}${responseLog}`);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
console.log(responseLog);
|
|
143
|
+
|
|
187
144
|
return responseData;
|
|
188
145
|
} catch (err) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
146
|
+
if (err.code === 'ENOENT' || err.code === 'ECONNREFUSED') {
|
|
147
|
+
console.error(`error: Failed to connect to Harper Pro (${err.code}): ${err.message}`);
|
|
148
|
+
} else if (err.code === 'EACCES') {
|
|
149
|
+
console.error(`error: Permission denied accessing the domain socket: ${err.message}`);
|
|
150
|
+
} else if (err.code === 'ENOTFOUND') {
|
|
151
|
+
console.error(`error: Host not found: "${err.hostname}" ${err.message}`);
|
|
194
152
|
} else {
|
|
195
|
-
|
|
153
|
+
console.error(`error: ${err.message ?? err}`);
|
|
196
154
|
}
|
|
197
|
-
|
|
155
|
+
process.exit(1);
|
|
198
156
|
}
|
|
199
157
|
}
|
package/core/bin/harper.js
CHANGED
|
@@ -56,14 +56,9 @@ async function harper() {
|
|
|
56
56
|
service = process.argv[2].toLowerCase();
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
const cliApiOp = cliOperations.buildRequest();
|
|
60
|
-
if (cliApiOp.operation) service = SERVICE_ACTIONS_ENUM.OPERATION;
|
|
61
|
-
|
|
62
59
|
switch (service) {
|
|
63
|
-
case SERVICE_ACTIONS_ENUM.
|
|
64
|
-
|
|
65
|
-
await cliOperations.cliOperations(cliApiOp);
|
|
66
|
-
return;
|
|
60
|
+
case SERVICE_ACTIONS_ENUM.HELP:
|
|
61
|
+
return HELP;
|
|
67
62
|
case SERVICE_ACTIONS_ENUM.START:
|
|
68
63
|
return require('./run.js').launch();
|
|
69
64
|
case SERVICE_ACTIONS_ENUM.INSTALL:
|
|
@@ -121,22 +116,22 @@ async function harper() {
|
|
|
121
116
|
console.warn(
|
|
122
117
|
`It appears you are running Harper Pro in an application directory, but did not specify the path. I'll go ahead and run the application for you since that's probably what you meant. But to avoid this warning in the future, run applications in the current directory like this: "harper ${service} ."`
|
|
123
118
|
);
|
|
124
|
-
process.env.RUN_HDB_APP =
|
|
119
|
+
process.env.RUN_HDB_APP = process.cwd();
|
|
125
120
|
} else if (fs.existsSync(hdbTerms.HARPER_CONFIG_FILE) || fs.existsSync(hdbTerms.HDB_CONFIG_FILE)) {
|
|
126
121
|
console.warn(
|
|
127
122
|
`It appears you are running Harper Pro in a root data directory, but did not specify the path. I'll go ahead and run Harper Pro with its root path set to "." for you since that's probably what you meant. But to avoid this warning in the future, run it like this: "harper ${service} ."`
|
|
128
123
|
);
|
|
129
|
-
process.env.ROOTPATH =
|
|
124
|
+
process.env.ROOTPATH = process.cwd();
|
|
130
125
|
}
|
|
131
126
|
}
|
|
132
127
|
// fall through
|
|
133
128
|
case undefined: // run harperdb in the foreground in standard mode
|
|
134
129
|
return require('./run.js').main();
|
|
135
130
|
default:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
return
|
|
131
|
+
const cliApiOp = cliOperations.buildRequest();
|
|
132
|
+
logger.trace('calling cli operations with:', cliApiOp);
|
|
133
|
+
await cliOperations.cliOperations(cliApiOp);
|
|
134
|
+
return;
|
|
140
135
|
}
|
|
141
136
|
}
|
|
142
137
|
exports.harper = harper;
|
|
@@ -835,6 +835,17 @@ function applyRuntimeEnvVarConfig(configDoc, configFilePath, options = {}) {
|
|
|
835
835
|
// Apply env vars with source tracking and drift detection
|
|
836
836
|
applyRuntimeEnvConfig(configObj, rootPath, options);
|
|
837
837
|
|
|
838
|
+
// If securePort was set to the same value as port, auto-null port to avoid clashing
|
|
839
|
+
if (configObj.http?.port && configObj.http?.port === configObj.http?.securePort) {
|
|
840
|
+
configObj.http.port = null;
|
|
841
|
+
}
|
|
842
|
+
if (
|
|
843
|
+
configObj.operationsApi?.network?.port &&
|
|
844
|
+
configObj.operationsApi?.network?.port === configObj.operationsApi?.network?.securePort
|
|
845
|
+
) {
|
|
846
|
+
configObj.operationsApi.network.port = null;
|
|
847
|
+
}
|
|
848
|
+
|
|
838
849
|
// Update the YAML document's contents
|
|
839
850
|
// We update only the 'contents' property to preserve the Document instance and its methods
|
|
840
851
|
const mergedDoc = YAML.parseDocument(YAML.stringify(configObj), { simpleKeys: true });
|
|
@@ -8,7 +8,6 @@ import { deepStrictEqual, ok, strictEqual } from 'node:assert/strict';
|
|
|
8
8
|
import { setupHarper, teardownHarper, type ContextWithHarper } from '../utils/harperLifecycle.ts';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
10
|
import { existsSync } from 'node:fs';
|
|
11
|
-
import { setTimeout as sleep } from 'node:timers/promises';
|
|
12
11
|
import { readFile } from 'node:fs/promises';
|
|
13
12
|
import { parse } from 'yaml';
|
|
14
13
|
|
|
@@ -44,7 +43,18 @@ suite('GitHub application deployment', (ctx: ContextWithHarper) => {
|
|
|
44
43
|
strictEqual(response.status, 200);
|
|
45
44
|
const body = await response.json();
|
|
46
45
|
deepStrictEqual(body, { message: 'Successfully deployed: test-application, restarting Harper' });
|
|
47
|
-
|
|
46
|
+
// Poll until the application API is ready (restart is async, fixed sleep is flaky)
|
|
47
|
+
const deadline = Date.now() + 30_000;
|
|
48
|
+
while (true) {
|
|
49
|
+
try {
|
|
50
|
+
const check = await fetch(`${ctx.harper.httpURL}/Greeting`);
|
|
51
|
+
if (check.status === 200) break;
|
|
52
|
+
} catch {
|
|
53
|
+
// server not yet accepting connections
|
|
54
|
+
}
|
|
55
|
+
if (Date.now() > deadline) throw new Error('Timed out waiting for application to be ready after restart');
|
|
56
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
57
|
+
}
|
|
48
58
|
ok(existsSync(join(ctx.harper.installDir, 'components', project)));
|
|
49
59
|
|
|
50
60
|
// const harperAppLock = await readFile(join(ctx.harper.installDir, 'harper-application-lock.json'), 'utf-8');
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import { spawn, ChildProcess } from 'node:child_process';
|
|
3
|
+
import { createWriteStream, existsSync, rmSync, type WriteStream } from 'node:fs';
|
|
3
4
|
import { join } from 'node:path';
|
|
4
|
-
import { existsSync } from 'node:fs';
|
|
5
5
|
import { tmpdir } from 'node:os';
|
|
6
|
-
import { mkdtemp, rm } from 'node:fs/promises';
|
|
6
|
+
import { mkdtemp, mkdir, rm } from 'node:fs/promises';
|
|
7
7
|
import { type SuiteContext, type TestContext } from 'node:test';
|
|
8
8
|
import { getNextAvailableLoopbackAddress, releaseLoopbackAddress } from './loopbackAddressPool.ts';
|
|
9
9
|
|
|
@@ -13,6 +13,7 @@ export const OPERATIONS_API_PORT = 9925;
|
|
|
13
13
|
export const DEFAULT_ADMIN_USERNAME = 'admin';
|
|
14
14
|
export const DEFAULT_ADMIN_PASSWORD = 'Abc1234!';
|
|
15
15
|
const DEFAULT_STARTUP_TIMEOUT_MS = parseInt(process.env.HARPER_INTEGRATION_TEST_STARTUP_TIMEOUT_MS, 10) || 30000;
|
|
16
|
+
const LOG_DIR = process.env.HARPER_INTEGRATION_TEST_LOG_DIR;
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Options for setting up a Harper instance.
|
|
@@ -51,6 +52,8 @@ export interface HarperContext {
|
|
|
51
52
|
hostname: string;
|
|
52
53
|
/** Child process for the Harper instance */
|
|
53
54
|
process: ChildProcess;
|
|
55
|
+
/** Absolute path to the log directory for this suite (only set when HARPER_INTEGRATION_TEST_LOG_DIR is configured) */
|
|
56
|
+
logDir?: string;
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
/**
|
|
@@ -81,27 +84,63 @@ function getHarperScript(): string {
|
|
|
81
84
|
return harperScript;
|
|
82
85
|
}
|
|
83
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Sanitizes a string for use as a filesystem directory name.
|
|
89
|
+
*/
|
|
90
|
+
function sanitizeForFilesystem(name: string): string {
|
|
91
|
+
return name
|
|
92
|
+
.replace(/[^a-zA-Z0-9_-]/g, '_')
|
|
93
|
+
.replace(/_+/g, '_')
|
|
94
|
+
.substring(0, 100);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
interface RunHarperCommandOptions {
|
|
98
|
+
args: string[];
|
|
99
|
+
env: any;
|
|
100
|
+
completionMessage?: string;
|
|
101
|
+
/** When set, stdout and stderr are written to files in this directory */
|
|
102
|
+
logDir?: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
84
105
|
/**
|
|
85
106
|
* Runs a Harper CLI command and captures output.
|
|
86
107
|
*
|
|
87
|
-
*
|
|
108
|
+
* When `logDir` is provided, stdout and stderr are also written to files
|
|
109
|
+
* (`stdout.log` and `stderr.log`) in that directory.
|
|
110
|
+
*
|
|
88
111
|
* @throws {AssertionError} If the command exits with a non-zero status code
|
|
89
112
|
*/
|
|
90
|
-
function runHarperCommand(args
|
|
113
|
+
function runHarperCommand({ args, env, completionMessage, logDir }: RunHarperCommandOptions): Promise<ChildProcess> {
|
|
91
114
|
const harperScript = getHarperScript();
|
|
92
115
|
const proc = spawn('node', ['--trace-warnings', harperScript, ...args], {
|
|
93
116
|
env: { ...process.env, ...env },
|
|
94
117
|
});
|
|
118
|
+
|
|
119
|
+
let stdoutStream: WriteStream | undefined;
|
|
120
|
+
let stderrStream: WriteStream | undefined;
|
|
121
|
+
if (logDir) {
|
|
122
|
+
stdoutStream = createWriteStream(join(logDir, 'stdout.log'));
|
|
123
|
+
stderrStream = createWriteStream(join(logDir, 'stderr.log'));
|
|
124
|
+
}
|
|
125
|
+
|
|
95
126
|
return new Promise((resolve, reject) => {
|
|
96
127
|
let stdout = '';
|
|
97
128
|
let stderr = '';
|
|
98
129
|
let timer = setTimeout(() => {
|
|
99
|
-
|
|
130
|
+
let errorMessage = `Harper process timed out after ${DEFAULT_STARTUP_TIMEOUT_MS}ms`;
|
|
131
|
+
if (stdout) {
|
|
132
|
+
errorMessage += `\n\nstdout:\n${stdout}`;
|
|
133
|
+
}
|
|
134
|
+
if (stderr) {
|
|
135
|
+
errorMessage += `\n\nstderr:\n${stderr}`;
|
|
136
|
+
}
|
|
137
|
+
reject(errorMessage);
|
|
100
138
|
proc.kill();
|
|
101
139
|
}, DEFAULT_STARTUP_TIMEOUT_MS);
|
|
102
140
|
|
|
103
141
|
proc.stdout?.on('data', (data: Buffer) => {
|
|
104
142
|
const dataString = data.toString();
|
|
143
|
+
stdoutStream?.write(data);
|
|
105
144
|
if (completionMessage && dataString.includes(completionMessage)) {
|
|
106
145
|
clearTimeout(timer);
|
|
107
146
|
resolve(proc);
|
|
@@ -110,8 +149,15 @@ function runHarperCommand(args: string[], env: any, completionMessage?: string):
|
|
|
110
149
|
});
|
|
111
150
|
|
|
112
151
|
proc.stderr?.on('data', (data: Buffer) => {
|
|
152
|
+
stderrStream?.write(data);
|
|
113
153
|
stderr += data.toString();
|
|
114
154
|
});
|
|
155
|
+
|
|
156
|
+
proc.on('exit', () => {
|
|
157
|
+
stdoutStream?.end();
|
|
158
|
+
stderrStream?.end();
|
|
159
|
+
});
|
|
160
|
+
|
|
115
161
|
proc.on('error', (error) => {
|
|
116
162
|
reject(error);
|
|
117
163
|
});
|
|
@@ -179,9 +225,33 @@ export async function startHarper(ctx: ContextWithHarper, options?: SetupHarperO
|
|
|
179
225
|
);
|
|
180
226
|
const installDir = ctx.harper?.installDir ?? (await mkdtemp(installDirPrefix));
|
|
181
227
|
|
|
182
|
-
const loopbackAddress = ctx.hostname ?? (await getNextAvailableLoopbackAddress());
|
|
183
|
-
|
|
184
|
-
|
|
228
|
+
const loopbackAddress = ctx.harper?.hostname ?? (await getNextAvailableLoopbackAddress());
|
|
229
|
+
|
|
230
|
+
// Set up per-suite log directory when HARPER_INTEGRATION_TEST_LOG_DIR is configured
|
|
231
|
+
let logDir: string | undefined;
|
|
232
|
+
if (LOG_DIR) {
|
|
233
|
+
const suiteName = sanitizeForFilesystem(ctx.name || 'unknown');
|
|
234
|
+
logDir = join(LOG_DIR, `${suiteName}-${sanitizeForFilesystem(loopbackAddress)}`);
|
|
235
|
+
await mkdir(logDir, { recursive: true });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Point Harper's log directory to the suite log dir so hdb.log is preserved for upload
|
|
239
|
+
const config = { ...(options?.config || {}) };
|
|
240
|
+
if (logDir) {
|
|
241
|
+
config.logging = { ...config.logging, root: logDir };
|
|
242
|
+
|
|
243
|
+
// Clean up log directory on successful exit — only keep logs when tests fail
|
|
244
|
+
process.on('exit', (code) => {
|
|
245
|
+
if (code === 0) {
|
|
246
|
+
try {
|
|
247
|
+
rmSync(logDir, { recursive: true, force: true });
|
|
248
|
+
} catch {}
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const harperProcess = await runHarperCommand({
|
|
254
|
+
args: [
|
|
185
255
|
`--ROOTPATH=${installDir}`,
|
|
186
256
|
'--DEFAULTS_MODE=dev',
|
|
187
257
|
`--HDB_ADMIN_USERNAME=${DEFAULT_ADMIN_USERNAME}`,
|
|
@@ -192,11 +262,12 @@ export async function startHarper(ctx: ContextWithHarper, options?: SetupHarperO
|
|
|
192
262
|
`--HTTP_PORT=${loopbackAddress}:${HTTP_PORT}`,
|
|
193
263
|
`--OPERATIONSAPI_NETWORK_PORT=${loopbackAddress}:${OPERATIONS_API_PORT}`,
|
|
194
264
|
'--LOGGING_LEVEL=debug',
|
|
195
|
-
'--HARPER_SET_CONFIG=' + JSON.stringify(
|
|
265
|
+
'--HARPER_SET_CONFIG=' + JSON.stringify(config),
|
|
196
266
|
],
|
|
197
|
-
options?.env || {},
|
|
198
|
-
'successfully started'
|
|
199
|
-
|
|
267
|
+
env: options?.env || {},
|
|
268
|
+
completionMessage: 'successfully started',
|
|
269
|
+
logDir,
|
|
270
|
+
});
|
|
200
271
|
|
|
201
272
|
ctx.harper = {
|
|
202
273
|
installDir,
|
|
@@ -208,6 +279,7 @@ export async function startHarper(ctx: ContextWithHarper, options?: SetupHarperO
|
|
|
208
279
|
operationsAPIURL: `http://${loopbackAddress}:${OPERATIONS_API_PORT}`,
|
|
209
280
|
hostname: loopbackAddress,
|
|
210
281
|
process: harperProcess,
|
|
282
|
+
logDir,
|
|
211
283
|
};
|
|
212
284
|
|
|
213
285
|
return ctx;
|
|
@@ -218,7 +290,6 @@ export async function startHarper(ctx: ContextWithHarper, options?: SetupHarperO
|
|
|
218
290
|
*
|
|
219
291
|
* This function stops the Harper instance, releases the loopback address,
|
|
220
292
|
* and removes the installation directory.
|
|
221
|
-
*
|
|
222
293
|
* @param ctx - The test context with Harper instance details
|
|
223
294
|
*
|
|
224
295
|
* @example
|
|
@@ -235,7 +306,7 @@ export async function startHarper(ctx: ContextWithHarper, options?: SetupHarperO
|
|
|
235
306
|
* ```
|
|
236
307
|
*/
|
|
237
308
|
export async function teardownHarper(ctx: ContextWithHarper): Promise<void> {
|
|
238
|
-
await new Promise((resolve) => {
|
|
309
|
+
await new Promise<void>((resolve) => {
|
|
239
310
|
let timer: NodeJS.Timeout;
|
|
240
311
|
ctx.harper.process.on('exit', () => {
|
|
241
312
|
resolve();
|
|
@@ -246,7 +317,7 @@ export async function teardownHarper(ctx: ContextWithHarper): Promise<void> {
|
|
|
246
317
|
try {
|
|
247
318
|
ctx.harper.process.kill('SIGKILL');
|
|
248
319
|
} catch {
|
|
249
|
-
// possible that the process terminated but the exit event hasn't
|
|
320
|
+
// possible that the process terminated but the exit event hasn't fired yet
|
|
250
321
|
}
|
|
251
322
|
resolve();
|
|
252
323
|
}, 200);
|
package/core/package.json
CHANGED
|
@@ -95,9 +95,7 @@
|
|
|
95
95
|
}
|
|
96
96
|
},
|
|
97
97
|
"exports": {
|
|
98
|
-
".": "./index.js"
|
|
99
|
-
"./v1": "./v1.js",
|
|
100
|
-
"./v2": "./v2.js"
|
|
98
|
+
".": "./dist/index.js"
|
|
101
99
|
},
|
|
102
100
|
"devDependencies": {
|
|
103
101
|
"@harperdb/code-guidelines": "^0.0.6",
|
|
@@ -140,7 +138,7 @@
|
|
|
140
138
|
"@fastify/cors": "~9.0.1",
|
|
141
139
|
"@fastify/static": "~7.0.4",
|
|
142
140
|
"@harperfast/extended-iterable": "^1.0.1",
|
|
143
|
-
"@harperfast/rocksdb-js": "^0.1.
|
|
141
|
+
"@harperfast/rocksdb-js": "^0.1.11",
|
|
144
142
|
"@turf/area": "6.5.0",
|
|
145
143
|
"@turf/boolean-contains": "6.5.0",
|
|
146
144
|
"@turf/boolean-disjoint": "6.5.0",
|
|
@@ -153,7 +151,7 @@
|
|
|
153
151
|
"alasql": "4.6.6",
|
|
154
152
|
"argon2": "0.44.0",
|
|
155
153
|
"asn1js": "3.0.7",
|
|
156
|
-
"cbor-x": "1.6.
|
|
154
|
+
"cbor-x": "1.6.4",
|
|
157
155
|
"chalk": "4.1.2",
|
|
158
156
|
"chokidar": "^4.0.3",
|
|
159
157
|
"cli-progress": "3.12.0",
|
|
@@ -182,14 +180,14 @@
|
|
|
182
180
|
"minimist": "1.2.8",
|
|
183
181
|
"moment": "2.30.1",
|
|
184
182
|
"mqtt-packet": "~9.0.1",
|
|
185
|
-
"msgpackr": "1.11.
|
|
183
|
+
"msgpackr": "1.11.9",
|
|
186
184
|
"needle": "3.3.1",
|
|
187
185
|
"node-forge": "^1.3.1",
|
|
188
186
|
"node-stream-zip": "1.15.0",
|
|
189
187
|
"node-unix-socket": "0.2.7",
|
|
190
188
|
"normalize-path": "^3.0.0",
|
|
191
189
|
"ora": "8.2.0",
|
|
192
|
-
"ordered-binary": "1.
|
|
190
|
+
"ordered-binary": "1.6.1",
|
|
193
191
|
"papaparse": "5.5.3",
|
|
194
192
|
"passport": "0.7.0",
|
|
195
193
|
"passport-http": "0.3.0",
|
|
@@ -351,7 +351,12 @@ function startMonitoringTxns() {
|
|
|
351
351
|
);
|
|
352
352
|
// reset the transaction
|
|
353
353
|
try {
|
|
354
|
-
txn.commit();
|
|
354
|
+
const result = txn.commit();
|
|
355
|
+
if (result?.then) {
|
|
356
|
+
result.catch((error) => {
|
|
357
|
+
harperLogger.debug?.(`Error committing timed out transaction: ${error.message}`);
|
|
358
|
+
});
|
|
359
|
+
}
|
|
355
360
|
} catch (error) {
|
|
356
361
|
harperLogger.debug?.(`Error committing timed out transaction: ${error.message}`);
|
|
357
362
|
}
|