@dinoxx/dinox-cli 2.1.1 → 2.2.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/dist/commands/notes/actions.js +25 -2
- package/dist/commands/notes/descriptors.js +3 -1
- package/dist/commands/notes/dryRun.d.ts +1 -0
- package/dist/commands/notes/dryRun.js +8 -2
- package/dist/commands/notes/repo/shared.d.ts +2 -0
- package/dist/commands/notes/repo/write.d.ts +1 -0
- package/dist/commands/notes/repo/write.js +15 -4
- package/package.json +1 -1
|
@@ -39,11 +39,17 @@ async function executeNoteUpdateOperation(args) {
|
|
|
39
39
|
await syncBeforeNoteCommand(runtime);
|
|
40
40
|
const durability = resolveWriteDurabilityForRuntime(runtime, args.durability);
|
|
41
41
|
const ids = await parseTargetNoteIds(args.id, args.idsOption, 'update');
|
|
42
|
+
const hasTitle = typeof args.title === 'string';
|
|
42
43
|
const hasTags = typeof args.tagsOption === 'string';
|
|
43
44
|
const hasBoxes = typeof args.boxOption === 'string';
|
|
44
45
|
const hasStarred = typeof args.starred === 'boolean';
|
|
45
|
-
if (!hasTags && !hasBoxes && !hasStarred) {
|
|
46
|
-
throw new DinoxError('At least one option is required: --tags, --boxes, or --starred');
|
|
46
|
+
if (!hasTitle && !hasTags && !hasBoxes && !hasStarred) {
|
|
47
|
+
throw new DinoxError('At least one option is required: --title, --tags, --boxes, or --starred');
|
|
48
|
+
}
|
|
49
|
+
if (hasTitle && ids.length !== 1) {
|
|
50
|
+
throw new DinoxError('--title can only be used with a single note id; run one `dino note update <id> --title` per note', {
|
|
51
|
+
code: 'USAGE_ERROR',
|
|
52
|
+
});
|
|
47
53
|
}
|
|
48
54
|
const requestedTags = hasTags
|
|
49
55
|
? await parseMutationListInput(args.tagsOption, '--tags')
|
|
@@ -68,6 +74,7 @@ async function executeNoteUpdateOperation(args) {
|
|
|
68
74
|
offline,
|
|
69
75
|
userId: tagUserId,
|
|
70
76
|
ids,
|
|
77
|
+
title: args.title,
|
|
71
78
|
tagsInput: tags,
|
|
72
79
|
zettelBoxNames: requestedBoxNames,
|
|
73
80
|
starred: hasStarred ? args.starred : undefined,
|
|
@@ -81,6 +88,7 @@ async function executeNoteUpdateOperation(args) {
|
|
|
81
88
|
for (const targetId of ids) {
|
|
82
89
|
results.push(await updateNote(db, {
|
|
83
90
|
id: targetId,
|
|
91
|
+
title: args.title,
|
|
84
92
|
tags,
|
|
85
93
|
zettelBoxIds,
|
|
86
94
|
starred: hasStarred ? args.starred : undefined,
|
|
@@ -98,6 +106,7 @@ async function executeNoteUpdateOperation(args) {
|
|
|
98
106
|
results: results.map((item) => ({
|
|
99
107
|
id: item.id,
|
|
100
108
|
updated: item.updated,
|
|
109
|
+
title: item.title,
|
|
101
110
|
tags: item.tags,
|
|
102
111
|
boxes: item.zettelBoxIds,
|
|
103
112
|
is_starred: item.is_starred,
|
|
@@ -452,6 +461,17 @@ export async function runNoteDelete({ command, options, args }) {
|
|
|
452
461
|
printStaleCacheWarning(runtime);
|
|
453
462
|
});
|
|
454
463
|
}
|
|
464
|
+
// Titles are single-line metadata; reject input that would render as a multi-line title.
|
|
465
|
+
function parseNoteTitleOption(raw) {
|
|
466
|
+
const title = raw.trim();
|
|
467
|
+
if (!title) {
|
|
468
|
+
throw new DinoxError('--title must not be empty', { code: 'USAGE_ERROR' });
|
|
469
|
+
}
|
|
470
|
+
if (/[\u0000-\u001f\u007f]/.test(title)) {
|
|
471
|
+
throw new DinoxError('--title must be a single line without control characters', { code: 'USAGE_ERROR' });
|
|
472
|
+
}
|
|
473
|
+
return title;
|
|
474
|
+
}
|
|
455
475
|
export async function runNoteUpdate({ command, options, args }) {
|
|
456
476
|
const updateOptions = options;
|
|
457
477
|
await executeNoteUpdateOperation({
|
|
@@ -463,6 +483,9 @@ export async function runNoteUpdate({ command, options, args }) {
|
|
|
463
483
|
starred: typeof updateOptions.starred === 'string'
|
|
464
484
|
? parseStarredOption(updateOptions.starred, '--starred')
|
|
465
485
|
: undefined,
|
|
486
|
+
title: typeof updateOptions.title === 'string'
|
|
487
|
+
? parseNoteTitleOption(updateOptions.title)
|
|
488
|
+
: undefined,
|
|
466
489
|
tagsOption: updateOptions.tags,
|
|
467
490
|
commandName: 'dino note update',
|
|
468
491
|
actionName: 'update_note',
|
|
@@ -202,10 +202,11 @@ export const noteCommandGroup = {
|
|
|
202
202
|
},
|
|
203
203
|
{
|
|
204
204
|
name: 'update',
|
|
205
|
-
description: 'Full-replace note metadata for explicit note ids',
|
|
205
|
+
description: 'Full-replace note metadata (title, tags, boxes, starred) for explicit note ids',
|
|
206
206
|
arguments: [{ syntax: '[id]', description: 'Optional note id for single-note full replacement' }],
|
|
207
207
|
options: [
|
|
208
208
|
{ flags: '--ids <string|@file>', description: 'Batch note ids (JSON array or comma/newline-separated)', type: 'string' },
|
|
209
|
+
{ flags: '--title <title>', description: 'Replace the note title (single note id only; content is unchanged)', type: 'string' },
|
|
209
210
|
{ flags: '--tags <string|@file>', description: 'Replace the entire tag list; use [] to clear all tags', type: 'string' },
|
|
210
211
|
{ flags: '--boxes <string|@file>', description: 'Replace the entire box list; use [] to clear all boxes', type: 'string' },
|
|
211
212
|
{ flags: '--starred <true|false>', description: 'Replace the starred state', type: 'string', enum: ['true', 'false'] },
|
|
@@ -224,6 +225,7 @@ export const noteCommandGroup = {
|
|
|
224
225
|
},
|
|
225
226
|
examples: [
|
|
226
227
|
'dino note update <id> --tags "work,ai" --format json --dry-run',
|
|
228
|
+
'dino note update <id> --title "New title" --format json --dry-run',
|
|
227
229
|
'dino note update --ids @/tmp/note-ids.txt --boxes "Inbox" --starred true --format json',
|
|
228
230
|
],
|
|
229
231
|
},
|
|
@@ -67,9 +67,14 @@ export async function buildNoteUpdateDryRunPreview(args) {
|
|
|
67
67
|
const nextBoxes = Array.isArray(args.zettelBoxNames) ? normalizeDistinctStrings(args.zettelBoxNames) : note.zettel_boxes;
|
|
68
68
|
const tagsTo = nextTags ?? currentTags;
|
|
69
69
|
const starredTo = typeof args.starred === 'boolean' ? args.starred : note.is_starred;
|
|
70
|
+
const titleFrom = note.title ?? '';
|
|
71
|
+
const titleTo = typeof args.title === 'string' ? args.title : titleFrom;
|
|
70
72
|
return {
|
|
71
73
|
id: note.id,
|
|
72
|
-
title:
|
|
74
|
+
title: titleFrom,
|
|
75
|
+
...(typeof args.title === 'string'
|
|
76
|
+
? { title_from: titleFrom, title_to: titleTo, title_changed: titleFrom !== titleTo }
|
|
77
|
+
: {}),
|
|
73
78
|
tags_from: currentTags,
|
|
74
79
|
tags_to: tagsTo,
|
|
75
80
|
tags_changed: Array.isArray(args.tagsInput) ? !sameNormalizedSet(currentTags, tagsTo) : false,
|
|
@@ -81,7 +86,7 @@ export async function buildNoteUpdateDryRunPreview(args) {
|
|
|
81
86
|
starred_changed: typeof args.starred === 'boolean' ? note.is_starred !== starredTo : false,
|
|
82
87
|
};
|
|
83
88
|
});
|
|
84
|
-
const changedCount = changes.filter((item) => item.tags_changed || item.boxes_changed || item.starred_changed).length;
|
|
89
|
+
const changedCount = changes.filter((item) => item.title_changed === true || item.tags_changed || item.boxes_changed || item.starred_changed).length;
|
|
85
90
|
return {
|
|
86
91
|
dry_run: true,
|
|
87
92
|
command: args.commandName,
|
|
@@ -91,6 +96,7 @@ export async function buildNoteUpdateDryRunPreview(args) {
|
|
|
91
96
|
warnings: buildExecutionWarnings(args.config, args.offline),
|
|
92
97
|
inputs: {
|
|
93
98
|
ids: args.ids,
|
|
99
|
+
...(typeof args.title === 'string' ? { title: args.title } : {}),
|
|
94
100
|
...(Array.isArray(args.tagsInput) ? { tags: nextTags } : {}),
|
|
95
101
|
...(Array.isArray(args.zettelBoxNames) ? { boxes: normalizeDistinctStrings(args.zettelBoxNames) } : {}),
|
|
96
102
|
...(typeof args.starred === 'boolean' ? { starred: args.starred } : {}),
|
|
@@ -163,6 +163,7 @@ export type NotePreview = {
|
|
|
163
163
|
};
|
|
164
164
|
export type NoteUpdateStateRow = {
|
|
165
165
|
id: string;
|
|
166
|
+
title: string | null;
|
|
166
167
|
tags: string | null;
|
|
167
168
|
zettel_boxes: string | null;
|
|
168
169
|
is_del: number | null;
|
|
@@ -176,6 +177,7 @@ export type UpdateNoteResult = {
|
|
|
176
177
|
id: string;
|
|
177
178
|
updated: boolean;
|
|
178
179
|
changed: string[];
|
|
180
|
+
title: string;
|
|
179
181
|
tags: string[];
|
|
180
182
|
zettelBoxIds: string[];
|
|
181
183
|
is_starred: boolean;
|
|
@@ -16,6 +16,7 @@ export declare function createNote(db: AbstractPowerSyncDatabase, input: {
|
|
|
16
16
|
}>;
|
|
17
17
|
export declare function updateNote(db: AbstractPowerSyncDatabase, input: {
|
|
18
18
|
id: string;
|
|
19
|
+
title?: string;
|
|
19
20
|
tags?: string[];
|
|
20
21
|
zettelBoxIds?: string[];
|
|
21
22
|
starred?: boolean;
|
|
@@ -124,14 +124,15 @@ export async function updateNote(db, input) {
|
|
|
124
124
|
if (!id) {
|
|
125
125
|
throw new DinoxError('Note id is required');
|
|
126
126
|
}
|
|
127
|
+
const shouldUpdateTitle = typeof input.title === 'string';
|
|
127
128
|
const shouldUpdateTags = Array.isArray(input.tags);
|
|
128
129
|
const shouldUpdateBoxes = Array.isArray(input.zettelBoxIds);
|
|
129
130
|
const shouldUpdateStarred = typeof input.starred === 'boolean';
|
|
130
|
-
if (!shouldUpdateTags && !shouldUpdateBoxes && !shouldUpdateStarred) {
|
|
131
|
-
throw new DinoxError('At least one field is required: tags, boxes, or starred');
|
|
131
|
+
if (!shouldUpdateTitle && !shouldUpdateTags && !shouldUpdateBoxes && !shouldUpdateStarred) {
|
|
132
|
+
throw new DinoxError('At least one field is required: title, tags, boxes, or starred');
|
|
132
133
|
}
|
|
133
134
|
const existing = await db.getOptional(`
|
|
134
|
-
SELECT id, tags, zettel_boxes, is_del, starred_at
|
|
135
|
+
SELECT id, title, tags, zettel_boxes, is_del, starred_at
|
|
135
136
|
FROM c_note
|
|
136
137
|
WHERE id = ?
|
|
137
138
|
`, [id]);
|
|
@@ -141,20 +142,24 @@ export async function updateNote(db, input) {
|
|
|
141
142
|
if (typeof existing.is_del === 'number' && existing.is_del !== 0) {
|
|
142
143
|
throw new DinoxError(`Note has been deleted: ${id}`);
|
|
143
144
|
}
|
|
145
|
+
const currentTitle = existing.title ?? '';
|
|
144
146
|
const currentTags = parseStoredTags(existing.tags);
|
|
145
147
|
const currentZettelBoxIds = parseZettelBoxIds(existing.zettel_boxes);
|
|
146
148
|
const currentStarred = existing.starred_at !== null;
|
|
149
|
+
const nextTitle = shouldUpdateTitle ? input.title : currentTitle;
|
|
147
150
|
const nextTags = shouldUpdateTags ? normalizeDistinctStrings(input.tags) : currentTags;
|
|
148
151
|
const nextZettelBoxIds = shouldUpdateBoxes ? normalizeDistinctStrings(input.zettelBoxIds) : currentZettelBoxIds;
|
|
149
152
|
const nextStarred = shouldUpdateStarred ? input.starred : currentStarred;
|
|
153
|
+
const titleChanged = shouldUpdateTitle && currentTitle !== nextTitle;
|
|
150
154
|
const tagsChanged = shouldUpdateTags && !sameTagSet(currentTags, nextTags);
|
|
151
155
|
const boxesChanged = shouldUpdateBoxes && !sameStringSet(currentZettelBoxIds, nextZettelBoxIds);
|
|
152
156
|
const starredChanged = shouldUpdateStarred && currentStarred !== nextStarred;
|
|
153
|
-
if (!tagsChanged && !boxesChanged && !starredChanged) {
|
|
157
|
+
if (!titleChanged && !tagsChanged && !boxesChanged && !starredChanged) {
|
|
154
158
|
return {
|
|
155
159
|
id,
|
|
156
160
|
updated: false,
|
|
157
161
|
changed: [],
|
|
162
|
+
title: currentTitle,
|
|
158
163
|
tags: currentTags,
|
|
159
164
|
zettelBoxIds: currentZettelBoxIds,
|
|
160
165
|
is_starred: currentStarred,
|
|
@@ -162,6 +167,10 @@ export async function updateNote(db, input) {
|
|
|
162
167
|
}
|
|
163
168
|
const setParts = [];
|
|
164
169
|
const params = [];
|
|
170
|
+
if (titleChanged) {
|
|
171
|
+
setParts.push('title = ?');
|
|
172
|
+
params.push(nextTitle);
|
|
173
|
+
}
|
|
165
174
|
if (tagsChanged) {
|
|
166
175
|
setParts.push('tags = ?');
|
|
167
176
|
params.push(JSON.stringify(nextTags));
|
|
@@ -190,10 +199,12 @@ export async function updateNote(db, input) {
|
|
|
190
199
|
id,
|
|
191
200
|
updated: true,
|
|
192
201
|
changed: [
|
|
202
|
+
...(titleChanged ? ['title'] : []),
|
|
193
203
|
...(tagsChanged ? ['tags'] : []),
|
|
194
204
|
...(boxesChanged ? ['boxes'] : []),
|
|
195
205
|
...(starredChanged ? ['is_starred'] : []),
|
|
196
206
|
],
|
|
207
|
+
title: nextTitle,
|
|
197
208
|
tags: nextTags,
|
|
198
209
|
zettelBoxIds: nextZettelBoxIds,
|
|
199
210
|
is_starred: nextStarred,
|