@jant/core 0.3.46 → 0.3.48
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/db/execute-file.js +12 -4
- package/bin/commands/db/rehearse.js +2 -2
- package/bin/commands/export.js +12 -4
- package/bin/commands/import-site.js +60 -267
- package/bin/commands/migrate.js +36 -69
- package/bin/commands/reset-password.js +10 -4
- package/bin/commands/site/export.js +59 -248
- package/bin/commands/site/snapshot/export.js +58 -45
- package/bin/commands/site/snapshot/import.js +104 -52
- package/bin/lib/node-env.js +100 -0
- package/bin/lib/runtime-target.js +64 -0
- package/bin/lib/site-snapshot.js +185 -54
- package/bin/lib/sql-export.js +19 -2
- package/dist/app-DU7dpJID.js +6 -0
- package/dist/{app-DB-P66E5.js → app-DdnIoX7y.js} +333 -191
- package/dist/client/.vite/manifest.json +2 -2
- package/dist/client/_assets/client-BoUn7xBo.css +2 -0
- package/dist/client/_assets/{client-auth-BLCUje4M.js → client-auth-Ce5WEAVS.js} +102 -49
- package/dist/{github-sync-CQ1x271f.js → export-ZBlfKSKm.js} +12 -439
- package/dist/github-sync-C593r22F.js +4 -0
- package/dist/github-sync-bL1hnx3Q.js +428 -0
- package/dist/index.js +3 -2
- package/dist/node.js +5 -4
- package/package.json +3 -2
- package/src/__tests__/helpers/export-fixtures.ts +0 -1
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -0
- package/src/client/components/__tests__/jant-settings-general.test.ts +70 -0
- package/src/client/components/jant-settings-general.ts +164 -22
- package/src/client/components/settings-types.ts +4 -6
- package/src/client-auth.ts +1 -1
- package/src/db/__tests__/demo-canonical-snapshot.test.ts +1 -1
- package/src/db/__tests__/migration-rehearsal.test.ts +2 -5
- package/src/db/backfills/0004_register_apple_touch_media_rows.sql +65 -0
- package/src/db/migrations/0021_thankful_phalanx.sql +16 -0
- package/src/db/migrations/meta/0021_snapshot.json +2121 -0
- package/src/db/migrations/meta/_journal.json +7 -0
- package/src/db/migrations/pg/0019_gray_natasha_romanoff.sql +20 -0
- package/src/db/migrations/pg/meta/0019_snapshot.json +2718 -0
- package/src/db/migrations/pg/meta/_journal.json +7 -0
- package/src/db/pg/schema.ts +21 -26
- package/src/db/rehearsal-fixtures/demo-current.json +1 -1
- package/src/db/schema.ts +16 -20
- package/src/i18n/__tests__/middleware.test.ts +43 -1
- package/src/i18n/coverage.generated.ts +17 -0
- package/src/i18n/i18n.ts +18 -2
- package/src/i18n/index.ts +3 -0
- package/src/i18n/locales/settings/en.po +16 -11
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +17 -12
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +16 -11
- package/src/i18n/locales/settings/zh-Hant.ts +1 -1
- package/src/i18n/locales.ts +84 -2
- package/src/i18n/middleware.ts +25 -16
- package/src/i18n/supported-locales.ts +153 -0
- package/src/lib/__tests__/csp-builder.test.ts +19 -2
- package/src/lib/__tests__/feed.test.ts +242 -1
- package/src/lib/__tests__/post-meta.test.ts +0 -1
- package/src/lib/__tests__/view.test.ts +0 -1
- package/src/lib/api-posts.ts +9 -7
- package/src/lib/csp-builder.ts +28 -10
- package/src/lib/feed.ts +153 -3
- package/src/middleware/__tests__/secure-headers.test.ts +89 -0
- package/src/middleware/auth.ts +1 -1
- package/src/middleware/secure-headers.ts +47 -1
- package/src/node/__tests__/cli-runtime-target.test.ts +110 -2
- package/src/node/__tests__/cli-site-snapshot.test.ts +308 -13
- package/src/node/__tests__/cli-site-token-env.test.ts +2 -7
- package/src/node/__tests__/cli-snapshot-meta.test.ts +85 -0
- package/src/node/__tests__/cli-sql-export.test.ts +49 -0
- package/src/node/index.ts +1 -0
- package/src/preset.css +8 -2
- package/src/routes/api/__tests__/settings.test.ts +3 -2
- package/src/routes/api/github-sync.tsx +1 -1
- package/src/routes/api/settings.ts +4 -1
- package/src/routes/auth/signin.tsx +6 -0
- package/src/routes/pages/archive.tsx +4 -2
- package/src/services/__tests__/post.test.ts +19 -19
- package/src/services/__tests__/search.test.ts +0 -1
- package/src/services/__tests__/settings.test.ts +22 -3
- package/src/services/bootstrap.ts +7 -3
- package/src/services/collection.ts +3 -3
- package/src/services/export.ts +0 -3
- package/src/services/navigation.ts +0 -2
- package/src/services/path.ts +1 -38
- package/src/services/post.ts +32 -66
- package/src/services/search.ts +0 -6
- package/src/services/settings.ts +47 -6
- package/src/services/site-admin.ts +6 -1
- package/src/styles/ui.css +14 -25
- package/src/types/entities.ts +0 -1
- package/src/ui/color-themes.ts +1 -1
- package/src/ui/dash/settings/GeneralContent.tsx +17 -19
- package/src/ui/dash/settings/SettingsRootContent.tsx +17 -28
- package/src/ui/feed/NoteCard.tsx +1 -11
- package/src/ui/feed/__tests__/timeline-cards.test.ts +1 -1
- package/src/ui/pages/PostPage.tsx +2 -0
- package/bin/commands/collections.js +0 -268
- package/bin/commands/media.js +0 -302
- package/bin/commands/posts.js +0 -262
- package/bin/commands/search.js +0 -53
- package/bin/commands/settings.js +0 -93
- package/bin/lib/http-api.js +0 -223
- package/bin/lib/media-upload.js +0 -206
- package/dist/app-CM7sb3xO.js +0 -5
- package/dist/client/_assets/client-DDs6NzB3.css +0 -2
- package/src/__tests__/bin/content-cli.test.ts +0 -179
- package/src/__tests__/bin/media-cli.test.ts +0 -192
- /package/dist/{github-api-BkRWnqMx.js → github-api-Bh0PH3zr.js} +0 -0
- /package/dist/{github-app-WeadXMb8.js → github-app-D0GvNnqp.js} +0 -0
|
@@ -3,13 +3,13 @@ import { parseArgs } from "node:util";
|
|
|
3
3
|
import { executeD1 } from "../../lib/d1-query.js";
|
|
4
4
|
import { openNodeDatabase } from "../../lib/node-database.js";
|
|
5
5
|
import {
|
|
6
|
+
bootstrapCliRuntime,
|
|
6
7
|
getCliRuntimeLabel,
|
|
7
|
-
resolveCliRuntime,
|
|
8
8
|
} from "../../lib/runtime-target.js";
|
|
9
9
|
|
|
10
10
|
function formatUsage() {
|
|
11
11
|
console.log(
|
|
12
|
-
"Usage: jant db execute-file --file <path> [--local | --remote] [--config <file>] [--env <name>] [--database <binding>]",
|
|
12
|
+
"Usage: jant db execute-file --file <path> [--local | --remote | --node] [--config <file>] [--env <name>] [--database <binding>]",
|
|
13
13
|
);
|
|
14
14
|
console.log("");
|
|
15
15
|
console.log(
|
|
@@ -20,6 +20,9 @@ function formatUsage() {
|
|
|
20
20
|
console.log(" --file SQL file to execute");
|
|
21
21
|
console.log(" --local Force local D1 instead of DATABASE_URL");
|
|
22
22
|
console.log(" --remote Run against remote D1");
|
|
23
|
+
console.log(
|
|
24
|
+
" --node Force Node runtime even if DATABASE_URL is unset",
|
|
25
|
+
);
|
|
23
26
|
console.log(
|
|
24
27
|
" --config Wrangler config file (default: wrangler.toml)",
|
|
25
28
|
);
|
|
@@ -28,8 +31,12 @@ function formatUsage() {
|
|
|
28
31
|
console.log(" --persist-to Local D1 state directory override");
|
|
29
32
|
console.log("");
|
|
30
33
|
console.log(
|
|
31
|
-
"
|
|
34
|
+
"`.env.node` next to your project (or in packages/core/) is auto-loaded.",
|
|
35
|
+
);
|
|
36
|
+
console.log(
|
|
37
|
+
"If DATABASE_URL or DATA_DIR is then set and no runtime flag is passed,",
|
|
32
38
|
);
|
|
39
|
+
console.log("this command uses the Node database runtime.");
|
|
33
40
|
}
|
|
34
41
|
|
|
35
42
|
async function loadSqlFile(filePath) {
|
|
@@ -59,6 +66,7 @@ export async function run(argv) {
|
|
|
59
66
|
file: { type: "string" },
|
|
60
67
|
help: { type: "boolean", short: "h" },
|
|
61
68
|
local: { type: "boolean", default: false },
|
|
69
|
+
node: { type: "boolean", default: false },
|
|
62
70
|
"persist-to": { type: "string" },
|
|
63
71
|
remote: { type: "boolean", default: false },
|
|
64
72
|
},
|
|
@@ -73,7 +81,7 @@ export async function run(argv) {
|
|
|
73
81
|
throw new Error("Missing required --file <path> argument.");
|
|
74
82
|
}
|
|
75
83
|
|
|
76
|
-
const runtime =
|
|
84
|
+
const { runtime } = bootstrapCliRuntime(values);
|
|
77
85
|
const sql = await loadSqlFile(values.file);
|
|
78
86
|
|
|
79
87
|
if (runtime === "node") {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
2
|
import { rehearseD1Migrations } from "../../lib/migration-rehearsal.js";
|
|
3
3
|
import {
|
|
4
|
+
bootstrapCliRuntime,
|
|
4
5
|
getCliRuntimeLabel,
|
|
5
|
-
resolveCliRuntime,
|
|
6
6
|
} from "../../lib/runtime-target.js";
|
|
7
7
|
|
|
8
8
|
export async function run(argv) {
|
|
@@ -46,7 +46,7 @@ export async function run(argv) {
|
|
|
46
46
|
throw new Error("Missing required --fixture option.");
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
const runtime =
|
|
49
|
+
const { runtime } = bootstrapCliRuntime(values);
|
|
50
50
|
if (runtime === "node") {
|
|
51
51
|
throw new Error(
|
|
52
52
|
"Migration rehearsal only supports D1. Pass --local or --remote.",
|
package/bin/commands/export.js
CHANGED
|
@@ -5,8 +5,8 @@ import { queryD1 } from "../lib/d1-query.js";
|
|
|
5
5
|
import { openNodeDatabase } from "../lib/node-database.js";
|
|
6
6
|
import { dumpDatabaseToSql } from "../lib/sql-export.js";
|
|
7
7
|
import {
|
|
8
|
+
bootstrapCliRuntime,
|
|
8
9
|
getCliRuntimeLabel,
|
|
9
|
-
resolveCliRuntime,
|
|
10
10
|
} from "../lib/runtime-target.js";
|
|
11
11
|
|
|
12
12
|
function createD1QueryRunner(runtime) {
|
|
@@ -25,6 +25,7 @@ export async function run(argv) {
|
|
|
25
25
|
database: { type: "string", default: "DB" },
|
|
26
26
|
env: { type: "string" },
|
|
27
27
|
local: { type: "boolean", default: false },
|
|
28
|
+
node: { type: "boolean", default: false },
|
|
28
29
|
remote: { type: "boolean", default: false },
|
|
29
30
|
output: { type: "string", short: "o", default: "jant-export.sql" },
|
|
30
31
|
help: { type: "boolean", short: "h" },
|
|
@@ -34,7 +35,7 @@ export async function run(argv) {
|
|
|
34
35
|
|
|
35
36
|
if (values.help) {
|
|
36
37
|
console.log(
|
|
37
|
-
"Usage: jant db export [--local | --remote] [--output <file>] [--config <file>] [--env <name>] [--database <binding>]",
|
|
38
|
+
"Usage: jant db export [--local | --remote | --node] [--output <file>] [--config <file>] [--env <name>] [--database <binding>]",
|
|
38
39
|
);
|
|
39
40
|
console.log("");
|
|
40
41
|
console.log("Export the current database to a SQL file.");
|
|
@@ -44,6 +45,9 @@ export async function run(argv) {
|
|
|
44
45
|
console.log(
|
|
45
46
|
" --remote Export from remote D1 database (default: local)",
|
|
46
47
|
);
|
|
48
|
+
console.log(
|
|
49
|
+
" --node Force Node runtime even if DATABASE_URL is unset",
|
|
50
|
+
);
|
|
47
51
|
console.log(
|
|
48
52
|
" --output, -o Output file path (default: jant-export.sql)",
|
|
49
53
|
);
|
|
@@ -55,14 +59,18 @@ export async function run(argv) {
|
|
|
55
59
|
console.log(" --persist-to Local D1 state directory override");
|
|
56
60
|
console.log("");
|
|
57
61
|
console.log(
|
|
58
|
-
"
|
|
62
|
+
"`.env.node` next to your project (or in packages/core/) is auto-loaded.",
|
|
63
|
+
);
|
|
64
|
+
console.log(
|
|
65
|
+
"If DATABASE_URL or DATA_DIR is then set and no runtime flag is passed,",
|
|
59
66
|
);
|
|
67
|
+
console.log("this command uses the Node database runtime.");
|
|
60
68
|
console.log("");
|
|
61
69
|
console.log("Compatibility alias: jant export");
|
|
62
70
|
process.exit(0);
|
|
63
71
|
}
|
|
64
72
|
|
|
65
|
-
const runtime =
|
|
73
|
+
const { runtime } = bootstrapCliRuntime(values);
|
|
66
74
|
const output = values.output;
|
|
67
75
|
let sql;
|
|
68
76
|
|
|
@@ -18,8 +18,6 @@ import {
|
|
|
18
18
|
normalizeImportedBody,
|
|
19
19
|
rewriteMediaReferences,
|
|
20
20
|
} from "../lib/site-media-parser.js";
|
|
21
|
-
import { openNodeDatabase } from "../lib/node-database.js";
|
|
22
|
-
import { loadNodeRuntime } from "../lib/load-node-runtime.js";
|
|
23
21
|
import { parseFrontMatter as parseFrontMatterShared } from "../lib/hugo-markdown.js";
|
|
24
22
|
|
|
25
23
|
/**
|
|
@@ -1483,223 +1481,6 @@ function createRemoteTarget(apiUrl, token) {
|
|
|
1483
1481
|
};
|
|
1484
1482
|
}
|
|
1485
1483
|
|
|
1486
|
-
async function createLocalTarget(env = process.env) {
|
|
1487
|
-
const nodeDatabase = await openNodeDatabase(env);
|
|
1488
|
-
const { createNodeCliRuntime, resolveConfig } = await loadNodeRuntime();
|
|
1489
|
-
const bindings = nodeDatabase.bindings;
|
|
1490
|
-
const runtime = await createNodeCliRuntime(bindings);
|
|
1491
|
-
const allSettings = await runtime.services.settings.getAll();
|
|
1492
|
-
const appConfig = resolveConfig(bindings, allSettings);
|
|
1493
|
-
const summaryConfig = {
|
|
1494
|
-
maxParagraphs: appConfig.summaryMaxParagraphs,
|
|
1495
|
-
maxChars: appConfig.summaryMaxChars,
|
|
1496
|
-
};
|
|
1497
|
-
|
|
1498
|
-
return {
|
|
1499
|
-
async close() {
|
|
1500
|
-
await nodeDatabase.close();
|
|
1501
|
-
},
|
|
1502
|
-
async getSetupStatus() {
|
|
1503
|
-
return runtime.services.settings.isOnboardingComplete();
|
|
1504
|
-
},
|
|
1505
|
-
async updateSettings(updates) {
|
|
1506
|
-
await runtime.services.settings.setMany(updates);
|
|
1507
|
-
return { settings: updates };
|
|
1508
|
-
},
|
|
1509
|
-
async updateImportSettings(updates) {
|
|
1510
|
-
await runtime.services.settings.setMany(updates);
|
|
1511
|
-
return { success: true };
|
|
1512
|
-
},
|
|
1513
|
-
async listNavItems() {
|
|
1514
|
-
return runtime.services.navItems.list();
|
|
1515
|
-
},
|
|
1516
|
-
async createNavItem(data) {
|
|
1517
|
-
return runtime.services.navItems.create(data);
|
|
1518
|
-
},
|
|
1519
|
-
async deleteNavItem(id) {
|
|
1520
|
-
return runtime.services.navItems.delete(id);
|
|
1521
|
-
},
|
|
1522
|
-
async removeSiteAvatar() {
|
|
1523
|
-
return runtime.services.settings.removeAvatar(runtime.storage);
|
|
1524
|
-
},
|
|
1525
|
-
async uploadSiteAvatar(data) {
|
|
1526
|
-
if (!runtime.storage) {
|
|
1527
|
-
throw new Error("Local import requires configured storage.");
|
|
1528
|
-
}
|
|
1529
|
-
|
|
1530
|
-
const avatarAsset = await readImportAsset({
|
|
1531
|
-
sourceUrl: data.avatarUrl,
|
|
1532
|
-
sourceFilePath: data.avatarFilePath,
|
|
1533
|
-
});
|
|
1534
|
-
if (!avatarAsset) {
|
|
1535
|
-
throw new Error(`Failed to read site avatar: ${data.avatarUrl}`);
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
let faviconIco;
|
|
1539
|
-
if (data.faviconUrl || data.faviconFilePath) {
|
|
1540
|
-
const faviconAsset = await readImportAsset({
|
|
1541
|
-
sourceUrl: data.faviconUrl,
|
|
1542
|
-
sourceFilePath: data.faviconFilePath,
|
|
1543
|
-
mimeType: "image/x-icon",
|
|
1544
|
-
originalName: "favicon.ico",
|
|
1545
|
-
});
|
|
1546
|
-
if (faviconAsset) {
|
|
1547
|
-
faviconIco = toArrayBuffer(faviconAsset.bytes);
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
let appleTouchIcon;
|
|
1552
|
-
if (data.appleTouchUrl) {
|
|
1553
|
-
const appleTouchAsset = await readImportAsset({
|
|
1554
|
-
sourceUrl: data.appleTouchUrl,
|
|
1555
|
-
sourceFilePath: data.appleTouchFilePath,
|
|
1556
|
-
});
|
|
1557
|
-
if (appleTouchAsset) {
|
|
1558
|
-
appleTouchIcon = toArrayBuffer(appleTouchAsset.bytes);
|
|
1559
|
-
}
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
|
-
await runtime.services.settings.uploadAvatar(
|
|
1563
|
-
{
|
|
1564
|
-
file: createUploadFile(
|
|
1565
|
-
avatarAsset.filename,
|
|
1566
|
-
avatarAsset.contentType,
|
|
1567
|
-
avatarAsset.bytes,
|
|
1568
|
-
),
|
|
1569
|
-
faviconIco,
|
|
1570
|
-
appleTouchIcon,
|
|
1571
|
-
},
|
|
1572
|
-
{
|
|
1573
|
-
media: runtime.services.media,
|
|
1574
|
-
storage: runtime.storage,
|
|
1575
|
-
storageProvider: appConfig.storageDriver,
|
|
1576
|
-
maxFileSizeMB: appConfig.uploadMaxFileSize,
|
|
1577
|
-
},
|
|
1578
|
-
);
|
|
1579
|
-
|
|
1580
|
-
return { success: true };
|
|
1581
|
-
},
|
|
1582
|
-
async syncSiteAvatar(data) {
|
|
1583
|
-
await this.removeSiteAvatar();
|
|
1584
|
-
if (!data) {
|
|
1585
|
-
return { success: true };
|
|
1586
|
-
}
|
|
1587
|
-
return this.uploadSiteAvatar(data);
|
|
1588
|
-
},
|
|
1589
|
-
async listCollections() {
|
|
1590
|
-
return runtime.services.collections.list();
|
|
1591
|
-
},
|
|
1592
|
-
async listCollectionDirectoryItems() {
|
|
1593
|
-
return runtime.services.collections.listDirectoryItems();
|
|
1594
|
-
},
|
|
1595
|
-
async createCollection(data) {
|
|
1596
|
-
return runtime.services.collections.create(data);
|
|
1597
|
-
},
|
|
1598
|
-
async createCollectionDirectoryItem(data) {
|
|
1599
|
-
return runtime.services.collections.createDirectoryItem(data);
|
|
1600
|
-
},
|
|
1601
|
-
async moveCollectionDirectoryItem(id, after, before) {
|
|
1602
|
-
return runtime.services.collections.moveDirectoryItem(id, after, before);
|
|
1603
|
-
},
|
|
1604
|
-
async deleteCollectionDirectoryItem(id) {
|
|
1605
|
-
return runtime.services.collections.deleteDirectoryItem(id);
|
|
1606
|
-
},
|
|
1607
|
-
async createPost(data) {
|
|
1608
|
-
const { attachments, ...postData } = data;
|
|
1609
|
-
return runtime.services.posts.createWithAttachments(
|
|
1610
|
-
postData,
|
|
1611
|
-
attachments,
|
|
1612
|
-
{
|
|
1613
|
-
media: runtime.services.media,
|
|
1614
|
-
storage: runtime.storage,
|
|
1615
|
-
storageDriver: appConfig.storageDriver,
|
|
1616
|
-
maxFileSizeMB: appConfig.uploadMaxFileSize,
|
|
1617
|
-
},
|
|
1618
|
-
summaryConfig,
|
|
1619
|
-
);
|
|
1620
|
-
},
|
|
1621
|
-
async createAlias(path, targetSlug) {
|
|
1622
|
-
const post = await runtime.services.posts.getBySlug(targetSlug);
|
|
1623
|
-
if (!post) {
|
|
1624
|
-
throw new Error(`Post with slug "${targetSlug}" not found`);
|
|
1625
|
-
}
|
|
1626
|
-
return runtime.services.customUrls.create({
|
|
1627
|
-
path,
|
|
1628
|
-
targetType: "post",
|
|
1629
|
-
targetId: post.id,
|
|
1630
|
-
});
|
|
1631
|
-
},
|
|
1632
|
-
async uploadMedia(mediaSpec) {
|
|
1633
|
-
if (!runtime.storage) {
|
|
1634
|
-
throw new Error("Local import requires configured storage.");
|
|
1635
|
-
}
|
|
1636
|
-
|
|
1637
|
-
const asset = await readMediaSpecAsset(mediaSpec);
|
|
1638
|
-
if (!asset) return null;
|
|
1639
|
-
|
|
1640
|
-
const originalName =
|
|
1641
|
-
mediaSpec.originalName ||
|
|
1642
|
-
asset.filename ||
|
|
1643
|
-
getFilenameFromUrl(mediaSpec.src) ||
|
|
1644
|
-
"file";
|
|
1645
|
-
const bytes = asset.bytes;
|
|
1646
|
-
const { id, filename, storageKey } =
|
|
1647
|
-
generateImportedStorageKey(originalName);
|
|
1648
|
-
const mimeType =
|
|
1649
|
-
mediaSpec.mimeType || asset.contentType || guessMimeType(originalName);
|
|
1650
|
-
let posterKey;
|
|
1651
|
-
|
|
1652
|
-
if (mediaSpec.poster) {
|
|
1653
|
-
const posterAsset = await readMediaSpecAsset(mediaSpec, "poster");
|
|
1654
|
-
if (posterAsset) {
|
|
1655
|
-
const posterName = posterAsset.filename || "poster.webp";
|
|
1656
|
-
const posterExt = extname(posterName) || ".webp";
|
|
1657
|
-
posterKey = storageKey.replace(/(\.[^.]+)?$/, `-poster${posterExt}`);
|
|
1658
|
-
await runtime.storage.put(posterKey, posterAsset.bytes, {
|
|
1659
|
-
contentType: posterAsset.contentType || guessMimeType(posterName),
|
|
1660
|
-
});
|
|
1661
|
-
}
|
|
1662
|
-
}
|
|
1663
|
-
|
|
1664
|
-
await runtime.storage.put(storageKey, bytes, {
|
|
1665
|
-
contentType: mimeType,
|
|
1666
|
-
});
|
|
1667
|
-
|
|
1668
|
-
const createdMedia = await runtime.services.media.create({
|
|
1669
|
-
id,
|
|
1670
|
-
filename,
|
|
1671
|
-
originalName,
|
|
1672
|
-
mimeType,
|
|
1673
|
-
size: mediaSpec.size ?? bytes.byteLength,
|
|
1674
|
-
storageKey,
|
|
1675
|
-
provider: appConfig.storageDriver,
|
|
1676
|
-
width: mediaSpec.width ?? undefined,
|
|
1677
|
-
height: mediaSpec.height ?? undefined,
|
|
1678
|
-
alt: mediaSpec.alt ?? undefined,
|
|
1679
|
-
position: mediaSpec.position ?? undefined,
|
|
1680
|
-
blurhash: mediaSpec.blurhash ?? undefined,
|
|
1681
|
-
waveform: mediaSpec.waveform ?? undefined,
|
|
1682
|
-
posterKey,
|
|
1683
|
-
summary: mediaSpec.summary ?? undefined,
|
|
1684
|
-
chars: mediaSpec.chars ?? undefined,
|
|
1685
|
-
mediaKind: mediaSpec.kind ?? undefined,
|
|
1686
|
-
});
|
|
1687
|
-
|
|
1688
|
-
return {
|
|
1689
|
-
id: createdMedia.id,
|
|
1690
|
-
url: getMediaPublicUrl(
|
|
1691
|
-
createdMedia.storageKey,
|
|
1692
|
-
createdMedia.provider,
|
|
1693
|
-
appConfig,
|
|
1694
|
-
),
|
|
1695
|
-
};
|
|
1696
|
-
},
|
|
1697
|
-
async checkPostSlugAvailability(slug) {
|
|
1698
|
-
return runtime.services.posts.checkSlugAvailability(slug);
|
|
1699
|
-
},
|
|
1700
|
-
};
|
|
1701
|
-
}
|
|
1702
|
-
|
|
1703
1484
|
/**
|
|
1704
1485
|
* Walk `content/` and classify each `_index.md` / `index.md` bundle by its
|
|
1705
1486
|
* front-matter `type`. Returns ordered root-post bundles (with child reply
|
|
@@ -1998,11 +1779,48 @@ export const __test__ = {
|
|
|
1998
1779
|
buildPostPayloadFromBundle,
|
|
1999
1780
|
};
|
|
2000
1781
|
|
|
1782
|
+
function printImportUsage() {
|
|
1783
|
+
console.log("Usage: jant site import <url> [options]");
|
|
1784
|
+
console.log("");
|
|
1785
|
+
console.log("Import a Hugo export directory or ZIP into a Jant site.");
|
|
1786
|
+
console.log("");
|
|
1787
|
+
console.log("Arguments:");
|
|
1788
|
+
console.log(" <url> Jant site URL (required)");
|
|
1789
|
+
console.log("");
|
|
1790
|
+
console.log("Options:");
|
|
1791
|
+
console.log(
|
|
1792
|
+
" --path Path to export directory or ZIP file (default: .)",
|
|
1793
|
+
);
|
|
1794
|
+
console.log(" --dry-run Parse and validate without making API calls");
|
|
1795
|
+
console.log(
|
|
1796
|
+
" --skip-remote-media Skip uploading absolute-URL images found in body (relative paths and declared media still import)",
|
|
1797
|
+
);
|
|
1798
|
+
console.log(" --token API token (overrides JANT_API_TOKEN)");
|
|
1799
|
+
console.log("");
|
|
1800
|
+
console.log(
|
|
1801
|
+
"Import expects an empty target site and fails on slug or alias conflicts.",
|
|
1802
|
+
);
|
|
1803
|
+
console.log("");
|
|
1804
|
+
console.log("Authentication:");
|
|
1805
|
+
console.log(` export ${CLI_API_TOKEN_ENV_VAR}=jnt_your_token`);
|
|
1806
|
+
console.log(" jant site import https://your-site.example --path ./export");
|
|
1807
|
+
console.log("");
|
|
1808
|
+
console.log("Examples:");
|
|
1809
|
+
console.log(
|
|
1810
|
+
" jant site import https://your-site.example --path ./jant-site",
|
|
1811
|
+
);
|
|
1812
|
+
console.log(
|
|
1813
|
+
" jant site import https://your-site.example --path ./jant-site-export.zip",
|
|
1814
|
+
);
|
|
1815
|
+
console.log("");
|
|
1816
|
+
console.log("Compatibility alias: jant import-site");
|
|
1817
|
+
}
|
|
1818
|
+
|
|
2001
1819
|
export async function run(argv) {
|
|
2002
|
-
const { values } = parseArgs({
|
|
1820
|
+
const { values, positionals } = parseArgs({
|
|
2003
1821
|
args: argv,
|
|
1822
|
+
allowPositionals: true,
|
|
2004
1823
|
options: {
|
|
2005
|
-
url: { type: "string" },
|
|
2006
1824
|
token: { type: "string" },
|
|
2007
1825
|
path: { type: "string", default: "." },
|
|
2008
1826
|
"dry-run": { type: "boolean", default: false },
|
|
@@ -2012,63 +1830,38 @@ export async function run(argv) {
|
|
|
2012
1830
|
});
|
|
2013
1831
|
|
|
2014
1832
|
if (values.help) {
|
|
2015
|
-
|
|
2016
|
-
console.log("");
|
|
2017
|
-
console.log("Import a Hugo export directory or ZIP into a Jant instance.");
|
|
2018
|
-
console.log("");
|
|
2019
|
-
console.log("Modes:");
|
|
2020
|
-
console.log(
|
|
2021
|
-
" Local No --url; imports into the local Node database runtime",
|
|
2022
|
-
);
|
|
2023
|
-
console.log(
|
|
2024
|
-
` Remote --url requires ${CLI_API_TOKEN_ENV_VAR} or --token`,
|
|
2025
|
-
);
|
|
2026
|
-
console.log("");
|
|
2027
|
-
console.log("Options:");
|
|
2028
|
-
console.log(" --url Target remote Jant instance URL");
|
|
2029
|
-
console.log(
|
|
2030
|
-
" --path Path to export directory or ZIP file (default: .)",
|
|
2031
|
-
);
|
|
2032
|
-
console.log(" --dry-run Parse and validate without making API calls");
|
|
2033
|
-
console.log(
|
|
2034
|
-
" --skip-remote-media Skip uploading absolute-URL images found in body (relative paths and declared media still import)",
|
|
2035
|
-
);
|
|
2036
|
-
console.log("");
|
|
2037
|
-
console.log(
|
|
2038
|
-
"Import expects an empty target site and fails on slug or alias conflicts.",
|
|
2039
|
-
);
|
|
2040
|
-
console.log("");
|
|
2041
|
-
console.log("Authentication:");
|
|
2042
|
-
console.log(` Set ${CLI_API_TOKEN_ENV_VAR} env var (recommended):`);
|
|
2043
|
-
console.log(` export ${CLI_API_TOKEN_ENV_VAR}=jnt_your_token`);
|
|
2044
|
-
console.log(" jant site import --url https://your-site.com");
|
|
2045
|
-
console.log("");
|
|
2046
|
-
console.log("Examples:");
|
|
2047
|
-
console.log(" jant site import --path ./jant-site");
|
|
2048
|
-
console.log(" jant site import --path ./jant-site-export.zip");
|
|
2049
|
-
console.log("");
|
|
2050
|
-
console.log("Compatibility alias: jant import-site");
|
|
1833
|
+
printImportUsage();
|
|
2051
1834
|
process.exit(0);
|
|
2052
1835
|
}
|
|
2053
1836
|
|
|
1837
|
+
const url = positionals[0];
|
|
1838
|
+
if (!url) {
|
|
1839
|
+
console.error("Error: site URL is required");
|
|
1840
|
+
console.error("");
|
|
1841
|
+
printImportUsage();
|
|
1842
|
+
process.exit(1);
|
|
1843
|
+
}
|
|
1844
|
+
if (positionals.length > 1) {
|
|
1845
|
+
console.error(
|
|
1846
|
+
`Error: unexpected extra arguments: ${positionals.slice(1).join(" ")}`,
|
|
1847
|
+
);
|
|
1848
|
+
process.exit(1);
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
const dryRun = values["dry-run"];
|
|
2054
1852
|
const token = getCliApiToken(process.env, values.token);
|
|
2055
|
-
if (
|
|
1853
|
+
if (!token && !dryRun) {
|
|
2056
1854
|
console.error(
|
|
2057
|
-
`Error:
|
|
1855
|
+
`Error: site import requires ${CLI_API_TOKEN_ENV_VAR} or --token (unless using --dry-run)`,
|
|
2058
1856
|
);
|
|
2059
1857
|
console.error("");
|
|
2060
1858
|
console.error(` export ${CLI_API_TOKEN_ENV_VAR}=jnt_your_token`);
|
|
2061
1859
|
process.exit(1);
|
|
2062
1860
|
}
|
|
2063
1861
|
|
|
2064
|
-
const apiUrl =
|
|
2065
|
-
const dryRun = values["dry-run"];
|
|
1862
|
+
const apiUrl = url.replace(/\/$/, "");
|
|
2066
1863
|
const skipRemoteMedia = values["skip-remote-media"];
|
|
2067
|
-
const target = dryRun
|
|
2068
|
-
? null
|
|
2069
|
-
: values.url
|
|
2070
|
-
? createRemoteTarget(apiUrl, token)
|
|
2071
|
-
: await createLocalTarget(process.env);
|
|
1864
|
+
const target = dryRun ? null : createRemoteTarget(apiUrl, token);
|
|
2072
1865
|
|
|
2073
1866
|
// 1. Read source — directory or ZIP
|
|
2074
1867
|
const inputPath = resolve(process.cwd(), values.path);
|
|
@@ -2118,7 +1911,7 @@ export async function run(argv) {
|
|
|
2118
1911
|
if (target) {
|
|
2119
1912
|
const setupError = await getIncompleteSetupError(
|
|
2120
1913
|
target,
|
|
2121
|
-
|
|
1914
|
+
`Target site at ${apiUrl}`,
|
|
2122
1915
|
);
|
|
2123
1916
|
if (setupError) {
|
|
2124
1917
|
console.error("");
|
package/bin/commands/migrate.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
2
2
|
import { resolve, dirname } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { parseArgs } from "node:util";
|
|
@@ -10,12 +10,15 @@ import {
|
|
|
10
10
|
applyPgBackfills,
|
|
11
11
|
} from "../lib/migration-runner.js";
|
|
12
12
|
import { loadNodeRuntime } from "../lib/load-node-runtime.js";
|
|
13
|
+
import { loadNodeEnvFile } from "../lib/node-env.js";
|
|
13
14
|
import { openNodeSqlite, resolveDatabaseDialect } from "../lib/node-sqlite.js";
|
|
14
15
|
import {
|
|
16
|
+
bootstrapCliRuntime,
|
|
15
17
|
getCliRuntimeLabel,
|
|
16
|
-
resolveCliRuntime,
|
|
17
18
|
} from "../lib/runtime-target.js";
|
|
18
19
|
|
|
20
|
+
export { loadNodeEnvFile };
|
|
21
|
+
|
|
19
22
|
export function isMigrationDebugEnabled(env = process.env) {
|
|
20
23
|
return env.JANT_DEBUG_MIGRATE === "1";
|
|
21
24
|
}
|
|
@@ -42,38 +45,6 @@ export function describeNodeDatabaseTarget(databaseUrl) {
|
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
47
|
|
|
45
|
-
export function loadNodeEnvFile(envPath, env = process.env) {
|
|
46
|
-
const result = {
|
|
47
|
-
envPath,
|
|
48
|
-
found: false,
|
|
49
|
-
assignedKeys: [],
|
|
50
|
-
skippedKeys: [],
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
const content = readFileSync(envPath, "utf8");
|
|
55
|
-
result.found = true;
|
|
56
|
-
for (const line of content.split("\n")) {
|
|
57
|
-
const trimmed = line.trim();
|
|
58
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
59
|
-
const eqIdx = trimmed.indexOf("=");
|
|
60
|
-
if (eqIdx < 1) continue;
|
|
61
|
-
const key = trimmed.slice(0, eqIdx).trim();
|
|
62
|
-
const value = trimmed.slice(eqIdx + 1).trim();
|
|
63
|
-
if (key in env) {
|
|
64
|
-
result.skippedKeys.push(key);
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
env[key] = value;
|
|
68
|
-
result.assignedKeys.push(key);
|
|
69
|
-
}
|
|
70
|
-
} catch {
|
|
71
|
-
// .env.node not found
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return result;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
48
|
function logMigrationDebug(message) {
|
|
78
49
|
console.log(`[jant:migrate] ${message}`);
|
|
79
50
|
}
|
|
@@ -222,7 +193,7 @@ export async function run(argv) {
|
|
|
222
193
|
console.log(" --local Force local D1 instead of DATABASE_URL");
|
|
223
194
|
console.log(" --remote Run against remote D1");
|
|
224
195
|
console.log(
|
|
225
|
-
" --node Force Node runtime
|
|
196
|
+
" --node Force Node runtime even if DATABASE_URL is unset",
|
|
226
197
|
);
|
|
227
198
|
console.log(
|
|
228
199
|
" --config Wrangler config file (default: wrangler.toml)",
|
|
@@ -232,20 +203,20 @@ export async function run(argv) {
|
|
|
232
203
|
console.log(" --persist-to Local D1 state directory override");
|
|
233
204
|
console.log("");
|
|
234
205
|
console.log(
|
|
235
|
-
"
|
|
206
|
+
"`.env.node` next to your project (or in packages/core/) is auto-loaded.",
|
|
207
|
+
);
|
|
208
|
+
console.log(
|
|
209
|
+
"If DATABASE_URL or DATA_DIR is then set and no runtime flag is passed,",
|
|
236
210
|
);
|
|
211
|
+
console.log("this command uses the Node database runtime.");
|
|
237
212
|
process.exit(0);
|
|
238
213
|
}
|
|
239
214
|
|
|
240
|
-
//
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
nodeEnvLoadResult = loadNodeEnvFile(envPath);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const runtime = resolveCliRuntime(values);
|
|
215
|
+
// bootstrapCliRuntime auto-loads `.env.node` (so DATABASE_URL/DATA_DIR
|
|
216
|
+
// resolve without sourcing the file) and prints a one-line banner with
|
|
217
|
+
// the chosen target.
|
|
218
|
+
const { runtime, envLoad } = bootstrapCliRuntime(values);
|
|
219
|
+
const nodeEnvLoadResult = envLoad;
|
|
249
220
|
const debugMigrate = isMigrationDebugEnabled();
|
|
250
221
|
const databaseUrl = process.env.DATABASE_URL ?? "";
|
|
251
222
|
const databaseDialect =
|
|
@@ -255,30 +226,26 @@ export async function run(argv) {
|
|
|
255
226
|
|
|
256
227
|
if (debugMigrate) {
|
|
257
228
|
logMigrationDebug(`cli.runtime=${runtime}`);
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
`cli.node_env.database_url_source=${databaseUrlSource}`,
|
|
279
|
-
);
|
|
280
|
-
logMigrationDebug(`cli.node_env.data_dir_source=${dataDirSource}`);
|
|
281
|
-
}
|
|
229
|
+
const databaseUrlSource = nodeEnvLoadResult?.assignedKeys.includes(
|
|
230
|
+
"DATABASE_URL",
|
|
231
|
+
)
|
|
232
|
+
? ".env.node"
|
|
233
|
+
: process.env.DATABASE_URL
|
|
234
|
+
? "process.env"
|
|
235
|
+
: "<unset>";
|
|
236
|
+
const dataDirSource = nodeEnvLoadResult?.assignedKeys.includes("DATA_DIR")
|
|
237
|
+
? ".env.node"
|
|
238
|
+
: process.env.DATA_DIR
|
|
239
|
+
? "process.env"
|
|
240
|
+
: "<unset>";
|
|
241
|
+
const envPath = nodeEnvLoadResult?.envPath ?? "<unknown>";
|
|
242
|
+
const envState = nodeEnvLoadResult?.found ? "loaded" : "missing";
|
|
243
|
+
const skippedKeys = nodeEnvLoadResult?.skippedKeys.join(", ") || "<none>";
|
|
244
|
+
logMigrationDebug(`cli.node_env.path=${envPath}`);
|
|
245
|
+
logMigrationDebug(`cli.node_env.state=${envState}`);
|
|
246
|
+
logMigrationDebug(`cli.node_env.skipped_keys=${skippedKeys}`);
|
|
247
|
+
logMigrationDebug(`cli.node_env.database_url_source=${databaseUrlSource}`);
|
|
248
|
+
logMigrationDebug(`cli.node_env.data_dir_source=${dataDirSource}`);
|
|
282
249
|
|
|
283
250
|
if (runtime === "node") {
|
|
284
251
|
logMigrationDebug(`cli.node.dialect=${databaseDialect ?? "<unset>"}`);
|