@jant/core 0.3.42 → 0.3.43

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 (79) hide show
  1. package/bin/commands/import-site.js +1 -1
  2. package/bin/commands/search-reindex.js +175 -0
  3. package/bin/lib/hugo-markdown.js +102 -0
  4. package/bin/lib/site-pull-media.js +1 -4
  5. package/dist/app-Ctl0T0zO.js +5 -0
  6. package/dist/{app-Cu3lveYI.js → app-GbfwoeDJ.js} +630 -123
  7. package/dist/client/.vite/manifest.json +1 -1
  8. package/dist/client/_assets/{client-auth-BRFl5zQA.js → client-auth-CXILhW1b.js} +144 -144
  9. package/dist/{env-wCpMcNXs.js → env-CgaH9Mut.js} +1 -1
  10. package/dist/{github-api-CficQztC.js → github-api-BkRWnqMx.js} +1 -1
  11. package/dist/{github-app-F4qZ05xk.js → github-app-WeadXMb8.js} +1 -1
  12. package/dist/{github-sync-zohnA9qv.js → github-sync-7y_nTXx1.js} +41 -14
  13. package/dist/index.js +5 -5
  14. package/dist/node.js +5 -5
  15. package/dist/{url-FvvgARU9.js → url-umUptr5z.js} +30 -1
  16. package/package.json +1 -1
  17. package/src/__tests__/helpers/app.ts +15 -4
  18. package/src/app.tsx +8 -0
  19. package/src/client/tiptap/__tests__/insert-paragraph-around.test.ts +228 -0
  20. package/src/client/tiptap/extensions.ts +3 -0
  21. package/src/client/tiptap/insert-paragraph-around.ts +79 -0
  22. package/src/db/migrations/0018_yummy_franklin_richards.sql +6 -0
  23. package/src/db/migrations/meta/0018_snapshot.json +2225 -0
  24. package/src/db/migrations/meta/_journal.json +8 -1
  25. package/src/db/migrations/pg/0016_familiar_lionheart.sql +6 -0
  26. package/src/db/migrations/pg/meta/0016_snapshot.json +2840 -0
  27. package/src/db/migrations/pg/meta/_journal.json +8 -1
  28. package/src/db/pg/schema.ts +18 -0
  29. package/src/db/schema.ts +23 -0
  30. package/src/index.ts +1 -2
  31. package/src/lib/__tests__/hosted-signin.test.ts +30 -0
  32. package/src/lib/__tests__/navigation.test.ts +4 -20
  33. package/src/lib/__tests__/rate-limit-d1.test.ts +82 -0
  34. package/src/lib/__tests__/rate-limit-memory.test.ts +69 -0
  35. package/src/lib/__tests__/summary.test.ts +140 -0
  36. package/src/lib/__tests__/view.test.ts +66 -0
  37. package/src/lib/feed.ts +70 -34
  38. package/src/lib/hosted-signin.ts +9 -3
  39. package/src/lib/navigation.ts +11 -12
  40. package/src/lib/post-meta.ts +20 -2
  41. package/src/lib/rate-limit-d1.ts +99 -0
  42. package/src/lib/rate-limit-memory.ts +105 -0
  43. package/src/lib/rate-limit.ts +63 -0
  44. package/src/lib/render.tsx +9 -0
  45. package/src/lib/resolve-config.ts +9 -0
  46. package/src/lib/summary.ts +42 -7
  47. package/src/lib/url.ts +34 -0
  48. package/src/lib/view.ts +42 -8
  49. package/src/middleware/__tests__/auth.test.ts +44 -4
  50. package/src/middleware/__tests__/rate-limit.test.ts +113 -0
  51. package/src/middleware/__tests__/session.test.ts +85 -0
  52. package/src/middleware/auth.ts +62 -25
  53. package/src/middleware/rate-limit.ts +54 -0
  54. package/src/middleware/session.ts +36 -0
  55. package/src/routes/__tests__/compose.test.ts +1 -1
  56. package/src/routes/api/__tests__/search.test.ts +48 -0
  57. package/src/routes/api/__tests__/upload-multipart.test.ts +11 -4
  58. package/src/routes/api/internal/search-reindex.ts +40 -0
  59. package/src/routes/api/search.ts +13 -0
  60. package/src/routes/auth/dev.ts +1 -1
  61. package/src/routes/auth/signin.tsx +23 -5
  62. package/src/routes/dash/settings.tsx +3 -5
  63. package/src/routes/feed/__tests__/sitemap.test.ts +320 -4
  64. package/src/routes/feed/sitemap.ts +208 -33
  65. package/src/routes/pages/__tests__/page-canonical.test.ts +101 -0
  66. package/src/routes/pages/home.tsx +24 -15
  67. package/src/routes/pages/page.tsx +34 -0
  68. package/src/routes/pages/partials.tsx +4 -15
  69. package/src/runtime/cloudflare.ts +4 -0
  70. package/src/runtime/node.ts +16 -0
  71. package/src/services/__tests__/post.test.ts +205 -0
  72. package/src/services/__tests__/search.test.ts +44 -0
  73. package/src/services/export.ts +9 -2
  74. package/src/services/post.ts +200 -2
  75. package/src/types/app-context.ts +20 -0
  76. package/src/types/config.ts +8 -0
  77. package/src/types/props.ts +0 -7
  78. package/src/ui/layouts/BaseLayout.tsx +9 -0
  79. package/dist/app-DzCB4yOp.js +0 -5
@@ -20,7 +20,7 @@ import {
20
20
  } from "../lib/site-media-parser.js";
21
21
  import { openNodeDatabase } from "../lib/node-database.js";
22
22
  import { loadNodeRuntime } from "../lib/load-node-runtime.js";
23
- import { parseFrontMatter as parseFrontMatterShared } from "../../src/lib/hugo-markdown.ts";
23
+ import { parseFrontMatter as parseFrontMatterShared } from "../lib/hugo-markdown.js";
24
24
 
25
25
  /**
26
26
  * Parse front matter from a Markdown file.
@@ -0,0 +1,175 @@
1
+ import { parseArgs } from "node:util";
2
+ import { resolveSiteUrl } from "../lib/site-url.js";
3
+
4
+ const INTERNAL_ADMIN_TOKEN_ENV_VAR = "INTERNAL_ADMIN_TOKEN";
5
+ const DEFAULT_BATCH_SIZE = 50;
6
+ const MAX_BATCH_SIZE = 500;
7
+
8
+ function normalizeBaseUrl(value) {
9
+ const trimmed = value.trim();
10
+ return trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
11
+ }
12
+
13
+ function buildReindexUrl(siteUrl) {
14
+ return new URL(
15
+ "api/internal/search/reindex",
16
+ normalizeBaseUrl(siteUrl),
17
+ ).toString();
18
+ }
19
+
20
+ function parseBatchSize(rawLimit) {
21
+ if (rawLimit === undefined) {
22
+ return DEFAULT_BATCH_SIZE;
23
+ }
24
+
25
+ const limit = Number.parseInt(rawLimit, 10);
26
+ if (!Number.isInteger(limit) || limit < 1 || limit > MAX_BATCH_SIZE) {
27
+ throw new Error(
28
+ `Batch size must be an integer between 1 and ${MAX_BATCH_SIZE}.`,
29
+ );
30
+ }
31
+ return limit;
32
+ }
33
+
34
+ async function requestBatch(url, token, { limit, cursor }) {
35
+ const response = await fetch(url, {
36
+ method: "POST",
37
+ headers: {
38
+ Authorization: `Bearer ${token}`,
39
+ "Content-Type": "application/json",
40
+ },
41
+ body: JSON.stringify(cursor ? { limit, cursor } : { limit }),
42
+ });
43
+
44
+ if (!response.ok) {
45
+ const text = await response.text();
46
+ throw new Error(`HTTP ${response.status}: ${text || response.statusText}`);
47
+ }
48
+
49
+ return response.json();
50
+ }
51
+
52
+ function logBatchResult(batchIndex, result) {
53
+ console.log(
54
+ ` batch ${batchIndex}: processed=${result.processed} updated=${result.updated} skipped=${result.skipped}`,
55
+ );
56
+ }
57
+
58
+ export async function run(argv) {
59
+ const { values } = parseArgs({
60
+ args: argv,
61
+ options: {
62
+ config: { type: "string" },
63
+ env: { type: "string" },
64
+ help: { type: "boolean", short: "h" },
65
+ limit: { type: "string" },
66
+ once: { type: "boolean", default: false },
67
+ token: { type: "string" },
68
+ url: { type: "string" },
69
+ },
70
+ });
71
+
72
+ if (values.help) {
73
+ console.log("Usage: jant search-reindex [--url <url>] [options]");
74
+ console.log("");
75
+ console.log("Rebuild the search index for every non-deleted post by");
76
+ console.log("recomputing `post.body_text` from the stored TipTap body.");
77
+ console.log("Useful after changes to the text extraction logic — for");
78
+ console.log("example, indexing link URLs from inline markdown links.");
79
+ console.log("");
80
+ console.log(
81
+ "Idempotent: rows whose body_text is already up to date are skipped.",
82
+ );
83
+ console.log("");
84
+ console.log("Options:");
85
+ console.log(" --url Target site URL");
86
+ console.log(
87
+ ` --limit Batch size per request (default: ${DEFAULT_BATCH_SIZE}, max: ${MAX_BATCH_SIZE})`,
88
+ );
89
+ console.log(
90
+ " --once Run a single batch and exit (default: loop until drained)",
91
+ );
92
+ console.log(" --token Internal admin token");
93
+ console.log(
94
+ " --config Wrangler config file (default: wrangler.toml)",
95
+ );
96
+ console.log(" --env Wrangler environment name");
97
+ console.log("");
98
+ console.log("Authentication:");
99
+ console.log(
100
+ ` export ${INTERNAL_ADMIN_TOKEN_ENV_VAR}=your-internal-admin-token`,
101
+ );
102
+ console.log(" jant search-reindex --url https://your-site.example");
103
+ console.log("");
104
+ console.log(
105
+ "If --url is omitted, uses SITE_ORIGIN + SITE_PATH_PREFIX from env or wrangler.toml.",
106
+ );
107
+ process.exit(0);
108
+ }
109
+
110
+ const siteUrl = resolveSiteUrl({
111
+ url: values.url,
112
+ config: values.config,
113
+ env: values.env,
114
+ });
115
+ if (!siteUrl) {
116
+ console.error(
117
+ "Error: search-reindex requires --url or SITE_ORIGIN in the environment or wrangler.toml.",
118
+ );
119
+ process.exit(1);
120
+ }
121
+
122
+ const token =
123
+ values.token?.trim() || process.env[INTERNAL_ADMIN_TOKEN_ENV_VAR]?.trim();
124
+ if (!token) {
125
+ console.error(
126
+ `Error: search-reindex requires --token or ${INTERNAL_ADMIN_TOKEN_ENV_VAR}.`,
127
+ );
128
+ process.exit(1);
129
+ }
130
+
131
+ const batchSize = parseBatchSize(values.limit);
132
+ const reindexUrl = buildReindexUrl(siteUrl);
133
+
134
+ console.log(`Rebuilding search index for ${siteUrl}`);
135
+ console.log(
136
+ ` batch size: ${batchSize}${values.once ? " (single batch)" : ""}`,
137
+ );
138
+
139
+ let totalProcessed = 0;
140
+ let totalUpdated = 0;
141
+ let totalSkipped = 0;
142
+ let batchIndex = 0;
143
+ let cursor;
144
+
145
+ for (;;) {
146
+ batchIndex += 1;
147
+ const result = await requestBatch(reindexUrl, token, {
148
+ limit: batchSize,
149
+ cursor,
150
+ });
151
+ logBatchResult(batchIndex, result);
152
+
153
+ totalProcessed += result.processed;
154
+ totalUpdated += result.updated;
155
+ totalSkipped += result.skipped;
156
+
157
+ if (values.once) break;
158
+ if (result.done) break;
159
+
160
+ // Stop if the server didn't advance the cursor — shouldn't happen with
161
+ // the service-level contract, but avoids an infinite loop just in case.
162
+ if (!result.nextCursor || result.nextCursor === cursor) {
163
+ console.error(
164
+ "Server returned no next cursor but reported more work; aborting to avoid an infinite loop.",
165
+ );
166
+ break;
167
+ }
168
+ cursor = result.nextCursor;
169
+ }
170
+
171
+ console.log("");
172
+ console.log(
173
+ `Done. processed=${totalProcessed} updated=${totalUpdated} skipped=${totalSkipped} batches=${batchIndex}`,
174
+ );
175
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Plain-JS runtime mirror of `src/lib/hugo-markdown.ts` for the published
3
+ * CLI. The `bin/` scripts ship as `.js` and are executed by Node directly
4
+ * from `node_modules`, where Node 24 refuses to strip TypeScript types.
5
+ * Importing the `.ts` source from these scripts fails with
6
+ * ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING, so we keep the runtime here
7
+ * as real JavaScript.
8
+ *
9
+ * Keep this file in lock-step with `src/lib/hugo-markdown.ts` — same front
10
+ * matter shapes, same key order, same YAML formatting options.
11
+ */
12
+
13
+ const FRONT_MATTER_KEY_ORDER = [
14
+ // Identity & routing
15
+ "id",
16
+ "title",
17
+ "date",
18
+ "updated",
19
+ "slug",
20
+ "type",
21
+ "draft",
22
+ "aliases",
23
+ "build",
24
+
25
+ // Post payload
26
+ "format",
27
+ "status",
28
+ "visibility",
29
+ "summary_text",
30
+ "link_url",
31
+ "source_name",
32
+ "source_url",
33
+ "quote_text",
34
+ "rating",
35
+ "featured_at",
36
+ "pinned_at",
37
+
38
+ // Bookkeeping / attachments
39
+ "root_aliases",
40
+ "collections",
41
+ "media",
42
+ ];
43
+
44
+ /**
45
+ * Parse front matter from a Markdown file. Accepts YAML (`---...---`) or
46
+ * TOML (`+++...+++`) delimiters.
47
+ *
48
+ * @param {string} content
49
+ * @returns {Promise<{ frontMatter: Record<string, unknown>, body: string }>}
50
+ */
51
+ export async function parseFrontMatter(content) {
52
+ const yamlMatch = content.match(
53
+ /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/,
54
+ );
55
+ if (yamlMatch) {
56
+ const { parse } = await import("yaml");
57
+ const frontMatter = parse(yamlMatch[1] ?? "") ?? {};
58
+ return { frontMatter, body: yamlMatch[2] ?? "" };
59
+ }
60
+
61
+ const tomlMatch = content.match(
62
+ /^\+\+\+\r?\n([\s\S]*?)\r?\n\+\+\+\r?\n?([\s\S]*)$/,
63
+ );
64
+ if (tomlMatch) {
65
+ const { parse } = await import("smol-toml");
66
+ const frontMatter = parse(tomlMatch[1] ?? "");
67
+ return { frontMatter, body: tomlMatch[2] ?? "" };
68
+ }
69
+
70
+ return { frontMatter: {}, body: content };
71
+ }
72
+
73
+ /**
74
+ * Serialize a front-matter object as a YAML `---...---` block with the
75
+ * canonical key order.
76
+ *
77
+ * @param {Record<string, unknown>} frontMatter
78
+ * @returns {Promise<string>}
79
+ */
80
+ export async function formatFrontMatter(frontMatter) {
81
+ const { stringify } = await import("yaml");
82
+
83
+ const ordered = {};
84
+ for (const key of FRONT_MATTER_KEY_ORDER) {
85
+ if (key in frontMatter) {
86
+ const value = frontMatter[key];
87
+ if (value !== undefined) ordered[key] = value;
88
+ }
89
+ }
90
+ for (const key of Object.keys(frontMatter)) {
91
+ if (key in ordered) continue;
92
+ const value = frontMatter[key];
93
+ if (value !== undefined) ordered[key] = value;
94
+ }
95
+
96
+ const yaml = stringify(ordered, {
97
+ defaultKeyType: "PLAIN",
98
+ defaultStringType: "QUOTE_DOUBLE",
99
+ lineWidth: 0,
100
+ });
101
+ return `---\n${yaml.trimEnd()}\n---\n`;
102
+ }
@@ -19,10 +19,7 @@ import {
19
19
  isSkippableUrl,
20
20
  rewriteMediaReferences,
21
21
  } from "./site-media-parser.js";
22
- import {
23
- formatFrontMatter,
24
- parseFrontMatter,
25
- } from "../../src/lib/hugo-markdown.ts";
22
+ import { formatFrontMatter, parseFrontMatter } from "./hugo-markdown.js";
26
23
 
27
24
  export function getSitePathPrefix(baseUrl) {
28
25
  if (typeof baseUrl !== "string" || baseUrl.trim() === "") {
@@ -0,0 +1,5 @@
1
+ import "./url-umUptr5z.js";
2
+ import { t as createApp } from "./app-GbfwoeDJ.js";
3
+ import "./github-sync-7y_nTXx1.js";
4
+ import "./env-CgaH9Mut.js";
5
+ export { createApp };