@jant/core 0.3.29 → 0.3.31

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.
@@ -0,0 +1,112 @@
1
+ import { execSync } from "node:child_process";
2
+ import { writeFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { parseArgs } from "node:util";
5
+
6
+ function sqlValue(v) {
7
+ if (v === null) return "NULL";
8
+ if (typeof v === "number") return String(v);
9
+ return "'" + String(v).replaceAll("'", "''") + "'";
10
+ }
11
+
12
+ function query(sql, flag) {
13
+ let stdout;
14
+ try {
15
+ stdout = execSync(
16
+ `npx wrangler d1 execute DB ${flag} --command "${sql}" --json`,
17
+ { encoding: "utf-8" },
18
+ );
19
+ } catch (err) {
20
+ const output = err.stdout || err.stderr || "";
21
+ try {
22
+ const errJson = JSON.parse(output.trim());
23
+ if (errJson.error?.text) {
24
+ console.error(`Wrangler error: ${errJson.error.text}`);
25
+ process.exit(1);
26
+ }
27
+ } catch {
28
+ // Not JSON, fall through
29
+ }
30
+ console.error(`Failed to query database: ${output || err.message}`);
31
+ process.exit(1);
32
+ }
33
+ const parsed = JSON.parse(stdout);
34
+ if (parsed.error?.text) {
35
+ console.error(`Wrangler error: ${parsed.error.text}`);
36
+ process.exit(1);
37
+ }
38
+ return parsed[0]?.results || [];
39
+ }
40
+
41
+ function dumpTable(name, flag, customQuery) {
42
+ const rows = query(customQuery || `SELECT * FROM ${name}`, flag);
43
+ return rows
44
+ .map(
45
+ (row) =>
46
+ `INSERT INTO ${name} VALUES(${Object.values(row).map(sqlValue).join(",")});`,
47
+ )
48
+ .join("\n");
49
+ }
50
+
51
+ export async function run(argv) {
52
+ const { values } = parseArgs({
53
+ args: argv,
54
+ options: {
55
+ remote: { type: "boolean", default: false },
56
+ output: { type: "string", short: "o", default: "jant-export.sql" },
57
+ help: { type: "boolean", short: "h" },
58
+ },
59
+ });
60
+
61
+ if (values.help) {
62
+ console.log("Usage: jant export [--remote] [--output <file>]");
63
+ console.log("");
64
+ console.log("Export D1 database to a SQL file.");
65
+ console.log("");
66
+ console.log("Options:");
67
+ console.log(
68
+ " --remote Export from remote D1 database (default: local)",
69
+ );
70
+ console.log(
71
+ " --output, -o Output file path (default: jant-export.sql)",
72
+ );
73
+ process.exit(0);
74
+ }
75
+
76
+ const flag = values.remote ? "--remote" : "--local";
77
+ const output = values.output;
78
+
79
+ // Order matters for foreign key constraints
80
+ const tables = [
81
+ // Auth
82
+ ["settings"],
83
+ ["user"],
84
+ ["account"],
85
+ // Content
86
+ ["pages"],
87
+ ["collections"],
88
+ ["posts", "SELECT * FROM posts WHERE deleted_at IS NULL"],
89
+ ["post_collections"],
90
+ ["collection_dividers"],
91
+ ["nav_items"],
92
+ ["media"],
93
+ ["redirects"],
94
+ ];
95
+
96
+ const timestamp = new Date().toISOString();
97
+ const source = values.remote ? "remote" : "local";
98
+ let sql = `-- Jant database export\n`;
99
+ sql += `-- Exported: ${timestamp}\n`;
100
+ sql += `-- Source: ${source}\n\n`;
101
+
102
+ for (const [name, customQuery] of tables) {
103
+ const data = dumpTable(name, flag, customQuery);
104
+ if (data) {
105
+ sql += `-- ${name}\n${data}\n\n`;
106
+ }
107
+ }
108
+
109
+ const outPath = resolve(process.cwd(), output);
110
+ writeFileSync(outPath, sql);
111
+ console.log(`Exported ${source} database to ${output}`);
112
+ }
@@ -0,0 +1,40 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { execSync } from "node:child_process";
3
+ import { parseArgs } from "node:util";
4
+
5
+ export async function run(argv) {
6
+ const { values } = parseArgs({
7
+ args: argv,
8
+ options: {
9
+ remote: { type: "boolean", default: false },
10
+ help: { type: "boolean", short: "h" },
11
+ },
12
+ });
13
+
14
+ if (values.help) {
15
+ console.log("Usage: jant reset-password [--remote]");
16
+ console.log("");
17
+ console.log("Generate a password reset token (expires in 15 minutes).");
18
+ console.log("");
19
+ console.log("Options:");
20
+ console.log(" --remote Run against remote D1 database (default: local)");
21
+ process.exit(0);
22
+ }
23
+
24
+ const flag = values.remote ? "--remote" : "--local";
25
+
26
+ const token = randomBytes(32).toString("hex");
27
+ const expiry = Math.floor(Date.now() / 1000) + 15 * 60;
28
+ const value = `${token}:${expiry}`;
29
+ const timestamp = Math.floor(Date.now() / 1000);
30
+
31
+ const sql = `INSERT OR REPLACE INTO settings (key, value, updated_at) VALUES ('PASSWORD_RESET_TOKEN', '${value}', ${timestamp})`;
32
+
33
+ execSync(`npx wrangler d1 execute DB ${flag} --command "${sql}"`, {
34
+ stdio: "inherit",
35
+ });
36
+
37
+ console.log("");
38
+ console.log("Password reset token generated (expires in 15 minutes).");
39
+ console.log(`Visit: /reset?token=${token}`);
40
+ }
package/bin/jant.js ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readdir } from "node:fs/promises";
4
+ import { fileURLToPath } from "node:url";
5
+ import { dirname, join, basename } from "node:path";
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const commandsDir = join(__dirname, "commands");
9
+
10
+ async function listCommands() {
11
+ const files = await readdir(commandsDir);
12
+ return files
13
+ .filter((f) => f.endsWith(".js"))
14
+ .map((f) => basename(f, ".js"));
15
+ }
16
+
17
+ async function showHelp() {
18
+ const commands = await listCommands();
19
+ console.log("Usage: jant <command> [options]");
20
+ console.log("");
21
+ console.log("Commands:");
22
+ for (const cmd of commands) {
23
+ console.log(` ${cmd}`);
24
+ }
25
+ console.log("");
26
+ console.log("Run 'jant <command> --help' for command-specific help.");
27
+ }
28
+
29
+ // First non-flag argument is the command name
30
+ const argv = process.argv.slice(2);
31
+ const command = argv.find((arg) => !arg.startsWith("-"));
32
+
33
+ if (!command) {
34
+ await showHelp();
35
+ process.exit(0);
36
+ }
37
+
38
+ const commands = await listCommands();
39
+ if (!commands.includes(command)) {
40
+ console.error(`Unknown command: ${command}`);
41
+ console.error("");
42
+ await showHelp();
43
+ process.exit(1);
44
+ }
45
+
46
+ // Pass everything after the command name to the subcommand
47
+ const commandIndex = argv.indexOf(command);
48
+ const mod = await import(join(commandsDir, `${command}.js`));
49
+ await mod.run(argv.slice(commandIndex + 1));
package/dist/index.js CHANGED
@@ -4692,7 +4692,7 @@ const I18nProvider = ({ c, children })=>{
4692
4692
  }
4693
4693
 
4694
4694
  const IS_VITE_DEV = typeof __JANT_DEV__ !== "undefined" && __JANT_DEV__ === true;
4695
- const CORE_VERSION = "0.3.29";
4695
+ const CORE_VERSION = "0.3.31";
4696
4696
 
4697
4697
  const BaseLayout = ({ title, description, lang, c, toast, faviconUrl, faviconVersion, noindex, isAuthenticated = false, children })=>{
4698
4698
  // Read lang from Hono context if available, otherwise use prop or default
@@ -6435,10 +6435,11 @@ const sqid = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
6435
6435
  const publicUrl = getPublicUrlForProvider(media.provider, ctx.r2PublicUrl, ctx.s3PublicUrl);
6436
6436
  const url = getMediaUrl(media.storageKey, publicUrl);
6437
6437
  const thumbnailUrl = getImageUrl(url, ctx.imageTransformUrl, {
6438
- width: 400,
6438
+ width: 1200,
6439
+ height: 768,
6439
6440
  quality: 80,
6440
6441
  format: "auto",
6441
- fit: "cover"
6442
+ fit: "scale-down"
6442
6443
  });
6443
6444
  return {
6444
6445
  id: media.id,
@@ -7231,10 +7232,11 @@ const SiteLayout = ({ siteName, siteDescription, links, currentPath, isAuthentic
7231
7232
  id: m.id,
7232
7233
  url: getMediaUrl(m.storageKey, publicUrl),
7233
7234
  previewUrl: getImageUrl(getMediaUrl(m.storageKey, publicUrl), imageTransformUrl, {
7234
- width: 400,
7235
+ width: 1200,
7236
+ height: 768,
7235
7237
  quality: 80,
7236
7238
  format: "auto",
7237
- fit: "cover"
7239
+ fit: "scale-down"
7238
7240
  }),
7239
7241
  alt: m.alt,
7240
7242
  blurhash: m.blurhash,
@@ -13780,10 +13782,11 @@ const postsApiRoutes = new Hono();
13780
13782
  const publicUrl = getPublicUrlForProvider(m.provider, r2PublicUrl, s3PublicUrl);
13781
13783
  const url = getMediaUrl(m.storageKey, publicUrl);
13782
13784
  const previewUrl = getImageUrl(url, imageTransformUrl, {
13783
- width: 400,
13785
+ width: 1200,
13786
+ height: 768,
13784
13787
  quality: 80,
13785
13788
  format: "auto",
13786
- fit: "cover"
13789
+ fit: "scale-down"
13787
13790
  });
13788
13791
  return {
13789
13792
  id: m.id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jant/core",
3
- "version": "0.3.29",
3
+ "version": "0.3.31",
4
4
  "description": "A modern, open-source microblogging platform built on Cloudflare Workers",
5
5
  "type": "module",
6
6
  "exports": {
@@ -11,7 +11,7 @@
11
11
  "./i18n": "./src/i18n/index.ts"
12
12
  },
13
13
  "bin": {
14
- "jant-reset-password": "bin/reset-password.js"
14
+ "jant": "bin/jant.js"
15
15
  },
16
16
  "files": [
17
17
  "bin",
@@ -49,7 +49,13 @@ export function buildMediaMap(
49
49
  previewUrl: getImageUrl(
50
50
  getMediaUrl(m.storageKey, publicUrl),
51
51
  imageTransformUrl,
52
- { width: 400, quality: 80, format: "auto", fit: "cover" },
52
+ {
53
+ width: 1200,
54
+ height: 768,
55
+ quality: 80,
56
+ format: "auto",
57
+ fit: "scale-down",
58
+ },
53
59
  ),
54
60
  alt: m.alt,
55
61
  blurhash: m.blurhash,
package/src/lib/view.ts CHANGED
@@ -79,10 +79,11 @@ export function toMediaView(media: Media, ctx: MediaContext): MediaView {
79
79
  );
80
80
  const url = getMediaUrl(media.storageKey, publicUrl);
81
81
  const thumbnailUrl = getImageUrl(url, ctx.imageTransformUrl, {
82
- width: 400,
82
+ width: 1200,
83
+ height: 768,
83
84
  quality: 80,
84
85
  format: "auto",
85
- fit: "cover",
86
+ fit: "scale-down",
86
87
  });
87
88
 
88
89
  return {
@@ -44,10 +44,11 @@ function toMediaAttachment(
44
44
  );
45
45
  const url = getMediaUrl(m.storageKey, publicUrl);
46
46
  const previewUrl = getImageUrl(url, imageTransformUrl, {
47
- width: 400,
47
+ width: 1200,
48
+ height: 768,
48
49
  quality: 80,
49
50
  format: "auto",
50
- fit: "cover",
51
+ fit: "scale-down",
51
52
  });
52
53
 
53
54
  return {
@@ -1,22 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { randomBytes } from "crypto";
4
- import { execSync } from "child_process";
5
-
6
- const isRemote = process.argv.includes("--remote");
7
- const flag = isRemote ? "--remote" : "--local";
8
-
9
- const token = randomBytes(32).toString("hex");
10
- const expiry = Math.floor(Date.now() / 1000) + 15 * 60; // 15 minutes
11
- const value = `${token}:${expiry}`;
12
- const timestamp = Math.floor(Date.now() / 1000);
13
-
14
- const sql = `INSERT OR REPLACE INTO settings (key, value, updated_at) VALUES ('PASSWORD_RESET_TOKEN', '${value}', ${timestamp})`;
15
-
16
- execSync(`npx wrangler d1 execute DB ${flag} --command "${sql}"`, {
17
- stdio: "inherit",
18
- });
19
-
20
- console.log("");
21
- console.log("Password reset token generated (expires in 15 minutes).");
22
- console.log(`Visit: /reset?token=${token}`);