@gmickel/gno 1.32.0 → 1.33.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 +11 -2
- package/assets/skill/SKILL.md +11 -0
- package/assets/skill/cli-reference.md +10 -2
- package/browser-extension/artifacts/{gno-browser-clipper-v1.32.0.zip → gno-browser-clipper-v1.33.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.33.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +5 -1
- package/spec/cli.md +16 -1
- package/spec/output-schemas/publish-artifact.schema.json +76 -1
- package/src/cli/commands/publish.ts +43 -7
- package/src/ingestion/strip.ts +152 -26
- 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/browser-extension/artifacts/gno-browser-clipper-v1.32.0.zip.sha256 +0 -1
|
@@ -2,12 +2,23 @@
|
|
|
2
2
|
* Obsidian-aware markdown pre-processor for publish export.
|
|
3
3
|
*
|
|
4
4
|
* Strips wikilinks, drops navigation sidebar idioms, removes references to
|
|
5
|
-
* private (`_internal/`) paths, and
|
|
6
|
-
*
|
|
5
|
+
* private (`_internal/`) paths, and resolves/bundles local raster embeds when
|
|
6
|
+
* an attachment context is provided (otherwise drops image embeds with a
|
|
7
|
+
* warning for backward compatibility).
|
|
7
8
|
*
|
|
8
9
|
* @module src/publish/obsidian-sanitize
|
|
9
10
|
*/
|
|
10
11
|
|
|
12
|
+
import { discoverImageOccurrences } from "./attachment-discover";
|
|
13
|
+
import { safePercentDecode } from "./attachment-path";
|
|
14
|
+
import {
|
|
15
|
+
rewriteAttachmentsInMarkdown,
|
|
16
|
+
type AttachmentDiagnostic,
|
|
17
|
+
type AttachmentResolveContext,
|
|
18
|
+
type AttachmentRewriteResult,
|
|
19
|
+
type PendingAssetPayload,
|
|
20
|
+
} from "./attachment-resolver";
|
|
21
|
+
|
|
11
22
|
const WIKILINK_INTERNAL_PREFIX = /_internal\//i;
|
|
12
23
|
const NAV_SIDEBAR_LINE = /^(!?\[\[[^\]]+\]\]\s*\|?\s*)+$/;
|
|
13
24
|
const IMAGE_EMBED = /!\[\[([^\]]+)\]\]/g;
|
|
@@ -16,6 +27,7 @@ const ALIASED_WIKILINK = /\[\[([^\]|]+)\|([^\]]+)\]\]/g;
|
|
|
16
27
|
const BARE_WIKILINK = /\[\[([^\]]+)\]\]/g;
|
|
17
28
|
const TAIL_SEGMENT = /[^/]+$/;
|
|
18
29
|
const BLOCK_ID_SUFFIX = /#\^?[\w-]+$/;
|
|
30
|
+
const EXTERNAL_DESTINATION = /^(?:\/\/|[a-z][a-z0-9+.-]*:)/iu;
|
|
19
31
|
|
|
20
32
|
export interface SanitizeWarning {
|
|
21
33
|
kind:
|
|
@@ -31,6 +43,13 @@ export interface SanitizeResult {
|
|
|
31
43
|
warnings: SanitizeWarning[];
|
|
32
44
|
}
|
|
33
45
|
|
|
46
|
+
export interface PublishSanitizeResult extends SanitizeResult {
|
|
47
|
+
diagnostics: AttachmentDiagnostic[];
|
|
48
|
+
externalCount: number;
|
|
49
|
+
payloads: Map<string, PendingAssetPayload>;
|
|
50
|
+
preDedupRawBytes: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
34
53
|
const splitFrontmatter = (
|
|
35
54
|
source: string
|
|
36
55
|
): { body: string; frontmatter: string } => {
|
|
@@ -61,8 +80,57 @@ const deriveLinkDisplay = (target: string): string => {
|
|
|
61
80
|
return tail.trim() || raw;
|
|
62
81
|
};
|
|
63
82
|
|
|
64
|
-
|
|
65
|
-
const
|
|
83
|
+
const isPrivateImageReference = (sourceRef: string): boolean => {
|
|
84
|
+
const trimmed = sourceRef.trim();
|
|
85
|
+
if (EXTERNAL_DESTINATION.test(trimmed)) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
const decoded = safePercentDecode(trimmed);
|
|
89
|
+
if (decoded === null) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return (
|
|
93
|
+
decoded
|
|
94
|
+
.split(/[?#]/u, 1)[0]
|
|
95
|
+
?.split("/")
|
|
96
|
+
.some((segment) => segment.toLowerCase() === "_internal") ?? false
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Remove private image references before attachment resolution can read bytes
|
|
102
|
+
* or replace the authored path with an opaque gno-asset sentinel.
|
|
103
|
+
*/
|
|
104
|
+
const stripPrivateImageReferences = (
|
|
105
|
+
body: string
|
|
106
|
+
): { markdown: string; warnings: SanitizeWarning[] } => {
|
|
107
|
+
const replacements = discoverImageOccurrences(body)
|
|
108
|
+
.filter((occurrence) => isPrivateImageReference(occurrence.sourceRef))
|
|
109
|
+
.map((occurrence) => ({
|
|
110
|
+
detail: occurrence.sourceRef.trim(),
|
|
111
|
+
end: occurrence.end,
|
|
112
|
+
start: occurrence.start,
|
|
113
|
+
}));
|
|
114
|
+
let markdown = body;
|
|
115
|
+
for (const replacement of [...replacements].sort(
|
|
116
|
+
(left, right) => right.start - left.start
|
|
117
|
+
)) {
|
|
118
|
+
markdown =
|
|
119
|
+
markdown.slice(0, replacement.start) + markdown.slice(replacement.end);
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
markdown,
|
|
123
|
+
warnings: replacements.map((replacement) => ({
|
|
124
|
+
detail: replacement.detail,
|
|
125
|
+
kind: "internal-reference-stripped" as const,
|
|
126
|
+
})),
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const applyWikilinkSanitize = (
|
|
131
|
+
body: string,
|
|
132
|
+
options: { dropImageEmbeds: boolean }
|
|
133
|
+
): { markdown: string; warnings: SanitizeWarning[] } => {
|
|
66
134
|
const warnings: SanitizeWarning[] = [];
|
|
67
135
|
const lines = body.split("\n");
|
|
68
136
|
const output: string[] = [];
|
|
@@ -81,13 +149,15 @@ export function sanitizeObsidianMarkdown(source: string): SanitizeResult {
|
|
|
81
149
|
|
|
82
150
|
let line = rawLine;
|
|
83
151
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
152
|
+
if (options.dropImageEmbeds) {
|
|
153
|
+
line = line.replace(IMAGE_EMBED, (_match, target: string) => {
|
|
154
|
+
warnings.push({
|
|
155
|
+
kind: "image-embed-dropped",
|
|
156
|
+
detail: target.trim(),
|
|
157
|
+
});
|
|
158
|
+
return "";
|
|
88
159
|
});
|
|
89
|
-
|
|
90
|
-
});
|
|
160
|
+
}
|
|
91
161
|
|
|
92
162
|
line = line.replace(INTERNAL_WIKILINK, (match) => {
|
|
93
163
|
warnings.push({
|
|
@@ -134,9 +204,46 @@ export function sanitizeObsidianMarkdown(source: string): SanitizeResult {
|
|
|
134
204
|
output.push(line);
|
|
135
205
|
}
|
|
136
206
|
|
|
207
|
+
return { markdown: output.join("\n"), warnings };
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Legacy sync sanitizer: drops Obsidian image embeds and converts wikilinks.
|
|
212
|
+
* Prefer `sanitizePublishMarkdown` when attachment bundling is enabled.
|
|
213
|
+
*/
|
|
214
|
+
export function sanitizeObsidianMarkdown(source: string): SanitizeResult {
|
|
215
|
+
const { body, frontmatter } = splitFrontmatter(source);
|
|
216
|
+
const sanitized = applyWikilinkSanitize(body, { dropImageEmbeds: true });
|
|
217
|
+
return {
|
|
218
|
+
markdown: `${frontmatter}${sanitized.markdown}`,
|
|
219
|
+
warnings: sanitized.warnings,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Parser-aware publish sanitize: resolve/rewrite local rasters in the same
|
|
225
|
+
* pass as Obsidian cleanup (image discovery is not a second independent scan).
|
|
226
|
+
*/
|
|
227
|
+
export async function sanitizePublishMarkdown(
|
|
228
|
+
source: string,
|
|
229
|
+
attachmentCtx: AttachmentResolveContext
|
|
230
|
+
): Promise<PublishSanitizeResult> {
|
|
231
|
+
const { body, frontmatter } = splitFrontmatter(source);
|
|
232
|
+
const privateImages = stripPrivateImageReferences(body);
|
|
233
|
+
const rewritten: AttachmentRewriteResult = await rewriteAttachmentsInMarkdown(
|
|
234
|
+
privateImages.markdown,
|
|
235
|
+
attachmentCtx
|
|
236
|
+
);
|
|
237
|
+
const sanitized = applyWikilinkSanitize(rewritten.markdown, {
|
|
238
|
+
dropImageEmbeds: false,
|
|
239
|
+
});
|
|
137
240
|
return {
|
|
138
|
-
|
|
139
|
-
|
|
241
|
+
diagnostics: rewritten.diagnostics,
|
|
242
|
+
externalCount: rewritten.externalCount,
|
|
243
|
+
markdown: `${frontmatter}${sanitized.markdown}`,
|
|
244
|
+
payloads: rewritten.payloads,
|
|
245
|
+
preDedupRawBytes: rewritten.preDedupRawBytes,
|
|
246
|
+
warnings: [...privateImages.warnings, ...sanitized.warnings],
|
|
140
247
|
};
|
|
141
248
|
}
|
|
142
249
|
|
|
@@ -159,7 +266,8 @@ export function formatSanitizeWarnings(warnings: SanitizeWarning[]): string[] {
|
|
|
159
266
|
}
|
|
160
267
|
|
|
161
268
|
const labels: Record<SanitizeWarning["kind"], string> = {
|
|
162
|
-
"image-embed-dropped":
|
|
269
|
+
"image-embed-dropped":
|
|
270
|
+
"Image embeds dropped (unsupported, missing, or unresolved)",
|
|
163
271
|
"internal-reference-stripped": "Private `_internal/` references stripped",
|
|
164
272
|
"nav-sidebar-dropped": "Navigation sidebar lines dropped",
|
|
165
273
|
"wikilink-unresolved":
|
package/src/serve/routes/api.ts
CHANGED
|
@@ -1280,7 +1280,7 @@ export async function handlePublishExport(
|
|
|
1280
1280
|
}
|
|
1281
1281
|
|
|
1282
1282
|
try {
|
|
1283
|
-
const { artifact, warnings } = await exportPublishArtifact({
|
|
1283
|
+
const { artifact, assetSummary, warnings } = await exportPublishArtifact({
|
|
1284
1284
|
collections: config.collections,
|
|
1285
1285
|
options: {
|
|
1286
1286
|
encryptionPassphrase: body.encryptionPassphrase,
|
|
@@ -1295,6 +1295,7 @@ export async function handlePublishExport(
|
|
|
1295
1295
|
|
|
1296
1296
|
return jsonResponse({
|
|
1297
1297
|
artifact,
|
|
1298
|
+
assetSummary,
|
|
1298
1299
|
fileName: derivePublishArtifactFilename(artifact),
|
|
1299
1300
|
uploadUrl: "https://gno.sh/studio",
|
|
1300
1301
|
warnings,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
cb5c4bba8cf1252da4e4fc89f1dc01a8df306851cccb50f636d89a6ef3133cff gno-browser-clipper-v1.32.0.zip
|