@velastack/cms 0.3.1 → 0.4.0
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/index.d.ts +1 -1
- package/dist/backend/routes/release.js +2 -2
- package/dist/backend/store/migrations/0004-release-publisher.d.ts +1 -0
- package/dist/backend/store/migrations/0004-release-publisher.js +15 -0
- package/dist/backend/store/migrations/index.js +3 -1
- package/dist/backend/store/queries.d.ts +3 -2
- package/dist/backend/store/queries.js +16 -11
- package/dist/components/admin-bar/history-panel.svelte +4 -5
- package/dist/core/wire.d.ts +11 -1
- package/package.json +4 -4
package/dist/backend/index.d.ts
CHANGED
|
@@ -49,6 +49,6 @@ export type { CmsStore } from './store/queries.js';
|
|
|
49
49
|
export type { SqliteDb, SqliteStatement } from './store/sqlite.js';
|
|
50
50
|
export type { Migration } from './store/migrations/index.js';
|
|
51
51
|
export { MIGRATIONS } from './store/migrations/index.js';
|
|
52
|
-
export type { DocsResponse, MediaItem, OpenRelease, PageMapEntry, PageMapRoute, PagesResponse, PagesRoute, PublishedRelease, ReleaseItem, SiteResponse } from '../core/wire.js';
|
|
52
|
+
export type { DocsResponse, MediaItem, OpenRelease, PageMapEntry, PageMapRoute, PagesResponse, PagesRoute, PublishedRelease, ReleaseItem, ReleasePublisher, SiteResponse } from '../core/wire.js';
|
|
53
53
|
export type { PageDeleteOutcome, PageEntry } from '../core/page-entry.js';
|
|
54
54
|
export type { Tree } from '../core/path.js';
|
|
@@ -101,7 +101,7 @@ export const postReleasePublish = async (ctx) => {
|
|
|
101
101
|
name = body.name;
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
-
const published = ctx.store.publishRelease(ctx.projectId, user
|
|
104
|
+
const published = ctx.store.publishRelease(ctx.projectId, user, name);
|
|
105
105
|
if (!published)
|
|
106
106
|
return new Response('no open release to publish', { status: 404 });
|
|
107
107
|
return Response.json({ release: published });
|
|
@@ -130,7 +130,7 @@ export const postReleaseRevert = (ctx) => {
|
|
|
130
130
|
const id = ctx.params.id;
|
|
131
131
|
if (!id)
|
|
132
132
|
return badRequest('id required');
|
|
133
|
-
const reverted = ctx.store.revertRelease(ctx.projectId, id, user
|
|
133
|
+
const reverted = ctx.store.revertRelease(ctx.projectId, id, user);
|
|
134
134
|
if (!reverted)
|
|
135
135
|
return new Response('release not found', { status: 404 });
|
|
136
136
|
return Response.json({ release: reverted });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const sql = "\nALTER TABLE releases ADD COLUMN published_by_email TEXT;\nALTER TABLE releases ADD COLUMN published_by_name TEXT;\nUPDATE releases SET\n published_by_email = (SELECT e.email FROM cms_editors e WHERE e.id = releases.published_by),\n published_by_name = (SELECT e.name FROM cms_editors e WHERE e.id = releases.published_by);\n";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Who published a release, as they were at the time.
|
|
2
|
+
//
|
|
3
|
+
// `published_by` is an opaque editor id, which is all the auth adapter
|
|
4
|
+
// promises to keep stable — and all the admin bar could show. The publisher's
|
|
5
|
+
// email and name are now captured on the row at publish time, so history reads
|
|
6
|
+
// the same under any adapter and survives the account being renamed or
|
|
7
|
+
// removed. Existing rows are filled from `cms_editors` where the id still
|
|
8
|
+
// resolves; the rest stay null and readers fall back to the id.
|
|
9
|
+
export const sql = `
|
|
10
|
+
ALTER TABLE releases ADD COLUMN published_by_email TEXT;
|
|
11
|
+
ALTER TABLE releases ADD COLUMN published_by_name TEXT;
|
|
12
|
+
UPDATE releases SET
|
|
13
|
+
published_by_email = (SELECT e.email FROM cms_editors e WHERE e.id = releases.published_by),
|
|
14
|
+
published_by_name = (SELECT e.name FROM cms_editors e WHERE e.id = releases.published_by);
|
|
15
|
+
`;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { sql as init } from './0001-init.js';
|
|
2
2
|
import { sql as site } from './0002-site.js';
|
|
3
3
|
import { sql as editors } from './0003-editors.js';
|
|
4
|
+
import { sql as releasePublisher } from './0004-release-publisher.js';
|
|
4
5
|
/**
|
|
5
6
|
* Every migration, in apply order.
|
|
6
7
|
*
|
|
@@ -14,5 +15,6 @@ import { sql as editors } from './0003-editors.js';
|
|
|
14
15
|
export const MIGRATIONS = [
|
|
15
16
|
{ name: '0001_init.sql', sql: init },
|
|
16
17
|
{ name: '0002_site.sql', sql: site },
|
|
17
|
-
{ name: '0003_editors.sql', sql: editors }
|
|
18
|
+
{ name: '0003_editors.sql', sql: editors },
|
|
19
|
+
{ name: '0004_release_publisher.sql', sql: releasePublisher }
|
|
18
20
|
];
|
|
@@ -2,6 +2,7 @@ import { type PageEntry, type PageDeleteOutcome } from '../../core/page-entry.js
|
|
|
2
2
|
import { type Tree } from '../../core/path.js';
|
|
3
3
|
import type { AddReleaseItemInput, CreatePageResult, DiscardItemTarget, ListMediaOptions, ListMediaResult, MediaItem, OpenRelease, PageMapRoute, PublishedRelease, ReleaseItem, RenamePageResult, StagePageDeleteResult } from '../../core/wire.js';
|
|
4
4
|
import type { SqliteDb } from './sqlite.js';
|
|
5
|
+
import type { CmsEditor } from '../types.js';
|
|
5
6
|
/** The locale a request falls back to when it names none. */
|
|
6
7
|
export declare const DEFAULT_LOCALE = "en";
|
|
7
8
|
/** An opaque, unguessable key for a release's preview URL. */
|
|
@@ -50,7 +51,7 @@ export declare const createStore: (db: SqliteDb) => {
|
|
|
50
51
|
id: string;
|
|
51
52
|
items: ReleaseItem[];
|
|
52
53
|
} | null;
|
|
53
|
-
publishRelease: (projectId: string,
|
|
54
|
+
publishRelease: (projectId: string, user: CmsEditor, name?: string) => PublishedRelease | null;
|
|
54
55
|
regeneratePreviewKey: (projectId: string, userId: string) => string | null;
|
|
55
56
|
regeneratePublishedPreviewKey: (projectId: string, releaseId: string) => string | null;
|
|
56
57
|
renamePage: (projectId: string, userId: string, args: {
|
|
@@ -60,7 +61,7 @@ export declare const createStore: (db: SqliteDb) => {
|
|
|
60
61
|
toParams: Record<string, string>;
|
|
61
62
|
toUrl: string;
|
|
62
63
|
}) => RenamePageResult;
|
|
63
|
-
revertRelease: (projectId: string, releaseId: string,
|
|
64
|
+
revertRelease: (projectId: string, releaseId: string, user: CmsEditor) => PublishedRelease | null;
|
|
64
65
|
seedPublishedDocs: (projectId: string, layouts: Record<string, Record<string, Tree>>, pages: Record<string, Record<string, PageEntry[]>>) => void;
|
|
65
66
|
stagePageDelete: (projectId: string, userId: string, routeId: string, locale: string, params: Record<string, string>, outcome?: PageDeleteOutcome) => StagePageDeleteResult;
|
|
66
67
|
};
|
|
@@ -517,7 +517,7 @@ export const createStore = (db) => {
|
|
|
517
517
|
updated_by_release = excluded.updated_by_release`);
|
|
518
518
|
const updatePublishedPageTombstoneStmt = () => db.prepare('UPDATE published_pages SET tombstone = ?, updated_at = ?, updated_by_release = ? WHERE project_id = ? AND locale = ? AND route_id = ? AND params_hash = ?');
|
|
519
519
|
const deletePublishedPageStmt = () => db.prepare('DELETE FROM published_pages WHERE project_id = ? AND locale = ? AND route_id = ? AND params_hash = ?');
|
|
520
|
-
const insertReleaseStmt = () => db.prepare('INSERT INTO releases (id, project_id, seq, name, published_by, published_at, preview_key) VALUES (?, ?, ?, ?, ?, ?, ?)');
|
|
520
|
+
const insertReleaseStmt = () => db.prepare('INSERT INTO releases (id, project_id, seq, name, published_by, published_by_email, published_by_name, published_at, preview_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
|
521
521
|
const insertReleaseItemStmt = () => db.prepare('INSERT INTO release_items (release_id, project_id, seq, kind, route_id, locale, params_hash, params, tree, prior_tree, outcome, prior_tombstone, added_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
|
522
522
|
const deleteOpenItemsForUserStmt = () => db.prepare('DELETE FROM open_release_items WHERE project_id = ? AND user_id = ?');
|
|
523
523
|
/**
|
|
@@ -527,7 +527,8 @@ export const createStore = (db) => {
|
|
|
527
527
|
* open release. Returns the new `PublishedRelease`, or null if there's nothing
|
|
528
528
|
* to publish.
|
|
529
529
|
*/
|
|
530
|
-
const publishRelease = (projectId,
|
|
530
|
+
const publishRelease = (projectId, user, name) => {
|
|
531
|
+
const userId = user.id;
|
|
531
532
|
const tx = db.transaction(() => {
|
|
532
533
|
ensureProjectState(projectId);
|
|
533
534
|
const releaseRow = getOpenReleaseStmt().get(projectId, userId);
|
|
@@ -546,7 +547,7 @@ export const createStore = (db) => {
|
|
|
546
547
|
// resolves on every per-item insert below. We delete the header at the
|
|
547
548
|
// end if the body turned out to be empty.
|
|
548
549
|
const finalName = name ?? releaseRow.name ?? null;
|
|
549
|
-
insertReleaseStmt().run(releaseId, projectId, seq, finalName,
|
|
550
|
+
insertReleaseStmt().run(releaseId, projectId, seq, finalName, user.id, user.email, user.name, publishedAt, previewKey);
|
|
550
551
|
for (const item of items) {
|
|
551
552
|
if (item.kind === 'site') {
|
|
552
553
|
const target = getPublishedSiteTree(projectId);
|
|
@@ -635,7 +636,7 @@ export const createStore = (db) => {
|
|
|
635
636
|
deleteOpenReleaseStmt().run(projectId, userId);
|
|
636
637
|
const result = {
|
|
637
638
|
id: releaseId,
|
|
638
|
-
publishedBy:
|
|
639
|
+
publishedBy: { id: user.id, email: user.email, name: user.name },
|
|
639
640
|
publishedAt,
|
|
640
641
|
preview_key: previewKey,
|
|
641
642
|
items: publishedItems
|
|
@@ -646,9 +647,9 @@ export const createStore = (db) => {
|
|
|
646
647
|
});
|
|
647
648
|
return tx();
|
|
648
649
|
};
|
|
649
|
-
const listReleaseHistoryStmt = () => db.prepare('SELECT id, name, published_by, published_at, preview_key, reverted_at FROM releases WHERE project_id = ? ORDER BY seq ASC');
|
|
650
|
-
const findReleaseByPreviewKeyStmt = () => db.prepare('SELECT id, name, published_by, published_at, preview_key, reverted_at, seq FROM releases WHERE project_id = ? AND preview_key = ?');
|
|
651
|
-
const getReleaseByIdStmt = () => db.prepare('SELECT id, name, published_by, published_at, preview_key, reverted_at, seq FROM releases WHERE project_id = ? AND id = ?');
|
|
650
|
+
const listReleaseHistoryStmt = () => db.prepare('SELECT id, name, published_by, published_by_email, published_by_name, published_at, preview_key, reverted_at FROM releases WHERE project_id = ? ORDER BY seq ASC');
|
|
651
|
+
const findReleaseByPreviewKeyStmt = () => db.prepare('SELECT id, name, published_by, published_by_email, published_by_name, published_at, preview_key, reverted_at, seq FROM releases WHERE project_id = ? AND preview_key = ?');
|
|
652
|
+
const getReleaseByIdStmt = () => db.prepare('SELECT id, name, published_by, published_by_email, published_by_name, published_at, preview_key, reverted_at, seq FROM releases WHERE project_id = ? AND id = ?');
|
|
652
653
|
const listReleaseItemsByReleaseIdStmt = () => db.prepare('SELECT kind, route_id, locale, params, tree, prior_tree, outcome, prior_tombstone FROM release_items WHERE release_id = ? ORDER BY id');
|
|
653
654
|
const listReleaseItemsAfterSeqStmt = () => db.prepare('SELECT seq, kind, route_id, locale, params, tree, prior_tree, outcome, prior_tombstone FROM release_items WHERE project_id = ? AND seq > ? ORDER BY seq DESC, id DESC');
|
|
654
655
|
const updateReleasePreviewKeyStmt = () => db.prepare('UPDATE releases SET preview_key = ? WHERE id = ?');
|
|
@@ -702,7 +703,11 @@ export const createStore = (db) => {
|
|
|
702
703
|
const items = listReleaseItemsByReleaseIdStmt().all(row.id).map(rowToPublishedItem);
|
|
703
704
|
const r = {
|
|
704
705
|
id: row.id,
|
|
705
|
-
publishedBy:
|
|
706
|
+
publishedBy: {
|
|
707
|
+
id: row.published_by,
|
|
708
|
+
email: row.published_by_email,
|
|
709
|
+
name: row.published_by_name
|
|
710
|
+
},
|
|
706
711
|
publishedAt: row.published_at,
|
|
707
712
|
preview_key: row.preview_key,
|
|
708
713
|
items
|
|
@@ -822,7 +827,7 @@ export const createStore = (db) => {
|
|
|
822
827
|
// ---------------------------------------------------------------------------
|
|
823
828
|
// Revert
|
|
824
829
|
// ---------------------------------------------------------------------------
|
|
825
|
-
const revertRelease = (projectId, releaseId,
|
|
830
|
+
const revertRelease = (projectId, releaseId, user) => {
|
|
826
831
|
const tx = db.transaction(() => {
|
|
827
832
|
ensureProjectState(projectId);
|
|
828
833
|
const sourceRow = getReleaseByIdStmt().get(projectId, releaseId);
|
|
@@ -839,7 +844,7 @@ export const createStore = (db) => {
|
|
|
839
844
|
const inverseName = sourceRow.name
|
|
840
845
|
? `Revert "${sourceRow.name}"`
|
|
841
846
|
: `Revert ${sourceRow.id.slice(0, 8)}`;
|
|
842
|
-
insertReleaseStmt().run(inverseId, projectId, seq, inverseName,
|
|
847
|
+
insertReleaseStmt().run(inverseId, projectId, seq, inverseName, user.id, user.email, user.name, publishedAt, previewKey);
|
|
843
848
|
for (const item of sourceItems) {
|
|
844
849
|
if (item.kind === 'site') {
|
|
845
850
|
const restoreTree = item.priorTree ?? {};
|
|
@@ -962,7 +967,7 @@ export const createStore = (db) => {
|
|
|
962
967
|
return {
|
|
963
968
|
id: inverseId,
|
|
964
969
|
name: inverseName,
|
|
965
|
-
publishedBy:
|
|
970
|
+
publishedBy: { id: user.id, email: user.email, name: user.name },
|
|
966
971
|
publishedAt,
|
|
967
972
|
preview_key: previewKey,
|
|
968
973
|
items: inverseItems
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
type PublishedRelease = {
|
|
27
27
|
id: string;
|
|
28
28
|
name?: string;
|
|
29
|
-
publishedBy: string;
|
|
29
|
+
publishedBy: { id: string; email: string | null; name: string | null };
|
|
30
30
|
publishedAt: string;
|
|
31
31
|
preview_key: string;
|
|
32
32
|
revertedAt?: string;
|
|
@@ -206,10 +206,9 @@
|
|
|
206
206
|
{/if}
|
|
207
207
|
</div>
|
|
208
208
|
<div class="vela:text-[11px] vela:text-bar-text-tertiary vela:mt-0.5">
|
|
209
|
-
{formatDate(release.publishedAt)} · published by {release.publishedBy
|
|
210
|
-
? ' · '
|
|
211
|
-
|
|
212
|
-
>live now</span
|
|
209
|
+
{formatDate(release.publishedAt)} · published by {release.publishedBy.email ??
|
|
210
|
+
release.publishedBy.id}{isLive ? ' · ' : ''}{#if isLive}<span
|
|
211
|
+
class="vela:text-[var(--cms-status-clean-dot)]">live now</span
|
|
213
212
|
>{/if}
|
|
214
213
|
</div>
|
|
215
214
|
{#if paths.length > 0}
|
package/dist/core/wire.d.ts
CHANGED
|
@@ -54,10 +54,20 @@ export type PublishedReleaseItem = ReleaseItemCore & {
|
|
|
54
54
|
priorTree: Tree | null;
|
|
55
55
|
priorTombstone?: PageDeleteOutcome;
|
|
56
56
|
};
|
|
57
|
+
/**
|
|
58
|
+
* The editor who published a release, captured at publish time. `email` and
|
|
59
|
+
* `name` are null only on rows published before they were recorded, where the
|
|
60
|
+
* account no longer resolves.
|
|
61
|
+
*/
|
|
62
|
+
export type ReleasePublisher = {
|
|
63
|
+
id: string;
|
|
64
|
+
email: string | null;
|
|
65
|
+
name: string | null;
|
|
66
|
+
};
|
|
57
67
|
export type PublishedRelease = {
|
|
58
68
|
id: string;
|
|
59
69
|
name?: string;
|
|
60
|
-
publishedBy:
|
|
70
|
+
publishedBy: ReleasePublisher;
|
|
61
71
|
publishedAt: string;
|
|
62
72
|
preview_key: string;
|
|
63
73
|
items: PublishedReleaseItem[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velastack/cms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "A scope-aware CMS for SvelteKit, for static or dynamic sites.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"svelte",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"type": "module",
|
|
24
24
|
"engines": {
|
|
25
|
-
"node": "
|
|
25
|
+
"node": ">=22.12.0"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"dev": "vite dev",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
},
|
|
77
77
|
"peerDependencies": {
|
|
78
78
|
"@sveltejs/kit": "^2.0.0",
|
|
79
|
-
"better-sqlite3": "^
|
|
79
|
+
"better-sqlite3": "^12.0.0 || ^13.0.0",
|
|
80
80
|
"marked": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
|
|
81
81
|
"svelte": "^5.0.0",
|
|
82
82
|
"vite": "^8.0.0"
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
"@tailwindcss/cli": "^4.3.3",
|
|
99
99
|
"@tailwindcss/vite": "^4.3.3",
|
|
100
100
|
"@types/node": "^26.4.0",
|
|
101
|
-
"better-sqlite3": "^
|
|
101
|
+
"better-sqlite3": "^13.0.3",
|
|
102
102
|
"marked": "^18.0.11",
|
|
103
103
|
"prettier": "^3.9.6",
|
|
104
104
|
"prettier-plugin-svelte": "^4.1.1",
|