smoodly 0.0.51 → 0.0.53
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/README.md +14 -13
- package/dist/admin/auth.d.ts +3 -11
- package/dist/admin/auth.js +1 -30
- package/dist/admin/client-ops.d.ts +5 -4
- package/dist/admin/client-ops.js +15 -12
- package/dist/admin/delivery-mode.d.ts +1 -1
- package/dist/admin/delivery-mode.js +2 -2
- package/dist/admin/drift.d.ts +0 -2
- package/dist/admin/drift.js +1 -1
- package/dist/admin/editor/EditorView.d.ts +0 -3
- package/dist/admin/editor/EditorView.js +11 -16
- package/dist/admin/editor/FloatingToolbar.d.ts +0 -1
- package/dist/admin/editor/FloatingToolbar.js +1 -1
- package/dist/admin/editor/SeoSettings.d.ts +0 -2
- package/dist/admin/editor/SeoSettings.js +2 -2
- package/dist/admin/editor/canvas-address.d.ts +8 -0
- package/dist/admin/editor/canvas-address.js +18 -2
- package/dist/admin/editor/canvas-diagnosis.d.ts +2 -0
- package/dist/admin/editor/canvas-diagnosis.js +3 -0
- package/dist/admin/editor/inline-dom.d.ts +0 -4
- package/dist/admin/editor/inline-dom.js +1 -1
- package/dist/admin/editor/preview-grant.d.ts +6 -0
- package/dist/admin/editor/preview-grant.js +25 -0
- package/dist/admin/editor/useCanvasDiagnosis.js +2 -2
- package/dist/admin/files.generated.js +1 -1
- package/dist/admin/fixed-nodes.d.ts +0 -6
- package/dist/admin/fixed-nodes.js +1 -81
- package/dist/admin/forms/CropEditor.d.ts +4 -2
- package/dist/admin/forms/CropEditor.js +58 -5
- package/dist/admin/forms/ImagePreview.d.ts +3 -1
- package/dist/admin/forms/ImagePreview.js +9 -3
- package/dist/admin/forms/MediaGrid.js +2 -1
- package/dist/admin/forms/MediaPicker.js +9 -6
- package/dist/admin/forms/MediaWidget.js +11 -9
- package/dist/admin/forms/assets-context.d.ts +7 -3
- package/dist/admin/forms/assets-context.js +5 -3
- package/dist/admin/forms/crop-editor.d.ts +24 -2
- package/dist/admin/forms/crop-editor.js +36 -2
- package/dist/admin/forms/date-picker.d.ts +0 -1
- package/dist/admin/forms/date-picker.js +1 -1
- package/dist/admin/forms/tabs.d.ts +0 -1
- package/dist/admin/forms/tabs.js +1 -1
- package/dist/admin/forms/upload.d.ts +18 -11
- package/dist/admin/forms/upload.js +62 -24
- package/dist/admin/global-items.d.ts +0 -3
- package/dist/admin/global-items.js +0 -54
- package/dist/admin/handler.d.ts +22 -28
- package/dist/admin/handler.js +146 -47
- package/dist/admin/health.d.ts +0 -4
- package/dist/admin/index.d.ts +6 -9
- package/dist/admin/index.js +3 -6
- package/dist/admin/ops.d.ts +40 -32
- package/dist/admin/page-data.d.ts +14 -6
- package/dist/admin/page-data.js +1 -1
- package/dist/admin/session-flow.d.ts +65 -0
- package/dist/admin/session-flow.js +121 -0
- package/dist/admin/shell/AdminApp.d.ts +8 -3
- package/dist/admin/shell/AdminApp.js +37 -58
- package/dist/admin/shell/ContentHealthPage.js +1 -3
- package/dist/admin/shell/GlobalFields.d.ts +0 -3
- package/dist/admin/shell/GlobalFields.js +1 -1
- package/dist/admin/shell/IconRail.js +10 -8
- package/dist/admin/shell/MediaScreen.js +9 -6
- package/dist/admin/shell/MembersPage.d.ts +1 -2
- package/dist/admin/shell/MembersPage.js +19 -90
- package/dist/admin/shell/SettingsView.d.ts +1 -2
- package/dist/admin/shell/SettingsView.js +2 -2
- package/dist/admin/shell/SignedOutView.d.ts +5 -0
- package/dist/admin/shell/SignedOutView.js +9 -0
- package/dist/admin/shell/console-links.d.ts +5 -0
- package/dist/admin/shell/console-links.js +11 -0
- package/dist/admin/shell/members-list.d.ts +1 -14
- package/dist/admin/shell/members-list.js +2 -23
- package/dist/admin/shell/pages-tree.d.ts +0 -2
- package/dist/admin/shell/pages-tree.js +1 -1
- package/dist/admin/shell/presence-socket.d.ts +10 -0
- package/dist/admin/shell/presence-socket.js +191 -0
- package/dist/admin/shell/presence.d.ts +6 -18
- package/dist/admin/shell/presence.js +4 -11
- package/dist/admin/shell/session.d.ts +19 -12
- package/dist/admin/shell/session.js +114 -47
- package/dist/admin/ui/LocaleSwitcher.d.ts +0 -4
- package/dist/admin/ui/LocaleSwitcher.js +2 -2
- package/dist/admin/ui/primitives.d.ts +4 -14
- package/dist/admin/ui/primitives.js +7 -9
- package/dist/admin/ui/theme.js +13 -2
- package/dist/admin/validate.js +10 -1
- package/dist/api-client.d.ts +26 -0
- package/dist/api-client.js +107 -0
- package/dist/assemble.d.ts +4 -41
- package/dist/assemble.js +5 -303
- package/dist/asset-store.d.ts +28 -30
- package/dist/asset-store.js +26 -121
- package/dist/astro/cache.d.ts +1 -1
- package/dist/astro/components/Head.astro +4 -1
- package/dist/astro/components/Image.astro +12 -17
- package/dist/astro/components/Video.astro +5 -2
- package/dist/astro/head.d.ts +3 -0
- package/dist/astro/head.js +24 -0
- package/dist/astro/index.js +61 -27
- package/dist/astro/locale-paths.d.ts +5 -2
- package/dist/astro/locale-paths.js +10 -2
- package/dist/astro/routes/admin.js +9 -7
- package/dist/astro/runtime.d.ts +3 -2
- package/dist/astro/runtime.js +5 -4
- package/dist/astro/server.d.ts +7 -11
- package/dist/astro/server.js +22 -28
- package/dist/astro/shell.d.ts +3 -3
- package/dist/astro/shell.js +1 -1
- package/dist/astro/static-media-node.d.ts +4 -0
- package/dist/astro/static-media-node.js +25 -0
- package/dist/astro/static-media.d.ts +17 -0
- package/dist/astro/static-media.js +55 -0
- package/dist/astro/static.d.ts +5 -10
- package/dist/astro/static.js +13 -29
- package/dist/canvas-marker.d.ts +14 -0
- package/dist/canvas-marker.js +36 -0
- package/dist/cli/api.d.ts +14 -0
- package/dist/cli/api.js +48 -0
- package/dist/cli/app-env.d.ts +2 -0
- package/dist/cli/app-env.js +20 -0
- package/dist/cli/export.d.ts +16 -2
- package/dist/cli/export.js +52 -35
- package/dist/cli/import.d.ts +7 -11
- package/dist/cli/import.js +50 -54
- package/dist/cli/load-config.d.ts +0 -1
- package/dist/cli/load-config.js +1 -1
- package/dist/cli/main.d.ts +4 -12
- package/dist/cli/main.js +26 -72
- package/dist/cli/push.d.ts +6 -0
- package/dist/cli/push.js +31 -0
- package/dist/collections.d.ts +0 -1
- package/dist/collections.js +1 -1
- package/dist/config.d.ts +20 -5
- package/dist/config.js +18 -1
- package/dist/content/content-api.d.ts +23 -23
- package/dist/content/content-api.js +48 -56
- package/dist/content/mime.d.ts +1 -0
- package/dist/content/mime.js +6 -0
- package/dist/css.js +1 -1
- package/dist/deploy-store.d.ts +19 -0
- package/dist/entry-store.d.ts +8 -94
- package/dist/entry-store.js +9 -557
- package/dist/env.d.ts +27 -20
- package/dist/env.js +38 -50
- package/dist/fields.d.ts +11 -3
- package/dist/fields.js +33 -2
- package/dist/global-id.d.ts +2 -0
- package/dist/global-id.js +4 -2
- package/dist/image/SmoodlyImage.d.ts +9 -5
- package/dist/image/SmoodlyImage.js +29 -15
- package/dist/image/SmoodlyVideo.d.ts +1 -1
- package/dist/image/SmoodlyVideo.js +3 -2
- package/dist/image/index.d.ts +2 -3
- package/dist/image/index.js +0 -2
- package/dist/image/markup.d.ts +53 -0
- package/dist/image/markup.js +111 -0
- package/dist/index.d.ts +13 -23
- package/dist/index.js +7 -20
- package/dist/media-cut.d.ts +44 -0
- package/dist/media-cut.js +70 -0
- package/dist/media.d.ts +58 -4
- package/dist/media.js +126 -16
- package/dist/member-store.d.ts +2 -50
- package/dist/member-store.js +0 -56
- package/dist/next/admin-route.d.ts +1 -1
- package/dist/next/admin-route.js +5 -3
- package/dist/next/meta.js +2 -3
- package/dist/next/page-renderer.d.ts +2 -2
- package/dist/next/page-renderer.js +22 -11
- package/dist/next/queries.d.ts +2 -2
- package/dist/path-index.d.ts +0 -24
- package/dist/path-index.js +0 -98
- package/dist/paths.d.ts +1 -1
- package/dist/paths.js +1 -1
- package/dist/push.d.ts +30 -0
- package/dist/push.js +52 -0
- package/dist/read-counter.d.ts +2 -0
- package/dist/read-counter.js +6 -0
- package/dist/reads.d.ts +7 -7
- package/dist/reads.js +3 -0
- package/dist/registry-json.d.ts +11 -0
- package/dist/registry-json.js +81 -0
- package/dist/render.d.ts +0 -1
- package/dist/render.js +1 -1
- package/dist/resolve.d.ts +12 -6
- package/dist/resolve.js +11 -8
- package/dist/revalidate.d.ts +8 -1
- package/dist/revalidate.js +8 -1
- package/dist/richtext/index.d.ts +1 -1
- package/dist/section-wrapper.d.ts +1 -1
- package/dist/section-wrapper.js +0 -9
- package/dist/signed-message.d.ts +26 -0
- package/dist/signed-message.js +103 -0
- package/dist/site.d.ts +59 -18
- package/dist/site.js +21 -252
- package/dist/space-transfer.d.ts +6 -3
- package/dist/space-transfer.js +18 -6
- package/dist/store.d.ts +2 -80
- package/dist/store.js +3 -489
- package/dist/toolset.d.ts +2 -1
- package/dist/toolset.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -15
- package/dist/admin/auth-config.d.ts +0 -6
- package/dist/admin/browser/fetch-op.d.ts +0 -10
- package/dist/admin/browser/fetch-op.js +0 -44
- package/dist/admin/browser/main.d.ts +0 -1
- package/dist/admin/browser/main.js +0 -23
- package/dist/admin/ops-impl.d.ts +0 -51
- package/dist/admin/ops-impl.js +0 -1403
- package/dist/admin/shell/InviteModal.d.ts +0 -6
- package/dist/admin/shell/InviteModal.js +0 -28
- package/dist/admin/shell/LoginView.d.ts +0 -5
- package/dist/admin/shell/LoginView.js +0 -80
- package/dist/admin/shell/SchemaNotice.d.ts +0 -4
- package/dist/admin/shell/SchemaNotice.js +0 -4
- package/dist/admin/shell/SetPasswordView.d.ts +0 -7
- package/dist/admin/shell/SetPasswordView.js +0 -41
- package/dist/admin/shell/presence-realtime.d.ts +0 -3
- package/dist/admin/shell/presence-realtime.js +0 -32
- package/dist/admin/supabase-auth.d.ts +0 -7
- package/dist/admin/supabase-auth.js +0 -30
- package/dist/astro/deploy-deps.d.ts +0 -10
- package/dist/astro/deploy-deps.js +0 -8
- package/dist/astro/image.d.ts +0 -27
- package/dist/astro/image.js +0 -29
- package/dist/bind.d.ts +0 -19
- package/dist/bind.js +0 -34
- package/dist/cli/add-admin.d.ts +0 -11
- package/dist/cli/add-admin.js +0 -25
- package/dist/cli/load-collections.d.ts +0 -2
- package/dist/cli/load-collections.js +0 -27
- package/dist/cli/migrate.d.ts +0 -43
- package/dist/cli/migrate.js +0 -138
- package/dist/cli/rebuild.d.ts +0 -23
- package/dist/cli/rebuild.js +0 -22
- package/dist/cli/space-io.d.ts +0 -41
- package/dist/cli/space-io.js +0 -84
- package/dist/client.d.ts +0 -11
- package/dist/client.js +0 -54
- package/dist/current-space.d.ts +0 -4
- package/dist/current-space.js +0 -9
- package/dist/document-stamp.d.ts +0 -7
- package/dist/document-stamp.js +0 -48
- package/dist/image/loaders.d.ts +0 -15
- package/dist/image/loaders.js +0 -5
- package/dist/image/next-config.d.ts +0 -18
- package/dist/image/next-config.js +0 -32
- package/dist/image/remote-pattern.d.ts +0 -1
- package/dist/image/remote-pattern.js +0 -5
- package/dist/migrations.d.ts +0 -37
- package/dist/migrations.js +0 -73
- package/dist/project.d.ts +0 -88
- package/dist/project.js +0 -231
- package/dist/published.d.ts +0 -142
- package/dist/published.js +0 -164
- package/dist/ref-index.d.ts +0 -19
- package/dist/ref-index.js +0 -0
- package/dist/schema-read.d.ts +0 -23
- package/dist/schema-read.js +0 -50
- package/dist/schema-version.d.ts +0 -19
- package/dist/schema-version.js +0 -34
- package/dist/sql-space.d.ts +0 -44
- package/dist/sql-space.js +0 -289
- package/dist/sql.d.ts +0 -15
- package/dist/sql.js +0 -556
- package/dist/supabase-asset-store.d.ts +0 -21
- package/dist/supabase-asset-store.js +0 -170
- package/dist/supabase-entry-store.d.ts +0 -92
- package/dist/supabase-entry-store.js +0 -824
- package/dist/supabase-member-store.d.ts +0 -21
- package/dist/supabase-member-store.js +0 -96
- package/dist/supabase-path-index.d.ts +0 -30
- package/dist/supabase-path-index.js +0 -138
- package/dist/supabase-published-store.d.ts +0 -56
- package/dist/supabase-published-store.js +0 -314
- package/dist/supabase-store.d.ts +0 -103
- package/dist/supabase-store.js +0 -678
- package/migrations/20260926120000_smoodly_v1.sql +0 -535
- package/migrations/20260926130000_smoodly_v2.sql +0 -26
- package/migrations/20260926140000_smoodly_v3.sql +0 -38
- package/migrations/20260926150000_smoodly_v4.sql +0 -41
- package/migrations/20260929120000_smoodly_v5.sql +0 -56
- package/migrations/20260929135523_smoodly_v6.sql +0 -200
- package/migrations/20260930040956_smoodly_v7.sql +0 -84
- package/migrations/20261007120000_smoodly_v8.sql +0 -154
- package/migrations/20261007200000_smoodly_v9.sql +0 -30
- package/migrations/20261008120000_smoodly_v10.sql +0 -47
- package/migrations/20261009120000_smoodly_v11.sql +0 -49
- /package/dist/{admin/auth-config.js → deploy-store.js} +0 -0
package/dist/cli/import.js
CHANGED
|
@@ -1,25 +1,39 @@
|
|
|
1
1
|
// packages/smoodly/src/cli/import.ts
|
|
2
|
-
// `smoodly import <dir>` (spec 2026-09-28 export/import §9–§10
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// export's rows and both writes
|
|
2
|
+
// `smoodly import <dir>` (spec 2026-09-28 export/import §9–§10; over the API
|
|
3
|
+
// since plan 2026-10-14 the service holds the stores): an export folder into
|
|
4
|
+
// the key's space. Every check runs before the first write; nothing is
|
|
5
|
+
// overwritten or deleted; a run that stops midway is finished by running it
|
|
6
|
+
// again, since the target may hold only this export's rows and both writes
|
|
7
|
+
// skip what already landed. The service writes each row into its own space;
|
|
8
|
+
// a read assembles from what landed, so nothing is rebuilt after.
|
|
7
9
|
import { existsSync } from "node:fs";
|
|
8
10
|
import { readFile } from "node:fs/promises";
|
|
9
11
|
import { join, resolve } from "node:path";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
import { CONTENT_TABLES } from "../space-transfer.js";
|
|
13
|
+
import { FORMAT, describeImport, emptyTables, foreignRows, insertPlan, missingRows, notEmptyMessage, rowKey, versionMismatchMessage, } from "../space-transfer.js";
|
|
14
|
+
import { connectApi } from "./api.js";
|
|
15
|
+
import { mediaUrl, readTable, transferHead } from "./export.js";
|
|
16
|
+
const BATCH_ROWS = 500;
|
|
17
|
+
/** A batch's JSON stays under the API's 4 MB request cap with room for the envelope. */
|
|
18
|
+
const MAX_BATCH_BYTES = 3500000;
|
|
19
|
+
const notAnExport = (dir) => `Refusing: ${dir} is not a Smoodly export (no manifest.json).`;
|
|
20
|
+
const formatMismatch = (format) => `Refusing: this export's format is ${String(format)}; this smoodly reads format ${FORMAT}. Update smoodly.`;
|
|
19
21
|
export const RUN_AGAIN = "Run the same command again to continue: rows and files that landed are skipped.";
|
|
20
|
-
export const MEMBERS_HEADING = "Members in the source —
|
|
21
|
-
export const nothingNew = (
|
|
22
|
+
export const MEMBERS_HEADING = "Members in the source — add them to the space's members in the project at smoodly.io:";
|
|
23
|
+
export const nothingNew = () => "Nothing new: the space already holds everything in this export.";
|
|
22
24
|
export const CACHE_NOTE = "Restart or redeploy the app: it may have cached pages from while the space was empty.";
|
|
25
|
+
/** `list` cut into batches of at most `size` rows, each halved while its JSON is over MAX_BATCH_BYTES. */
|
|
26
|
+
function batches(list, size) {
|
|
27
|
+
const out = [];
|
|
28
|
+
for (let i = 0; i < list.length;) {
|
|
29
|
+
let take = Math.min(size, list.length - i);
|
|
30
|
+
while (take > 1 && Buffer.byteLength(JSON.stringify({ rows: list.slice(i, i + take) })) > MAX_BATCH_BYTES)
|
|
31
|
+
take = Math.ceil(take / 2);
|
|
32
|
+
out.push(list.slice(i, i + take));
|
|
33
|
+
i += take;
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
23
37
|
export async function runImport(args, out, deps = {}) {
|
|
24
38
|
const cwd = deps.cwd ?? process.cwd();
|
|
25
39
|
const dir = resolve(cwd, args.dir);
|
|
@@ -33,64 +47,48 @@ export async function runImport(args, out, deps = {}) {
|
|
|
33
47
|
out(formatMismatch(manifest.format));
|
|
34
48
|
return 1;
|
|
35
49
|
}
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
50
|
+
const env = deps.env ?? process.env;
|
|
51
|
+
const api = deps.api ?? connectApi(cwd, env);
|
|
52
|
+
// Read after connectApi: it loads the app's .env files.
|
|
53
|
+
const space = env.SMOODLY_SPACE_ID ?? "";
|
|
54
|
+
const head = await transferHead(api, out);
|
|
55
|
+
if (!head)
|
|
40
56
|
return 1;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (schema.version !== manifest.schemaVersion) {
|
|
44
|
-
out(versionMismatchMessage(schema.version, manifest.schemaVersion));
|
|
57
|
+
if (head.schemaVersion !== manifest.schemaVersion) {
|
|
58
|
+
out(versionMismatchMessage(head.schemaVersion, manifest.schemaVersion));
|
|
45
59
|
return 1;
|
|
46
60
|
}
|
|
47
61
|
const rows = emptyTables();
|
|
48
62
|
for (const table of CONTENT_TABLES)
|
|
49
63
|
rows[table] = JSON.parse(await readFile(join(dir, "rows", `${table}.json`), "utf8"));
|
|
50
64
|
const target = {};
|
|
51
|
-
for (const table of CONTENT_TABLES)
|
|
52
|
-
target[table] = (await
|
|
53
|
-
}
|
|
65
|
+
for (const table of CONTENT_TABLES)
|
|
66
|
+
target[table] = (await readTable(api, table, true)).map((row) => rowKey(table, row));
|
|
54
67
|
const foreign = foreignRows(target, rows);
|
|
55
68
|
if (CONTENT_TABLES.some((t) => foreign[t] > 0)) {
|
|
56
69
|
out(notEmptyMessage(foreign));
|
|
57
70
|
return 1;
|
|
58
71
|
}
|
|
72
|
+
// The files that travel (a file missing in the source as well is in manifest.media.missing), each sent whole with its
|
|
73
|
+
// length: the service streams it into the bucket (spec 2026-10-18 media on R2 and Images §4).
|
|
74
|
+
const files = rows.assets.map((asset) => ({ path: String(asset.path), mime: String(asset.mime), file: join(dir, "media", String(asset.path)) }))
|
|
75
|
+
.filter((m) => existsSync(m.file));
|
|
59
76
|
const newRows = missingRows(target, rows);
|
|
60
77
|
let newFiles = 0;
|
|
61
|
-
// The rebuild after the writes assembles with the app's config: a folder without one is refused before them.
|
|
62
|
-
const config = deps.config ?? (await loadConfig(cwd));
|
|
63
|
-
let rebuilt = { pages: 0, entries: 0 };
|
|
64
78
|
try {
|
|
65
79
|
// Media first, so no ready asset row ever points at a missing file.
|
|
66
|
-
for (const
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
if (!existsSync(file))
|
|
70
|
-
continue; // missing in the source as well (manifest.media.missing)
|
|
71
|
-
const up = await db.storage.from(MEDIA_BUCKET).upload(`${space}/${path}`, await readFile(file), {
|
|
72
|
-
contentType: String(asset.mime),
|
|
73
|
-
upsert: false,
|
|
74
|
-
cacheControl: String(MEDIA_CACHE_SECONDS),
|
|
75
|
-
});
|
|
76
|
-
if (up.error && !isAlreadyExists(up.error))
|
|
77
|
-
throw new Error(`uploading ${path}: ${up.error.message}`);
|
|
78
|
-
if (!up.error)
|
|
80
|
+
for (const m of files) {
|
|
81
|
+
const up = await api.postBytes(mediaUrl(m.path), new Uint8Array(await readFile(m.file)), m.mime);
|
|
82
|
+
if (up.created)
|
|
79
83
|
newFiles++;
|
|
80
84
|
}
|
|
81
85
|
const size = deps.batchSize ?? BATCH_ROWS;
|
|
82
86
|
for (const { table, rows: list } of insertPlan(rows)) {
|
|
83
|
-
for (
|
|
87
|
+
for (const [i, batch] of batches(list, size).entries()) {
|
|
84
88
|
deps.beforeBatch?.(table, i);
|
|
85
|
-
|
|
86
|
-
const res = await db.from(table).upsert(batch, { onConflict: CONFLICT_COLUMNS[table], ignoreDuplicates: true });
|
|
87
|
-
if (res.error)
|
|
88
|
-
throw new Error(`inserting ${table}: ${res.error.message}`);
|
|
89
|
+
await api.post(`/transfer/rows/${table}`, { rows: batch });
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
|
-
// The delivery tables are derived, never exported: rebuilt from what landed (spec 2026-10-07 delivery model §2).
|
|
92
|
-
// Also on a run that landed nothing new, so a run stopped here is finished by running it again.
|
|
93
|
-
rebuilt = await rebuildSpace(db, config);
|
|
94
92
|
}
|
|
95
93
|
catch (e) {
|
|
96
94
|
out(`Import stopped: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -98,12 +96,10 @@ export async function runImport(args, out, deps = {}) {
|
|
|
98
96
|
return 1;
|
|
99
97
|
}
|
|
100
98
|
if (newRows === 0 && newFiles === 0) {
|
|
101
|
-
out(nothingNew(
|
|
102
|
-
out(rebuiltMessage(rebuilt));
|
|
99
|
+
out(nothingNew());
|
|
103
100
|
return 0;
|
|
104
101
|
}
|
|
105
102
|
out(`Imported ${describeImport(rows)} into space ${space}.`);
|
|
106
|
-
out(rebuiltMessage(rebuilt));
|
|
107
103
|
if (manifest.members.length > 0) {
|
|
108
104
|
out(MEMBERS_HEADING);
|
|
109
105
|
out(` ${manifest.members.map((m) => `${m.email ?? "(no email)"} (${m.role})`).join(", ")}`);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { ResolvedConfig } from "../config.ts";
|
|
2
|
-
export declare const CONFIG_FILES: string[];
|
|
3
2
|
export declare const noConfig: (cwd: string) => string;
|
|
4
3
|
export declare function loadConfig(cwd: string): Promise<ResolvedConfig>;
|
|
5
4
|
/** One named config file (the Astro integration's `config` option names it): the project's tsconfig beside `cwd`
|
package/dist/cli/load-config.js
CHANGED
|
@@ -8,7 +8,7 @@ import { existsSync } from "node:fs";
|
|
|
8
8
|
import { dirname, join } from "node:path";
|
|
9
9
|
import { pathToFileURL } from "node:url";
|
|
10
10
|
import { tsImport } from "tsx/esm/api";
|
|
11
|
-
|
|
11
|
+
const CONFIG_FILES = ["smoodly.config.ts", "smoodly/config.ts", "src/smoodly/config.ts"];
|
|
12
12
|
export const noConfig = (cwd) => `smoodly: no ${CONFIG_FILES.join(", ")} in ${cwd} — run this in your app's folder.`;
|
|
13
13
|
export async function loadConfig(cwd) {
|
|
14
14
|
const file = CONFIG_FILES.map((name) => join(cwd, name)).find((path) => existsSync(path));
|
package/dist/cli/main.d.ts
CHANGED
|
@@ -1,24 +1,16 @@
|
|
|
1
|
-
import { type MigrateArgs } from "./migrate.ts";
|
|
2
1
|
import { type ExportArgs } from "./export.ts";
|
|
3
2
|
import { type ImportArgs } from "./import.ts";
|
|
4
|
-
import { type
|
|
5
|
-
|
|
6
|
-
export declare const USAGE = "usage: smoodly <command> (smoodly --version prints the version)\n\n migrate [--check] [--collections <file>] [--migrations <dir>]\n Copies the core schema files this package ships that supabase/migrations/\n lacks, and writes one views file for the collections whose view is new or\n changed. Prints the push or reset command; never runs it.\n\n --check write nothing; exit 1 when anything is pending, or when\n SMOODLY_SECRET_KEY (if set) cannot read the database\n --collections <file> the module exporting `collections` (default\n smoodly/collections.ts, or src/smoodly/collections.ts\n when only that one exists)\n --migrations <dir> the app's migration folder (default supabase/migrations)\n\n export [--out <dir>]\n Writes the space SMOODLY_SECRET_KEY names \u2014 every content row, history\n included, and the media files \u2014 into a folder (default\n smoodly-export-<date>). Reads only.\n\n import <dir>\n Writes an export folder into the key's space, which must be empty or hold\n only this export's rows (an earlier run to continue). Never deletes. Ends\n by rebuilding the delivery tables with the app's config (smoodly.config.ts,\n smoodly/config.ts or src/smoodly/config.ts), so run it in the app's folder.\n\n rebuild\n Rewrites the delivery tables of the key's space: the finished document of\n every published page, entry and global, assembled with the app's config.\n A publish keeps them current and a document behind the content model is\n rebuilt by its first visitor; run this after a deploy for a warm start.\n\n add-admin <email> [--admin-url <url>]\n Makes <email> an admin of the key's space: with SMOODLY_ADMIN_PASSWORD\n set, creates the account with that password; without, mails an invite\n whose link lands on --admin-url (default: SITE_URL + /admin).";
|
|
3
|
+
import { type PushArgs } from "./push.ts";
|
|
4
|
+
export declare const USAGE = "usage: smoodly <command> (smoodly --version prints the version)\nEvery command reads SMOODLY_API_URL, SMOODLY_SPACE_ID and SMOODLY_SECRET_KEY from the app's .env.local or .env.\n\n push\n Sends the app's registry (its smoodly config), its address (SITE_URL) and\n the package version to the space. A Next site's prebuild runs it, Astro's\n build and both dev servers push on their own; run it from CI or a script.\n SMOODLY_STATIC=1 marks a static site. As a prebuild it never fails the build.\n\n export [--out <dir>]\n Writes the space SMOODLY_SECRET_KEY names \u2014 every content row, history\n included, and the media files \u2014 into a folder (default\n smoodly-export-<date>). Reads only.\n\n import <dir>\n Writes an export folder into the key's space, which must be empty or hold\n only this export's rows (an earlier run to continue). Never deletes.";
|
|
7
5
|
export type Parsed = {
|
|
8
|
-
command: "migrate";
|
|
9
|
-
args: MigrateArgs;
|
|
10
|
-
} | {
|
|
11
6
|
command: "export";
|
|
12
7
|
args: ExportArgs;
|
|
13
8
|
} | {
|
|
14
9
|
command: "import";
|
|
15
10
|
args: ImportArgs;
|
|
16
11
|
} | {
|
|
17
|
-
command: "
|
|
18
|
-
args:
|
|
19
|
-
} | {
|
|
20
|
-
command: "rebuild";
|
|
21
|
-
args: RebuildArgs;
|
|
12
|
+
command: "push";
|
|
13
|
+
args: PushArgs;
|
|
22
14
|
} | {
|
|
23
15
|
command: "help";
|
|
24
16
|
} | {
|
package/dist/cli/main.js
CHANGED
|
@@ -1,29 +1,25 @@
|
|
|
1
1
|
// packages/smoodly/src/cli/main.ts
|
|
2
|
-
// `smoodly` — the package's command line.
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
2
|
+
// `smoodly` — the package's command line. Three subcommands: push (spec
|
|
3
|
+
// 2026-10-16 the control plane §4), export and import (spec 2026-09-28). A
|
|
4
|
+
// space's schema lives in its object, sent there by the push, so there is
|
|
5
|
+
// nothing to migrate or rebuild;
|
|
6
|
+
// its members are set by smoodly.io (spec 2026-10-16 the control plane §5),
|
|
7
|
+
// so there is no add-admin. Exit codes: 0 done or nothing to do,
|
|
8
|
+
// 1 refused or failed, 2 usage.
|
|
6
9
|
import { resolve } from "node:path";
|
|
7
10
|
import { fileURLToPath } from "node:url";
|
|
8
|
-
import { DEFAULT_COLLECTIONS, runMigrate } from "./migrate.js";
|
|
9
11
|
import { runExport } from "./export.js";
|
|
10
12
|
import { runImport } from "./import.js";
|
|
11
|
-
import {
|
|
12
|
-
import { runRebuild } from "./rebuild.js";
|
|
13
|
+
import { runPush } from "./push.js";
|
|
13
14
|
import { SMOODLY_VERSION } from "../version.js";
|
|
14
15
|
export const USAGE = `usage: smoodly <command> (smoodly --version prints the version)
|
|
16
|
+
Every command reads SMOODLY_API_URL, SMOODLY_SPACE_ID and SMOODLY_SECRET_KEY from the app's .env.local or .env.
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
--check write nothing; exit 1 when anything is pending, or when
|
|
22
|
-
SMOODLY_SECRET_KEY (if set) cannot read the database
|
|
23
|
-
--collections <file> the module exporting \`collections\` (default
|
|
24
|
-
smoodly/collections.ts, or src/smoodly/collections.ts
|
|
25
|
-
when only that one exists)
|
|
26
|
-
--migrations <dir> the app's migration folder (default supabase/migrations)
|
|
18
|
+
push
|
|
19
|
+
Sends the app's registry (its smoodly config), its address (SITE_URL) and
|
|
20
|
+
the package version to the space. A Next site's prebuild runs it, Astro's
|
|
21
|
+
build and both dev servers push on their own; run it from CI or a script.
|
|
22
|
+
SMOODLY_STATIC=1 marks a static site. As a prebuild it never fails the build.
|
|
27
23
|
|
|
28
24
|
export [--out <dir>]
|
|
29
25
|
Writes the space SMOODLY_SECRET_KEY names — every content row, history
|
|
@@ -32,20 +28,7 @@ export const USAGE = `usage: smoodly <command> (smoodly --version prints the
|
|
|
32
28
|
|
|
33
29
|
import <dir>
|
|
34
30
|
Writes an export folder into the key's space, which must be empty or hold
|
|
35
|
-
only this export's rows (an earlier run to continue). Never deletes
|
|
36
|
-
by rebuilding the delivery tables with the app's config (smoodly.config.ts,
|
|
37
|
-
smoodly/config.ts or src/smoodly/config.ts), so run it in the app's folder.
|
|
38
|
-
|
|
39
|
-
rebuild
|
|
40
|
-
Rewrites the delivery tables of the key's space: the finished document of
|
|
41
|
-
every published page, entry and global, assembled with the app's config.
|
|
42
|
-
A publish keeps them current and a document behind the content model is
|
|
43
|
-
rebuilt by its first visitor; run this after a deploy for a warm start.
|
|
44
|
-
|
|
45
|
-
add-admin <email> [--admin-url <url>]
|
|
46
|
-
Makes <email> an admin of the key's space: with SMOODLY_ADMIN_PASSWORD
|
|
47
|
-
set, creates the account with that password; without, mails an invite
|
|
48
|
-
whose link lands on --admin-url (default: SITE_URL + /admin).`;
|
|
31
|
+
only this export's rows (an earlier run to continue). Never deletes.`;
|
|
49
32
|
export function parseArgs(argv) {
|
|
50
33
|
const [command, ...rest] = argv;
|
|
51
34
|
if (!command || command === "help" || argv.some((a) => a === "--help" || a === "-h"))
|
|
@@ -63,42 +46,17 @@ export function parseArgs(argv) {
|
|
|
63
46
|
}
|
|
64
47
|
return { command: "export", args };
|
|
65
48
|
}
|
|
49
|
+
if (command === "push") {
|
|
50
|
+
if (rest.length > 0)
|
|
51
|
+
return { command: "error", message: `unexpected argument "${rest[0]}"` };
|
|
52
|
+
return { command: "push", args: {} };
|
|
53
|
+
}
|
|
66
54
|
if (command === "import") {
|
|
67
55
|
if (rest.length !== 1 || rest[0].startsWith("-"))
|
|
68
56
|
return { command: "error", message: "import needs one export folder: smoodly import <dir>" };
|
|
69
57
|
return { command: "import", args: { dir: rest[0] } };
|
|
70
58
|
}
|
|
71
|
-
|
|
72
|
-
if (rest.length > 0)
|
|
73
|
-
return { command: "error", message: `unexpected argument "${rest[0]}"` };
|
|
74
|
-
return { command: "rebuild", args: {} };
|
|
75
|
-
}
|
|
76
|
-
if (command === "add-admin") {
|
|
77
|
-
const usage = { command: "error", message: "add-admin needs one email: smoodly add-admin <email> [--admin-url <url>]" };
|
|
78
|
-
const [email, ...flags] = rest;
|
|
79
|
-
if (!email || email.startsWith("-"))
|
|
80
|
-
return usage;
|
|
81
|
-
if (flags.length === 0)
|
|
82
|
-
return { command: "add-admin", args: { email } };
|
|
83
|
-
if (flags.length !== 2 || flags[0] !== "--admin-url" || flags[1].startsWith("-"))
|
|
84
|
-
return usage;
|
|
85
|
-
return { command: "add-admin", args: { email, adminUrl: flags[1] } };
|
|
86
|
-
}
|
|
87
|
-
if (command !== "migrate")
|
|
88
|
-
return { command: "error", message: `unknown command "${command}"` };
|
|
89
|
-
const args = { check: false, collections: DEFAULT_COLLECTIONS, migrations: "supabase/migrations" };
|
|
90
|
-
for (let i = 0; i < rest.length; i++) {
|
|
91
|
-
const a = rest[i];
|
|
92
|
-
if (a === "--check")
|
|
93
|
-
args.check = true;
|
|
94
|
-
else if (a === "--collections" && rest[i + 1] !== undefined)
|
|
95
|
-
args.collections = rest[++i];
|
|
96
|
-
else if (a === "--migrations" && rest[i + 1] !== undefined)
|
|
97
|
-
args.migrations = rest[++i];
|
|
98
|
-
else
|
|
99
|
-
return { command: "error", message: `unexpected argument "${a}"` };
|
|
100
|
-
}
|
|
101
|
-
return { command: "migrate", args };
|
|
59
|
+
return { command: "error", message: `unknown command "${command}"` };
|
|
102
60
|
}
|
|
103
61
|
export async function main(argv, out = console.log) {
|
|
104
62
|
const parsed = parseArgs(argv);
|
|
@@ -115,23 +73,19 @@ export async function main(argv, out = console.log) {
|
|
|
115
73
|
return 2;
|
|
116
74
|
}
|
|
117
75
|
try {
|
|
76
|
+
if (parsed.command === "push")
|
|
77
|
+
return await runPush(parsed.args, out);
|
|
118
78
|
if (parsed.command === "import")
|
|
119
79
|
return await runImport(parsed.args, out);
|
|
120
|
-
|
|
121
|
-
return await runExport(parsed.args, out);
|
|
122
|
-
if (parsed.command === "add-admin")
|
|
123
|
-
return await runAddAdmin(parsed.args, out);
|
|
124
|
-
if (parsed.command === "rebuild")
|
|
125
|
-
return await runRebuild(parsed.args, out);
|
|
126
|
-
return await runMigrate(parsed.args, out);
|
|
80
|
+
return await runExport(parsed.args, out);
|
|
127
81
|
}
|
|
128
82
|
catch (e) {
|
|
129
83
|
out(e instanceof Error ? e.message : String(e));
|
|
130
84
|
return 1;
|
|
131
85
|
}
|
|
132
86
|
}
|
|
133
|
-
// Run when this file is the entry (`tsx src/cli/main.ts
|
|
134
|
-
//
|
|
87
|
+
// Run when this file is the entry (`tsx src/cli/main.ts …`); the published bin
|
|
88
|
+
// imports `main` and runs it itself.
|
|
135
89
|
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(new URL(import.meta.url))) {
|
|
136
90
|
void main(process.argv.slice(2)).then((code) => {
|
|
137
91
|
process.exitCode = code;
|
package/dist/cli/push.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// `smoodly push` (spec 2026-10-16 the control plane §4): the app's registry, its address (SITE_URL) and the package
|
|
2
|
+
// version to the space SMOODLY_SECRET_KEY names. The Next templates' prebuild runs it; Astro's build hook and the API
|
|
3
|
+
// client in development push the same body themselves. Static mode when SMOODLY_STATIC=1, else live. A refusal exits 1,
|
|
4
|
+
// except when npm runs the command as a build's `prebuild`: a push never fails a build (the spec's rule), so there it
|
|
5
|
+
// says what failed and exits 0, and the site runs against the schema the space has.
|
|
6
|
+
import { apiEnv } from "../env.js";
|
|
7
|
+
import { pushBody, pushSchema } from "../push.js";
|
|
8
|
+
import { loadAppEnv } from "./app-env.js";
|
|
9
|
+
import { loadConfig } from "./load-config.js";
|
|
10
|
+
export async function runPush(_args, out, deps = {}) {
|
|
11
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
12
|
+
const env = deps.env ?? process.env;
|
|
13
|
+
loadAppEnv(cwd, env);
|
|
14
|
+
try {
|
|
15
|
+
const config = await loadConfig(cwd);
|
|
16
|
+
const { apiUrl, spaceId, key } = apiEnv(env);
|
|
17
|
+
const body = pushBody(config, env, env.SMOODLY_STATIC === "1" ? "static" : "live");
|
|
18
|
+
const { version, changed } = await pushSchema({ apiUrl, spaceId, key, fetch: deps.fetch }, body);
|
|
19
|
+
out(changed ? `Pushed schema version ${version} to ${spaceId}.` : `Schema unchanged (version ${version}).`);
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
24
|
+
if (env.npm_lifecycle_event === "prebuild") {
|
|
25
|
+
out(`smoodly: the schema push failed — ${message}; the site runs against the schema the space has.`);
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
out(message);
|
|
29
|
+
return 1;
|
|
30
|
+
}
|
|
31
|
+
}
|
package/dist/collections.d.ts
CHANGED
|
@@ -8,7 +8,6 @@ export type EntryOrder = "manual" | {
|
|
|
8
8
|
direction: "asc" | "desc";
|
|
9
9
|
};
|
|
10
10
|
export declare const DEFAULT_ORDER: EntryOrder;
|
|
11
|
-
export declare const BUILT_IN_ORDER_FIELDS: readonly ["createdAt", "updatedAt"];
|
|
12
11
|
export type CollectionSchema<F extends BuilderMap = BuilderMap, N extends string = string> = {
|
|
13
12
|
/** "global" marks a code-declared singleton (spec 2026-09-07 decision
|
|
14
13
|
* 4): a global item IS a collection with one entry, so both entry
|
package/dist/collections.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SEO_DESCRIPTOR, SEO_KEY, validateCollectionSeo } from "./seo.js";
|
|
2
2
|
export const DEFAULT_ORDER = { by: "createdAt", direction: "desc" };
|
|
3
|
-
|
|
3
|
+
const BUILT_IN_ORDER_FIELDS = ["createdAt", "updatedAt"];
|
|
4
4
|
export const collectionOrder = (c) => c.order ?? DEFAULT_ORDER;
|
|
5
5
|
export const collectionDepth = (c) => c.tree?.depth ?? 1;
|
|
6
6
|
export function collection(input) {
|
package/dist/config.d.ts
CHANGED
|
@@ -3,8 +3,10 @@ import type { CollectionSchema } from "./collections.ts";
|
|
|
3
3
|
import type { GlobalSchema } from "./global.ts";
|
|
4
4
|
import { type Addresses } from "./addresses.ts";
|
|
5
5
|
import type { Anchors } from "./anchors.ts";
|
|
6
|
+
import { type ImageQuality } from "./media-cut.ts";
|
|
6
7
|
import { type StyleValues, type StyleValuesInput } from "./styles.ts";
|
|
7
8
|
import { type CustomConfigInput, type ResolvedCustom } from "./custom/index.ts";
|
|
9
|
+
import type { LocaleRouting } from "./astro/locale-paths.ts";
|
|
8
10
|
/** A section, element or page schema as the config reads it. */
|
|
9
11
|
export type RegisteredSchema = {
|
|
10
12
|
kind: string;
|
|
@@ -58,16 +60,24 @@ export type SmoodlyConfig = {
|
|
|
58
60
|
database?: {
|
|
59
61
|
provider: string;
|
|
60
62
|
};
|
|
61
|
-
/** Upload limits (spec 2026-09-08 media §10): bytes per file and the accepted mime types.
|
|
63
|
+
/** Upload limits (spec 2026-09-08 media §10): bytes per file and the accepted mime types. The project's image
|
|
64
|
+
* quality and srcset cap (spec 2026-10-18 image quality and max width §1), under a field's and a view's own. */
|
|
62
65
|
media?: {
|
|
63
66
|
maxSize?: number;
|
|
64
67
|
accept?: string[];
|
|
68
|
+
quality?: ImageQuality;
|
|
69
|
+
maxWidth?: number;
|
|
65
70
|
};
|
|
66
71
|
/** Where the admin is mounted (spec 2026-09-17): must match the route
|
|
67
72
|
* folder under app/(smoodly)/. Default "/admin". */
|
|
68
73
|
admin?: {
|
|
69
74
|
path?: string;
|
|
70
75
|
};
|
|
76
|
+
/** Where the default locale's pages live (spec 2026-10-16 the control plane §4): at the root (default), or under
|
|
77
|
+
* its code like every other locale. Astro's integration holds it equal to astro.config's i18n. */
|
|
78
|
+
routing?: {
|
|
79
|
+
prefixDefaultLocale: boolean;
|
|
80
|
+
};
|
|
71
81
|
};
|
|
72
82
|
/** The registry as defineConfig resolves it: schemas, and page types only under `pages`. */
|
|
73
83
|
export type Registry = {
|
|
@@ -77,7 +87,7 @@ export type Registry = {
|
|
|
77
87
|
globals: GlobalSchema[];
|
|
78
88
|
pages: RegisteredSchema[];
|
|
79
89
|
};
|
|
80
|
-
export type ResolvedConfig = Omit<SmoodlyConfig, "styles" | "custom" | "admin" | "registry"> & {
|
|
90
|
+
export type ResolvedConfig = Omit<SmoodlyConfig, "styles" | "custom" | "admin" | "registry" | "routing"> & {
|
|
81
91
|
registry: Registry;
|
|
82
92
|
homePageSlug: string;
|
|
83
93
|
/** How deep editors may nest pages in the tree. Default 1 (flat). */
|
|
@@ -93,17 +103,22 @@ export type ResolvedConfig = Omit<SmoodlyConfig, "styles" | "custom" | "admin" |
|
|
|
93
103
|
media: {
|
|
94
104
|
maxSize: number;
|
|
95
105
|
accept: string[];
|
|
106
|
+
quality?: ImageQuality;
|
|
107
|
+
maxWidth?: number;
|
|
96
108
|
};
|
|
97
109
|
/** The admin's mount path, always present. */
|
|
98
110
|
admin: {
|
|
99
111
|
path: string;
|
|
100
112
|
};
|
|
113
|
+
/** Where the default locale's pages live; `prefixDefaultLocale` is false unless the config says otherwise. */
|
|
114
|
+
routing: {
|
|
115
|
+
prefixDefaultLocale: boolean;
|
|
116
|
+
};
|
|
101
117
|
/** Code-owned addresses by owner key (spec 2026-09-29 nested paths §3). */
|
|
102
118
|
addresses: Addresses;
|
|
103
119
|
/** The path index's view of the code-owned addresses (spec 2026-09-29 anchored paths §2): pass it to the index. */
|
|
104
120
|
anchors: Anchors;
|
|
105
121
|
};
|
|
106
|
-
/** `global`: the placement node type and an admin route; `asset`: the refs
|
|
107
|
-
* target of every media edge; `media`: the admin's Media screen route. */
|
|
108
|
-
export declare const RESERVED_NAMES: readonly string[];
|
|
109
122
|
export declare function defineConfig(config: SmoodlyConfig): ResolvedConfig;
|
|
123
|
+
/** The address rule the config's routing asks for: a prefixed default locale, whose bare root goes to its home. */
|
|
124
|
+
export declare const configRouting: (config: Pick<ResolvedConfig, "routing">) => LocaleRouting;
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { assertSegment, fixedPageSegments } from "./paths.js";
|
|
2
2
|
import { addressMap, assertAddress, codeAddresses, lastPart } from "./addresses.js";
|
|
3
3
|
import { DEFAULT_MEDIA } from "./media.js";
|
|
4
|
+
import { isImageQuality, isMaxWidth, qualityError } from "./media-cut.js";
|
|
4
5
|
import { DEFAULT_ADMIN_PATH, assertAdminPath, isAdminSlug } from "./admin-path.js";
|
|
5
6
|
import { resolveStyleValues, styleDescriptors } from "./styles.js";
|
|
6
7
|
import { resolveCustom } from "./custom/index.js";
|
|
@@ -8,7 +9,7 @@ import { isValidZone } from "./time-zone.js";
|
|
|
8
9
|
import { schemaOnly } from "./registration.js";
|
|
9
10
|
/** `global`: the placement node type and an admin route; `asset`: the refs
|
|
10
11
|
* target of every media edge; `media`: the admin's Media screen route. */
|
|
11
|
-
|
|
12
|
+
const RESERVED_NAMES = ["global", "asset", "media"];
|
|
12
13
|
const isReserved = (name) => RESERVED_NAMES.includes(name);
|
|
13
14
|
function normalizeRegistry(config) {
|
|
14
15
|
const collections = config.registry.collections ?? [];
|
|
@@ -234,13 +235,23 @@ export function defineConfig(config) {
|
|
|
234
235
|
const styles = resolveStyleValues(config.styles);
|
|
235
236
|
const custom = resolveCustom(config.custom);
|
|
236
237
|
const byName = new Map(registry.sections.map((s) => [s.name, s]));
|
|
238
|
+
const quality = config.media?.quality, maxWidth = config.media?.maxWidth;
|
|
237
239
|
const media = {
|
|
238
240
|
maxSize: config.media?.maxSize ?? DEFAULT_MEDIA.maxSize,
|
|
239
241
|
accept: config.media?.accept ? [...config.media.accept] : [...DEFAULT_MEDIA.accept],
|
|
242
|
+
...(quality !== undefined ? { quality } : {}),
|
|
243
|
+
...(maxWidth !== undefined ? { maxWidth } : {}),
|
|
240
244
|
};
|
|
241
245
|
if (!Number.isInteger(media.maxSize) || media.maxSize <= 0) {
|
|
242
246
|
throw new Error("smoodly: media.maxSize must be a positive integer of bytes.");
|
|
243
247
|
}
|
|
248
|
+
if (quality !== undefined && !isImageQuality(quality))
|
|
249
|
+
throw new Error(qualityError(quality, "media.quality"));
|
|
250
|
+
if (maxWidth !== undefined && !isMaxWidth(maxWidth))
|
|
251
|
+
throw new Error("smoodly: media.maxWidth must be a positive whole number of pixels.");
|
|
252
|
+
const prefixDefaultLocale = config.routing?.prefixDefaultLocale ?? false;
|
|
253
|
+
if (typeof prefixDefaultLocale !== "boolean")
|
|
254
|
+
throw new Error("smoodly: routing.prefixDefaultLocale must be true or false.");
|
|
244
255
|
const addresses = addressMap(codeAddresses({ locales: config.locales, pages: registry.pages, collections: registry.collections }));
|
|
245
256
|
return {
|
|
246
257
|
...config,
|
|
@@ -249,6 +260,7 @@ export function defineConfig(config) {
|
|
|
249
260
|
pageDepth: depth,
|
|
250
261
|
media,
|
|
251
262
|
admin: { path: adminPath },
|
|
263
|
+
routing: { prefixDefaultLocale },
|
|
252
264
|
addresses,
|
|
253
265
|
anchors: { addresses, homePageSlug, defaultLocale: config.locales.default },
|
|
254
266
|
styles,
|
|
@@ -264,3 +276,8 @@ export function defineConfig(config) {
|
|
|
264
276
|
},
|
|
265
277
|
};
|
|
266
278
|
}
|
|
279
|
+
/** The address rule the config's routing asks for: a prefixed default locale, whose bare root goes to its home. */
|
|
280
|
+
export const configRouting = (config) => ({
|
|
281
|
+
prefixDefault: config.routing.prefixDefaultLocale,
|
|
282
|
+
redirectToDefault: config.routing.prefixDefaultLocale,
|
|
283
|
+
});
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
1
|
+
import type { AdminOpCall, EntryCreateInput, OpError, OpResult, PageCreateInput } from "../admin/ops.ts";
|
|
2
|
+
import type { PathHit } from "../path-index.ts";
|
|
3
3
|
import type { FieldError } from "../admin/validate.ts";
|
|
4
4
|
import type { EntryRecord } from "../entry-store.ts";
|
|
5
5
|
import type { PageRecord } from "../store.ts";
|
|
6
6
|
import type { PageTree } from "../page-context.ts";
|
|
7
7
|
import type { ImageValue } from "../media.ts";
|
|
8
|
-
import type {
|
|
8
|
+
import type { ResolvedConfig } from "../config.ts";
|
|
9
9
|
export type UploadMeta = {
|
|
10
10
|
filename?: string;
|
|
11
11
|
mime?: string;
|
|
@@ -19,17 +19,26 @@ export declare class SmoodlyContentError extends Error {
|
|
|
19
19
|
readonly fields: FieldError[];
|
|
20
20
|
constructor(error: Pick<OpError, "code" | "message" | "fields">);
|
|
21
21
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
22
|
+
/** What content() writes through: the operations, the paths index's lookup, an upload. */
|
|
23
|
+
export type ContentTransport = {
|
|
24
|
+
op(call: AdminOpCall): Promise<OpResult<unknown>>;
|
|
25
|
+
/** The target at an address, as a draft read: what the script is writing, never an edge copy. */
|
|
26
|
+
locate(locale: string, path: string): Promise<PathHit | null>;
|
|
27
|
+
upload(bytes: Uint8Array, filename: string, mime: string | undefined, meta: UploadMeta): Promise<ImageValue>;
|
|
28
|
+
};
|
|
29
|
+
export type ContentDeps = {
|
|
30
|
+
config: ResolvedConfig;
|
|
31
|
+
href?: (path: string, locale: string) => string;
|
|
32
|
+
transport: ContentTransport;
|
|
33
|
+
};
|
|
34
|
+
/** Over the content API with the secret key (a seed, a script, the installer). A refusal throws SmoodlyContentError
|
|
35
|
+
* with the service's code and message; a network failure names SMOODLY_API_URL. */
|
|
36
|
+
export declare function apiTransport(options: {
|
|
37
|
+
apiUrl: string;
|
|
38
|
+
spaceId: string;
|
|
39
|
+
key: string;
|
|
40
|
+
fetch?: typeof fetch;
|
|
41
|
+
}): ContentTransport;
|
|
33
42
|
export declare function createSmoodlyContent(deps: ContentDeps): {
|
|
34
43
|
pages: {
|
|
35
44
|
create: (input: PageCreateInput) => Promise<PageRecord>;
|
|
@@ -77,14 +86,5 @@ export declare function createSmoodlyContent(deps: ContentDeps): {
|
|
|
77
86
|
/** A file path or bytes in, a ready asset and the value an image field stores out. */
|
|
78
87
|
upload(file: string | Uint8Array, meta?: UploadMeta): Promise<ImageValue>;
|
|
79
88
|
};
|
|
80
|
-
members: {
|
|
81
|
-
addAdmin: (email: string, options?: {
|
|
82
|
-
password?: string;
|
|
83
|
-
redirectTo?: string;
|
|
84
|
-
}) => Promise<{
|
|
85
|
-
email: string;
|
|
86
|
-
invited: boolean;
|
|
87
|
-
}>;
|
|
88
|
-
};
|
|
89
89
|
};
|
|
90
90
|
export type SmoodlyContent = ReturnType<typeof createSmoodlyContent>;
|