@dinoxx/dinox-cli 1.0.22 → 1.1.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 +29 -9
- package/dist/auth/userInfo.d.ts +2 -0
- package/dist/auth/userInfo.js +55 -14
- package/dist/cli.js +36 -2
- package/dist/commands/auth/index.d.ts +35 -0
- package/dist/commands/auth/index.js +382 -83
- package/dist/commands/boxes/index.js +2 -6
- package/dist/commands/boxes/model.d.ts +3 -0
- package/dist/commands/boxes/model.js +16 -1
- package/dist/commands/boxes/operations.js +18 -8
- package/dist/commands/boxes/repo.js +13 -11
- package/dist/commands/boxes/store.js +1 -1
- package/dist/commands/config/index.js +7 -5
- package/dist/commands/descriptor/register.js +9 -0
- package/dist/commands/doctor.d.ts +123 -0
- package/dist/commands/doctor.js +539 -0
- package/dist/commands/graph/index.d.ts +1 -1
- package/dist/commands/graph/index.js +35 -21
- package/dist/commands/graph/repo.js +92 -17
- package/dist/commands/meta/index.js +4 -4
- package/dist/commands/meta/repo.js +4 -3
- package/dist/commands/notes/actions.d.ts +1 -1
- package/dist/commands/notes/actions.js +121 -78
- package/dist/commands/notes/bulk.d.ts +1 -0
- package/dist/commands/notes/bulk.js +23 -17
- package/dist/commands/notes/contentEngine.js +69 -11
- package/dist/commands/notes/contentPatch.d.ts +4 -1
- package/dist/commands/notes/contentPatch.js +105 -41
- package/dist/commands/notes/contentPatchCommand.js +28 -11
- package/dist/commands/notes/descriptors.js +50 -20
- package/dist/commands/notes/dryRun.d.ts +1 -0
- package/dist/commands/notes/dryRun.js +4 -7
- package/dist/commands/notes/export.d.ts +6 -0
- package/dist/commands/notes/export.js +26 -6
- package/dist/commands/notes/metadataMutation.d.ts +1 -0
- package/dist/commands/notes/metadataMutation.js +14 -3
- package/dist/commands/notes/repo.d.ts +46 -4
- package/dist/commands/notes/repo.js +113 -28
- package/dist/commands/notes/runtime.d.ts +2 -1
- package/dist/commands/notes/runtime.js +17 -1
- package/dist/commands/notes/searchSql.js +13 -6
- package/dist/commands/notes/writeReceipt.d.ts +20 -0
- package/dist/commands/notes/writeReceipt.js +82 -0
- package/dist/commands/prompt/index.js +3 -3
- package/dist/commands/prompt/repo.d.ts +4 -1
- package/dist/commands/prompt/repo.js +17 -7
- package/dist/commands/runtime.d.ts +14 -2
- package/dist/commands/runtime.js +74 -15
- package/dist/commands/schema/renderSkills.js +5 -2
- package/dist/commands/schema/spec.js +4 -0
- package/dist/commands/storage/index.js +8 -2
- package/dist/commands/storage/repo.d.ts +4 -0
- package/dist/commands/storage/repo.js +356 -92
- package/dist/commands/sync.js +16 -12
- package/dist/commands/tags/content.js +9 -0
- package/dist/commands/tags/index.js +2 -6
- package/dist/commands/tags/operations.js +15 -10
- package/dist/commands/tags/repo.d.ts +2 -0
- package/dist/commands/tags/repo.js +18 -5
- package/dist/commands/tags/store.js +1 -1
- package/dist/commands/todo/contentModel.d.ts +4 -3
- package/dist/commands/todo/contentModel.js +78 -28
- package/dist/commands/todo/dryRun.d.ts +1 -0
- package/dist/commands/todo/dryRun.js +17 -13
- package/dist/commands/todo/index.d.ts +6 -0
- package/dist/commands/todo/index.js +89 -123
- package/dist/commands/todo/repo.d.ts +36 -1
- package/dist/commands/todo/repo.js +146 -37
- package/dist/commands/todo/taskIdentity.d.ts +2 -0
- package/dist/commands/todo/taskIdentity.js +64 -0
- package/dist/commands/todo/taskModel.js +27 -17
- package/dist/commands/update.d.ts +13 -1
- package/dist/commands/update.js +135 -23
- package/dist/commands/writeDryRun.d.ts +3 -2
- package/dist/commands/writeDryRun.js +4 -3
- package/dist/commands/writeDurability.d.ts +5 -0
- package/dist/commands/writeDurability.js +22 -0
- package/dist/config/paths.js +6 -1
- package/dist/config/resolve.d.ts +2 -0
- package/dist/config/resolve.js +23 -9
- package/dist/config/store.d.ts +3 -0
- package/dist/config/store.js +61 -14
- package/dist/config/types.d.ts +4 -0
- package/dist/daemon/client.d.ts +26 -1
- package/dist/daemon/client.js +236 -12
- package/dist/daemon/ensure.js +74 -12
- package/dist/daemon/index.d.ts +42 -0
- package/dist/daemon/index.js +202 -52
- package/dist/daemon/lifecycle.d.ts +42 -0
- package/dist/daemon/lifecycle.js +115 -0
- package/dist/daemon/pid.d.ts +19 -6
- package/dist/daemon/pid.js +147 -13
- package/dist/daemon/route.d.ts +14 -0
- package/dist/daemon/route.js +40 -0
- package/dist/daemon/server.d.ts +11 -0
- package/dist/daemon/server.js +201 -50
- package/dist/daemon/types.d.ts +29 -1
- package/dist/daemon/types.js +1 -0
- package/dist/dinox.js +25 -1
- package/dist/powersync/connector.js +31 -16
- package/dist/powersync/exclusiveRuntime.d.ts +19 -0
- package/dist/powersync/exclusiveRuntime.js +42 -0
- package/dist/powersync/runtime.d.ts +24 -4
- package/dist/powersync/runtime.js +234 -36
- package/dist/powersync/syncGate.d.ts +3 -1
- package/dist/powersync/syncGate.js +7 -1
- package/dist/powersync/tokenIndex.d.ts +11 -0
- package/dist/powersync/tokenIndex.js +159 -22
- package/dist/powersync/uploader.js +107 -52
- package/dist/utils/abort.js +0 -4
- package/dist/utils/asyncRelease.d.ts +2 -0
- package/dist/utils/asyncRelease.js +25 -0
- package/dist/utils/bidlinkCanonicalization.js +3 -0
- package/dist/utils/dryRun.d.ts +1 -1
- package/dist/utils/errors.d.ts +15 -1
- package/dist/utils/errors.js +223 -5
- package/dist/utils/output.d.ts +1 -0
- package/dist/utils/output.js +51 -15
- package/dist/utils/redact.js +1 -1
- package/dist/utils/sqliteLease.d.ts +8 -0
- package/dist/utils/sqliteLease.js +59 -0
- package/dist/utils/syncTimeout.d.ts +7 -1
- package/dist/utils/syncTimeout.js +8 -7
- package/dist/utils/terminalSanitization.js +6 -2
- package/dist/utils/tiptapMarkdown.js +49 -3
- package/dist/utils/tiptapMarkdownSyntax.d.ts +9 -0
- package/dist/utils/tiptapMarkdownSyntax.js +249 -0
- package/dist/utils/tokenize.js +24 -2
- package/dist/utils/updateNotice.js +60 -12
- package/package.json +12 -10
- package/dist/commands/mcp/auth.d.ts +0 -4
- package/dist/commands/mcp/auth.js +0 -75
- package/dist/commands/mcp/index.d.ts +0 -2
- package/dist/commands/mcp/index.js +0 -86
- package/dist/commands/mcp/noteContentTools.d.ts +0 -222
- package/dist/commands/mcp/noteContentTools.js +0 -390
- package/dist/commands/mcp/server.d.ts +0 -34
- package/dist/commands/mcp/server.js +0 -424
- package/dist/commands/mcp/tools.d.ts +0 -2
- package/dist/commands/mcp/tools.js +0 -32
- package/dist/commands/mcp/types.d.ts +0 -39
- package/dist/commands/mcp/types.js +0 -1
- package/dist/utils/noteContentEngine.d.ts +0 -333
- package/dist/utils/noteContentEngine.js +0 -740
package/dist/utils/errors.js
CHANGED
|
@@ -3,14 +3,20 @@ export class DinoxError extends Error {
|
|
|
3
3
|
details;
|
|
4
4
|
code;
|
|
5
5
|
suggestion;
|
|
6
|
+
suggestedAction;
|
|
6
7
|
recoverable;
|
|
7
8
|
constructor(message, options) {
|
|
8
9
|
super(message);
|
|
10
|
+
const code = normalizeErrorCode(options?.code ?? inferDinoxErrorCode(message));
|
|
11
|
+
const suggestion = options?.suggestion ?? defaultSuggestionForCode(code);
|
|
9
12
|
this.name = 'DinoxError';
|
|
10
|
-
this.exitCode = options?.exitCode ??
|
|
13
|
+
this.exitCode = options?.exitCode ?? exitCodeForErrorCode(code);
|
|
11
14
|
this.details = options?.details;
|
|
12
|
-
this.code =
|
|
13
|
-
this.suggestion =
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.suggestion = suggestion;
|
|
17
|
+
this.suggestedAction = options?.suggestedAction
|
|
18
|
+
?? suggestedActionFromSuggestion(suggestion)
|
|
19
|
+
?? defaultSuggestedActionForCode(code, message);
|
|
14
20
|
this.recoverable = options?.recoverable ?? true;
|
|
15
21
|
if (options?.cause !== undefined) {
|
|
16
22
|
// Node 16+ supports Error.cause.
|
|
@@ -19,20 +25,47 @@ export class DinoxError extends Error {
|
|
|
19
25
|
}
|
|
20
26
|
}
|
|
21
27
|
export function isDinoxError(error) {
|
|
22
|
-
return Boolean(error)
|
|
28
|
+
return Boolean(error)
|
|
29
|
+
&& typeof error === 'object'
|
|
30
|
+
&& error.name === 'DinoxError'
|
|
31
|
+
&& typeof error.exitCode === 'number';
|
|
32
|
+
}
|
|
33
|
+
function serializeCause(error) {
|
|
34
|
+
if (!error || typeof error !== 'object' || !('cause' in error)) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
const cause = error.cause;
|
|
38
|
+
if (cause === undefined) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
if (cause instanceof Error) {
|
|
42
|
+
return {
|
|
43
|
+
name: cause.name,
|
|
44
|
+
message: cause.message,
|
|
45
|
+
...(cause.stack ? { stack: cause.stack } : {}),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
name: 'Error',
|
|
50
|
+
message: String(cause),
|
|
51
|
+
};
|
|
23
52
|
}
|
|
24
53
|
export function buildStructuredErrorPayload(error, verbose) {
|
|
25
54
|
const message = error instanceof Error ? error.message : String(error);
|
|
26
55
|
const name = error instanceof Error ? error.name : 'Error';
|
|
27
56
|
const stack = error instanceof Error ? error.stack : undefined;
|
|
28
57
|
if (isDinoxError(error)) {
|
|
58
|
+
const cause = verbose ? serializeCause(error) : undefined;
|
|
29
59
|
return {
|
|
30
60
|
name,
|
|
31
|
-
code: error.code
|
|
61
|
+
code: error.code,
|
|
32
62
|
message,
|
|
33
63
|
recoverable: error.recoverable,
|
|
64
|
+
exit_code: error.exitCode,
|
|
34
65
|
...(error.suggestion ? { suggestion: error.suggestion } : {}),
|
|
66
|
+
...(error.suggestedAction ? { suggested_action: error.suggestedAction } : {}),
|
|
35
67
|
...(error.details !== undefined ? { details: error.details } : {}),
|
|
68
|
+
...(cause ? { cause } : {}),
|
|
36
69
|
...(verbose && stack ? { stack } : {}),
|
|
37
70
|
};
|
|
38
71
|
}
|
|
@@ -41,6 +74,191 @@ export function buildStructuredErrorPayload(error, verbose) {
|
|
|
41
74
|
code: 'INTERNAL_ERROR',
|
|
42
75
|
message,
|
|
43
76
|
recoverable: false,
|
|
77
|
+
exit_code: 1,
|
|
78
|
+
suggested_action: {
|
|
79
|
+
command: 'dino doctor --format json',
|
|
80
|
+
reason: 'Inspect local auth, sync, index, daemon, and database health before retrying.',
|
|
81
|
+
},
|
|
44
82
|
...(verbose && stack ? { stack } : {}),
|
|
45
83
|
};
|
|
46
84
|
}
|
|
85
|
+
function normalizeErrorCode(value) {
|
|
86
|
+
const normalized = value.trim().toUpperCase().replace(/[^A-Z0-9_]+/g, '_').replace(/^_+|_+$/g, '');
|
|
87
|
+
return normalized || 'COMMAND_FAILED';
|
|
88
|
+
}
|
|
89
|
+
function inferDinoxErrorCode(message) {
|
|
90
|
+
const text = message.trim();
|
|
91
|
+
const lower = text.toLowerCase();
|
|
92
|
+
if (/cache is not fully synchronized|stale|\bsync required\b|\bsync timed out\b|\bsync could not\b/.test(lower)) {
|
|
93
|
+
return 'SYNC_STALE';
|
|
94
|
+
}
|
|
95
|
+
if (lower.includes('not logged in')
|
|
96
|
+
|| lower.includes('auth login')
|
|
97
|
+
|| lower.includes('missing resolved userid')
|
|
98
|
+
|| lower.includes('requires a logged-in user')
|
|
99
|
+
|| lower.includes('missing backend base url')) {
|
|
100
|
+
return 'AUTH_REQUIRED';
|
|
101
|
+
}
|
|
102
|
+
if (text.startsWith('--')
|
|
103
|
+
|| /^(invalid|provide|at least one|conflicting|missing|token is required|file path is required|note id is required|taskid is required|prompt name is required|prompt text is required|tag name is required|box path is required)/i.test(text)
|
|
104
|
+
|| /\b(must be|requires|cannot be used together|expected|one of|required)\b/i.test(text)) {
|
|
105
|
+
return 'USAGE_ERROR';
|
|
106
|
+
}
|
|
107
|
+
if (/\b(not found|unknown|does not exist|no active|no eligible|no custom|not available)\b/i.test(text)) {
|
|
108
|
+
return 'NOT_FOUND';
|
|
109
|
+
}
|
|
110
|
+
if (/\b(already exists|duplicate|refusing|ambiguous|not unique|deleted|expired|changed after|does not match|belongs to a different user|cannot move|cannot merge|protected)\b/i.test(text)) {
|
|
111
|
+
return 'PRECONDITION_FAILED';
|
|
112
|
+
}
|
|
113
|
+
if (lower.includes('daemon')) {
|
|
114
|
+
return 'DAEMON_ERROR';
|
|
115
|
+
}
|
|
116
|
+
return 'COMMAND_FAILED';
|
|
117
|
+
}
|
|
118
|
+
export function exitCodeForErrorCode(code) {
|
|
119
|
+
const normalized = normalizeErrorCode(code);
|
|
120
|
+
if (normalized === 'USAGE_ERROR' || normalized === 'INVALID_ARGUMENT' || normalized.startsWith('INVALID_')) {
|
|
121
|
+
return 2;
|
|
122
|
+
}
|
|
123
|
+
if (normalized === 'AUTH_REQUIRED' || normalized === 'AUTH_INVALID' || normalized === 'UNAUTHENTICATED' || normalized.startsWith('AUTH_')) {
|
|
124
|
+
return 3;
|
|
125
|
+
}
|
|
126
|
+
if (normalized === 'SYNC_REQUIRED' || normalized === 'SYNC_STALE' || normalized === 'UPLOAD_PENDING' || normalized.startsWith('SYNC_')) {
|
|
127
|
+
return 4;
|
|
128
|
+
}
|
|
129
|
+
if (normalized === 'PRECONDITION_FAILED'
|
|
130
|
+
|| normalized === 'NOT_FOUND'
|
|
131
|
+
|| normalized === 'ALREADY_EXISTS'
|
|
132
|
+
|| normalized === 'DAEMON_STALE'
|
|
133
|
+
|| normalized === 'DAEMON_ERROR'
|
|
134
|
+
|| normalized === 'DAEMON_START_FAILED'
|
|
135
|
+
|| normalized.endsWith('_NOT_FOUND')
|
|
136
|
+
|| normalized.endsWith('_ALREADY_EXISTS')
|
|
137
|
+
|| normalized.endsWith('_REQUIRED')) {
|
|
138
|
+
return 5;
|
|
139
|
+
}
|
|
140
|
+
return 1;
|
|
141
|
+
}
|
|
142
|
+
function defaultSuggestionForCode(code) {
|
|
143
|
+
switch (normalizeErrorCode(code)) {
|
|
144
|
+
case 'AUTH_REQUIRED':
|
|
145
|
+
case 'AUTH_INVALID':
|
|
146
|
+
case 'UNAUTHENTICATED':
|
|
147
|
+
return 'Run `dino auth status --format json`; if auth is missing, set DINOX_TOKEN or pipe a token to `dino auth login --token-stdin` in your own terminal.';
|
|
148
|
+
case 'SYNC_REQUIRED':
|
|
149
|
+
case 'SYNC_STALE':
|
|
150
|
+
return 'Run `dino sync --strict --sync-timeout 600000 --format json`, then retry the command.';
|
|
151
|
+
case 'UPLOAD_PENDING':
|
|
152
|
+
return 'Run `dino doctor --fix --format json` to wait for pending uploads and inspect sync health.';
|
|
153
|
+
case 'DAEMON_STALE':
|
|
154
|
+
case 'DAEMON_ERROR':
|
|
155
|
+
case 'DAEMON_START_FAILED':
|
|
156
|
+
return 'Run `dino doctor --fix --format json` to restart a stale daemon and inspect local health.';
|
|
157
|
+
case 'USAGE_ERROR':
|
|
158
|
+
case 'INVALID_ARGUMENT':
|
|
159
|
+
return 'Run `dino schema --format json` to inspect valid commands, arguments, and flags.';
|
|
160
|
+
default: {
|
|
161
|
+
const normalized = normalizeErrorCode(code);
|
|
162
|
+
if (normalized.startsWith('AUTH_')) {
|
|
163
|
+
return 'Run `dino auth status --format json`; if auth is missing, set DINOX_TOKEN or pipe a token to `dino auth login --token-stdin` in your own terminal.';
|
|
164
|
+
}
|
|
165
|
+
if (normalized.startsWith('SYNC_')) {
|
|
166
|
+
return 'Run `dino sync --strict --sync-timeout 600000 --format json`, then retry the command.';
|
|
167
|
+
}
|
|
168
|
+
if (normalized.startsWith('DAEMON_')) {
|
|
169
|
+
return 'Run `dino doctor --fix --format json` to restart a stale daemon and inspect local health.';
|
|
170
|
+
}
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function suggestedActionFromSuggestion(suggestion) {
|
|
176
|
+
if (!suggestion) {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
const match = suggestion.match(/`(dino\s+[^`]+)`/) ?? suggestion.match(/['"](dino\s+[^'"]+)['"]/);
|
|
180
|
+
const command = match?.[1]?.trim();
|
|
181
|
+
if (!command) {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
command,
|
|
186
|
+
reason: suggestion,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function defaultSuggestedActionForCode(code, message) {
|
|
190
|
+
const normalized = normalizeErrorCode(code);
|
|
191
|
+
if (normalized === 'AUTH_REQUIRED' || normalized === 'AUTH_INVALID' || normalized === 'UNAUTHENTICATED' || normalized.startsWith('AUTH_')) {
|
|
192
|
+
return {
|
|
193
|
+
command: 'dino auth status --format json',
|
|
194
|
+
reason: 'Confirm whether CLI authentication is configured before retrying.',
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (normalized === 'SYNC_REQUIRED' || normalized === 'SYNC_STALE' || normalized.startsWith('SYNC_')) {
|
|
198
|
+
return {
|
|
199
|
+
command: 'dino sync --strict --sync-timeout 600000 --format json',
|
|
200
|
+
reason: 'Refresh the local PowerSync cache and prove freshness before retrying the read.',
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (normalized === 'UPLOAD_PENDING') {
|
|
204
|
+
return {
|
|
205
|
+
command: 'dino doctor --fix --format json',
|
|
206
|
+
reason: 'Drain pending uploads without discarding queued mutations, then recheck sync health.',
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (normalized.startsWith('DAEMON_') || normalized === 'DAEMON_ERROR') {
|
|
210
|
+
return {
|
|
211
|
+
command: 'dino doctor --fix --format json',
|
|
212
|
+
reason: 'Repair local daemon state and verify version/account freshness.',
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
if (normalized === 'USAGE_ERROR' || normalized === 'INVALID_ARGUMENT') {
|
|
216
|
+
return {
|
|
217
|
+
command: 'dino schema --format json',
|
|
218
|
+
reason: 'Inspect valid command arguments and flags before retrying.',
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
if (normalized === 'NOT_FOUND' || normalized.endsWith('_NOT_FOUND')) {
|
|
222
|
+
return defaultNotFoundAction(message);
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
command: 'dino doctor --format json',
|
|
226
|
+
reason: 'Inspect auth, sync, index, daemon, and database health before retrying.',
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function defaultNotFoundAction(message) {
|
|
230
|
+
if (/\bnote\b/i.test(message)) {
|
|
231
|
+
return {
|
|
232
|
+
command: 'dino note search --format json',
|
|
233
|
+
reason: 'Search notes to find a valid note id before retrying.',
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
if (/\btag\b/i.test(message)) {
|
|
237
|
+
return {
|
|
238
|
+
command: 'dino tag list --format json',
|
|
239
|
+
reason: 'List existing tags before retrying.',
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
if (/\b(box|zettel)\b/i.test(message)) {
|
|
243
|
+
return {
|
|
244
|
+
command: 'dino box list --format json',
|
|
245
|
+
reason: 'List existing zettel boxes before retrying.',
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
if (/\bstorage\b/i.test(message)) {
|
|
249
|
+
return {
|
|
250
|
+
command: 'dino storage list --format json',
|
|
251
|
+
reason: 'List available storage configs before retrying.',
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
if (/\bprompt\b/i.test(message)) {
|
|
255
|
+
return {
|
|
256
|
+
command: 'dino prompt list --format json',
|
|
257
|
+
reason: 'List existing prompts before retrying.',
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
command: 'dino schema --format json',
|
|
262
|
+
reason: 'Inspect available command paths and retry with an existing resource.',
|
|
263
|
+
};
|
|
264
|
+
}
|
package/dist/utils/output.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export declare function toYaml(value: unknown): string;
|
|
|
4
4
|
export declare function toJson(value: unknown): string;
|
|
5
5
|
export declare function printHumanReadableLine(text: string): void;
|
|
6
6
|
export declare function printHumanReadableBlock(text: string): void;
|
|
7
|
+
export declare function printHumanReadableWarning(text: string): void;
|
|
7
8
|
export declare function printHumanReadableError(text: string): void;
|
|
8
9
|
export declare function resolveStructuredOutputFormat(input: {
|
|
9
10
|
format?: unknown;
|
package/dist/utils/output.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { stringify as stringifyYaml } from 'yaml';
|
|
2
|
+
import { DinoxError } from './errors.js';
|
|
2
3
|
import { sanitizeForTerminal } from './terminalSanitization.js';
|
|
3
4
|
let requestedStructuredOutputFormat = null;
|
|
4
5
|
let pendingStructuredNoticeProvider = null;
|
|
@@ -23,49 +24,72 @@ export function printHumanReadableLine(text) {
|
|
|
23
24
|
export function printHumanReadableBlock(text) {
|
|
24
25
|
writeToStdout(sanitizeForTerminal(text));
|
|
25
26
|
}
|
|
27
|
+
export function printHumanReadableWarning(text) {
|
|
28
|
+
writeToStderr(`${sanitizeForTerminal(text)}\n`);
|
|
29
|
+
}
|
|
26
30
|
export function printHumanReadableError(text) {
|
|
27
31
|
writeToStderr(`${sanitizeForTerminal(text)}\n`);
|
|
28
32
|
}
|
|
29
33
|
function normalizeStructuredOutputFormat(value) {
|
|
30
34
|
if (typeof value !== 'string') {
|
|
31
|
-
throw new
|
|
35
|
+
throw new DinoxError('--format must be one of: yaml, json');
|
|
32
36
|
}
|
|
33
37
|
const normalized = value.trim().toLowerCase();
|
|
34
38
|
if (normalized === 'yaml' || normalized === 'json') {
|
|
35
39
|
return normalized;
|
|
36
40
|
}
|
|
37
|
-
throw new
|
|
41
|
+
throw new DinoxError('--format must be one of: yaml, json');
|
|
38
42
|
}
|
|
39
43
|
export function resolveStructuredOutputFormat(input) {
|
|
40
|
-
|
|
44
|
+
const hasFormat = input.format !== undefined && input.format !== null && input.format !== '';
|
|
45
|
+
const hasLegacyJson = Boolean(input.json);
|
|
46
|
+
if (hasFormat && hasLegacyJson) {
|
|
47
|
+
throw new DinoxError('--json cannot be used together with --format');
|
|
48
|
+
}
|
|
49
|
+
if (hasFormat) {
|
|
41
50
|
return normalizeStructuredOutputFormat(input.format);
|
|
42
51
|
}
|
|
43
|
-
if (
|
|
52
|
+
if (hasLegacyJson) {
|
|
44
53
|
return 'yaml';
|
|
45
54
|
}
|
|
46
55
|
return null;
|
|
47
56
|
}
|
|
48
57
|
export function parseStructuredOutputFormatFromArgv(argv) {
|
|
49
|
-
let
|
|
58
|
+
let explicitFormat = null;
|
|
59
|
+
let hasLegacyJson = false;
|
|
50
60
|
for (let index = 0; index < argv.length; index += 1) {
|
|
51
61
|
const arg = argv[index];
|
|
52
62
|
if (!arg) {
|
|
53
63
|
continue;
|
|
54
64
|
}
|
|
65
|
+
if (arg === '--') {
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
55
68
|
if (arg === '--json') {
|
|
56
|
-
|
|
69
|
+
hasLegacyJson = true;
|
|
57
70
|
continue;
|
|
58
71
|
}
|
|
59
72
|
if (arg === '--format') {
|
|
60
|
-
|
|
73
|
+
const nextFormat = normalizeStructuredOutputFormat(argv[index + 1]);
|
|
74
|
+
if (explicitFormat && explicitFormat !== nextFormat) {
|
|
75
|
+
throw new DinoxError('--format cannot be specified with conflicting values');
|
|
76
|
+
}
|
|
77
|
+
explicitFormat = nextFormat;
|
|
61
78
|
index += 1;
|
|
62
79
|
continue;
|
|
63
80
|
}
|
|
64
81
|
if (arg.startsWith('--format=')) {
|
|
65
|
-
|
|
82
|
+
const nextFormat = normalizeStructuredOutputFormat(arg.slice('--format='.length));
|
|
83
|
+
if (explicitFormat && explicitFormat !== nextFormat) {
|
|
84
|
+
throw new DinoxError('--format cannot be specified with conflicting values');
|
|
85
|
+
}
|
|
86
|
+
explicitFormat = nextFormat;
|
|
66
87
|
}
|
|
67
88
|
}
|
|
68
|
-
|
|
89
|
+
if (hasLegacyJson && explicitFormat) {
|
|
90
|
+
throw new DinoxError('--json cannot be used together with --format');
|
|
91
|
+
}
|
|
92
|
+
return explicitFormat ?? (hasLegacyJson ? 'yaml' : null);
|
|
69
93
|
}
|
|
70
94
|
export function setRequestedStructuredOutputFormat(format) {
|
|
71
95
|
requestedStructuredOutputFormat = format;
|
|
@@ -117,11 +141,6 @@ function deriveStructuredNotice(value) {
|
|
|
117
141
|
message: 'Result may be stale because the local cache did not fully synchronize.',
|
|
118
142
|
};
|
|
119
143
|
}
|
|
120
|
-
if (record.uploadEnabled === false) {
|
|
121
|
-
notice.upload_disabled = {
|
|
122
|
-
message: 'Local changes cannot upload because powersync.uploadBaseUrl is unset.',
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
144
|
return Object.keys(notice).length > 0 ? notice : null;
|
|
126
145
|
}
|
|
127
146
|
function mergeStructuredNotices(base, derived) {
|
|
@@ -131,6 +150,19 @@ function mergeStructuredNotices(base, derived) {
|
|
|
131
150
|
};
|
|
132
151
|
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
133
152
|
}
|
|
153
|
+
function summarizeStructuredError(value) {
|
|
154
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
155
|
+
return {};
|
|
156
|
+
}
|
|
157
|
+
const record = value;
|
|
158
|
+
const summary = {};
|
|
159
|
+
for (const key of ['code', 'message', 'recoverable', 'exit_code', 'suggested_action']) {
|
|
160
|
+
if (record[key] !== undefined) {
|
|
161
|
+
summary[key] = record[key];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return summary;
|
|
165
|
+
}
|
|
134
166
|
export function printSuccess(value, format = getRequestedStructuredOutputFormat()) {
|
|
135
167
|
if (format === 'json') {
|
|
136
168
|
const data = normalizeSuccessEnvelopeData(value);
|
|
@@ -149,14 +181,18 @@ export function printError(value, format = getRequestedStructuredOutputFormat())
|
|
|
149
181
|
const notice = getPendingStructuredNotice();
|
|
150
182
|
writeToStderr(toJson({
|
|
151
183
|
ok: false,
|
|
184
|
+
...summarizeStructuredError(value),
|
|
152
185
|
error: value,
|
|
153
186
|
...(notice ? { _notice: notice } : {}),
|
|
154
187
|
}));
|
|
155
188
|
return;
|
|
156
189
|
}
|
|
157
|
-
|
|
190
|
+
const notice = getPendingStructuredNotice();
|
|
191
|
+
writeToStderr(toYaml({
|
|
158
192
|
ok: false,
|
|
193
|
+
...summarizeStructuredError(value),
|
|
159
194
|
error: value,
|
|
195
|
+
...(notice ? { _notice: notice } : {}),
|
|
160
196
|
}));
|
|
161
197
|
}
|
|
162
198
|
export function printYaml(value) {
|
package/dist/utils/redact.js
CHANGED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type SqliteLeaseOptions = {
|
|
2
|
+
lockPath: string;
|
|
3
|
+
timeoutMs: number;
|
|
4
|
+
maxWaitMs?: number;
|
|
5
|
+
pollIntervalMs?: number;
|
|
6
|
+
createBusyError?: () => Error;
|
|
7
|
+
};
|
|
8
|
+
export declare function acquireExclusiveSqliteLease(options: SqliteLeaseOptions): Promise<() => Promise<void>>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import Database from 'better-sqlite3';
|
|
4
|
+
function isSqliteBusyError(error) {
|
|
5
|
+
if (!error || typeof error !== 'object' || !('code' in error)) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
const code = String(error.code ?? '');
|
|
9
|
+
return code === 'SQLITE_BUSY' || code === 'SQLITE_LOCKED';
|
|
10
|
+
}
|
|
11
|
+
export async function acquireExclusiveSqliteLease(options) {
|
|
12
|
+
const requestedTimeout = Number.isFinite(options.timeoutMs)
|
|
13
|
+
? Math.max(1, options.timeoutMs)
|
|
14
|
+
: 1;
|
|
15
|
+
const maxWaitMs = Number.isFinite(options.maxWaitMs)
|
|
16
|
+
? Math.max(1, options.maxWaitMs)
|
|
17
|
+
: requestedTimeout;
|
|
18
|
+
const waitMs = Math.min(requestedTimeout, maxWaitMs);
|
|
19
|
+
const pollIntervalMs = Number.isFinite(options.pollIntervalMs)
|
|
20
|
+
? Math.max(1, options.pollIntervalMs)
|
|
21
|
+
: 50;
|
|
22
|
+
const deadline = Date.now() + waitMs;
|
|
23
|
+
await fs.mkdir(path.dirname(options.lockPath), { recursive: true, mode: 0o700 });
|
|
24
|
+
while (Date.now() <= deadline) {
|
|
25
|
+
let leaseDb = null;
|
|
26
|
+
try {
|
|
27
|
+
leaseDb = new Database(options.lockPath);
|
|
28
|
+
leaseDb.pragma('busy_timeout = 0');
|
|
29
|
+
leaseDb.exec('BEGIN EXCLUSIVE');
|
|
30
|
+
await fs.chmod(options.lockPath, 0o600).catch(() => undefined);
|
|
31
|
+
return async () => {
|
|
32
|
+
const current = leaseDb;
|
|
33
|
+
if (!current) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
if (current.inTransaction) {
|
|
38
|
+
current.exec('ROLLBACK');
|
|
39
|
+
}
|
|
40
|
+
current.close();
|
|
41
|
+
leaseDb = null;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
// Keep the handle reachable so a caller can retry instead of silently
|
|
45
|
+
// dropping an owner lease whose SQLite connection may still be open.
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
leaseDb?.close();
|
|
52
|
+
if (!isSqliteBusyError(error)) {
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
throw options.createBusyError?.() ?? new Error(`Timed out waiting for SQLite lease: ${options.lockPath}`);
|
|
59
|
+
}
|
|
@@ -1 +1,7 @@
|
|
|
1
|
-
export declare
|
|
1
|
+
export declare const MAX_SYNC_TIMEOUT_MS = 2147483647;
|
|
2
|
+
type ParseSyncTimeoutOptions = {
|
|
3
|
+
invalid?: 'throw' | 'undefined';
|
|
4
|
+
label?: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function parseSyncTimeoutMs(value: unknown, options?: ParseSyncTimeoutOptions): number | undefined;
|
|
7
|
+
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DinoxError } from './errors.js';
|
|
2
|
-
export
|
|
2
|
+
export const MAX_SYNC_TIMEOUT_MS = 2_147_483_647;
|
|
3
|
+
export function parseSyncTimeoutMs(value, options = {}) {
|
|
3
4
|
if (value === undefined || value === null) {
|
|
4
5
|
return undefined;
|
|
5
6
|
}
|
|
@@ -7,13 +8,13 @@ export function parseSyncTimeoutMs(value) {
|
|
|
7
8
|
if (!text) {
|
|
8
9
|
return undefined;
|
|
9
10
|
}
|
|
10
|
-
// Commander options are typically strings; be strict and fail fast for non-integers.
|
|
11
|
-
if (!/^\d+$/.test(text)) {
|
|
12
|
-
throw new DinoxError('--sync-timeout must be a positive integer');
|
|
13
|
-
}
|
|
14
11
|
const parsed = Number(text);
|
|
15
|
-
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
16
|
-
|
|
12
|
+
if (!/^\d+$/.test(text) || !Number.isSafeInteger(parsed) || parsed <= 0 || parsed > MAX_SYNC_TIMEOUT_MS) {
|
|
13
|
+
if (options.invalid === 'undefined') {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
const label = options.label ?? '--sync-timeout';
|
|
17
|
+
throw new DinoxError(`${label} must be a positive integer no greater than ${MAX_SYNC_TIMEOUT_MS}`);
|
|
17
18
|
}
|
|
18
19
|
return parsed;
|
|
19
20
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
const ANSI_ESCAPE_RE = /\x1b\[[0
|
|
1
|
+
const ANSI_ESCAPE_RE = /\x1b\[[0-?]*[ -/]*[@-~]|\x1b\](?:(?!\x07|\x1b\\)[\s\S]){0,2048}(?:\x07|\x1b\\)/g;
|
|
2
2
|
export function isDangerousUnicode(rune) {
|
|
3
3
|
return ((rune >= 0x200b && rune <= 0x200d) ||
|
|
4
|
+
rune === 0x061c ||
|
|
5
|
+
rune === 0x200e ||
|
|
6
|
+
rune === 0x200f ||
|
|
7
|
+
rune === 0x2060 ||
|
|
4
8
|
rune === 0xfeff ||
|
|
5
9
|
(rune >= 0x202a && rune <= 0x202e) ||
|
|
6
10
|
(rune >= 0x2028 && rune <= 0x2029) ||
|
|
@@ -21,7 +25,7 @@ export function sanitizeForTerminal(text) {
|
|
|
21
25
|
result += char;
|
|
22
26
|
continue;
|
|
23
27
|
}
|
|
24
|
-
if (code < 0x20 || code === 0x7f) {
|
|
28
|
+
if (code < 0x20 || code === 0x7f || (code >= 0x80 && code <= 0x9f)) {
|
|
25
29
|
continue;
|
|
26
30
|
}
|
|
27
31
|
if (isDangerousUnicode(code)) {
|
|
@@ -2,6 +2,7 @@ import { DinoxError } from './errors.js';
|
|
|
2
2
|
import { generateId } from './id.js';
|
|
3
3
|
import { createAudioNodeFromLink, createFileNodeFromLink, createImageNodeWithMetadata, createVideoNodeFromLink, getDinoxMarkdownManager, } from './tiptapMarkdownSchema.js';
|
|
4
4
|
import { inferMimeTypeFromHref, normalizeDinoxImageAttrs, normalizeFileBlockAttrs, normalizeFrontmatterRaw, parseDinoxMarkdownComment, } from './tiptapMarkdownSchemaUtils.js';
|
|
5
|
+
import { findMarkdownCodeRanges, findMarkdownLiteralRanges, isMarkdownOffsetInSortedRanges, } from './tiptapMarkdownSyntax.js';
|
|
5
6
|
const CONTENT_PREVIEW_MAX_LENGTH = 400;
|
|
6
7
|
const MEDIA_COMMENT_PATTERN = /<!--\s*({[\s\S]*?})\s*-->/g;
|
|
7
8
|
const MEDIA_COMMENT_PLACEHOLDER_PREFIX = 'DINOX_MEDIA_META_';
|
|
@@ -124,7 +125,11 @@ function validateAndCleanDocument(doc) {
|
|
|
124
125
|
function preprocessMarkdownMediaComments(markdown) {
|
|
125
126
|
const metadataByPlaceholder = new Map();
|
|
126
127
|
let counter = 0;
|
|
127
|
-
const
|
|
128
|
+
const codeRanges = findMarkdownCodeRanges(markdown);
|
|
129
|
+
const nextMarkdown = markdown.replace(MEDIA_COMMENT_PATTERN, (rawComment, _json, offset) => {
|
|
130
|
+
if (isMarkdownOffsetInSortedRanges(offset, codeRanges)) {
|
|
131
|
+
return rawComment;
|
|
132
|
+
}
|
|
128
133
|
const parsed = parseDinoxMarkdownComment(rawComment);
|
|
129
134
|
if (!parsed) {
|
|
130
135
|
return rawComment;
|
|
@@ -138,6 +143,46 @@ function preprocessMarkdownMediaComments(markdown) {
|
|
|
138
143
|
metadataByPlaceholder,
|
|
139
144
|
};
|
|
140
145
|
}
|
|
146
|
+
function preprocessMarkdownEscapes(markdown) {
|
|
147
|
+
const protectedRanges = findMarkdownLiteralRanges(markdown);
|
|
148
|
+
const placeholderPrefix = `DINOX_ESC_${generateId().replace(/-/g, '')}_`;
|
|
149
|
+
const replacements = new Map();
|
|
150
|
+
let output = '';
|
|
151
|
+
for (let index = 0; index < markdown.length;) {
|
|
152
|
+
const next = markdown[index + 1] ?? '';
|
|
153
|
+
if (markdown[index] === '\\' &&
|
|
154
|
+
/[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]/.test(next) &&
|
|
155
|
+
!isMarkdownOffsetInSortedRanges(index, protectedRanges)) {
|
|
156
|
+
const placeholder = `${placeholderPrefix}${replacements.size}__`;
|
|
157
|
+
replacements.set(placeholder, next);
|
|
158
|
+
output += placeholder;
|
|
159
|
+
index += 2;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
output += markdown[index];
|
|
163
|
+
index += 1;
|
|
164
|
+
}
|
|
165
|
+
return { markdown: output, placeholderPrefix, replacements };
|
|
166
|
+
}
|
|
167
|
+
function restoreMarkdownEscapes(node, placeholderPrefix, replacements) {
|
|
168
|
+
if (replacements.size === 0) {
|
|
169
|
+
return node;
|
|
170
|
+
}
|
|
171
|
+
const placeholderPattern = new RegExp(`${placeholderPrefix}\\d+__`, 'g');
|
|
172
|
+
const restoreValue = (value) => {
|
|
173
|
+
if (typeof value === 'string') {
|
|
174
|
+
return value.replace(placeholderPattern, (placeholder) => replacements.get(placeholder) ?? placeholder);
|
|
175
|
+
}
|
|
176
|
+
if (Array.isArray(value)) {
|
|
177
|
+
return value.map(restoreValue);
|
|
178
|
+
}
|
|
179
|
+
if (value && typeof value === 'object') {
|
|
180
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, restoreValue(item)]));
|
|
181
|
+
}
|
|
182
|
+
return value;
|
|
183
|
+
};
|
|
184
|
+
return restoreValue(node);
|
|
185
|
+
}
|
|
141
186
|
function extractStandaloneMarkdownLink(content) {
|
|
142
187
|
if (!Array.isArray(content) || content.length !== 1) {
|
|
143
188
|
return null;
|
|
@@ -308,9 +353,10 @@ export function convertMarkdownToNoteContent(markdown) {
|
|
|
308
353
|
};
|
|
309
354
|
}
|
|
310
355
|
const preprocessed = preprocessMarkdownMediaComments(source);
|
|
311
|
-
const
|
|
356
|
+
const escaped = preprocessMarkdownEscapes(preprocessed.markdown);
|
|
357
|
+
const parsed = getDinoxMarkdownManager().parse(escaped.markdown);
|
|
312
358
|
const normalized = normalizeStandaloneMarkdownLinksToMediaBlocks(parsed, preprocessed.metadataByPlaceholder);
|
|
313
|
-
const contentJson = validateAndCleanDocument(normalized);
|
|
359
|
+
const contentJson = validateAndCleanDocument(restoreMarkdownEscapes(normalized, escaped.placeholderPrefix, escaped.replacements));
|
|
314
360
|
const contentText = normalizeContentText(getPreviewTextFromDocument(contentJson));
|
|
315
361
|
return { contentJson, contentText };
|
|
316
362
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type MarkdownSourceRange = {
|
|
2
|
+
start: number;
|
|
3
|
+
end: number;
|
|
4
|
+
};
|
|
5
|
+
export declare function escapeLiteralMarkdownText(value: string): string;
|
|
6
|
+
export declare function isMarkdownOffsetInRanges(offset: number, ranges: readonly MarkdownSourceRange[]): boolean;
|
|
7
|
+
export declare function isMarkdownOffsetInSortedRanges(offset: number, ranges: readonly MarkdownSourceRange[]): boolean;
|
|
8
|
+
export declare function findMarkdownCodeRanges(source: string): MarkdownSourceRange[];
|
|
9
|
+
export declare function findMarkdownLiteralRanges(source: string): MarkdownSourceRange[];
|