borgmcp 2.6.1 → 2.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/assimilate-cmd.d.ts.map +1 -1
- package/dist/assimilate-cmd.js +15 -1
- package/dist/assimilate-cmd.js.map +1 -1
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +5 -2
- package/dist/cli-help.js.map +1 -1
- package/dist/regen-format.d.ts +1 -1
- package/dist/regen-format.js +2 -2
- package/dist/regen-format.js.map +1 -1
- package/dist/repository-cube-init.d.ts +3 -1
- package/dist/repository-cube-init.d.ts.map +1 -1
- package/dist/repository-cube-init.js +43 -25
- package/dist/repository-cube-init.js.map +1 -1
- package/dist/repository-identity.d.ts +1 -1
- package/dist/repository-identity.d.ts.map +1 -1
- package/dist/repository-identity.js +2 -1
- package/dist/repository-identity.js.map +1 -1
- package/docs/EXTRACTION_PROVENANCE.md +3 -3
- package/docs/LOCAL_SERVER.md +11 -9
- package/docs/RELEASING.md +11 -1
- package/package.json +1 -1
- package/src/assimilate-cmd.ts +14 -1
- package/src/cli-help.ts +7 -2
- package/src/regen-format.ts +2 -2
- package/src/repository-cube-init.ts +51 -26
- package/src/repository-identity.ts +2 -2
|
@@ -33,6 +33,17 @@ export interface RepositoryCubeCreation {
|
|
|
33
33
|
cube: RepositoryCubeDetail;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
type NewCubeTemplate = Exclude<CubeTemplate, 'default'>;
|
|
37
|
+
|
|
38
|
+
const NEW_CUBE_TEMPLATE_NAMES: readonly NewCubeTemplate[] =
|
|
39
|
+
NEW_CUBE_TEMPLATE_PRESENTATIONS.map(({ name }) => name);
|
|
40
|
+
const NEW_CUBE_TEMPLATE_OPTIONS = NEW_CUBE_TEMPLATE_NAMES.join('|');
|
|
41
|
+
const NEW_CUBE_TEMPLATE_LIST = NEW_CUBE_TEMPLATE_NAMES.join(', ');
|
|
42
|
+
|
|
43
|
+
function parseNewCubeTemplate(value: string): NewCubeTemplate | undefined {
|
|
44
|
+
return NEW_CUBE_TEMPLATE_PRESENTATIONS.find(({ name }) => name === value)?.name;
|
|
45
|
+
}
|
|
46
|
+
|
|
36
47
|
export type RepositoryCubeResolution = ResolveRepositoryCubeResponse;
|
|
37
48
|
|
|
38
49
|
export interface RepositoryCubeInitFlags {
|
|
@@ -63,7 +74,7 @@ export interface RepositoryCubeInitDeps {
|
|
|
63
74
|
name: string;
|
|
64
75
|
workingRepoName: string;
|
|
65
76
|
repository: CreateCubeRepository;
|
|
66
|
-
template:
|
|
77
|
+
template: NewCubeTemplate;
|
|
67
78
|
}): Promise<RepositoryCubeCreation>;
|
|
68
79
|
}
|
|
69
80
|
|
|
@@ -158,7 +169,7 @@ function renderResult(
|
|
|
158
169
|
`Next: borg assimilate --host ${shellEscape(input.serverOrigin)}`,
|
|
159
170
|
);
|
|
160
171
|
} else {
|
|
161
|
-
lines.push('Continuing with role and seat setup
|
|
172
|
+
lines.push('Continuing with role and seat setup…');
|
|
162
173
|
}
|
|
163
174
|
resultWriter(`${lines.join('\n')}\n`);
|
|
164
175
|
}
|
|
@@ -176,6 +187,7 @@ export async function initializeRepositoryCube(input: {
|
|
|
176
187
|
input.flags.template !== undefined || input.flags.noTemplate === true || input.flags.yes === true;
|
|
177
188
|
const creationOptionsUnused = association !== null && creationOptionsRequested;
|
|
178
189
|
if (association) {
|
|
190
|
+
deps.write(`Checking for this repository's cube on ${input.serverOrigin}…\n`);
|
|
179
191
|
const cube = await deps.getCube(association.cubeId);
|
|
180
192
|
const response: CreateCubeResponse = {
|
|
181
193
|
result: 'resolved',
|
|
@@ -241,6 +253,7 @@ export async function initializeRepositoryCube(input: {
|
|
|
241
253
|
return { kind: 'success', creation: { response, cube }, existing: true };
|
|
242
254
|
};
|
|
243
255
|
|
|
256
|
+
deps.write(`Checking for this repository's cube on ${input.serverOrigin}…\n`);
|
|
244
257
|
const serverAssociation = await deps.resolveAssociation(repository, input.context.derivedName);
|
|
245
258
|
if (serverAssociation.result === 'resolved') {
|
|
246
259
|
return saveResolvedAssociation(serverAssociation, {
|
|
@@ -263,9 +276,14 @@ export async function initializeRepositoryCube(input: {
|
|
|
263
276
|
return { kind: 'stop', code: 1 };
|
|
264
277
|
}
|
|
265
278
|
if (!deps.isTTY() || input.flags.yes) {
|
|
279
|
+
const retryCommand = input.mode === 'cube-init'
|
|
280
|
+
? `borg server cube init --host ${shellEscape(input.serverOrigin)}`
|
|
281
|
+
: `borg assimilate --host ${shellEscape(input.serverOrigin)}`;
|
|
266
282
|
deps.write(
|
|
267
|
-
|
|
268
|
-
'
|
|
283
|
+
`Found existing cube '${matches[0].name}' on ${input.serverOrigin}.\n` +
|
|
284
|
+
'Linking a repository to an existing cube requires one interactive confirmation.\n' +
|
|
285
|
+
`Run ${retryCommand} --cube-name ${shellEscape(matches[0].name)} once in an interactive terminal to link it; scripted runs work from then on.\n` +
|
|
286
|
+
'No cube, repository binding, or drone was created.\n',
|
|
269
287
|
);
|
|
270
288
|
return { kind: 'stop', code: 1 };
|
|
271
289
|
}
|
|
@@ -276,16 +294,17 @@ export async function initializeRepositoryCube(input: {
|
|
|
276
294
|
` server: ${input.serverOrigin}\n`,
|
|
277
295
|
);
|
|
278
296
|
while (true) {
|
|
279
|
-
const answer = await ask(deps, 'Link this repository to that cube? [
|
|
297
|
+
const answer = await ask(deps, 'Link this repository to that cube? [Y/n]: ', 'adoption');
|
|
280
298
|
if ('stop' in answer) return { kind: 'stop', code: answer.stop };
|
|
281
299
|
const confirmation = answer.value.trim().toLowerCase();
|
|
282
|
-
if (confirmation === 'y' || confirmation === 'yes') break;
|
|
283
|
-
if (confirmation === '
|
|
300
|
+
if (confirmation === '' || confirmation === 'y' || confirmation === 'yes') break;
|
|
301
|
+
if (confirmation === 'n' || confirmation === 'no') {
|
|
284
302
|
deps.write('No cube, repository binding, or drone was created.\n');
|
|
285
303
|
return { kind: 'stop', code: 0 };
|
|
286
304
|
}
|
|
287
305
|
deps.write('Enter y or n.\n');
|
|
288
306
|
}
|
|
307
|
+
deps.write(`Linking this repository to cube '${matches[0].name}'…\n`);
|
|
289
308
|
const associated = await deps.associateCube({
|
|
290
309
|
cubeId: matches[0].id,
|
|
291
310
|
workingRepoName: input.context.derivedName,
|
|
@@ -299,7 +318,11 @@ export async function initializeRepositoryCube(input: {
|
|
|
299
318
|
if (confirmed.result !== 'resolved' || confirmed.cube_id !== associated.cube_id) {
|
|
300
319
|
throw new RepositoryAssociationConfirmationError();
|
|
301
320
|
}
|
|
302
|
-
return await saveResolvedAssociation(confirmed, {
|
|
321
|
+
return await saveResolvedAssociation(confirmed, {
|
|
322
|
+
adopted: true,
|
|
323
|
+
creationOptionsUnused: input.flags.template !== undefined ||
|
|
324
|
+
input.flags.noTemplate === true,
|
|
325
|
+
});
|
|
303
326
|
} catch (error) {
|
|
304
327
|
if (error instanceof RepositoryAssociationSaveError) throw error;
|
|
305
328
|
throw new RepositoryAssociationConfirmationError();
|
|
@@ -320,16 +343,19 @@ export async function initializeRepositoryCube(input: {
|
|
|
320
343
|
}
|
|
321
344
|
|
|
322
345
|
if (input.flags.noTemplate) {
|
|
323
|
-
deps.write(
|
|
346
|
+
deps.write(`--no-template is not supported for repository cube creation. Use --template ${NEW_CUBE_TEMPLATE_OPTIONS}.\n`);
|
|
324
347
|
return { kind: 'stop', code: 1 };
|
|
325
348
|
}
|
|
326
|
-
|
|
349
|
+
const requestedTemplate = input.flags.template === undefined
|
|
350
|
+
? undefined
|
|
351
|
+
: parseNewCubeTemplate(input.flags.template);
|
|
352
|
+
if (input.flags.template !== undefined && requestedTemplate === undefined) {
|
|
327
353
|
const safe = input.flags.template.replace(/[\u0000-\u001f\u007f]/g, '?').slice(0, 120);
|
|
328
|
-
deps.write(`Unknown template '${safe}'.
|
|
354
|
+
deps.write(`Unknown template '${safe}'. Available templates: ${NEW_CUBE_TEMPLATE_LIST}.\n`);
|
|
329
355
|
return { kind: 'stop', code: 1 };
|
|
330
356
|
}
|
|
331
357
|
if (!deps.isTTY() && !input.flags.yes && (!input.flags.cubeName || !input.flags.template)) {
|
|
332
|
-
deps.write(
|
|
358
|
+
deps.write(`Non-interactive cube creation requires --cube-name <name> and --template ${NEW_CUBE_TEMPLATE_OPTIONS}, or --yes to use repository defaults.\n`);
|
|
333
359
|
return { kind: 'stop', code: 1 };
|
|
334
360
|
}
|
|
335
361
|
|
|
@@ -358,7 +384,7 @@ export async function initializeRepositoryCube(input: {
|
|
|
358
384
|
if (editedNameAdoption) return editedNameAdoption;
|
|
359
385
|
}
|
|
360
386
|
|
|
361
|
-
let template =
|
|
387
|
+
let template = requestedTemplate;
|
|
362
388
|
if (!template && deps.isTTY() && !input.flags.yes) {
|
|
363
389
|
let menu = 'Choose a template:\n';
|
|
364
390
|
for (let i = 0; i < NEW_CUBE_TEMPLATE_PRESENTATIONS.length; i += 1) {
|
|
@@ -372,24 +398,23 @@ export async function initializeRepositoryCube(input: {
|
|
|
372
398
|
const answer = await ask(deps, 'Template [1]: ');
|
|
373
399
|
if ('stop' in answer) return { kind: 'stop', code: answer.stop };
|
|
374
400
|
const selected = answer.value.trim();
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
401
|
+
const selectedIndex = selected === ''
|
|
402
|
+
? 0
|
|
403
|
+
: /^[1-9]\d*$/.test(selected) ? Number(selected) - 1 : -1;
|
|
404
|
+
const selectedPresentation = NEW_CUBE_TEMPLATE_PRESENTATIONS[selectedIndex];
|
|
405
|
+
if (selectedPresentation) template = selectedPresentation.name;
|
|
406
|
+
else deps.write(`Choose 1-${NEW_CUBE_TEMPLATE_PRESENTATIONS.length}.\n`);
|
|
378
407
|
}
|
|
379
408
|
}
|
|
380
|
-
template ??=
|
|
409
|
+
template ??= NEW_CUBE_TEMPLATE_PRESENTATIONS[0].name;
|
|
381
410
|
|
|
382
411
|
if (deps.isTTY() && !input.flags.yes) {
|
|
383
|
-
deps.write(
|
|
384
|
-
`Create this cube?\n` +
|
|
385
|
-
` Name: ${name}\n` +
|
|
386
|
-
` Template: ${presentation(template).label}\n` +
|
|
387
|
-
` Repository: ${input.context.root}\n` +
|
|
388
|
-
` Server: ${input.serverOrigin}\n`,
|
|
389
|
-
);
|
|
390
412
|
let confirmed = false;
|
|
391
413
|
while (!confirmed) {
|
|
392
|
-
const answer = await ask(
|
|
414
|
+
const answer = await ask(
|
|
415
|
+
deps,
|
|
416
|
+
`Create cube '${name}' (${presentation(template).label}) on ${input.serverOrigin}? [Y/n]: `,
|
|
417
|
+
);
|
|
393
418
|
if ('stop' in answer) return { kind: 'stop', code: answer.stop };
|
|
394
419
|
const confirmation = answer.value.trim().toLowerCase();
|
|
395
420
|
if (confirmation === '' || confirmation === 'y' || confirmation === 'yes') {
|
|
@@ -404,7 +429,7 @@ export async function initializeRepositoryCube(input: {
|
|
|
404
429
|
}
|
|
405
430
|
}
|
|
406
431
|
|
|
407
|
-
deps.write(
|
|
432
|
+
deps.write(`Creating cube '${name}'…\n`);
|
|
408
433
|
const workingRepoName = input.context.derivedName;
|
|
409
434
|
let creation: RepositoryCubeCreation;
|
|
410
435
|
try {
|
|
@@ -2,7 +2,7 @@ import { createHmac, randomBytes, randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { spawnSync } from 'node:child_process';
|
|
3
3
|
import { basename, isAbsolute, join } from 'node:path';
|
|
4
4
|
import { realpath } from 'node:fs/promises';
|
|
5
|
-
import type
|
|
5
|
+
import { CUBE_TEMPLATES, type CreateCubeRepository, type CubeTemplate } from 'borgmcp-shared/protocol';
|
|
6
6
|
import { canonicalizeWorkingRepoIdentity } from './working-repo.js';
|
|
7
7
|
import { normalizeCubeName } from './cube-name.js';
|
|
8
8
|
import { borgConfigRoot, ensurePrivateBorgConfigRoot } from './private-root.js';
|
|
@@ -118,7 +118,7 @@ function parseState(raw: string | null): RepositoryIdentityState {
|
|
|
118
118
|
!DISPLAY_NAME_RE.test(value.name) ||
|
|
119
119
|
typeof value.workingRepoName !== 'string' || Buffer.byteLength(value.workingRepoName, 'utf8') > 120 ||
|
|
120
120
|
!DISPLAY_NAME_RE.test(value.workingRepoName) ||
|
|
121
|
-
(
|
|
121
|
+
!CUBE_TEMPLATES.some((template) => template === value.template)
|
|
122
122
|
) {
|
|
123
123
|
throw new Error('Borg repository identity store is malformed or unsupported');
|
|
124
124
|
}
|