@agent-cms/agentcms 0.0.0-stage → 0.1.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.
Files changed (84) hide show
  1. package/LICENSE +143 -0
  2. package/README.md +1119 -2
  3. package/changelog.json +31 -0
  4. package/dist/admin/admin-files.json +48 -0
  5. package/dist/admin/assets/CodeEditor-Wx1WlF3h.js +24 -0
  6. package/dist/admin/assets/flexweg-logo-CdtuqAFA.png +0 -0
  7. package/dist/admin/assets/index-DFVZw8E0.js +5460 -0
  8. package/dist/admin/assets/index-DMcGogR2.css +1 -0
  9. package/dist/admin/assets/sql-wasm-Bfkbn6Oa.js +2 -0
  10. package/dist/admin/assets/sql-wasm-C1U8OeUW.wasm +0 -0
  11. package/dist/admin/assets/sql-wasm-V5jTDcrT.js +1 -0
  12. package/dist/admin/config.js +11 -0
  13. package/dist/admin/external.default.json +72 -0
  14. package/dist/admin/index.html +116 -0
  15. package/dist/admin/plugins/core-seo/bundle.js +55 -0
  16. package/dist/admin/plugins/core-seo/manifest.json +7 -0
  17. package/dist/admin/plugins/flexweg-archives/bundle.js +1414 -0
  18. package/dist/admin/plugins/flexweg-archives/manifest.json +7 -0
  19. package/dist/admin/plugins/flexweg-multilang/bundle.js +1187 -0
  20. package/dist/admin/plugins/flexweg-multilang/manifest.json +7 -0
  21. package/dist/admin/plugins/flexweg-rss/bundle.js +1354 -0
  22. package/dist/admin/plugins/flexweg-rss/manifest.json +7 -0
  23. package/dist/admin/plugins/flexweg-search/bundle.js +1243 -0
  24. package/dist/admin/plugins/flexweg-search/manifest.json +7 -0
  25. package/dist/admin/plugins/flexweg-sitemaps/bundle.js +1293 -0
  26. package/dist/admin/plugins/flexweg-sitemaps/manifest.json +7 -0
  27. package/dist/admin/runtime/cms-runtime.js +164 -0
  28. package/dist/admin/runtime/react-dom-client.js +13 -0
  29. package/dist/admin/runtime/react-dom.js +16 -0
  30. package/dist/admin/runtime/react-i18next.js +21 -0
  31. package/dist/admin/runtime/react-jsx-runtime.js +17 -0
  32. package/dist/admin/runtime/react.js +55 -0
  33. package/dist/admin/themes/corporate/bundle.js +14465 -0
  34. package/dist/admin/themes/corporate/manifest.json +7 -0
  35. package/dist/admin/themes/corporate/theme.css +2 -0
  36. package/dist/admin/themes/magazine/bundle.js +10229 -0
  37. package/dist/admin/themes/magazine/manifest.json +7 -0
  38. package/dist/admin/themes/magazine/theme.css +2 -0
  39. package/dist/admin/themes/marketplace-core/bundle.js +11213 -0
  40. package/dist/admin/themes/marketplace-core/manifest.json +7 -0
  41. package/dist/admin/themes/marketplace-core/theme.css +1613 -0
  42. package/dist/admin/themes/portfolio/bundle.js +9195 -0
  43. package/dist/admin/themes/portfolio/manifest.json +7 -0
  44. package/dist/admin/themes/portfolio/theme.css +2 -0
  45. package/dist/admin/themes/storefront/bundle.js +13172 -0
  46. package/dist/admin/themes/storefront/manifest.json +7 -0
  47. package/dist/admin/themes/storefront/theme.css +2 -0
  48. package/dist/agent/agentcms.mjs +1711 -0
  49. package/dist/agent/chunks/hosting-BftlDdsL.mjs +117 -0
  50. package/dist/agent/chunks/index-BG05qslv.mjs +3608 -0
  51. package/dist/agent/chunks/install-DnmgOT3F.mjs +577 -0
  52. package/dist/agent/chunks/plan-BrM8ztEp.mjs +245 -0
  53. package/dist/agent/chunks/registry-C9TSnZ77.mjs +53517 -0
  54. package/dist/agent/chunks/siteAdd-BcXhN9fk.mjs +118 -0
  55. package/dist/agent/chunks/sql-wasm-GjJDiefO.mjs +10 -0
  56. package/dist/agent/chunks/sqljs-DjSCStJ0.mjs +20 -0
  57. package/dist/agent/vendor/sql-wasm.cjs +188 -0
  58. package/dist/theme-assets/corporate-menu.js +266 -0
  59. package/dist/theme-assets/corporate-posts.js +571 -0
  60. package/dist/theme-assets/corporate.css +2 -0
  61. package/dist/theme-assets/default-menu.js +266 -0
  62. package/dist/theme-assets/default-posts.js +378 -0
  63. package/dist/theme-assets/default.css +1 -0
  64. package/dist/theme-assets/magazine-menu.js +227 -0
  65. package/dist/theme-assets/magazine-posts.js +361 -0
  66. package/dist/theme-assets/magazine.css +2 -0
  67. package/dist/theme-assets/portfolio-filters.js +104 -0
  68. package/dist/theme-assets/portfolio-menu.js +240 -0
  69. package/dist/theme-assets/portfolio-posts.js +358 -0
  70. package/dist/theme-assets/portfolio.css +2 -0
  71. package/dist/theme-assets/storefront-catalog.js +504 -0
  72. package/dist/theme-assets/storefront-menu.js +239 -0
  73. package/dist/theme-assets/storefront-posts.js +404 -0
  74. package/dist/theme-assets/storefront.css +2 -0
  75. package/dist/version.json +9 -0
  76. package/package.json +101 -4
  77. package/plugin/.claude-plugin/plugin.json +16 -0
  78. package/plugin/skills/agentcms-install/SKILL.md +110 -0
  79. package/plugin/skills/agentcms-install/references/cloudflare.md +15 -0
  80. package/plugin/skills/agentcms-install/references/flexweg.md +11 -0
  81. package/plugin/skills/agentcms-install/references/github.md +67 -0
  82. package/plugin/skills/agentcms-install/references/gitlab.md +15 -0
  83. package/plugin/skills/agentcms-install/references/s3.md +81 -0
  84. package/plugin/skills/agentcms-manage/SKILL.md +92 -0
@@ -0,0 +1,577 @@
1
+ import { createRequire as __acmsRequire } from "node:module";
2
+ import { fileURLToPath as __acmsPath } from "node:url";
3
+ import { dirname as __acmsDirname } from "node:path";
4
+ const require2 = __acmsRequire(import.meta.url);
5
+ const __filename = __acmsPath(import.meta.url);
6
+ const __dirname = __acmsDirname(__filename);
7
+ import { g as getDoc, s as setDoc, d as doc, a as getDb, c as configDocs, b as collections, e as sqlQuery, f as sqlExec, h as getDocStore, i as getBackendKind, j as isDocStoreBackend, o as onSnapshot, k as settingsDocs, l as deleteField, m as sqlVersion, S as SqliteApiError, n as setFlexwegConfig, D as DEFAULT_FLEXWEG_API_BASE_URL, p as setGitToken, q as saveSqliteConnectorSecrets, r as saveSqlitePassphrase, t as testStorageConnection, u as openDatabaseBytes, v as githubPagesCheck, w as resetRuntimeConfigCache, x as resetDocStore } from "./registry-C9TSnZ77.mjs";
8
+ import { g as generatePassphrase, s as setCredential, i as isEncryptedDatabase, S as SqlitePassphraseError } from "../agentcms.mjs";
9
+ import { e as ensurePublishBranch } from "./plan-BrM8ztEp.mjs";
10
+ import { j as joinRepoPath, s as subscribeToToasts, c as createRemoteGitTransport } from "./index-BG05qslv.mjs";
11
+ import { loadSqlWasm, themeAssetOps, adminOps, applyOps, hostingConnector } from "./hosting-BftlDdsL.mjs";
12
+ import "util";
13
+ import "crypto";
14
+ import "process";
15
+ import "tls";
16
+ import "fs";
17
+ import "os";
18
+ import "net";
19
+ import "events";
20
+ import "stream";
21
+ import "path";
22
+ import "http2";
23
+ import "http";
24
+ import "url";
25
+ import "dns";
26
+ import "zlib";
27
+ import "buffer";
28
+ import "node:util";
29
+ import "node:fs";
30
+ import "node:path";
31
+ import "node:url";
32
+ import "node:os";
33
+ import "node:child_process";
34
+ import "node:http";
35
+ const storageDocRef = () => doc(getDb(), collections.config, configDocs.storage);
36
+ async function getStorageConfig$2() {
37
+ const snap = await getDoc(storageDocRef());
38
+ if (!snap.exists()) return null;
39
+ const data = snap.data();
40
+ if (!data?.connectorId) return null;
41
+ return { connectorId: data.connectorId, settings: data.settings ?? {} };
42
+ }
43
+ async function setStorageConfig$3(next) {
44
+ await setDoc(storageDocRef(), { connectorId: next.connectorId, settings: next.settings ?? {} });
45
+ }
46
+ const firebase$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
47
+ __proto__: null,
48
+ getStorageConfig: getStorageConfig$2,
49
+ setStorageConfig: setStorageConfig$3
50
+ }, Symbol.toStringTag, { value: "Module" }));
51
+ const CONFIG_KEY = "storage";
52
+ async function getStorageConfig$1() {
53
+ const { rows } = await sqlQuery(
54
+ "SELECT value FROM config WHERE key = ?",
55
+ [CONFIG_KEY]
56
+ );
57
+ if (rows.length === 0) return null;
58
+ let data;
59
+ try {
60
+ data = JSON.parse(rows[0].value);
61
+ } catch {
62
+ return null;
63
+ }
64
+ if (!data.connectorId) return null;
65
+ return { connectorId: data.connectorId, settings: data.settings ?? {} };
66
+ }
67
+ async function setStorageConfig$2(next) {
68
+ const payload = JSON.stringify({ connectorId: next.connectorId, settings: next.settings ?? {} });
69
+ await sqlExec(
70
+ `INSERT INTO config (key, value) VALUES (?, ?)
71
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`,
72
+ [CONFIG_KEY, payload]
73
+ );
74
+ }
75
+ const sqlite$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
76
+ __proto__: null,
77
+ getStorageConfig: getStorageConfig$1,
78
+ setStorageConfig: setStorageConfig$2
79
+ }, Symbol.toStringTag, { value: "Module" }));
80
+ const CONFIG_ID = "storage";
81
+ async function getStorageConfig() {
82
+ const store = getDocStore();
83
+ await store.ready();
84
+ const data = store.get("config", CONFIG_ID);
85
+ if (!data?.connectorId) return null;
86
+ return { connectorId: data.connectorId, settings: { ...data.settings ?? {} } };
87
+ }
88
+ async function setStorageConfig$1(next) {
89
+ await getDocStore().write(
90
+ [
91
+ {
92
+ op: "set",
93
+ collection: "config",
94
+ doc: { id: CONFIG_ID, connectorId: next.connectorId, settings: next.settings ?? {} }
95
+ }
96
+ ],
97
+ { message: "Update publish storage settings" }
98
+ );
99
+ }
100
+ const docstore$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
101
+ __proto__: null,
102
+ getStorageConfig,
103
+ setStorageConfig: setStorageConfig$1
104
+ }, Symbol.toStringTag, { value: "Module" }));
105
+ let _impl$1 = null;
106
+ function impl$1() {
107
+ if (!_impl$1) {
108
+ const kind = getBackendKind();
109
+ _impl$1 = kind === "flexweg-sqlite" ? sqlite$1 : isDocStoreBackend(kind) ? docstore$1 : firebase$1;
110
+ }
111
+ return _impl$1;
112
+ }
113
+ function setStorageConfig(...args) {
114
+ return impl$1().setStorageConfig(...args);
115
+ }
116
+ const siteSettingsRef = () => doc(getDb(), collections.settings, settingsDocs.site);
117
+ const DEFAULT_SITE_SETTINGS = {
118
+ title: "My site",
119
+ description: "",
120
+ language: "en",
121
+ baseUrl: "",
122
+ activeThemeId: "default",
123
+ enabledPlugins: {
124
+ "core-seo": true,
125
+ "flexweg-sitemaps": true,
126
+ "flexweg-rss": true,
127
+ "flexweg-archives": true,
128
+ // Off by default — a one-shot migration tool. Users enable it
129
+ // when they need to import a batch and disable it afterward.
130
+ "flexweg-import": false
131
+ },
132
+ homeMode: "latest-posts",
133
+ postsPerPage: 10,
134
+ menus: { header: [], footer: [] },
135
+ pluginConfigs: {},
136
+ themeConfigs: {},
137
+ paginationMode: "global"
138
+ };
139
+ function subscribeToSettings$2(onChange, onError) {
140
+ return onSnapshot(
141
+ siteSettingsRef(),
142
+ (snap) => {
143
+ const data = snap.data();
144
+ onChange({ ...DEFAULT_SITE_SETTINGS, ...data ?? {} });
145
+ },
146
+ onError
147
+ );
148
+ }
149
+ async function getSettings$3() {
150
+ const snap = await getDoc(siteSettingsRef());
151
+ const data = snap.exists() ? snap.data() : {};
152
+ return { ...DEFAULT_SITE_SETTINGS, ...data };
153
+ }
154
+ async function updateSettings$3(patch) {
155
+ const sanitized = {};
156
+ for (const [key, value] of Object.entries(patch)) {
157
+ sanitized[key] = value === void 0 ? deleteField() : value;
158
+ }
159
+ await setDoc(siteSettingsRef(), sanitized, { merge: true });
160
+ }
161
+ async function updatePluginConfig$2(pluginId, config) {
162
+ await setDoc(
163
+ siteSettingsRef(),
164
+ { pluginConfigs: { [pluginId]: config } },
165
+ { merge: true }
166
+ );
167
+ }
168
+ async function updateThemeConfig$2(themeId, config) {
169
+ await setDoc(
170
+ siteSettingsRef(),
171
+ { themeConfigs: { [themeId]: config } },
172
+ { merge: true }
173
+ );
174
+ }
175
+ const firebase = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
176
+ __proto__: null,
177
+ DEFAULT_SITE_SETTINGS,
178
+ getSettings: getSettings$3,
179
+ subscribeToSettings: subscribeToSettings$2,
180
+ updatePluginConfig: updatePluginConfig$2,
181
+ updateSettings: updateSettings$3,
182
+ updateThemeConfig: updateThemeConfig$2
183
+ }, Symbol.toStringTag, { value: "Module" }));
184
+ const POLL_INTERVAL_MS = 4e3;
185
+ const subscribers = /* @__PURE__ */ new Set();
186
+ let currentVersion = null;
187
+ let pollerTimer = null;
188
+ let pollerInflight = false;
189
+ async function poll() {
190
+ if (pollerInflight) return;
191
+ pollerInflight = true;
192
+ try {
193
+ const info = await sqlVersion();
194
+ if (currentVersion === null) {
195
+ currentVersion = info.version;
196
+ return;
197
+ }
198
+ if (info.version !== currentVersion) {
199
+ currentVersion = info.version;
200
+ const snapshot = Array.from(subscribers);
201
+ for (const sub of snapshot) {
202
+ try {
203
+ const data = await sub.fetch();
204
+ sub.onChange(data);
205
+ } catch (err) {
206
+ if (sub.onError) sub.onError(err);
207
+ }
208
+ }
209
+ }
210
+ } catch (err) {
211
+ if (err instanceof SqliteApiError && (err.status === 401 || err.status === 403)) {
212
+ const snapshot = Array.from(subscribers);
213
+ for (const sub of snapshot) {
214
+ if (sub.onError) sub.onError(err);
215
+ }
216
+ }
217
+ } finally {
218
+ pollerInflight = false;
219
+ }
220
+ }
221
+ function startPoller() {
222
+ if (pollerTimer !== null) return;
223
+ poll();
224
+ pollerTimer = setInterval(poll, POLL_INTERVAL_MS);
225
+ }
226
+ function stopPoller() {
227
+ if (pollerTimer === null) return;
228
+ clearInterval(pollerTimer);
229
+ pollerTimer = null;
230
+ currentVersion = null;
231
+ }
232
+ function subscribeWithPolling(fetch, onChange, onError) {
233
+ const sub = { fetch, onChange, onError };
234
+ subscribers.add(sub);
235
+ startPoller();
236
+ fetch().then(onChange).catch((err) => {
237
+ if (onError) onError(err);
238
+ else console.error("subscribeWithPolling initial fetch failed", err);
239
+ });
240
+ return () => {
241
+ subscribers.delete(sub);
242
+ if (subscribers.size === 0) stopPoller();
243
+ };
244
+ }
245
+ function notifyPotentialChange() {
246
+ setTimeout(() => {
247
+ poll();
248
+ }, 50);
249
+ }
250
+ const SETTINGS_KEY = "site";
251
+ function readBlob(value) {
252
+ if (!value) return {};
253
+ try {
254
+ const v = JSON.parse(value);
255
+ return v && typeof v === "object" ? v : {};
256
+ } catch {
257
+ return {};
258
+ }
259
+ }
260
+ async function readRow() {
261
+ const { rows } = await sqlQuery(
262
+ "SELECT value FROM settings WHERE key = ?",
263
+ [SETTINGS_KEY]
264
+ );
265
+ if (rows.length === 0) return {};
266
+ return readBlob(rows[0].value);
267
+ }
268
+ async function writeRow(data) {
269
+ await sqlExec(
270
+ `INSERT INTO settings (key, value) VALUES (?, ?)
271
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`,
272
+ [SETTINGS_KEY, JSON.stringify(data)]
273
+ );
274
+ }
275
+ function subscribeToSettings$1(onChange, onError) {
276
+ return subscribeWithPolling(
277
+ async () => {
278
+ const data = await readRow();
279
+ return { ...DEFAULT_SITE_SETTINGS, ...data };
280
+ },
281
+ onChange,
282
+ onError
283
+ );
284
+ }
285
+ async function getSettings$2() {
286
+ const data = await readRow();
287
+ return { ...DEFAULT_SITE_SETTINGS, ...data };
288
+ }
289
+ async function updateSettings$2(patch) {
290
+ const current = await readRow();
291
+ const next = { ...current };
292
+ for (const [key, value] of Object.entries(patch)) {
293
+ if (value === void 0) {
294
+ delete next[key];
295
+ } else {
296
+ next[key] = value;
297
+ }
298
+ }
299
+ await writeRow(next);
300
+ notifyPotentialChange();
301
+ }
302
+ async function updatePluginConfig$1(pluginId, config) {
303
+ const current = await readRow();
304
+ const pluginConfigs = { ...current.pluginConfigs ?? {} };
305
+ pluginConfigs[pluginId] = config;
306
+ await writeRow({ ...current, pluginConfigs });
307
+ notifyPotentialChange();
308
+ }
309
+ async function updateThemeConfig$1(themeId, config) {
310
+ const current = await readRow();
311
+ const themeConfigs = { ...current.themeConfigs ?? {} };
312
+ themeConfigs[themeId] = config;
313
+ await writeRow({ ...current, themeConfigs });
314
+ notifyPotentialChange();
315
+ }
316
+ const sqlite = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
317
+ __proto__: null,
318
+ DEFAULT_SITE_SETTINGS,
319
+ getSettings: getSettings$2,
320
+ subscribeToSettings: subscribeToSettings$1,
321
+ updatePluginConfig: updatePluginConfig$1,
322
+ updateSettings: updateSettings$2,
323
+ updateThemeConfig: updateThemeConfig$1
324
+ }, Symbol.toStringTag, { value: "Module" }));
325
+ const SETTINGS_ID = "site";
326
+ function readDoc() {
327
+ const doc2 = getDocStore().get("settings", SETTINGS_ID);
328
+ if (!doc2) return {};
329
+ const { id: _id, ...data } = structuredClone(doc2);
330
+ return data;
331
+ }
332
+ async function writeDoc(data, message) {
333
+ await getDocStore().write(
334
+ [{ op: "set", collection: "settings", doc: { ...data, id: SETTINGS_ID } }],
335
+ { message }
336
+ );
337
+ }
338
+ function merged() {
339
+ return { ...DEFAULT_SITE_SETTINGS, ...readDoc() };
340
+ }
341
+ function subscribeToSettings(onChange, onError) {
342
+ const store = getDocStore();
343
+ let active = true;
344
+ let loaded = false;
345
+ const emit = () => {
346
+ if (!active || !loaded) return;
347
+ try {
348
+ onChange(merged());
349
+ } catch (err) {
350
+ onError?.(err);
351
+ }
352
+ };
353
+ const unsub = store.subscribe((changed) => {
354
+ if (changed.has("settings")) emit();
355
+ });
356
+ store.ready().then(
357
+ () => {
358
+ loaded = true;
359
+ emit();
360
+ },
361
+ (err) => {
362
+ if (active) onError?.(err);
363
+ }
364
+ );
365
+ return () => {
366
+ active = false;
367
+ unsub();
368
+ };
369
+ }
370
+ async function getSettings$1() {
371
+ await getDocStore().ready();
372
+ return merged();
373
+ }
374
+ async function updateSettings$1(patch) {
375
+ await getDocStore().ready();
376
+ const next = { ...readDoc() };
377
+ for (const [key, value] of Object.entries(patch)) {
378
+ if (value === void 0) delete next[key];
379
+ else next[key] = value;
380
+ }
381
+ await writeDoc(next, "Update site settings");
382
+ }
383
+ async function updatePluginConfig(pluginId, config) {
384
+ await getDocStore().ready();
385
+ const current = readDoc();
386
+ const pluginConfigs = { ...current.pluginConfigs ?? {} };
387
+ pluginConfigs[pluginId] = config;
388
+ await writeDoc({ ...current, pluginConfigs }, `Update ${pluginId} plugin settings`);
389
+ }
390
+ async function updateThemeConfig(themeId, config) {
391
+ await getDocStore().ready();
392
+ const current = readDoc();
393
+ const themeConfigs = { ...current.themeConfigs ?? {} };
394
+ themeConfigs[themeId] = config;
395
+ await writeDoc({ ...current, themeConfigs }, `Update ${themeId} theme settings`);
396
+ }
397
+ const docstore = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
398
+ __proto__: null,
399
+ DEFAULT_SITE_SETTINGS,
400
+ getSettings: getSettings$1,
401
+ subscribeToSettings,
402
+ updatePluginConfig,
403
+ updateSettings: updateSettings$1,
404
+ updateThemeConfig
405
+ }, Symbol.toStringTag, { value: "Module" }));
406
+ let _impl = null;
407
+ function impl() {
408
+ if (!_impl) {
409
+ const kind = getBackendKind();
410
+ _impl = kind === "flexweg-sqlite" ? sqlite : isDocStoreBackend(kind) ? docstore : firebase;
411
+ }
412
+ return _impl;
413
+ }
414
+ function getSettings(...args) {
415
+ return impl().getSettings(...args);
416
+ }
417
+ function updateSettings(...args) {
418
+ return impl().updateSettings(...args);
419
+ }
420
+ function targetSiteUrl(target) {
421
+ const raw = typeof target.settings.siteUrl === "string" ? target.settings.siteUrl.trim().replace(/\/+$/, "") : "";
422
+ return /^https?:\/\//.test(raw) ? raw : "";
423
+ }
424
+ async function savePublishTarget(target, opts = {}) {
425
+ if (target.connectorId === "flexweg") {
426
+ const s = target.settings;
427
+ await setFlexwegConfig({
428
+ apiKey: (s.apiKey ?? "").trim(),
429
+ siteUrl: (s.siteUrl ?? "").trim(),
430
+ apiBaseUrl: (s.apiBaseUrl ?? "").trim() || DEFAULT_FLEXWEG_API_BASE_URL
431
+ });
432
+ await setStorageConfig({ connectorId: "flexweg", settings: {} });
433
+ } else {
434
+ const clean = {};
435
+ for (const [k, v] of Object.entries(target.settings)) {
436
+ if (v === void 0) continue;
437
+ clean[k] = typeof v === "string" ? v.trim() : v;
438
+ }
439
+ await setStorageConfig({ connectorId: target.connectorId, settings: clean });
440
+ }
441
+ await adoptSiteUrl(target, opts.siteUrl ?? "ifEmpty");
442
+ }
443
+ async function adoptSiteUrl(target, mode) {
444
+ const siteUrl = targetSiteUrl(target);
445
+ if (!siteUrl) return;
446
+ try {
447
+ const settings = await getSettings();
448
+ const current = (settings.baseUrl ?? "").replace(/\/+$/, "");
449
+ if (current === siteUrl) return;
450
+ if (!current || mode === "always") await updateSettings({ baseUrl: siteUrl });
451
+ } catch (err) {
452
+ console.warn("[storage] could not adopt the publish target URL as base URL:", err);
453
+ }
454
+ }
455
+ function collectToasts() {
456
+ const notices = [];
457
+ const stop = subscribeToToasts((t) => notices.push({ level: t.level, message: t.message }));
458
+ return { notices, stop };
459
+ }
460
+ async function settleHosting(target) {
461
+ const s = target.settings;
462
+ if (target.connectorId === "git-pages" && s.provider === "github") {
463
+ await githubPagesCheck(String(s.owner ?? ""), String(s.repo ?? ""))?.catch(() => {
464
+ });
465
+ }
466
+ }
467
+ function gitTransport(git, token) {
468
+ return createRemoteGitTransport(
469
+ git.provider === "github" ? { provider: "github", owner: git.owner ?? "", repo: git.repo ?? "", branch: git.branch, token, apiBaseUrl: git.apiBaseUrl } : { provider: "gitlab", project: git.project ?? "", branch: git.branch, token, apiBaseUrl: git.apiBaseUrl }
470
+ );
471
+ }
472
+ async function checkPlan(plan) {
473
+ const hosting = await testStorageConnection(plan.target.connectorId, plan.target.settings);
474
+ let data;
475
+ if (plan.runtime.backend === "markdown-git") {
476
+ const git = plan.runtime.git;
477
+ try {
478
+ const transport = gitTransport(git, plan.gitToken ?? "");
479
+ const perms = await transport.permissions();
480
+ if (!perms.canWrite) {
481
+ data = { ok: false, detail: `${perms.login}: read-only access to ${transport.label}`, existing: false };
482
+ } else {
483
+ const tree = await transport.listTree();
484
+ const storagePath = joinRepoPath(git.contentDir, ".cms/config/storage.json");
485
+ const entry = tree.entries.find((e) => e.path === storagePath);
486
+ let storedTarget = null;
487
+ if (entry) {
488
+ const raw = (await transport.readTexts([entry])).get(entry.path);
489
+ const stored = raw ? JSON.parse(raw) : null;
490
+ storedTarget = stored?.connectorId ? { connectorId: stored.connectorId, settings: stored.settings ?? {} } : null;
491
+ }
492
+ data = {
493
+ ok: true,
494
+ detail: `${perms.login} → ${transport.label}`,
495
+ existing: tree.entries.some((e) => e.path.startsWith(joinRepoPath(git.contentDir, ".cms/"))),
496
+ storedTarget
497
+ };
498
+ }
499
+ } catch (err) {
500
+ data = { ok: false, detail: err.message, existing: false };
501
+ }
502
+ } else {
503
+ const location = plan.dataLocation ?? plan.target;
504
+ try {
505
+ if (location !== plan.target) {
506
+ const res = await testStorageConnection(location.connectorId, location.settings, { ignore: ["siteUrl"] });
507
+ if (!res.ok) throw new Error(`data location: ${res.detail}`);
508
+ }
509
+ const connector = hostingConnector(location);
510
+ if (!connector.getBytes) throw new Error(`${location.connectorId} can't hold a SQLite file`);
511
+ const path = plan.runtime.backend === "sqlite-file" ? plan.runtime.sqlite.path : "";
512
+ const bytes = await connector.getBytes(path);
513
+ const where = `${path}${location !== plan.target ? ` (${plan.dataLocationType})` : ""}`;
514
+ if (!bytes) {
515
+ data = { ok: true, detail: `new ${plan.passphraseAccount ? "encrypted " : ""}database at ${where}`, existing: false };
516
+ } else if (isEncryptedDatabase(bytes)) {
517
+ if (!plan.passphrase) throw new Error(`an encrypted database already exists at ${where}: store its passphrase as ${plan.passphraseAccount ?? "passphrase:<alias>"} (and set data.encryption)`);
518
+ await openDatabaseBytes(bytes, plan.passphrase);
519
+ data = { ok: true, detail: `existing encrypted database at ${where}`, existing: true };
520
+ } else {
521
+ data = { ok: true, detail: `existing database at ${where}${plan.passphraseAccount ? " — encrypted from its next save" : ""}`, existing: true };
522
+ }
523
+ } catch (err) {
524
+ const message = err instanceof SqlitePassphraseError ? `wrong passphrase in ${plan.passphraseAccount}` : err.message;
525
+ data = { ok: false, detail: message, existing: false };
526
+ }
527
+ }
528
+ return { hosting, data };
529
+ }
530
+ function activate(runtime) {
531
+ window.__FLEXWEG_CONFIG__ = runtime;
532
+ resetRuntimeConfigCache();
533
+ resetDocStore();
534
+ }
535
+ async function runInstall(plan, onStep) {
536
+ const toasts = collectToasts();
537
+ try {
538
+ activate(plan.runtime);
539
+ if (plan.runtime.backend === "markdown-git") setGitToken(plan.runtime.git, plan.gitToken ?? null);
540
+ if (plan.runtime.backend === "sqlite-file") {
541
+ saveSqliteConnectorSecrets(plan.sqliteSecrets ?? {});
542
+ if (plan.passphraseAccount) {
543
+ if (!plan.passphrase) {
544
+ plan.passphrase = generatePassphrase();
545
+ setCredential(plan.passphraseAccount, { passphrase: plan.passphrase });
546
+ onStep(`Generated the database passphrase (stored as ${plan.passphraseAccount})`);
547
+ }
548
+ saveSqlitePassphrase(plan.passphrase);
549
+ }
550
+ loadSqlWasm();
551
+ await ensurePublishBranch(plan.target);
552
+ }
553
+ onStep("Opening the content store");
554
+ await getDocStore().ready();
555
+ onStep("Saving the publish target and site URL");
556
+ await savePublishTarget(plan.target, { siteUrl: "always" });
557
+ const ops = themeAssetOps();
558
+ let adminVersion = null;
559
+ if (plan.adminFolder) {
560
+ const admin = adminOps(plan.adminFolder, plan.runtime);
561
+ ops.push(...admin.ops);
562
+ adminVersion = admin.manifest.version;
563
+ }
564
+ onStep(`Uploading ${ops.length} files (theme assets${plan.adminFolder ? " + admin" : ""})`);
565
+ await applyOps(hostingConnector(plan.target), ops);
566
+ await settleHosting(plan.target);
567
+ return { adminVersion, files: ops.length, notices: toasts.notices };
568
+ } finally {
569
+ toasts.stop();
570
+ }
571
+ }
572
+ export {
573
+ checkPlan,
574
+ collectToasts,
575
+ runInstall,
576
+ settleHosting
577
+ };