@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
package/bin/lib/site-snapshot.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
3
|
|
|
4
4
|
export const SNAPSHOT_FORMAT = "jant-site-snapshot";
|
|
5
5
|
export const SNAPSHOT_VERSION = 1;
|
|
6
|
-
export const SNAPSHOT_SCOPE = "content";
|
|
7
6
|
|
|
8
7
|
export const SNAPSHOT_TABLES = [
|
|
9
8
|
"site_setting",
|
|
@@ -47,11 +46,6 @@ export const SNAPSHOT_SETTING_KEYS = [
|
|
|
47
46
|
"NOINDEX",
|
|
48
47
|
];
|
|
49
48
|
|
|
50
|
-
export const SNAPSHOT_STORAGE_SETTING_KEYS = [
|
|
51
|
-
"SITE_AVATAR",
|
|
52
|
-
"SITE_FAVICON_APPLE_TOUCH",
|
|
53
|
-
];
|
|
54
|
-
|
|
55
49
|
function escapeSqlString(value) {
|
|
56
50
|
return String(value).replaceAll("'", "''");
|
|
57
51
|
}
|
|
@@ -143,16 +137,6 @@ export function buildSnapshotStorageQuery(siteId) {
|
|
|
143
137
|
WHERE "poster_key" IS NOT NULL
|
|
144
138
|
AND "site_id" = '${escapeSqlString(siteId)}'
|
|
145
139
|
AND trim("poster_key") <> ''
|
|
146
|
-
|
|
147
|
-
UNION ALL
|
|
148
|
-
|
|
149
|
-
SELECT
|
|
150
|
-
"value" AS "key",
|
|
151
|
-
NULL AS "contentType"
|
|
152
|
-
FROM "site_setting"
|
|
153
|
-
WHERE "key" IN (${quoteList(SNAPSHOT_STORAGE_SETTING_KEYS)})
|
|
154
|
-
AND "site_id" = '${escapeSqlString(siteId)}'
|
|
155
|
-
AND trim("value") <> ''
|
|
156
140
|
)
|
|
157
141
|
WHERE "key" IS NOT NULL
|
|
158
142
|
AND trim("key") <> ''
|
|
@@ -191,19 +175,24 @@ export function snapshotObjectPath(key) {
|
|
|
191
175
|
return `objects/${key}`.replace(/\\/g, "/");
|
|
192
176
|
}
|
|
193
177
|
|
|
194
|
-
export
|
|
178
|
+
export const SNAPSHOT_DIALECTS = ["sqlite", "pg"];
|
|
179
|
+
|
|
180
|
+
export function buildSnapshotMeta(site, options = {}) {
|
|
181
|
+
const dialect = options.dialect;
|
|
182
|
+
if (dialect && !SNAPSHOT_DIALECTS.includes(dialect)) {
|
|
183
|
+
throw new Error(
|
|
184
|
+
`Unsupported snapshot dialect: ${dialect}. Expected one of ${SNAPSHOT_DIALECTS.join(", ")}.`,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
195
188
|
return {
|
|
196
189
|
format: SNAPSHOT_FORMAT,
|
|
197
190
|
version: SNAPSHOT_VERSION,
|
|
198
|
-
|
|
199
|
-
createdAt: new Date().toISOString(),
|
|
200
|
-
source,
|
|
191
|
+
...(dialect ? { dialect } : {}),
|
|
201
192
|
site: {
|
|
202
193
|
id: site.id,
|
|
203
194
|
key: site.key,
|
|
204
195
|
},
|
|
205
|
-
tables: SNAPSHOT_TABLES,
|
|
206
|
-
settingKeys: SNAPSHOT_SETTING_KEYS,
|
|
207
196
|
};
|
|
208
197
|
}
|
|
209
198
|
|
|
@@ -224,9 +213,12 @@ export function assertSnapshotMeta(meta) {
|
|
|
224
213
|
);
|
|
225
214
|
}
|
|
226
215
|
|
|
227
|
-
if (
|
|
216
|
+
if (
|
|
217
|
+
meta.dialect !== undefined &&
|
|
218
|
+
!SNAPSHOT_DIALECTS.includes(meta.dialect)
|
|
219
|
+
) {
|
|
228
220
|
throw new Error(
|
|
229
|
-
`
|
|
221
|
+
`Snapshot meta has unsupported dialect "${String(meta.dialect)}". Expected one of ${SNAPSHOT_DIALECTS.join(", ")}.`,
|
|
230
222
|
);
|
|
231
223
|
}
|
|
232
224
|
|
|
@@ -241,20 +233,39 @@ export function assertSnapshotMeta(meta) {
|
|
|
241
233
|
}
|
|
242
234
|
}
|
|
243
235
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
236
|
+
/**
|
|
237
|
+
* Read the snapshot's source dialect, if recorded.
|
|
238
|
+
*
|
|
239
|
+
* Older snapshots predate the `dialect` field — those return `undefined` and
|
|
240
|
+
* the caller decides whether to skip the check or refuse with a clear error.
|
|
241
|
+
*/
|
|
242
|
+
export function getSnapshotDialect(meta) {
|
|
243
|
+
return SNAPSHOT_DIALECTS.includes(meta?.dialect) ? meta.dialect : undefined;
|
|
244
|
+
}
|
|
248
245
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
246
|
+
/**
|
|
247
|
+
* Refuse to apply a snapshot whose source dialect doesn't match the target.
|
|
248
|
+
*
|
|
249
|
+
* Cross-dialect db.sql is not safe to replay: SQLite and Postgres differ on
|
|
250
|
+
* BLOB literals (`X'...'` vs `'\x...'`), boolean encoding (`0/1` vs `t/f`),
|
|
251
|
+
* `tsvector`/`generated` columns, identifier quoting edge cases, etc. Better
|
|
252
|
+
* to fail at the start of import than mid-way with a cryptic SQL error.
|
|
253
|
+
*/
|
|
254
|
+
export function assertSnapshotDialectMatches(meta, targetDialect) {
|
|
255
|
+
const sourceDialect = getSnapshotDialect(meta);
|
|
256
|
+
if (!sourceDialect) {
|
|
257
|
+
return;
|
|
253
258
|
}
|
|
254
259
|
|
|
255
|
-
if (
|
|
260
|
+
if (sourceDialect !== targetDialect) {
|
|
256
261
|
throw new Error(
|
|
257
|
-
|
|
262
|
+
[
|
|
263
|
+
`Snapshot dialect mismatch: source is ${sourceDialect}, target is ${targetDialect}.`,
|
|
264
|
+
"Snapshot db.sql is dialect-specific (BLOB literals, generated columns, FTS, etc.)",
|
|
265
|
+
"and cannot be replayed across SQLite and Postgres safely.",
|
|
266
|
+
"Use `jant site export <url>` (HTTP, dialect-neutral) to move content between",
|
|
267
|
+
"different DB engines.",
|
|
268
|
+
].join("\n"),
|
|
258
269
|
);
|
|
259
270
|
}
|
|
260
271
|
}
|
|
@@ -301,22 +312,55 @@ export function rewriteSnapshotSiteIdentifiers(
|
|
|
301
312
|
return sql.replaceAll(escapedSource, escapedTarget);
|
|
302
313
|
}
|
|
303
314
|
|
|
304
|
-
export function
|
|
305
|
-
manifest,
|
|
306
|
-
sourceSiteId,
|
|
307
|
-
targetSiteId,
|
|
308
|
-
) {
|
|
315
|
+
export function remapSnapshotObjectKey(key, sourceSiteId, targetSiteId) {
|
|
309
316
|
if (!sourceSiteId || sourceSiteId === targetSiteId) {
|
|
310
|
-
return
|
|
317
|
+
return key;
|
|
311
318
|
}
|
|
319
|
+
return String(key).replaceAll(sourceSiteId, targetSiteId);
|
|
320
|
+
}
|
|
312
321
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
322
|
+
/**
|
|
323
|
+
* Walks `<rootDir>/objects/` recursively and returns one entry per file.
|
|
324
|
+
*
|
|
325
|
+
* The relative path inside `objects/` is the storage key as it existed at
|
|
326
|
+
* export time (with forward slashes). If the snapshot was produced by a
|
|
327
|
+
* different site than the import target, callers apply
|
|
328
|
+
* `remapSnapshotObjectKey()` before uploading.
|
|
329
|
+
*/
|
|
330
|
+
export async function enumerateSnapshotObjectFiles(rootDir) {
|
|
331
|
+
const objectsRoot = join(rootDir, "objects");
|
|
332
|
+
const entries = [];
|
|
333
|
+
|
|
334
|
+
async function walk(dir) {
|
|
335
|
+
let items;
|
|
336
|
+
try {
|
|
337
|
+
items = await readdir(dir, { withFileTypes: true });
|
|
338
|
+
} catch (error) {
|
|
339
|
+
if (error && error.code === "ENOENT") {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
throw error;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
for (const item of items) {
|
|
346
|
+
const fullPath = join(dir, item.name);
|
|
347
|
+
if (item.isDirectory()) {
|
|
348
|
+
await walk(fullPath);
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const key = relative(objectsRoot, fullPath).replace(/\\/g, "/");
|
|
353
|
+
entries.push({
|
|
354
|
+
key,
|
|
355
|
+
filePath: fullPath,
|
|
356
|
+
contentType: guessContentTypeFromKey(key),
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
await walk(objectsRoot);
|
|
362
|
+
entries.sort((a, b) => a.key.localeCompare(b.key));
|
|
363
|
+
return entries;
|
|
320
364
|
}
|
|
321
365
|
|
|
322
366
|
function prependSiteIdInsert(sql, tableName, siteId) {
|
|
@@ -407,6 +451,98 @@ export function rewriteLegacySnapshotSql(sql, siteId) {
|
|
|
407
451
|
return `${rewrittenStatements.join(";\n")};\n`;
|
|
408
452
|
}
|
|
409
453
|
|
|
454
|
+
/**
|
|
455
|
+
* Pull the storage_key + poster_key values referenced by every media INSERT
|
|
456
|
+
* inside a snapshot's `db.sql`.
|
|
457
|
+
*
|
|
458
|
+
* The dump format is controlled by `dumpDatabaseToSql`, which produces
|
|
459
|
+
* `INSERT INTO "media" (col, ...) VALUES (val, ...);` statements with single
|
|
460
|
+
* quoted string literals. We use the SQL-aware splitter to chunk the dump,
|
|
461
|
+
* then parse each media INSERT via the column list. This is the import-side
|
|
462
|
+
* "what should be on storage after this snapshot lands" question, used by
|
|
463
|
+
* the preflight check that runs before db.sql is applied.
|
|
464
|
+
*/
|
|
465
|
+
export function extractMediaStorageKeysFromDumpSql(sql, sourceSiteId) {
|
|
466
|
+
const uncommented = sql
|
|
467
|
+
.split("\n")
|
|
468
|
+
.filter((line) => !line.trimStart().startsWith("--"))
|
|
469
|
+
.join("\n");
|
|
470
|
+
const statements = splitSqlStatements(uncommented);
|
|
471
|
+
const keys = new Set();
|
|
472
|
+
|
|
473
|
+
for (const statement of statements) {
|
|
474
|
+
const match = statement.match(
|
|
475
|
+
/INSERT\s+INTO\s+"?media"?\s*\(([^)]+)\)\s*VALUES\s*\(([\s\S]+)\)\s*;?\s*$/i,
|
|
476
|
+
);
|
|
477
|
+
if (!match) continue;
|
|
478
|
+
|
|
479
|
+
const colNames = match[1]
|
|
480
|
+
.split(",")
|
|
481
|
+
.map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
482
|
+
const values = parseSqlValueList(match[2]);
|
|
483
|
+
if (values.length !== colNames.length) continue;
|
|
484
|
+
|
|
485
|
+
if (sourceSiteId) {
|
|
486
|
+
const siteIdIdx = colNames.indexOf("site_id");
|
|
487
|
+
if (siteIdIdx >= 0) {
|
|
488
|
+
const siteIdVal = parseSqlScalar(values[siteIdIdx]);
|
|
489
|
+
if (siteIdVal !== sourceSiteId) continue;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
for (const col of ["storage_key", "poster_key"]) {
|
|
494
|
+
const idx = colNames.indexOf(col);
|
|
495
|
+
if (idx < 0) continue;
|
|
496
|
+
const value = parseSqlScalar(values[idx]);
|
|
497
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
498
|
+
keys.add(value);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return keys;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function parseSqlValueList(raw) {
|
|
507
|
+
const values = [];
|
|
508
|
+
let current = "";
|
|
509
|
+
let inString = false;
|
|
510
|
+
|
|
511
|
+
for (let index = 0; index < raw.length; index += 1) {
|
|
512
|
+
const char = raw[index];
|
|
513
|
+
if (char === "'") {
|
|
514
|
+
current += char;
|
|
515
|
+
if (inString && raw[index + 1] === "'") {
|
|
516
|
+
current += "'";
|
|
517
|
+
index += 1;
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
inString = !inString;
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
if (char === "," && !inString) {
|
|
524
|
+
values.push(current.trim());
|
|
525
|
+
current = "";
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
current += char;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const tail = current.trim();
|
|
532
|
+
if (tail) values.push(tail);
|
|
533
|
+
return values;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function parseSqlScalar(raw) {
|
|
537
|
+
if (raw === undefined) return null;
|
|
538
|
+
const trimmed = raw.trim();
|
|
539
|
+
if (trimmed === "" || /^null$/i.test(trimmed)) return null;
|
|
540
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
541
|
+
return trimmed.slice(1, -1).replaceAll("''", "'");
|
|
542
|
+
}
|
|
543
|
+
return trimmed;
|
|
544
|
+
}
|
|
545
|
+
|
|
410
546
|
export function buildReplaceSql(siteId) {
|
|
411
547
|
const statements = [];
|
|
412
548
|
|
|
@@ -431,11 +567,6 @@ export function normalizeD1Sql(sql) {
|
|
|
431
567
|
.trim();
|
|
432
568
|
}
|
|
433
569
|
|
|
434
|
-
export async function sha256File(filePath) {
|
|
435
|
-
const bytes = await readFile(filePath);
|
|
436
|
-
return createHash("sha256").update(bytes).digest("hex");
|
|
437
|
-
}
|
|
438
|
-
|
|
439
570
|
export function guessContentTypeFromKey(key) {
|
|
440
571
|
const normalized = String(key).toLowerCase();
|
|
441
572
|
|
package/bin/lib/sql-export.js
CHANGED
|
@@ -136,29 +136,41 @@ export async function getTableColumns(
|
|
|
136
136
|
dialect = "sqlite",
|
|
137
137
|
) {
|
|
138
138
|
if (dialect === "pg") {
|
|
139
|
+
// Skip GENERATED ALWAYS columns (e.g. post.search_text, post.search_document):
|
|
140
|
+
// Postgres rejects any explicit value — even `DEFAULT` — on those, so they
|
|
141
|
+
// must not appear in the INSERT column list. The target instance recomputes
|
|
142
|
+
// them from the source columns when the row is inserted.
|
|
139
143
|
const rows = await queryRunner.query(`
|
|
140
144
|
SELECT column_name AS name
|
|
141
145
|
FROM information_schema.columns
|
|
142
146
|
WHERE table_schema = 'public'
|
|
143
147
|
AND table_name = ${sqlValue(tableName)}
|
|
148
|
+
AND is_generated = 'NEVER'
|
|
144
149
|
ORDER BY ordinal_position
|
|
145
150
|
`);
|
|
146
151
|
|
|
147
152
|
return rows.map((row) => String(row.name));
|
|
148
153
|
}
|
|
149
154
|
|
|
155
|
+
// SQLite doesn't currently use generated columns in this codebase, but
|
|
156
|
+
// table_xinfo (a strict superset of table_info) exposes the `hidden`
|
|
157
|
+
// flag (2 = VIRTUAL generated, 3 = STORED generated) so we filter those
|
|
158
|
+
// out defensively if any are introduced later.
|
|
150
159
|
const rows = await queryRunner.query(
|
|
151
|
-
`PRAGMA
|
|
160
|
+
`PRAGMA table_xinfo(${quoteIdentifier(tableName)})`,
|
|
152
161
|
);
|
|
153
162
|
|
|
154
163
|
return rows
|
|
155
164
|
.slice()
|
|
156
165
|
.sort((left, right) => Number(left.cid) - Number(right.cid))
|
|
166
|
+
.filter((row) => Number(row.hidden) !== 2 && Number(row.hidden) !== 3)
|
|
157
167
|
.map((row) => String(row.name));
|
|
158
168
|
}
|
|
159
169
|
|
|
160
170
|
export async function dumpDatabaseToSql(queryRunner, options) {
|
|
161
171
|
const dialect = options.dialect ?? "sqlite";
|
|
172
|
+
const onProgress =
|
|
173
|
+
typeof options.onProgress === "function" ? options.onProgress : null;
|
|
162
174
|
const configuredTables = Array.isArray(options.tables)
|
|
163
175
|
? sortExportTables(options.tables)
|
|
164
176
|
: null;
|
|
@@ -169,7 +181,12 @@ export async function dumpDatabaseToSql(queryRunner, options) {
|
|
|
169
181
|
sql += `-- Exported: ${timestamp}\n`;
|
|
170
182
|
sql += `-- Source: ${options.source}\n\n`;
|
|
171
183
|
|
|
172
|
-
for (const tableName of tables) {
|
|
184
|
+
for (const [tableIndex, tableName] of tables.entries()) {
|
|
185
|
+
onProgress?.({
|
|
186
|
+
index: tableIndex + 1,
|
|
187
|
+
total: tables.length,
|
|
188
|
+
table: tableName,
|
|
189
|
+
});
|
|
173
190
|
const columnNames = await getTableColumns(queryRunner, tableName, dialect);
|
|
174
191
|
if (columnNames.length === 0) {
|
|
175
192
|
continue;
|