mdkg 0.5.0 → 0.5.2
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/CHANGELOG.md +39 -0
- package/CLI_COMMAND_MATRIX.md +25 -5
- package/README.md +21 -4
- package/dist/cli.js +62 -5
- package/dist/command-contract.json +2029 -125
- package/dist/commands/archive.js +176 -39
- package/dist/commands/git_materialize.js +1100 -0
- package/dist/init/init-manifest.json +1 -1
- package/dist/util/argparse.js +1 -0
- package/package.json +4 -3
package/dist/commands/archive.js
CHANGED
|
@@ -83,6 +83,22 @@ function normalizeArchiveId(raw) {
|
|
|
83
83
|
}
|
|
84
84
|
return normalized;
|
|
85
85
|
}
|
|
86
|
+
function archiveTargetFromInput(value, ws) {
|
|
87
|
+
const uriId = (0, refs_1.archiveIdFromUri)(value);
|
|
88
|
+
if (uriId) {
|
|
89
|
+
return { id: normalizeArchiveId(uriId), workspace: normalizeWorkspace(ws) };
|
|
90
|
+
}
|
|
91
|
+
const separator = value.indexOf(":");
|
|
92
|
+
if (separator > 0) {
|
|
93
|
+
const workspace = normalizeWorkspace(value.slice(0, separator));
|
|
94
|
+
const id = normalizeArchiveId(value.slice(separator + 1));
|
|
95
|
+
if (ws && normalizeWorkspace(ws) !== workspace) {
|
|
96
|
+
throw new errors_1.UsageError(`archive qid ${value} conflicts with --ws ${normalizeWorkspace(ws)}`);
|
|
97
|
+
}
|
|
98
|
+
return { id, workspace };
|
|
99
|
+
}
|
|
100
|
+
return { id: normalizeArchiveId(value), workspace: normalizeWorkspace(ws) };
|
|
101
|
+
}
|
|
86
102
|
function archiveIdFromInput(value) {
|
|
87
103
|
return normalizeArchiveId((0, refs_1.archiveIdFromUri)(value) ?? value);
|
|
88
104
|
}
|
|
@@ -133,9 +149,8 @@ function maybeReindex(root) {
|
|
|
133
149
|
function resolveArchiveNode(root, id, ws) {
|
|
134
150
|
const config = (0, config_1.loadConfig)(root);
|
|
135
151
|
const { index } = (0, index_cache_1.loadIndex)({ root, config });
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
const qid = archiveId.includes(":") ? archiveId : `${wsHint}:${archiveId}`;
|
|
152
|
+
const target = archiveTargetFromInput(id, ws);
|
|
153
|
+
const qid = `${target.workspace}:${target.id}`;
|
|
139
154
|
const node = index.nodes[qid];
|
|
140
155
|
if (!node || node.type !== "archive") {
|
|
141
156
|
throw new errors_1.NotFoundError(`archive not found: ${id}`);
|
|
@@ -143,6 +158,9 @@ function resolveArchiveNode(root, id, ws) {
|
|
|
143
158
|
return node;
|
|
144
159
|
}
|
|
145
160
|
function archiveNodePaths(root, node) {
|
|
161
|
+
if (node.source?.imported || node.source?.read_only || node.path.includes("#")) {
|
|
162
|
+
throw new errors_1.ValidationError(`refusing to derive filesystem paths for read-only archive projection ${node.qid}`);
|
|
163
|
+
}
|
|
146
164
|
const sidecarPath = path_1.default.resolve(root, node.path);
|
|
147
165
|
const sidecarDir = path_1.default.dirname(sidecarPath);
|
|
148
166
|
return {
|
|
@@ -175,9 +193,12 @@ function stringAttribute(value) {
|
|
|
175
193
|
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
176
194
|
}
|
|
177
195
|
function writeArchiveSidecar(root, sidecarPath, frontmatter, body) {
|
|
196
|
+
(0, filesystem_authority_1.atomicReplaceContainedFile)({ root, relativePath: toPosixPath(path_1.default.relative(root, sidecarPath)) }, formatArchiveSidecar(frontmatter, body));
|
|
197
|
+
}
|
|
198
|
+
function formatArchiveSidecar(frontmatter, body) {
|
|
178
199
|
const lines = (0, frontmatter_1.formatFrontmatter)(frontmatter);
|
|
179
200
|
const content = ["---", ...lines, "---", body.trimStart()].join("\n");
|
|
180
|
-
|
|
201
|
+
return content.endsWith("\n") ? content : `${content}\n`;
|
|
181
202
|
}
|
|
182
203
|
function verifyArchiveSidecar(root, ws, sidecarPath) {
|
|
183
204
|
const relativePath = toPosixPath(path_1.default.relative(root, sidecarPath));
|
|
@@ -436,42 +457,137 @@ function runArchiveVerifyCommand(options) {
|
|
|
436
457
|
throw new errors_1.ValidationError("archive verification failed");
|
|
437
458
|
}
|
|
438
459
|
}
|
|
439
|
-
function
|
|
440
|
-
|
|
441
|
-
|
|
460
|
+
function isReadOnlyArchiveProjection(config, node) {
|
|
461
|
+
return Boolean(node.source?.imported ||
|
|
462
|
+
node.source?.read_only ||
|
|
463
|
+
config.subgraphs[node.source?.subgraph_alias ?? node.ws]);
|
|
464
|
+
}
|
|
465
|
+
function readOnlyArchiveError(node) {
|
|
466
|
+
const alias = node.source?.subgraph_alias ?? node.ws;
|
|
467
|
+
return new errors_1.ValidationError(`cannot compress read-only archive ${node.qid} from imported workspace ${alias}; ` +
|
|
468
|
+
"run compression in the source workspace and refresh the subgraph bundle");
|
|
469
|
+
}
|
|
470
|
+
function readOnlyWorkspaceError(alias) {
|
|
471
|
+
return new errors_1.ValidationError(`cannot compress archives in read-only imported workspace ${alias}; ` +
|
|
472
|
+
"run compression in the source workspace and refresh the subgraph bundle");
|
|
473
|
+
}
|
|
474
|
+
function assertWritableArchiveOwner(config, node) {
|
|
475
|
+
if (isReadOnlyArchiveProjection(config, node)) {
|
|
476
|
+
throw readOnlyArchiveError(node);
|
|
442
477
|
}
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
478
|
+
const workspace = config.workspaces[node.ws];
|
|
479
|
+
if (!workspace) {
|
|
480
|
+
throw new errors_1.ValidationError(`archive ${node.qid} has no configured local workspace owner`);
|
|
481
|
+
}
|
|
482
|
+
if (!workspace.enabled) {
|
|
483
|
+
throw new errors_1.ValidationError(`archive workspace ${node.ws} is disabled and not writable`);
|
|
484
|
+
}
|
|
485
|
+
const archiveRoot = (0, workspace_path_1.workspaceDocumentRelativePath)(workspace.path, workspace.mdkg_dir, "archive");
|
|
486
|
+
const nodePath = toPosixPath(node.path);
|
|
487
|
+
if (!nodePath.startsWith(`${archiveRoot}/`)) {
|
|
488
|
+
throw new errors_1.ValidationError(`archive ${node.qid} path is outside its configured local archive root: ${archiveRoot}`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
function resolveArchiveNodeFromIndex(index, id, ws) {
|
|
492
|
+
const target = archiveTargetFromInput(id, ws);
|
|
493
|
+
const qid = `${target.workspace}:${target.id}`;
|
|
494
|
+
const node = index.nodes[qid];
|
|
495
|
+
if (!node || node.type !== "archive") {
|
|
496
|
+
throw new errors_1.NotFoundError(`archive not found: ${id}`);
|
|
497
|
+
}
|
|
498
|
+
return node;
|
|
499
|
+
}
|
|
500
|
+
function selectArchiveCompressionNodes(options, config, index) {
|
|
501
|
+
if (options.all && options.id) {
|
|
502
|
+
throw new errors_1.UsageError("archive compress accepts either <id-or-archive-uri-or-qid> or --all, not both");
|
|
503
|
+
}
|
|
504
|
+
const requestedWorkspace = options.ws ? normalizeWorkspace(options.ws) : null;
|
|
505
|
+
const excluded = [];
|
|
506
|
+
let nodes;
|
|
507
|
+
if (options.all) {
|
|
508
|
+
if (requestedWorkspace && config.subgraphs[requestedWorkspace]) {
|
|
509
|
+
throw readOnlyWorkspaceError(requestedWorkspace);
|
|
457
510
|
}
|
|
458
|
-
if (
|
|
459
|
-
|
|
511
|
+
if (requestedWorkspace) {
|
|
512
|
+
const workspace = config.workspaces[requestedWorkspace];
|
|
513
|
+
if (!workspace) {
|
|
514
|
+
throw new errors_1.NotFoundError(`workspace not found: ${requestedWorkspace}`);
|
|
515
|
+
}
|
|
516
|
+
if (!workspace.enabled) {
|
|
517
|
+
throw new errors_1.ValidationError(`archive workspace ${requestedWorkspace} is disabled and not writable`);
|
|
518
|
+
}
|
|
460
519
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
520
|
+
nodes = Object.values(index.nodes)
|
|
521
|
+
.filter((node) => node.type === "archive")
|
|
522
|
+
.sort((a, b) => a.qid.localeCompare(b.qid))
|
|
523
|
+
.filter((node) => {
|
|
524
|
+
if (requestedWorkspace && node.ws !== requestedWorkspace) {
|
|
525
|
+
return false;
|
|
526
|
+
}
|
|
527
|
+
if (isReadOnlyArchiveProjection(config, node)) {
|
|
528
|
+
if (!requestedWorkspace) {
|
|
529
|
+
excluded.push({
|
|
530
|
+
workspace: node.ws,
|
|
531
|
+
qid: node.qid,
|
|
532
|
+
reason: "read_only_imported_subgraph",
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
assertWritableArchiveOwner(config, node);
|
|
538
|
+
return true;
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
const node = resolveArchiveNodeFromIndex(index, String(options.id), options.ws);
|
|
543
|
+
assertWritableArchiveOwner(config, node);
|
|
544
|
+
nodes = [node];
|
|
545
|
+
}
|
|
546
|
+
return {
|
|
547
|
+
nodes,
|
|
548
|
+
selection: {
|
|
549
|
+
requested_workspace: requestedWorkspace,
|
|
550
|
+
selected_workspaces: Array.from(new Set(nodes.map((node) => node.ws))).sort(),
|
|
551
|
+
excluded_read_only: excluded.sort((a, b) => a.qid.localeCompare(b.qid)),
|
|
552
|
+
},
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
function preflightArchiveCompression(root, node, today) {
|
|
556
|
+
for (const attribute of ["stored_path", "compressed_path"]) {
|
|
557
|
+
if (typeof node.attributes[attribute] !== "string" || !String(node.attributes[attribute]).trim()) {
|
|
558
|
+
throw new errors_1.ValidationError(`${node.qid}: ${attribute} is required before archive compression`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
const { sidecarPath, rawPath, zipPath } = archiveNodePaths(root, node);
|
|
562
|
+
const rawRelativePath = toPosixPath(path_1.default.relative(root, rawPath));
|
|
563
|
+
const zipRelativePath = toPosixPath(path_1.default.relative(root, zipPath));
|
|
564
|
+
const sidecarRelativePath = toPosixPath(path_1.default.relative(root, sidecarPath));
|
|
565
|
+
for (const relativePath of [rawRelativePath, zipRelativePath, sidecarRelativePath]) {
|
|
566
|
+
(0, filesystem_authority_1.withContainedPathSink)({ root, relativePath, operation: relativePath === rawRelativePath ? "read" : "replace" }, () => undefined);
|
|
567
|
+
}
|
|
568
|
+
if (!(0, filesystem_authority_1.containedPathExists)({ root, relativePath: rawRelativePath })) {
|
|
569
|
+
throw new errors_1.NotFoundError(`raw archive file missing for ${node.qid}: ${path_1.default.relative(root, rawPath)}`);
|
|
570
|
+
}
|
|
571
|
+
const rawData = (0, filesystem_authority_1.readContainedFile)({ root, relativePath: rawRelativePath }, null);
|
|
572
|
+
const parsed = (0, frontmatter_1.parseFrontmatter)((0, filesystem_authority_1.readContainedFile)({ root, relativePath: sidecarRelativePath }), sidecarPath);
|
|
573
|
+
if (parsed.frontmatter.type !== "archive" || parsed.frontmatter.id !== node.id) {
|
|
574
|
+
throw new errors_1.ValidationError(`${node.qid}: archive sidecar identity changed before compression`);
|
|
575
|
+
}
|
|
576
|
+
const zipData = (0, zip_1.createDeterministicZip)(path_1.default.basename(rawPath), rawData);
|
|
577
|
+
const nextFrontmatter = {
|
|
578
|
+
...parsed.frontmatter,
|
|
579
|
+
byte_size: String(rawData.length),
|
|
580
|
+
sha256: (0, archive_integrity_1.hashArchiveBuffer)(rawData),
|
|
581
|
+
compressed_sha256: (0, archive_integrity_1.hashArchiveBuffer)(zipData),
|
|
582
|
+
ingest_status: "compressed",
|
|
583
|
+
updated: today,
|
|
584
|
+
};
|
|
585
|
+
return {
|
|
586
|
+
zipRelativePath,
|
|
587
|
+
sidecarRelativePath,
|
|
588
|
+
zipData,
|
|
589
|
+
sidecarContent: formatArchiveSidecar(nextFrontmatter, parsed.body),
|
|
590
|
+
receipt: {
|
|
475
591
|
workspace: node.ws,
|
|
476
592
|
id: node.id,
|
|
477
593
|
qid: node.qid,
|
|
@@ -482,14 +598,35 @@ function runArchiveCompressCommandLocked(options) {
|
|
|
482
598
|
sha256: String(nextFrontmatter.sha256),
|
|
483
599
|
compressed_sha256: String(nextFrontmatter.compressed_sha256),
|
|
484
600
|
visibility: (0, visibility_1.normalizeVisibility)(typeof nextFrontmatter.visibility === "string" ? nextFrontmatter.visibility : undefined, "archive visibility"),
|
|
485
|
-
}
|
|
601
|
+
},
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
function runArchiveCompressCommandLocked(options) {
|
|
605
|
+
if (!options.all && !options.id) {
|
|
606
|
+
throw new errors_1.UsageError("archive compress requires <id-or-archive-uri-or-qid> or --all");
|
|
607
|
+
}
|
|
608
|
+
const config = (0, config_1.loadConfig)(options.root);
|
|
609
|
+
const { index } = (0, index_cache_1.loadIndex)({ root: options.root, config });
|
|
610
|
+
const { nodes, selection } = selectArchiveCompressionNodes(options, config, index);
|
|
611
|
+
const today = (0, date_1.formatDate)(options.now ?? new Date());
|
|
612
|
+
const plans = nodes.map((node) => preflightArchiveCompression(options.root, node, today));
|
|
613
|
+
const updated = [];
|
|
614
|
+
for (const plan of plans) {
|
|
615
|
+
(0, filesystem_authority_1.atomicReplaceContainedFile)({ root: options.root, relativePath: plan.zipRelativePath }, plan.zipData);
|
|
616
|
+
(0, filesystem_authority_1.atomicReplaceContainedFile)({ root: options.root, relativePath: plan.sidecarRelativePath }, plan.sidecarContent);
|
|
617
|
+
updated.push(plan.receipt);
|
|
486
618
|
}
|
|
487
619
|
maybeReindex(options.root);
|
|
488
620
|
if (options.json) {
|
|
489
|
-
console.log(JSON.stringify({ action: "compressed", count: updated.length, archives: updated }, null, 2));
|
|
621
|
+
console.log(JSON.stringify({ action: "compressed", count: updated.length, archives: updated, selection }, null, 2));
|
|
490
622
|
return;
|
|
491
623
|
}
|
|
492
624
|
console.log(`archive compressed: ${updated.length}`);
|
|
625
|
+
console.log(`selected workspaces: ${selection.selected_workspaces.join(", ") || "none"}`);
|
|
626
|
+
console.log(`excluded read-only projections: ${selection.excluded_read_only.length}`);
|
|
627
|
+
for (const excluded of selection.excluded_read_only) {
|
|
628
|
+
console.log(` - ${excluded.qid} (${excluded.reason})`);
|
|
629
|
+
}
|
|
493
630
|
}
|
|
494
631
|
function withArchiveLock(root, fn) {
|
|
495
632
|
const config = (0, config_1.loadConfig)(root);
|