@velastack/cms 0.6.1 → 0.6.3
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/dist/backend/factory.js +2 -2
- package/dist/backend/routes/test-reset.d.ts +5 -2
- package/dist/backend/routes/test-reset.js +15 -16
- package/dist/backend/store/db.js +2 -2
- package/dist/backend/store/queries.d.ts +1 -1
- package/dist/backend/store/queries.js +10 -2
- package/dist/backend/store/sqlite.d.ts +12 -0
- package/dist/backend/store/sqlite.js +24 -0
- package/dist/server/load-cms.js +20 -2
- package/package.json +1 -1
package/dist/backend/factory.js
CHANGED
|
@@ -214,9 +214,9 @@ export const createCmsBackend = (options = {}) => {
|
|
|
214
214
|
if (!testReset) {
|
|
215
215
|
throw new Error('createCmsBackend({ testReset: true }) is required to call reset()');
|
|
216
216
|
}
|
|
217
|
-
store.__resetProjectForTests(projectId);
|
|
217
|
+
const filenames = store.__resetProjectForTests(projectId);
|
|
218
218
|
cache.clear();
|
|
219
|
-
await clearUploads(uploadDir);
|
|
219
|
+
await clearUploads(uploadDir, filenames);
|
|
220
220
|
const { layouts, pages } = normalizeSeed(seed);
|
|
221
221
|
if (Object.keys(layouts).length > 0 || Object.keys(pages).length > 0) {
|
|
222
222
|
store.seedPublishedDocs(projectId, layouts, pages);
|
|
@@ -10,6 +10,9 @@ export declare const normalizeSeed: (seed: SeedInput) => {
|
|
|
10
10
|
layouts: Record<string, Record<string, Tree>>;
|
|
11
11
|
pages: Record<string, Record<string, PageEntry[]>>;
|
|
12
12
|
};
|
|
13
|
-
/**
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Unlink the given uploads (the reset project's media files), skipping any name
|
|
15
|
+
* this backend could not have generated. A file already gone is not an error.
|
|
16
|
+
*/
|
|
17
|
+
export declare const clearUploads: (uploadDir: string, filenames: string[]) => Promise<void>;
|
|
15
18
|
export declare const postTestReset: (ctx: RouteCtx) => Promise<Response>;
|
|
@@ -8,11 +8,14 @@
|
|
|
8
8
|
*
|
|
9
9
|
* One deliberate change from the original: that version did
|
|
10
10
|
* `rm(uploadDir(), { recursive: true, force: true })` — a recursive delete of a
|
|
11
|
-
* host-configured directory, on an unauthenticated POST. Here only files
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* host-configured directory, on an unauthenticated POST. Here only the files of
|
|
12
|
+
* the project's own media rows are unlinked, and never the directory itself, so
|
|
13
|
+
* a misconfigured `uploadDir` cannot be destroyed and a reset of one project
|
|
14
|
+
* cannot delete another's uploads (several projects, or several test files
|
|
15
|
+
* running in parallel, share one `uploadDir`).
|
|
14
16
|
*/
|
|
15
|
-
import {
|
|
17
|
+
import { unlink } from 'node:fs/promises';
|
|
18
|
+
import { join } from 'node:path';
|
|
16
19
|
import { DEFAULT_LOCALE } from '../store/queries.js';
|
|
17
20
|
import { isSafeUploadFilename } from '../storage.js';
|
|
18
21
|
import { badRequest, isObjectRecord, readJson } from './context.js';
|
|
@@ -86,24 +89,20 @@ export const normalizeSeed = (seed) => {
|
|
|
86
89
|
}
|
|
87
90
|
return { layouts, pages };
|
|
88
91
|
};
|
|
89
|
-
/**
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
await Promise.all(names.filter(isSafeUploadFilename).map((n) => unlink(`${uploadDir}/${n}`).catch(() => { })));
|
|
92
|
+
/**
|
|
93
|
+
* Unlink the given uploads (the reset project's media files), skipping any name
|
|
94
|
+
* this backend could not have generated. A file already gone is not an error.
|
|
95
|
+
*/
|
|
96
|
+
export const clearUploads = async (uploadDir, filenames) => {
|
|
97
|
+
await Promise.all(filenames.filter(isSafeUploadFilename).map((n) => unlink(join(uploadDir, n)).catch(() => { })));
|
|
99
98
|
};
|
|
100
99
|
export const postTestReset = async (ctx) => {
|
|
101
100
|
const body = await readJson(ctx.event);
|
|
102
101
|
if (body !== undefined && !isObjectRecord(body))
|
|
103
102
|
return badRequest('invalid json');
|
|
104
|
-
ctx.store.__resetProjectForTests(ctx.projectId);
|
|
103
|
+
const filenames = ctx.store.__resetProjectForTests(ctx.projectId);
|
|
105
104
|
ctx.cache.clear();
|
|
106
|
-
await clearUploads(ctx.storage.dir());
|
|
105
|
+
await clearUploads(ctx.storage.dir(), filenames);
|
|
107
106
|
const { layouts, pages } = normalizeSeed((body ?? {}));
|
|
108
107
|
if (Object.keys(layouts).length > 0 || Object.keys(pages).length > 0) {
|
|
109
108
|
ctx.store.seedPublishedDocs(ctx.projectId, layouts, pages);
|
package/dist/backend/store/db.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { dirname } from 'node:path';
|
|
2
2
|
import { mkdirSync } from 'node:fs';
|
|
3
|
-
import { loadDriver } from './sqlite.js';
|
|
3
|
+
import { loadDriver, withoutReportNetwork } from './sqlite.js';
|
|
4
4
|
import { runMigrations } from './migrate.js';
|
|
5
5
|
import { MIGRATIONS } from './migrations/index.js';
|
|
6
6
|
/**
|
|
@@ -16,7 +16,7 @@ export const openDatabase = (dbPath) => {
|
|
|
16
16
|
if (dbPath !== ':memory:') {
|
|
17
17
|
mkdirSync(dirname(dbPath), { recursive: true });
|
|
18
18
|
}
|
|
19
|
-
const db = new Driver(dbPath);
|
|
19
|
+
const db = withoutReportNetwork(() => new Driver(dbPath));
|
|
20
20
|
db.pragma('journal_mode = WAL');
|
|
21
21
|
db.pragma('synchronous = NORMAL');
|
|
22
22
|
db.pragma('cache_size = -65536');
|
|
@@ -21,7 +21,7 @@ export declare const generatePreviewKey: () => string;
|
|
|
21
21
|
* The body is otherwise a verbatim move.
|
|
22
22
|
*/
|
|
23
23
|
export declare const createStore: (db: SqliteDb) => {
|
|
24
|
-
__resetProjectForTests: (projectId: string) =>
|
|
24
|
+
__resetProjectForTests: (projectId: string) => string[];
|
|
25
25
|
addReleaseItems: (projectId: string, userId: string, inputs: AddReleaseItemInput[]) => OpenRelease;
|
|
26
26
|
allPageRouteIds: (projectId: string) => string[];
|
|
27
27
|
buildStateAtRelease: (projectId: string, releaseId: string) => {
|
|
@@ -1138,18 +1138,26 @@ export const createStore = (db) => {
|
|
|
1138
1138
|
});
|
|
1139
1139
|
};
|
|
1140
1140
|
let _truncators = null;
|
|
1141
|
+
const mediaFilenamesStmt = () => db.prepare('SELECT filename FROM media_items WHERE project_id = ?');
|
|
1141
1142
|
/** Wipe every CMS table for one project. Used by `__test_reset__`.
|
|
1142
1143
|
* Also clears the in-process LRU — without this, the next test's first publish
|
|
1143
1144
|
* would re-mint `cms_version=1` and collide with cached entries from the
|
|
1144
|
-
* previous test run (production never resets, so version is monotonic).
|
|
1145
|
+
* previous test run (production never resets, so version is monotonic).
|
|
1146
|
+
*
|
|
1147
|
+
* Returns the filenames of the project's media rows it deleted, so the caller
|
|
1148
|
+
* can unlink exactly those files and leave other projects' uploads alone. */
|
|
1145
1149
|
const __resetProjectForTests = (projectId) => {
|
|
1146
1150
|
if (!_truncators)
|
|
1147
1151
|
_truncators = truncateProjectStmts();
|
|
1148
1152
|
const tx = db.transaction(() => {
|
|
1153
|
+
const filenames = mediaFilenamesStmt()
|
|
1154
|
+
.all(projectId)
|
|
1155
|
+
.map((r) => r.filename);
|
|
1149
1156
|
for (const fn of _truncators)
|
|
1150
1157
|
fn(projectId);
|
|
1158
|
+
return filenames;
|
|
1151
1159
|
});
|
|
1152
|
-
tx();
|
|
1160
|
+
return tx();
|
|
1153
1161
|
};
|
|
1154
1162
|
/**
|
|
1155
1163
|
* Seed published page/layout content for a project. Bypasses the release
|
|
@@ -20,3 +20,15 @@ export type SqliteDriver = new (path: string) => SqliteDb;
|
|
|
20
20
|
* it — and never compiles a native module.
|
|
21
21
|
*/
|
|
22
22
|
export declare const loadDriver: () => SqliteDriver;
|
|
23
|
+
/**
|
|
24
|
+
* Run `fn` with Node's diagnostic report leaving out networking details.
|
|
25
|
+
*
|
|
26
|
+
* better-sqlite3 loads its native addon on the first `new Database`, and on
|
|
27
|
+
* Linux picks the prebuilt binary by calling `process.report.getReport()` to
|
|
28
|
+
* tell glibc from musl. The report's network section resolves the address of
|
|
29
|
+
* every open socket, synchronously, so in a server with live connections the
|
|
30
|
+
* first open could block the event loop for seconds (10s on a CI runner).
|
|
31
|
+
* Nothing in that check needs the network section. The setting is restored
|
|
32
|
+
* before returning; `fn` is synchronous, so nothing else sees it changed.
|
|
33
|
+
*/
|
|
34
|
+
export declare const withoutReportNetwork: <T>(fn: () => T) => T;
|
|
@@ -27,3 +27,27 @@ export const loadDriver = () => {
|
|
|
27
27
|
'dependency. Install it with `npm i better-sqlite3`.', { cause });
|
|
28
28
|
}
|
|
29
29
|
};
|
|
30
|
+
/**
|
|
31
|
+
* Run `fn` with Node's diagnostic report leaving out networking details.
|
|
32
|
+
*
|
|
33
|
+
* better-sqlite3 loads its native addon on the first `new Database`, and on
|
|
34
|
+
* Linux picks the prebuilt binary by calling `process.report.getReport()` to
|
|
35
|
+
* tell glibc from musl. The report's network section resolves the address of
|
|
36
|
+
* every open socket, synchronously, so in a server with live connections the
|
|
37
|
+
* first open could block the event loop for seconds (10s on a CI runner).
|
|
38
|
+
* Nothing in that check needs the network section. The setting is restored
|
|
39
|
+
* before returning; `fn` is synchronous, so nothing else sees it changed.
|
|
40
|
+
*/
|
|
41
|
+
export const withoutReportNetwork = (fn) => {
|
|
42
|
+
// `excludeNetwork` exists from Node 20.13 / 22.0; older versions skip this.
|
|
43
|
+
const report = process.report;
|
|
44
|
+
if (!report || typeof report.excludeNetwork !== 'boolean' || report.excludeNetwork)
|
|
45
|
+
return fn();
|
|
46
|
+
report.excludeNetwork = true;
|
|
47
|
+
try {
|
|
48
|
+
return fn();
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
report.excludeNetwork = false;
|
|
52
|
+
}
|
|
53
|
+
};
|
package/dist/server/load-cms.js
CHANGED
|
@@ -168,6 +168,24 @@ export const resolveCmsPayload = async (args) => {
|
|
|
168
168
|
redirectTo: null
|
|
169
169
|
};
|
|
170
170
|
};
|
|
171
|
+
/**
|
|
172
|
+
* A query parameter of the request, or null where the page cannot have one:
|
|
173
|
+
* while building, and on a prerendered page, where SvelteKit (3.0 in dev too,
|
|
174
|
+
* via `prerender_default`) replaces `url.search`/`url.searchParams` with a
|
|
175
|
+
* getter that throws `url_search_unavailable_prerender`. A prerendered page
|
|
176
|
+
* cannot vary by preview or version key, so null is the right answer, not an
|
|
177
|
+
* error that takes the whole page down in `vite dev`.
|
|
178
|
+
*/
|
|
179
|
+
const searchParam = (event, key) => {
|
|
180
|
+
if (building)
|
|
181
|
+
return null;
|
|
182
|
+
try {
|
|
183
|
+
return event.url.searchParams.get(key);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
171
189
|
/**
|
|
172
190
|
* Resolve the CMS payload for the current SvelteKit request.
|
|
173
191
|
*
|
|
@@ -217,8 +235,8 @@ export const loadCms = (event, options) => {
|
|
|
217
235
|
manifest: builtManifest,
|
|
218
236
|
routeId: event.route.id,
|
|
219
237
|
params: event.params,
|
|
220
|
-
previewKey:
|
|
221
|
-
versionKey:
|
|
238
|
+
previewKey: searchParam(event, 'preview'),
|
|
239
|
+
versionKey: searchParam(event, 'version'),
|
|
222
240
|
locale: options.locale,
|
|
223
241
|
locales: options.locales,
|
|
224
242
|
adapter: options.adapter,
|