@cardinali-studio/adminkit 2.2.2 → 2.2.5
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/api.d.ts +20 -0
- package/dist/api.js +4 -0
- package/dist/app/App.js +2 -1
- package/dist/app/lib/publish.js +29 -5
- package/dist/app/lib/settings.js +7 -2
- package/dist/app/site.d.ts +17 -0
- package/dist/deployment.d.ts +2 -0
- package/dist/deployment.js +3 -2
- package/dist/functions/api/content-ci.js +4 -1
- package/dist/persistence.d.ts +24 -0
- package/dist/persistence.js +61 -0
- package/package.json +1 -1
package/dist/api.d.ts
CHANGED
|
@@ -132,6 +132,26 @@ export declare const api: {
|
|
|
132
132
|
ok: true;
|
|
133
133
|
discarded: true;
|
|
134
134
|
}>;
|
|
135
|
+
/** D1 publication is site-owned: the site chooses which documents it can
|
|
136
|
+
* materialize, while the panel only displays the pending immutable inputs. */
|
|
137
|
+
contentPublishState: () => Promise<{
|
|
138
|
+
ok: true;
|
|
139
|
+
configured: true;
|
|
140
|
+
ahead: number;
|
|
141
|
+
documents: {
|
|
142
|
+
documentId: string;
|
|
143
|
+
kind: string;
|
|
144
|
+
updatedAt: number;
|
|
145
|
+
}[];
|
|
146
|
+
activeReleaseId?: string | null;
|
|
147
|
+
}>;
|
|
148
|
+
contentPublish: () => Promise<{
|
|
149
|
+
ok: true;
|
|
150
|
+
published: boolean;
|
|
151
|
+
releaseId?: string;
|
|
152
|
+
attemptId?: string;
|
|
153
|
+
message?: string;
|
|
154
|
+
}>;
|
|
135
155
|
publishState: () => Promise<{
|
|
136
156
|
ok: true;
|
|
137
157
|
configured: boolean;
|
package/dist/api.js
CHANGED
|
@@ -60,6 +60,10 @@ export const api = {
|
|
|
60
60
|
contentDocument: (documentId) => call(`/admin/api/content/document?id=${encodeURIComponent(documentId)}`),
|
|
61
61
|
saveContent: async (command) => post("/admin/api/content/save", await contentSaveRequest(command)),
|
|
62
62
|
discardContent: (documentId, expectedDraftRevisionId, baselineRevisionId) => post("/admin/api/content/discard", { documentId, expectedDraftRevisionId, baselineRevisionId }),
|
|
63
|
+
/** D1 publication is site-owned: the site chooses which documents it can
|
|
64
|
+
* materialize, while the panel only displays the pending immutable inputs. */
|
|
65
|
+
contentPublishState: () => call("/admin/api/content/publish"),
|
|
66
|
+
contentPublish: () => post("/admin/api/content/publish", {}),
|
|
63
67
|
// ---- pubblicazione
|
|
64
68
|
/* Salvare scrive sulla bozza e non fa partire nessuna build; è questo che manda online. Due
|
|
65
69
|
* gesti separati, quindi due chiamate: una chiede quanto c'è di non pubblicato, l'altra lo
|
package/dist/app/App.js
CHANGED
|
@@ -58,6 +58,7 @@ export function App() {
|
|
|
58
58
|
const local = isLocalPreview();
|
|
59
59
|
const pages = usePages();
|
|
60
60
|
const home = useHome(section === "home");
|
|
61
|
+
const HomeEditorView = site().homeEditor ?? HomeEditor;
|
|
61
62
|
/* Anche la Panoramica: le sue card devono sapere quali funzioni aggiuntive sono attive, e
|
|
62
63
|
lo stato è quello — non una seconda copia. È una lettura sola, e la Panoramica è la
|
|
63
64
|
schermata da cui si entra, quindi arriva prima che serva. */
|
|
@@ -257,7 +258,7 @@ export function App() {
|
|
|
257
258
|
/* Con un errore di caricamento c'era solo la riga rossa: nessun modo di riprovare
|
|
258
259
|
se non ricaricando il pannello a mano. Le altre sezioni il bottone ce l'hanno. */
|
|
259
260
|
home.error ? (_jsxs("div", { className: "pane-empty", children: [_jsx("p", { className: "ak-error", children: home.error }), _jsx("button", { className: "ak-btn", onClick: () => void home.load(), children: "Riprova" })] }))
|
|
260
|
-
: home.data ? _jsx(
|
|
261
|
+
: home.data ? _jsx(HomeEditorView, { data: home.data, group: homeGroup, onChange: home.change, pickMedia: pickMedia })
|
|
261
262
|
: _jsx("p", { className: "ak-hint", children: "Carico\u2026" })), section === "settings" && (settings.error ? (_jsxs("div", { className: "pane-empty", children: [_jsx("p", { className: "ak-error", children: settings.error }), _jsx("button", { className: "ak-btn", onClick: () => void settings.load(), children: "Riprova" })] })) : settings.data ? (settingsView === "seo"
|
|
262
263
|
? _jsx(WebsiteSeoSettingsEditor, { value: settings.data, onChange: settings.change })
|
|
263
264
|
: _jsx(SiteSettingsEditor, { value: settings.data, onChange: settings.change, pickMediaFiles: pickMediaFiles, focus: route[1] })) : _jsx("p", { className: "pane-empty ak-hint", children: "Carico\u2026" })), view === "media" && _jsx(MediaGrid, { lib: media }), view === "trash" && _jsx(Trash, {}), section === "requests" && (requests.error ? _jsx("p", { className: "ak-error", children: requests.error })
|
package/dist/app/lib/publish.js
CHANGED
|
@@ -17,10 +17,19 @@ export function usePublish() {
|
|
|
17
17
|
const [error, setError] = useState(null);
|
|
18
18
|
const reload = useCallback(async () => {
|
|
19
19
|
if (contentPersistenceMode() === "d1") {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
try {
|
|
21
|
+
const d = await api.contentPublishState();
|
|
22
|
+
setAhead(d.ahead ?? 0);
|
|
23
|
+
setCommits((d.documents ?? []).map((document) => ({
|
|
24
|
+
message: `Modifica salvata (${document.kind})`, date: document.updatedAt ? new Date(document.updatedAt).toISOString() : null,
|
|
25
|
+
})));
|
|
26
|
+
setConfigured(true);
|
|
27
|
+
setError(null);
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
setConfigured(false);
|
|
31
|
+
setError(e instanceof ApiError ? e.message : String(e));
|
|
32
|
+
}
|
|
24
33
|
return;
|
|
25
34
|
}
|
|
26
35
|
try {
|
|
@@ -42,7 +51,22 @@ export function usePublish() {
|
|
|
42
51
|
useEffect(() => { void reload(); }, [reload]);
|
|
43
52
|
const publish = useCallback(async () => {
|
|
44
53
|
if (contentPersistenceMode() === "d1") {
|
|
45
|
-
|
|
54
|
+
setPublishing(true);
|
|
55
|
+
setNotice(null);
|
|
56
|
+
try {
|
|
57
|
+
const r = await api.contentPublish();
|
|
58
|
+
setNotice(r.published === false
|
|
59
|
+
? r.message ?? "Non c'erano modifiche salvate da pubblicare."
|
|
60
|
+
: "Pubblicazione avviata. Il sito si aggiorna fra qualche minuto.");
|
|
61
|
+
await reload();
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
const err = e;
|
|
65
|
+
setNotice(`Pubblicazione non riuscita: ${err.message}`);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
setPublishing(false);
|
|
69
|
+
}
|
|
46
70
|
return;
|
|
47
71
|
}
|
|
48
72
|
setPublishing(true);
|
package/dist/app/lib/settings.js
CHANGED
|
@@ -45,7 +45,11 @@ export function useSiteSettings(active) {
|
|
|
45
45
|
const payload = document.draftPayload ?? document.publishedPayload;
|
|
46
46
|
if (!payload || (document.draftPayload !== null ? document.draftTombstone : document.publishedTombstone))
|
|
47
47
|
throw new Error("content_unavailable");
|
|
48
|
-
|
|
48
|
+
const stored = JSON.parse(payload);
|
|
49
|
+
const settings = mapped.fromPayload ? mapped.fromPayload(stored) : stored;
|
|
50
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings))
|
|
51
|
+
throw new Error("content_unavailable");
|
|
52
|
+
setData({ ...empty, ...settings, password: "" });
|
|
49
53
|
setRevision({ base: document.draftRevisionId ?? document.publishedRevisionId, expected: document.draftRevisionId });
|
|
50
54
|
}
|
|
51
55
|
else {
|
|
@@ -72,7 +76,8 @@ export function useSiteSettings(active) {
|
|
|
72
76
|
const mapped = site().siteSettingsDocument;
|
|
73
77
|
if (contentPersistenceMode() === "d1" && mapped && revision) {
|
|
74
78
|
const revisionId = crypto.randomUUID();
|
|
75
|
-
|
|
79
|
+
const payload = mapped.toPayload ? mapped.toPayload(data) : data;
|
|
80
|
+
await api.saveContent({ revisionId, documentId: mapped.documentId, baseRevisionId: revision.base, expectedDraftRevisionId: revision.expected, kind: mapped.kind, locale: mapped.locale, schemaVersion: mapped.schemaVersion, payload: payload, tombstone: false });
|
|
76
81
|
setRevision({ base: revisionId, expected: revisionId });
|
|
77
82
|
setDirty(false);
|
|
78
83
|
setNotice("Bozza D1 salvata");
|
package/dist/app/site.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type * as z from "zod";
|
|
2
|
+
import type { ComponentType } from "react";
|
|
2
3
|
export type Shape = Record<string, z.ZodType>;
|
|
3
4
|
/** Contratto di UNA tipologia di pagina. Il tipo sceglie la forma prima che un file venga
|
|
4
5
|
* letto, validato o riscritto: non esiste più uno schema che contenga i campi di tutte le
|
|
@@ -38,6 +39,17 @@ export interface SiteAdminConfig {
|
|
|
38
39
|
languages?: LanguagesConfig;
|
|
39
40
|
/** Forma del singleton home: i suoi `group` diventano le voci della seconda colonna. */
|
|
40
41
|
home: Shape;
|
|
42
|
+
/** Optional site-owned Home surface. The kit keeps its legacy visual editor
|
|
43
|
+
* as a fallback, while a site with a different document shape can render its
|
|
44
|
+
* own schema-driven editor without teaching the shared package its fields. */
|
|
45
|
+
homeEditor?: ComponentType<{
|
|
46
|
+
data: Record<string, unknown>;
|
|
47
|
+
group: string;
|
|
48
|
+
onChange: (next: Record<string, unknown>) => void;
|
|
49
|
+
pickMedia: (opts: {
|
|
50
|
+
multiple: boolean;
|
|
51
|
+
}) => Promise<string[] | null>;
|
|
52
|
+
}>;
|
|
41
53
|
/** Peso di ordinamento di una pagina data la sua posizione nell'elenco. */
|
|
42
54
|
positionWeight: (item: Record<string, unknown>, index: number, all: Record<string, unknown>[]) => number;
|
|
43
55
|
/**
|
|
@@ -56,11 +68,16 @@ export interface SiteAdminConfig {
|
|
|
56
68
|
locale: string;
|
|
57
69
|
schemaVersion: number;
|
|
58
70
|
};
|
|
71
|
+
/** Identity plus the deliberately site-owned conversion between the generic
|
|
72
|
+
* settings form and an immutable D1 document payload. A site may persist the
|
|
73
|
+
* object directly or keep it inside a repository-specific file envelope. */
|
|
59
74
|
siteSettingsDocument?: {
|
|
60
75
|
documentId: string;
|
|
61
76
|
kind: string;
|
|
62
77
|
locale: string;
|
|
63
78
|
schemaVersion: number;
|
|
79
|
+
fromPayload?: (payload: unknown) => Record<string, unknown>;
|
|
80
|
+
toPayload?: (settings: Record<string, unknown>) => unknown;
|
|
64
81
|
};
|
|
65
82
|
}
|
|
66
83
|
/** Chiamata da `mountAdmin` prima del primo render. */
|
package/dist/deployment.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export type DeploymentAttempt = Readonly<{
|
|
|
7
7
|
intentKind: DeploymentIntent;
|
|
8
8
|
releaseId: string;
|
|
9
9
|
codeSha: string | null;
|
|
10
|
+
expectedFingerprint: string | null;
|
|
10
11
|
phase: DeploymentPhase;
|
|
11
12
|
claimTokenChecksum: string | null;
|
|
12
13
|
claimExpiresAt: number | null;
|
|
@@ -36,6 +37,7 @@ export declare class DeploymentAttemptKernel {
|
|
|
36
37
|
intentKind: DeploymentIntent;
|
|
37
38
|
releaseId: string;
|
|
38
39
|
codeSha: string | null;
|
|
40
|
+
expectedFingerprint: string | null;
|
|
39
41
|
phase: DeploymentPhase;
|
|
40
42
|
claimTokenChecksum: string | null;
|
|
41
43
|
claimExpiresAt: number | null;
|
package/dist/deployment.js
CHANGED
|
@@ -75,7 +75,7 @@ function asAttempt(row) {
|
|
|
75
75
|
if (!row?.releaseId)
|
|
76
76
|
return null;
|
|
77
77
|
return {
|
|
78
|
-
attemptId: row.attemptId, intentKind: row.intentKind, releaseId: row.releaseId, codeSha: row.codeSha,
|
|
78
|
+
attemptId: row.attemptId, intentKind: row.intentKind, releaseId: row.releaseId, codeSha: row.codeSha, expectedFingerprint: row.expectedFingerprint,
|
|
79
79
|
phase: row.phase, claimTokenChecksum: row.claimTokenChecksum, claimExpiresAt: row.claimExpiresAt,
|
|
80
80
|
};
|
|
81
81
|
}
|
|
@@ -88,7 +88,8 @@ export class D1DeploymentAttemptRepository {
|
|
|
88
88
|
}
|
|
89
89
|
async get(attemptId) {
|
|
90
90
|
const row = await this.db.prepare(`SELECT attempt_id AS attemptId, intent_kind AS intentKind, release_id AS releaseId,
|
|
91
|
-
code_sha AS codeSha,
|
|
91
|
+
code_sha AS codeSha, expected_fingerprint AS expectedFingerprint, phase,
|
|
92
|
+
ci_claim_token_checksum AS claimTokenChecksum, ci_claim_expires_at AS claimExpiresAt
|
|
92
93
|
FROM production_deployment_attempts WHERE attempt_id = ?`).bind(attemptId).first();
|
|
93
94
|
return asAttempt(row);
|
|
94
95
|
}
|
|
@@ -109,7 +109,10 @@ export async function handleInternalContentCi({ request, env }) {
|
|
|
109
109
|
const { attempt } = await claimedAttempt(request, attempts, identity, now);
|
|
110
110
|
const snapshot = await content.getReleaseExport(attempt.releaseId);
|
|
111
111
|
if (!snapshot) throw new Error("attempt_not_available");
|
|
112
|
-
return response({ ok: true, attempt: {
|
|
112
|
+
return response({ ok: true, attempt: {
|
|
113
|
+
attemptId: attempt.attemptId, releaseId: attempt.releaseId, codeSha: attempt.codeSha,
|
|
114
|
+
expectedFingerprint: attempt.expectedFingerprint, phase: attempt.phase,
|
|
115
|
+
}, release: await releaseExportFromStored(snapshot) });
|
|
113
116
|
}
|
|
114
117
|
|
|
115
118
|
const input = await body(request);
|
package/dist/persistence.d.ts
CHANGED
|
@@ -116,6 +116,16 @@ export type ReleaseInput = {
|
|
|
116
116
|
revisionId: string;
|
|
117
117
|
}[];
|
|
118
118
|
};
|
|
119
|
+
export type ReleaseDeploymentInput = ReleaseInput & {
|
|
120
|
+
attempt: {
|
|
121
|
+
attemptId: string;
|
|
122
|
+
intentKind: string;
|
|
123
|
+
requestedReleaseId: string | null;
|
|
124
|
+
requestedCodeSha: string;
|
|
125
|
+
codeSha: string;
|
|
126
|
+
expectedFingerprint: string;
|
|
127
|
+
};
|
|
128
|
+
};
|
|
119
129
|
export type ContentImportRun = {
|
|
120
130
|
importId: string;
|
|
121
131
|
requestChecksum: string;
|
|
@@ -149,6 +159,20 @@ export declare class D1ContentRepository {
|
|
|
149
159
|
save(input: SaveInput): Promise<"created" | "repeated">;
|
|
150
160
|
discard(documentId: string, expectedDraftRevisionId: string | null, baselineRevisionId: string | null, now: number): Promise<void>;
|
|
151
161
|
publish(input: ReleaseInput): Promise<"created" | "repeated">;
|
|
162
|
+
/** Candidate release and deployment-lane reservation are inseparable: a
|
|
163
|
+
* failed reservation must not leave an unclaimable publishing release. */
|
|
164
|
+
publishWithDeploymentAttempt(input: ReleaseDeploymentInput): Promise<"created">;
|
|
165
|
+
/** The browser-side dispatcher is allowed to undo only an untouched waiting
|
|
166
|
+
* attempt. Once CI has claimed it, failure handling belongs exclusively to
|
|
167
|
+
* the short-lived CI capability. */
|
|
168
|
+
abortUnclaimedDeploymentAttempt(input: {
|
|
169
|
+
attemptId: string;
|
|
170
|
+
releaseId: string;
|
|
171
|
+
now: number;
|
|
172
|
+
code: string;
|
|
173
|
+
stage: string;
|
|
174
|
+
message: string;
|
|
175
|
+
}): Promise<void>;
|
|
152
176
|
finalize(releaseId: string, now: number): Promise<void>;
|
|
153
177
|
}
|
|
154
178
|
/** Pure strict-CAS kernel. The D1 adapter uses these guards around its SQL operations. */
|
package/dist/persistence.js
CHANGED
|
@@ -232,6 +232,67 @@ export class D1ContentRepository {
|
|
|
232
232
|
throw new Error("invalid_document");
|
|
233
233
|
return "created";
|
|
234
234
|
}
|
|
235
|
+
/** Candidate release and deployment-lane reservation are inseparable: a
|
|
236
|
+
* failed reservation must not leave an unclaimable publishing release. */
|
|
237
|
+
async publishWithDeploymentAttempt(input) {
|
|
238
|
+
const [existingRelease, existingAttempt] = await Promise.all([
|
|
239
|
+
this.db.prepare("SELECT release_id FROM content_releases WHERE release_id = ?").bind(input.releaseId).first(),
|
|
240
|
+
this.db.prepare("SELECT attempt_id FROM production_deployment_attempts WHERE attempt_id = ?").bind(input.attempt.attemptId).first(),
|
|
241
|
+
]);
|
|
242
|
+
if (existingRelease || existingAttempt)
|
|
243
|
+
throw new Error("duplicate_request");
|
|
244
|
+
const state = await this.db.prepare("SELECT current_release_id FROM content_site_state WHERE singleton = 1").first();
|
|
245
|
+
if (!state)
|
|
246
|
+
throw new Error("content_unavailable");
|
|
247
|
+
const inherited = state.current_release_id
|
|
248
|
+
? (await this.db.prepare("SELECT document_id, revision_id FROM content_release_entries WHERE release_id = ?").bind(state.current_release_id).all()).results ?? []
|
|
249
|
+
: [];
|
|
250
|
+
const manifest = new Map(inherited.map((entry) => [entry.document_id, entry.revision_id]));
|
|
251
|
+
const selected = [...input.entries];
|
|
252
|
+
if (!selected.length)
|
|
253
|
+
throw new Error("invalid_document");
|
|
254
|
+
for (const entry of selected)
|
|
255
|
+
manifest.set(entry.documentId, entry.revisionId);
|
|
256
|
+
const selectionGuard = selected.map(() => "EXISTS (SELECT 1 FROM content_documents WHERE document_id = ? AND draft_revision_id = ?)").join(" AND ");
|
|
257
|
+
const candidate = this.db.prepare(`INSERT INTO content_releases (release_id, base_release_id, request_checksum, status, active_slot, manifest_checksum, schema_package_version, materializer_version, created_by_subject, created_at)
|
|
258
|
+
SELECT ?, current_release_id, ?, 'publishing', 1, ?, ?, ?, ?, ? FROM content_site_state
|
|
259
|
+
WHERE singleton = 1 AND current_release_id IS ? AND NOT EXISTS (SELECT 1 FROM content_releases WHERE active_slot = 1)
|
|
260
|
+
AND NOT EXISTS (SELECT 1 FROM production_deployment_attempts WHERE active_slot = 1) AND ${selectionGuard}`)
|
|
261
|
+
.bind(input.releaseId, input.requestChecksum, input.manifestChecksum, input.schemaPackageVersion, input.materializerVersion, input.authorSubject, input.now, state.current_release_id, ...selected.flatMap((entry) => [entry.documentId, entry.revisionId]));
|
|
262
|
+
const statements = [candidate];
|
|
263
|
+
for (const [documentId, revisionId] of manifest)
|
|
264
|
+
statements.push(this.db.prepare("INSERT INTO content_release_entries (release_id, document_id, revision_id) SELECT ?, ?, ? WHERE EXISTS (SELECT 1 FROM content_revisions WHERE document_id = ? AND revision_id = ?)")
|
|
265
|
+
.bind(input.releaseId, documentId, revisionId, documentId, revisionId));
|
|
266
|
+
statements.push(this.db.prepare(`INSERT INTO production_deployment_attempts
|
|
267
|
+
(attempt_id, intent_kind, requested_release_id, requested_code_sha, code_sha, release_id, expected_fingerprint, phase, active_slot, created_at, updated_at)
|
|
268
|
+
SELECT ?, ?, ?, ?, ?, ?, ?, 'waiting', 1, ?, ?
|
|
269
|
+
WHERE NOT EXISTS (SELECT 1 FROM production_deployment_attempts WHERE active_slot = 1)
|
|
270
|
+
AND EXISTS (SELECT 1 FROM content_releases WHERE release_id = ? AND status = 'publishing')`)
|
|
271
|
+
.bind(input.attempt.attemptId, input.attempt.intentKind, input.attempt.requestedReleaseId, input.attempt.requestedCodeSha, input.attempt.codeSha, input.releaseId, input.attempt.expectedFingerprint, input.now, input.now, input.releaseId));
|
|
272
|
+
const results = await this.db.batch(statements);
|
|
273
|
+
if (results[0]?.meta?.changes !== 1 || results.slice(1, -1).some((result) => result.meta?.changes !== 1)
|
|
274
|
+
|| results.at(-1)?.meta?.changes !== 1)
|
|
275
|
+
throw new Error("deployment_lane_busy");
|
|
276
|
+
return "created";
|
|
277
|
+
}
|
|
278
|
+
/** The browser-side dispatcher is allowed to undo only an untouched waiting
|
|
279
|
+
* attempt. Once CI has claimed it, failure handling belongs exclusively to
|
|
280
|
+
* the short-lived CI capability. */
|
|
281
|
+
async abortUnclaimedDeploymentAttempt(input) {
|
|
282
|
+
const guard = `EXISTS (SELECT 1 FROM production_deployment_attempts WHERE attempt_id = ? AND release_id = ?
|
|
283
|
+
AND phase = 'waiting' AND ci_claim_token_checksum IS NULL AND ci_claim_expires_at IS NULL)`;
|
|
284
|
+
const results = await this.db.batch([
|
|
285
|
+
this.db.prepare(`UPDATE content_releases SET status = 'failed', active_slot = NULL, finished_at = ?
|
|
286
|
+
WHERE release_id = ? AND status = 'publishing' AND ${guard}`)
|
|
287
|
+
.bind(input.now, input.releaseId, input.attemptId, input.releaseId),
|
|
288
|
+
this.db.prepare(`UPDATE production_deployment_attempts
|
|
289
|
+
SET phase = 'failed', active_slot = NULL, error_code = ?, error_stage = ?, safe_message = ?, updated_at = ?, finished_at = ?
|
|
290
|
+
WHERE attempt_id = ? AND release_id = ? AND phase = 'waiting' AND ci_claim_token_checksum IS NULL AND ci_claim_expires_at IS NULL`)
|
|
291
|
+
.bind(input.code, input.stage, input.message, input.now, input.now, input.attemptId, input.releaseId),
|
|
292
|
+
]);
|
|
293
|
+
if (results[0]?.meta?.changes !== 1 || results[1]?.meta?.changes !== 1)
|
|
294
|
+
throw new Error("attempt_unavailable");
|
|
295
|
+
}
|
|
235
296
|
async finalize(releaseId, now) {
|
|
236
297
|
const release = await this.db.prepare("SELECT release_id, request_checksum, status, base_release_id FROM content_releases WHERE release_id = ?").bind(releaseId).first();
|
|
237
298
|
if (!release || release.status !== "publishing")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cardinali-studio/adminkit",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Motore riusabile di un pannello di amministrazione per siti Hugo su Cloudflare Pages: codec del front matter guidato dallo schema, form React, libreria media su R2. NIENTE di specifico di un sito qui dentro.",
|
|
6
6
|
"license": "UNLICENSED",
|