@dinoxx/dinox-cli 1.1.1 → 2.0.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 +44 -3
- package/dist/cli.js +4 -0
- package/dist/commands/auth/index.js +1 -2
- package/dist/commands/boxes/index.js +37 -13
- package/dist/commands/boxes/operations.d.ts +3 -3
- package/dist/commands/boxes/operations.js +51 -28
- package/dist/commands/info/index.js +30 -5
- package/dist/commands/meta/repo.js +2 -1
- package/dist/commands/notes/actions.js +20 -37
- package/dist/commands/notes/bulk.d.ts +2 -2
- package/dist/commands/notes/bulk.js +3 -18
- package/dist/commands/notes/contentPatch.d.ts +5 -1
- package/dist/commands/notes/contentPatch.js +12 -1
- package/dist/commands/notes/contentPatchCommand.d.ts +3 -0
- package/dist/commands/notes/contentPatchCommand.js +29 -8
- package/dist/commands/notes/descriptors.js +19 -16
- package/dist/commands/notes/export.js +3 -0
- package/dist/commands/notes/repo.d.ts +9 -0
- package/dist/commands/notes/repo.js +65 -15
- package/dist/commands/notes/searchOutput.d.ts +2 -1
- package/dist/commands/notes/searchOutput.js +2 -1
- package/dist/commands/notes/searchSql.js +9 -3
- package/dist/commands/prompt/index.d.ts +1 -0
- package/dist/commands/prompt/index.js +26 -11
- package/dist/commands/savedViews/index.d.ts +7 -0
- package/dist/commands/savedViews/index.js +458 -0
- package/dist/commands/savedViews/model.d.ts +87 -0
- package/dist/commands/savedViews/model.js +306 -0
- package/dist/commands/savedViews/repo.d.ts +123 -0
- package/dist/commands/savedViews/repo.js +512 -0
- package/dist/commands/savedViews/semantics.d.ts +46 -0
- package/dist/commands/savedViews/semantics.js +738 -0
- package/dist/commands/schema/renderSkills.d.ts +2 -0
- package/dist/commands/schema/renderSkills.js +69 -10
- package/dist/commands/schema/spec.js +4 -0
- package/dist/commands/skills/index.d.ts +54 -0
- package/dist/commands/skills/index.js +468 -0
- package/dist/commands/storage/index.js +2 -2
- package/dist/commands/sync.js +2 -1
- package/dist/commands/tags/index.js +30 -11
- package/dist/commands/tags/operations.d.ts +3 -3
- package/dist/commands/tags/operations.js +45 -20
- package/dist/commands/templates/model.d.ts +50 -0
- package/dist/commands/templates/model.js +239 -0
- package/dist/commands/todo/index.js +3 -3
- package/dist/commands/update.d.ts +1 -0
- package/dist/commands/update.js +46 -9
- package/dist/commands/writeConfirmation.d.ts +13 -0
- package/dist/commands/writeConfirmation.js +54 -0
- package/dist/config/resolve.js +5 -10
- package/dist/config/types.d.ts +3 -6
- package/dist/constants/links.d.ts +18 -1
- package/dist/constants/links.js +26 -1
- package/dist/daemon/client.d.ts +2 -2
- package/dist/daemon/client.js +106 -52
- package/dist/daemon/index.d.ts +5 -3
- package/dist/daemon/index.js +28 -5
- package/dist/daemon/lifecycle.d.ts +7 -2
- package/dist/daemon/lifecycle.js +53 -13
- package/dist/daemon/pid.d.ts +1 -1
- package/dist/daemon/pid.js +6 -3
- package/dist/dinox.js +9 -1
- package/dist/powersync/exclusiveRuntime.d.ts +1 -0
- package/dist/powersync/exclusiveRuntime.js +26 -8
- package/dist/powersync/runtime.d.ts +3 -2
- package/dist/powersync/runtime.js +19 -3
- package/dist/powersync/schema/content.d.ts +1 -78
- package/dist/powersync/schema/content.js +1 -78
- package/dist/powersync/schema/electronCanonical.d.ts +1561 -0
- package/dist/powersync/schema/electronCanonical.js +1154 -0
- package/dist/powersync/schema/index.d.ts +573 -129
- package/dist/powersync/schema/index.js +3 -32
- package/dist/powersync/schema/local.d.ts +1 -56
- package/dist/powersync/schema/local.js +1 -60
- package/dist/powersync/schema/note.d.ts +1 -35
- package/dist/powersync/schema/note.js +1 -35
- package/dist/powersync/schema/notesExtras.d.ts +1 -62
- package/dist/powersync/schema/notesExtras.js +1 -71
- package/dist/powersync/schema/projects.d.ts +1 -104
- package/dist/powersync/schema/projects.js +1 -104
- package/dist/powersync/schema/savedViews.d.ts +1 -0
- package/dist/powersync/schema/savedViews.js +1 -0
- package/dist/powersync/schema/storage.d.ts +1 -24
- package/dist/powersync/schema/storage.js +1 -24
- package/dist/powersync/schema/tags.d.ts +1 -37
- package/dist/powersync/schema/tags.js +1 -37
- package/dist/powersync/syncGate.d.ts +20 -2
- package/dist/powersync/uploader.d.ts +16 -2
- package/dist/powersync/uploader.js +152 -158
- package/dist/skills/check.d.ts +1 -0
- package/dist/skills/check.js +538 -70
- package/dist/skills/retired.d.ts +60 -0
- package/dist/skills/retired.js +58 -0
- package/dist/utils/deadline.d.ts +8 -0
- package/dist/utils/deadline.js +31 -0
- package/dist/utils/dryRun.d.ts +20 -1
- package/dist/utils/dryRun.js +22 -3
- package/dist/utils/errors.d.ts +2 -0
- package/dist/utils/errors.js +2 -0
- package/dist/utils/processExit.d.ts +1 -0
- package/dist/utils/processExit.js +33 -0
- package/dist/utils/updateNotice.d.ts +1 -0
- package/dist/utils/updateNotice.js +1 -1
- package/package.json +8 -5
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { withPowerSyncWriteTransaction } from '../../powersync/runtime.js';
|
|
2
2
|
import { DinoxError } from '../../utils/errors.js';
|
|
3
3
|
import { nowIsoUtc } from '../../utils/time.js';
|
|
4
|
+
import { assertConfirmedWrite, assertWriteConfirmationProvided, } from '../writeConfirmation.js';
|
|
4
5
|
import { mergeTagReferencesInNote, renameTagReferencesInNote } from './content.js';
|
|
5
6
|
import { MAX_TAG_SEGMENT_LENGTH, REQUIRED_NOTE_TAG_COLUMNS, REQUIRED_TAG_NODE_COLUMNS, canonicalizeTagPath, getLeafName, isActiveTagNode, isActualTagNode, isTagPathWithinPrefix, normalizeTagLookupKey, parseStoredTags, parentPathOf, renameTagPathWithPrefix, trimString, } from './model.js';
|
|
6
7
|
import { activeTagNodes, actualActiveTagNodes, assertCanWriteTag, assertRequiredColumns, assertWritableTagNodes, descendantsOf, ensureNoTagPathConflict, getTableColumns, loadTagNodes, parentIdForPath, requireActiveTagPath, toTagListItem, } from './store.js';
|
|
@@ -297,7 +298,7 @@ export async function moveTag(db, input, options = {}) {
|
|
|
297
298
|
updatedNotes: noteUpdates.map(({ id, before, after }) => ({ id, before, after })),
|
|
298
299
|
};
|
|
299
300
|
}
|
|
300
|
-
|
|
301
|
+
async function buildTagMergePlan(db, input) {
|
|
301
302
|
const from = assertPath(input.from, 'Source tag path');
|
|
302
303
|
const to = assertPath(input.to, 'Target tag path');
|
|
303
304
|
const userId = trimString(input.userId);
|
|
@@ -318,28 +319,52 @@ export async function mergeTag(db, input, options = {}) {
|
|
|
318
319
|
.map((row) => updateNoteTagsForPrefix(row, source.fullPath, target.fullPath, 'merge', lookup))
|
|
319
320
|
.filter((row) => row.id && row.changed);
|
|
320
321
|
const softDeletedTagIds = sourceNodes.map((node) => node.id);
|
|
321
|
-
|
|
322
|
-
await withPowerSyncWriteTransaction(db, async (tx) => {
|
|
323
|
-
const timestamp = nowIsoUtc();
|
|
324
|
-
await applyNoteUpdates(tx, noteUpdates, timestamp);
|
|
325
|
-
for (const tagId of softDeletedTagIds) {
|
|
326
|
-
await tx.execute(`
|
|
327
|
-
UPDATE c_tag_node
|
|
328
|
-
SET is_del = 1, updated_at = ?
|
|
329
|
-
WHERE id = ?
|
|
330
|
-
`, [timestamp, tagId]);
|
|
331
|
-
}
|
|
332
|
-
});
|
|
333
|
-
}
|
|
322
|
+
const affectedCount = softDeletedTagIds.length + noteUpdates.length;
|
|
334
323
|
return {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
324
|
+
result: {
|
|
325
|
+
fromId: source.id,
|
|
326
|
+
toId: target.id,
|
|
327
|
+
fromPath: source.fullPath,
|
|
328
|
+
toPath: target.fullPath,
|
|
329
|
+
softDeletedTagIds,
|
|
330
|
+
updatedNotes: noteUpdates.map(({ id, before, after }) => ({ id, before, after })),
|
|
331
|
+
affectedCount,
|
|
332
|
+
},
|
|
333
|
+
noteUpdates,
|
|
334
|
+
userId,
|
|
341
335
|
};
|
|
342
336
|
}
|
|
337
|
+
function assertTagMergeCount(plan, options) {
|
|
338
|
+
assertConfirmedWrite({
|
|
339
|
+
operation: 'Tag merge',
|
|
340
|
+
schemaPath: 'tag.merge',
|
|
341
|
+
actualCount: plan.result.affectedCount,
|
|
342
|
+
options,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
export async function mergeTag(db, input, options = {}) {
|
|
346
|
+
if (options.dryRun) {
|
|
347
|
+
const plan = await buildTagMergePlan(db, input);
|
|
348
|
+
assertTagMergeCount(plan, options);
|
|
349
|
+
return plan.result;
|
|
350
|
+
}
|
|
351
|
+
assertWriteConfirmationProvided('Tag merge', options);
|
|
352
|
+
return withPowerSyncWriteTransaction(db, async (tx) => {
|
|
353
|
+
const plan = await buildTagMergePlan(tx, input);
|
|
354
|
+
assertTagMergeCount(plan, options);
|
|
355
|
+
const timestamp = nowIsoUtc();
|
|
356
|
+
await applyNoteUpdates(tx, plan.noteUpdates, timestamp);
|
|
357
|
+
for (const tagId of plan.result.softDeletedTagIds) {
|
|
358
|
+
await tx.execute(`
|
|
359
|
+
UPDATE c_tag_node
|
|
360
|
+
SET is_del = 1, updated_at = ?
|
|
361
|
+
WHERE id = ?
|
|
362
|
+
AND TRIM(COALESCE(user_id, '')) = ?
|
|
363
|
+
`, [timestamp, tagId, plan.userId]);
|
|
364
|
+
}
|
|
365
|
+
return plan.result;
|
|
366
|
+
});
|
|
367
|
+
}
|
|
343
368
|
export async function inspectTagCleanup(db, input = {}) {
|
|
344
369
|
const columns = await getTableColumns(db, 'c_tag_node');
|
|
345
370
|
assertRequiredColumns('c_tag_node', columns, REQUIRED_TAG_NODE_COLUMNS);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type TiptapNode } from '../notes/contentEngine.js';
|
|
2
|
+
export declare const NOTE_TEMPLATE_PROPERTY_DEF_VERSION = 1;
|
|
3
|
+
export declare const MAX_NOTE_TEMPLATE_PROPERTY_KEYS = 50;
|
|
4
|
+
export declare const MAX_NOTE_TEMPLATE_CONTENT_LENGTH = 200000;
|
|
5
|
+
export type NoteTemplatePropertyDef = {
|
|
6
|
+
version: typeof NOTE_TEMPLATE_PROPERTY_DEF_VERSION;
|
|
7
|
+
keys: string[];
|
|
8
|
+
};
|
|
9
|
+
export type NoteTemplateRow = {
|
|
10
|
+
id: string;
|
|
11
|
+
user_id: string | null;
|
|
12
|
+
name: string | null;
|
|
13
|
+
content: string | null;
|
|
14
|
+
priority: number | null;
|
|
15
|
+
is_show: number | null;
|
|
16
|
+
extra_data: string | null;
|
|
17
|
+
property_def: string | null;
|
|
18
|
+
insert_at: string | null;
|
|
19
|
+
update_at: string | null;
|
|
20
|
+
is_del: number | null;
|
|
21
|
+
};
|
|
22
|
+
export type NoteTemplateSummary = {
|
|
23
|
+
id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
propertyKeys: string[];
|
|
26
|
+
content: string;
|
|
27
|
+
hasContent: boolean;
|
|
28
|
+
isShow: boolean;
|
|
29
|
+
priority: number;
|
|
30
|
+
updateAt: string | null;
|
|
31
|
+
};
|
|
32
|
+
export type NoteTemplateTokenValues = {
|
|
33
|
+
date: string;
|
|
34
|
+
datetime: string;
|
|
35
|
+
time: string;
|
|
36
|
+
location: string;
|
|
37
|
+
};
|
|
38
|
+
export declare function isNotePropertyKeyValid(value: unknown): value is string;
|
|
39
|
+
export declare function validateNoteTemplatePropertyKeys(keys: readonly unknown[]): string[];
|
|
40
|
+
export declare function parseNoteTemplatePropertyDef(raw: string | null | undefined): NoteTemplatePropertyDef | null;
|
|
41
|
+
export declare function serializeNoteTemplatePropertyDef(keys: readonly string[]): string | null;
|
|
42
|
+
export declare function isNoteTemplateDocumentEmpty(document: TiptapNode | null | undefined): boolean;
|
|
43
|
+
export declare function parseNoteTemplateContent(raw: string | null | undefined): TiptapNode | null;
|
|
44
|
+
export declare function serializeNoteTemplateContent(document: unknown): string;
|
|
45
|
+
export declare function buildNoteTemplateTokenValues(now?: Date, location?: string): NoteTemplateTokenValues;
|
|
46
|
+
export declare function hydrateNoteTemplateDocument(document: TiptapNode, options?: {
|
|
47
|
+
now?: Date;
|
|
48
|
+
location?: string;
|
|
49
|
+
}): TiptapNode;
|
|
50
|
+
export declare function mapNoteTemplateRow(row: NoteTemplateRow): NoteTemplateSummary;
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { projectNoteContent } from '../notes/contentEngine.js';
|
|
2
|
+
import { DinoxError } from '../../utils/errors.js';
|
|
3
|
+
export const NOTE_TEMPLATE_PROPERTY_DEF_VERSION = 1;
|
|
4
|
+
export const MAX_NOTE_TEMPLATE_PROPERTY_KEYS = 50;
|
|
5
|
+
export const MAX_NOTE_TEMPLATE_CONTENT_LENGTH = 200_000;
|
|
6
|
+
const MAX_TIPTAP_NODE_DEPTH = 100;
|
|
7
|
+
const MAX_TIPTAP_NODE_COUNT = 10_000;
|
|
8
|
+
const NON_TEXT_TEMPLATE_NODE_TYPES = new Set([
|
|
9
|
+
'image',
|
|
10
|
+
'table',
|
|
11
|
+
'mermaid',
|
|
12
|
+
'mindgraph',
|
|
13
|
+
'codeBlock',
|
|
14
|
+
'horizontalRule',
|
|
15
|
+
'taskList',
|
|
16
|
+
]);
|
|
17
|
+
function formatError(message, code, details) {
|
|
18
|
+
return new DinoxError(message, {
|
|
19
|
+
code,
|
|
20
|
+
details,
|
|
21
|
+
recoverable: false,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function isPlainRecord(value) {
|
|
25
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
export function isNotePropertyKeyValid(value) {
|
|
28
|
+
if (typeof value !== 'string')
|
|
29
|
+
return false;
|
|
30
|
+
const trimmed = value.trim();
|
|
31
|
+
if (!trimmed || trimmed !== value || trimmed.length > 64)
|
|
32
|
+
return false;
|
|
33
|
+
if (trimmed === '__proto__')
|
|
34
|
+
return false;
|
|
35
|
+
return !trimmed.includes('.') && !/[\n\r\t]/.test(trimmed);
|
|
36
|
+
}
|
|
37
|
+
export function validateNoteTemplatePropertyKeys(keys) {
|
|
38
|
+
if (keys.length > MAX_NOTE_TEMPLATE_PROPERTY_KEYS) {
|
|
39
|
+
throw formatError(`Note templates support at most ${MAX_NOTE_TEMPLATE_PROPERTY_KEYS} property keys`, 'NOTE_TEMPLATE_INVALID_PROPERTY_DEF', { maxKeys: MAX_NOTE_TEMPLATE_PROPERTY_KEYS, receivedKeys: keys.length });
|
|
40
|
+
}
|
|
41
|
+
const seen = new Set();
|
|
42
|
+
const validated = [];
|
|
43
|
+
for (const key of keys) {
|
|
44
|
+
if (!isNotePropertyKeyValid(key)) {
|
|
45
|
+
throw formatError('Note template property_def contains an invalid property key', 'NOTE_TEMPLATE_INVALID_PROPERTY_DEF', {
|
|
46
|
+
key,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (seen.has(key)) {
|
|
50
|
+
throw formatError('Note template property_def contains duplicate property keys', 'NOTE_TEMPLATE_INVALID_PROPERTY_DEF', {
|
|
51
|
+
key,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
seen.add(key);
|
|
55
|
+
validated.push(key);
|
|
56
|
+
}
|
|
57
|
+
return validated;
|
|
58
|
+
}
|
|
59
|
+
export function parseNoteTemplatePropertyDef(raw) {
|
|
60
|
+
const value = raw?.trim() ?? '';
|
|
61
|
+
if (!value)
|
|
62
|
+
return null;
|
|
63
|
+
let parsed;
|
|
64
|
+
try {
|
|
65
|
+
parsed = JSON.parse(value);
|
|
66
|
+
}
|
|
67
|
+
catch (cause) {
|
|
68
|
+
throw formatError('Note template property_def must be valid JSON', 'NOTE_TEMPLATE_INVALID_PROPERTY_DEF', {
|
|
69
|
+
cause: cause instanceof Error ? cause.message : String(cause),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
if (!isPlainRecord(parsed)) {
|
|
73
|
+
throw formatError('Note template property_def must be a versioned object', 'NOTE_TEMPLATE_INVALID_PROPERTY_DEF');
|
|
74
|
+
}
|
|
75
|
+
if (parsed.version !== NOTE_TEMPLATE_PROPERTY_DEF_VERSION) {
|
|
76
|
+
throw formatError(`Unsupported note template property_def version: ${String(parsed.version)}`, 'NOTE_TEMPLATE_UNSUPPORTED_PROPERTY_DEF_VERSION', { supportedVersion: NOTE_TEMPLATE_PROPERTY_DEF_VERSION, receivedVersion: parsed.version });
|
|
77
|
+
}
|
|
78
|
+
if (!Array.isArray(parsed.keys)) {
|
|
79
|
+
throw formatError('Note template property_def keys must be an array', 'NOTE_TEMPLATE_INVALID_PROPERTY_DEF');
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
version: NOTE_TEMPLATE_PROPERTY_DEF_VERSION,
|
|
83
|
+
keys: validateNoteTemplatePropertyKeys(parsed.keys),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export function serializeNoteTemplatePropertyDef(keys) {
|
|
87
|
+
const validated = validateNoteTemplatePropertyKeys(keys);
|
|
88
|
+
if (validated.length === 0)
|
|
89
|
+
return null;
|
|
90
|
+
return JSON.stringify({
|
|
91
|
+
version: NOTE_TEMPLATE_PROPERTY_DEF_VERSION,
|
|
92
|
+
keys: validated,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function assertTiptapNode(value, state, depth) {
|
|
96
|
+
if (depth > MAX_TIPTAP_NODE_DEPTH) {
|
|
97
|
+
throw formatError(`Note template content exceeds the maximum node depth of ${MAX_TIPTAP_NODE_DEPTH}`, 'NOTE_TEMPLATE_INVALID_CONTENT');
|
|
98
|
+
}
|
|
99
|
+
if (!isPlainRecord(value) || typeof value.type !== 'string' || !value.type.trim()) {
|
|
100
|
+
throw formatError('Note template content contains an invalid Tiptap node', 'NOTE_TEMPLATE_INVALID_CONTENT');
|
|
101
|
+
}
|
|
102
|
+
state.count += 1;
|
|
103
|
+
if (state.count > MAX_TIPTAP_NODE_COUNT) {
|
|
104
|
+
throw formatError(`Note template content exceeds the maximum node count of ${MAX_TIPTAP_NODE_COUNT}`, 'NOTE_TEMPLATE_INVALID_CONTENT');
|
|
105
|
+
}
|
|
106
|
+
if (value.text !== undefined && typeof value.text !== 'string') {
|
|
107
|
+
throw formatError('Tiptap node text must be a string', 'NOTE_TEMPLATE_INVALID_CONTENT');
|
|
108
|
+
}
|
|
109
|
+
if (value.attrs !== undefined && !isPlainRecord(value.attrs)) {
|
|
110
|
+
throw formatError('Tiptap node attrs must be an object', 'NOTE_TEMPLATE_INVALID_CONTENT');
|
|
111
|
+
}
|
|
112
|
+
if (value.marks !== undefined) {
|
|
113
|
+
if (!Array.isArray(value.marks)) {
|
|
114
|
+
throw formatError('Tiptap node marks must be an array', 'NOTE_TEMPLATE_INVALID_CONTENT');
|
|
115
|
+
}
|
|
116
|
+
for (const mark of value.marks) {
|
|
117
|
+
if (!isPlainRecord(mark) || typeof mark.type !== 'string' || !mark.type.trim()) {
|
|
118
|
+
throw formatError('Tiptap node contains an invalid mark', 'NOTE_TEMPLATE_INVALID_CONTENT');
|
|
119
|
+
}
|
|
120
|
+
if (mark.attrs !== undefined && !isPlainRecord(mark.attrs)) {
|
|
121
|
+
throw formatError('Tiptap mark attrs must be an object', 'NOTE_TEMPLATE_INVALID_CONTENT');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (value.content !== undefined) {
|
|
126
|
+
if (!Array.isArray(value.content)) {
|
|
127
|
+
throw formatError('Tiptap node content must be an array', 'NOTE_TEMPLATE_INVALID_CONTENT');
|
|
128
|
+
}
|
|
129
|
+
for (const child of value.content) {
|
|
130
|
+
assertTiptapNode(child, state, depth + 1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function assertCanonicalTemplateDocument(value) {
|
|
135
|
+
assertTiptapNode(value, { count: 0 }, 1);
|
|
136
|
+
if (value.type !== 'doc' || !Array.isArray(value.content)) {
|
|
137
|
+
throw formatError('Note template content must be a Tiptap doc with a content array', 'NOTE_TEMPLATE_INVALID_CONTENT');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function extractTemplatePlainText(node) {
|
|
141
|
+
if (node.type === 'text')
|
|
142
|
+
return typeof node.text === 'string' ? node.text : '';
|
|
143
|
+
if (node.type === 'hardBreak')
|
|
144
|
+
return '\n';
|
|
145
|
+
return (node.content ?? []).map(extractTemplatePlainText).join('');
|
|
146
|
+
}
|
|
147
|
+
function hasNonTextTemplateContent(node) {
|
|
148
|
+
if (NON_TEXT_TEMPLATE_NODE_TYPES.has(node.type))
|
|
149
|
+
return true;
|
|
150
|
+
return (node.content ?? []).some(hasNonTextTemplateContent);
|
|
151
|
+
}
|
|
152
|
+
export function isNoteTemplateDocumentEmpty(document) {
|
|
153
|
+
if (!document)
|
|
154
|
+
return true;
|
|
155
|
+
if (extractTemplatePlainText(document).trim())
|
|
156
|
+
return false;
|
|
157
|
+
return !hasNonTextTemplateContent(document);
|
|
158
|
+
}
|
|
159
|
+
export function parseNoteTemplateContent(raw) {
|
|
160
|
+
const value = raw?.trim() ?? '';
|
|
161
|
+
if (!value)
|
|
162
|
+
return null;
|
|
163
|
+
if (value.length > MAX_NOTE_TEMPLATE_CONTENT_LENGTH) {
|
|
164
|
+
throw formatError(`Note template content must be at most ${MAX_NOTE_TEMPLATE_CONTENT_LENGTH} characters`, 'NOTE_TEMPLATE_INVALID_CONTENT');
|
|
165
|
+
}
|
|
166
|
+
let parsed;
|
|
167
|
+
try {
|
|
168
|
+
parsed = JSON.parse(value);
|
|
169
|
+
}
|
|
170
|
+
catch (cause) {
|
|
171
|
+
throw formatError('Note template content must be valid Tiptap JSON', 'NOTE_TEMPLATE_INVALID_CONTENT', {
|
|
172
|
+
cause: cause instanceof Error ? cause.message : String(cause),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
assertCanonicalTemplateDocument(parsed);
|
|
176
|
+
return projectNoteContent(parsed).document;
|
|
177
|
+
}
|
|
178
|
+
export function serializeNoteTemplateContent(document) {
|
|
179
|
+
assertCanonicalTemplateDocument(document);
|
|
180
|
+
if (isNoteTemplateDocumentEmpty(document))
|
|
181
|
+
return '';
|
|
182
|
+
const content = projectNoteContent(document).contentJson;
|
|
183
|
+
if (content.length > MAX_NOTE_TEMPLATE_CONTENT_LENGTH) {
|
|
184
|
+
throw formatError(`Note template content must be at most ${MAX_NOTE_TEMPLATE_CONTENT_LENGTH} characters`, 'NOTE_TEMPLATE_INVALID_CONTENT');
|
|
185
|
+
}
|
|
186
|
+
return content;
|
|
187
|
+
}
|
|
188
|
+
function pad(value) {
|
|
189
|
+
return String(value).padStart(2, '0');
|
|
190
|
+
}
|
|
191
|
+
export function buildNoteTemplateTokenValues(now = new Date(), location = '') {
|
|
192
|
+
if (Number.isNaN(now.getTime())) {
|
|
193
|
+
throw formatError('Note template token time must be a valid date', 'NOTE_TEMPLATE_INVALID_TOKEN_CONTEXT');
|
|
194
|
+
}
|
|
195
|
+
const date = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
|
|
196
|
+
const time = `${pad(now.getHours())}:${pad(now.getMinutes())}`;
|
|
197
|
+
return {
|
|
198
|
+
date,
|
|
199
|
+
time,
|
|
200
|
+
datetime: `${date} ${time}`,
|
|
201
|
+
location,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function hydrateTemplateNode(node, values) {
|
|
205
|
+
if (node.type === 'text' && typeof node.text === 'string') {
|
|
206
|
+
const text = node.text
|
|
207
|
+
.replaceAll('{{date}}', values.date)
|
|
208
|
+
.replaceAll('{{datetime}}', values.datetime)
|
|
209
|
+
.replaceAll('{{time}}', values.time)
|
|
210
|
+
.replaceAll('{{location}}', values.location);
|
|
211
|
+
return text ? { ...node, text } : null;
|
|
212
|
+
}
|
|
213
|
+
if (!Array.isArray(node.content))
|
|
214
|
+
return { ...node };
|
|
215
|
+
const content = node.content
|
|
216
|
+
.map((child) => hydrateTemplateNode(child, values))
|
|
217
|
+
.filter((child) => child !== null);
|
|
218
|
+
return { ...node, content };
|
|
219
|
+
}
|
|
220
|
+
export function hydrateNoteTemplateDocument(document, options = {}) {
|
|
221
|
+
assertCanonicalTemplateDocument(document);
|
|
222
|
+
const values = buildNoteTemplateTokenValues(options.now, options.location ?? '');
|
|
223
|
+
return hydrateTemplateNode(document, values) ?? { type: 'doc', content: [] };
|
|
224
|
+
}
|
|
225
|
+
export function mapNoteTemplateRow(row) {
|
|
226
|
+
const propertyDef = parseNoteTemplatePropertyDef(row.property_def);
|
|
227
|
+
const document = parseNoteTemplateContent(row.content);
|
|
228
|
+
const content = document ? serializeNoteTemplateContent(document) : '';
|
|
229
|
+
return {
|
|
230
|
+
id: row.id,
|
|
231
|
+
name: (row.name ?? '').trim(),
|
|
232
|
+
propertyKeys: propertyDef?.keys ?? [],
|
|
233
|
+
content,
|
|
234
|
+
hasContent: Boolean(content),
|
|
235
|
+
isShow: Number(row.is_show ?? 0) === 1,
|
|
236
|
+
priority: Number.isFinite(row.priority) ? Math.trunc(Number(row.priority)) : 0,
|
|
237
|
+
updateAt: row.update_at ?? null,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
@@ -182,7 +182,7 @@ async function runTodoAppend({ args, globals, options }) {
|
|
|
182
182
|
noteId: typeof appendOptions.noteId === 'string' ? appendOptions.noteId : undefined,
|
|
183
183
|
taskTexts,
|
|
184
184
|
});
|
|
185
|
-
printDryRunPreview(preview);
|
|
185
|
+
printDryRunPreview(preview, runtime);
|
|
186
186
|
return;
|
|
187
187
|
}
|
|
188
188
|
const result = await appendTodoTasks(db, {
|
|
@@ -216,7 +216,7 @@ async function runTodoCreate({ args, globals, options }) {
|
|
|
216
216
|
title: typeof createOptions.title === 'string' ? createOptions.title : undefined,
|
|
217
217
|
taskTexts,
|
|
218
218
|
});
|
|
219
|
-
printDryRunPreview(preview);
|
|
219
|
+
printDryRunPreview(preview, runtime);
|
|
220
220
|
return;
|
|
221
221
|
}
|
|
222
222
|
const identity = await resolveAuthIdentity(config);
|
|
@@ -254,7 +254,7 @@ async function runTodoUpdate({ args, globals, options }) {
|
|
|
254
254
|
noteId: typeof updateOptions.noteId === 'string' ? updateOptions.noteId : undefined,
|
|
255
255
|
checked,
|
|
256
256
|
});
|
|
257
|
-
printDryRunPreview(preview);
|
|
257
|
+
printDryRunPreview(preview, runtime);
|
|
258
258
|
return;
|
|
259
259
|
}
|
|
260
260
|
const result = await updateTodoTaskStatus(db, {
|
package/dist/commands/update.js
CHANGED
|
@@ -2,9 +2,11 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { realpathSync } from 'node:fs';
|
|
3
3
|
import { registerStandaloneCommand } from './descriptor/register.js';
|
|
4
4
|
import { FORMAT_OPTIONS } from './schema/globalOptions.js';
|
|
5
|
-
import {
|
|
5
|
+
import { getSkillsReleaseMetadata } from '../constants/links.js';
|
|
6
|
+
import { RETIRED_SKILLS, RETIRED_SKILLS_CLEANUP, formatRetiredSkillsSummary, } from '../skills/retired.js';
|
|
6
7
|
import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, } from '../utils/output.js';
|
|
7
8
|
import { DinoxError } from '../utils/errors.js';
|
|
9
|
+
import { fetchLatestPublishedVersion } from '../utils/updateNotice.js';
|
|
8
10
|
import { getPackageVersion } from '../utils/version.js';
|
|
9
11
|
const PACKAGE_NAME = '@dinoxx/dinox-cli';
|
|
10
12
|
const MAX_CAPTURED_UPDATE_OUTPUT_BYTES = 64 * 1024;
|
|
@@ -123,13 +125,20 @@ function resolveDeps(overrides) {
|
|
|
123
125
|
detectPackageManager,
|
|
124
126
|
runUpdate,
|
|
125
127
|
getVersion: getPackageVersion,
|
|
128
|
+
getLatestVersion: fetchLatestPublishedVersion,
|
|
126
129
|
log: logHumanReadable,
|
|
127
130
|
...overrides,
|
|
128
131
|
};
|
|
129
132
|
}
|
|
130
|
-
function printSkillsReminder(log) {
|
|
131
|
-
|
|
132
|
-
log(
|
|
133
|
+
function printSkillsReminder(log, version) {
|
|
134
|
+
const skills = getSkillsReleaseMetadata(version);
|
|
135
|
+
log(`skills repo: ${skills.repository}`);
|
|
136
|
+
log(`skills release: ${skills.release}`);
|
|
137
|
+
log(`skills installer: ${skills.installer.name}@${skills.installer.version}`);
|
|
138
|
+
log(`skills install: ${skills.installCommand}`);
|
|
139
|
+
log(`ai reminder: update local Dinox skills to ${skills.tag} after this CLI update.`);
|
|
140
|
+
log(`retired skills: ${formatRetiredSkillsSummary()}`);
|
|
141
|
+
log(`retired skills cleanup: ${RETIRED_SKILLS_CLEANUP.guidance}`);
|
|
133
142
|
}
|
|
134
143
|
function parsePackageManagerOverride(value) {
|
|
135
144
|
if (value === undefined || value === null || value === '') {
|
|
@@ -161,32 +170,60 @@ export function createUpdateCommandDescriptor(overrides = {}) {
|
|
|
161
170
|
globalOptions: FORMAT_OPTIONS,
|
|
162
171
|
output: {
|
|
163
172
|
type: 'structured',
|
|
164
|
-
summary: 'Updater result and
|
|
165
|
-
fields: [
|
|
173
|
+
summary: 'Updater result and matching standalone skills release metadata',
|
|
174
|
+
fields: [
|
|
175
|
+
'previousVersion',
|
|
176
|
+
'version',
|
|
177
|
+
'packageManager',
|
|
178
|
+
'package',
|
|
179
|
+
'target',
|
|
180
|
+
'skillsRepo',
|
|
181
|
+
'skillsVersion',
|
|
182
|
+
'skillsTag',
|
|
183
|
+
'skillsRelease',
|
|
184
|
+
'skillsInstaller',
|
|
185
|
+
'skillsInstallCommand',
|
|
186
|
+
'retiredSkills',
|
|
187
|
+
'retiredSkillsCleanup',
|
|
188
|
+
],
|
|
166
189
|
},
|
|
167
190
|
examples: ['dino update', 'dino update --package-manager pnpm --format json'],
|
|
168
191
|
},
|
|
169
192
|
action: async (context) => {
|
|
170
193
|
const pm = parsePackageManagerOverride(context.options.packageManager) ?? deps.detectPackageManager();
|
|
171
194
|
const previousVersion = deps.getVersion();
|
|
195
|
+
const targetVersion = await deps.getLatestVersion();
|
|
172
196
|
const structuredOutput = getRequestedStructuredOutputFormat() !== null;
|
|
173
197
|
if (!structuredOutput) {
|
|
174
198
|
deps.log(`current version: ${previousVersion}`);
|
|
175
199
|
deps.log(`updating ${PACKAGE_NAME} via ${pm}...`);
|
|
176
200
|
}
|
|
177
201
|
await deps.runUpdate(pm, { structuredOutput });
|
|
202
|
+
const observedVersion = deps.getVersion();
|
|
203
|
+
const version = observedVersion !== previousVersion
|
|
204
|
+
? observedVersion
|
|
205
|
+
: targetVersion ?? observedVersion;
|
|
206
|
+
const skills = getSkillsReleaseMetadata(version);
|
|
178
207
|
if (structuredOutput) {
|
|
179
208
|
printSuccess({
|
|
180
209
|
previousVersion,
|
|
210
|
+
version,
|
|
181
211
|
packageManager: pm,
|
|
182
212
|
package: PACKAGE_NAME,
|
|
183
213
|
target: 'latest',
|
|
184
|
-
skillsRepo:
|
|
214
|
+
skillsRepo: skills.repository,
|
|
215
|
+
skillsVersion: skills.version,
|
|
216
|
+
skillsTag: skills.tag,
|
|
217
|
+
skillsRelease: skills.release,
|
|
218
|
+
skillsInstaller: skills.installer,
|
|
219
|
+
skillsInstallCommand: skills.installCommand,
|
|
220
|
+
retiredSkills: RETIRED_SKILLS,
|
|
221
|
+
retiredSkillsCleanup: RETIRED_SKILLS_CLEANUP,
|
|
185
222
|
});
|
|
186
223
|
return;
|
|
187
224
|
}
|
|
188
|
-
deps.log(
|
|
189
|
-
printSkillsReminder(deps.log);
|
|
225
|
+
deps.log(`done: ${version}`);
|
|
226
|
+
printSkillsReminder(deps.log, version);
|
|
190
227
|
},
|
|
191
228
|
};
|
|
192
229
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type ConfirmedWriteOptions = {
|
|
2
|
+
dryRun?: boolean;
|
|
3
|
+
confirm?: boolean;
|
|
4
|
+
expectedCount?: number | null;
|
|
5
|
+
};
|
|
6
|
+
export declare function parseExpectedCount(value: unknown): number | null;
|
|
7
|
+
export declare function assertConfirmedWrite(args: {
|
|
8
|
+
operation: string;
|
|
9
|
+
schemaPath: string;
|
|
10
|
+
actualCount: number;
|
|
11
|
+
options?: ConfirmedWriteOptions;
|
|
12
|
+
}): void;
|
|
13
|
+
export declare function assertWriteConfirmationProvided(operation: string, options?: ConfirmedWriteOptions): void;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { DinoxError } from '../utils/errors.js';
|
|
2
|
+
export function parseExpectedCount(value) {
|
|
3
|
+
if (value === undefined || value === null || value === '') {
|
|
4
|
+
return null;
|
|
5
|
+
}
|
|
6
|
+
if (typeof value !== 'string') {
|
|
7
|
+
throw new DinoxError('--expected-count must be a non-negative integer');
|
|
8
|
+
}
|
|
9
|
+
const trimmed = value.trim();
|
|
10
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
11
|
+
throw new DinoxError('--expected-count must be a non-negative integer');
|
|
12
|
+
}
|
|
13
|
+
const parsed = Number(trimmed);
|
|
14
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) {
|
|
15
|
+
throw new DinoxError('--expected-count must be a non-negative integer');
|
|
16
|
+
}
|
|
17
|
+
return parsed;
|
|
18
|
+
}
|
|
19
|
+
export function assertConfirmedWrite(args) {
|
|
20
|
+
if (args.options?.dryRun) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const options = args.options;
|
|
24
|
+
assertWriteConfirmationProvided(args.operation, options);
|
|
25
|
+
const expectedCount = options?.expectedCount;
|
|
26
|
+
if (expectedCount === undefined || expectedCount === null) {
|
|
27
|
+
throw new DinoxError(`${args.operation} requires --expected-count <n> for writes`);
|
|
28
|
+
}
|
|
29
|
+
if (expectedCount !== args.actualCount) {
|
|
30
|
+
throw new DinoxError(`--expected-count ${expectedCount} does not match current affected record count ${args.actualCount}`, {
|
|
31
|
+
code: 'PRECONDITION_FAILED',
|
|
32
|
+
details: {
|
|
33
|
+
expectedCount,
|
|
34
|
+
actualCount: args.actualCount,
|
|
35
|
+
},
|
|
36
|
+
suggestion: `Re-run the merge with --dry-run, review changes.affected_count, then retry with the matching count. See \`dino schema ${args.schemaPath} --format json\`.`,
|
|
37
|
+
suggestedAction: {
|
|
38
|
+
command: `dino schema ${args.schemaPath} --format json`,
|
|
39
|
+
reason: 'Review the merge guard contract, then run a fresh dry-run before retrying.',
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export function assertWriteConfirmationProvided(operation, options) {
|
|
45
|
+
if (options?.dryRun) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!options?.confirm) {
|
|
49
|
+
throw new DinoxError(`${operation} requires --confirm for writes`);
|
|
50
|
+
}
|
|
51
|
+
if (options.expectedCount === undefined || options.expectedCount === null) {
|
|
52
|
+
throw new DinoxError(`${operation} requires --expected-count <n> for writes`);
|
|
53
|
+
}
|
|
54
|
+
}
|
package/dist/config/resolve.js
CHANGED
|
@@ -2,8 +2,7 @@ import crypto from 'node:crypto';
|
|
|
2
2
|
import { POWERSYNC_DEFAULT_ENDPOINT, POWERSYNC_DEFAULT_TOKEN_ENDPOINT, POWERSYNC_DEFAULT_UPLOAD_BASE_URL, } from './serviceEndpoints.js';
|
|
3
3
|
import { normalizeAuthToken, readEnvAuthToken } from '../utils/authToken.js';
|
|
4
4
|
import { parseSyncTimeoutMs } from '../utils/syncTimeout.js';
|
|
5
|
-
const
|
|
6
|
-
const DEFAULT_UPLOAD_V2_PATH = '/powersync/v2/uploadData';
|
|
5
|
+
const DEFAULT_UPLOAD_PATH = '/powersync/v6/uploadData';
|
|
7
6
|
const DEFAULT_SYNC_TIMEOUT_MS = 300_000;
|
|
8
7
|
function nonEmptyString(value) {
|
|
9
8
|
if (typeof value !== 'string') {
|
|
@@ -24,8 +23,7 @@ export function fingerprintDaemonRuntimeConfig(config) {
|
|
|
24
23
|
endpoint: config.powersync.endpoint,
|
|
25
24
|
tokenEndpoint: config.powersync.tokenEndpoint,
|
|
26
25
|
uploadBaseUrl: config.powersync.uploadBaseUrl,
|
|
27
|
-
|
|
28
|
-
uploadV2Path: config.powersync.uploadV2Path,
|
|
26
|
+
uploadPath: config.powersync.uploadPath,
|
|
29
27
|
},
|
|
30
28
|
sync: {
|
|
31
29
|
timeoutMs: config.sync.timeoutMs,
|
|
@@ -40,13 +38,11 @@ export function resolveConfig(raw) {
|
|
|
40
38
|
const fileEmail = nonEmptyString(raw.auth?.email ?? undefined) ?? raw.auth?.email ?? undefined;
|
|
41
39
|
const fileUserInfoSyncedAt = nonEmptyString(raw.auth?.userInfoSyncedAt);
|
|
42
40
|
const fileTokenEndpoint = nonEmptyString(raw.powersync?.tokenEndpoint);
|
|
43
|
-
const
|
|
44
|
-
const fileUploadV2Path = nonEmptyString(raw.powersync?.uploadV2Path);
|
|
41
|
+
const fileUploadPath = nonEmptyString(raw.powersync?.uploadPath);
|
|
45
42
|
const fileTimeoutMs = parseSyncTimeoutMs(raw.sync?.timeoutMs, { invalid: 'undefined' });
|
|
46
43
|
const envAuth = readEnvAuthToken();
|
|
47
44
|
const envTokenEndpoint = nonEmptyString(process.env.DINOX_POWERSYNC_TOKEN_ENDPOINT) ?? nonEmptyString(process.env.POWERSYNC_TOKEN_ENDPOINT);
|
|
48
|
-
const
|
|
49
|
-
const envUploadV2Path = nonEmptyString(process.env.DINOX_POWERSYNC_UPLOAD_V2_PATH);
|
|
45
|
+
const envUploadPath = nonEmptyString(process.env.DINOX_POWERSYNC_UPLOAD_PATH);
|
|
50
46
|
const envTimeoutMs = parseSyncTimeoutMs(process.env.DINOX_SYNC_TIMEOUT_MS, { invalid: 'undefined' });
|
|
51
47
|
const effectiveAuth = envAuth ?? fileAuth;
|
|
52
48
|
const canReusePersistedIdentity = !envAuth || envAuth === fileAuth;
|
|
@@ -63,8 +59,7 @@ export function resolveConfig(raw) {
|
|
|
63
59
|
endpoint: POWERSYNC_DEFAULT_ENDPOINT,
|
|
64
60
|
tokenEndpoint: envTokenEndpoint ?? fileTokenEndpoint ?? POWERSYNC_DEFAULT_TOKEN_ENDPOINT,
|
|
65
61
|
uploadBaseUrl: POWERSYNC_DEFAULT_UPLOAD_BASE_URL,
|
|
66
|
-
|
|
67
|
-
uploadV2Path: envUploadV2Path ?? fileUploadV2Path ?? DEFAULT_UPLOAD_V2_PATH,
|
|
62
|
+
uploadPath: envUploadPath ?? fileUploadPath ?? DEFAULT_UPLOAD_PATH,
|
|
68
63
|
},
|
|
69
64
|
sync: {
|
|
70
65
|
timeoutMs: envTimeoutMs ?? fileTimeoutMs ?? DEFAULT_SYNC_TIMEOUT_MS,
|
package/dist/config/types.d.ts
CHANGED
|
@@ -21,10 +21,8 @@ export type DinoxConfig = {
|
|
|
21
21
|
tokenEndpoint?: string;
|
|
22
22
|
/** Base URL for upload endpoints. */
|
|
23
23
|
uploadBaseUrl?: string;
|
|
24
|
-
/** Defaults to /powersync/
|
|
25
|
-
|
|
26
|
-
/** Defaults to /powersync/v2/uploadData */
|
|
27
|
-
uploadV2Path?: string;
|
|
24
|
+
/** Batch upload endpoint path. Defaults to /powersync/v6/uploadData */
|
|
25
|
+
uploadPath?: string;
|
|
28
26
|
};
|
|
29
27
|
sync?: {
|
|
30
28
|
/** Default sync/connect timeout in milliseconds. */
|
|
@@ -45,8 +43,7 @@ export type ResolvedDinoxConfig = {
|
|
|
45
43
|
endpoint: string;
|
|
46
44
|
tokenEndpoint: string;
|
|
47
45
|
uploadBaseUrl: string;
|
|
48
|
-
|
|
49
|
-
uploadV2Path: string;
|
|
46
|
+
uploadPath: string;
|
|
50
47
|
};
|
|
51
48
|
sync: {
|
|
52
49
|
timeoutMs: number;
|