@cancia/astro 0.2.0 → 0.3.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/dist/chunk-52URFK5Y.js +580 -0
- package/dist/chunk-AE4SIY24.js +63 -0
- package/dist/chunk-AIPRCBJM.js +258 -0
- package/dist/{chunk-ST44VULL.js → chunk-L2VKQJPY.js} +9 -4
- package/dist/chunk-PIDFNJME.js +19 -0
- package/dist/{chunk-337LJIKX.js → chunk-UR5WC3RA.js} +1 -1
- package/dist/endpoints/publish.js +23 -17
- package/dist/git-backed-DFAB0tzf.d.ts +98 -0
- package/dist/github-client-BAZ1pW24.d.ts +48 -0
- package/dist/index.d.ts +57 -1
- package/dist/index.js +112 -176
- package/dist/loader/index.js +2 -2
- package/dist/runtime.d.ts +63 -1
- package/dist/runtime.js +7 -1
- package/dist/storage/index.d.ts +13 -129
- package/dist/storage/index.js +15 -5
- package/package.json +1 -1
- package/dist/chunk-DGCGIEFD.js +0 -14
- package/dist/chunk-SXKZ2WUL.js +0 -305
package/dist/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
firePublish
|
|
3
|
+
} from "./chunk-PIDFNJME.js";
|
|
1
4
|
import {
|
|
2
5
|
makeListsRoutes
|
|
3
6
|
} from "./chunk-22DJVJBR.js";
|
|
@@ -6,8 +9,11 @@ import {
|
|
|
6
9
|
} from "./chunk-IIGDU5SV.js";
|
|
7
10
|
import "./chunk-NG5GJME5.js";
|
|
8
11
|
import {
|
|
12
|
+
makeLocalUploadHandler,
|
|
13
|
+
makeR2UploadHandler,
|
|
14
|
+
makeUploadRoute,
|
|
9
15
|
setCanciaRuntime
|
|
10
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-AIPRCBJM.js";
|
|
11
17
|
import {
|
|
12
18
|
defineField,
|
|
13
19
|
defineList,
|
|
@@ -16,27 +22,29 @@ import {
|
|
|
16
22
|
} from "./chunk-MCHQV6Y7.js";
|
|
17
23
|
import {
|
|
18
24
|
canciaLoader
|
|
19
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-UR5WC3RA.js";
|
|
20
26
|
import {
|
|
21
|
-
createGitBackedAdapter,
|
|
22
27
|
createSQLiteAdapter
|
|
23
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-AE4SIY24.js";
|
|
29
|
+
import {
|
|
30
|
+
closeSqliteAdapterV2,
|
|
31
|
+
createGitBackedAdapter,
|
|
32
|
+
createSqliteAdapterV2
|
|
33
|
+
} from "./chunk-52URFK5Y.js";
|
|
24
34
|
import {
|
|
25
35
|
createJsonFileAdapter,
|
|
26
36
|
createJsonFileAdapterV2
|
|
27
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-L2VKQJPY.js";
|
|
28
38
|
import {
|
|
29
39
|
RevConflictError
|
|
30
40
|
} from "./chunk-7IA5B5CF.js";
|
|
31
41
|
import "./chunk-BOIQNZAO.js";
|
|
32
|
-
import
|
|
33
|
-
detectImageType,
|
|
34
|
-
isValidSite
|
|
35
|
-
} from "./chunk-5IPHDIC6.js";
|
|
42
|
+
import "./chunk-5IPHDIC6.js";
|
|
36
43
|
|
|
37
44
|
// src/integration.ts
|
|
38
45
|
import { loadEnv } from "vite";
|
|
39
|
-
import { fileURLToPath } from "url";
|
|
46
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
47
|
+
import { isAbsolute, join as join2 } from "path";
|
|
40
48
|
|
|
41
49
|
// src/routes/content.ts
|
|
42
50
|
function makeContentRoute(storage, secret) {
|
|
@@ -84,59 +92,8 @@ function makeContentRoute(storage, secret) {
|
|
|
84
92
|
};
|
|
85
93
|
}
|
|
86
94
|
|
|
87
|
-
// src/routes/upload.ts
|
|
88
|
-
import { writeFile, mkdir } from "fs/promises";
|
|
89
|
-
import { join } from "path";
|
|
90
|
-
import { randomUUID } from "crypto";
|
|
91
|
-
function makeLocalUploadHandler(opts) {
|
|
92
|
-
const uploadDir = opts.uploadDir ?? join(process.cwd(), "public/uploads");
|
|
93
|
-
const publicUrlBase = opts.publicUrlBase ?? "/uploads";
|
|
94
|
-
const maxBytes = (opts.maxMB ?? 10) * 1024 * 1024;
|
|
95
|
-
return async (file, site, detected) => {
|
|
96
|
-
const dest = join(uploadDir, site);
|
|
97
|
-
await mkdir(dest, { recursive: true });
|
|
98
|
-
const name = `${randomUUID()}.${detected.ext}`;
|
|
99
|
-
await writeFile(join(dest, name), Buffer.from(await file.arrayBuffer()));
|
|
100
|
-
return `${publicUrlBase}/${site}/${name}`;
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
function makeUploadRoute(uploadHandler, secret, maxMB = 10) {
|
|
104
|
-
return async ({ request }) => {
|
|
105
|
-
if (secret) {
|
|
106
|
-
const token = request.headers.get("Authorization")?.replace("Bearer ", "").trim();
|
|
107
|
-
if (token !== secret) {
|
|
108
|
-
return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401 });
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
const form = await request.formData().catch(() => null);
|
|
112
|
-
const file = form?.get("file");
|
|
113
|
-
const site = form?.get("site");
|
|
114
|
-
if (!(file instanceof File)) {
|
|
115
|
-
return new Response(JSON.stringify({ error: "Missing file field" }), { status: 400 });
|
|
116
|
-
}
|
|
117
|
-
if (typeof site !== "string" || !isValidSite(site)) {
|
|
118
|
-
return new Response(
|
|
119
|
-
JSON.stringify({ error: "Invalid site (allowed: a-z, 0-9, and . _ -; must start alphanumeric)" }),
|
|
120
|
-
{ status: 400 }
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
if (file.size > maxMB * 1024 * 1024) {
|
|
124
|
-
return new Response(JSON.stringify({ error: `File too large (max ${maxMB}MB)` }), { status: 413 });
|
|
125
|
-
}
|
|
126
|
-
const buf = new Uint8Array(await file.arrayBuffer());
|
|
127
|
-
const detected = detectImageType(buf);
|
|
128
|
-
if (!detected) {
|
|
129
|
-
return new Response(JSON.stringify({ error: "File type not allowed" }), { status: 415 });
|
|
130
|
-
}
|
|
131
|
-
const url = await uploadHandler(file, site, detected);
|
|
132
|
-
return new Response(JSON.stringify({ url }), {
|
|
133
|
-
headers: { "Content-Type": "application/json" }
|
|
134
|
-
});
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
95
|
// src/routes/publish.ts
|
|
139
|
-
function makePublishRoute(deployHook, secret) {
|
|
96
|
+
function makePublishRoute(deployHook, secret, hookOptions) {
|
|
140
97
|
return async ({ request }) => {
|
|
141
98
|
if (secret) {
|
|
142
99
|
const token = request.headers.get("Authorization")?.replace("Bearer ", "").trim();
|
|
@@ -145,26 +102,25 @@ function makePublishRoute(deployHook, secret) {
|
|
|
145
102
|
}
|
|
146
103
|
}
|
|
147
104
|
const hook = deployHook ?? process.env.CANCIA_DEPLOY_HOOK;
|
|
148
|
-
|
|
105
|
+
const result = await firePublish(hook, hookOptions);
|
|
106
|
+
if (result.ok) {
|
|
107
|
+
return new Response(JSON.stringify({ ok: true }), {
|
|
108
|
+
headers: { "Content-Type": "application/json" }
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (result.kind === "no-hook") {
|
|
149
112
|
return new Response(
|
|
150
113
|
JSON.stringify({ error: "No deploy hook configured. Set CANCIA_DEPLOY_HOOK." }),
|
|
151
114
|
{ status: 503 }
|
|
152
115
|
);
|
|
153
116
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
{ status: 502 }
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
return new Response(JSON.stringify({ ok: true }), {
|
|
163
|
-
headers: { "Content-Type": "application/json" }
|
|
164
|
-
});
|
|
165
|
-
} catch {
|
|
166
|
-
return new Response(JSON.stringify({ error: "Failed to reach deploy hook" }), { status: 502 });
|
|
117
|
+
if (result.kind === "bad-response") {
|
|
118
|
+
return new Response(
|
|
119
|
+
JSON.stringify({ error: `Deploy hook responded with ${result.status}` }),
|
|
120
|
+
{ status: 502 }
|
|
121
|
+
);
|
|
167
122
|
}
|
|
123
|
+
return new Response(JSON.stringify({ error: "Failed to reach deploy hook" }), { status: 502 });
|
|
168
124
|
};
|
|
169
125
|
}
|
|
170
126
|
|
|
@@ -227,109 +183,15 @@ function makeAuthRoute(secret) {
|
|
|
227
183
|
};
|
|
228
184
|
}
|
|
229
185
|
|
|
230
|
-
// src/routes/upload-r2.ts
|
|
231
|
-
import { createHmac, createHash } from "crypto";
|
|
232
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
233
|
-
function sha256hex(data) {
|
|
234
|
-
return createHash("sha256").update(data).digest("hex");
|
|
235
|
-
}
|
|
236
|
-
function hmacSha256(key, data) {
|
|
237
|
-
return createHmac("sha256", key).update(data).digest();
|
|
238
|
-
}
|
|
239
|
-
function getSigningKey(secretKey, date, region, service) {
|
|
240
|
-
const kDate = hmacSha256(Buffer.from(`AWS4${secretKey}`, "utf8"), date);
|
|
241
|
-
const kRegion = hmacSha256(kDate, region);
|
|
242
|
-
const kService = hmacSha256(kRegion, service);
|
|
243
|
-
const kSigning = hmacSha256(kService, "aws4_request");
|
|
244
|
-
return kSigning;
|
|
245
|
-
}
|
|
246
|
-
async function signedPutRequest(opts) {
|
|
247
|
-
const { endpoint, bucket, key, body, contentType, accessKeyId, secretAccessKey } = opts;
|
|
248
|
-
const region = "auto";
|
|
249
|
-
const service = "s3";
|
|
250
|
-
const now = /* @__PURE__ */ new Date();
|
|
251
|
-
const isoDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "").slice(0, 15) + "Z";
|
|
252
|
-
const shortDate = isoDate.slice(0, 8);
|
|
253
|
-
const url = `${endpoint}/${bucket}/${key}`;
|
|
254
|
-
const host = new URL(endpoint).host;
|
|
255
|
-
const payloadHash = sha256hex(body);
|
|
256
|
-
const headers = {
|
|
257
|
-
"content-type": contentType,
|
|
258
|
-
"host": host,
|
|
259
|
-
"x-amz-content-sha256": payloadHash,
|
|
260
|
-
"x-amz-date": isoDate
|
|
261
|
-
};
|
|
262
|
-
const signedHeaders = Object.keys(headers).sort().join(";");
|
|
263
|
-
const canonicalHeaders = Object.keys(headers).sort().map((k) => `${k}:${headers[k]}
|
|
264
|
-
`).join("");
|
|
265
|
-
const canonicalRequest = [
|
|
266
|
-
"PUT",
|
|
267
|
-
`/${bucket}/${key}`,
|
|
268
|
-
"",
|
|
269
|
-
canonicalHeaders,
|
|
270
|
-
signedHeaders,
|
|
271
|
-
payloadHash
|
|
272
|
-
].join("\n");
|
|
273
|
-
const credentialScope = `${shortDate}/${region}/${service}/aws4_request`;
|
|
274
|
-
const stringToSign = [
|
|
275
|
-
"AWS4-HMAC-SHA256",
|
|
276
|
-
isoDate,
|
|
277
|
-
credentialScope,
|
|
278
|
-
sha256hex(canonicalRequest)
|
|
279
|
-
].join("\n");
|
|
280
|
-
const signingKey = getSigningKey(secretAccessKey, shortDate, region, service);
|
|
281
|
-
const signature = createHmac("sha256", signingKey).update(stringToSign).digest("hex");
|
|
282
|
-
const authHeader = `AWS4-HMAC-SHA256 Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
283
|
-
let res;
|
|
284
|
-
try {
|
|
285
|
-
res = await fetch(url, {
|
|
286
|
-
method: "PUT",
|
|
287
|
-
headers: { ...headers, Authorization: authHeader },
|
|
288
|
-
body: new Uint8Array(body)
|
|
289
|
-
});
|
|
290
|
-
} catch (err) {
|
|
291
|
-
throw new Error(`R2 upload: network error reaching ${url} \u2014 ${err}`);
|
|
292
|
-
}
|
|
293
|
-
if (!res.ok) {
|
|
294
|
-
const text = await res.text().catch(() => res.statusText);
|
|
295
|
-
throw new Error(`R2 upload failed (${res.status}): ${text}`);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
function makeR2UploadHandler(opts) {
|
|
299
|
-
const {
|
|
300
|
-
accountId,
|
|
301
|
-
bucket,
|
|
302
|
-
accessKeyId,
|
|
303
|
-
secretAccessKey,
|
|
304
|
-
publicUrl,
|
|
305
|
-
prefix = "uploads"
|
|
306
|
-
} = opts;
|
|
307
|
-
const endpoint = `https://${accountId}.r2.cloudflarestorage.com`;
|
|
308
|
-
return async (file, _site, detected) => {
|
|
309
|
-
const key = `${prefix}/${randomUUID2()}.${detected.ext}`;
|
|
310
|
-
const body = Buffer.from(await file.arrayBuffer());
|
|
311
|
-
await signedPutRequest({
|
|
312
|
-
endpoint,
|
|
313
|
-
bucket,
|
|
314
|
-
key,
|
|
315
|
-
body,
|
|
316
|
-
contentType: file.type,
|
|
317
|
-
accessKeyId,
|
|
318
|
-
secretAccessKey
|
|
319
|
-
});
|
|
320
|
-
return `${publicUrl.replace(/\/$/, "")}/${key}`;
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
|
|
324
186
|
// src/token.ts
|
|
325
187
|
import { randomBytes } from "crypto";
|
|
326
188
|
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
327
|
-
import { join
|
|
189
|
+
import { join } from "path";
|
|
328
190
|
function generateToken() {
|
|
329
191
|
return randomBytes(32).toString("hex");
|
|
330
192
|
}
|
|
331
193
|
function ensureToken(rootPath) {
|
|
332
|
-
const envPath =
|
|
194
|
+
const envPath = join(rootPath, ".env");
|
|
333
195
|
let contents = "";
|
|
334
196
|
if (existsSync(envPath)) {
|
|
335
197
|
contents = readFileSync(envPath, "utf-8");
|
|
@@ -348,6 +210,13 @@ CANCIA_TOKEN=${token}
|
|
|
348
210
|
}
|
|
349
211
|
|
|
350
212
|
// src/integration.ts
|
|
213
|
+
function buildDefaultV2Store(opts, projectRoot) {
|
|
214
|
+
if (opts.db?.kind === "sqlite") {
|
|
215
|
+
const path = opts.db.path ? isAbsolute(opts.db.path) ? opts.db.path : join2(projectRoot, opts.db.path) : join2(projectRoot, "cancia.db");
|
|
216
|
+
return createSqliteAdapterV2({ dbPath: path });
|
|
217
|
+
}
|
|
218
|
+
return createJsonFileAdapterV2({ projectRoot });
|
|
219
|
+
}
|
|
351
220
|
function canciaIntegration(opts = {}) {
|
|
352
221
|
let storage;
|
|
353
222
|
let resolvedToken = "";
|
|
@@ -356,6 +225,7 @@ function canciaIntegration(opts = {}) {
|
|
|
356
225
|
let resolvedUploadHandler;
|
|
357
226
|
let resolvedDeployHook;
|
|
358
227
|
let resolvedLocales = ["en"];
|
|
228
|
+
let bakedConfig = null;
|
|
359
229
|
return {
|
|
360
230
|
name: "@cancia/astro",
|
|
361
231
|
hooks: {
|
|
@@ -385,6 +255,46 @@ function canciaIntegration(opts = {}) {
|
|
|
385
255
|
\x1B[32mcancia\x1B[0m No CANCIA_TOKEN found \u2014 generated one for you.`);
|
|
386
256
|
}
|
|
387
257
|
const hasDeployHook = !!(opts.deployHook ?? env.CANCIA_DEPLOY_HOOK ?? process.env.CANCIA_DEPLOY_HOOK);
|
|
258
|
+
let storageDescriptor;
|
|
259
|
+
if (opts.git) {
|
|
260
|
+
storageDescriptor = {
|
|
261
|
+
kind: "git-backed",
|
|
262
|
+
repo: opts.git.repo,
|
|
263
|
+
branch: opts.git.branch,
|
|
264
|
+
committer: opts.git.committer,
|
|
265
|
+
debounceMs: opts.git.debounceMs,
|
|
266
|
+
commitMessage: opts.git.commitMessage
|
|
267
|
+
};
|
|
268
|
+
} else if (opts.storageV2 === null) {
|
|
269
|
+
storageDescriptor = void 0;
|
|
270
|
+
} else if (opts.db?.kind === "sqlite") {
|
|
271
|
+
storageDescriptor = { kind: "sqlite-v2", dbPath: opts.db.path };
|
|
272
|
+
} else {
|
|
273
|
+
storageDescriptor = { kind: "json-file", dbPath: opts.dbPath };
|
|
274
|
+
}
|
|
275
|
+
let r2Baked;
|
|
276
|
+
if (opts.r2) {
|
|
277
|
+
const r2Opts = typeof opts.r2 === "function" ? opts.r2() : opts.r2;
|
|
278
|
+
r2Baked = {
|
|
279
|
+
accountId: r2Opts.accountId,
|
|
280
|
+
bucket: r2Opts.bucket,
|
|
281
|
+
publicUrl: r2Opts.publicUrl,
|
|
282
|
+
prefix: r2Opts.prefix
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
bakedConfig = {
|
|
286
|
+
projectRoot: resolvedRootPath,
|
|
287
|
+
schemasPath: opts.schemasPath,
|
|
288
|
+
defaultLocale: resolvedLocales[0],
|
|
289
|
+
locales: resolvedLocales,
|
|
290
|
+
maxUploadMB: opts.maxUploadMB ?? 10,
|
|
291
|
+
public: opts.public ?? false,
|
|
292
|
+
hasDeployHook,
|
|
293
|
+
deployHookMethod: opts.deployHookMethod,
|
|
294
|
+
deployHookHeaders: opts.deployHookHeaders,
|
|
295
|
+
storage: storageDescriptor,
|
|
296
|
+
r2: r2Baked
|
|
297
|
+
};
|
|
388
298
|
injectScript(
|
|
389
299
|
"head-inline",
|
|
390
300
|
`window.__CANCIA__=${JSON.stringify({
|
|
@@ -410,6 +320,8 @@ function canciaIntegration(opts = {}) {
|
|
|
410
320
|
injectRoute({ pattern: "/api/cancia/schemas", entrypoint: endpointPath("schemas"), prerender: false });
|
|
411
321
|
injectRoute({ pattern: "/api/cancia/lists/[listName]", entrypoint: endpointPath("lists"), prerender: false });
|
|
412
322
|
injectRoute({ pattern: "/api/cancia/lists/[listName]/[id]", entrypoint: endpointPath("lists"), prerender: false });
|
|
323
|
+
const runtimeModulePath = fileURLToPath(new URL("./runtime.js", import.meta.url));
|
|
324
|
+
const RESOLVED_VIRTUAL_ID = "\0virtual:cancia/runtime";
|
|
413
325
|
updateConfig({
|
|
414
326
|
vite: {
|
|
415
327
|
plugins: [
|
|
@@ -417,8 +329,18 @@ function canciaIntegration(opts = {}) {
|
|
|
417
329
|
name: "vite-plugin-cancia-runtime",
|
|
418
330
|
resolveId(id) {
|
|
419
331
|
if (id === "virtual:cancia/runtime") {
|
|
420
|
-
return
|
|
332
|
+
return RESOLVED_VIRTUAL_ID;
|
|
421
333
|
}
|
|
334
|
+
},
|
|
335
|
+
load(id) {
|
|
336
|
+
if (id !== RESOLVED_VIRTUAL_ID) return;
|
|
337
|
+
const importUrl = pathToFileURL(runtimeModulePath).href;
|
|
338
|
+
const bakedLiteral = JSON.stringify(bakedConfig ?? null);
|
|
339
|
+
return [
|
|
340
|
+
`import { setBakedConfig, getCanciaRuntime } from ${JSON.stringify(importUrl)};`,
|
|
341
|
+
`setBakedConfig(${bakedLiteral});`,
|
|
342
|
+
`export { getCanciaRuntime };`
|
|
343
|
+
].join("\n");
|
|
422
344
|
}
|
|
423
345
|
}
|
|
424
346
|
]
|
|
@@ -429,6 +351,7 @@ function canciaIntegration(opts = {}) {
|
|
|
429
351
|
storage = opts.storage ?? createJsonFileAdapter(opts.dbPath);
|
|
430
352
|
const secret = resolvedToken;
|
|
431
353
|
const deployHook = opts.deployHook ?? process.env.CANCIA_DEPLOY_HOOK;
|
|
354
|
+
const deployHookToken = process.env.CANCIA_DEPLOY_HOOK_TOKEN || opts.deployHookToken;
|
|
432
355
|
let uploadHandler;
|
|
433
356
|
if (opts.r2) {
|
|
434
357
|
const serverEnv = loadEnv(process.env.NODE_ENV ?? "development", resolvedRootPath, "");
|
|
@@ -448,7 +371,7 @@ function canciaIntegration(opts = {}) {
|
|
|
448
371
|
const routeSecret = opts.public ? void 0 : secret || void 0;
|
|
449
372
|
resolvedUploadHandler = uploadHandler;
|
|
450
373
|
resolvedDeployHook = deployHook;
|
|
451
|
-
const storageV2 = opts.storageV2 === null ? void 0 : opts.storageV2 ??
|
|
374
|
+
const storageV2 = opts.storageV2 === null ? void 0 : opts.storageV2 ?? buildDefaultV2Store(opts, resolvedRootPath);
|
|
452
375
|
setCanciaRuntime({
|
|
453
376
|
storage,
|
|
454
377
|
storageV2,
|
|
@@ -459,11 +382,18 @@ function canciaIntegration(opts = {}) {
|
|
|
459
382
|
secret: routeSecret,
|
|
460
383
|
uploadHandler,
|
|
461
384
|
deployHook,
|
|
385
|
+
deployHookToken,
|
|
386
|
+
deployHookMethod: opts.deployHookMethod,
|
|
387
|
+
deployHookHeaders: opts.deployHookHeaders,
|
|
462
388
|
maxUploadMB: opts.maxUploadMB ?? 10
|
|
463
389
|
});
|
|
464
390
|
const contentRoutes = makeContentRoute(storage, routeSecret);
|
|
465
391
|
const uploadRoute = makeUploadRoute(uploadHandler, routeSecret, opts.maxUploadMB);
|
|
466
|
-
const publishRoute = makePublishRoute(deployHook, routeSecret
|
|
392
|
+
const publishRoute = makePublishRoute(deployHook, routeSecret, {
|
|
393
|
+
token: deployHookToken,
|
|
394
|
+
method: opts.deployHookMethod,
|
|
395
|
+
headers: opts.deployHookHeaders
|
|
396
|
+
});
|
|
467
397
|
const authRoute = makeAuthRoute(secret);
|
|
468
398
|
const listsRoutes = makeListsRoutes({
|
|
469
399
|
storageV2,
|
|
@@ -595,7 +525,8 @@ function canciaIntegration(opts = {}) {
|
|
|
595
525
|
uploadHandler = opts.uploadHandler ?? makeLocalUploadHandler({ maxMB: opts.maxUploadMB });
|
|
596
526
|
}
|
|
597
527
|
const deployHook = opts.deployHook ?? process.env.CANCIA_DEPLOY_HOOK;
|
|
598
|
-
const
|
|
528
|
+
const deployHookToken = process.env.CANCIA_DEPLOY_HOOK_TOKEN || opts.deployHookToken;
|
|
529
|
+
const storageV2 = opts.storageV2 === null ? void 0 : opts.storageV2 ?? buildDefaultV2Store(opts, resolvedRootPath);
|
|
599
530
|
setCanciaRuntime({
|
|
600
531
|
storage,
|
|
601
532
|
storageV2,
|
|
@@ -606,6 +537,9 @@ function canciaIntegration(opts = {}) {
|
|
|
606
537
|
secret: routeSecret,
|
|
607
538
|
uploadHandler,
|
|
608
539
|
deployHook,
|
|
540
|
+
deployHookToken,
|
|
541
|
+
deployHookMethod: opts.deployHookMethod,
|
|
542
|
+
deployHookHeaders: opts.deployHookHeaders,
|
|
609
543
|
maxUploadMB: opts.maxUploadMB ?? 10
|
|
610
544
|
});
|
|
611
545
|
}
|
|
@@ -646,10 +580,12 @@ export {
|
|
|
646
580
|
RevConflictError,
|
|
647
581
|
canciaIntegration,
|
|
648
582
|
canciaLoader,
|
|
583
|
+
closeSqliteAdapterV2,
|
|
649
584
|
createGitBackedAdapter,
|
|
650
585
|
createJsonFileAdapter,
|
|
651
586
|
createJsonFileAdapterV2,
|
|
652
587
|
createSQLiteAdapter,
|
|
588
|
+
createSqliteAdapterV2,
|
|
653
589
|
canciaIntegration as default,
|
|
654
590
|
defineField,
|
|
655
591
|
defineList,
|
package/dist/loader/index.js
CHANGED
package/dist/runtime.d.ts
CHANGED
|
@@ -1,5 +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
4
|
|
|
4
5
|
interface CanciaRuntime {
|
|
5
6
|
/** v1 KV-only storage. Kept for the existing /content endpoint. */
|
|
@@ -26,9 +27,70 @@ interface CanciaRuntime {
|
|
|
26
27
|
secret: string | undefined;
|
|
27
28
|
uploadHandler: UploadHandler;
|
|
28
29
|
deployHook: string | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Auth token for the deploy hook (e.g. Coolify's "auth required" hook).
|
|
32
|
+
* SECRET — read from CANCIA_DEPLOY_HOOK_TOKEN at runtime, never baked. When
|
|
33
|
+
* set the publish POST sends `Authorization: Bearer <token>`.
|
|
34
|
+
*/
|
|
35
|
+
deployHookToken: string | undefined;
|
|
36
|
+
/** Optional HTTP method for the hook (default POST). Non-secret, baked. */
|
|
37
|
+
deployHookMethod: string | undefined;
|
|
38
|
+
/** Optional extra headers merged into the hook request. Non-secret, baked. */
|
|
39
|
+
deployHookHeaders: Record<string, string> | undefined;
|
|
40
|
+
maxUploadMB: number;
|
|
41
|
+
}
|
|
42
|
+
/** Describes which storage adapter to build lazily in the server process. */
|
|
43
|
+
type BakedStorageDescriptor = {
|
|
44
|
+
kind: "json-file";
|
|
45
|
+
dbPath?: string;
|
|
46
|
+
} | {
|
|
47
|
+
kind: "sqlite-v2";
|
|
48
|
+
dbPath?: string;
|
|
49
|
+
} | {
|
|
50
|
+
kind: "git-backed";
|
|
51
|
+
repo: string;
|
|
52
|
+
branch?: string;
|
|
53
|
+
committer?: GitHubCommitter;
|
|
54
|
+
debounceMs?: number;
|
|
55
|
+
commitMessage?: string;
|
|
56
|
+
};
|
|
57
|
+
/** R2 config baked minus its secrets (accessKeyId/secretAccessKey come from env). */
|
|
58
|
+
interface BakedR2Config {
|
|
59
|
+
accountId?: string;
|
|
60
|
+
bucket?: string;
|
|
61
|
+
publicUrl?: string;
|
|
62
|
+
prefix?: string;
|
|
63
|
+
}
|
|
64
|
+
interface BakedConfig {
|
|
65
|
+
projectRoot: string;
|
|
66
|
+
schemasPath: string | undefined;
|
|
67
|
+
defaultLocale: string;
|
|
68
|
+
locales: string[];
|
|
29
69
|
maxUploadMB: number;
|
|
70
|
+
/** When true, endpoints skip auth (public demos). Baked, never a secret. */
|
|
71
|
+
public: boolean;
|
|
72
|
+
/** Whether a deploy hook exists — the value itself is read from env. */
|
|
73
|
+
hasDeployHook: boolean;
|
|
74
|
+
/** Optional deploy-hook HTTP method (default POST). Non-secret. */
|
|
75
|
+
deployHookMethod: string | undefined;
|
|
76
|
+
/** Optional deploy-hook extra headers. Non-secret (the token stays in env). */
|
|
77
|
+
deployHookHeaders: Record<string, string> | undefined;
|
|
78
|
+
/** How to build the v2 storage adapter at runtime. Absent = no v2 storage. */
|
|
79
|
+
storage: BakedStorageDescriptor | undefined;
|
|
80
|
+
/** When set, build an R2 upload handler; secrets come from env at runtime. */
|
|
81
|
+
r2: BakedR2Config | undefined;
|
|
30
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Called by the generated virtual:cancia/runtime module at import time with the
|
|
85
|
+
* build-baked, non-secret config. Enables lazy self-init in a fresh process.
|
|
86
|
+
*/
|
|
87
|
+
declare function setBakedConfig(config: BakedConfig): void;
|
|
88
|
+
/**
|
|
89
|
+
* Eagerly set the runtime. Used in dev (astro:server:setup) and in-process
|
|
90
|
+
* builds where the resolved runtime already exists. Production falls through
|
|
91
|
+
* to lazy init instead.
|
|
92
|
+
*/
|
|
31
93
|
declare function setCanciaRuntime(runtime: CanciaRuntime): void;
|
|
32
94
|
declare function getCanciaRuntime(): CanciaRuntime;
|
|
33
95
|
|
|
34
|
-
export { type CanciaRuntime, getCanciaRuntime, setCanciaRuntime };
|
|
96
|
+
export { type BakedConfig, type BakedR2Config, type BakedStorageDescriptor, type CanciaRuntime, getCanciaRuntime, setBakedConfig, setCanciaRuntime };
|
package/dist/runtime.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getCanciaRuntime,
|
|
3
|
+
setBakedConfig,
|
|
3
4
|
setCanciaRuntime
|
|
4
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-AIPRCBJM.js";
|
|
6
|
+
import "./chunk-52URFK5Y.js";
|
|
7
|
+
import "./chunk-L2VKQJPY.js";
|
|
8
|
+
import "./chunk-7IA5B5CF.js";
|
|
9
|
+
import "./chunk-5IPHDIC6.js";
|
|
5
10
|
export {
|
|
6
11
|
getCanciaRuntime,
|
|
12
|
+
setBakedConfig,
|
|
7
13
|
setCanciaRuntime
|
|
8
14
|
};
|
package/dist/storage/index.d.ts
CHANGED
|
@@ -1,129 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { b as CanciaKVStore, c as CanciaListStore, d as CanciaPageStore, L as ListEntry, P as PageMeta, e as PageRecord, f as PageSEO,
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
listsDir?: string;
|
|
15
|
-
}
|
|
16
|
-
declare function createJsonFileAdapterV2(opts?: JsonFileV2Options): CanciaStorageV2;
|
|
17
|
-
|
|
18
|
-
declare function createSQLiteAdapter(dbPath?: string): CanciaStorage;
|
|
19
|
-
|
|
20
|
-
/** Minimal fetch signature — matches the global `fetch` we depend on. */
|
|
21
|
-
type FetchLike = (input: string, init?: {
|
|
22
|
-
method?: string;
|
|
23
|
-
headers?: Record<string, string>;
|
|
24
|
-
body?: string;
|
|
25
|
-
}) => Promise<{
|
|
26
|
-
ok: boolean;
|
|
27
|
-
status: number;
|
|
28
|
-
json(): Promise<unknown>;
|
|
29
|
-
text(): Promise<string>;
|
|
30
|
-
}>;
|
|
31
|
-
interface GitHubCommitter {
|
|
32
|
-
name: string;
|
|
33
|
-
email: string;
|
|
34
|
-
}
|
|
35
|
-
interface GitHubClientOptions {
|
|
36
|
-
/** "owner/name" */
|
|
37
|
-
repo: string;
|
|
38
|
-
/** Branch to commit onto, e.g. "main". */
|
|
39
|
-
branch: string;
|
|
40
|
-
/** Fine-grained PAT with contents:write on the one repo. */
|
|
41
|
-
token: string;
|
|
42
|
-
/** Optional committer identity. GitHub uses the token's user if omitted. */
|
|
43
|
-
committer?: GitHubCommitter;
|
|
44
|
-
/** Injectable fetch (defaults to global fetch) — tests mock this. */
|
|
45
|
-
fetch?: FetchLike;
|
|
46
|
-
/** API base — defaults to https://api.github.com. Overridable for tests. */
|
|
47
|
-
apiBase?: string;
|
|
48
|
-
}
|
|
49
|
-
interface CommitFile {
|
|
50
|
-
/** Repo-relative path, forward slashes, no leading slash. */
|
|
51
|
-
path: string;
|
|
52
|
-
/** Raw file content (UTF-8 text). Encoded to base64 before PUT. */
|
|
53
|
-
content: string;
|
|
54
|
-
}
|
|
55
|
-
interface GitHubClient {
|
|
56
|
-
/** Current blob sha for `path`, or null if the file doesn't exist yet. */
|
|
57
|
-
getFileSha(path: string): Promise<string | null>;
|
|
58
|
-
/**
|
|
59
|
-
* Commit each file to the branch. Updates pass the current sha; creates omit
|
|
60
|
-
* it. Resolves once every PUT succeeds; rejects (without partial silence) if
|
|
61
|
-
* any PUT fails so the caller can keep the batch dirty and retry.
|
|
62
|
-
*/
|
|
63
|
-
commitFiles(files: CommitFile[], message: string): Promise<void>;
|
|
64
|
-
}
|
|
65
|
-
declare function createGitHubClient(opts: GitHubClientOptions): GitHubClient;
|
|
66
|
-
|
|
67
|
-
interface GitBackedContentPaths {
|
|
68
|
-
/** KV file path. Default <projectRoot>/cancia-content.json. */
|
|
69
|
-
kvPath?: string;
|
|
70
|
-
/** Pages file path. Default <projectRoot>/.cancia/pages.json. */
|
|
71
|
-
pagesPath?: string;
|
|
72
|
-
/** Lists directory. Default <projectRoot>/.cancia/lists. */
|
|
73
|
-
listsDir?: string;
|
|
74
|
-
}
|
|
75
|
-
interface GitBackedOptions {
|
|
76
|
-
/** The wrapped local adapter — the on-disk source of truth. */
|
|
77
|
-
local: CanciaStorageV2;
|
|
78
|
-
/** "owner/name" of the GitHub repo whose builds carry the content. */
|
|
79
|
-
repo: string;
|
|
80
|
-
/** Branch to commit onto. Default "main". */
|
|
81
|
-
branch?: string;
|
|
82
|
-
/**
|
|
83
|
-
* GitHub PAT. Reads CANCIA_GITHUB_TOKEN if omitted. When absent entirely the
|
|
84
|
-
* adapter runs in local-only mode (disk writes only; no commits) + warns once.
|
|
85
|
-
*/
|
|
86
|
-
token?: string;
|
|
87
|
-
/** Optional committer identity for commits. */
|
|
88
|
-
committer?: GitHubCommitter;
|
|
89
|
-
/**
|
|
90
|
-
* Project root the local adapter writes under — needed to turn absolute
|
|
91
|
-
* on-disk paths into repo-relative commit paths. Default process.cwd().
|
|
92
|
-
*/
|
|
93
|
-
projectRoot?: string;
|
|
94
|
-
/** Override where the local adapter's content lives (must match `local`). */
|
|
95
|
-
contentPaths?: GitBackedContentPaths;
|
|
96
|
-
/** Quiet window (ms) before a flush fires. Default 3000. */
|
|
97
|
-
debounceMs?: number;
|
|
98
|
-
/** Commit message for content updates. */
|
|
99
|
-
commitMessage?: string;
|
|
100
|
-
/** Injected GitHub client (tests pass a mock). Overrides token/fetch. */
|
|
101
|
-
client?: GitHubClient;
|
|
102
|
-
/** Injected fetch, forwarded to the default GitHub client. */
|
|
103
|
-
fetch?: FetchLike;
|
|
104
|
-
/** API base override, forwarded to the default GitHub client (tests). */
|
|
105
|
-
apiBase?: string;
|
|
106
|
-
/** Warn sink (tests capture). Default console.warn. */
|
|
107
|
-
warn?: (msg: string) => void;
|
|
108
|
-
/** Error sink for push failures (tests capture). Default console.error. */
|
|
109
|
-
onError?: (msg: string, err: unknown) => void;
|
|
110
|
-
}
|
|
111
|
-
/** The extra control surface the git adapter adds on top of CanciaStorageV2. */
|
|
112
|
-
interface GitBackedControls {
|
|
113
|
-
/**
|
|
114
|
-
* Force any pending dirty files to commit now, bypassing the debounce.
|
|
115
|
-
* Resolves once the flush completes (or rejects if the push failed — the
|
|
116
|
-
* files stay dirty for the next flush). For tests + graceful shutdown.
|
|
117
|
-
*/
|
|
118
|
-
flush(): Promise<void>;
|
|
119
|
-
/** True if git commits are active (token present). */
|
|
120
|
-
readonly gitEnabled: boolean;
|
|
121
|
-
/** Snapshot of currently-dirty repo-relative paths (for tests/inspection). */
|
|
122
|
-
pendingPaths(): string[];
|
|
123
|
-
}
|
|
124
|
-
type GitBackedStorage = CanciaStorageV2 & {
|
|
125
|
-
git: GitBackedControls;
|
|
126
|
-
};
|
|
127
|
-
declare function createGitBackedAdapter(opts: GitBackedOptions): GitBackedStorage;
|
|
128
|
-
|
|
129
|
-
export { CanciaStorage, CanciaStorageV2, type CommitFile, type GitBackedContentPaths, type GitBackedControls, type GitBackedOptions, type GitBackedStorage, type GitHubClient, type GitHubClientOptions, type GitHubCommitter, createGitBackedAdapter, createGitHubClient, createJsonFileAdapter, createJsonFileAdapterV2, createSQLiteAdapter };
|
|
1
|
+
import { R as Rev } from '../types-BMlLS-OS.js';
|
|
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';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Canonical JSON: keys sorted alphabetically at every level. Two values that
|
|
8
|
+
* are logically equal hash to the same _rev regardless of insertion order.
|
|
9
|
+
*/
|
|
10
|
+
declare function canonicalize(value: unknown): string;
|
|
11
|
+
declare function hashRev(value: unknown): Rev;
|
|
12
|
+
|
|
13
|
+
export { Rev, canonicalize, hashRev };
|