@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.
- 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-DB-P66E5.js → app-3REcR-3U.js} +331 -189
- package/dist/app-B67XOEyo.js +6 -0
- package/dist/client/.vite/manifest.json +2 -2
- package/dist/client/_assets/{client-auth-BLCUje4M.js → client-auth-Ce5WEAVS.js} +102 -49
- package/dist/client/_assets/client-s71Js1Cu.css +2 -0
- 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/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 +12 -23
- 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
|
@@ -4,8 +4,8 @@ import { executeD1, queryD1 } from "../lib/d1-query.js";
|
|
|
4
4
|
import { openNodeDatabase } from "../lib/node-database.js";
|
|
5
5
|
import { loadNodeRuntime } from "../lib/load-node-runtime.js";
|
|
6
6
|
import {
|
|
7
|
+
bootstrapCliRuntime,
|
|
7
8
|
getCliRuntimeLabel,
|
|
8
|
-
resolveCliRuntime,
|
|
9
9
|
} from "../lib/runtime-target.js";
|
|
10
10
|
import { resolveCliSite } from "../lib/site-selection.js";
|
|
11
11
|
|
|
@@ -18,6 +18,7 @@ export async function run(argv) {
|
|
|
18
18
|
env: { type: "string" },
|
|
19
19
|
host: { type: "string" },
|
|
20
20
|
local: { type: "boolean", default: false },
|
|
21
|
+
node: { type: "boolean", default: false },
|
|
21
22
|
"path-prefix": { type: "string" },
|
|
22
23
|
"persist-to": { type: "string" },
|
|
23
24
|
remote: { type: "boolean", default: false },
|
|
@@ -28,13 +29,14 @@ export async function run(argv) {
|
|
|
28
29
|
});
|
|
29
30
|
|
|
30
31
|
if (values.help) {
|
|
31
|
-
console.log("Usage: jant reset-password [--local | --remote]");
|
|
32
|
+
console.log("Usage: jant reset-password [--local | --remote | --node]");
|
|
32
33
|
console.log("");
|
|
33
34
|
console.log("Generate a password reset token (expires in 15 minutes).");
|
|
34
35
|
console.log("");
|
|
35
36
|
console.log("Options:");
|
|
36
37
|
console.log(" --local Force local D1 instead of DATABASE_URL");
|
|
37
38
|
console.log(" --remote Run against remote D1 database (default: local)");
|
|
39
|
+
console.log(" --node Force Node runtime even if DATABASE_URL is unset");
|
|
38
40
|
console.log(" --site Target site id");
|
|
39
41
|
console.log(" --host Target site host");
|
|
40
42
|
console.log(" --url Target site URL");
|
|
@@ -45,12 +47,16 @@ export async function run(argv) {
|
|
|
45
47
|
console.log(" --persist-to Local D1 state directory override");
|
|
46
48
|
console.log("");
|
|
47
49
|
console.log(
|
|
48
|
-
"
|
|
50
|
+
"`.env.node` next to your project (or in packages/core/) is auto-loaded.",
|
|
49
51
|
);
|
|
52
|
+
console.log(
|
|
53
|
+
"If DATABASE_URL or DATA_DIR is then set and no runtime flag is passed,",
|
|
54
|
+
);
|
|
55
|
+
console.log("this command uses the Node database runtime.");
|
|
50
56
|
process.exit(0);
|
|
51
57
|
}
|
|
52
58
|
|
|
53
|
-
const runtime =
|
|
59
|
+
const { runtime } = bootstrapCliRuntime(values);
|
|
54
60
|
|
|
55
61
|
const token = randomBytes(32).toString("hex");
|
|
56
62
|
const hash = createHash("sha256").update(token).digest("hex");
|
|
@@ -6,22 +6,8 @@ import {
|
|
|
6
6
|
CLI_API_TOKEN_ENV_VAR,
|
|
7
7
|
getCliApiToken,
|
|
8
8
|
} from "../../lib/cli-api-token.js";
|
|
9
|
-
import { openNodeDatabase } from "../../lib/node-database.js";
|
|
10
|
-
import { loadNodeRuntime } from "../../lib/load-node-runtime.js";
|
|
11
9
|
import { pullSiteExportZipBytes } from "../../lib/site-pull-media.js";
|
|
12
10
|
|
|
13
|
-
function describeLocalExportSource(input) {
|
|
14
|
-
if (input.siteUrl) {
|
|
15
|
-
return input.siteUrl;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (input.siteDomain?.host) {
|
|
19
|
-
return `https://${input.siteDomain.host}${input.siteDomain.pathPrefix || ""}`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return `site "${input.site.key}"`;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
11
|
async function exportRemoteSite(url, token) {
|
|
26
12
|
const response = await fetch(`${url.replace(/\/$/, "")}/api/export/hugo`, {
|
|
27
13
|
method: "POST",
|
|
@@ -83,189 +69,46 @@ function logPullProgress(event) {
|
|
|
83
69
|
}
|
|
84
70
|
}
|
|
85
71
|
|
|
86
|
-
function
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (sitePathPrefix && pathname.startsWith(`${sitePathPrefix}/`)) {
|
|
119
|
-
pathname = pathname.slice(sitePathPrefix.length + 1);
|
|
120
|
-
} else {
|
|
121
|
-
pathname = pathname.replace(/^\/+/, "");
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (!pathname.startsWith("media/") && !pathname.startsWith("favicon/")) {
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return pathname;
|
|
129
|
-
} catch {
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
async function readStorageBody(body) {
|
|
135
|
-
const reader = body.getReader();
|
|
136
|
-
const chunks = [];
|
|
137
|
-
let totalLength = 0;
|
|
138
|
-
|
|
139
|
-
for (;;) {
|
|
140
|
-
const { done, value } = await reader.read();
|
|
141
|
-
if (done) break;
|
|
142
|
-
chunks.push(value);
|
|
143
|
-
totalLength += value.length;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const bytes = new Uint8Array(totalLength);
|
|
147
|
-
let offset = 0;
|
|
148
|
-
for (const chunk of chunks) {
|
|
149
|
-
bytes.set(chunk, offset);
|
|
150
|
-
offset += chunk.length;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return bytes;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function createLocalAssetLoader(storage, appConfig) {
|
|
157
|
-
if (!storage) {
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return async ({ resolvedUrl }) => {
|
|
162
|
-
const storageKey = getStorageKeyFromUrl(resolvedUrl, appConfig);
|
|
163
|
-
if (!storageKey) {
|
|
164
|
-
return null;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const object = await storage.get(storageKey);
|
|
168
|
-
if (!object?.body) {
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return {
|
|
173
|
-
bytes: await readStorageBody(object.body),
|
|
174
|
-
contentType: object.contentType || "",
|
|
175
|
-
};
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
async function exportLocalSite(env = process.env) {
|
|
180
|
-
const nodeDatabase = await openNodeDatabase(env);
|
|
181
|
-
|
|
182
|
-
try {
|
|
183
|
-
const {
|
|
184
|
-
createExportService,
|
|
185
|
-
createNodeCliRuntime,
|
|
186
|
-
resolveConfig,
|
|
187
|
-
buildThemeStyle,
|
|
188
|
-
BUILTIN_COLOR_THEMES,
|
|
189
|
-
BUILTIN_FONT_THEMES,
|
|
190
|
-
getCjkSerifCssVariables,
|
|
191
|
-
getFontThemeCssVariables,
|
|
192
|
-
} = await loadNodeRuntime();
|
|
193
|
-
const runtime = await createNodeCliRuntime(nodeDatabase.bindings);
|
|
194
|
-
const allSettings = await runtime.services.settings.getAll();
|
|
195
|
-
const navItems = await runtime.services.navItems.list();
|
|
196
|
-
const appConfig = resolveConfig(nodeDatabase.bindings, allSettings);
|
|
197
|
-
const activeTheme = BUILTIN_COLOR_THEMES.find(
|
|
198
|
-
(theme) => theme.id === (appConfig.themeId || appConfig.defaultThemeId),
|
|
199
|
-
);
|
|
200
|
-
const fontTheme = appConfig.fontThemeId
|
|
201
|
-
? BUILTIN_FONT_THEMES.find((theme) => theme.id === appConfig.fontThemeId)
|
|
202
|
-
: undefined;
|
|
203
|
-
const fontOverrides = {
|
|
204
|
-
...getCjkSerifCssVariables(appConfig.siteLanguage),
|
|
205
|
-
...(fontTheme ? getFontThemeCssVariables(fontTheme) : {}),
|
|
206
|
-
};
|
|
207
|
-
const themeCss = buildThemeStyle(
|
|
208
|
-
activeTheme,
|
|
209
|
-
appConfig.themeMode,
|
|
210
|
-
fontOverrides,
|
|
211
|
-
);
|
|
212
|
-
const appleTouchKey = allSettings.SITE_FAVICON_APPLE_TOUCH || "";
|
|
213
|
-
const exportService = createExportService(
|
|
214
|
-
runtime.services,
|
|
215
|
-
{
|
|
216
|
-
siteName: appConfig.siteName,
|
|
217
|
-
siteUrl: appConfig.siteUrl,
|
|
218
|
-
siteDescription: appConfig.siteDescription,
|
|
219
|
-
siteLanguage: appConfig.siteLanguage,
|
|
220
|
-
showJantBrandingOnHome: appConfig.showJantBrandingOnHome,
|
|
221
|
-
homeDefaultView: appConfig.homeDefaultView,
|
|
222
|
-
mainRssFeed: appConfig.mainRssFeed,
|
|
223
|
-
siteFooter: appConfig.siteFooter,
|
|
224
|
-
showHeaderAvatar: appConfig.showHeaderAvatar,
|
|
225
|
-
siteAvatarUrl: appConfig.siteAvatarUrl,
|
|
226
|
-
faviconIcoBase64: allSettings.SITE_FAVICON_ICO || undefined,
|
|
227
|
-
appleTouchIconStorageKey: appleTouchKey || undefined,
|
|
228
|
-
faviconVersion: appConfig.faviconVersion,
|
|
229
|
-
themeId: appConfig.themeId,
|
|
230
|
-
defaultThemeId: appConfig.defaultThemeId,
|
|
231
|
-
fontThemeId: appConfig.fontThemeId,
|
|
232
|
-
themeMode: appConfig.themeMode,
|
|
233
|
-
noindex: appConfig.noindex,
|
|
234
|
-
themeCss,
|
|
235
|
-
customCss: appConfig.customCSS,
|
|
236
|
-
r2PublicUrl: appConfig.r2PublicUrl,
|
|
237
|
-
s3PublicUrl: appConfig.s3PublicUrl,
|
|
238
|
-
localPublicUrl: appConfig.localPublicUrl,
|
|
239
|
-
imageTransformUrl: appConfig.imageTransformUrl,
|
|
240
|
-
sitePathPrefix: appConfig.sitePathPrefix,
|
|
241
|
-
navItems,
|
|
242
|
-
pageSize: appConfig.pageSize,
|
|
243
|
-
archivePageSize: appConfig.archivePageSize,
|
|
244
|
-
rssFeedLimit: appConfig.rssFeedLimit,
|
|
245
|
-
},
|
|
246
|
-
{
|
|
247
|
-
storage: runtime.storage,
|
|
248
|
-
},
|
|
249
|
-
);
|
|
250
|
-
|
|
251
|
-
return {
|
|
252
|
-
zip: await exportService.generateHugoSite(),
|
|
253
|
-
assetLoader: createLocalAssetLoader(runtime.storage, appConfig),
|
|
254
|
-
source: describeLocalExportSource({
|
|
255
|
-
site: runtime.currentSite,
|
|
256
|
-
siteDomain: runtime.currentSiteDomain,
|
|
257
|
-
siteUrl: appConfig.siteUrl,
|
|
258
|
-
}),
|
|
259
|
-
};
|
|
260
|
-
} finally {
|
|
261
|
-
await nodeDatabase.close();
|
|
262
|
-
}
|
|
72
|
+
function printUsage() {
|
|
73
|
+
console.log("Usage: jant site export <url> [options]");
|
|
74
|
+
console.log("");
|
|
75
|
+
console.log("Export a Jant site as a Hugo ZIP archive or directory.");
|
|
76
|
+
console.log("");
|
|
77
|
+
console.log("Arguments:");
|
|
78
|
+
console.log(" <url> Jant site URL (required)");
|
|
79
|
+
console.log("");
|
|
80
|
+
console.log("Options:");
|
|
81
|
+
console.log(
|
|
82
|
+
" --output, -o Output ZIP path (default: jant-site-export.zip)",
|
|
83
|
+
);
|
|
84
|
+
console.log(
|
|
85
|
+
" --directory, -d Export directly to a directory for hugo serve/debugging",
|
|
86
|
+
);
|
|
87
|
+
console.log(
|
|
88
|
+
" --pull-media Download referenced media into static/media/ (default: on)",
|
|
89
|
+
);
|
|
90
|
+
console.log(" --no-pull-media Skip the media pull and keep original URLs");
|
|
91
|
+
console.log(" --token API token (overrides JANT_API_TOKEN)");
|
|
92
|
+
console.log("");
|
|
93
|
+
console.log("Authentication:");
|
|
94
|
+
console.log(` export ${CLI_API_TOKEN_ENV_VAR}=jnt_your_token`);
|
|
95
|
+
console.log(" jant site export https://your-site.example");
|
|
96
|
+
console.log("");
|
|
97
|
+
console.log("Examples:");
|
|
98
|
+
console.log(
|
|
99
|
+
" jant site export https://your-site.example -o ./export.zip",
|
|
100
|
+
);
|
|
101
|
+
console.log(
|
|
102
|
+
" jant site export https://your-site.example -d ./jant-site && cd ./jant-site && hugo serve",
|
|
103
|
+
);
|
|
263
104
|
}
|
|
264
105
|
|
|
265
106
|
export async function run(argv) {
|
|
266
107
|
const noPullMedia = argv.includes("--no-pull-media");
|
|
267
|
-
const
|
|
268
|
-
|
|
108
|
+
const filteredArgv = argv.filter((arg) => arg !== "--no-pull-media");
|
|
109
|
+
const { values, positionals } = parseArgs({
|
|
110
|
+
args: filteredArgv,
|
|
111
|
+
allowPositionals: true,
|
|
269
112
|
options: {
|
|
270
113
|
directory: {
|
|
271
114
|
type: "string",
|
|
@@ -279,49 +122,28 @@ export async function run(argv) {
|
|
|
279
122
|
default: "jant-site-export.zip",
|
|
280
123
|
},
|
|
281
124
|
token: { type: "string" },
|
|
282
|
-
url: { type: "string" },
|
|
283
125
|
},
|
|
284
126
|
});
|
|
285
127
|
|
|
286
128
|
if (values.help) {
|
|
287
|
-
|
|
288
|
-
console.log("");
|
|
289
|
-
console.log("Export a Jant site as a Hugo ZIP archive or directory.");
|
|
290
|
-
console.log("");
|
|
291
|
-
console.log("Modes:");
|
|
292
|
-
console.log(
|
|
293
|
-
" Local No --url; exports from the local Node database runtime",
|
|
294
|
-
);
|
|
295
|
-
console.log(
|
|
296
|
-
` Remote --url requires ${CLI_API_TOKEN_ENV_VAR} or --token`,
|
|
297
|
-
);
|
|
298
|
-
console.log("");
|
|
299
|
-
console.log("Options:");
|
|
300
|
-
console.log(" --url Remote Jant site URL");
|
|
301
|
-
console.log(
|
|
302
|
-
" --output, -o Output ZIP path (default: jant-site-export.zip)",
|
|
303
|
-
);
|
|
304
|
-
console.log(
|
|
305
|
-
" --directory, -d Export directly to a directory for hugo serve/debugging",
|
|
306
|
-
);
|
|
307
|
-
console.log(
|
|
308
|
-
" --pull-media Download referenced media into static/media/ (default: on)",
|
|
309
|
-
);
|
|
310
|
-
console.log(
|
|
311
|
-
" --no-pull-media Skip the media pull and keep original URLs",
|
|
312
|
-
);
|
|
313
|
-
console.log(" --token API token for remote export");
|
|
314
|
-
console.log("");
|
|
315
|
-
console.log("Authentication:");
|
|
316
|
-
console.log(` export ${CLI_API_TOKEN_ENV_VAR}=jnt_your_token`);
|
|
317
|
-
console.log(" jant site export --url https://your-site.com");
|
|
318
|
-
console.log("");
|
|
319
|
-
console.log("Examples:");
|
|
320
|
-
console.log(" jant site export --directory ./jant-site");
|
|
321
|
-
console.log(" cd ./jant-site && hugo serve");
|
|
129
|
+
printUsage();
|
|
322
130
|
process.exit(0);
|
|
323
131
|
}
|
|
324
132
|
|
|
133
|
+
const url = positionals[0];
|
|
134
|
+
if (!url) {
|
|
135
|
+
console.error("Error: site URL is required");
|
|
136
|
+
console.error("");
|
|
137
|
+
printUsage();
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
if (positionals.length > 1) {
|
|
141
|
+
console.error(
|
|
142
|
+
`Error: unexpected extra arguments: ${positionals.slice(1).join(" ")}`,
|
|
143
|
+
);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
|
|
325
147
|
if (values.directory && values.output !== "jant-site-export.zip") {
|
|
326
148
|
console.error("Error: use either --output or --directory, not both");
|
|
327
149
|
process.exit(1);
|
|
@@ -334,40 +156,29 @@ export async function run(argv) {
|
|
|
334
156
|
const token = getCliApiToken(process.env, values.token);
|
|
335
157
|
const pullMedia = values["pull-media"] ?? !noPullMedia;
|
|
336
158
|
|
|
337
|
-
if (
|
|
159
|
+
if (!token) {
|
|
338
160
|
console.error(
|
|
339
|
-
`Error:
|
|
161
|
+
`Error: site export requires ${CLI_API_TOKEN_ENV_VAR} or --token`,
|
|
340
162
|
);
|
|
341
163
|
process.exit(1);
|
|
342
164
|
}
|
|
343
165
|
|
|
344
|
-
console.log(
|
|
345
|
-
values.url
|
|
346
|
-
? `Exporting site from ${values.url}...`
|
|
347
|
-
: "Exporting site from the local runtime...",
|
|
348
|
-
);
|
|
166
|
+
console.log(`Exporting site from ${url}...`);
|
|
349
167
|
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
zip: await exportRemoteSite(values.url, token),
|
|
353
|
-
assetLoader: null,
|
|
354
|
-
source: values.url,
|
|
355
|
-
}
|
|
356
|
-
: await exportLocalSite(process.env);
|
|
357
|
-
let zip = exported.zip;
|
|
168
|
+
const zipBytes = await exportRemoteSite(url, token);
|
|
169
|
+
let zip = zipBytes;
|
|
358
170
|
let pullStats = null;
|
|
359
171
|
|
|
360
172
|
if (pullMedia) {
|
|
361
173
|
console.log("Preparing pull-media export ZIP...");
|
|
362
174
|
const pulled = await pullSiteExportZipBytes(zip, {
|
|
363
|
-
assetLoader:
|
|
175
|
+
assetLoader: null,
|
|
364
176
|
logger: logPullProgress,
|
|
365
177
|
});
|
|
366
178
|
zip = pulled.zipBytes;
|
|
367
179
|
pullStats = pulled.stats;
|
|
368
180
|
}
|
|
369
181
|
|
|
370
|
-
const source = exported.source;
|
|
371
182
|
if (outputDirectory) {
|
|
372
183
|
let existingEntries = [];
|
|
373
184
|
try {
|
|
@@ -393,12 +204,12 @@ export async function run(argv) {
|
|
|
393
204
|
mkdirSync(dirname(fullPath), { recursive: true });
|
|
394
205
|
writeFileSync(fullPath, Buffer.from(bytes));
|
|
395
206
|
}
|
|
396
|
-
console.log(`Exported site from ${
|
|
207
|
+
console.log(`Exported site from ${url} to ${values.directory}`);
|
|
397
208
|
console.log(`Preview with: cd ${values.directory} && hugo serve`);
|
|
398
209
|
} else {
|
|
399
210
|
console.log(`Writing ${values.output}...`);
|
|
400
211
|
writeFileSync(output, Buffer.from(zip));
|
|
401
|
-
console.log(`Exported site from ${
|
|
212
|
+
console.log(`Exported site from ${url} to ${values.output}`);
|
|
402
213
|
}
|
|
403
214
|
|
|
404
215
|
if (pullStats) {
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
readFile,
|
|
6
6
|
readdir,
|
|
7
7
|
rm,
|
|
8
|
-
stat,
|
|
9
8
|
writeFile,
|
|
10
9
|
} from "node:fs/promises";
|
|
11
10
|
import { tmpdir } from "node:os";
|
|
@@ -24,15 +23,14 @@ import {
|
|
|
24
23
|
buildSnapshotStorageQuery,
|
|
25
24
|
collectSnapshotObjects,
|
|
26
25
|
getSnapshotSelectSql,
|
|
27
|
-
sha256File,
|
|
28
26
|
SNAPSHOT_TABLES,
|
|
29
27
|
snapshotObjectPath,
|
|
30
28
|
} from "../../../lib/site-snapshot.js";
|
|
31
29
|
import { resolveCliSite } from "../../../lib/site-selection.js";
|
|
32
30
|
import { dumpDatabaseToSql } from "../../../lib/sql-export.js";
|
|
33
31
|
import {
|
|
32
|
+
bootstrapCliRuntime,
|
|
34
33
|
getCliRuntimeLabel,
|
|
35
|
-
resolveCliRuntime,
|
|
36
34
|
} from "../../../lib/runtime-target.js";
|
|
37
35
|
import { resolveWranglerVarString } from "../../../lib/wrangler-config.js";
|
|
38
36
|
|
|
@@ -99,8 +97,12 @@ async function assertWritableOutput(outputPath, force) {
|
|
|
99
97
|
|
|
100
98
|
async function createNodeExportContext() {
|
|
101
99
|
const nodeDatabase = await openNodeDatabase(process.env);
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
// Only need the storage driver — `createNodeCliRuntime` would also resolve
|
|
101
|
+
// the current site, which (a) is redundant with the bin-level resolveCliSite
|
|
102
|
+
// call below and (b) prints a generic "/setup first" error when the
|
|
103
|
+
// snapshot's own error path is more informative.
|
|
104
|
+
const { createStorageDriver } = await loadNodeRuntime();
|
|
105
|
+
const storage = createStorageDriver(nodeDatabase.bindings);
|
|
104
106
|
|
|
105
107
|
return {
|
|
106
108
|
dialect: nodeDatabase.database.dialect,
|
|
@@ -111,11 +113,11 @@ async function createNodeExportContext() {
|
|
|
111
113
|
return nodeDatabase.query(sql);
|
|
112
114
|
},
|
|
113
115
|
async downloadObject(key, filePath) {
|
|
114
|
-
if (!
|
|
116
|
+
if (!storage) {
|
|
115
117
|
throw new Error("Snapshot export requires configured storage.");
|
|
116
118
|
}
|
|
117
119
|
|
|
118
|
-
const object = await
|
|
120
|
+
const object = await storage.get(key);
|
|
119
121
|
if (!object?.body) {
|
|
120
122
|
throw new Error(`Storage object not found: ${key}`);
|
|
121
123
|
}
|
|
@@ -178,6 +180,7 @@ export async function run(argv) {
|
|
|
178
180
|
host: { type: "string" },
|
|
179
181
|
help: { type: "boolean", short: "h" },
|
|
180
182
|
local: { type: "boolean", default: false },
|
|
183
|
+
node: { type: "boolean", default: false },
|
|
181
184
|
output: {
|
|
182
185
|
type: "string",
|
|
183
186
|
short: "o",
|
|
@@ -187,13 +190,14 @@ export async function run(argv) {
|
|
|
187
190
|
"persist-to": { type: "string" },
|
|
188
191
|
remote: { type: "boolean", default: false },
|
|
189
192
|
site: { type: "string" },
|
|
193
|
+
"skip-objects": { type: "boolean", default: false },
|
|
190
194
|
url: { type: "string" },
|
|
191
195
|
},
|
|
192
196
|
});
|
|
193
197
|
|
|
194
198
|
if (values.help) {
|
|
195
199
|
console.log(
|
|
196
|
-
"Usage: jant site snapshot export [--local | --remote] [--output <dir|zip>]",
|
|
200
|
+
"Usage: jant site snapshot export [--local | --remote | --node] [--output <dir|zip>]",
|
|
197
201
|
);
|
|
198
202
|
console.log("");
|
|
199
203
|
console.log(
|
|
@@ -205,6 +209,9 @@ export async function run(argv) {
|
|
|
205
209
|
" --local Force local D1 instead of DATABASE_URL",
|
|
206
210
|
);
|
|
207
211
|
console.log(" --remote Export from remote D1");
|
|
212
|
+
console.log(
|
|
213
|
+
" --node Force Node runtime even if DATABASE_URL is unset",
|
|
214
|
+
);
|
|
208
215
|
console.log(
|
|
209
216
|
" --output, -o Output directory or .zip file (default: jant-site-snapshot)",
|
|
210
217
|
);
|
|
@@ -227,14 +234,33 @@ export async function run(argv) {
|
|
|
227
234
|
console.log(
|
|
228
235
|
" --persist-to Local D1/R2 state directory override",
|
|
229
236
|
);
|
|
237
|
+
console.log(
|
|
238
|
+
" --skip-objects Skip downloading storage objects. The archive only contains meta.json and db.sql.",
|
|
239
|
+
);
|
|
240
|
+
console.log(
|
|
241
|
+
" Only safe when the import target's storage already has the same keys",
|
|
242
|
+
);
|
|
243
|
+
console.log(
|
|
244
|
+
" (e.g. moving between Workers that share an R2 bucket). Otherwise the",
|
|
245
|
+
);
|
|
246
|
+
console.log(
|
|
247
|
+
" imported site will be missing media — pair with `--allow-missing-objects`",
|
|
248
|
+
);
|
|
249
|
+
console.log(" on import.");
|
|
230
250
|
console.log("");
|
|
231
251
|
console.log(
|
|
232
|
-
"
|
|
252
|
+
"`.env.node` next to your project (or in packages/core/) is auto-loaded.",
|
|
253
|
+
);
|
|
254
|
+
console.log(
|
|
255
|
+
"If DATABASE_URL or DATA_DIR is then set and no runtime flag is passed,",
|
|
256
|
+
);
|
|
257
|
+
console.log(
|
|
258
|
+
"this command uses the Node database runtime and configured storage driver.",
|
|
233
259
|
);
|
|
234
260
|
process.exit(0);
|
|
235
261
|
}
|
|
236
262
|
|
|
237
|
-
const runtime =
|
|
263
|
+
const { runtime } = bootstrapCliRuntime(values);
|
|
238
264
|
const outputPath = resolve(process.cwd(), values.output);
|
|
239
265
|
const shouldZip = isZipPath(outputPath);
|
|
240
266
|
const scratchDir = shouldZip
|
|
@@ -259,6 +285,7 @@ export async function run(argv) {
|
|
|
259
285
|
url: values.url,
|
|
260
286
|
});
|
|
261
287
|
|
|
288
|
+
console.log(`Dumping database (${SNAPSHOT_TABLES.length} tables)...`);
|
|
262
289
|
const dbSql = await dumpDatabaseToSql(
|
|
263
290
|
{
|
|
264
291
|
query(sql) {
|
|
@@ -275,55 +302,41 @@ export async function run(argv) {
|
|
|
275
302
|
getSnapshotSelectSql(tableName, site.id),
|
|
276
303
|
]),
|
|
277
304
|
),
|
|
305
|
+
onProgress: ({ index, total, table }) => {
|
|
306
|
+
console.log(` [${index}/${total}] ${table}`);
|
|
307
|
+
},
|
|
278
308
|
},
|
|
279
309
|
);
|
|
280
310
|
|
|
311
|
+
console.log("Listing storage objects...");
|
|
281
312
|
const objectRows = await context.query(buildSnapshotStorageQuery(site.id));
|
|
282
313
|
const objects = collectSnapshotObjects(objectRows);
|
|
283
|
-
const manifestObjects = [];
|
|
284
314
|
|
|
285
315
|
await writeFile(join(scratchDir, "db.sql"), dbSql);
|
|
286
316
|
|
|
287
|
-
if (objects
|
|
288
|
-
|
|
289
|
-
|
|
317
|
+
if (values["skip-objects"]) {
|
|
318
|
+
if (objects.length > 0) {
|
|
319
|
+
console.log(
|
|
320
|
+
`--skip-objects: leaving ${objects.length} referenced object(s) out of the archive.`,
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
if (objects.length > 0) {
|
|
325
|
+
console.log(`Downloading ${objects.length} referenced object(s)...`);
|
|
326
|
+
}
|
|
290
327
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
manifestObjects.push({
|
|
298
|
-
key: object.key,
|
|
299
|
-
file: relativeObjectPath,
|
|
300
|
-
contentType: object.contentType || undefined,
|
|
301
|
-
size: fileStat.size,
|
|
302
|
-
sha256: await sha256File(absoluteObjectPath),
|
|
303
|
-
});
|
|
328
|
+
for (const [index, object] of objects.entries()) {
|
|
329
|
+
const relativeObjectPath = snapshotObjectPath(object.key);
|
|
330
|
+
const absoluteObjectPath = join(scratchDir, relativeObjectPath);
|
|
331
|
+
console.log(`[${index + 1}/${objects.length}] ${object.key}`);
|
|
332
|
+
await context.downloadObject(object.key, absoluteObjectPath);
|
|
333
|
+
}
|
|
304
334
|
}
|
|
305
335
|
|
|
306
336
|
await writeFile(
|
|
307
337
|
join(scratchDir, "meta.json"),
|
|
308
338
|
JSON.stringify(
|
|
309
|
-
buildSnapshotMeta(
|
|
310
|
-
{
|
|
311
|
-
runtime,
|
|
312
|
-
label: getCliRuntimeLabel(runtime),
|
|
313
|
-
},
|
|
314
|
-
site,
|
|
315
|
-
),
|
|
316
|
-
null,
|
|
317
|
-
2,
|
|
318
|
-
) + "\n",
|
|
319
|
-
);
|
|
320
|
-
await writeFile(
|
|
321
|
-
join(scratchDir, "storage-manifest.json"),
|
|
322
|
-
JSON.stringify(
|
|
323
|
-
{
|
|
324
|
-
version: 1,
|
|
325
|
-
objects: manifestObjects,
|
|
326
|
-
},
|
|
339
|
+
buildSnapshotMeta(site, { dialect: context.dialect }),
|
|
327
340
|
null,
|
|
328
341
|
2,
|
|
329
342
|
) + "\n",
|