@aopslabs/aops 0.3.21 → 0.3.23
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/agent-assets-release/agent-assets/core/{47516047e20f49d9df0f9c63f3ce98e96ee5b788baf13edf48613a2667edca3a → 647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09}/files/user-guides/docman.md +45 -0
- package/agent-assets-release/agent-assets/core/{47516047e20f49d9df0f9c63f3ce98e96ee5b788baf13edf48613a2667edca3a → 647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09}/manifest.json +5 -5
- package/agent-assets-release/agent-assets/inventory.json +5 -5
- package/agent-assets-release/agent-assets/projection.json +1 -1
- package/agent-assets-release/agent-assets-release.json +36 -36
- package/agent-assets-release/agent-assets-release.sigstore.json +29 -28
- package/assets/agent-assets/core/user-guides/docman.md +45 -0
- package/dist/commands/doc.js +561 -32
- package/dist/commands/pm/projectman.js +14 -5
- package/dist/lib/agent-assets/native-fs.js +2 -2
- package/native/bin/darwin-arm64/aops-agent-assets-fs +0 -0
- package/native/bin/darwin-x64/aops-agent-assets-fs +0 -0
- package/native/bin/win32-x64/aops-agent-assets-fs.exe +0 -0
- package/native/manifest.json +4 -4
- package/native/qualifications/win32-x64.json +2 -2
- package/native/tui/darwin-arm64/aops-tui +0 -0
- package/native/tui/win32-x64/aops-tui.exe +0 -0
- package/package.json +4 -4
- /package/agent-assets-release/agent-assets/core/{47516047e20f49d9df0f9c63f3ce98e96ee5b788baf13edf48613a2667edca3a → 647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09}/files/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{47516047e20f49d9df0f9c63f3ce98e96ee5b788baf13edf48613a2667edca3a → 647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09}/files/references/agentspace/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{47516047e20f49d9df0f9c63f3ce98e96ee5b788baf13edf48613a2667edca3a → 647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09}/files/references/aops-cli-core/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{47516047e20f49d9df0f9c63f3ce98e96ee5b788baf13edf48613a2667edca3a → 647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09}/files/references/chatv3/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{47516047e20f49d9df0f9c63f3ce98e96ee5b788baf13edf48613a2667edca3a → 647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09}/files/references/collaborative-work/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{47516047e20f49d9df0f9c63f3ce98e96ee5b788baf13edf48613a2667edca3a → 647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09}/files/references/discuss/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{47516047e20f49d9df0f9c63f3ce98e96ee5b788baf13edf48613a2667edca3a → 647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09}/files/references/docman/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{47516047e20f49d9df0f9c63f3ce98e96ee5b788baf13edf48613a2667edca3a → 647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09}/files/references/mission/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{47516047e20f49d9df0f9c63f3ce98e96ee5b788baf13edf48613a2667edca3a → 647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09}/files/references/projectman/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{47516047e20f49d9df0f9c63f3ce98e96ee5b788baf13edf48613a2667edca3a → 647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09}/files/references/sys/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{47516047e20f49d9df0f9c63f3ce98e96ee5b788baf13edf48613a2667edca3a → 647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09}/files/references/view/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{47516047e20f49d9df0f9c63f3ce98e96ee5b788baf13edf48613a2667edca3a → 647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09}/files/references/working-disciplines/SKILL.md +0 -0
- /package/agent-assets-release/agent-assets/core/{47516047e20f49d9df0f9c63f3ce98e96ee5b788baf13edf48613a2667edca3a → 647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09}/files/user-guides/agent-assets.md +0 -0
- /package/agent-assets-release/agent-assets/core/{47516047e20f49d9df0f9c63f3ce98e96ee5b788baf13edf48613a2667edca3a → 647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09}/files/user-guides/agentspace.md +0 -0
- /package/agent-assets-release/agent-assets/core/{47516047e20f49d9df0f9c63f3ce98e96ee5b788baf13edf48613a2667edca3a → 647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09}/files/user-guides/aops-cli.md +0 -0
- /package/agent-assets-release/agent-assets/core/{47516047e20f49d9df0f9c63f3ce98e96ee5b788baf13edf48613a2667edca3a → 647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09}/files/user-guides/aops-system.md +0 -0
- /package/agent-assets-release/agent-assets/core/{47516047e20f49d9df0f9c63f3ce98e96ee5b788baf13edf48613a2667edca3a → 647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09}/files/user-guides/chatv3.md +0 -0
- /package/agent-assets-release/agent-assets/core/{47516047e20f49d9df0f9c63f3ce98e96ee5b788baf13edf48613a2667edca3a → 647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09}/files/user-guides/projectman.md +0 -0
- /package/agent-assets-release/agent-assets/core/{47516047e20f49d9df0f9c63f3ce98e96ee5b788baf13edf48613a2667edca3a → 647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09}/files/user-guides/sys.md +0 -0
- /package/agent-assets-release/agent-assets/core/{47516047e20f49d9df0f9c63f3ce98e96ee5b788baf13edf48613a2667edca3a → 647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09}/files/user-guides/working-disciplines.md +0 -0
package/dist/commands/doc.js
CHANGED
|
@@ -13,6 +13,7 @@ import { probeCliRuntimeMode } from '../utils/api.js';
|
|
|
13
13
|
import { parseFrontmatterDocument } from '../utils/memory-workspace.js';
|
|
14
14
|
const DEFAULT_DOC_BUILD_TIMEOUT_MS = 120_000;
|
|
15
15
|
const DOC_MIRROR_CONTRACT = 'doc-mirror-v2';
|
|
16
|
+
export const DOC_SOURCE_MIRROR_CONTRACT = 'doc-mirror-v3';
|
|
16
17
|
function collectRepeatedOption(value, previous = []) {
|
|
17
18
|
const normalized = normalizeNonEmpty(value);
|
|
18
19
|
return normalized ? [...previous, normalized] : previous;
|
|
@@ -288,6 +289,210 @@ function materializedContent(result) {
|
|
|
288
289
|
function sha256Hex(content) {
|
|
289
290
|
return createHash('sha256').update(content).digest('hex');
|
|
290
291
|
}
|
|
292
|
+
function sha256ContentHash(content) {
|
|
293
|
+
return `sha256:${sha256Hex(content)}`;
|
|
294
|
+
}
|
|
295
|
+
export function validateSourceFileExtension(value, label = 'file extension') {
|
|
296
|
+
const extension = normalizeNonEmpty(value);
|
|
297
|
+
if (!extension || !/^[a-z0-9][a-z0-9_-]{0,31}$/.test(extension)) {
|
|
298
|
+
throw new Error(`${label} must be explicit lowercase ASCII without a leading dot (for example: cs, txt, or py).`);
|
|
299
|
+
}
|
|
300
|
+
return extension;
|
|
301
|
+
}
|
|
302
|
+
export function resolveDocMirrorVersionProfile(version, documentVersionId = '<unknown>') {
|
|
303
|
+
const rawContentMode = normalizeNonEmpty(version.contentMode)?.toLowerCase();
|
|
304
|
+
if (rawContentMode && rawContentMode !== 'structured' && rawContentMode !== 'text-file') {
|
|
305
|
+
throw new Error(`Docman version ${documentVersionId} has unsupported contentMode ${rawContentMode}.`);
|
|
306
|
+
}
|
|
307
|
+
const contentMode = rawContentMode === 'text-file' ? 'text-file' : 'structured';
|
|
308
|
+
const persistedExtension = normalizeNonEmpty(version.fileExtension);
|
|
309
|
+
if (contentMode === 'text-file') {
|
|
310
|
+
return {
|
|
311
|
+
contentMode,
|
|
312
|
+
fileExtension: validateSourceFileExtension(persistedExtension, `Document version ${documentVersionId} fileExtension`),
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
const fileExtension = persistedExtension
|
|
316
|
+
? validateSourceFileExtension(persistedExtension, `Document version ${documentVersionId} fileExtension`)
|
|
317
|
+
: 'md';
|
|
318
|
+
if (fileExtension !== 'md') {
|
|
319
|
+
throw new Error(`Structured Docman version ${documentVersionId} declares persisted fileExtension ${fileExtension}; ` +
|
|
320
|
+
'doc-mirror-v2 only supports md until an explicit structured mirror path migration contract exists.');
|
|
321
|
+
}
|
|
322
|
+
return { contentMode, fileExtension };
|
|
323
|
+
}
|
|
324
|
+
export function decodeSourceFileBuffer(buffer, sourcePath = '<source>') {
|
|
325
|
+
if (buffer.includes(0)) {
|
|
326
|
+
throw new Error(`Source file ${sourcePath} contains a binary NUL byte; text-file push stopped.`);
|
|
327
|
+
}
|
|
328
|
+
try {
|
|
329
|
+
return new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(buffer);
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
throw new Error(`Source file ${sourcePath} is not valid UTF-8; text-file push stopped.`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
async function readUtf8SourceFile(sourceInput) {
|
|
336
|
+
const source = normalizeNonEmpty(sourceInput);
|
|
337
|
+
if (!source)
|
|
338
|
+
throw new Error('Provide --source or --source-file.');
|
|
339
|
+
if (source === '-') {
|
|
340
|
+
throw new Error('Text-file source must be a filesystem path; stdin cannot provide an extension-safe mirror identity.');
|
|
341
|
+
}
|
|
342
|
+
const absolutePath = path.resolve(source.startsWith('@') ? source.slice(1) : source);
|
|
343
|
+
const buffer = await readFile(absolutePath);
|
|
344
|
+
const content = decodeSourceFileBuffer(buffer, absolutePath);
|
|
345
|
+
return { absolutePath, content, contentHash: sha256ContentHash(content) };
|
|
346
|
+
}
|
|
347
|
+
function requireSha256ContentHash(value, label) {
|
|
348
|
+
const contentHash = normalizeNonEmpty(value);
|
|
349
|
+
if (!contentHash || !/^sha256:[a-f0-9]{64}$/.test(contentHash)) {
|
|
350
|
+
throw new Error(`${label} must use sha256:<64 lowercase hex characters>.`);
|
|
351
|
+
}
|
|
352
|
+
return contentHash;
|
|
353
|
+
}
|
|
354
|
+
export function parseDocSourceMirrorSidecar(value, sidecarPath = '<sidecar>') {
|
|
355
|
+
if (!isRecord(value))
|
|
356
|
+
throw new Error(`Docman source sidecar ${sidecarPath} must contain a JSON object.`);
|
|
357
|
+
if (value.contract !== DOC_SOURCE_MIRROR_CONTRACT || value.schemaVersion !== 3) {
|
|
358
|
+
throw new Error(`Docman source sidecar ${sidecarPath} must use ${DOC_SOURCE_MIRROR_CONTRACT} schemaVersion 3.`);
|
|
359
|
+
}
|
|
360
|
+
if (value.entityType !== 'docman.document-source-mirror' ||
|
|
361
|
+
value.source !== 'docman' ||
|
|
362
|
+
value.readOnly !== false ||
|
|
363
|
+
value.contentMode !== 'text-file' ||
|
|
364
|
+
value.sourceFormat !== 'text') {
|
|
365
|
+
throw new Error(`Docman source sidecar ${sidecarPath} has an invalid v3 mirror identity.`);
|
|
366
|
+
}
|
|
367
|
+
const documentId = normalizeNonEmpty(value.documentId);
|
|
368
|
+
const documentVersionId = normalizeNonEmpty(value.documentVersionId);
|
|
369
|
+
const documentSlug = normalizeNonEmpty(value.documentSlug);
|
|
370
|
+
const title = normalizeNonEmpty(value.title);
|
|
371
|
+
const mediaType = normalizeNonEmpty(value.mediaType);
|
|
372
|
+
const mirrorPath = normalizeNonEmpty(value.mirrorPath);
|
|
373
|
+
const pulledAt = normalizeNonEmpty(value.pulledAt);
|
|
374
|
+
const documentVersion = Number(value.documentVersion);
|
|
375
|
+
if (!documentId || !documentVersionId || !documentSlug || !title || !mediaType || !mirrorPath || !pulledAt) {
|
|
376
|
+
throw new Error(`Docman source sidecar ${sidecarPath} is missing required mirror provenance.`);
|
|
377
|
+
}
|
|
378
|
+
if (!Number.isInteger(documentVersion) || documentVersion < 1) {
|
|
379
|
+
throw new Error(`Docman source sidecar ${sidecarPath} has an invalid documentVersion.`);
|
|
380
|
+
}
|
|
381
|
+
const fileExtension = validateSourceFileExtension(value.fileExtension, `Sidecar ${sidecarPath} fileExtension`);
|
|
382
|
+
const contentHash = requireSha256ContentHash(value.contentHash, `Sidecar ${sidecarPath} contentHash`);
|
|
383
|
+
return {
|
|
384
|
+
schemaVersion: 3,
|
|
385
|
+
entityType: 'docman.document-source-mirror',
|
|
386
|
+
contract: DOC_SOURCE_MIRROR_CONTRACT,
|
|
387
|
+
readOnly: false,
|
|
388
|
+
source: 'docman',
|
|
389
|
+
projectId: normalizeNonEmpty(value.projectId),
|
|
390
|
+
projectName: normalizeNonEmpty(value.projectName),
|
|
391
|
+
projectSlug: normalizeNonEmpty(value.projectSlug),
|
|
392
|
+
scopeId: normalizeNonEmpty(value.scopeId),
|
|
393
|
+
groupId: normalizeNonEmpty(value.groupId),
|
|
394
|
+
groupUid: normalizeNonEmpty(value.groupUid),
|
|
395
|
+
documentId,
|
|
396
|
+
documentVersionId,
|
|
397
|
+
documentVersion,
|
|
398
|
+
documentSlug,
|
|
399
|
+
title,
|
|
400
|
+
contentMode: 'text-file',
|
|
401
|
+
sourceFormat: 'text',
|
|
402
|
+
fileExtension,
|
|
403
|
+
mediaType,
|
|
404
|
+
contentHash,
|
|
405
|
+
mirrorPath,
|
|
406
|
+
pulledAt,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
export async function readDocSourceMirrorSidecar(sidecarInput) {
|
|
410
|
+
const absolutePath = path.resolve(sidecarInput);
|
|
411
|
+
let content;
|
|
412
|
+
try {
|
|
413
|
+
content = await readFile(absolutePath, 'utf8');
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
if (isFileSystemError(error, 'ENOENT')) {
|
|
417
|
+
throw new Error(`Docman source mirror sidecar is missing: ${absolutePath}. Push stopped.`);
|
|
418
|
+
}
|
|
419
|
+
throw error;
|
|
420
|
+
}
|
|
421
|
+
let parsed;
|
|
422
|
+
try {
|
|
423
|
+
parsed = JSON.parse(content);
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
throw new Error(`Docman source mirror sidecar is not valid JSON: ${absolutePath}.`);
|
|
427
|
+
}
|
|
428
|
+
return parseDocSourceMirrorSidecar(parsed, absolutePath);
|
|
429
|
+
}
|
|
430
|
+
function parseSourceMaterializedPayload(result, expectedVersionId) {
|
|
431
|
+
const payload = unwrapResultData(result);
|
|
432
|
+
const documentVersionId = normalizeNonEmpty(payload?.documentVersionId);
|
|
433
|
+
if (documentVersionId !== expectedVersionId) {
|
|
434
|
+
throw new Error(`Docman source materialize returned version ${documentVersionId ?? '<missing>'}; expected ${expectedVersionId}.`);
|
|
435
|
+
}
|
|
436
|
+
const content = payload?.content;
|
|
437
|
+
if (typeof content !== 'string')
|
|
438
|
+
throw new Error('Docman source materialize did not return textual content.');
|
|
439
|
+
if (payload?.contentMode !== 'text-file' || payload.sourceFormat !== 'text') {
|
|
440
|
+
throw new Error('Docman source materialize did not return a text-file/text payload.');
|
|
441
|
+
}
|
|
442
|
+
const fileExtension = validateSourceFileExtension(payload.fileExtension, 'Materialized fileExtension');
|
|
443
|
+
const contentHash = requireSha256ContentHash(payload.contentHash, 'Materialized contentHash');
|
|
444
|
+
if (contentHash !== sha256ContentHash(content)) {
|
|
445
|
+
throw new Error('Docman source materialize contentHash does not match the exact UTF-8 content.');
|
|
446
|
+
}
|
|
447
|
+
const mediaType = normalizeNonEmpty(payload.mediaType);
|
|
448
|
+
const suggestedFileName = normalizeNonEmpty(payload.suggestedFileName);
|
|
449
|
+
if (!mediaType || !suggestedFileName) {
|
|
450
|
+
throw new Error('Docman source materialize is missing mediaType or suggestedFileName.');
|
|
451
|
+
}
|
|
452
|
+
return {
|
|
453
|
+
documentVersionId,
|
|
454
|
+
content,
|
|
455
|
+
contentHash,
|
|
456
|
+
contentMode: 'text-file',
|
|
457
|
+
sourceFormat: 'text',
|
|
458
|
+
fileExtension,
|
|
459
|
+
mediaType,
|
|
460
|
+
suggestedFileName,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
export function validateDocSourceMirrorPushBaseline(params) {
|
|
464
|
+
const filenameExtension = path.extname(params.sourcePath).slice(1);
|
|
465
|
+
if (filenameExtension !== params.sidecar.fileExtension) {
|
|
466
|
+
throw new Error(`Source filename extension ${filenameExtension || '<missing>'} does not match persisted sidecar extension ${params.sidecar.fileExtension}.`);
|
|
467
|
+
}
|
|
468
|
+
if (path.basename(params.sidecar.mirrorPath) !== path.basename(params.sourcePath)) {
|
|
469
|
+
throw new Error(`Source filename ${path.basename(params.sourcePath)} does not match sidecar mirrorPath ${params.sidecar.mirrorPath}.`);
|
|
470
|
+
}
|
|
471
|
+
if (params.sidecar.scopeId && params.resolvedScopeId && params.sidecar.scopeId !== params.resolvedScopeId) {
|
|
472
|
+
throw new Error(`Source sidecar scope ${params.sidecar.scopeId} does not match resolved scope ${params.resolvedScopeId}.`);
|
|
473
|
+
}
|
|
474
|
+
if (params.sidecar.projectId && params.resolvedProjectId && params.sidecar.projectId !== params.resolvedProjectId) {
|
|
475
|
+
throw new Error(`Source sidecar project ${params.sidecar.projectId} does not match resolved project ${params.resolvedProjectId}.`);
|
|
476
|
+
}
|
|
477
|
+
const currentVersionId = recordId(params.currentVersion, 'documentVersionId');
|
|
478
|
+
if (currentVersionId !== params.sidecar.documentVersionId) {
|
|
479
|
+
throw new Error(`Stale source sidecar version ${params.sidecar.documentVersionId}; hosted current version is ${currentVersionId ?? '<missing>'}.`);
|
|
480
|
+
}
|
|
481
|
+
if (normalizeNonEmpty(params.currentVersion.contentMode) !== 'text-file') {
|
|
482
|
+
throw new Error(`Hosted current version ${currentVersionId} is not a text-file document version.`);
|
|
483
|
+
}
|
|
484
|
+
const currentExtension = validateSourceFileExtension(params.currentVersion.fileExtension, `Hosted current version ${currentVersionId} fileExtension`);
|
|
485
|
+
if (currentExtension !== params.sidecar.fileExtension) {
|
|
486
|
+
throw new Error(`Stale source sidecar extension ${params.sidecar.fileExtension}; hosted current extension is ${currentExtension}.`);
|
|
487
|
+
}
|
|
488
|
+
if (params.hostedSource.fileExtension !== params.sidecar.fileExtension) {
|
|
489
|
+
throw new Error(`Stale source sidecar extension ${params.sidecar.fileExtension}; materialized extension is ${params.hostedSource.fileExtension}.`);
|
|
490
|
+
}
|
|
491
|
+
if (params.hostedSource.contentHash !== params.sidecar.contentHash) {
|
|
492
|
+
throw new Error(`Stale source sidecar hash ${params.sidecar.contentHash}; hosted current hash is ${params.hostedSource.contentHash}.`);
|
|
493
|
+
}
|
|
494
|
+
return params.sourceContentHash === params.sidecar.contentHash ? 'up-to-date' : 'create-version';
|
|
495
|
+
}
|
|
291
496
|
function pathSegmentSlug(value, fallback) {
|
|
292
497
|
const normalized = normalizeNonEmpty(value) ?? fallback;
|
|
293
498
|
const safe = normalized
|
|
@@ -1574,6 +1779,67 @@ function buildDocMirrorFile(params) {
|
|
|
1574
1779
|
content: `${frontmatter}<!-- READ-ONLY MIRROR: update Docman/source docs, then run aops-cli doc mirror pull. -->\n\n${params.content}`,
|
|
1575
1780
|
};
|
|
1576
1781
|
}
|
|
1782
|
+
export function buildDocSourceMirrorArtifacts(params) {
|
|
1783
|
+
const documentId = recordId(params.document, 'documentId');
|
|
1784
|
+
const documentVersionId = recordId(params.documentVersion, 'documentVersionId');
|
|
1785
|
+
const documentSlug = normalizeNonEmpty(params.document.slug) ??
|
|
1786
|
+
pathSegmentSlug(normalizeNonEmpty(params.document.title), documentId ?? 'document');
|
|
1787
|
+
const title = normalizeNonEmpty(params.document.title) ?? documentSlug;
|
|
1788
|
+
const version = recordVersion(params.documentVersion);
|
|
1789
|
+
if (!documentId || !documentVersionId || version < 1) {
|
|
1790
|
+
throw new Error('Text-file mirror plan is missing document/version provenance.');
|
|
1791
|
+
}
|
|
1792
|
+
const fileExtension = validateSourceFileExtension(params.materialized.fileExtension);
|
|
1793
|
+
if (params.materialized.contentHash !== sha256ContentHash(params.materialized.content)) {
|
|
1794
|
+
throw new Error('Docman source mirror artifact contentHash does not match the exact raw UTF-8 content.');
|
|
1795
|
+
}
|
|
1796
|
+
if (!params.materialized.suggestedFileName.endsWith(`.${fileExtension}`)) {
|
|
1797
|
+
throw new Error(`Docman suggested source filename ${params.materialized.suggestedFileName} conflicts with persisted extension ${fileExtension}.`);
|
|
1798
|
+
}
|
|
1799
|
+
const mirrorPath = path.join(params.groupPath, `${pathSegmentSlug(documentSlug, 'document')}.${fileExtension}`);
|
|
1800
|
+
const rawPath = path.join(params.outputRoot, mirrorPath);
|
|
1801
|
+
const sidecarPath = `${rawPath}.docman.json`;
|
|
1802
|
+
const sidecar = {
|
|
1803
|
+
schemaVersion: 3,
|
|
1804
|
+
entityType: 'docman.document-source-mirror',
|
|
1805
|
+
contract: DOC_SOURCE_MIRROR_CONTRACT,
|
|
1806
|
+
readOnly: false,
|
|
1807
|
+
source: 'docman',
|
|
1808
|
+
projectId: params.resolvedContext.projectId,
|
|
1809
|
+
projectName: params.resolvedContext.projectName,
|
|
1810
|
+
projectSlug: params.resolvedContext.projectSlug,
|
|
1811
|
+
scopeId: params.resolvedContext.scopeId,
|
|
1812
|
+
groupId: normalizeNonEmpty(params.document.groupId),
|
|
1813
|
+
groupUid: normalizeNonEmpty(params.document.groupUid),
|
|
1814
|
+
documentId,
|
|
1815
|
+
documentVersionId,
|
|
1816
|
+
documentVersion: version,
|
|
1817
|
+
documentSlug,
|
|
1818
|
+
title,
|
|
1819
|
+
contentMode: 'text-file',
|
|
1820
|
+
sourceFormat: 'text',
|
|
1821
|
+
fileExtension,
|
|
1822
|
+
mediaType: params.materialized.mediaType,
|
|
1823
|
+
contentHash: params.materialized.contentHash,
|
|
1824
|
+
mirrorPath: mirrorPath.split(path.sep).join('/'),
|
|
1825
|
+
pulledAt: new Date().toISOString(),
|
|
1826
|
+
};
|
|
1827
|
+
return {
|
|
1828
|
+
raw: { filePath: rawPath, content: params.materialized.content },
|
|
1829
|
+
sidecar: { filePath: sidecarPath, content: `${JSON.stringify(sidecar, null, 2)}\n` },
|
|
1830
|
+
entry: {
|
|
1831
|
+
title,
|
|
1832
|
+
mirrorPath: sidecar.mirrorPath,
|
|
1833
|
+
sidecarPath: path.relative(params.outputRoot, sidecarPath).split(path.sep).join('/'),
|
|
1834
|
+
documentId,
|
|
1835
|
+
documentVersionId,
|
|
1836
|
+
documentVersion: version,
|
|
1837
|
+
contentMode: 'text-file',
|
|
1838
|
+
fileExtension,
|
|
1839
|
+
contentHash: sidecar.contentHash,
|
|
1840
|
+
},
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1577
1843
|
function buildDocMirrorIndex(params) {
|
|
1578
1844
|
const indexPath = path.join(params.outputRoot, 'index.md');
|
|
1579
1845
|
const frontmatter = renderFrontmatter({
|
|
@@ -1591,7 +1857,7 @@ function buildDocMirrorIndex(params) {
|
|
|
1591
1857
|
frontmatter,
|
|
1592
1858
|
'# Docman Mirror Index',
|
|
1593
1859
|
'',
|
|
1594
|
-
'This directory
|
|
1860
|
+
'This directory mirrors hosted Docman documents. Structured v2 `.md` files are read-only; text-file v3 entries are editable only with their adjacent `.docman.json` sidecar and guarded `aops-cli doc mirror push --source-file <path>`.',
|
|
1595
1861
|
'',
|
|
1596
1862
|
...params.entries.map((entry) => {
|
|
1597
1863
|
const title = normalizeNonEmpty(entry.title) ?? 'Untitled';
|
|
@@ -1678,11 +1944,47 @@ export async function commitStagedMirrorFiles(files) {
|
|
|
1678
1944
|
async function listDocMirrorIndexEntries(outputRoot) {
|
|
1679
1945
|
const files = await listLocalMirrorMarkdownFiles(outputRoot);
|
|
1680
1946
|
const docs = await readLocalMirrorDocumentsFromFiles(outputRoot, files);
|
|
1681
|
-
|
|
1947
|
+
const structured = docs.map((doc) => ({
|
|
1682
1948
|
title: doc.title,
|
|
1683
1949
|
mirrorPath: doc.relativePath,
|
|
1684
1950
|
documentVersion: doc.documentVersion,
|
|
1685
1951
|
}));
|
|
1952
|
+
return [...structured, ...(await listDocSourceMirrorIndexEntries(outputRoot))];
|
|
1953
|
+
}
|
|
1954
|
+
async function listDocSourceMirrorSidecarFiles(root) {
|
|
1955
|
+
try {
|
|
1956
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
1957
|
+
const nested = await Promise.all(entries.map(async (entry) => {
|
|
1958
|
+
const fullPath = path.join(root, entry.name);
|
|
1959
|
+
if (entry.isDirectory() && !entry.name.startsWith('.'))
|
|
1960
|
+
return listDocSourceMirrorSidecarFiles(fullPath);
|
|
1961
|
+
if (entry.isFile() && entry.name.endsWith('.docman.json'))
|
|
1962
|
+
return [fullPath];
|
|
1963
|
+
return [];
|
|
1964
|
+
}));
|
|
1965
|
+
return nested.flat().sort();
|
|
1966
|
+
}
|
|
1967
|
+
catch {
|
|
1968
|
+
return [];
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
async function listDocSourceMirrorIndexEntries(outputRoot) {
|
|
1972
|
+
const entries = [];
|
|
1973
|
+
for (const sidecarPath of await listDocSourceMirrorSidecarFiles(outputRoot)) {
|
|
1974
|
+
const sidecar = await readDocSourceMirrorSidecar(sidecarPath);
|
|
1975
|
+
entries.push({
|
|
1976
|
+
title: sidecar.title,
|
|
1977
|
+
mirrorPath: sidecar.mirrorPath,
|
|
1978
|
+
sidecarPath: path.relative(outputRoot, sidecarPath).split(path.sep).join('/'),
|
|
1979
|
+
documentId: sidecar.documentId,
|
|
1980
|
+
documentVersionId: sidecar.documentVersionId,
|
|
1981
|
+
documentVersion: sidecar.documentVersion,
|
|
1982
|
+
contentMode: sidecar.contentMode,
|
|
1983
|
+
fileExtension: sidecar.fileExtension,
|
|
1984
|
+
contentHash: sidecar.contentHash,
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
return entries;
|
|
1686
1988
|
}
|
|
1687
1989
|
function localMirrorLimit(value) {
|
|
1688
1990
|
const parsed = toInteger(value, '--limit');
|
|
@@ -1702,8 +2004,15 @@ async function listLocalMirrorMarkdownFiles(root) {
|
|
|
1702
2004
|
const fullPath = path.join(root, entry.name);
|
|
1703
2005
|
if (entry.isDirectory() && !entry.name.startsWith('.'))
|
|
1704
2006
|
return listLocalMirrorMarkdownFiles(fullPath);
|
|
1705
|
-
if (entry.isFile() && entry.name.endsWith('.md') && entry.name.toLowerCase() !== 'index.md')
|
|
1706
|
-
|
|
2007
|
+
if (entry.isFile() && entry.name.endsWith('.md') && entry.name.toLowerCase() !== 'index.md') {
|
|
2008
|
+
try {
|
|
2009
|
+
await stat(`${fullPath}.docman.json`);
|
|
2010
|
+
return [];
|
|
2011
|
+
}
|
|
2012
|
+
catch {
|
|
2013
|
+
return [fullPath];
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
1707
2016
|
return [];
|
|
1708
2017
|
}));
|
|
1709
2018
|
return nested.flat().sort();
|
|
@@ -2320,12 +2629,172 @@ export async function runDocImportMarkdown(options = {}) {
|
|
|
2320
2629
|
process.exitCode = 1;
|
|
2321
2630
|
}
|
|
2322
2631
|
}
|
|
2632
|
+
export async function runDocSourceFileSave(options = {}) {
|
|
2633
|
+
try {
|
|
2634
|
+
const documentId = normalizeNonEmpty(options.documentId);
|
|
2635
|
+
if (!documentId)
|
|
2636
|
+
throw new Error('Provide --document-id.');
|
|
2637
|
+
const source = await readUtf8SourceFile(options.source);
|
|
2638
|
+
const fileExtension = validateSourceFileExtension(options.extension, '--extension');
|
|
2639
|
+
const expectedPreviousContentHash = options.expectedContentHash === undefined
|
|
2640
|
+
? undefined
|
|
2641
|
+
: requireSha256ContentHash(options.expectedContentHash, '--expected-content-hash');
|
|
2642
|
+
const apiState = await requireApiState(options);
|
|
2643
|
+
if (!apiState)
|
|
2644
|
+
return;
|
|
2645
|
+
const resolvedContext = await resolveDocContext(options, apiState, { forceHostedScope: true });
|
|
2646
|
+
const auditFields = await resolveDocAuditFields(apiState, options, {}, 'create');
|
|
2647
|
+
const input = compactPayload({
|
|
2648
|
+
documentId,
|
|
2649
|
+
content: source.content,
|
|
2650
|
+
fileExtension,
|
|
2651
|
+
title: normalizeNonEmpty(options.title),
|
|
2652
|
+
expectedPreviousVersionId: normalizeNonEmpty(options.expectedVersionId),
|
|
2653
|
+
expectedPreviousContentHash,
|
|
2654
|
+
publishNow: options.publishNow === true,
|
|
2655
|
+
...auditFields,
|
|
2656
|
+
});
|
|
2657
|
+
const previewInput = compactPayload({
|
|
2658
|
+
...input,
|
|
2659
|
+
content: undefined,
|
|
2660
|
+
sourcePath: source.absolutePath,
|
|
2661
|
+
sourceContentHash: source.contentHash,
|
|
2662
|
+
sourceBytes: Buffer.byteLength(source.content, 'utf8'),
|
|
2663
|
+
});
|
|
2664
|
+
if (await guardDocWrite({
|
|
2665
|
+
options,
|
|
2666
|
+
command: 'doc.source-file.save',
|
|
2667
|
+
surface: 'docman.document.source-file.save',
|
|
2668
|
+
resolvedContext,
|
|
2669
|
+
input: previewInput,
|
|
2670
|
+
}))
|
|
2671
|
+
return;
|
|
2672
|
+
const result = await invokeDocCrudMutationRaw(apiState, options, resolvedContext, 'docman.document.source-file.save', input, { idempotencyKey: normalizeNonEmpty(options.idempotencyKey) });
|
|
2673
|
+
await emitDocResult({
|
|
2674
|
+
options,
|
|
2675
|
+
command: 'doc.source-file.save',
|
|
2676
|
+
surface: 'docman.document.source-file.save',
|
|
2677
|
+
resolvedContext,
|
|
2678
|
+
input: previewInput,
|
|
2679
|
+
result,
|
|
2680
|
+
artifacts: compactPayload({
|
|
2681
|
+
documentId,
|
|
2682
|
+
documentVersionId: normalizeNonEmpty(unwrapCrudData(result).documentVersionId),
|
|
2683
|
+
contentHash: normalizeNonEmpty(unwrapCrudData(result).contentHash),
|
|
2684
|
+
}),
|
|
2685
|
+
successText: options.publishNow === true
|
|
2686
|
+
? 'Docman source-file snapshot saved and published.'
|
|
2687
|
+
: 'Docman source-file draft snapshot saved.',
|
|
2688
|
+
});
|
|
2689
|
+
}
|
|
2690
|
+
catch (error) {
|
|
2691
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
2692
|
+
process.exitCode = 1;
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
async function runDocSourceMirrorPush(options, apiState, resolvedContext) {
|
|
2696
|
+
const source = await readUtf8SourceFile(options.sourceFile);
|
|
2697
|
+
const sidecarPath = path.resolve(normalizeNonEmpty(options.sidecar) ?? `${source.absolutePath}.docman.json`);
|
|
2698
|
+
const sidecar = await readDocSourceMirrorSidecar(sidecarPath);
|
|
2699
|
+
const versions = await listDocumentVersions(apiState, options, resolvedContext, sidecar.documentId);
|
|
2700
|
+
const current = selectCurrentPublishedDocumentVersion(versions, sidecar.documentId);
|
|
2701
|
+
const hostedMaterialized = await callDocmanVersionRoute(apiState, options, resolvedContext, {
|
|
2702
|
+
documentVersionId: sidecar.documentVersionId,
|
|
2703
|
+
suffix: 'materialize',
|
|
2704
|
+
method: 'POST',
|
|
2705
|
+
body: compactPayload({
|
|
2706
|
+
documentVersionId: sidecar.documentVersionId,
|
|
2707
|
+
target: 'source',
|
|
2708
|
+
locale: normalizeNonEmpty(options.locale),
|
|
2709
|
+
fallbackLocale: normalizeNonEmpty(options.fallbackLocale),
|
|
2710
|
+
}),
|
|
2711
|
+
});
|
|
2712
|
+
const hostedSource = parseSourceMaterializedPayload(hostedMaterialized, sidecar.documentVersionId);
|
|
2713
|
+
const action = validateDocSourceMirrorPushBaseline({
|
|
2714
|
+
sourcePath: source.absolutePath,
|
|
2715
|
+
sourceContentHash: source.contentHash,
|
|
2716
|
+
sidecar,
|
|
2717
|
+
currentVersion: current,
|
|
2718
|
+
hostedSource,
|
|
2719
|
+
resolvedScopeId: normalizeNonEmpty(resolvedContext.scopeId),
|
|
2720
|
+
resolvedProjectId: normalizeNonEmpty(resolvedContext.projectId),
|
|
2721
|
+
});
|
|
2722
|
+
const auditFields = await resolveDocAuditFields(apiState, options, {}, 'create');
|
|
2723
|
+
const saveInput = compactPayload({
|
|
2724
|
+
documentId: sidecar.documentId,
|
|
2725
|
+
content: source.content,
|
|
2726
|
+
fileExtension: sidecar.fileExtension,
|
|
2727
|
+
title: normalizeNonEmpty(options.title) ?? sidecar.title,
|
|
2728
|
+
expectedPreviousVersionId: sidecar.documentVersionId,
|
|
2729
|
+
expectedPreviousContentHash: sidecar.contentHash,
|
|
2730
|
+
publishNow: options.publishNow === true,
|
|
2731
|
+
...auditFields,
|
|
2732
|
+
});
|
|
2733
|
+
const input = {
|
|
2734
|
+
sourcePath: source.absolutePath,
|
|
2735
|
+
sidecarPath,
|
|
2736
|
+
action,
|
|
2737
|
+
sourceContentHash: source.contentHash,
|
|
2738
|
+
sourceBytes: Buffer.byteLength(source.content, 'utf8'),
|
|
2739
|
+
documentId: sidecar.documentId,
|
|
2740
|
+
expectedPreviousVersionId: sidecar.documentVersionId,
|
|
2741
|
+
expectedPreviousContentHash: sidecar.contentHash,
|
|
2742
|
+
fileExtension: sidecar.fileExtension,
|
|
2743
|
+
publishNow: options.publishNow === true,
|
|
2744
|
+
};
|
|
2745
|
+
if (await guardDocWrite({
|
|
2746
|
+
options,
|
|
2747
|
+
command: 'doc.mirror.push',
|
|
2748
|
+
surface: `${DOC_SOURCE_MIRROR_CONTRACT} guarded source push`,
|
|
2749
|
+
resolvedContext,
|
|
2750
|
+
input,
|
|
2751
|
+
}))
|
|
2752
|
+
return;
|
|
2753
|
+
if (action === 'up-to-date') {
|
|
2754
|
+
await emitDocResult({
|
|
2755
|
+
options,
|
|
2756
|
+
command: 'doc.mirror.push',
|
|
2757
|
+
surface: `${DOC_SOURCE_MIRROR_CONTRACT} guarded source push`,
|
|
2758
|
+
resolvedContext,
|
|
2759
|
+
input,
|
|
2760
|
+
result: { action, changed: false, documentId: sidecar.documentId, documentVersionId: sidecar.documentVersionId },
|
|
2761
|
+
successText: 'Docman source mirror is already up to date.',
|
|
2762
|
+
});
|
|
2763
|
+
return;
|
|
2764
|
+
}
|
|
2765
|
+
const result = await invokeDocCrudMutationRaw(apiState, options, resolvedContext, 'docman.document.source-file.save', saveInput, { idempotencyKey: normalizeNonEmpty(options.idempotencyKey) });
|
|
2766
|
+
await emitDocResult({
|
|
2767
|
+
options,
|
|
2768
|
+
command: 'doc.mirror.push',
|
|
2769
|
+
surface: `${DOC_SOURCE_MIRROR_CONTRACT} guarded source push`,
|
|
2770
|
+
resolvedContext,
|
|
2771
|
+
input,
|
|
2772
|
+
result,
|
|
2773
|
+
artifacts: compactPayload({
|
|
2774
|
+
sourcePath: source.absolutePath,
|
|
2775
|
+
sidecarPath,
|
|
2776
|
+
documentId: sidecar.documentId,
|
|
2777
|
+
documentVersionId: normalizeNonEmpty(unwrapCrudData(result).documentVersionId),
|
|
2778
|
+
contentHash: normalizeNonEmpty(unwrapCrudData(result).contentHash),
|
|
2779
|
+
}),
|
|
2780
|
+
successText: options.publishNow === true
|
|
2781
|
+
? 'Docman source mirror snapshot pushed and published.'
|
|
2782
|
+
: 'Docman source mirror draft snapshot pushed; run pull only after publishing it current.',
|
|
2783
|
+
});
|
|
2784
|
+
}
|
|
2323
2785
|
export async function runDocMirrorPush(options = {}) {
|
|
2324
2786
|
try {
|
|
2325
2787
|
const apiState = await requireApiState(options);
|
|
2326
2788
|
if (!apiState)
|
|
2327
2789
|
return;
|
|
2328
2790
|
const resolvedContext = await resolveDocContext(options, apiState, { forceHostedScope: true });
|
|
2791
|
+
if (normalizeNonEmpty(options.sourceFile)) {
|
|
2792
|
+
await runDocSourceMirrorPush(options, apiState, resolvedContext);
|
|
2793
|
+
return;
|
|
2794
|
+
}
|
|
2795
|
+
if (normalizeNonEmpty(options.sidecar)) {
|
|
2796
|
+
throw new Error('--sidecar requires --source-file.');
|
|
2797
|
+
}
|
|
2329
2798
|
const groupUid = normalizeNonEmpty(options.groupUid) ?? 'architecture';
|
|
2330
2799
|
const groupTitle = normalizeNonEmpty(options.groupTitle) ?? 'Architecture';
|
|
2331
2800
|
const sources = await readRootMarkdownSources(options.sourceDir);
|
|
@@ -2581,7 +3050,11 @@ export async function runDocMirrorPull(options = {}) {
|
|
|
2581
3050
|
const groupPath = buildGroupPath(document, groupsById, groupsByUid);
|
|
2582
3051
|
const documentSlug = normalizeNonEmpty(document.slug) ??
|
|
2583
3052
|
pathSegmentSlug(normalizeNonEmpty(document.title), normalizeNonEmpty(documentId) ?? 'document');
|
|
2584
|
-
const
|
|
3053
|
+
const profile = resolveDocMirrorVersionProfile(latest, documentVersionId);
|
|
3054
|
+
const { contentMode } = profile;
|
|
3055
|
+
const fileExtension = contentMode === 'text-file' ? profile.fileExtension : undefined;
|
|
3056
|
+
const materializeTarget = contentMode === 'text-file' ? 'source' : target;
|
|
3057
|
+
const mirrorPath = path.join(groupPath, `${pathSegmentSlug(documentSlug, 'document')}.${fileExtension ?? 'md'}`);
|
|
2585
3058
|
const canonicalMirrorPath = path.resolve(outputRoot, mirrorPath).toLowerCase();
|
|
2586
3059
|
if (seenMirrorPaths.has(canonicalMirrorPath)) {
|
|
2587
3060
|
throw new Error(`Docman mirror plan contains duplicate output path ${mirrorPath}.`);
|
|
@@ -2595,6 +3068,9 @@ export async function runDocMirrorPull(options = {}) {
|
|
|
2595
3068
|
slug: documentSlug,
|
|
2596
3069
|
groupPath,
|
|
2597
3070
|
mirrorPath,
|
|
3071
|
+
contentMode,
|
|
3072
|
+
fileExtension,
|
|
3073
|
+
target: materializeTarget,
|
|
2598
3074
|
});
|
|
2599
3075
|
}
|
|
2600
3076
|
const input = compactPayload({
|
|
@@ -2623,14 +3099,23 @@ export async function runDocMirrorPull(options = {}) {
|
|
|
2623
3099
|
const document = documents.find((entry) => recordId(entry, 'documentId') === documentId);
|
|
2624
3100
|
if (!document)
|
|
2625
3101
|
throw new Error(`Validated mirror plan references unknown document ${documentId}.`);
|
|
2626
|
-
const documentVersion = {
|
|
3102
|
+
const documentVersion = {
|
|
3103
|
+
id: documentVersionId,
|
|
3104
|
+
documentId,
|
|
3105
|
+
version: plan.documentVersion,
|
|
3106
|
+
status: 'published',
|
|
3107
|
+
isCurrent: true,
|
|
3108
|
+
contentMode: plan.contentMode,
|
|
3109
|
+
fileExtension: plan.fileExtension,
|
|
3110
|
+
};
|
|
3111
|
+
const materializeTarget = normalizeNonEmpty(plan.target) ?? target;
|
|
2627
3112
|
const materialized = await callDocmanVersionRoute(apiState, options, resolvedContext, {
|
|
2628
3113
|
documentVersionId,
|
|
2629
3114
|
suffix: 'materialize',
|
|
2630
3115
|
method: 'POST',
|
|
2631
3116
|
body: compactPayload({
|
|
2632
3117
|
documentVersionId,
|
|
2633
|
-
target,
|
|
3118
|
+
target: materializeTarget,
|
|
2634
3119
|
locale: normalizeNonEmpty(options.locale),
|
|
2635
3120
|
fallbackLocale: normalizeNonEmpty(options.fallbackLocale),
|
|
2636
3121
|
}),
|
|
@@ -2641,27 +3126,54 @@ export async function runDocMirrorPull(options = {}) {
|
|
|
2641
3126
|
throw new Error(`Docman materialize returned version ${returnedVersionId ?? '<missing>'}; expected ${documentVersionId}. ` +
|
|
2642
3127
|
'Mirror pull stopped before replacing any destination file.');
|
|
2643
3128
|
}
|
|
2644
|
-
|
|
2645
|
-
|
|
3129
|
+
if (plan.contentMode === 'text-file') {
|
|
3130
|
+
const source = parseSourceMaterializedPayload(materialized, documentVersionId);
|
|
3131
|
+
if (source.fileExtension !== plan.fileExtension) {
|
|
3132
|
+
throw new Error(`Docman source materialize extension ${source.fileExtension} differs from persisted version extension ${String(plan.fileExtension)}.`);
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
else {
|
|
3136
|
+
materializedContent(materialized);
|
|
3137
|
+
}
|
|
3138
|
+
materializedPlans.push({ plan, document, documentVersion, materialized });
|
|
2646
3139
|
}
|
|
2647
3140
|
const existingIndexEntries = await listDocMirrorIndexEntries(outputRoot);
|
|
2648
3141
|
const written = [];
|
|
2649
|
-
const artifacts =
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
3142
|
+
const artifacts = [];
|
|
3143
|
+
for (const { plan, document, documentVersion, materialized } of materializedPlans) {
|
|
3144
|
+
if (plan.contentMode === 'text-file') {
|
|
3145
|
+
const sourceArtifacts = buildDocSourceMirrorArtifacts({
|
|
3146
|
+
outputRoot,
|
|
3147
|
+
groupPath: normalizeNonEmpty(plan.groupPath) ?? 'ungrouped',
|
|
3148
|
+
document,
|
|
3149
|
+
documentVersion,
|
|
3150
|
+
resolvedContext,
|
|
3151
|
+
materialized: parseSourceMaterializedPayload(materialized, normalizeNonEmpty(plan.documentVersionId) ?? ''),
|
|
3152
|
+
});
|
|
3153
|
+
artifacts.push(sourceArtifacts.raw, sourceArtifacts.sidecar);
|
|
3154
|
+
written.push({
|
|
3155
|
+
...plan,
|
|
3156
|
+
...sourceArtifacts.entry,
|
|
3157
|
+
outputPath: sourceArtifacts.raw.filePath,
|
|
3158
|
+
});
|
|
3159
|
+
}
|
|
3160
|
+
else {
|
|
3161
|
+
const artifact = buildDocMirrorFile({
|
|
3162
|
+
outputRoot,
|
|
3163
|
+
groupPath: normalizeNonEmpty(plan.groupPath) ?? 'ungrouped',
|
|
3164
|
+
document,
|
|
3165
|
+
documentVersion,
|
|
3166
|
+
resolvedContext,
|
|
3167
|
+
target: normalizeNonEmpty(plan.target) ?? target,
|
|
3168
|
+
content: materializedContent(materialized),
|
|
3169
|
+
});
|
|
3170
|
+
artifacts.push(artifact);
|
|
3171
|
+
written.push({
|
|
3172
|
+
...plan,
|
|
3173
|
+
outputPath: artifact.filePath,
|
|
3174
|
+
});
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
2665
3177
|
const indexEntriesByPath = new Map();
|
|
2666
3178
|
for (const entry of existingIndexEntries) {
|
|
2667
3179
|
const mirrorPath = normalizeNonEmpty(entry.mirrorPath);
|
|
@@ -4758,8 +5270,8 @@ export async function runDocPublish(options = {}) {
|
|
|
4758
5270
|
const resolvedContext = await resolveDocContext(options, apiState);
|
|
4759
5271
|
const documentVersionId = requireDocumentVersionId(options);
|
|
4760
5272
|
const target = normalizeNonEmpty(options.target) ?? 'markdown';
|
|
4761
|
-
if (target !== 'markdown' && target !== 'html') {
|
|
4762
|
-
throw new Error('Target must be markdown or
|
|
5273
|
+
if (target !== 'markdown' && target !== 'html' && target !== 'source') {
|
|
5274
|
+
throw new Error('Target must be markdown, html, or source.');
|
|
4763
5275
|
}
|
|
4764
5276
|
const input = compactPayload({
|
|
4765
5277
|
documentVersionId,
|
|
@@ -5305,11 +5817,28 @@ export function makeDocCommand() {
|
|
|
5305
5817
|
'A dry-run returns a short-lived planToken. Pass it to --apply --plan-token <token> to reject source or graph drift; apply without a token remains supported.',
|
|
5306
5818
|
],
|
|
5307
5819
|
}));
|
|
5308
|
-
const
|
|
5820
|
+
const sourceFile = cmd.command('source-file').description('Immutable Docman text-file snapshot commands');
|
|
5821
|
+
applyDocContextOptions(applyWriteOptions(sourceFile
|
|
5822
|
+
.command('save')
|
|
5823
|
+
.description('Save an exact UTF-8 source file as a new immutable text-file document version')
|
|
5824
|
+
.requiredOption('--document-id <id>', 'Existing Docman document id')
|
|
5825
|
+
.requiredOption('--source <path>', 'UTF-8 source file path')
|
|
5826
|
+
.requiredOption('--extension <extension>', 'Explicit persisted lowercase extension without a leading dot')
|
|
5827
|
+
.option('--title <title>', 'Snapshot/page title')
|
|
5828
|
+
.option('--expected-version-id <id>', 'Reject unless this remains the previous current/latest version')
|
|
5829
|
+
.option('--expected-content-hash <hash>', 'Reject unless previous source content still has this sha256 hash')
|
|
5830
|
+
.option('--publish-now', 'Atomically publish/select the new snapshot as current')).action(async (options) => {
|
|
5831
|
+
await runDocSourceFileSave(options);
|
|
5832
|
+
}));
|
|
5833
|
+
const mirror = cmd.command('mirror').description('Docman v2 structured and v3 source-file mirror helpers');
|
|
5309
5834
|
applyDocContextOptions(applyWriteOptions(mirror
|
|
5310
5835
|
.command('push')
|
|
5311
|
-
.description('Import root markdown files
|
|
5836
|
+
.description('Import root markdown files or guarded-push one v3 source file with its adjacent sidecar')
|
|
5312
5837
|
.option('--source-dir <path>', 'Directory containing root *.md files', 'docs')
|
|
5838
|
+
.option('--source-file <path>', 'V3 source file to guarded-push; requires adjacent <file>.docman.json')
|
|
5839
|
+
.option('--sidecar <path>', 'Explicit v3 sidecar path (default: <source-file>.docman.json)')
|
|
5840
|
+
.option('--title <title>', 'Optional title for the new v3 source snapshot')
|
|
5841
|
+
.option('--publish-now', 'Atomically publish/select the pushed v3 snapshot as current')
|
|
5313
5842
|
.option('--group-title <title>', 'Document group title to create/use')
|
|
5314
5843
|
.option('--group-uid <uid>', 'Document group uid to create/use')
|
|
5315
5844
|
.option('--document-status <value>', 'Document status: draft, published, archived', 'published')
|
|
@@ -5321,7 +5850,7 @@ export function makeDocCommand() {
|
|
|
5321
5850
|
}));
|
|
5322
5851
|
applyDocContextOptions(applyWriteOptions(mirror
|
|
5323
5852
|
.command('pull')
|
|
5324
|
-
.description('Materialize
|
|
5853
|
+
.description('Materialize structured v2 .md mirrors and exact v3 source files with adjacent sidecars')
|
|
5325
5854
|
.option('--group-uid <uid>', 'Only pull one document group uid')
|
|
5326
5855
|
.option('--document-slug <slug>', 'Only pull matching document slug (repeatable)', collectRepeatedOption, [])
|
|
5327
5856
|
.option('--status <value>', 'Document status filter')
|
|
@@ -5356,9 +5885,9 @@ export function makeDocCommand() {
|
|
|
5356
5885
|
}));
|
|
5357
5886
|
applyDocContextOptions(cmd
|
|
5358
5887
|
.command('publish')
|
|
5359
|
-
.description('Materialize saved document content to markdown or
|
|
5888
|
+
.description('Materialize saved document content to markdown, html, or exact source')
|
|
5360
5889
|
.requiredOption('--document-version-id <id>', 'Document version id')
|
|
5361
|
-
.option('--target <target>', 'Publish target: markdown or
|
|
5890
|
+
.option('--target <target>', 'Publish target: markdown, html, or source', 'markdown')
|
|
5362
5891
|
.option('--out <path>', 'Write materialized content to a file path')
|
|
5363
5892
|
.option('--section-id <id>', 'Section id')
|
|
5364
5893
|
.option('--page-version-id <id>', 'Page version id')
|