@cancia/astro 0.3.0 → 0.4.1

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.
@@ -1,3 +1,6 @@
1
+ import {
2
+ createGitHubClient
3
+ } from "./chunk-U7V53JX7.js";
1
4
  import {
2
5
  hashRev
3
6
  } from "./chunk-L2VKQJPY.js";
@@ -332,70 +335,6 @@ function closeSqliteAdapterV2(dbPath) {
332
335
  }
333
336
  }
334
337
 
335
- // src/storage/github-client.ts
336
- function toBase64(text) {
337
- if (typeof Buffer !== "undefined") {
338
- return Buffer.from(text, "utf-8").toString("base64");
339
- }
340
- const bytes = new TextEncoder().encode(text);
341
- let binary = "";
342
- for (const b of bytes) binary += String.fromCharCode(b);
343
- return btoa(binary);
344
- }
345
- function createGitHubClient(opts) {
346
- const { repo, branch, token, committer } = opts;
347
- const doFetch = opts.fetch ?? globalThis.fetch;
348
- const apiBase = (opts.apiBase ?? "https://api.github.com").replace(/\/$/, "");
349
- if (!doFetch) {
350
- throw new Error("createGitHubClient: no fetch available (pass opts.fetch)");
351
- }
352
- const headers = () => ({
353
- Authorization: `Bearer ${token}`,
354
- Accept: "application/vnd.github+json",
355
- "X-GitHub-Api-Version": "2022-11-28"
356
- });
357
- const contentsUrl = (path) => {
358
- const encoded = path.split("/").map((seg) => encodeURIComponent(seg)).join("/");
359
- return `${apiBase}/repos/${repo}/contents/${encoded}`;
360
- };
361
- async function getFileSha(path) {
362
- const url = `${contentsUrl(path)}?ref=${encodeURIComponent(branch)}`;
363
- const res = await doFetch(url, { method: "GET", headers: headers() });
364
- if (res.status === 404) return null;
365
- if (!res.ok) {
366
- const detail = await res.text().catch(() => "");
367
- throw new Error(`GitHub getFileSha ${path} failed: ${res.status} ${detail}`);
368
- }
369
- const body = await res.json();
370
- return body.sha ?? null;
371
- }
372
- async function putFile(file, message) {
373
- const sha = await getFileSha(file.path);
374
- const payload = {
375
- message,
376
- content: toBase64(file.content),
377
- branch
378
- };
379
- if (sha) payload.sha = sha;
380
- if (committer) payload.committer = committer;
381
- const res = await doFetch(contentsUrl(file.path), {
382
- method: "PUT",
383
- headers: headers(),
384
- body: JSON.stringify(payload)
385
- });
386
- if (!res.ok) {
387
- const detail = await res.text().catch(() => "");
388
- throw new Error(`GitHub commit ${file.path} failed: ${res.status} ${detail}`);
389
- }
390
- }
391
- async function commitFiles(files, message) {
392
- for (const file of files) {
393
- await putFile(file, message);
394
- }
395
- }
396
- return { getFileSha, commitFiles };
397
- }
398
-
399
338
  // src/storage/git-backed.ts
400
339
  import { existsSync, readFileSync, readdirSync, statSync } from "fs";
401
340
  import { join, relative } from "path";
@@ -575,6 +514,5 @@ function createGitBackedAdapter(opts) {
575
514
  export {
576
515
  createSqliteAdapterV2,
577
516
  closeSqliteAdapterV2,
578
- createGitHubClient,
579
517
  createGitBackedAdapter
580
518
  };
@@ -0,0 +1,45 @@
1
+ import {
2
+ createGitHubClient
3
+ } from "./chunk-U7V53JX7.js";
4
+
5
+ // src/publish-hook.ts
6
+ async function firePublish(hook, opts = {}) {
7
+ if (!hook) return { ok: false, kind: "no-hook" };
8
+ const headers = { ...opts.headers ?? {} };
9
+ if (opts.token) headers.Authorization = `Bearer ${opts.token}`;
10
+ const init = { method: opts.method ?? "POST" };
11
+ if (Object.keys(headers).length > 0) init.headers = headers;
12
+ try {
13
+ const res = await fetch(hook, init);
14
+ if (!res.ok) return { ok: false, kind: "bad-response", status: res.status };
15
+ return { ok: true };
16
+ } catch {
17
+ return { ok: false, kind: "unreachable" };
18
+ }
19
+ }
20
+
21
+ // src/publish-dispatch.ts
22
+ var CANCIA_PUBLISH_EVENT = "cancia-publish";
23
+ async function fireDispatch(opts) {
24
+ if (!opts.repo || !opts.token) return { ok: false, kind: "not-configured" };
25
+ const client = createGitHubClient({
26
+ repo: opts.repo,
27
+ // branch is irrelevant for dispatch (repo-level event) but required by the
28
+ // client's options — a harmless placeholder.
29
+ branch: "main",
30
+ token: opts.token,
31
+ fetch: opts.fetch,
32
+ apiBase: opts.apiBase
33
+ });
34
+ try {
35
+ await client.dispatch(opts.eventType ?? CANCIA_PUBLISH_EVENT, opts.clientPayload);
36
+ return { ok: true };
37
+ } catch {
38
+ return { ok: false, kind: "failed" };
39
+ }
40
+ }
41
+
42
+ export {
43
+ firePublish,
44
+ fireDispatch
45
+ };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createGitBackedAdapter,
3
3
  createSqliteAdapterV2
4
- } from "./chunk-52URFK5Y.js";
4
+ } from "./chunk-5AUIPW2I.js";
5
5
  import {
6
6
  createJsonFileAdapter,
7
7
  createJsonFileAdapterV2
@@ -172,6 +172,8 @@ function buildRuntimeFromBaked(baked) {
172
172
  const secret = baked.public ? void 0 : token || void 0;
173
173
  const deployHook = process.env.CANCIA_DEPLOY_HOOK || void 0;
174
174
  const deployHookToken = process.env.CANCIA_DEPLOY_HOOK_TOKEN || void 0;
175
+ const publishRepo = baked.publishRepo || void 0;
176
+ const publishGithubToken = process.env.CANCIA_GITHUB_TOKEN?.trim() || void 0;
175
177
  const storageV2 = buildStorageV2(baked, projectRoot);
176
178
  const storage = storageV2 ? storageV2.kv : lazyJsonFileAdapter(projectRoot);
177
179
  const uploadHandler = buildUploadHandler(baked, projectRoot);
@@ -188,6 +190,8 @@ function buildRuntimeFromBaked(baked) {
188
190
  deployHookToken,
189
191
  deployHookMethod: baked.deployHookMethod,
190
192
  deployHookHeaders: baked.deployHookHeaders,
193
+ publishRepo,
194
+ publishGithubToken,
191
195
  maxUploadMB: baked.maxUploadMB
192
196
  };
193
197
  }
@@ -0,0 +1,83 @@
1
+ // src/storage/github-client.ts
2
+ function toBase64(text) {
3
+ if (typeof Buffer !== "undefined") {
4
+ return Buffer.from(text, "utf-8").toString("base64");
5
+ }
6
+ const bytes = new TextEncoder().encode(text);
7
+ let binary = "";
8
+ for (const b of bytes) binary += String.fromCharCode(b);
9
+ return btoa(binary);
10
+ }
11
+ function createGitHubClient(opts) {
12
+ const { repo, branch, token, committer } = opts;
13
+ const doFetch = opts.fetch ?? globalThis.fetch;
14
+ const apiBase = (opts.apiBase ?? "https://api.github.com").replace(/\/$/, "");
15
+ if (!doFetch) {
16
+ throw new Error("createGitHubClient: no fetch available (pass opts.fetch)");
17
+ }
18
+ const headers = () => ({
19
+ Authorization: `Bearer ${token}`,
20
+ Accept: "application/vnd.github+json",
21
+ "X-GitHub-Api-Version": "2022-11-28"
22
+ });
23
+ const contentsUrl = (path) => {
24
+ const encoded = path.split("/").map((seg) => encodeURIComponent(seg)).join("/");
25
+ return `${apiBase}/repos/${repo}/contents/${encoded}`;
26
+ };
27
+ async function getFileSha(path) {
28
+ const url = `${contentsUrl(path)}?ref=${encodeURIComponent(branch)}`;
29
+ const res = await doFetch(url, { method: "GET", headers: headers() });
30
+ if (res.status === 404) return null;
31
+ if (!res.ok) {
32
+ const detail = await res.text().catch(() => "");
33
+ throw new Error(`GitHub getFileSha ${path} failed: ${res.status} ${detail}`);
34
+ }
35
+ const body = await res.json();
36
+ return body.sha ?? null;
37
+ }
38
+ async function putFile(file, message) {
39
+ const sha = await getFileSha(file.path);
40
+ const payload = {
41
+ message,
42
+ content: toBase64(file.content),
43
+ branch
44
+ };
45
+ if (sha) payload.sha = sha;
46
+ if (committer) payload.committer = committer;
47
+ const res = await doFetch(contentsUrl(file.path), {
48
+ method: "PUT",
49
+ headers: headers(),
50
+ body: JSON.stringify(payload)
51
+ });
52
+ if (!res.ok) {
53
+ const detail = await res.text().catch(() => "");
54
+ throw new Error(`GitHub commit ${file.path} failed: ${res.status} ${detail}`);
55
+ }
56
+ }
57
+ async function commitFiles(files, message) {
58
+ for (const file of files) {
59
+ await putFile(file, message);
60
+ }
61
+ }
62
+ async function dispatch(eventType, clientPayload) {
63
+ const url = `${apiBase}/repos/${repo}/dispatches`;
64
+ const payload = { event_type: eventType };
65
+ if (clientPayload !== void 0) payload.client_payload = clientPayload;
66
+ const res = await doFetch(url, {
67
+ method: "POST",
68
+ headers: headers(),
69
+ body: JSON.stringify(payload)
70
+ });
71
+ if (!res.ok) {
72
+ const detail = await res.text().catch(() => "");
73
+ throw new Error(
74
+ `GitHub repository_dispatch ${eventType} failed: ${res.status} ${detail}`
75
+ );
76
+ }
77
+ }
78
+ return { getFileSha, commitFiles, dispatch };
79
+ }
80
+
81
+ export {
82
+ createGitHubClient
83
+ };
@@ -1,16 +1,38 @@
1
1
  import {
2
+ fireDispatch,
2
3
  firePublish
3
- } from "../chunk-PIDFNJME.js";
4
+ } from "../chunk-BMCI2F2Y.js";
5
+ import "../chunk-U7V53JX7.js";
4
6
 
5
7
  // src/endpoints/publish.ts
6
8
  import { getCanciaRuntime } from "virtual:cancia/runtime";
7
9
  async function POST({ request }) {
8
- const { deployHook, deployHookToken, deployHookMethod, deployHookHeaders, secret } = getCanciaRuntime();
10
+ const {
11
+ deployHook,
12
+ deployHookToken,
13
+ deployHookMethod,
14
+ deployHookHeaders,
15
+ publishRepo,
16
+ publishGithubToken,
17
+ secret
18
+ } = getCanciaRuntime();
9
19
  if (secret) {
10
20
  const token = request.headers.get("Authorization")?.replace("Bearer ", "").trim();
11
21
  if (token !== secret)
12
22
  return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401 });
13
23
  }
24
+ const ghToken = publishGithubToken ?? process.env.CANCIA_GITHUB_TOKEN?.trim();
25
+ if (publishRepo && ghToken) {
26
+ const dispatched = await fireDispatch({ repo: publishRepo, token: ghToken });
27
+ if (dispatched.ok)
28
+ return new Response(JSON.stringify({ ok: true }), {
29
+ headers: { "Content-Type": "application/json" }
30
+ });
31
+ return new Response(
32
+ JSON.stringify({ error: "GitHub repository_dispatch failed" }),
33
+ { status: 502 }
34
+ );
35
+ }
14
36
  const hook = deployHook ?? process.env.CANCIA_DEPLOY_HOOK;
15
37
  const result = await firePublish(hook, {
16
38
  token: deployHookToken,
@@ -23,7 +45,9 @@ async function POST({ request }) {
23
45
  });
24
46
  if (result.kind === "no-hook")
25
47
  return new Response(
26
- JSON.stringify({ error: "No deploy hook configured. Set CANCIA_DEPLOY_HOOK." }),
48
+ JSON.stringify({
49
+ error: "No publish mechanism configured. Set a publish repo + CANCIA_GITHUB_TOKEN, or CANCIA_DEPLOY_HOOK."
50
+ }),
27
51
  { status: 503 }
28
52
  );
29
53
  if (result.kind === "bad-response")
@@ -1,5 +1,5 @@
1
1
  import { C as CanciaStorage, a as CanciaStorageV2 } from './types-BMlLS-OS.js';
2
- import { G as GitHubCommitter, a as GitHubClient, F as FetchLike } from './github-client-BAZ1pW24.js';
2
+ import { G as GitHubCommitter, a as GitHubClient, F as FetchLike } from './github-client-Db0pIrqE.js';
3
3
 
4
4
  declare function createJsonFileAdapter(filePath?: string): CanciaStorage;
5
5
 
@@ -42,6 +42,15 @@ interface GitHubClient {
42
42
  * any PUT fails so the caller can keep the batch dirty and retry.
43
43
  */
44
44
  commitFiles(files: CommitFile[], message: string): Promise<void>;
45
+ /**
46
+ * Fire a `repository_dispatch` event on the repo. POSTs
47
+ * `/repos/{owner}/{repo}/dispatches` with `{ event_type, client_payload }`
48
+ * and the Bearer token. GitHub returns 204 on success — throws on anything
49
+ * else. This is the host-independent publish trigger (031): a workflow in the
50
+ * client repo listens for the event and makes an empty commit, so the host's
51
+ * normal git integration rebuilds. Same repo-write PAT the Contents API uses.
52
+ */
53
+ dispatch(eventType: string, clientPayload?: object): Promise<void>;
45
54
  }
46
55
  declare function createGitHubClient(opts: GitHubClientOptions): GitHubClient;
47
56
 
package/dist/index.d.ts CHANGED
@@ -3,10 +3,10 @@ import { C as CanciaStorage, a as CanciaStorageV2 } from './types-BMlLS-OS.js';
3
3
  export { b as CanciaKVStore, c as CanciaListStore, d as CanciaPageStore, L as ListEntry, P as PageMeta, e as PageRecord, f as PageSEO, R as Rev, g as RevConflictError } from './types-BMlLS-OS.js';
4
4
  import { U as UploadHandler } from './upload-DwCGjXbz.js';
5
5
  export { m as makeLocalUploadHandler } from './upload-DwCGjXbz.js';
6
- import { G as GitHubCommitter } from './github-client-BAZ1pW24.js';
6
+ import { G as GitHubCommitter } from './github-client-Db0pIrqE.js';
7
7
  export { CanciaLoaderOptions, canciaLoader } from './loader/index.js';
8
8
  export { FieldDescription, FieldMeta, FieldMetaBase, FieldWidget, ListDescription, ListSchema, SchemasModule, defineField, defineList, describeList } from './schema/index.js';
9
- export { G as GitBackedContentPaths, a as GitBackedControls, b as GitBackedOptions, c as GitBackedStorage, S as SqliteV2Options, d as closeSqliteAdapterV2, e as createGitBackedAdapter, f as createJsonFileAdapter, g as createJsonFileAdapterV2, h as createSQLiteAdapter, i as createSqliteAdapterV2 } from './git-backed-DFAB0tzf.js';
9
+ export { G as GitBackedContentPaths, a as GitBackedControls, b as GitBackedOptions, c as GitBackedStorage, S as SqliteV2Options, d as closeSqliteAdapterV2, e as createGitBackedAdapter, f as createJsonFileAdapter, g as createJsonFileAdapterV2, h as createSQLiteAdapter, i as createSqliteAdapterV2 } from './git-backed-DtiH52EI.js';
10
10
  export { z } from 'zod';
11
11
  import 'astro/loaders';
12
12
  import './portable-text-BikSqS9T.js';
@@ -59,6 +59,26 @@ interface CanciaIntegrationOptions {
59
59
  deployHookMethod?: string;
60
60
  /** Optional extra headers merged into the deploy-hook request. */
61
61
  deployHookHeaders?: Record<string, string>;
62
+ /**
63
+ * The host-independent standard publish path (031). When set, the Publish
64
+ * button fires a GitHub `repository_dispatch` (event `cancia-publish`) on
65
+ * this repo instead of the raw deploy hook — a workflow in the repo
66
+ * (templates/cancia-publish.yml) makes an empty commit → the host's normal
67
+ * git integration rebuilds. Identical for every host; no host API / IP
68
+ * allowlist. The token is NEVER baked — it is read from
69
+ * `process.env.CANCIA_GITHUB_TOKEN` at runtime (a fine-grained PAT with
70
+ * `contents:write`, which covers repository_dispatch). Precedence:
71
+ * publish-dispatch (if repo + token) → deploy hook (030) → 503.
72
+ *
73
+ * If a git-backed `git.repo` is already declared, that repo is reused as the
74
+ * publish target unless overridden here.
75
+ */
76
+ publish?: {
77
+ github?: {
78
+ /** "owner/name" of the repo whose `cancia-publish` workflow rebuilds. */
79
+ repo: string;
80
+ };
81
+ };
62
82
  /**
63
83
  * Custom storage adapter.
64
84
  * Default: SQLite (cancia.db in project root) — works anywhere with Node.
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
+ fireDispatch,
2
3
  firePublish
3
- } from "./chunk-PIDFNJME.js";
4
+ } from "./chunk-BMCI2F2Y.js";
4
5
  import {
5
6
  makeListsRoutes
6
7
  } from "./chunk-22DJVJBR.js";
@@ -13,7 +14,7 @@ import {
13
14
  makeR2UploadHandler,
14
15
  makeUploadRoute,
15
16
  setCanciaRuntime
16
- } from "./chunk-AIPRCBJM.js";
17
+ } from "./chunk-U46HCJN3.js";
17
18
  import {
18
19
  defineField,
19
20
  defineList,
@@ -30,7 +31,8 @@ import {
30
31
  closeSqliteAdapterV2,
31
32
  createGitBackedAdapter,
32
33
  createSqliteAdapterV2
33
- } from "./chunk-52URFK5Y.js";
34
+ } from "./chunk-5AUIPW2I.js";
35
+ import "./chunk-U7V53JX7.js";
34
36
  import {
35
37
  createJsonFileAdapter,
36
38
  createJsonFileAdapterV2
@@ -93,7 +95,7 @@ function makeContentRoute(storage, secret) {
93
95
  }
94
96
 
95
97
  // src/routes/publish.ts
96
- function makePublishRoute(deployHook, secret, hookOptions) {
98
+ function makePublishRoute(deployHook, secret, hookOptions, dispatch) {
97
99
  return async ({ request }) => {
98
100
  if (secret) {
99
101
  const token = request.headers.get("Authorization")?.replace("Bearer ", "").trim();
@@ -101,6 +103,18 @@ function makePublishRoute(deployHook, secret, hookOptions) {
101
103
  return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401 });
102
104
  }
103
105
  }
106
+ if (dispatch?.repo && dispatch?.token) {
107
+ const dispatched = await fireDispatch({ repo: dispatch.repo, token: dispatch.token });
108
+ if (dispatched.ok) {
109
+ return new Response(JSON.stringify({ ok: true }), {
110
+ headers: { "Content-Type": "application/json" }
111
+ });
112
+ }
113
+ return new Response(
114
+ JSON.stringify({ error: "GitHub repository_dispatch failed" }),
115
+ { status: 502 }
116
+ );
117
+ }
104
118
  const hook = deployHook ?? process.env.CANCIA_DEPLOY_HOOK;
105
119
  const result = await firePublish(hook, hookOptions);
106
120
  if (result.ok) {
@@ -110,7 +124,9 @@ function makePublishRoute(deployHook, secret, hookOptions) {
110
124
  }
111
125
  if (result.kind === "no-hook") {
112
126
  return new Response(
113
- JSON.stringify({ error: "No deploy hook configured. Set CANCIA_DEPLOY_HOOK." }),
127
+ JSON.stringify({
128
+ error: "No publish mechanism configured. Set a publish repo + CANCIA_GITHUB_TOKEN, or CANCIA_DEPLOY_HOOK."
129
+ }),
114
130
  { status: 503 }
115
131
  );
116
132
  }
@@ -224,6 +240,7 @@ function canciaIntegration(opts = {}) {
224
240
  let resolvedRootPath = "";
225
241
  let resolvedUploadHandler;
226
242
  let resolvedDeployHook;
243
+ let resolvedPublishRepo;
227
244
  let resolvedLocales = ["en"];
228
245
  let bakedConfig = null;
229
246
  return {
@@ -255,6 +272,8 @@ function canciaIntegration(opts = {}) {
255
272
  \x1B[32mcancia\x1B[0m No CANCIA_TOKEN found \u2014 generated one for you.`);
256
273
  }
257
274
  const hasDeployHook = !!(opts.deployHook ?? env.CANCIA_DEPLOY_HOOK ?? process.env.CANCIA_DEPLOY_HOOK);
275
+ resolvedPublishRepo = opts.publish?.github?.repo ?? opts.git?.repo;
276
+ const canPublish = hasDeployHook || !!resolvedPublishRepo;
258
277
  let storageDescriptor;
259
278
  if (opts.git) {
260
279
  storageDescriptor = {
@@ -289,9 +308,10 @@ function canciaIntegration(opts = {}) {
289
308
  locales: resolvedLocales,
290
309
  maxUploadMB: opts.maxUploadMB ?? 10,
291
310
  public: opts.public ?? false,
292
- hasDeployHook,
311
+ canPublish,
293
312
  deployHookMethod: opts.deployHookMethod,
294
313
  deployHookHeaders: opts.deployHookHeaders,
314
+ publishRepo: resolvedPublishRepo,
295
315
  storage: storageDescriptor,
296
316
  r2: r2Baked
297
317
  };
@@ -305,7 +325,7 @@ function canciaIntegration(opts = {}) {
305
325
  languages,
306
326
  page: "unknown",
307
327
  public: opts.public ?? false,
308
- hasDeployHook
328
+ canPublish
309
329
  })};`
310
330
  );
311
331
  injectScript("page", `import "@cancia/toolbar";`);
@@ -352,6 +372,7 @@ function canciaIntegration(opts = {}) {
352
372
  const secret = resolvedToken;
353
373
  const deployHook = opts.deployHook ?? process.env.CANCIA_DEPLOY_HOOK;
354
374
  const deployHookToken = process.env.CANCIA_DEPLOY_HOOK_TOKEN || opts.deployHookToken;
375
+ const publishGithubToken = process.env.CANCIA_GITHUB_TOKEN?.trim() || void 0;
355
376
  let uploadHandler;
356
377
  if (opts.r2) {
357
378
  const serverEnv = loadEnv(process.env.NODE_ENV ?? "development", resolvedRootPath, "");
@@ -385,15 +406,22 @@ function canciaIntegration(opts = {}) {
385
406
  deployHookToken,
386
407
  deployHookMethod: opts.deployHookMethod,
387
408
  deployHookHeaders: opts.deployHookHeaders,
409
+ publishRepo: resolvedPublishRepo,
410
+ publishGithubToken,
388
411
  maxUploadMB: opts.maxUploadMB ?? 10
389
412
  });
390
413
  const contentRoutes = makeContentRoute(storage, routeSecret);
391
414
  const uploadRoute = makeUploadRoute(uploadHandler, routeSecret, opts.maxUploadMB);
392
- const publishRoute = makePublishRoute(deployHook, routeSecret, {
393
- token: deployHookToken,
394
- method: opts.deployHookMethod,
395
- headers: opts.deployHookHeaders
396
- });
415
+ const publishRoute = makePublishRoute(
416
+ deployHook,
417
+ routeSecret,
418
+ {
419
+ token: deployHookToken,
420
+ method: opts.deployHookMethod,
421
+ headers: opts.deployHookHeaders
422
+ },
423
+ { repo: resolvedPublishRepo, token: publishGithubToken }
424
+ );
397
425
  const authRoute = makeAuthRoute(secret);
398
426
  const listsRoutes = makeListsRoutes({
399
427
  storageV2,
@@ -526,6 +554,7 @@ function canciaIntegration(opts = {}) {
526
554
  }
527
555
  const deployHook = opts.deployHook ?? process.env.CANCIA_DEPLOY_HOOK;
528
556
  const deployHookToken = process.env.CANCIA_DEPLOY_HOOK_TOKEN || opts.deployHookToken;
557
+ const publishGithubToken = process.env.CANCIA_GITHUB_TOKEN?.trim() || void 0;
529
558
  const storageV2 = opts.storageV2 === null ? void 0 : opts.storageV2 ?? buildDefaultV2Store(opts, resolvedRootPath);
530
559
  setCanciaRuntime({
531
560
  storage,
@@ -540,6 +569,8 @@ function canciaIntegration(opts = {}) {
540
569
  deployHookToken,
541
570
  deployHookMethod: opts.deployHookMethod,
542
571
  deployHookHeaders: opts.deployHookHeaders,
572
+ publishRepo: resolvedPublishRepo,
573
+ publishGithubToken,
543
574
  maxUploadMB: opts.maxUploadMB ?? 10
544
575
  });
545
576
  }
package/dist/runtime.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { C as CanciaStorage, a as CanciaStorageV2 } from './types-BMlLS-OS.js';
2
2
  import { U as UploadHandler } from './upload-DwCGjXbz.js';
3
- import { G as GitHubCommitter } from './github-client-BAZ1pW24.js';
3
+ import { G as GitHubCommitter } from './github-client-Db0pIrqE.js';
4
4
 
5
5
  interface CanciaRuntime {
6
6
  /** v1 KV-only storage. Kept for the existing /content endpoint. */
@@ -37,6 +37,18 @@ interface CanciaRuntime {
37
37
  deployHookMethod: string | undefined;
38
38
  /** Optional extra headers merged into the hook request. Non-secret, baked. */
39
39
  deployHookHeaders: Record<string, string> | undefined;
40
+ /**
41
+ * "owner/name" of the GitHub repo whose `cancia-publish` workflow rebuilds
42
+ * the site (031). Non-secret, baked. When set (with CANCIA_GITHUB_TOKEN in
43
+ * env) the publish route fires a repository_dispatch instead of the raw
44
+ * deploy hook — the host-independent standard publish path.
45
+ */
46
+ publishRepo?: string | undefined;
47
+ /**
48
+ * GitHub PAT (contents:write) used to fire the repository_dispatch. SECRET —
49
+ * read from CANCIA_GITHUB_TOKEN at runtime, never baked into dist/.
50
+ */
51
+ publishGithubToken?: string | undefined;
40
52
  maxUploadMB: number;
41
53
  }
42
54
  /** Describes which storage adapter to build lazily in the server process. */
@@ -69,12 +81,22 @@ interface BakedConfig {
69
81
  maxUploadMB: number;
70
82
  /** When true, endpoints skip auth (public demos). Baked, never a secret. */
71
83
  public: boolean;
72
- /** Whether a deploy hook exists — the value itself is read from env. */
73
- hasDeployHook: boolean;
84
+ /**
85
+ * Whether the site can publish — a deploy hook (030) OR a repository_dispatch
86
+ * repo (031) is configured. Client-inject flag for the toolbar's Publish
87
+ * button; the actual publish credentials are read from env at request time.
88
+ */
89
+ canPublish: boolean;
74
90
  /** Optional deploy-hook HTTP method (default POST). Non-secret. */
75
91
  deployHookMethod: string | undefined;
76
92
  /** Optional deploy-hook extra headers. Non-secret (the token stays in env). */
77
93
  deployHookHeaders: Record<string, string> | undefined;
94
+ /**
95
+ * "owner/name" of the GitHub repo for repository_dispatch publish (031).
96
+ * Non-secret, baked. The token (CANCIA_GITHUB_TOKEN) is read from env at
97
+ * runtime. Absent = no dispatch path (falls through to the deploy hook).
98
+ */
99
+ publishRepo?: string | undefined;
78
100
  /** How to build the v2 storage adapter at runtime. Absent = no v2 storage. */
79
101
  storage: BakedStorageDescriptor | undefined;
80
102
  /** When set, build an R2 upload handler; secrets come from env at runtime. */
package/dist/runtime.js CHANGED
@@ -2,8 +2,9 @@ import {
2
2
  getCanciaRuntime,
3
3
  setBakedConfig,
4
4
  setCanciaRuntime
5
- } from "./chunk-AIPRCBJM.js";
6
- import "./chunk-52URFK5Y.js";
5
+ } from "./chunk-U46HCJN3.js";
6
+ import "./chunk-5AUIPW2I.js";
7
+ import "./chunk-U7V53JX7.js";
7
8
  import "./chunk-L2VKQJPY.js";
8
9
  import "./chunk-7IA5B5CF.js";
9
10
  import "./chunk-5IPHDIC6.js";
@@ -1,7 +1,7 @@
1
1
  import { R as Rev } from '../types-BMlLS-OS.js';
2
2
  export { b as CanciaKVStore, c as CanciaListStore, d as CanciaPageStore, C as CanciaStorage, a as CanciaStorageV2, L as ListEntry, P as PageMeta, e as PageRecord, f as PageSEO, g as RevConflictError } from '../types-BMlLS-OS.js';
3
- export { G as GitBackedContentPaths, a as GitBackedControls, b as GitBackedOptions, c as GitBackedStorage, S as SqliteV2Options, d as closeSqliteAdapterV2, e as createGitBackedAdapter, f as createJsonFileAdapter, g as createJsonFileAdapterV2, h as createSQLiteAdapter, i as createSqliteAdapterV2 } from '../git-backed-DFAB0tzf.js';
4
- export { C as CommitFile, a as GitHubClient, b as GitHubClientOptions, G as GitHubCommitter, c as createGitHubClient } from '../github-client-BAZ1pW24.js';
3
+ export { G as GitBackedContentPaths, a as GitBackedControls, b as GitBackedOptions, c as GitBackedStorage, S as SqliteV2Options, d as closeSqliteAdapterV2, e as createGitBackedAdapter, f as createJsonFileAdapter, g as createJsonFileAdapterV2, h as createSQLiteAdapter, i as createSqliteAdapterV2 } from '../git-backed-DtiH52EI.js';
4
+ export { C as CommitFile, a as GitHubClient, b as GitHubClientOptions, G as GitHubCommitter, c as createGitHubClient } from '../github-client-Db0pIrqE.js';
5
5
 
6
6
  /**
7
7
  * Canonical JSON: keys sorted alphabetically at every level. Two values that
@@ -4,9 +4,11 @@ import {
4
4
  import {
5
5
  closeSqliteAdapterV2,
6
6
  createGitBackedAdapter,
7
- createGitHubClient,
8
7
  createSqliteAdapterV2
9
- } from "../chunk-52URFK5Y.js";
8
+ } from "../chunk-5AUIPW2I.js";
9
+ import {
10
+ createGitHubClient
11
+ } from "../chunk-U7V53JX7.js";
10
12
  import {
11
13
  canonicalize,
12
14
  createJsonFileAdapter,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cancia/astro",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Astro integration for Cancia CMS — inline editing with zero separate server",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,19 +0,0 @@
1
- // src/publish-hook.ts
2
- async function firePublish(hook, opts = {}) {
3
- if (!hook) return { ok: false, kind: "no-hook" };
4
- const headers = { ...opts.headers ?? {} };
5
- if (opts.token) headers.Authorization = `Bearer ${opts.token}`;
6
- const init = { method: opts.method ?? "POST" };
7
- if (Object.keys(headers).length > 0) init.headers = headers;
8
- try {
9
- const res = await fetch(hook, init);
10
- if (!res.ok) return { ok: false, kind: "bad-response", status: res.status };
11
- return { ok: true };
12
- } catch {
13
- return { ok: false, kind: "unreachable" };
14
- }
15
- }
16
-
17
- export {
18
- firePublish
19
- };