@gmickel/gno 0.41.0 → 0.42.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 +70 -0
- package/assets/screenshots/publish-reader.jpg +0 -0
- package/assets/skill/SKILL.md +2 -0
- package/assets/skill/cli-reference.md +28 -0
- package/package.json +1 -1
- package/src/cli/commands/embed.ts +216 -8
- package/src/cli/commands/index.ts +6 -0
- package/src/cli/commands/publish.ts +140 -0
- package/src/cli/options.ts +2 -0
- package/src/cli/program.ts +72 -0
- package/src/embed/batch.ts +154 -3
- package/src/publish/artifact.ts +252 -0
- package/src/publish/export-service.ts +238 -0
- package/src/serve/AGENTS.md +17 -16
- package/src/serve/CLAUDE.md +17 -16
- package/src/serve/public/lib/publish-export.ts +21 -0
- package/src/serve/public/pages/Collections.tsx +63 -0
- package/src/serve/public/pages/DocView.tsx +71 -0
- package/src/serve/routes/api.ts +82 -0
- package/src/serve/server.ts +12 -0
- package/src/store/vector/sqlite-vec.ts +11 -6
package/src/serve/routes/api.ts
CHANGED
|
@@ -91,6 +91,12 @@ import {
|
|
|
91
91
|
import { searchHybrid } from "../../pipeline/hybrid";
|
|
92
92
|
import { validateQueryModes } from "../../pipeline/query-modes";
|
|
93
93
|
import { searchBm25 } from "../../pipeline/search";
|
|
94
|
+
import {
|
|
95
|
+
derivePublishArtifactFilename,
|
|
96
|
+
isPublishVisibility,
|
|
97
|
+
type PublishVisibility,
|
|
98
|
+
} from "../../publish/artifact";
|
|
99
|
+
import { exportPublishArtifact } from "../../publish/export-service";
|
|
94
100
|
import { buildBrowseTree, normalizeBrowsePath } from "../browse-tree";
|
|
95
101
|
import { applyConfigChange, applyConfigChangeTyped } from "../config-sync";
|
|
96
102
|
import { getConnectorStatuses, installConnector } from "../connectors";
|
|
@@ -333,6 +339,14 @@ export interface CreateEditableCopyRequestBody {
|
|
|
333
339
|
uri?: string;
|
|
334
340
|
}
|
|
335
341
|
|
|
342
|
+
export interface PublishExportRequestBody {
|
|
343
|
+
slug?: string;
|
|
344
|
+
summary?: string;
|
|
345
|
+
target: string;
|
|
346
|
+
title?: string;
|
|
347
|
+
visibility?: PublishVisibility;
|
|
348
|
+
}
|
|
349
|
+
|
|
336
350
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
337
351
|
// Helpers
|
|
338
352
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -726,6 +740,70 @@ export async function handleCollections(config: Config): Promise<Response> {
|
|
|
726
740
|
);
|
|
727
741
|
}
|
|
728
742
|
|
|
743
|
+
/**
|
|
744
|
+
* POST /api/publish/export
|
|
745
|
+
* Build a gno.sh-compatible publish artifact for a collection or single doc.
|
|
746
|
+
*/
|
|
747
|
+
export async function handlePublishExport(
|
|
748
|
+
config: Config,
|
|
749
|
+
store: SqliteAdapter,
|
|
750
|
+
req: Request
|
|
751
|
+
): Promise<Response> {
|
|
752
|
+
let body: PublishExportRequestBody;
|
|
753
|
+
try {
|
|
754
|
+
body = (await req.json()) as PublishExportRequestBody;
|
|
755
|
+
} catch {
|
|
756
|
+
return errorResponse("VALIDATION", "Invalid JSON body");
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (!body.target || typeof body.target !== "string") {
|
|
760
|
+
return errorResponse("VALIDATION", "Missing or invalid target");
|
|
761
|
+
}
|
|
762
|
+
if (body.slug !== undefined && typeof body.slug !== "string") {
|
|
763
|
+
return errorResponse("VALIDATION", "slug must be a string");
|
|
764
|
+
}
|
|
765
|
+
if (body.summary !== undefined && typeof body.summary !== "string") {
|
|
766
|
+
return errorResponse("VALIDATION", "summary must be a string");
|
|
767
|
+
}
|
|
768
|
+
if (body.title !== undefined && typeof body.title !== "string") {
|
|
769
|
+
return errorResponse("VALIDATION", "title must be a string");
|
|
770
|
+
}
|
|
771
|
+
if (body.visibility !== undefined && !isPublishVisibility(body.visibility)) {
|
|
772
|
+
return errorResponse(
|
|
773
|
+
"VALIDATION",
|
|
774
|
+
"visibility must be public, secret-link, invite-only, or encrypted"
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
try {
|
|
779
|
+
const artifact = await exportPublishArtifact({
|
|
780
|
+
collections: config.collections,
|
|
781
|
+
options: {
|
|
782
|
+
routeSlug: body.slug,
|
|
783
|
+
summary: body.summary,
|
|
784
|
+
title: body.title,
|
|
785
|
+
visibility: body.visibility,
|
|
786
|
+
},
|
|
787
|
+
store,
|
|
788
|
+
target: body.target.trim(),
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
return jsonResponse({
|
|
792
|
+
artifact,
|
|
793
|
+
fileName: derivePublishArtifactFilename(artifact),
|
|
794
|
+
uploadUrl: "https://gno.sh/studio",
|
|
795
|
+
});
|
|
796
|
+
} catch (error) {
|
|
797
|
+
return errorResponse(
|
|
798
|
+
"RUNTIME",
|
|
799
|
+
error instanceof Error
|
|
800
|
+
? error.message
|
|
801
|
+
: "Failed to export publish artifact",
|
|
802
|
+
500
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
729
807
|
/**
|
|
730
808
|
* POST /api/collections
|
|
731
809
|
* Create a new collection and start sync job.
|
|
@@ -3894,6 +3972,10 @@ export async function routeApi(
|
|
|
3894
3972
|
return handleCollections(config);
|
|
3895
3973
|
}
|
|
3896
3974
|
|
|
3975
|
+
if (path === "/api/publish/export" && req.method === "POST") {
|
|
3976
|
+
return handlePublishExport(config, store, req);
|
|
3977
|
+
}
|
|
3978
|
+
|
|
3897
3979
|
if (path === "/api/docs") {
|
|
3898
3980
|
return handleDocs(store, url);
|
|
3899
3981
|
}
|
package/src/serve/server.ts
CHANGED
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
handleJob,
|
|
43
43
|
handleModelPull,
|
|
44
44
|
handleModelStatus,
|
|
45
|
+
handlePublishExport,
|
|
45
46
|
handlePresets,
|
|
46
47
|
handleQuery,
|
|
47
48
|
handleRefactorPlan,
|
|
@@ -234,6 +235,17 @@ export async function startServer(
|
|
|
234
235
|
);
|
|
235
236
|
},
|
|
236
237
|
},
|
|
238
|
+
"/api/publish/export": {
|
|
239
|
+
POST: async (req: Request) => {
|
|
240
|
+
if (!isRequestAllowed(req, port)) {
|
|
241
|
+
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
242
|
+
}
|
|
243
|
+
return withSecurityHeaders(
|
|
244
|
+
await handlePublishExport(ctxHolder.config, store, req),
|
|
245
|
+
isDev
|
|
246
|
+
);
|
|
247
|
+
},
|
|
248
|
+
},
|
|
237
249
|
"/api/sync": {
|
|
238
250
|
POST: async (req: Request) => {
|
|
239
251
|
if (!isRequestAllowed(req, port)) {
|
|
@@ -117,10 +117,12 @@ export async function createVectorIndexPort(
|
|
|
117
117
|
`);
|
|
118
118
|
|
|
119
119
|
// Prepared statements for vec0 table (if available)
|
|
120
|
-
const
|
|
121
|
-
? db.prepare(
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
const deleteVecChunkStmt = searchAvailable
|
|
121
|
+
? db.prepare(`DELETE FROM ${tableName} WHERE chunk_id = ?`)
|
|
122
|
+
: null;
|
|
123
|
+
|
|
124
|
+
const insertVecStmt = searchAvailable
|
|
125
|
+
? db.prepare(`INSERT INTO ${tableName} (chunk_id, embedding) VALUES (?, ?)`)
|
|
124
126
|
: null;
|
|
125
127
|
|
|
126
128
|
const searchStmt = searchAvailable
|
|
@@ -175,12 +177,15 @@ export async function createVectorIndexPort(
|
|
|
175
177
|
}
|
|
176
178
|
|
|
177
179
|
// 2. Best-effort update vec0 (graceful degradation)
|
|
178
|
-
if (
|
|
180
|
+
if (deleteVecChunkStmt && insertVecStmt) {
|
|
179
181
|
try {
|
|
180
182
|
db.transaction(() => {
|
|
181
183
|
for (const row of rows) {
|
|
182
184
|
const chunkId = `${row.mirrorHash}:${row.seq}`;
|
|
183
|
-
|
|
185
|
+
// sqlite-vec vec0 tables do not reliably support OR REPLACE semantics.
|
|
186
|
+
// Delete first, then insert the fresh vector row.
|
|
187
|
+
deleteVecChunkStmt.run(chunkId);
|
|
188
|
+
insertVecStmt.run(chunkId, encodeEmbedding(row.embedding));
|
|
184
189
|
}
|
|
185
190
|
})();
|
|
186
191
|
} catch (e) {
|