@jant/core 0.3.46 → 0.3.47

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 (109) hide show
  1. package/bin/commands/db/execute-file.js +12 -4
  2. package/bin/commands/db/rehearse.js +2 -2
  3. package/bin/commands/export.js +12 -4
  4. package/bin/commands/import-site.js +60 -267
  5. package/bin/commands/migrate.js +36 -69
  6. package/bin/commands/reset-password.js +10 -4
  7. package/bin/commands/site/export.js +59 -248
  8. package/bin/commands/site/snapshot/export.js +58 -45
  9. package/bin/commands/site/snapshot/import.js +104 -52
  10. package/bin/lib/node-env.js +100 -0
  11. package/bin/lib/runtime-target.js +64 -0
  12. package/bin/lib/site-snapshot.js +185 -54
  13. package/bin/lib/sql-export.js +19 -2
  14. package/dist/{app-DB-P66E5.js → app-3REcR-3U.js} +331 -189
  15. package/dist/app-B67XOEyo.js +6 -0
  16. package/dist/client/.vite/manifest.json +2 -2
  17. package/dist/client/_assets/{client-auth-BLCUje4M.js → client-auth-Ce5WEAVS.js} +102 -49
  18. package/dist/client/_assets/client-s71Js1Cu.css +2 -0
  19. package/dist/{github-sync-CQ1x271f.js → export-ZBlfKSKm.js} +12 -439
  20. package/dist/github-sync-C593r22F.js +4 -0
  21. package/dist/github-sync-bL1hnx3Q.js +428 -0
  22. package/dist/index.js +3 -2
  23. package/dist/node.js +5 -4
  24. package/package.json +3 -2
  25. package/src/__tests__/helpers/export-fixtures.ts +0 -1
  26. package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -0
  27. package/src/client/components/__tests__/jant-settings-general.test.ts +70 -0
  28. package/src/client/components/jant-settings-general.ts +164 -22
  29. package/src/client/components/settings-types.ts +4 -6
  30. package/src/client-auth.ts +1 -1
  31. package/src/db/__tests__/demo-canonical-snapshot.test.ts +1 -1
  32. package/src/db/__tests__/migration-rehearsal.test.ts +2 -5
  33. package/src/db/backfills/0004_register_apple_touch_media_rows.sql +65 -0
  34. package/src/db/migrations/0021_thankful_phalanx.sql +16 -0
  35. package/src/db/migrations/meta/0021_snapshot.json +2121 -0
  36. package/src/db/migrations/meta/_journal.json +7 -0
  37. package/src/db/migrations/pg/0019_gray_natasha_romanoff.sql +20 -0
  38. package/src/db/migrations/pg/meta/0019_snapshot.json +2718 -0
  39. package/src/db/migrations/pg/meta/_journal.json +7 -0
  40. package/src/db/pg/schema.ts +21 -26
  41. package/src/db/rehearsal-fixtures/demo-current.json +1 -1
  42. package/src/db/schema.ts +16 -20
  43. package/src/i18n/__tests__/middleware.test.ts +43 -1
  44. package/src/i18n/coverage.generated.ts +17 -0
  45. package/src/i18n/i18n.ts +18 -2
  46. package/src/i18n/index.ts +3 -0
  47. package/src/i18n/locales/settings/en.po +16 -11
  48. package/src/i18n/locales/settings/en.ts +1 -1
  49. package/src/i18n/locales/settings/zh-Hans.po +17 -12
  50. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  51. package/src/i18n/locales/settings/zh-Hant.po +16 -11
  52. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  53. package/src/i18n/locales.ts +84 -2
  54. package/src/i18n/middleware.ts +25 -16
  55. package/src/i18n/supported-locales.ts +153 -0
  56. package/src/lib/__tests__/csp-builder.test.ts +19 -2
  57. package/src/lib/__tests__/feed.test.ts +242 -1
  58. package/src/lib/__tests__/post-meta.test.ts +0 -1
  59. package/src/lib/__tests__/view.test.ts +0 -1
  60. package/src/lib/csp-builder.ts +28 -10
  61. package/src/lib/feed.ts +153 -3
  62. package/src/middleware/__tests__/secure-headers.test.ts +89 -0
  63. package/src/middleware/auth.ts +1 -1
  64. package/src/middleware/secure-headers.ts +47 -1
  65. package/src/node/__tests__/cli-runtime-target.test.ts +110 -2
  66. package/src/node/__tests__/cli-site-snapshot.test.ts +308 -13
  67. package/src/node/__tests__/cli-site-token-env.test.ts +2 -7
  68. package/src/node/__tests__/cli-snapshot-meta.test.ts +85 -0
  69. package/src/node/__tests__/cli-sql-export.test.ts +49 -0
  70. package/src/node/index.ts +1 -0
  71. package/src/preset.css +8 -2
  72. package/src/routes/api/__tests__/settings.test.ts +3 -2
  73. package/src/routes/api/github-sync.tsx +1 -1
  74. package/src/routes/api/settings.ts +4 -1
  75. package/src/routes/auth/signin.tsx +6 -0
  76. package/src/routes/pages/archive.tsx +4 -2
  77. package/src/services/__tests__/post.test.ts +19 -19
  78. package/src/services/__tests__/search.test.ts +0 -1
  79. package/src/services/__tests__/settings.test.ts +22 -3
  80. package/src/services/bootstrap.ts +7 -3
  81. package/src/services/collection.ts +3 -3
  82. package/src/services/export.ts +0 -3
  83. package/src/services/navigation.ts +0 -2
  84. package/src/services/path.ts +1 -38
  85. package/src/services/post.ts +32 -66
  86. package/src/services/search.ts +0 -6
  87. package/src/services/settings.ts +47 -6
  88. package/src/services/site-admin.ts +6 -1
  89. package/src/styles/ui.css +12 -23
  90. package/src/types/entities.ts +0 -1
  91. package/src/ui/color-themes.ts +1 -1
  92. package/src/ui/dash/settings/GeneralContent.tsx +17 -19
  93. package/src/ui/dash/settings/SettingsRootContent.tsx +17 -28
  94. package/src/ui/feed/NoteCard.tsx +1 -11
  95. package/src/ui/feed/__tests__/timeline-cards.test.ts +1 -1
  96. package/src/ui/pages/PostPage.tsx +2 -0
  97. package/bin/commands/collections.js +0 -268
  98. package/bin/commands/media.js +0 -302
  99. package/bin/commands/posts.js +0 -262
  100. package/bin/commands/search.js +0 -53
  101. package/bin/commands/settings.js +0 -93
  102. package/bin/lib/http-api.js +0 -223
  103. package/bin/lib/media-upload.js +0 -206
  104. package/dist/app-CM7sb3xO.js +0 -5
  105. package/dist/client/_assets/client-DDs6NzB3.css +0 -2
  106. package/src/__tests__/bin/content-cli.test.ts +0 -179
  107. package/src/__tests__/bin/media-cli.test.ts +0 -192
  108. /package/dist/{github-api-BkRWnqMx.js → github-api-Bh0PH3zr.js} +0 -0
  109. /package/dist/{github-app-WeadXMb8.js → github-app-D0GvNnqp.js} +0 -0
@@ -1,6 +1,4 @@
1
1
  import { d as sanitizeUrl, g as toPublicPath, v as __exportAll } from "./url-umUptr5z.js";
2
- import { r as getInstallationToken } from "./github-app-WeadXMb8.js";
3
- import { r as parseRepoSlug, t as createGitHubClient } from "./github-api-BkRWnqMx.js";
4
2
  import { strToU8, zipSync } from "fflate";
5
3
  import { Extension, Node } from "@tiptap/core";
6
4
  import { MarkdownManager } from "@tiptap/markdown";
@@ -3125,28 +3123,6 @@ function serializeMarkdownDocument(doc) {
3125
3123
  return plain.slice(0, maxLength).trim() + "...";
3126
3124
  }
3127
3125
  //#endregion
3128
- //#region src/lib/markdown-to-tiptap.ts
3129
- /**
3130
- * Markdown → TipTap JSON Conversion
3131
- *
3132
- * Converts Markdown strings to TipTap JSON documents using the official
3133
- * Tiptap MarkdownManager and the same extension schema used elsewhere in Jant.
3134
- */
3135
- /**
3136
- * Converts a Markdown string to a TipTap JSON document string.
3137
- *
3138
- * @param markdown - Markdown source text
3139
- * @returns Stringified TipTap JSON document
3140
- *
3141
- * @example
3142
- * ```ts
3143
- * const json = markdownToTiptapJson("Hello **world**");
3144
- * // '{"type":"doc","content":[{"type":"paragraph","content":[...]}]}'
3145
- * ```
3146
- */ function markdownToTiptapJson(markdown) {
3147
- return JSON.stringify(parseMarkdownDocument(markdown));
3148
- }
3149
- //#endregion
3150
3126
  //#region src/lib/tiptap-to-markdown.ts
3151
3127
  /**
3152
3128
  * Tiptap JSON → Markdown Converter
@@ -3388,7 +3364,17 @@ var feed_post_content_default = "{{- /*\n Feed entry content builder — mirror
3388
3364
  *
3389
3365
  * Real Hugo templates and CSS are scaffolded as placeholders here and
3390
3366
  * filled in by Commit 5.
3391
- */ function buildDefaultAppleTouchAsset() {
3367
+ */ var export_exports = /* @__PURE__ */ __exportAll({
3368
+ buildExportedCollectionDirectoryItems: () => buildExportedCollectionDirectoryItems,
3369
+ buildExportedCollectionMetrics: () => buildExportedCollectionMetrics,
3370
+ buildSiteIconAssets: () => buildSiteIconAssets,
3371
+ createExportService: () => createExportService,
3372
+ getArchiveSummaryText: () => getArchiveSummaryText,
3373
+ getMediaUrl: () => getMediaUrl,
3374
+ getPublicUrlForProvider: () => getPublicUrlForProvider,
3375
+ readStorageObjectBytes: () => readStorageObjectBytes
3376
+ });
3377
+ function buildDefaultAppleTouchAsset() {
3392
3378
  return {
3393
3379
  appleTouchBytes: getDefaultJantAppleTouchIconBytes(),
3394
3380
  appleTouchMode: "default"
@@ -4023,7 +4009,6 @@ function buildExportedCollectionMetrics(collections, posts, collectionsByPost) {
4023
4009
  recentActivityAt: collection.updatedAt
4024
4010
  });
4025
4011
  for (const post of posts) {
4026
- if (post.deletedAt !== null) continue;
4027
4012
  if (post.status === "draft" || post.visibility === "private") continue;
4028
4013
  if (post.replyToId !== null) continue;
4029
4014
  const activityAt = post.lastActivityAt ?? post.publishedAt ?? post.updatedAt ?? post.createdAt;
@@ -4311,416 +4296,4 @@ Safe to re-run; files already on disk are reused. Anything that fails to downloa
4311
4296
  `;
4312
4297
  }
4313
4298
  //#endregion
4314
- //#region src/services/github-sync.ts
4315
- /**
4316
- * GitHub Sync Service
4317
- *
4318
- * Handles bidirectional content synchronization between Jant and a GitHub repo.
4319
- * Posts are serialized as Hugo-format Markdown bundles (reusing the export
4320
- * format): each post is a branch bundle at `content/{slug}/_index.md` with
4321
- * reply leaves at `content/{root-slug}/{reply-slug}/index.md`.
4322
- *
4323
- * Push (Jant → GitHub):
4324
- * - Always full sync: regenerate Jant-managed files in a single atomic commit
4325
- * - Uses base_tree so untracked files in the repo (READMEs, CI, etc.) are preserved
4326
- * - Debounced: multiple rapid changes collapse into one sync
4327
- *
4328
- * Pull (GitHub → Jant):
4329
- * - Webhook-triggered: match modified files to existing posts by slug and update
4330
- * - Unknown files are skipped; new posts cannot be created from GitHub
4331
- * - File deletions are intentionally ignored to avoid catastrophic data loss
4332
- * (e.g. user deletes the repo → site wiped). Deletes must go through Jant's UI.
4333
- *
4334
- * Anti-loop: all commits from Jant include `[jant-sync]` in the message.
4335
- * Incoming webhooks with this marker are skipped.
4336
- */ var github_sync_exports = /* @__PURE__ */ __exportAll({
4337
- JANT_MANAGED_GLOBS: () => JANT_MANAGED_GLOBS,
4338
- JANT_SYNC_MARKER_PATH: () => JANT_SYNC_MARKER_PATH,
4339
- JANT_SYNC_MARKER_SCHEMA_VERSION: () => 3,
4340
- SYNC_COMMIT_MARKER: () => SYNC_COMMIT_MARKER,
4341
- classifyRepoForSync: () => classifyRepoForSync,
4342
- computeManagedDeletions: () => computeManagedDeletions,
4343
- createGitHubSyncService: () => createGitHubSyncService,
4344
- isManagedPath: () => isManagedPath,
4345
- pathMatchesManagedGlob: () => pathMatchesManagedGlob
4346
- });
4347
- /** Marker included in commit messages to prevent webhook loops. */ var SYNC_COMMIT_MARKER = "[jant-sync]";
4348
- /**
4349
- * Path of the ownership marker file written at the repo root.
4350
- *
4351
- * Presence of this file (with a matching `site_id`) identifies the repo
4352
- * as actively managed by a Jant site. Used to distinguish three states
4353
- * during connect: empty repo, Jant-owned repo, foreign repo with existing
4354
- * content. Also serves as the initialization file for empty repos so we
4355
- * don't need a separate throwaway placeholder.
4356
- */ var JANT_SYNC_MARKER_PATH = ".jant-sync";
4357
- /**
4358
- * Hard list of paths Jant fully owns and always overwrites on push.
4359
- * Anything outside this set is user territory and preserved via base_tree.
4360
- * Files inside this set that Jant no longer generates are deleted on the
4361
- * next push (see `computeManagedDeletions`).
4362
- *
4363
- * - `content/**` — posts, collections, sections (rendered by Hugo)
4364
- * - `data/jant.toml` — nav, branding, collections directory (the rest of
4365
- * `data/` is user territory so Hugo's `data/menu.toml` convention, etc.,
4366
- * can be used freely)
4367
- * - `themes/jant/**` — the packaged Jant theme (layouts + static assets)
4368
- * - `hugo.toml` — site config, including `theme = "jant"`
4369
- * - `.gitignore`, `README.md` — scaffolded once, then kept in sync
4370
- * - `.jant-sync` — ownership marker; written by this service, not by export
4371
- *
4372
- * The list is also stored in the marker itself (`managed_globs`) so future
4373
- * schema bumps can diff the old and new sets to decide what needs cleanup.
4374
- */ var JANT_MANAGED_GLOBS = [
4375
- "content/**",
4376
- "data/jant.toml",
4377
- "themes/jant/**",
4378
- "hugo.toml",
4379
- ".gitignore",
4380
- "README.md",
4381
- ".jant-sync"
4382
- ];
4383
- /**
4384
- * Match a repo-relative path against a single glob from
4385
- * `JANT_MANAGED_GLOBS`. Only two forms are supported because those are
4386
- * the only two shapes the constant uses:
4387
- *
4388
- * - Exact path (`"hugo.toml"`, `"data/jant.toml"`)
4389
- * - Directory prefix + `/**` (`"content/**"`, `"themes/jant/**"`)
4390
- *
4391
- * Exported for testing.
4392
- */ function pathMatchesManagedGlob(path, glob) {
4393
- if (glob.endsWith("/**")) {
4394
- const prefix = glob.slice(0, -3);
4395
- return path === prefix || path.startsWith(prefix + "/");
4396
- }
4397
- return path === glob;
4398
- }
4399
- /**
4400
- * True when `path` falls inside one of Jant's managed globs.
4401
- *
4402
- * Exported for testing.
4403
- */ function isManagedPath(path) {
4404
- return JANT_MANAGED_GLOBS.some((g) => pathMatchesManagedGlob(path, g));
4405
- }
4406
- /**
4407
- * Compute the tree items that should null-out files on the remote HEAD
4408
- * which Jant claims ownership of (matches `JANT_MANAGED_GLOBS`) but is
4409
- * not writing in the current push (not in `writtenPaths`).
4410
- *
4411
- * Returns a list of `{ sha: null }` tree entries suitable for appending
4412
- * to the payload of `createTree`. Exported for testing.
4413
- */ function computeManagedDeletions(headTreeItems, writtenPaths) {
4414
- const items = [];
4415
- for (const item of headTreeItems) {
4416
- if (item.type !== "blob") continue;
4417
- if (!isManagedPath(item.path)) continue;
4418
- if (writtenPaths.has(item.path)) continue;
4419
- items.push({
4420
- path: item.path,
4421
- mode: "100644",
4422
- type: "blob",
4423
- sha: null
4424
- });
4425
- }
4426
- return items;
4427
- }
4428
- function parseMarker(text) {
4429
- try {
4430
- const parsed = JSON.parse(text);
4431
- if (typeof parsed.site_id === "string" && typeof parsed.site_host === "string" && typeof parsed.created_at === "number" && typeof parsed.schema_version === "number") return parsed;
4432
- } catch {}
4433
- return null;
4434
- }
4435
- function decodeMarkerContent(file) {
4436
- if (file.encoding === "base64") try {
4437
- const cleaned = file.content.replace(/\s+/g, "");
4438
- const binary = atob(cleaned);
4439
- const bytes = Uint8Array.from(binary, (ch) => ch.charCodeAt(0));
4440
- return new TextDecoder("utf-8").decode(bytes);
4441
- } catch {
4442
- return "";
4443
- }
4444
- return file.content;
4445
- }
4446
- function formatMarker(marker) {
4447
- return `${JSON.stringify(marker, null, 2)}\n`;
4448
- }
4449
- function safeHost(siteUrl) {
4450
- try {
4451
- return new URL(siteUrl).host;
4452
- } catch {
4453
- return "";
4454
- }
4455
- }
4456
- /**
4457
- * Classify a repository to decide how to proceed with a sync connection.
4458
- *
4459
- * - `empty`: repo has no commits on the default branch (or repo is brand new).
4460
- * - `owned`: `.jant-sync` marker present with matching `site_id`.
4461
- * - `owned-by-other-site`: `.jant-sync` present but `site_id` differs —
4462
- * another Jant site already backs up here, blocking the connect.
4463
- * - `foreign`: non-empty repo without a marker — requires explicit
4464
- * user confirmation before connect.
4465
- */ async function classifyRepoForSync(client, owner, repo, siteId) {
4466
- const defaultBranch = (await client.getRepo(owner, repo)).default_branch;
4467
- try {
4468
- await client.getRef(owner, repo, `heads/${defaultBranch}`);
4469
- } catch {
4470
- return { kind: "empty" };
4471
- }
4472
- const markerFile = await client.getFileContent(owner, repo, JANT_SYNC_MARKER_PATH);
4473
- if (!markerFile) return {
4474
- kind: "foreign",
4475
- defaultBranch
4476
- };
4477
- const marker = parseMarker(decodeMarkerContent(markerFile));
4478
- if (!marker) return {
4479
- kind: "foreign",
4480
- defaultBranch
4481
- };
4482
- if (marker.site_id === siteId) return {
4483
- kind: "owned",
4484
- marker
4485
- };
4486
- return {
4487
- kind: "owned-by-other-site",
4488
- marker
4489
- };
4490
- }
4491
- function createGitHubSyncService(services, siteId, siteConfig, deps = {}) {
4492
- /**
4493
- * Build the ownership marker for this push. Preserves `created_at`
4494
- * from an existing marker (when readable) so the timestamp reflects
4495
- * when this repo was first bound to this site, not the latest push.
4496
- */ function buildMarker(existingContent, now) {
4497
- const existing = existingContent ? parseMarker(existingContent) : null;
4498
- const preservedCreatedAt = existing && existing.site_id === siteId ? existing.created_at : now;
4499
- return {
4500
- schema_version: 3,
4501
- site_id: siteId,
4502
- site_host: safeHost(siteConfig.siteUrl),
4503
- created_at: preservedCreatedAt,
4504
- managed_globs: [...JANT_MANAGED_GLOBS]
4505
- };
4506
- }
4507
- async function loadConfig() {
4508
- const [repo, enabled, authModeRaw] = await Promise.all([
4509
- services.settings.get("GITHUB_SYNC_REPO"),
4510
- services.settings.get("GITHUB_SYNC_ENABLED"),
4511
- services.settings.get("GITHUB_SYNC_AUTH_MODE")
4512
- ]);
4513
- if (!repo || enabled !== "true") return null;
4514
- const authMode = authModeRaw === "app" ? "app" : "pat";
4515
- const [token, installationId, webhookId, webhookSecret, lastPushSha] = await Promise.all([
4516
- services.settings.get("GITHUB_SYNC_TOKEN"),
4517
- services.settings.get("GITHUB_SYNC_APP_INSTALLATION_ID"),
4518
- services.settings.get("GITHUB_SYNC_WEBHOOK_ID"),
4519
- services.settings.get("GITHUB_SYNC_WEBHOOK_SECRET"),
4520
- services.settings.get("GITHUB_SYNC_LAST_PUSH_SHA")
4521
- ]);
4522
- if (authMode === "pat" && !token) return null;
4523
- if (authMode === "app" && !installationId) return null;
4524
- return {
4525
- authMode,
4526
- token: token ?? void 0,
4527
- installationId: installationId ?? void 0,
4528
- repo,
4529
- enabled: true,
4530
- webhookId: webhookId ?? void 0,
4531
- webhookSecret: webhookSecret ?? void 0,
4532
- lastPushSha: lastPushSha ?? void 0
4533
- };
4534
- }
4535
- function createClient(config) {
4536
- const parsed = parseRepoSlug(config.repo);
4537
- if (!parsed) throw new Error(`Invalid repo slug: ${config.repo}`);
4538
- let client;
4539
- if (config.authMode === "app") {
4540
- if (!deps.githubApp) throw new Error("GitHub App is not configured on this deployment. Set GITHUB_APP_ID, GITHUB_APP_PRIVATE_KEY, and GITHUB_APP_SLUG to use App auth.");
4541
- if (!config.installationId) throw new Error("GitHub App installation id is missing.");
4542
- const app = deps.githubApp;
4543
- const installationId = config.installationId;
4544
- client = createGitHubClient(() => getInstallationToken(app, installationId));
4545
- } else {
4546
- if (!config.token) throw new Error("GitHub sync PAT is missing.");
4547
- client = createGitHubClient(config.token);
4548
- }
4549
- return {
4550
- client,
4551
- owner: parsed.owner,
4552
- repo: parsed.repo
4553
- };
4554
- }
4555
- /**
4556
- * Get the HEAD SHA, initializing an empty repo if needed.
4557
- * GitHub's Git Trees API requires at least one commit to exist.
4558
- */ async function getOrInitHead(client, owner, repo, defaultBranch, seedMarker) {
4559
- try {
4560
- return { sha: (await client.getRef(owner, repo, `heads/${defaultBranch}`)).sha };
4561
- } catch {
4562
- await client.createOrUpdateFile(owner, repo, JANT_SYNC_MARKER_PATH, {
4563
- content: formatMarker(seedMarker),
4564
- message: `Initialize Jant sync ${SYNC_COMMIT_MARKER}`
4565
- });
4566
- return { sha: (await client.getRef(owner, repo, `heads/${defaultBranch}`)).sha };
4567
- }
4568
- }
4569
- return {
4570
- getConfig: loadConfig,
4571
- async pushFullSync() {
4572
- const config = await loadConfig();
4573
- if (!config) throw new Error("GitHub Sync is not configured");
4574
- const { client, owner, repo } = createClient(config);
4575
- const exportFiles = await createExportService(services, siteConfig, deps).generateHugoFiles();
4576
- const defaultBranch = (await client.getRepo(owner, repo)).default_branch;
4577
- const now = Math.floor(Date.now() / 1e3);
4578
- const existingMarkerBeforeInit = await client.getFileContent(owner, repo, JANT_SYNC_MARKER_PATH).catch(() => null);
4579
- const marker = buildMarker(existingMarkerBeforeInit ? decodeMarkerContent(existingMarkerBeforeInit) : null, now);
4580
- const { sha: headSha } = await getOrInitHead(client, owner, repo, defaultBranch, marker);
4581
- const treeItems = [{
4582
- path: JANT_SYNC_MARKER_PATH,
4583
- mode: "100644",
4584
- type: "blob",
4585
- content: formatMarker(marker)
4586
- }];
4587
- for (const file of exportFiles) if (typeof file.content === "string") treeItems.push({
4588
- path: file.path,
4589
- mode: "100644",
4590
- type: "blob",
4591
- content: file.content
4592
- });
4593
- else {
4594
- const blob = await client.createBlob(owner, repo, uint8ArrayToBase64(file.content), "base64");
4595
- treeItems.push({
4596
- path: file.path,
4597
- mode: "100644",
4598
- type: "blob",
4599
- sha: blob.sha
4600
- });
4601
- }
4602
- const headCommit = await client.getCommit(owner, repo, headSha);
4603
- const headTree = await client.getTree(owner, repo, headCommit.treeSha, { recursive: true });
4604
- if (headTree.truncated) throw new Error("GitHub tree exceeds API limits (>100k entries or >7MB); incremental deletion cannot run safely against this repo.");
4605
- const writtenPaths = new Set(treeItems.map((item) => item.path));
4606
- treeItems.push(...computeManagedDeletions(headTree.tree, writtenPaths));
4607
- const tree = await client.createTree(owner, repo, treeItems, headCommit.treeSha);
4608
- const commit = await client.createCommit(owner, repo, {
4609
- message: `Sync site ${SYNC_COMMIT_MARKER}`,
4610
- tree: tree.sha,
4611
- parents: [headSha]
4612
- });
4613
- await client.updateRef(owner, repo, `heads/${defaultBranch}`, commit.sha);
4614
- await services.settings.set("GITHUB_SYNC_LAST_PUSH_SHA", commit.sha);
4615
- await services.settings.set("GITHUB_SYNC_LAST_PUSH_AT", String(Math.floor(Date.now() / 1e3)));
4616
- return { commitSha: commit.sha };
4617
- },
4618
- async handleWebhookPush(payload) {
4619
- if (payload.commits.some((c) => c.message.includes("[jant-sync]")) && payload.commits.length === 1) return;
4620
- const modified = /* @__PURE__ */ new Set();
4621
- for (const commit of payload.commits) {
4622
- if (commit.message.includes("[jant-sync]")) continue;
4623
- for (const file of [...commit.modified, ...commit.added]) if (classifyBundlePath(file)) modified.add(file);
4624
- }
4625
- if (modified.size === 0) return;
4626
- const config = await loadConfig();
4627
- if (!config) return;
4628
- const { client, owner, repo } = createClient(config);
4629
- for (const filePath of modified) {
4630
- const classification = classifyBundlePath(filePath);
4631
- if (!classification) continue;
4632
- const fileContent = await client.getFileContent(owner, repo, filePath, payload.after);
4633
- if (!fileContent) continue;
4634
- const { frontMatter, body } = await parseFrontMatter(decodeBase64Content(fileContent.content));
4635
- const slug = typeof frontMatter.slug === "string" && frontMatter.slug.trim() ? frontMatter.slug.trim() : classification.slug;
4636
- if (!slug) continue;
4637
- const pathRecord = await services.paths.getByPath(slug);
4638
- if (!pathRecord?.postId) continue;
4639
- const existingPost = await services.posts.getById(pathRecord.postId);
4640
- if (!existingPost) continue;
4641
- if (classification.kind === "reply") {
4642
- const rootPath = await services.paths.getByPath(classification.rootSlug);
4643
- if (!rootPath?.postId) continue;
4644
- if (existingPost.threadId !== rootPath.postId) continue;
4645
- }
4646
- const trimmedBody = body.trim();
4647
- const tiptapBody = trimmedBody ? markdownToTiptapJson(trimmedBody) : null;
4648
- const updateData = {};
4649
- if (tiptapBody !== null) updateData.body = tiptapBody;
4650
- if (frontMatter.title !== void 0) updateData.title = frontMatter.title;
4651
- if (frontMatter.link_url !== void 0) updateData.url = frontMatter.link_url;
4652
- if (frontMatter.source_name !== void 0) updateData.sourceName = frontMatter.source_name;
4653
- if (frontMatter.source_url !== void 0) updateData.sourceUrl = frontMatter.source_url;
4654
- if (frontMatter.quote_text !== void 0) updateData.quoteText = frontMatter.quote_text;
4655
- if (frontMatter.rating !== void 0) updateData.rating = frontMatter.rating;
4656
- if (Object.keys(updateData).length > 0) await services.posts.update(existingPost.id, updateData);
4657
- }
4658
- },
4659
- async setupWebhook(callbackUrl) {
4660
- const config = await loadConfig();
4661
- if (!config) throw new Error("GitHub Sync is not configured");
4662
- const { client, owner, repo } = createClient(config);
4663
- const secretBytes = new Uint8Array(32);
4664
- crypto.getRandomValues(secretBytes);
4665
- const secret = Array.from(secretBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
4666
- const webhook = await client.createWebhook(owner, repo, {
4667
- url: callbackUrl,
4668
- secret,
4669
- events: ["push"]
4670
- });
4671
- await services.settings.set("GITHUB_SYNC_WEBHOOK_SECRET", secret);
4672
- await services.settings.set("GITHUB_SYNC_WEBHOOK_ID", String(webhook.id));
4673
- return { webhookId: webhook.id };
4674
- },
4675
- async teardownWebhook() {
4676
- const config = await loadConfig();
4677
- if (!config) return;
4678
- if (config.webhookId) try {
4679
- const { client, owner, repo } = createClient(config);
4680
- await client.deleteWebhook(owner, repo, parseInt(config.webhookId));
4681
- } catch {}
4682
- await services.settings.set("GITHUB_SYNC_ENABLED", "false");
4683
- await services.settings.set("GITHUB_SYNC_TOKEN", "");
4684
- await services.settings.set("GITHUB_SYNC_REPO", "");
4685
- await services.settings.set("GITHUB_SYNC_WEBHOOK_SECRET", "");
4686
- await services.settings.set("GITHUB_SYNC_WEBHOOK_ID", "");
4687
- await services.settings.set("GITHUB_SYNC_LAST_PUSH_SHA", "");
4688
- await services.settings.set("GITHUB_SYNC_AUTH_MODE", "pat");
4689
- await services.settings.set("GITHUB_SYNC_APP_INSTALLATION_ID", "");
4690
- }
4691
- };
4692
- }
4693
- function decodeBase64Content(content) {
4694
- const cleaned = content.replace(/\n/g, "");
4695
- return decodeURIComponent(escape(atob(cleaned)));
4696
- }
4697
- function classifyBundlePath(path) {
4698
- if (!path.startsWith("content/")) return null;
4699
- const segments = path.slice(8).split("/");
4700
- if (segments.length === 2 && segments[1] === "_index.md") {
4701
- const slug = segments[0];
4702
- if (!slug) return null;
4703
- return {
4704
- kind: "root",
4705
- slug
4706
- };
4707
- }
4708
- if (segments.length === 3 && segments[2] === "index.md") {
4709
- const rootSlug = segments[0];
4710
- const slug = segments[1];
4711
- if (!rootSlug || !slug) return null;
4712
- return {
4713
- kind: "reply",
4714
- rootSlug,
4715
- slug
4716
- };
4717
- }
4718
- return null;
4719
- }
4720
- function uint8ArrayToBase64(bytes) {
4721
- let binary = "";
4722
- for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
4723
- return btoa(binary);
4724
- }
4725
- //#endregion
4726
- export { JANT_BRAND_PACK_FILENAME as A, getJantLogoFills as B, formatTime as C, toISOString as D, time_exports as E, getJantBrandPackHref as F, arrayBufferToBase64 as G, getJantPositiveLogoPngHref as H, getJantBundledAsset as I, base64ToUint8Array as K, getJantIconFilename as L, JANT_REPO_URL as M, getDefaultJantAppleTouchIconBytes as N, HOME_BRANDING_LINK_LABEL as O, getDefaultJantFaviconIcoBytes as P, getJantIconHref as R, formatRelativeTime as S, now as T, JANT_LOGO_PATH_DATA as U, getJantLogoHref as V, JANT_LOGO_VIEW_BOX as W, getImageUrl as _, tiptapJsonToMarkdown as a, formatDate as b, render as c, extractSummary as d, extractSummaryHtml as f, escapeHtml as g, trimTiptapBody as h, createExportService as i, JANT_POSITIVE_LOGO_PNG_FILENAME as j, HOME_BRANDING_PREFIX as k, toPlainText as l, renderTiptapJson as m, createGitHubSyncService as n, markdownToTiptapJson as o, renderTiptapDocument as p, github_sync_exports as r, markdown_exports as s, SYNC_COMMIT_MARKER as t, extractBodyText as u, getMediaUrl as v, formatYearMonth as w, formatRelativeAge as x, getPublicUrlForProvider as y, getJantLogoFilename as z };
4299
+ export { JANT_POSITIVE_LOGO_PNG_FILENAME as A, getJantLogoHref as B, formatYearMonth as C, HOME_BRANDING_LINK_LABEL as D, toISOString as E, getJantBundledAsset as F, base64ToUint8Array as G, JANT_LOGO_PATH_DATA as H, getJantIconFilename as I, getJantIconHref as L, getDefaultJantAppleTouchIconBytes as M, getDefaultJantFaviconIcoBytes as N, HOME_BRANDING_PREFIX as O, getJantBrandPackHref as P, getJantLogoFilename as R, formatTime as S, time_exports as T, JANT_LOGO_VIEW_BOX as U, getJantPositiveLogoPngHref as V, arrayBufferToBase64 as W, getMediaUrl as _, markdown_exports as a, formatRelativeAge as b, parseMarkdownDocument as c, extractSummaryHtml as d, renderTiptapDocument as f, getImageUrl as g, escapeHtml as h, tiptapJsonToMarkdown as i, JANT_REPO_URL as j, JANT_BRAND_PACK_FILENAME as k, extractBodyText as l, trimTiptapBody as m, export_exports as n, render as o, renderTiptapJson as p, parseFrontMatter as r, toPlainText as s, createExportService as t, extractSummary as u, getPublicUrlForProvider as v, now as w, formatRelativeTime as x, formatDate as y, getJantLogoFills as z };
@@ -0,0 +1,4 @@
1
+ import "./url-umUptr5z.js";
2
+ import "./export-ZBlfKSKm.js";
3
+ import { i as classifyRepoForSync, o as createGitHubSyncService } from "./github-sync-bL1hnx3Q.js";
4
+ export { classifyRepoForSync, createGitHubSyncService };