@dinoxx/dinox-cli 1.1.0 → 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 +48 -4
- 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/runtime.js +17 -6
- 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.d.ts +23 -1
- package/dist/commands/sync.js +100 -58
- 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 +10 -3
- package/dist/daemon/lifecycle.js +54 -11
- 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 +2 -0
- package/dist/powersync/exclusiveRuntime.js +28 -4
- package/dist/powersync/runtime.d.ts +4 -2
- package/dist/powersync/runtime.js +43 -5
- 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 +22 -3
- package/dist/powersync/syncGate.js +4 -1
- package/dist/powersync/syncPass.d.ts +2 -0
- package/dist/powersync/syncPass.js +15 -4
- package/dist/powersync/tokenIndex.d.ts +2 -0
- package/dist/powersync/tokenIndex.js +60 -18
- 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 +5 -3
- 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 +10 -5
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { SKILLS_INSTALL_AGENTS, SKILLS_INSTALLER_NAME, SKILLS_INSTALLER_VERSION, } from '../../constants/links.js';
|
|
6
|
+
import { RETIRED_SKILLS } from '../../skills/retired.js';
|
|
7
|
+
import { DinoxError } from '../../utils/errors.js';
|
|
8
|
+
import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, } from '../../utils/output.js';
|
|
9
|
+
import { registerCommandGroup } from '../descriptor/register.js';
|
|
10
|
+
import { FORMAT_OPTIONS } from '../schema/globalOptions.js';
|
|
11
|
+
const MAX_CAPTURED_INSTALLER_OUTPUT_BYTES = 64 * 1024;
|
|
12
|
+
function isRecord(value) {
|
|
13
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
function defaultLockFilePath() {
|
|
16
|
+
return path.join(homedir(), '.agents', '.skill-lock.json');
|
|
17
|
+
}
|
|
18
|
+
async function defaultReadLockFile(lockFilePath) {
|
|
19
|
+
return readFile(lockFilePath, 'utf8');
|
|
20
|
+
}
|
|
21
|
+
function absentScan(lockFilePath) {
|
|
22
|
+
const skills = RETIRED_SKILLS.map((skill) => ({
|
|
23
|
+
name: skill.name,
|
|
24
|
+
replacement: skill.replacement,
|
|
25
|
+
...('replacementNote' in skill ? { replacementNote: skill.replacementNote } : {}),
|
|
26
|
+
status: 'absent',
|
|
27
|
+
expected: {
|
|
28
|
+
source: skill.source,
|
|
29
|
+
skillFolderHash: skill.skillFolderHash,
|
|
30
|
+
},
|
|
31
|
+
observed: null,
|
|
32
|
+
mismatches: [],
|
|
33
|
+
}));
|
|
34
|
+
return {
|
|
35
|
+
lockFile: lockFilePath,
|
|
36
|
+
lockFound: false,
|
|
37
|
+
lockVersion: null,
|
|
38
|
+
summary: {
|
|
39
|
+
total: skills.length,
|
|
40
|
+
absent: skills.length,
|
|
41
|
+
safeToRemove: 0,
|
|
42
|
+
conflict: 0,
|
|
43
|
+
},
|
|
44
|
+
skills,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function invalidLockError(lockFilePath, message, cause) {
|
|
48
|
+
return new DinoxError(`Invalid skills installer lock at ${lockFilePath}: ${message}`, {
|
|
49
|
+
code: 'SKILLS_LOCK_INVALID',
|
|
50
|
+
cause,
|
|
51
|
+
details: { lockFile: lockFilePath },
|
|
52
|
+
suggestion: 'Inspect the lock file without deleting it, then rerun `dino skills doctor --format json`.',
|
|
53
|
+
recoverable: true,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
export async function scanRetiredSkillsLock(lockFilePath = defaultLockFilePath(), readLockFile = defaultReadLockFile) {
|
|
57
|
+
let raw;
|
|
58
|
+
try {
|
|
59
|
+
raw = await readLockFile(lockFilePath);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (error?.code === 'ENOENT') {
|
|
63
|
+
return absentScan(lockFilePath);
|
|
64
|
+
}
|
|
65
|
+
throw new DinoxError(`Failed to read skills installer lock at ${lockFilePath}`, {
|
|
66
|
+
code: 'SKILLS_LOCK_READ_FAILED',
|
|
67
|
+
cause: error,
|
|
68
|
+
details: { lockFile: lockFilePath },
|
|
69
|
+
suggestion: 'Check file permissions, then rerun `dino skills doctor --format json`.',
|
|
70
|
+
recoverable: true,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
let parsed;
|
|
74
|
+
try {
|
|
75
|
+
parsed = JSON.parse(raw);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
throw invalidLockError(lockFilePath, 'file is not valid JSON', error);
|
|
79
|
+
}
|
|
80
|
+
if (!isRecord(parsed) || !isRecord(parsed.skills)) {
|
|
81
|
+
throw invalidLockError(lockFilePath, 'expected an object with a skills map');
|
|
82
|
+
}
|
|
83
|
+
const entries = parsed.skills;
|
|
84
|
+
const skills = RETIRED_SKILLS.map((skill) => {
|
|
85
|
+
if (!Object.prototype.hasOwnProperty.call(entries, skill.name)) {
|
|
86
|
+
return {
|
|
87
|
+
name: skill.name,
|
|
88
|
+
replacement: skill.replacement,
|
|
89
|
+
...('replacementNote' in skill ? { replacementNote: skill.replacementNote } : {}),
|
|
90
|
+
status: 'absent',
|
|
91
|
+
expected: {
|
|
92
|
+
source: skill.source,
|
|
93
|
+
skillFolderHash: skill.skillFolderHash,
|
|
94
|
+
},
|
|
95
|
+
observed: null,
|
|
96
|
+
mismatches: [],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const rawEntry = entries[skill.name];
|
|
100
|
+
const entry = isRecord(rawEntry) ? rawEntry : {};
|
|
101
|
+
const observed = {
|
|
102
|
+
source: typeof entry.source === 'string' ? entry.source : null,
|
|
103
|
+
skillFolderHash: typeof entry.skillFolderHash === 'string' ? entry.skillFolderHash : null,
|
|
104
|
+
};
|
|
105
|
+
const mismatches = [];
|
|
106
|
+
if (observed.source !== skill.source)
|
|
107
|
+
mismatches.push('source');
|
|
108
|
+
if (observed.skillFolderHash !== skill.skillFolderHash)
|
|
109
|
+
mismatches.push('skillFolderHash');
|
|
110
|
+
return {
|
|
111
|
+
name: skill.name,
|
|
112
|
+
replacement: skill.replacement,
|
|
113
|
+
...('replacementNote' in skill ? { replacementNote: skill.replacementNote } : {}),
|
|
114
|
+
status: mismatches.length === 0 ? 'safe-to-remove' : 'conflict',
|
|
115
|
+
expected: {
|
|
116
|
+
source: skill.source,
|
|
117
|
+
skillFolderHash: skill.skillFolderHash,
|
|
118
|
+
},
|
|
119
|
+
observed,
|
|
120
|
+
mismatches,
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
return {
|
|
124
|
+
lockFile: lockFilePath,
|
|
125
|
+
lockFound: true,
|
|
126
|
+
lockVersion: typeof parsed.version === 'number' ? parsed.version : null,
|
|
127
|
+
summary: {
|
|
128
|
+
total: skills.length,
|
|
129
|
+
absent: skills.filter((skill) => skill.status === 'absent').length,
|
|
130
|
+
safeToRemove: skills.filter((skill) => skill.status === 'safe-to-remove').length,
|
|
131
|
+
conflict: skills.filter((skill) => skill.status === 'conflict').length,
|
|
132
|
+
},
|
|
133
|
+
skills,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function appendBoundedOutput(current, chunk) {
|
|
137
|
+
const currentBytes = Buffer.byteLength(current, 'utf8');
|
|
138
|
+
if (currentBytes >= MAX_CAPTURED_INSTALLER_OUTPUT_BYTES)
|
|
139
|
+
return current;
|
|
140
|
+
const remaining = MAX_CAPTURED_INSTALLER_OUTPUT_BYTES - currentBytes;
|
|
141
|
+
return current + chunk.subarray(0, remaining).toString('utf8');
|
|
142
|
+
}
|
|
143
|
+
export function resolveSkillsRemoveInvocation(skillNames, platform = process.platform) {
|
|
144
|
+
const args = [
|
|
145
|
+
'--yes',
|
|
146
|
+
`${SKILLS_INSTALLER_NAME}@${SKILLS_INSTALLER_VERSION}`,
|
|
147
|
+
'remove',
|
|
148
|
+
'-g',
|
|
149
|
+
...SKILLS_INSTALL_AGENTS.flatMap((agent) => ['-a', agent]),
|
|
150
|
+
'--skill',
|
|
151
|
+
...skillNames,
|
|
152
|
+
'-y',
|
|
153
|
+
];
|
|
154
|
+
const displayCommand = ['npx', ...args].join(' ');
|
|
155
|
+
if (platform === 'win32') {
|
|
156
|
+
return {
|
|
157
|
+
command: ['npx.cmd', ...args].join(' '),
|
|
158
|
+
args: [],
|
|
159
|
+
shell: true,
|
|
160
|
+
displayCommand,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
command: 'npx',
|
|
165
|
+
args,
|
|
166
|
+
shell: false,
|
|
167
|
+
displayCommand,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
export function runSkillsRemoval(skillNames) {
|
|
171
|
+
const invocation = resolveSkillsRemoveInvocation(skillNames);
|
|
172
|
+
return new Promise((resolve, reject) => {
|
|
173
|
+
let stdout = '';
|
|
174
|
+
let stderr = '';
|
|
175
|
+
const child = spawn(invocation.command, invocation.args, {
|
|
176
|
+
shell: invocation.shell,
|
|
177
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
178
|
+
});
|
|
179
|
+
child.stdout.on('data', (chunk) => {
|
|
180
|
+
stdout = appendBoundedOutput(stdout, chunk);
|
|
181
|
+
});
|
|
182
|
+
child.stderr.on('data', (chunk) => {
|
|
183
|
+
stderr = appendBoundedOutput(stderr, chunk);
|
|
184
|
+
});
|
|
185
|
+
child.once('error', (error) => {
|
|
186
|
+
reject(new DinoxError(`Failed to start the skills installer: ${error.message}`, {
|
|
187
|
+
code: 'SKILLS_MIGRATION_FAILED',
|
|
188
|
+
cause: error,
|
|
189
|
+
details: {
|
|
190
|
+
command: invocation.displayCommand,
|
|
191
|
+
},
|
|
192
|
+
suggestion: 'Verify that Node.js and npx are available, then rerun `dino skills migrate --confirm --format json`.',
|
|
193
|
+
recoverable: true,
|
|
194
|
+
}));
|
|
195
|
+
});
|
|
196
|
+
child.once('close', (exitCode) => {
|
|
197
|
+
resolve({ invocation, exitCode, stdout, stderr });
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
function resolveDependencies(overrides) {
|
|
202
|
+
return {
|
|
203
|
+
getLockFilePath: defaultLockFilePath,
|
|
204
|
+
readLockFile: defaultReadLockFile,
|
|
205
|
+
runRemoval: runSkillsRemoval,
|
|
206
|
+
...overrides,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function replacementLabel(skill) {
|
|
210
|
+
if (skill.replacement)
|
|
211
|
+
return skill.replacement;
|
|
212
|
+
return skill.replacementNote ?? 'no direct replacement';
|
|
213
|
+
}
|
|
214
|
+
function printScan(scan) {
|
|
215
|
+
printHumanReadableLine(`skills lock: ${scan.lockFile} (${scan.lockFound ? 'found' : 'not found'})`);
|
|
216
|
+
for (const skill of scan.skills) {
|
|
217
|
+
if (skill.status === 'conflict') {
|
|
218
|
+
printHumanReadableLine(`${skill.name}: conflict; replacement: ${replacementLabel(skill)}; observed source=${skill.observed?.source ?? 'missing'} hash=${skill.observed?.skillFolderHash ?? 'missing'}`);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
printHumanReadableLine(`${skill.name}: ${skill.status}; replacement: ${replacementLabel(skill)}`);
|
|
222
|
+
}
|
|
223
|
+
printHumanReadableLine(`summary: safe-to-remove=${scan.summary.safeToRemove} conflict=${scan.summary.conflict} absent=${scan.summary.absent}`);
|
|
224
|
+
}
|
|
225
|
+
function installerPayload(skillNames) {
|
|
226
|
+
return {
|
|
227
|
+
name: SKILLS_INSTALLER_NAME,
|
|
228
|
+
version: SKILLS_INSTALLER_VERSION,
|
|
229
|
+
scope: 'global',
|
|
230
|
+
agents: [...SKILLS_INSTALL_AGENTS],
|
|
231
|
+
command: skillNames.length > 0
|
|
232
|
+
? resolveSkillsRemoveInvocation(skillNames).displayCommand
|
|
233
|
+
: null,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function safeSkillNames(scan) {
|
|
237
|
+
return scan.skills
|
|
238
|
+
.filter((skill) => skill.status === 'safe-to-remove')
|
|
239
|
+
.map((skill) => skill.name);
|
|
240
|
+
}
|
|
241
|
+
function conflictSkillNames(scan) {
|
|
242
|
+
return scan.skills
|
|
243
|
+
.filter((skill) => skill.status === 'conflict')
|
|
244
|
+
.map((skill) => skill.name);
|
|
245
|
+
}
|
|
246
|
+
function scanStatus(scan, name) {
|
|
247
|
+
return scan.skills.find((skill) => skill.name === name)?.status ?? null;
|
|
248
|
+
}
|
|
249
|
+
async function rescanForVerification(dependencies, lockFilePath) {
|
|
250
|
+
try {
|
|
251
|
+
return await scanRetiredSkillsLock(lockFilePath, dependencies.readLockFile);
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
throw new DinoxError('Failed to verify the retired skills migration', {
|
|
255
|
+
code: 'SKILLS_MIGRATION_VERIFICATION_FAILED',
|
|
256
|
+
cause: error,
|
|
257
|
+
details: { lockFile: lockFilePath },
|
|
258
|
+
suggestion: 'Run `dino skills doctor --format json` and inspect the lock before retrying.',
|
|
259
|
+
recoverable: true,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
export function createSkillsCommandGroup(overrides = {}) {
|
|
264
|
+
const dependencies = resolveDependencies(overrides);
|
|
265
|
+
return {
|
|
266
|
+
name: 'skills',
|
|
267
|
+
description: 'Inspect and safely migrate installed Dinox agent skills',
|
|
268
|
+
commands: [
|
|
269
|
+
{
|
|
270
|
+
name: 'doctor',
|
|
271
|
+
description: 'Inspect the skills installer lock for safely removable retired Dinox skills',
|
|
272
|
+
schema: {
|
|
273
|
+
path: 'skills.doctor',
|
|
274
|
+
risk: 'read',
|
|
275
|
+
auth: 'none',
|
|
276
|
+
globalOptions: FORMAT_OPTIONS,
|
|
277
|
+
output: {
|
|
278
|
+
type: 'structured',
|
|
279
|
+
summary: 'Retired skill status from the global skills installer lock',
|
|
280
|
+
fields: ['lockFile', 'lockFound', 'lockVersion', 'summary', 'skills'],
|
|
281
|
+
},
|
|
282
|
+
examples: ['dino skills doctor', 'dino skills doctor --format json'],
|
|
283
|
+
},
|
|
284
|
+
action: async (_context) => {
|
|
285
|
+
const scan = await scanRetiredSkillsLock(dependencies.getLockFilePath(), dependencies.readLockFile);
|
|
286
|
+
if (getRequestedStructuredOutputFormat()) {
|
|
287
|
+
printSuccess(scan);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
printScan(scan);
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
name: 'migrate',
|
|
295
|
+
description: 'Preview or confirm safe removal of retired Dinox skills',
|
|
296
|
+
options: [
|
|
297
|
+
{
|
|
298
|
+
flags: '--dry-run',
|
|
299
|
+
description: 'Preview safe removals without changing installed skills (default)',
|
|
300
|
+
type: 'boolean',
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
flags: '--confirm',
|
|
304
|
+
description: 'Execute the reviewed migration using the pinned public skills installer',
|
|
305
|
+
type: 'boolean',
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
schema: {
|
|
309
|
+
path: 'skills.migrate',
|
|
310
|
+
risk: 'high-risk-write',
|
|
311
|
+
auth: 'none',
|
|
312
|
+
globalOptions: FORMAT_OPTIONS,
|
|
313
|
+
output: {
|
|
314
|
+
type: 'structured',
|
|
315
|
+
summary: 'Retired skill migration plan or verified removal result',
|
|
316
|
+
fields: [
|
|
317
|
+
'mode',
|
|
318
|
+
'confirmed',
|
|
319
|
+
'installer',
|
|
320
|
+
'before',
|
|
321
|
+
'plannedRemoval',
|
|
322
|
+
'conflicts',
|
|
323
|
+
'removed',
|
|
324
|
+
'after',
|
|
325
|
+
'verified',
|
|
326
|
+
],
|
|
327
|
+
},
|
|
328
|
+
examples: [
|
|
329
|
+
'dino skills migrate --dry-run --format json',
|
|
330
|
+
'dino skills migrate --confirm --format json',
|
|
331
|
+
],
|
|
332
|
+
},
|
|
333
|
+
action: async (context) => {
|
|
334
|
+
const explicitDryRun = Boolean(context.options.dryRun);
|
|
335
|
+
const confirmed = Boolean(context.options.confirm);
|
|
336
|
+
if (explicitDryRun && confirmed) {
|
|
337
|
+
throw new DinoxError('--dry-run cannot be used together with --confirm', {
|
|
338
|
+
code: 'USAGE_ERROR',
|
|
339
|
+
suggestion: 'Run `dino skills migrate --dry-run --format json`, review the plan, then rerun with `--confirm`.',
|
|
340
|
+
recoverable: true,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
const lockFilePath = dependencies.getLockFilePath();
|
|
344
|
+
const before = await scanRetiredSkillsLock(lockFilePath, dependencies.readLockFile);
|
|
345
|
+
const plannedRemoval = safeSkillNames(before);
|
|
346
|
+
const conflicts = conflictSkillNames(before);
|
|
347
|
+
const installer = installerPayload(plannedRemoval);
|
|
348
|
+
if (!confirmed) {
|
|
349
|
+
const payload = {
|
|
350
|
+
mode: 'dry-run',
|
|
351
|
+
confirmed: false,
|
|
352
|
+
installer,
|
|
353
|
+
before,
|
|
354
|
+
plannedRemoval,
|
|
355
|
+
conflicts,
|
|
356
|
+
removed: [],
|
|
357
|
+
after: null,
|
|
358
|
+
verified: false,
|
|
359
|
+
};
|
|
360
|
+
if (getRequestedStructuredOutputFormat()) {
|
|
361
|
+
printSuccess(payload);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
printHumanReadableLine('mode: dry-run');
|
|
365
|
+
printScan(before);
|
|
366
|
+
printHumanReadableLine(`would remove: ${plannedRemoval.join(', ') || 'none'}`);
|
|
367
|
+
printHumanReadableLine('no changes made; use --confirm only after reviewing this plan');
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (plannedRemoval.length === 0) {
|
|
371
|
+
const after = await rescanForVerification(dependencies, lockFilePath);
|
|
372
|
+
const payload = {
|
|
373
|
+
mode: 'migrate',
|
|
374
|
+
confirmed: true,
|
|
375
|
+
installer,
|
|
376
|
+
before,
|
|
377
|
+
plannedRemoval,
|
|
378
|
+
conflicts,
|
|
379
|
+
removed: [],
|
|
380
|
+
after,
|
|
381
|
+
verified: true,
|
|
382
|
+
};
|
|
383
|
+
if (getRequestedStructuredOutputFormat()) {
|
|
384
|
+
printSuccess(payload);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
printHumanReadableLine('mode: migrate');
|
|
388
|
+
printScan(after);
|
|
389
|
+
printHumanReadableLine('removed: none');
|
|
390
|
+
printHumanReadableLine('verified: yes');
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
const preExecution = await scanRetiredSkillsLock(lockFilePath, dependencies.readLockFile);
|
|
394
|
+
const changedCandidates = plannedRemoval.filter((name) => scanStatus(preExecution, name) !== 'safe-to-remove');
|
|
395
|
+
if (changedCandidates.length > 0) {
|
|
396
|
+
throw new DinoxError('The skills installer lock changed before migration execution', {
|
|
397
|
+
code: 'SKILLS_MIGRATION_PRECONDITION_FAILED',
|
|
398
|
+
exitCode: 5,
|
|
399
|
+
details: {
|
|
400
|
+
lockFile: lockFilePath,
|
|
401
|
+
changedCandidates,
|
|
402
|
+
before,
|
|
403
|
+
preExecution,
|
|
404
|
+
},
|
|
405
|
+
suggestion: 'Run `dino skills doctor --format json`, review the new status, then retry the migration.',
|
|
406
|
+
recoverable: true,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
const execution = await dependencies.runRemoval(plannedRemoval);
|
|
410
|
+
const after = await rescanForVerification(dependencies, lockFilePath);
|
|
411
|
+
if (execution.exitCode !== 0) {
|
|
412
|
+
throw new DinoxError(`The skills installer exited with code ${execution.exitCode ?? 'unknown'}`, {
|
|
413
|
+
code: 'SKILLS_MIGRATION_FAILED',
|
|
414
|
+
details: {
|
|
415
|
+
command: execution.invocation.displayCommand,
|
|
416
|
+
exitCode: execution.exitCode,
|
|
417
|
+
...(execution.stdout.trim() ? { stdout: execution.stdout.trim() } : {}),
|
|
418
|
+
...(execution.stderr.trim() ? { stderr: execution.stderr.trim() } : {}),
|
|
419
|
+
after,
|
|
420
|
+
},
|
|
421
|
+
suggestion: 'Run `dino skills doctor --format json` before deciding whether to retry.',
|
|
422
|
+
recoverable: true,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
const remainingCandidates = plannedRemoval.filter((name) => scanStatus(after, name) !== 'absent');
|
|
426
|
+
const unexpectedlyRemovedConflicts = conflicts.filter((name) => scanStatus(after, name) === 'absent');
|
|
427
|
+
if (remainingCandidates.length > 0 || unexpectedlyRemovedConflicts.length > 0) {
|
|
428
|
+
throw new DinoxError('Retired skills migration verification failed', {
|
|
429
|
+
code: 'SKILLS_MIGRATION_VERIFICATION_FAILED',
|
|
430
|
+
details: {
|
|
431
|
+
command: execution.invocation.displayCommand,
|
|
432
|
+
remainingCandidates,
|
|
433
|
+
unexpectedlyRemovedConflicts,
|
|
434
|
+
before,
|
|
435
|
+
after,
|
|
436
|
+
},
|
|
437
|
+
suggestion: 'Run `dino skills doctor --format json` and inspect every conflict before taking further action.',
|
|
438
|
+
recoverable: true,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
const payload = {
|
|
442
|
+
mode: 'migrate',
|
|
443
|
+
confirmed: true,
|
|
444
|
+
installer,
|
|
445
|
+
before,
|
|
446
|
+
plannedRemoval,
|
|
447
|
+
conflicts,
|
|
448
|
+
removed: [...plannedRemoval],
|
|
449
|
+
after,
|
|
450
|
+
verified: true,
|
|
451
|
+
};
|
|
452
|
+
if (getRequestedStructuredOutputFormat()) {
|
|
453
|
+
printSuccess(payload);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
printHumanReadableLine('mode: migrate');
|
|
457
|
+
printHumanReadableLine(`removed: ${plannedRemoval.join(', ')}`);
|
|
458
|
+
printHumanReadableLine(`conflicts preserved: ${conflicts.join(', ') || 'none'}`);
|
|
459
|
+
printHumanReadableLine('verified: yes');
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
],
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
export const skillsCommandGroup = createSkillsCommandGroup();
|
|
466
|
+
export function registerSkillsCommands(program, overrides = {}) {
|
|
467
|
+
registerCommandGroup(program, createSkillsCommandGroup(overrides));
|
|
468
|
+
}
|
|
@@ -53,7 +53,7 @@ async function runStorageUpload(context) {
|
|
|
53
53
|
storageUrl: plan.storageUrl,
|
|
54
54
|
selectionSource: plan.selectionSource,
|
|
55
55
|
overwrite: options.overwrite === true,
|
|
56
|
-
}));
|
|
56
|
+
}), runtime);
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
59
59
|
const result = await uploadFileToStorage(db, {
|
|
@@ -117,7 +117,7 @@ async function runStorageTest(context) {
|
|
|
117
117
|
storageUrl: plan.storageUrl,
|
|
118
118
|
sizeBytes: plan.sizeBytes,
|
|
119
119
|
selectionSource: plan.selectionSource,
|
|
120
|
-
}));
|
|
120
|
+
}), runtime);
|
|
121
121
|
return;
|
|
122
122
|
}
|
|
123
123
|
const result = await testStorageUpload(db, {
|
package/dist/commands/sync.d.ts
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import
|
|
2
|
+
import { loadConfig } from '../config/store.js';
|
|
3
|
+
import { resolveConfig } from '../config/resolve.js';
|
|
4
|
+
import { hydrateResolvedAuthIdentity } from '../auth/userInfo.js';
|
|
5
|
+
import { withExclusivePowerSync } from '../powersync/exclusiveRuntime.js';
|
|
6
|
+
import type { CommandDescriptor, CommandExecutionContext } from './descriptor/types.js';
|
|
7
|
+
import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess } from '../utils/output.js';
|
|
8
|
+
import { getStatusSnapshot, waitForLastSyncedAfter } from '../powersync/runtime.js';
|
|
9
|
+
import { runSingleSyncPass } from '../powersync/syncPass.js';
|
|
10
|
+
type SyncCommandDependencies = {
|
|
11
|
+
loadConfig: typeof loadConfig;
|
|
12
|
+
resolveConfig: typeof resolveConfig;
|
|
13
|
+
hydrateResolvedAuthIdentity: typeof hydrateResolvedAuthIdentity;
|
|
14
|
+
withExclusivePowerSync: typeof withExclusivePowerSync;
|
|
15
|
+
getRequestedStructuredOutputFormat: typeof getRequestedStructuredOutputFormat;
|
|
16
|
+
getStatusSnapshot: typeof getStatusSnapshot;
|
|
17
|
+
waitForLastSyncedAfter: typeof waitForLastSyncedAfter;
|
|
18
|
+
runSingleSyncPass: typeof runSingleSyncPass;
|
|
19
|
+
printHumanReadableLine: typeof printHumanReadableLine;
|
|
20
|
+
printSuccess: typeof printSuccess;
|
|
21
|
+
now: () => number;
|
|
22
|
+
};
|
|
23
|
+
export declare function executeSyncCommand(context: CommandExecutionContext, overrides?: Partial<SyncCommandDependencies>): Promise<void>;
|
|
3
24
|
export declare const syncCommandDescriptor: CommandDescriptor;
|
|
4
25
|
export declare function registerSyncCommand(program: Command): void;
|
|
26
|
+
export {};
|