@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.
- package/bin/commands/export.js +112 -0
- package/bin/commands/reset-password.js +40 -0
- package/bin/jant.js +49 -0
- package/dist/index.js +10 -7
- package/package.json +2 -2
- package/src/lib/media-helpers.ts +7 -1
- package/src/lib/view.ts +3 -2
- package/src/routes/api/posts.ts +3 -2
- package/bin/reset-password.js +0 -22
|
@@ -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.
|
|
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:
|
|
6438
|
+
width: 1200,
|
|
6439
|
+
height: 768,
|
|
6439
6440
|
quality: 80,
|
|
6440
6441
|
format: "auto",
|
|
6441
|
-
fit: "
|
|
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:
|
|
7235
|
+
width: 1200,
|
|
7236
|
+
height: 768,
|
|
7235
7237
|
quality: 80,
|
|
7236
7238
|
format: "auto",
|
|
7237
|
-
fit: "
|
|
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:
|
|
13785
|
+
width: 1200,
|
|
13786
|
+
height: 768,
|
|
13784
13787
|
quality: 80,
|
|
13785
13788
|
format: "auto",
|
|
13786
|
-
fit: "
|
|
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.
|
|
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
|
|
14
|
+
"jant": "bin/jant.js"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"bin",
|
package/src/lib/media-helpers.ts
CHANGED
|
@@ -49,7 +49,13 @@ export function buildMediaMap(
|
|
|
49
49
|
previewUrl: getImageUrl(
|
|
50
50
|
getMediaUrl(m.storageKey, publicUrl),
|
|
51
51
|
imageTransformUrl,
|
|
52
|
-
{
|
|
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:
|
|
82
|
+
width: 1200,
|
|
83
|
+
height: 768,
|
|
83
84
|
quality: 80,
|
|
84
85
|
format: "auto",
|
|
85
|
-
fit: "
|
|
86
|
+
fit: "scale-down",
|
|
86
87
|
});
|
|
87
88
|
|
|
88
89
|
return {
|
package/src/routes/api/posts.ts
CHANGED
|
@@ -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:
|
|
47
|
+
width: 1200,
|
|
48
|
+
height: 768,
|
|
48
49
|
quality: 80,
|
|
49
50
|
format: "auto",
|
|
50
|
-
fit: "
|
|
51
|
+
fit: "scale-down",
|
|
51
52
|
});
|
|
52
53
|
|
|
53
54
|
return {
|
package/bin/reset-password.js
DELETED
|
@@ -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}`);
|