@gmickel/gno 1.32.0 → 1.34.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 +17 -3
- package/assets/skill/SKILL.md +30 -0
- package/assets/skill/cli-reference.md +10 -2
- package/browser-extension/artifacts/{gno-browser-clipper-v1.32.0.zip → gno-browser-clipper-v1.34.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.34.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +5 -1
- package/spec/cli.md +60 -1
- package/spec/mcp.md +21 -0
- package/spec/output-schemas/audit-report.schema.json +284 -0
- package/spec/output-schemas/publish-artifact.schema.json +76 -1
- package/src/cli/commands/audit.ts +231 -0
- package/src/cli/commands/publish.ts +43 -7
- package/src/cli/errors.ts +9 -2
- package/src/cli/program.ts +112 -0
- package/src/core/audit-contract.ts +296 -0
- package/src/core/audit-freshness.ts +233 -0
- package/src/core/audit-links.ts +222 -0
- package/src/core/audit-provenance.ts +154 -0
- package/src/core/audit-report.ts +318 -0
- package/src/core/audit-workspace.ts +678 -0
- package/src/core/audit.ts +569 -0
- package/src/core/capture.ts +196 -3
- package/src/core/document-capabilities.ts +9 -8
- package/src/core/record-metadata.ts +33 -0
- package/src/ingestion/strip.ts +152 -26
- package/src/mcp/http-egress.ts +8 -0
- package/src/mcp/tools/audit.ts +97 -0
- package/src/mcp/tools/index.ts +13 -0
- package/src/publish/artifact-asset-codec.ts +75 -0
- package/src/publish/artifact-asset-contract.ts +152 -0
- package/src/publish/artifact-asset-parse.ts +401 -0
- package/src/publish/artifact-asset-sniff.ts +108 -0
- package/src/publish/artifact-asset-validate.ts +209 -0
- package/src/publish/artifact-assets.ts +58 -0
- package/src/publish/artifact-validation.ts +32 -6
- package/src/publish/artifact.ts +50 -3
- package/src/publish/attachment-bundle.ts +145 -0
- package/src/publish/attachment-discover.ts +203 -0
- package/src/publish/attachment-load.ts +133 -0
- package/src/publish/attachment-obsidian.ts +45 -0
- package/src/publish/attachment-path.ts +334 -0
- package/src/publish/attachment-raster.ts +852 -0
- package/src/publish/attachment-resolver.ts +280 -0
- package/src/publish/attachment-types.ts +54 -0
- package/src/publish/encrypted-export.ts +121 -44
- package/src/publish/export-attachments.ts +224 -0
- package/src/publish/export-service.ts +142 -80
- package/src/publish/obsidian-sanitize.ts +121 -13
- package/src/serve/routes/api.ts +2 -1
- package/src/store/sqlite/adapter.ts +82 -0
- package/src/store/sqlite/graph-link-bulk-resolver.ts +191 -0
- package/src/store/sqlite/graph-link-resolver.ts +241 -2
- package/browser-extension/artifacts/gno-browser-clipper-v1.32.0.zip.sha256 +0 -1
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared note sanitize + v1/v2 asset finalize helpers for publish export.
|
|
3
|
+
*
|
|
4
|
+
* @module src/publish/export-attachments
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { EgressLineage } from "../core/egress-provenance";
|
|
8
|
+
import type {
|
|
9
|
+
PublishArtifactNote,
|
|
10
|
+
PublishArtifactV1,
|
|
11
|
+
PublishArtifactV2,
|
|
12
|
+
} from "./artifact";
|
|
13
|
+
import type { AttachmentDiagnostic } from "./attachment-types";
|
|
14
|
+
|
|
15
|
+
import { stripFrontmatter } from "../ingestion/frontmatter";
|
|
16
|
+
import {
|
|
17
|
+
BUNDLED_RASTER_ASSETS_CAPABILITY,
|
|
18
|
+
buildEncryptedPublishArtifact,
|
|
19
|
+
} from "./artifact";
|
|
20
|
+
import { measureArtifactUploadBytes } from "./artifact-asset-codec";
|
|
21
|
+
import { validatePublishAssetContract } from "./artifact-asset-validate";
|
|
22
|
+
import {
|
|
23
|
+
attachAssetsToV1Artifact,
|
|
24
|
+
buildDeterministicAssets,
|
|
25
|
+
summarizeAssetEgress,
|
|
26
|
+
type PendingAssetPayload,
|
|
27
|
+
type PublishAssetEgressSummary,
|
|
28
|
+
} from "./attachment-resolver";
|
|
29
|
+
import { buildEncryptedArtifactPayload } from "./encrypted-export";
|
|
30
|
+
import {
|
|
31
|
+
sanitizeObsidianMarkdown,
|
|
32
|
+
sanitizePublishMarkdown,
|
|
33
|
+
type SanitizeWarning,
|
|
34
|
+
} from "./obsidian-sanitize";
|
|
35
|
+
|
|
36
|
+
export interface NoteBuildAccumulator {
|
|
37
|
+
diagnostics: AttachmentDiagnostic[];
|
|
38
|
+
encodedAssetBytes: number;
|
|
39
|
+
externalCount: number;
|
|
40
|
+
payloads: Map<string, PendingAssetPayload>;
|
|
41
|
+
preDedupRawBytes: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function sanitizeNoteMarkdown(input: {
|
|
45
|
+
basenameIndex: Map<string, string[]> | null;
|
|
46
|
+
collectionExcludes?: readonly string[];
|
|
47
|
+
collectionRoot: string | null;
|
|
48
|
+
existingAssetIds?: ReadonlySet<string>;
|
|
49
|
+
existingEncodedAssetBytes?: number;
|
|
50
|
+
noteSlug: string;
|
|
51
|
+
rawMarkdown: string;
|
|
52
|
+
sourceRelPath: string;
|
|
53
|
+
warnings: SanitizeWarning[];
|
|
54
|
+
}): Promise<{
|
|
55
|
+
diagnostics: AttachmentDiagnostic[];
|
|
56
|
+
externalCount: number;
|
|
57
|
+
markdown: string;
|
|
58
|
+
payloads: Map<string, PendingAssetPayload>;
|
|
59
|
+
preDedupRawBytes: number;
|
|
60
|
+
}> {
|
|
61
|
+
if (input.basenameIndex && input.collectionRoot) {
|
|
62
|
+
const sanitized = await sanitizePublishMarkdown(input.rawMarkdown, {
|
|
63
|
+
basenameIndex: input.basenameIndex,
|
|
64
|
+
collectionExcludes: input.collectionExcludes,
|
|
65
|
+
collectionRoot: input.collectionRoot,
|
|
66
|
+
existingAssetIds: input.existingAssetIds,
|
|
67
|
+
existingEncodedAssetBytes: input.existingEncodedAssetBytes,
|
|
68
|
+
noteSlug: input.noteSlug,
|
|
69
|
+
sourceRelPath: input.sourceRelPath,
|
|
70
|
+
});
|
|
71
|
+
input.warnings.push(...sanitized.warnings);
|
|
72
|
+
return {
|
|
73
|
+
diagnostics: sanitized.diagnostics,
|
|
74
|
+
externalCount: sanitized.externalCount,
|
|
75
|
+
markdown: stripFrontmatter(sanitized.markdown),
|
|
76
|
+
payloads: sanitized.payloads,
|
|
77
|
+
preDedupRawBytes: sanitized.preDedupRawBytes,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const sanitized = sanitizeObsidianMarkdown(input.rawMarkdown);
|
|
82
|
+
input.warnings.push(...sanitized.warnings);
|
|
83
|
+
return {
|
|
84
|
+
diagnostics: [],
|
|
85
|
+
externalCount: 0,
|
|
86
|
+
markdown: stripFrontmatter(sanitized.markdown),
|
|
87
|
+
payloads: new Map(),
|
|
88
|
+
preDedupRawBytes: 0,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function mergePayloads(
|
|
93
|
+
target: Map<string, PendingAssetPayload>,
|
|
94
|
+
source: Map<string, PendingAssetPayload>
|
|
95
|
+
): number {
|
|
96
|
+
let addedEncodedBytes = 0;
|
|
97
|
+
for (const [id, payload] of source) {
|
|
98
|
+
const existing = target.get(id);
|
|
99
|
+
if (!existing) {
|
|
100
|
+
target.set(id, {
|
|
101
|
+
...payload,
|
|
102
|
+
references: [...payload.references],
|
|
103
|
+
});
|
|
104
|
+
addedEncodedBytes += payload.data.length;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
existing.references.push(...payload.references);
|
|
108
|
+
}
|
|
109
|
+
return addedEncodedBytes;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function finalizeV1Artifact(
|
|
113
|
+
artifact: PublishArtifactV1,
|
|
114
|
+
acc: NoteBuildAccumulator
|
|
115
|
+
): { artifact: PublishArtifactV1; assetSummary: PublishAssetEgressSummary } {
|
|
116
|
+
const assets = buildDeterministicAssets(acc.payloads);
|
|
117
|
+
const withAssets = attachAssetsToV1Artifact(artifact, assets);
|
|
118
|
+
const contract = validatePublishAssetContract(withAssets, {
|
|
119
|
+
serializedUploadBytes: measureArtifactUploadBytes(withAssets),
|
|
120
|
+
});
|
|
121
|
+
if (!contract.ok) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`${contract.diagnostic.code}: ${contract.diagnostic.message}`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
const assetSummary = summarizeAssetEgress({
|
|
127
|
+
artifact: withAssets,
|
|
128
|
+
diagnostics: acc.diagnostics,
|
|
129
|
+
externalCount: acc.externalCount,
|
|
130
|
+
preDedupRawBytes: acc.preDedupRawBytes,
|
|
131
|
+
});
|
|
132
|
+
return { artifact: withAssets, assetSummary };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Validate assets, encrypt inside ReaderSpaceData, enforce outer upload size. */
|
|
136
|
+
export async function finalizeEncryptedArtifact(input: {
|
|
137
|
+
acc: NoteBuildAccumulator;
|
|
138
|
+
egressLineage: EgressLineage;
|
|
139
|
+
exportedAt: string;
|
|
140
|
+
homeNoteSlug?: string;
|
|
141
|
+
notes: PublishArtifactNote[];
|
|
142
|
+
passphrase: string;
|
|
143
|
+
routeSlug: string;
|
|
144
|
+
sourceType: "note" | "collection";
|
|
145
|
+
summary: string;
|
|
146
|
+
title: string;
|
|
147
|
+
}): Promise<{
|
|
148
|
+
artifact: PublishArtifactV2;
|
|
149
|
+
assetSummary: PublishAssetEgressSummary;
|
|
150
|
+
}> {
|
|
151
|
+
const assets = buildDeterministicAssets(input.acc.payloads);
|
|
152
|
+
// Probe uses a non-encrypted visibility so asset contract validation runs
|
|
153
|
+
// without requiring a public manifest; ciphertext wraps the real payload.
|
|
154
|
+
const space: PublishArtifactV1["spaces"][number] = {
|
|
155
|
+
notes: input.notes,
|
|
156
|
+
routeSlug: input.routeSlug,
|
|
157
|
+
sourceType: input.sourceType,
|
|
158
|
+
summary: input.summary,
|
|
159
|
+
title: input.title,
|
|
160
|
+
visibility: "secret-link",
|
|
161
|
+
};
|
|
162
|
+
if (input.homeNoteSlug !== undefined) {
|
|
163
|
+
space.homeNoteSlug = input.homeNoteSlug;
|
|
164
|
+
}
|
|
165
|
+
const probe: PublishArtifactV1 = {
|
|
166
|
+
egressLineage: input.egressLineage,
|
|
167
|
+
exportedAt: input.exportedAt,
|
|
168
|
+
source: input.routeSlug,
|
|
169
|
+
spaces: [space],
|
|
170
|
+
version: 1,
|
|
171
|
+
...(assets.length > 0
|
|
172
|
+
? {
|
|
173
|
+
assets,
|
|
174
|
+
requiredCapabilities: [BUNDLED_RASTER_ASSETS_CAPABILITY],
|
|
175
|
+
}
|
|
176
|
+
: {}),
|
|
177
|
+
};
|
|
178
|
+
const contract = validatePublishAssetContract(probe);
|
|
179
|
+
if (!contract.ok) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`${contract.diagnostic.code}: ${contract.diagnostic.message}`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const encrypted = await buildEncryptedArtifactPayload({
|
|
186
|
+
assets,
|
|
187
|
+
exportedAt: input.exportedAt,
|
|
188
|
+
homeNoteSlug: input.homeNoteSlug,
|
|
189
|
+
notes: input.notes,
|
|
190
|
+
passphrase: input.passphrase,
|
|
191
|
+
routeSlug: input.routeSlug,
|
|
192
|
+
sourceType: input.sourceType,
|
|
193
|
+
summary: input.summary,
|
|
194
|
+
title: input.title,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const artifact = buildEncryptedPublishArtifact({
|
|
198
|
+
egressLineage: input.egressLineage,
|
|
199
|
+
encryptedPayload: encrypted.encryptedPayload,
|
|
200
|
+
requiredCapabilities:
|
|
201
|
+
assets.length > 0 ? [BUNDLED_RASTER_ASSETS_CAPABILITY] : undefined,
|
|
202
|
+
routeSlug: input.routeSlug,
|
|
203
|
+
secretToken: encrypted.secretToken,
|
|
204
|
+
sourceType: input.sourceType,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const outerContract = validatePublishAssetContract(artifact, {
|
|
208
|
+
serializedUploadBytes: measureArtifactUploadBytes(artifact),
|
|
209
|
+
});
|
|
210
|
+
if (!outerContract.ok) {
|
|
211
|
+
throw new Error(
|
|
212
|
+
`${outerContract.diagnostic.code}: ${outerContract.diagnostic.message}`
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const assetSummary = summarizeAssetEgress({
|
|
217
|
+
assets,
|
|
218
|
+
artifact,
|
|
219
|
+
diagnostics: input.acc.diagnostics,
|
|
220
|
+
externalCount: input.acc.externalCount,
|
|
221
|
+
preDedupRawBytes: input.acc.preDedupRawBytes,
|
|
222
|
+
});
|
|
223
|
+
return { artifact, assetSummary };
|
|
224
|
+
}
|
|
@@ -9,10 +9,9 @@ import type { DocumentRow, StorePort, TagRow } from "../store/types";
|
|
|
9
9
|
|
|
10
10
|
import { enforceCollectionEgressWithAudit } from "../core/egress-enforcement";
|
|
11
11
|
import { parseRef } from "../core/ref-parser";
|
|
12
|
-
import { parseFrontmatter
|
|
12
|
+
import { parseFrontmatter } from "../ingestion/frontmatter";
|
|
13
13
|
import { getContentBatch } from "../store/content-batch";
|
|
14
14
|
import {
|
|
15
|
-
buildEncryptedPublishArtifact,
|
|
16
15
|
buildPublishArtifact,
|
|
17
16
|
buildExportedMetadata,
|
|
18
17
|
derivePublishSlug,
|
|
@@ -24,10 +23,19 @@ import {
|
|
|
24
23
|
type PublishArtifactNote,
|
|
25
24
|
type PublishVisibility,
|
|
26
25
|
} from "./artifact";
|
|
27
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
buildAttachmentBasenameIndex,
|
|
28
|
+
type PublishAssetEgressSummary,
|
|
29
|
+
} from "./attachment-resolver";
|
|
30
|
+
import {
|
|
31
|
+
finalizeEncryptedArtifact,
|
|
32
|
+
finalizeV1Artifact,
|
|
33
|
+
mergePayloads,
|
|
34
|
+
sanitizeNoteMarkdown,
|
|
35
|
+
type NoteBuildAccumulator,
|
|
36
|
+
} from "./export-attachments";
|
|
28
37
|
import {
|
|
29
38
|
isPublishDisabledByFrontmatter,
|
|
30
|
-
sanitizeObsidianMarkdown,
|
|
31
39
|
type SanitizeWarning,
|
|
32
40
|
} from "./obsidian-sanitize";
|
|
33
41
|
|
|
@@ -138,7 +146,10 @@ async function exportCollectionArtifact(
|
|
|
138
146
|
target: string,
|
|
139
147
|
options: PublishExportCoreOptions,
|
|
140
148
|
warnings: SanitizeWarning[]
|
|
141
|
-
) {
|
|
149
|
+
): Promise<{
|
|
150
|
+
artifact: PublishArtifact;
|
|
151
|
+
assetSummary: PublishAssetEgressSummary;
|
|
152
|
+
} | null> {
|
|
142
153
|
const collection = resolveCollection(collections, target);
|
|
143
154
|
if (!collection) {
|
|
144
155
|
return null;
|
|
@@ -174,8 +185,21 @@ async function exportCollectionArtifact(
|
|
|
174
185
|
|
|
175
186
|
const contentByHash = contentResult.value;
|
|
176
187
|
const tagsByDocId = tagsResult.value;
|
|
188
|
+
const visibility = resolveVisibility(options.visibility);
|
|
189
|
+
const basenameIndex = await buildAttachmentBasenameIndex(
|
|
190
|
+
collection.path,
|
|
191
|
+
collection.exclude
|
|
192
|
+
);
|
|
177
193
|
|
|
194
|
+
const acc: NoteBuildAccumulator = {
|
|
195
|
+
diagnostics: [],
|
|
196
|
+
encodedAssetBytes: 0,
|
|
197
|
+
externalCount: 0,
|
|
198
|
+
payloads: new Map(),
|
|
199
|
+
preDedupRawBytes: 0,
|
|
200
|
+
};
|
|
178
201
|
const notes: PublishArtifactNote[] = [];
|
|
202
|
+
|
|
179
203
|
for (const doc of activeDocs) {
|
|
180
204
|
if (!doc.mirrorHash) {
|
|
181
205
|
throw new Error(`Document has no converted content: ${doc.uri}`);
|
|
@@ -187,17 +211,34 @@ async function exportCollectionArtifact(
|
|
|
187
211
|
if (isPublishDisabledByFrontmatter(rawMarkdown)) {
|
|
188
212
|
continue;
|
|
189
213
|
}
|
|
214
|
+
|
|
190
215
|
const frontmatter = parseFrontmatter(rawMarkdown).metadata;
|
|
191
|
-
const sanitized = sanitizeObsidianMarkdown(rawMarkdown);
|
|
192
|
-
warnings.push(...sanitized.warnings);
|
|
193
|
-
const markdown = stripFrontmatter(sanitized.markdown);
|
|
194
|
-
const tags = tagsByDocId.get(doc.id) ?? [];
|
|
195
216
|
const title = deriveExportedTitle(doc);
|
|
217
|
+
const slug = deriveExportedSlug(doc);
|
|
218
|
+
const sanitized = await sanitizeNoteMarkdown({
|
|
219
|
+
basenameIndex,
|
|
220
|
+
collectionExcludes: collection.exclude,
|
|
221
|
+
collectionRoot: collection.path,
|
|
222
|
+
existingAssetIds: new Set(acc.payloads.keys()),
|
|
223
|
+
existingEncodedAssetBytes: acc.encodedAssetBytes,
|
|
224
|
+
noteSlug: slug,
|
|
225
|
+
rawMarkdown,
|
|
226
|
+
sourceRelPath: doc.relPath,
|
|
227
|
+
warnings,
|
|
228
|
+
});
|
|
229
|
+
acc.diagnostics.push(...sanitized.diagnostics);
|
|
230
|
+
acc.externalCount += sanitized.externalCount;
|
|
231
|
+
acc.preDedupRawBytes += sanitized.preDedupRawBytes;
|
|
232
|
+
acc.encodedAssetBytes += mergePayloads(acc.payloads, sanitized.payloads);
|
|
196
233
|
notes.push({
|
|
197
|
-
markdown,
|
|
198
|
-
metadata: buildExportedMetadata(
|
|
199
|
-
|
|
200
|
-
|
|
234
|
+
markdown: sanitized.markdown,
|
|
235
|
+
metadata: buildExportedMetadata(
|
|
236
|
+
doc,
|
|
237
|
+
frontmatter,
|
|
238
|
+
tagsByDocId.get(doc.id) ?? []
|
|
239
|
+
),
|
|
240
|
+
slug,
|
|
241
|
+
summary: deriveExportedSummary(sanitized.markdown, frontmatter),
|
|
201
242
|
title,
|
|
202
243
|
});
|
|
203
244
|
}
|
|
@@ -217,7 +258,6 @@ async function exportCollectionArtifact(
|
|
|
217
258
|
collection.name,
|
|
218
259
|
target,
|
|
219
260
|
]);
|
|
220
|
-
const visibility = resolveVisibility(options.visibility);
|
|
221
261
|
const { lineage } = await enforceCollectionEgressWithAudit({
|
|
222
262
|
collections,
|
|
223
263
|
collectionNames: [collection.name],
|
|
@@ -235,7 +275,9 @@ async function exportCollectionArtifact(
|
|
|
235
275
|
);
|
|
236
276
|
}
|
|
237
277
|
|
|
238
|
-
|
|
278
|
+
return finalizeEncryptedArtifact({
|
|
279
|
+
acc,
|
|
280
|
+
egressLineage: lineage,
|
|
239
281
|
exportedAt: new Date().toISOString(),
|
|
240
282
|
homeNoteSlug: chooseHomeNoteSlug(notes),
|
|
241
283
|
notes,
|
|
@@ -245,26 +287,21 @@ async function exportCollectionArtifact(
|
|
|
245
287
|
summary,
|
|
246
288
|
title,
|
|
247
289
|
});
|
|
290
|
+
}
|
|
248
291
|
|
|
249
|
-
|
|
292
|
+
return finalizeV1Artifact(
|
|
293
|
+
buildPublishArtifact({
|
|
250
294
|
egressLineage: lineage,
|
|
251
|
-
|
|
295
|
+
homeNoteSlug: chooseHomeNoteSlug(notes),
|
|
296
|
+
notes,
|
|
252
297
|
routeSlug,
|
|
253
|
-
secretToken: encrypted.secretToken,
|
|
254
298
|
sourceType: "collection",
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
notes,
|
|
262
|
-
routeSlug,
|
|
263
|
-
sourceType: "collection",
|
|
264
|
-
summary,
|
|
265
|
-
title,
|
|
266
|
-
visibility,
|
|
267
|
-
});
|
|
299
|
+
summary,
|
|
300
|
+
title,
|
|
301
|
+
visibility,
|
|
302
|
+
}),
|
|
303
|
+
acc
|
|
304
|
+
);
|
|
268
305
|
}
|
|
269
306
|
|
|
270
307
|
async function exportDocumentArtifact(
|
|
@@ -273,28 +310,47 @@ async function exportDocumentArtifact(
|
|
|
273
310
|
target: string,
|
|
274
311
|
options: PublishExportCoreOptions,
|
|
275
312
|
warnings: SanitizeWarning[]
|
|
276
|
-
) {
|
|
313
|
+
): Promise<{
|
|
314
|
+
artifact: PublishArtifact;
|
|
315
|
+
assetSummary: PublishAssetEgressSummary;
|
|
316
|
+
}> {
|
|
277
317
|
const doc = await lookupDocument(store, target);
|
|
278
318
|
if (!doc?.active) {
|
|
279
319
|
throw new Error(`Document not found: ${target}`);
|
|
280
320
|
}
|
|
281
321
|
|
|
322
|
+
const collection =
|
|
323
|
+
collections.find((entry) => entry.name === doc.collection) ?? null;
|
|
282
324
|
const rawMarkdown = await loadDocumentMarkdown(store, doc);
|
|
283
325
|
if (isPublishDisabledByFrontmatter(rawMarkdown)) {
|
|
284
326
|
throw new Error(
|
|
285
327
|
`Refused to export: ${doc.uri} has publish: false in frontmatter`
|
|
286
328
|
);
|
|
287
329
|
}
|
|
330
|
+
|
|
288
331
|
const frontmatter = parseFrontmatter(rawMarkdown).metadata;
|
|
289
|
-
const sanitized = sanitizeObsidianMarkdown(rawMarkdown);
|
|
290
|
-
warnings.push(...sanitized.warnings);
|
|
291
|
-
const markdown = stripFrontmatter(sanitized.markdown);
|
|
292
|
-
const tags = await loadDocumentTags(store, doc);
|
|
293
332
|
const title = options.title ?? deriveExportedTitle(doc);
|
|
294
|
-
const summary =
|
|
295
|
-
options.summary ?? deriveExportedSummary(markdown, frontmatter);
|
|
296
333
|
const slug = deriveExportedSlug(doc);
|
|
297
334
|
const visibility = resolveVisibility(options.visibility);
|
|
335
|
+
const bundleAttachments = collection !== null;
|
|
336
|
+
const basenameIndex =
|
|
337
|
+
bundleAttachments && collection
|
|
338
|
+
? await buildAttachmentBasenameIndex(collection.path, collection.exclude)
|
|
339
|
+
: null;
|
|
340
|
+
|
|
341
|
+
const sanitized = await sanitizeNoteMarkdown({
|
|
342
|
+
basenameIndex,
|
|
343
|
+
collectionExcludes: collection?.exclude,
|
|
344
|
+
collectionRoot: bundleAttachments && collection ? collection.path : null,
|
|
345
|
+
noteSlug: slug,
|
|
346
|
+
rawMarkdown,
|
|
347
|
+
sourceRelPath: doc.relPath,
|
|
348
|
+
warnings,
|
|
349
|
+
});
|
|
350
|
+
const markdown = sanitized.markdown;
|
|
351
|
+
const summary =
|
|
352
|
+
options.summary ?? deriveExportedSummary(markdown, frontmatter);
|
|
353
|
+
const tags = await loadDocumentTags(store, doc);
|
|
298
354
|
const routeSlug = derivePublishSlug([options.routeSlug ?? "", slug, target]);
|
|
299
355
|
const { lineage } = await enforceCollectionEgressWithAudit({
|
|
300
356
|
collections,
|
|
@@ -306,6 +362,25 @@ async function exportDocumentArtifact(
|
|
|
306
362
|
store,
|
|
307
363
|
});
|
|
308
364
|
|
|
365
|
+
const note: PublishArtifactNote = {
|
|
366
|
+
markdown,
|
|
367
|
+
metadata: buildExportedMetadata(doc, frontmatter, tags),
|
|
368
|
+
slug,
|
|
369
|
+
summary,
|
|
370
|
+
title,
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const acc: NoteBuildAccumulator = {
|
|
374
|
+
diagnostics: sanitized.diagnostics,
|
|
375
|
+
encodedAssetBytes: [...sanitized.payloads.values()].reduce(
|
|
376
|
+
(total, payload) => total + payload.data.length,
|
|
377
|
+
0
|
|
378
|
+
),
|
|
379
|
+
externalCount: sanitized.externalCount,
|
|
380
|
+
payloads: sanitized.payloads,
|
|
381
|
+
preDedupRawBytes: sanitized.preDedupRawBytes,
|
|
382
|
+
};
|
|
383
|
+
|
|
309
384
|
if (visibility === "encrypted") {
|
|
310
385
|
if (!options.encryptionPassphrase) {
|
|
311
386
|
throw new Error(
|
|
@@ -313,54 +388,36 @@ async function exportDocumentArtifact(
|
|
|
313
388
|
);
|
|
314
389
|
}
|
|
315
390
|
|
|
316
|
-
|
|
391
|
+
return finalizeEncryptedArtifact({
|
|
392
|
+
acc,
|
|
393
|
+
egressLineage: lineage,
|
|
317
394
|
exportedAt: new Date().toISOString(),
|
|
318
|
-
notes: [
|
|
319
|
-
{
|
|
320
|
-
markdown,
|
|
321
|
-
metadata: buildExportedMetadata(doc, frontmatter, tags),
|
|
322
|
-
slug,
|
|
323
|
-
summary,
|
|
324
|
-
title,
|
|
325
|
-
},
|
|
326
|
-
],
|
|
395
|
+
notes: [note],
|
|
327
396
|
passphrase: options.encryptionPassphrase,
|
|
328
397
|
routeSlug,
|
|
329
398
|
sourceType: "note",
|
|
330
399
|
summary,
|
|
331
400
|
title,
|
|
332
401
|
});
|
|
402
|
+
}
|
|
333
403
|
|
|
334
|
-
|
|
404
|
+
return finalizeV1Artifact(
|
|
405
|
+
buildPublishArtifact({
|
|
335
406
|
egressLineage: lineage,
|
|
336
|
-
|
|
407
|
+
notes: [note],
|
|
337
408
|
routeSlug,
|
|
338
|
-
secretToken: encrypted.secretToken,
|
|
339
409
|
sourceType: "note",
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
{
|
|
347
|
-
markdown,
|
|
348
|
-
metadata: buildExportedMetadata(doc, frontmatter, tags),
|
|
349
|
-
slug,
|
|
350
|
-
summary,
|
|
351
|
-
title,
|
|
352
|
-
},
|
|
353
|
-
],
|
|
354
|
-
routeSlug,
|
|
355
|
-
sourceType: "note",
|
|
356
|
-
summary,
|
|
357
|
-
title,
|
|
358
|
-
visibility,
|
|
359
|
-
});
|
|
410
|
+
summary,
|
|
411
|
+
title,
|
|
412
|
+
visibility,
|
|
413
|
+
}),
|
|
414
|
+
acc
|
|
415
|
+
);
|
|
360
416
|
}
|
|
361
417
|
|
|
362
418
|
export interface ExportPublishArtifactResult {
|
|
363
419
|
artifact: PublishArtifact;
|
|
420
|
+
assetSummary: PublishAssetEgressSummary;
|
|
364
421
|
warnings: SanitizeWarning[];
|
|
365
422
|
}
|
|
366
423
|
|
|
@@ -371,14 +428,15 @@ export async function exportPublishArtifact(input: {
|
|
|
371
428
|
target: string;
|
|
372
429
|
}): Promise<ExportPublishArtifactResult> {
|
|
373
430
|
const warnings: SanitizeWarning[] = [];
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
431
|
+
const collectionExport = await exportCollectionArtifact(
|
|
432
|
+
input.store,
|
|
433
|
+
input.collections,
|
|
434
|
+
input.target,
|
|
435
|
+
input.options,
|
|
436
|
+
warnings
|
|
437
|
+
);
|
|
438
|
+
const result =
|
|
439
|
+
collectionExport ??
|
|
382
440
|
(await exportDocumentArtifact(
|
|
383
441
|
input.store,
|
|
384
442
|
input.collections,
|
|
@@ -386,5 +444,9 @@ export async function exportPublishArtifact(input: {
|
|
|
386
444
|
input.options,
|
|
387
445
|
warnings
|
|
388
446
|
));
|
|
389
|
-
return {
|
|
447
|
+
return {
|
|
448
|
+
artifact: result.artifact,
|
|
449
|
+
assetSummary: result.assetSummary,
|
|
450
|
+
warnings,
|
|
451
|
+
};
|
|
390
452
|
}
|