@git-stunts/git-warp 10.7.0 → 11.2.1
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 +53 -32
- package/SECURITY.md +64 -0
- package/bin/cli/commands/check.js +168 -0
- package/bin/cli/commands/doctor/checks.js +422 -0
- package/bin/cli/commands/doctor/codes.js +46 -0
- package/bin/cli/commands/doctor/index.js +239 -0
- package/bin/cli/commands/doctor/types.js +89 -0
- package/bin/cli/commands/history.js +73 -0
- package/bin/cli/commands/info.js +139 -0
- package/bin/cli/commands/install-hooks.js +128 -0
- package/bin/cli/commands/materialize.js +99 -0
- package/bin/cli/commands/path.js +88 -0
- package/bin/cli/commands/query.js +194 -0
- package/bin/cli/commands/registry.js +28 -0
- package/bin/cli/commands/seek.js +592 -0
- package/bin/cli/commands/trust.js +154 -0
- package/bin/cli/commands/verify-audit.js +113 -0
- package/bin/cli/commands/view.js +45 -0
- package/bin/cli/infrastructure.js +336 -0
- package/bin/cli/schemas.js +177 -0
- package/bin/cli/shared.js +244 -0
- package/bin/cli/types.js +85 -0
- package/bin/presenters/index.js +214 -0
- package/bin/presenters/json.js +66 -0
- package/bin/presenters/text.js +543 -0
- package/bin/warp-graph.js +19 -2824
- package/index.d.ts +32 -2
- package/index.js +2 -0
- package/package.json +9 -7
- package/src/domain/WarpGraph.js +106 -3252
- package/src/domain/errors/QueryError.js +2 -2
- package/src/domain/errors/TrustError.js +29 -0
- package/src/domain/errors/index.js +1 -0
- package/src/domain/services/AuditMessageCodec.js +137 -0
- package/src/domain/services/AuditReceiptService.js +471 -0
- package/src/domain/services/AuditVerifierService.js +693 -0
- package/src/domain/services/HttpSyncServer.js +36 -22
- package/src/domain/services/MessageCodecInternal.js +3 -0
- package/src/domain/services/MessageSchemaDetector.js +2 -2
- package/src/domain/services/SyncAuthService.js +69 -3
- package/src/domain/services/WarpMessageCodec.js +4 -1
- package/src/domain/trust/TrustCanonical.js +42 -0
- package/src/domain/trust/TrustCrypto.js +111 -0
- package/src/domain/trust/TrustEvaluator.js +180 -0
- package/src/domain/trust/TrustRecordService.js +274 -0
- package/src/domain/trust/TrustStateBuilder.js +209 -0
- package/src/domain/trust/canonical.js +68 -0
- package/src/domain/trust/reasonCodes.js +64 -0
- package/src/domain/trust/schemas.js +160 -0
- package/src/domain/trust/verdict.js +42 -0
- package/src/domain/types/git-cas.d.ts +20 -0
- package/src/domain/utils/RefLayout.js +59 -0
- package/src/domain/warp/PatchSession.js +18 -0
- package/src/domain/warp/Writer.js +18 -3
- package/src/domain/warp/_internal.js +26 -0
- package/src/domain/warp/_wire.js +58 -0
- package/src/domain/warp/_wiredMethods.d.ts +100 -0
- package/src/domain/warp/checkpoint.methods.js +397 -0
- package/src/domain/warp/fork.methods.js +323 -0
- package/src/domain/warp/materialize.methods.js +188 -0
- package/src/domain/warp/materializeAdvanced.methods.js +339 -0
- package/src/domain/warp/patch.methods.js +529 -0
- package/src/domain/warp/provenance.methods.js +284 -0
- package/src/domain/warp/query.methods.js +279 -0
- package/src/domain/warp/subscribe.methods.js +272 -0
- package/src/domain/warp/sync.methods.js +549 -0
- package/src/infrastructure/adapters/GitGraphAdapter.js +67 -1
- package/src/infrastructure/adapters/InMemoryGraphAdapter.js +36 -0
- package/src/ports/CommitPort.js +10 -0
- package/src/ports/RefPort.js +17 -0
- package/src/hooks/post-merge.sh +0 -60
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// History
|
|
5
|
+
// ============================================================================
|
|
6
|
+
|
|
7
|
+
export const historySchema = z.object({
|
|
8
|
+
node: z.string().optional(),
|
|
9
|
+
}).strict();
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Install-hooks
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
export const installHooksSchema = z.object({
|
|
16
|
+
force: z.boolean().default(false),
|
|
17
|
+
}).strict();
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Verify-audit
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
export const verifyAuditSchema = z.object({
|
|
24
|
+
since: z.string().min(1, 'Missing value for --since').optional(),
|
|
25
|
+
writer: z.string().min(1, 'Missing value for --writer').optional(),
|
|
26
|
+
'trust-mode': z.enum(['warn', 'enforce']).optional(),
|
|
27
|
+
'trust-pin': z.string().min(1, 'Missing value for --trust-pin').optional(),
|
|
28
|
+
}).strict();
|
|
29
|
+
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// Path
|
|
32
|
+
// ============================================================================
|
|
33
|
+
|
|
34
|
+
export const pathSchema = z.object({
|
|
35
|
+
from: z.string().optional(),
|
|
36
|
+
to: z.string().optional(),
|
|
37
|
+
dir: z.enum(['out', 'in', 'both']).optional(),
|
|
38
|
+
label: z.union([z.string(), z.array(z.string())]).optional(),
|
|
39
|
+
'max-depth': z.coerce.number().int().nonnegative().optional(),
|
|
40
|
+
}).strict().transform((val) => ({
|
|
41
|
+
from: val.from ?? null,
|
|
42
|
+
to: val.to ?? null,
|
|
43
|
+
dir: val.dir,
|
|
44
|
+
labels: Array.isArray(val.label) ? val.label : val.label ? [val.label] : [],
|
|
45
|
+
maxDepth: val['max-depth'],
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
// ============================================================================
|
|
49
|
+
// Query
|
|
50
|
+
// ============================================================================
|
|
51
|
+
|
|
52
|
+
export const querySchema = z.object({
|
|
53
|
+
match: z.string().optional(),
|
|
54
|
+
'where-prop': z.union([z.string(), z.array(z.string())]).optional(),
|
|
55
|
+
select: z.string().optional(),
|
|
56
|
+
}).strict().transform((val) => ({
|
|
57
|
+
match: val.match ?? null,
|
|
58
|
+
whereProp: Array.isArray(val['where-prop']) ? val['where-prop'] : val['where-prop'] ? [val['where-prop']] : [],
|
|
59
|
+
select: val.select,
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
// ============================================================================
|
|
63
|
+
// View
|
|
64
|
+
// ============================================================================
|
|
65
|
+
|
|
66
|
+
export const viewSchema = z.object({
|
|
67
|
+
list: z.boolean().default(false),
|
|
68
|
+
log: z.boolean().default(false),
|
|
69
|
+
}).strict();
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// Trust
|
|
73
|
+
// ============================================================================
|
|
74
|
+
|
|
75
|
+
export const trustSchema = z.object({
|
|
76
|
+
mode: z.enum(['warn', 'enforce']).optional(),
|
|
77
|
+
'trust-pin': z.string().min(1, 'Missing value for --trust-pin').optional(),
|
|
78
|
+
}).strict().transform((val) => ({
|
|
79
|
+
mode: val.mode ?? null,
|
|
80
|
+
trustPin: val['trust-pin'] ?? null,
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
// ============================================================================
|
|
84
|
+
// Doctor
|
|
85
|
+
// ============================================================================
|
|
86
|
+
|
|
87
|
+
export const doctorSchema = z.object({
|
|
88
|
+
strict: z.boolean().default(false),
|
|
89
|
+
}).strict();
|
|
90
|
+
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// Seek
|
|
93
|
+
// ============================================================================
|
|
94
|
+
|
|
95
|
+
export const seekSchema = z.object({
|
|
96
|
+
tick: z.string().optional(),
|
|
97
|
+
latest: z.boolean().default(false),
|
|
98
|
+
save: z.string().min(1, 'Missing value for --save').optional(),
|
|
99
|
+
load: z.string().min(1, 'Missing value for --load').optional(),
|
|
100
|
+
list: z.boolean().default(false),
|
|
101
|
+
drop: z.string().min(1, 'Missing value for --drop').optional(),
|
|
102
|
+
'clear-cache': z.boolean().default(false),
|
|
103
|
+
'no-persistent-cache': z.boolean().default(false),
|
|
104
|
+
diff: z.boolean().default(false),
|
|
105
|
+
'diff-limit': z.coerce.number().int({ message: '--diff-limit must be a positive integer' }).positive({ message: '--diff-limit must be a positive integer' }).default(2000),
|
|
106
|
+
}).strict().superRefine((val, ctx) => {
|
|
107
|
+
// Count mutually exclusive action flags
|
|
108
|
+
const actions = [
|
|
109
|
+
val.tick !== undefined,
|
|
110
|
+
val.latest,
|
|
111
|
+
val.save !== undefined,
|
|
112
|
+
val.load !== undefined,
|
|
113
|
+
val.list,
|
|
114
|
+
val.drop !== undefined,
|
|
115
|
+
val['clear-cache'],
|
|
116
|
+
].filter(Boolean);
|
|
117
|
+
|
|
118
|
+
if (actions.length > 1) {
|
|
119
|
+
ctx.addIssue({
|
|
120
|
+
code: z.ZodIssueCode.custom,
|
|
121
|
+
message: 'Only one seek action flag allowed at a time (--tick, --latest, --save, --load, --list, --drop, --clear-cache)',
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// --diff only with tick/latest/load
|
|
126
|
+
const DIFF_ACTIONS = val.tick !== undefined || val.latest || val.load !== undefined;
|
|
127
|
+
if (val.diff && !DIFF_ACTIONS) {
|
|
128
|
+
ctx.addIssue({
|
|
129
|
+
code: z.ZodIssueCode.custom,
|
|
130
|
+
message: '--diff cannot be used without --tick, --latest, or --load',
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// --diff-limit requires --diff
|
|
135
|
+
if (val['diff-limit'] !== 2000 && !val.diff) {
|
|
136
|
+
ctx.addIssue({
|
|
137
|
+
code: z.ZodIssueCode.custom,
|
|
138
|
+
message: '--diff-limit requires --diff',
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}).transform((val) => {
|
|
142
|
+
/** @type {string} */
|
|
143
|
+
let action = 'status';
|
|
144
|
+
/** @type {string|null} */
|
|
145
|
+
let tickValue = null;
|
|
146
|
+
/** @type {string|null} */
|
|
147
|
+
let name = null;
|
|
148
|
+
|
|
149
|
+
if (val.tick !== undefined) {
|
|
150
|
+
action = 'tick';
|
|
151
|
+
tickValue = val.tick;
|
|
152
|
+
} else if (val.latest) {
|
|
153
|
+
action = 'latest';
|
|
154
|
+
} else if (val.save !== undefined) {
|
|
155
|
+
action = 'save';
|
|
156
|
+
name = val.save;
|
|
157
|
+
} else if (val.load !== undefined) {
|
|
158
|
+
action = 'load';
|
|
159
|
+
name = val.load;
|
|
160
|
+
} else if (val.list) {
|
|
161
|
+
action = 'list';
|
|
162
|
+
} else if (val.drop !== undefined) {
|
|
163
|
+
action = 'drop';
|
|
164
|
+
name = val.drop;
|
|
165
|
+
} else if (val['clear-cache']) {
|
|
166
|
+
action = 'clear-cache';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
action,
|
|
171
|
+
tickValue,
|
|
172
|
+
name,
|
|
173
|
+
noPersistentCache: val['no-persistent-cache'],
|
|
174
|
+
diff: val.diff,
|
|
175
|
+
diffLimit: val['diff-limit'],
|
|
176
|
+
};
|
|
177
|
+
});
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import readline from 'node:readline';
|
|
5
|
+
import { execFileSync } from 'node:child_process';
|
|
6
|
+
// @ts-expect-error — no type declarations for @git-stunts/plumbing
|
|
7
|
+
import GitPlumbing, { ShellRunnerFactory } from '@git-stunts/plumbing';
|
|
8
|
+
import WarpGraph from '../../src/domain/WarpGraph.js';
|
|
9
|
+
import GitGraphAdapter from '../../src/infrastructure/adapters/GitGraphAdapter.js';
|
|
10
|
+
import WebCryptoAdapter from '../../src/infrastructure/adapters/WebCryptoAdapter.js';
|
|
11
|
+
import {
|
|
12
|
+
REF_PREFIX,
|
|
13
|
+
buildCursorActiveRef,
|
|
14
|
+
} from '../../src/domain/utils/RefLayout.js';
|
|
15
|
+
import CasSeekCacheAdapter from '../../src/infrastructure/adapters/CasSeekCacheAdapter.js';
|
|
16
|
+
import { HookInstaller } from '../../src/domain/services/HookInstaller.js';
|
|
17
|
+
import { parseCursorBlob } from '../../src/domain/utils/parseCursorBlob.js';
|
|
18
|
+
import { usageError, notFoundError } from './infrastructure.js';
|
|
19
|
+
|
|
20
|
+
/** @typedef {import('./types.js').Persistence} Persistence */
|
|
21
|
+
/** @typedef {import('./types.js').WarpGraphInstance} WarpGraphInstance */
|
|
22
|
+
/** @typedef {import('./types.js').CursorBlob} CursorBlob */
|
|
23
|
+
/** @typedef {import('./types.js').CliOptions} CliOptions */
|
|
24
|
+
/** @typedef {import('./types.js').SeekSpec} SeekSpec */
|
|
25
|
+
|
|
26
|
+
/** @param {string} repoPath @returns {Promise<{persistence: Persistence}>} */
|
|
27
|
+
export async function createPersistence(repoPath) {
|
|
28
|
+
const runner = ShellRunnerFactory.create();
|
|
29
|
+
const plumbing = new GitPlumbing({ cwd: repoPath, runner });
|
|
30
|
+
const persistence = new GitGraphAdapter({ plumbing });
|
|
31
|
+
const ping = await persistence.ping();
|
|
32
|
+
if (!ping.ok) {
|
|
33
|
+
throw usageError(`Repository not accessible: ${repoPath}`);
|
|
34
|
+
}
|
|
35
|
+
return { persistence };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** @param {Persistence} persistence @returns {Promise<string[]>} */
|
|
39
|
+
export async function listGraphNames(persistence) {
|
|
40
|
+
if (typeof persistence.listRefs !== 'function') {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
const refs = await persistence.listRefs(REF_PREFIX);
|
|
44
|
+
const prefix = `${REF_PREFIX}/`;
|
|
45
|
+
const names = new Set();
|
|
46
|
+
|
|
47
|
+
for (const ref of refs) {
|
|
48
|
+
if (!ref.startsWith(prefix)) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const rest = ref.slice(prefix.length);
|
|
52
|
+
const [graphName] = rest.split('/');
|
|
53
|
+
if (graphName) {
|
|
54
|
+
names.add(graphName);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return [...names].sort();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {Persistence} persistence
|
|
63
|
+
* @param {string|null} explicitGraph
|
|
64
|
+
* @returns {Promise<string>}
|
|
65
|
+
*/
|
|
66
|
+
export async function resolveGraphName(persistence, explicitGraph) {
|
|
67
|
+
if (explicitGraph) {
|
|
68
|
+
return explicitGraph;
|
|
69
|
+
}
|
|
70
|
+
const graphNames = await listGraphNames(persistence);
|
|
71
|
+
if (graphNames.length === 1) {
|
|
72
|
+
return graphNames[0];
|
|
73
|
+
}
|
|
74
|
+
if (graphNames.length === 0) {
|
|
75
|
+
throw notFoundError('No graphs found in repo; specify --graph');
|
|
76
|
+
}
|
|
77
|
+
throw usageError('Multiple graphs found; specify --graph');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Opens a WarpGraph for the given CLI options.
|
|
82
|
+
* @param {CliOptions} options - Parsed CLI options
|
|
83
|
+
* @returns {Promise<{graph: WarpGraphInstance, graphName: string, persistence: Persistence}>}
|
|
84
|
+
* @throws {import('./infrastructure.js').CliError} If the specified graph is not found
|
|
85
|
+
*/
|
|
86
|
+
export async function openGraph(options) {
|
|
87
|
+
const { persistence } = await createPersistence(options.repo);
|
|
88
|
+
const graphName = await resolveGraphName(persistence, options.graph);
|
|
89
|
+
if (options.graph) {
|
|
90
|
+
const graphNames = await listGraphNames(persistence);
|
|
91
|
+
if (!graphNames.includes(options.graph)) {
|
|
92
|
+
throw notFoundError(`Graph not found: ${options.graph}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const graph = /** @type {WarpGraphInstance} */ (/** @type {*} */ (await WarpGraph.open({ // TODO(ts-cleanup): narrow port type
|
|
96
|
+
persistence,
|
|
97
|
+
graphName,
|
|
98
|
+
writerId: options.writer,
|
|
99
|
+
crypto: new WebCryptoAdapter(),
|
|
100
|
+
})));
|
|
101
|
+
return { graph, graphName, persistence };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Reads the active cursor and sets `_seekCeiling` on the graph instance
|
|
106
|
+
* so that subsequent materialize calls respect the time-travel boundary.
|
|
107
|
+
*
|
|
108
|
+
* @param {WarpGraphInstance} graph - WarpGraph instance
|
|
109
|
+
* @param {Persistence} persistence - GraphPersistencePort adapter
|
|
110
|
+
* @param {string} graphName - Name of the WARP graph
|
|
111
|
+
* @returns {Promise<{active: boolean, tick: number|null, maxTick: number|null}>}
|
|
112
|
+
*/
|
|
113
|
+
export async function applyCursorCeiling(graph, persistence, graphName) {
|
|
114
|
+
const cursor = await readActiveCursor(persistence, graphName);
|
|
115
|
+
if (cursor) {
|
|
116
|
+
graph._seekCeiling = cursor.tick;
|
|
117
|
+
return { active: true, tick: cursor.tick, maxTick: null };
|
|
118
|
+
}
|
|
119
|
+
return { active: false, tick: null, maxTick: null };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Prints a seek cursor warning banner to stderr when a cursor is active.
|
|
124
|
+
*
|
|
125
|
+
* @param {{active: boolean, tick: number|null, maxTick: number|null}} cursorInfo
|
|
126
|
+
* @param {number|null} maxTick
|
|
127
|
+
* @returns {void}
|
|
128
|
+
*/
|
|
129
|
+
export function emitCursorWarning(cursorInfo, maxTick) {
|
|
130
|
+
if (cursorInfo.active) {
|
|
131
|
+
const maxLabel = maxTick !== null && maxTick !== undefined ? ` of ${maxTick}` : '';
|
|
132
|
+
process.stderr.write(`\u26A0 seek active (tick ${cursorInfo.tick}${maxLabel}) \u2014 run "git warp seek --latest" to return to present\n`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Reads the active seek cursor for a graph from Git ref storage.
|
|
138
|
+
*
|
|
139
|
+
* @param {Persistence} persistence - GraphPersistencePort adapter
|
|
140
|
+
* @param {string} graphName - Name of the WARP graph
|
|
141
|
+
* @returns {Promise<CursorBlob|null>}
|
|
142
|
+
*/
|
|
143
|
+
export async function readActiveCursor(persistence, graphName) {
|
|
144
|
+
const ref = buildCursorActiveRef(graphName);
|
|
145
|
+
const oid = await persistence.readRef(ref);
|
|
146
|
+
if (!oid) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
const buf = await persistence.readBlob(oid);
|
|
150
|
+
return parseCursorBlob(buf, 'active cursor');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Writes (creates or overwrites) the active seek cursor for a graph.
|
|
155
|
+
*
|
|
156
|
+
* @param {Persistence} persistence - GraphPersistencePort adapter
|
|
157
|
+
* @param {string} graphName - Name of the WARP graph
|
|
158
|
+
* @param {CursorBlob} cursor - Cursor state to persist
|
|
159
|
+
* @returns {Promise<void>}
|
|
160
|
+
*/
|
|
161
|
+
export async function writeActiveCursor(persistence, graphName, cursor) {
|
|
162
|
+
const ref = buildCursorActiveRef(graphName);
|
|
163
|
+
const json = JSON.stringify(cursor);
|
|
164
|
+
const oid = await persistence.writeBlob(Buffer.from(json, 'utf8'));
|
|
165
|
+
await persistence.updateRef(ref, oid);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @param {Persistence} persistence
|
|
170
|
+
* @param {string|null} checkpointSha
|
|
171
|
+
*/
|
|
172
|
+
export async function readCheckpointDate(persistence, checkpointSha) {
|
|
173
|
+
if (!checkpointSha) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
const info = await persistence.getNodeInfo(checkpointSha);
|
|
177
|
+
return info.date || null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function createHookInstaller() {
|
|
181
|
+
const __filename = new URL(import.meta.url).pathname;
|
|
182
|
+
const __dirname = path.dirname(__filename);
|
|
183
|
+
const templateDir = path.resolve(__dirname, '..', '..', 'scripts', 'hooks');
|
|
184
|
+
const { version } = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', '..', 'package.json'), 'utf8'));
|
|
185
|
+
return new HookInstaller({
|
|
186
|
+
fs: /** @type {*} */ (fs), // TODO(ts-cleanup): narrow port type
|
|
187
|
+
execGitConfig: execGitConfigValue,
|
|
188
|
+
version,
|
|
189
|
+
templateDir,
|
|
190
|
+
path,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* @param {string} repoPath
|
|
196
|
+
* @param {string} key
|
|
197
|
+
* @returns {string|null}
|
|
198
|
+
*/
|
|
199
|
+
export function execGitConfigValue(repoPath, key) {
|
|
200
|
+
try {
|
|
201
|
+
if (key === '--git-dir') {
|
|
202
|
+
return execFileSync('git', ['-C', repoPath, 'rev-parse', '--git-dir'], {
|
|
203
|
+
encoding: 'utf8',
|
|
204
|
+
}).trim();
|
|
205
|
+
}
|
|
206
|
+
return execFileSync('git', ['-C', repoPath, 'config', key], {
|
|
207
|
+
encoding: 'utf8',
|
|
208
|
+
}).trim();
|
|
209
|
+
} catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function isInteractive() {
|
|
215
|
+
return Boolean(process.stderr.isTTY);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** @param {string} question @returns {Promise<string>} */
|
|
219
|
+
export function promptUser(question) {
|
|
220
|
+
const rl = readline.createInterface({
|
|
221
|
+
input: process.stdin,
|
|
222
|
+
output: process.stderr,
|
|
223
|
+
});
|
|
224
|
+
return new Promise((resolve) => {
|
|
225
|
+
rl.question(question, (answer) => {
|
|
226
|
+
rl.close();
|
|
227
|
+
resolve(answer.trim());
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* @param {{graph: WarpGraphInstance, persistence: Persistence, graphName: string, seekSpec: SeekSpec}} params
|
|
234
|
+
*/
|
|
235
|
+
export function wireSeekCache({ graph, persistence, graphName, seekSpec }) {
|
|
236
|
+
if (seekSpec.noPersistentCache) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
graph.setSeekCache(new CasSeekCacheAdapter({
|
|
240
|
+
persistence,
|
|
241
|
+
plumbing: persistence.plumbing,
|
|
242
|
+
graphName,
|
|
243
|
+
}));
|
|
244
|
+
}
|
package/bin/cli/types.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} Persistence
|
|
3
|
+
* @property {(prefix: string) => Promise<string[]>} listRefs
|
|
4
|
+
* @property {(ref: string) => Promise<string|null>} readRef
|
|
5
|
+
* @property {(ref: string, oid: string) => Promise<void>} updateRef
|
|
6
|
+
* @property {(ref: string) => Promise<void>} deleteRef
|
|
7
|
+
* @property {(oid: string) => Promise<Buffer>} readBlob
|
|
8
|
+
* @property {(buf: Buffer) => Promise<string>} writeBlob
|
|
9
|
+
* @property {(sha: string) => Promise<{date?: string|null}>} getNodeInfo
|
|
10
|
+
* @property {(sha: string) => Promise<boolean>} nodeExists
|
|
11
|
+
* @property {(sha: string, coverageSha: string) => Promise<boolean>} isAncestor
|
|
12
|
+
* @property {() => Promise<{ok: boolean}>} ping
|
|
13
|
+
* @property {*} plumbing
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {Object} WarpGraphInstance
|
|
18
|
+
* @property {(opts?: {ceiling?: number}) => Promise<void>} materialize
|
|
19
|
+
* @property {() => Promise<Array<{id: string}>>} getNodes
|
|
20
|
+
* @property {() => Promise<Array<{from: string, to: string, label?: string}>>} getEdges
|
|
21
|
+
* @property {() => Promise<string|null>} createCheckpoint
|
|
22
|
+
* @property {() => *} query
|
|
23
|
+
* @property {{ shortestPath: Function }} traverse
|
|
24
|
+
* @property {(writerId: string) => Promise<Array<{patch: any, sha: string}>>} getWriterPatches
|
|
25
|
+
* @property {() => Promise<{frontier: Record<string, any>}>} status
|
|
26
|
+
* @property {() => Promise<Map<string, any>>} getFrontier
|
|
27
|
+
* @property {() => {totalTombstones: number, tombstoneRatio: number}} getGCMetrics
|
|
28
|
+
* @property {() => Promise<number>} getPropertyCount
|
|
29
|
+
* @property {() => Promise<import('../../src/domain/services/JoinReducer.js').WarpStateV5 | null>} getStateSnapshot
|
|
30
|
+
* @property {() => Promise<{ticks: number[], maxTick: number, perWriter: Map<string, WriterTickInfo>}>} discoverTicks
|
|
31
|
+
* @property {(sha: string) => Promise<{ops?: any[]}>} loadPatchBySha
|
|
32
|
+
* @property {(cache: any) => void} setSeekCache
|
|
33
|
+
* @property {*} seekCache
|
|
34
|
+
* @property {number} [_seekCeiling]
|
|
35
|
+
* @property {boolean} [_provenanceDegraded]
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @typedef {Object} WriterTickInfo
|
|
40
|
+
* @property {number[]} ticks
|
|
41
|
+
* @property {string|null} tipSha
|
|
42
|
+
* @property {Record<number, string>} [tickShas]
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @typedef {Object} CursorBlob
|
|
47
|
+
* @property {number} tick
|
|
48
|
+
* @property {string} [mode]
|
|
49
|
+
* @property {number} [nodes]
|
|
50
|
+
* @property {number} [edges]
|
|
51
|
+
* @property {string} [frontierHash]
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @typedef {Object} CliOptions
|
|
56
|
+
* @property {string} repo
|
|
57
|
+
* @property {boolean} json
|
|
58
|
+
* @property {boolean} ndjson
|
|
59
|
+
* @property {string|null} view
|
|
60
|
+
* @property {string|null} graph
|
|
61
|
+
* @property {string} writer
|
|
62
|
+
* @property {boolean} help
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @typedef {Object} GraphInfoResult
|
|
67
|
+
* @property {string} name
|
|
68
|
+
* @property {{count: number, ids?: string[]}} writers
|
|
69
|
+
* @property {{ref: string, sha: string|null, date?: string|null}} [checkpoint]
|
|
70
|
+
* @property {{ref: string, sha: string|null}} [coverage]
|
|
71
|
+
* @property {Record<string, number>} [writerPatches]
|
|
72
|
+
* @property {{active: boolean, tick?: number, mode?: string}} [cursor]
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @typedef {Object} SeekSpec
|
|
77
|
+
* @property {string} action
|
|
78
|
+
* @property {string|null} tickValue
|
|
79
|
+
* @property {string|null} name
|
|
80
|
+
* @property {boolean} noPersistentCache
|
|
81
|
+
* @property {boolean} diff
|
|
82
|
+
* @property {number} diffLimit
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
export {};
|