@trypattern/sdk 0.1.11 → 0.1.13
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 +73 -12
- package/dist/access.d.ts +0 -4
- package/dist/access.js +0 -5
- package/dist/api.d.ts +3 -1
- package/dist/api.js +5 -17
- package/dist/app.d.ts +20 -5
- package/dist/app.js +32 -2
- package/dist/blocks.d.ts +18 -0
- package/dist/blocks.js +36 -0
- package/dist/bookings.d.ts +400 -0
- package/dist/bookings.js +300 -0
- package/dist/bookingsMemory.d.ts +9 -0
- package/dist/bookingsMemory.js +79 -0
- package/dist/bookingsStore.d.ts +698 -0
- package/dist/bookingsStore.js +129 -0
- package/dist/cli/app.js +143 -59
- package/dist/cli/publishing.d.ts +3 -1
- package/dist/cli/publishing.js +33 -5
- package/dist/cli/starter.d.ts +17 -0
- package/dist/cli/starter.js +140 -0
- package/dist/components/Button.d.ts +2 -24
- package/dist/components/Button.js +4 -251
- package/dist/components/ButtonBody.d.ts +25 -0
- package/dist/components/ButtonBody.js +266 -0
- package/dist/components/Calendar.d.ts +20 -0
- package/dist/components/Calendar.js +195 -0
- package/dist/components/Card.d.ts +1 -1
- package/dist/components/Card.js +7 -29
- package/dist/components/Choice.js +27 -13
- package/dist/components/ChoiceGroup.d.ts +3 -1
- package/dist/components/ChoiceGroup.js +3 -4
- package/dist/components/ContinueWithGoogle.d.ts +11 -0
- package/dist/components/ContinueWithGoogle.js +5 -0
- package/dist/components/Dialog.d.ts +1 -1
- package/dist/components/Dialog.js +3 -1
- package/dist/components/Icon.js +1 -1
- package/dist/components/ListRow.js +2 -1
- package/dist/components/RadioGroup.d.ts +2 -1
- package/dist/components/RadioGroup.js +10 -2
- package/dist/components/Text.d.ts +1 -1
- package/dist/components/Text.js +43 -2
- package/dist/components/calendarDates.d.ts +14 -0
- package/dist/components/calendarDates.js +84 -0
- package/dist/components/controlLook.js +11 -11
- package/dist/components/icons.d.ts +9 -0
- package/dist/components/icons.js +17 -1
- package/dist/components/interaction.d.ts +1 -1
- package/dist/flows/browser.d.ts +7 -1
- package/dist/flows/browser.js +25 -7
- package/dist/flows/flowBrowser.d.ts +2 -2
- package/dist/flows/flowBrowser.js +2 -2
- package/dist/flows/flows.js +8 -2
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1 -0
- package/dist/media.d.ts +11 -1
- package/dist/patternAccess.d.ts +11 -2
- package/dist/patternAccess.js +8 -4
- package/dist/publishWire.d.ts +24 -2
- package/dist/publishWire.js +22 -2
- package/dist/react.d.ts +3 -0
- package/dist/react.js +2 -0
- package/dist/rules/checkAllFiles.js +16 -2
- package/dist/rules/lintPlugin.js +1 -0
- package/dist/schema.d.ts +3 -1
- package/dist/schema.js +1 -0
- package/dist/server/bookings.d.ts +12 -0
- package/dist/server/bookings.js +142 -0
- package/dist/server/createServer.d.ts +3 -2
- package/dist/server/createServer.js +13 -4
- package/dist/server/env.d.ts +8 -0
- package/dist/server/index.d.ts +3 -1
- package/dist/server/index.js +1 -0
- package/dist/server/media.d.ts +16 -3
- package/dist/server/media.js +72 -23
- package/dist/server/patternViewer.js +16 -10
- package/dist/server/tables.d.ts +2 -0
- package/dist/server/tables.js +17 -1
- package/dist/start.js +12 -0
- package/dist/table.js +9 -3
- package/dist/theme/scales.d.ts +1 -1
- package/dist/theme/scales.js +1 -0
- package/dist/theme/tokens.stylex.d.ts +18 -0
- package/dist/theme/tokens.stylex.js +19 -1
- package/dist/vite/localWorker.d.ts +2 -0
- package/dist/vite/localWorker.js +48 -3
- package/dist/vite/vite.js +5 -1
- package/package.json +1 -1
package/dist/server/media.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { eq } from 'drizzle-orm';
|
|
1
2
|
import { drizzle } from 'drizzle-orm/d1';
|
|
2
3
|
import { MEDIA_LIMIT_BYTES, media, mediaPath } from "../media.js";
|
|
3
4
|
import { isHeic, readImageHeader } from "./imageHeader.js";
|
|
4
|
-
import { sqlTable } from "./tables.js";
|
|
5
|
+
import { column, sqlTable } from "./tables.js";
|
|
5
6
|
const FORM_OVERHEAD_BYTES = 64 * 1024;
|
|
6
7
|
const ALT_LIMIT = 500;
|
|
7
8
|
const KEY = /^images\/[0-9a-f]{16}\.(?:jpg|png|webp|gif|avif)$/;
|
|
8
|
-
const
|
|
9
|
+
const MEDIA_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
10
|
+
export const IMMUTABLE = 'public, max-age=31536000, immutable';
|
|
9
11
|
const PLACEHOLDER = {
|
|
10
12
|
width: 24,
|
|
11
13
|
quality: 50,
|
|
@@ -18,6 +20,7 @@ const NO_ALT = 'Describe this photo for someone who can’t see it: alt is requi
|
|
|
18
20
|
const TOO_LARGE = 'Photos can be up to 10 MB.';
|
|
19
21
|
const HEIC = 'This is an iPhone HEIC photo, which browsers can’t show. Send it as a JPEG instead.';
|
|
20
22
|
const UNKNOWN_TYPE = 'Photos must be JPEG, PNG, WebP, GIF or AVIF.';
|
|
23
|
+
const BAD_ID = 'id must be 1 to 64 letters, numbers, hyphens or underscores.';
|
|
21
24
|
const target = sqlTable(media);
|
|
22
25
|
function hex(bytes) {
|
|
23
26
|
return [...new Uint8Array(bytes)]
|
|
@@ -55,6 +58,25 @@ async function placeholderOf(c, key) {
|
|
|
55
58
|
return null;
|
|
56
59
|
}
|
|
57
60
|
}
|
|
61
|
+
export async function photoFacts(buffer) {
|
|
62
|
+
if (buffer.byteLength > MEDIA_LIMIT_BYTES) {
|
|
63
|
+
return { problem: TOO_LARGE, status: 413 };
|
|
64
|
+
}
|
|
65
|
+
const bytes = new Uint8Array(buffer);
|
|
66
|
+
if (isHeic(bytes))
|
|
67
|
+
return { problem: HEIC, status: 415 };
|
|
68
|
+
const header = readImageHeader(bytes);
|
|
69
|
+
if (header === undefined)
|
|
70
|
+
return { problem: UNKNOWN_TYPE, status: 415 };
|
|
71
|
+
const digest = await crypto.subtle.digest('SHA-256', buffer);
|
|
72
|
+
return {
|
|
73
|
+
key: `images/${hex(digest).slice(0, 16)}.${header.extension}`,
|
|
74
|
+
type: header.type,
|
|
75
|
+
width: header.width,
|
|
76
|
+
height: header.height,
|
|
77
|
+
bytes: bytes.byteLength
|
|
78
|
+
};
|
|
79
|
+
}
|
|
58
80
|
export async function uploadMedia(c) {
|
|
59
81
|
const viewer = c.get('viewer');
|
|
60
82
|
if (viewer === null)
|
|
@@ -71,6 +93,7 @@ export async function uploadMedia(c) {
|
|
|
71
93
|
const form = await c.req.formData().catch(() => undefined);
|
|
72
94
|
const file = form?.get('file');
|
|
73
95
|
const alt = form?.get('alt');
|
|
96
|
+
const id = form?.get('id') ?? null;
|
|
74
97
|
if (!(file instanceof File)) {
|
|
75
98
|
return c.json({ error: 'Send the photo as the form field "file".' }, 400);
|
|
76
99
|
}
|
|
@@ -80,31 +103,38 @@ export async function uploadMedia(c) {
|
|
|
80
103
|
if (alt.trim().length > ALT_LIMIT) {
|
|
81
104
|
return c.json({ error: `Keep alt to ${ALT_LIMIT} characters.` }, 400);
|
|
82
105
|
}
|
|
106
|
+
if (id !== null && (typeof id !== 'string' || !MEDIA_ID.test(id))) {
|
|
107
|
+
return c.json({ error: BAD_ID }, 400);
|
|
108
|
+
}
|
|
83
109
|
if (file.size > MEDIA_LIMIT_BYTES)
|
|
84
110
|
return c.json({ error: TOO_LARGE }, 413);
|
|
85
111
|
const buffer = await file.arrayBuffer();
|
|
86
|
-
const
|
|
87
|
-
if (
|
|
88
|
-
return c.json({ error:
|
|
89
|
-
const
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
112
|
+
const facts = await photoFacts(buffer);
|
|
113
|
+
if ('problem' in facts)
|
|
114
|
+
return c.json({ error: facts.problem }, facts.status);
|
|
115
|
+
const database = drizzle(c.env.DB);
|
|
116
|
+
if (id !== null) {
|
|
117
|
+
const existing = await database
|
|
118
|
+
.select()
|
|
119
|
+
.from(target)
|
|
120
|
+
.where(eq(column(target, 'id'), id))
|
|
121
|
+
.get();
|
|
122
|
+
if (existing !== undefined) {
|
|
123
|
+
return Reflect.get(existing, 'key') === facts.key
|
|
124
|
+
? c.json(existing, 200)
|
|
125
|
+
: c.json({ error: `Another photo already has the id ${id}.` }, 409);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
await bucket.put(facts.key, buffer, {
|
|
129
|
+
httpMetadata: { contentType: facts.type, cacheControl: IMMUTABLE }
|
|
96
130
|
});
|
|
97
|
-
const row = await
|
|
131
|
+
const row = await database
|
|
98
132
|
.insert(target)
|
|
99
133
|
.values({
|
|
100
|
-
id: crypto.randomUUID(),
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
placeholder: await placeholderOf(c, key),
|
|
105
|
-
alt: alt.trim(),
|
|
106
|
-
type: header.type,
|
|
107
|
-
bytes: bytes.byteLength
|
|
134
|
+
id: id ?? crypto.randomUUID(),
|
|
135
|
+
...facts,
|
|
136
|
+
placeholder: await placeholderOf(c, facts.key),
|
|
137
|
+
alt: alt.trim()
|
|
108
138
|
})
|
|
109
139
|
.returning()
|
|
110
140
|
.get();
|
|
@@ -113,6 +143,23 @@ export async function uploadMedia(c) {
|
|
|
113
143
|
function unquoted(etag) {
|
|
114
144
|
return etag.replace(/^W\//, '').replaceAll('"', '').trim();
|
|
115
145
|
}
|
|
146
|
+
async function fromLiveSite(key) {
|
|
147
|
+
const origin = import.meta.env['PATTERN_LIVE_ORIGIN'];
|
|
148
|
+
if (typeof origin !== 'string')
|
|
149
|
+
return undefined;
|
|
150
|
+
const response = await fetch(`${origin}${mediaPath(key)}`).catch(() => undefined);
|
|
151
|
+
if (response === undefined || !response.ok) {
|
|
152
|
+
await response?.body?.cancel();
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
return new Response(response.body, {
|
|
156
|
+
headers: {
|
|
157
|
+
'cache-control': IMMUTABLE,
|
|
158
|
+
'content-type': response.headers.get('content-type') ?? 'application/octet-stream',
|
|
159
|
+
'x-content-type-options': 'nosniff'
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
116
163
|
export async function serveMedia(c) {
|
|
117
164
|
const key = decodeURIComponent(c.req.path.slice('/media/'.length));
|
|
118
165
|
const bucket = c.env.MEDIA;
|
|
@@ -121,8 +168,10 @@ export async function serveMedia(c) {
|
|
|
121
168
|
}
|
|
122
169
|
const seen = c.req.header('if-none-match');
|
|
123
170
|
const object = await bucket.get(key, seen === undefined ? {} : { onlyIf: { etagDoesNotMatch: unquoted(seen) } });
|
|
124
|
-
if (object === null)
|
|
125
|
-
|
|
171
|
+
if (object === null) {
|
|
172
|
+
const live = import.meta.env.DEV ? await fromLiveSite(key) : undefined;
|
|
173
|
+
return live ?? c.text('No such photo', 404);
|
|
174
|
+
}
|
|
126
175
|
const headers = new Headers({
|
|
127
176
|
'cache-control': IMMUTABLE,
|
|
128
177
|
'content-type': object.httpMetadata?.contentType ?? 'application/octet-stream',
|
|
@@ -1,25 +1,31 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MEDIA_WRITE, TABLES_READ, verifiedAssertion } from "../patternAccess.js";
|
|
2
2
|
import { PATTERN_KEYS } from "./patternKeys.js";
|
|
3
3
|
const BEARER = /^bearer\s+/i;
|
|
4
4
|
const SCHEMA_PATH = '/api/schema';
|
|
5
5
|
const TABLES_PATH = '/api/tables/';
|
|
6
|
+
const MEDIA_PATH = '/api/media';
|
|
6
7
|
export function bearerOf(c) {
|
|
7
8
|
const header = c.req.header('authorization') ?? '';
|
|
8
9
|
return BEARER.test(header) ? header.replace(BEARER, '').trim() : undefined;
|
|
9
10
|
}
|
|
10
|
-
function
|
|
11
|
-
const { path } = c.req;
|
|
12
|
-
|
|
13
|
-
(path === SCHEMA_PATH || path.startsWith(TABLES_PATH)))
|
|
11
|
+
function scopeOf(c) {
|
|
12
|
+
const { path, method } = c.req;
|
|
13
|
+
if (method === 'GET' &&
|
|
14
|
+
(path === SCHEMA_PATH || path.startsWith(TABLES_PATH))) {
|
|
15
|
+
return TABLES_READ;
|
|
16
|
+
}
|
|
17
|
+
return method === 'POST' && path === MEDIA_PATH ? MEDIA_WRITE : undefined;
|
|
14
18
|
}
|
|
15
19
|
export async function attachPatternViewer(c, next, token) {
|
|
16
|
-
const
|
|
17
|
-
|
|
20
|
+
const scope = scopeOf(c);
|
|
21
|
+
const email = scope === undefined
|
|
22
|
+
? undefined
|
|
23
|
+
: await verifiedAssertion(token, {
|
|
18
24
|
hostname: new URL(c.req.url).hostname,
|
|
19
25
|
keys: PATTERN_KEYS,
|
|
20
|
-
now: Math.floor(Date.now() / 1000)
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
now: Math.floor(Date.now() / 1000),
|
|
27
|
+
scope
|
|
28
|
+
});
|
|
23
29
|
if (email === undefined)
|
|
24
30
|
return c.json({ error: 'Signed out' }, 401);
|
|
25
31
|
c.set('viewer', { userId: `pattern:${email}`, isAdmin: true });
|
package/dist/server/tables.d.ts
CHANGED
|
@@ -10,7 +10,9 @@ type SqlTable = SQLiteTableWithColumns<{
|
|
|
10
10
|
}>;
|
|
11
11
|
export declare function sqlTable(table: Table): SqlTable;
|
|
12
12
|
export declare function sqlTables<Group extends Tables>(tables: Group): Record<keyof Group, SqlTable>;
|
|
13
|
+
export declare function column(target: SqlTable, key: string): SqlTable["_"]["columns"][string];
|
|
13
14
|
export declare function seedSql<Group extends Tables>(tables: Group, seed: Seed<Group>): string;
|
|
15
|
+
export declare function starterSql<Group extends Tables>(tables: Group, rows: Seed<Group>): string;
|
|
14
16
|
export declare function readRows(d1: D1Database, table: Table): Promise<readonly Record<string, unknown>[]>;
|
|
15
17
|
export declare function readRow(d1: D1Database, table: Table, id: string): Promise<Record<string, unknown> | undefined>;
|
|
16
18
|
export declare function serveTables<Group extends Tables>(tables: Group): import("hono/hono-base").HonoBase<ServerEnv, {
|
package/dist/server/tables.js
CHANGED
|
@@ -47,7 +47,7 @@ export function sqlTable(table) {
|
|
|
47
47
|
export function sqlTables(tables) {
|
|
48
48
|
return Object.fromEntries(Object.entries(tables).map(([key, table]) => [key, sqlTable(table)]));
|
|
49
49
|
}
|
|
50
|
-
function column(target, key) {
|
|
50
|
+
export function column(target, key) {
|
|
51
51
|
const found = Reflect.get(target, key);
|
|
52
52
|
if (found === undefined)
|
|
53
53
|
throw new Error(`No column ${key}`);
|
|
@@ -99,6 +99,22 @@ export function seedSql(tables, seed) {
|
|
|
99
99
|
});
|
|
100
100
|
return [...users, ...rowsSql].join('\n');
|
|
101
101
|
}
|
|
102
|
+
export function starterSql(tables, rows) {
|
|
103
|
+
return Object.values(tables)
|
|
104
|
+
.flatMap((table) => {
|
|
105
|
+
const listed = seededRows(rows, table.name);
|
|
106
|
+
if (listed.length === 0 || table.owned)
|
|
107
|
+
return [];
|
|
108
|
+
const keys = ['id', 'createdAt', ...Object.keys(table.columns)];
|
|
109
|
+
const values = listed
|
|
110
|
+
.map((row) => `(${keys.map((key) => sqlLiteral((row[key] ?? null))).join(', ')})`)
|
|
111
|
+
.join(',\n ');
|
|
112
|
+
return [
|
|
113
|
+
`INSERT OR IGNORE INTO ${snakeCase(table.name)} (${keys.map(snakeCase).join(', ')}) VALUES\n ${values};`
|
|
114
|
+
];
|
|
115
|
+
})
|
|
116
|
+
.join('\n');
|
|
117
|
+
}
|
|
102
118
|
function selection(table, target) {
|
|
103
119
|
return Object.fromEntries([
|
|
104
120
|
'id',
|
package/dist/start.js
CHANGED
|
@@ -17,6 +17,8 @@ const TEST_SEED = 'patternSeed';
|
|
|
17
17
|
const INLINE_DATA_ID = 'pattern-data';
|
|
18
18
|
const TEST_ADMINS = 'patternAdmins';
|
|
19
19
|
const TEST_WORLD = 'patternWorld';
|
|
20
|
+
const TEST_TITLE = 'patternTitle';
|
|
21
|
+
const TEST_SIGNED_IN_AS = 'patternSignedInAs';
|
|
20
22
|
function testSeed() {
|
|
21
23
|
const seed = Reflect.get(window, TEST_SEED);
|
|
22
24
|
return typeof seed === 'object' && seed !== null ? seed : {};
|
|
@@ -27,6 +29,14 @@ function testAdmins() {
|
|
|
27
29
|
? admins.filter((email) => typeof email === 'string')
|
|
28
30
|
: [];
|
|
29
31
|
}
|
|
32
|
+
function testTitle() {
|
|
33
|
+
const title = Reflect.get(window, TEST_TITLE);
|
|
34
|
+
return typeof title === 'string' ? title : '';
|
|
35
|
+
}
|
|
36
|
+
function testSignedInAs() {
|
|
37
|
+
const email = Reflect.get(window, TEST_SIGNED_IN_AS);
|
|
38
|
+
return typeof email === 'string' ? email : null;
|
|
39
|
+
}
|
|
30
40
|
function readInlineData() {
|
|
31
41
|
const text = document.getElementById(INLINE_DATA_ID)?.textContent ?? '';
|
|
32
42
|
if (text === '')
|
|
@@ -58,7 +68,9 @@ export function startApp(App, { theme = [], icons = {}, onInteraction } = {}) {
|
|
|
58
68
|
const app = new App(router, {
|
|
59
69
|
api: browserApi(tables, inMemory ? testSeed() : {}, gate, {
|
|
60
70
|
inMemory,
|
|
71
|
+
signedInAs: inMemory ? testSignedInAs() : null,
|
|
61
72
|
admins: inMemory ? testAdmins() : [],
|
|
73
|
+
title: inMemory ? testTitle() : '',
|
|
62
74
|
...(services === undefined ? {} : { services }),
|
|
63
75
|
...(memory === undefined ? {} : { memory }),
|
|
64
76
|
world
|
package/dist/table.js
CHANGED
|
@@ -2,7 +2,12 @@ import { z } from 'zod';
|
|
|
2
2
|
const NAME = /^[a-z][a-zA-Z0-9]*$/;
|
|
3
3
|
const RESERVED = ['id', 'createdAt', 'ownerId'];
|
|
4
4
|
const RESERVED_TABLES = ['users', 'sessions', 'session'];
|
|
5
|
-
const SDK_TABLES =
|
|
5
|
+
const SDK_TABLES = {
|
|
6
|
+
media: 'media',
|
|
7
|
+
bookingRules: '...bookingTables',
|
|
8
|
+
bookingBlackouts: '...bookingTables',
|
|
9
|
+
bookingRequests: '...bookingTables'
|
|
10
|
+
};
|
|
6
11
|
const SEEDED_AT = Date.UTC(2000, 0, 1);
|
|
7
12
|
function problemIn(name, definition) {
|
|
8
13
|
const { access, columns } = definition;
|
|
@@ -39,8 +44,9 @@ function table(name, definition, isPatterns = false) {
|
|
|
39
44
|
if (RESERVED_TABLES.includes(name)) {
|
|
40
45
|
throw new Error(`A table can't be called "${name}": Pattern keeps its own ${name} for sign-in`);
|
|
41
46
|
}
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
const own = Object.hasOwn(SDK_TABLES, name) ? SDK_TABLES[name] : undefined;
|
|
48
|
+
if (!isPatterns && own !== undefined) {
|
|
49
|
+
throw new Error(`A table can't be called "${name}": Pattern has its own ${name} table. Add it to tables with ${own} from '@trypattern/sdk/schema'`);
|
|
44
50
|
}
|
|
45
51
|
const reserved = RESERVED.find((key) => key in columns);
|
|
46
52
|
if (reserved !== undefined) {
|
package/dist/theme/scales.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export declare const ascendingScales: {
|
|
|
5
5
|
readonly controlGaps: readonly ["gapXs", "gapSm", "gapMd", "gapLg", "gapXl"];
|
|
6
6
|
readonly contentWidths: readonly ["contentXs", "contentSm", "contentMd", "contentLg"];
|
|
7
7
|
readonly iconSizes: readonly ["xs", "sm", "md", "lg", "xl"];
|
|
8
|
-
readonly fontSizes: readonly ["captionXs", "captionSm", "captionBase", "captionLg", "bodySm", "bodyBase", "bodyLg", "heading6", "heading5", "heading4", "heading3", "heading2", "heading1"];
|
|
8
|
+
readonly fontSizes: readonly ["captionXs", "captionSm", "captionBase", "captionLg", "bodySm", "bodyMd", "bodyBase", "bodyLg", "heading6", "heading5", "heading4", "heading3", "heading2", "heading1"];
|
|
9
9
|
readonly lineHeights: readonly ["captionXs", "caption", "title", "bodySm", "body", "heading6", "heading5", "heading4", "heading3", "heading2", "heading1"];
|
|
10
10
|
};
|
|
11
11
|
export declare const scaleGroups: {
|
package/dist/theme/scales.js
CHANGED
|
@@ -116,6 +116,16 @@ export declare const controls: stylex.VarGroup<Readonly<{
|
|
|
116
116
|
gapMd: string;
|
|
117
117
|
gapLg: string;
|
|
118
118
|
gapXl: string;
|
|
119
|
+
fontSizeXs: string;
|
|
120
|
+
fontSizeSm: string;
|
|
121
|
+
fontSizeMd: string;
|
|
122
|
+
fontSizeLg: string;
|
|
123
|
+
fontSizeXl: string;
|
|
124
|
+
lineHeightXs: string;
|
|
125
|
+
lineHeightSm: string;
|
|
126
|
+
lineHeightMd: string;
|
|
127
|
+
lineHeightLg: string;
|
|
128
|
+
lineHeightXl: string;
|
|
119
129
|
}>, symbol>;
|
|
120
130
|
export declare const iconSizes: stylex.VarGroup<Readonly<{
|
|
121
131
|
xs: string;
|
|
@@ -161,6 +171,7 @@ export declare const fontSizes: stylex.VarGroup<Readonly<{
|
|
|
161
171
|
captionBase: string;
|
|
162
172
|
captionLg: string;
|
|
163
173
|
bodySm: string;
|
|
174
|
+
bodyMd: string;
|
|
164
175
|
bodyBase: string;
|
|
165
176
|
bodyLg: string;
|
|
166
177
|
heading6: string;
|
|
@@ -170,12 +181,19 @@ export declare const fontSizes: stylex.VarGroup<Readonly<{
|
|
|
170
181
|
heading2: string;
|
|
171
182
|
heading1: string;
|
|
172
183
|
}>, symbol>;
|
|
184
|
+
export declare const trackings: stylex.VarGroup<Readonly<{
|
|
185
|
+
hero: string;
|
|
186
|
+
display: string;
|
|
187
|
+
section: string;
|
|
188
|
+
heading: string;
|
|
189
|
+
}>, symbol>;
|
|
173
190
|
export declare const lineHeights: stylex.VarGroup<Readonly<{
|
|
174
191
|
captionXs: string;
|
|
175
192
|
caption: string;
|
|
176
193
|
title: string;
|
|
177
194
|
bodySm: string;
|
|
178
195
|
body: string;
|
|
196
|
+
bodyLg: string;
|
|
179
197
|
heading6: string;
|
|
180
198
|
heading5: string;
|
|
181
199
|
heading4: string;
|
|
@@ -115,7 +115,17 @@ export const controls = stylex.defineVars({
|
|
|
115
115
|
gapSm: '4px',
|
|
116
116
|
gapMd: '6px',
|
|
117
117
|
gapLg: '6px',
|
|
118
|
-
gapXl: '8px'
|
|
118
|
+
gapXl: '8px',
|
|
119
|
+
fontSizeXs: '12px',
|
|
120
|
+
fontSizeSm: '13px',
|
|
121
|
+
fontSizeMd: '13px',
|
|
122
|
+
fontSizeLg: '13px',
|
|
123
|
+
fontSizeXl: '13px',
|
|
124
|
+
lineHeightXs: '16px',
|
|
125
|
+
lineHeightSm: '20px',
|
|
126
|
+
lineHeightMd: '20px',
|
|
127
|
+
lineHeightLg: '20px',
|
|
128
|
+
lineHeightXl: '20px'
|
|
119
129
|
});
|
|
120
130
|
export const iconSizes = stylex.defineVars({
|
|
121
131
|
xs: '12px',
|
|
@@ -161,6 +171,7 @@ export const fontSizes = stylex.defineVars({
|
|
|
161
171
|
captionBase: '11px',
|
|
162
172
|
captionLg: '12px',
|
|
163
173
|
bodySm: '13px',
|
|
174
|
+
bodyMd: '14px',
|
|
164
175
|
bodyBase: '15px',
|
|
165
176
|
bodyLg: '18px',
|
|
166
177
|
heading6: '20px',
|
|
@@ -170,12 +181,19 @@ export const fontSizes = stylex.defineVars({
|
|
|
170
181
|
heading2: '44px',
|
|
171
182
|
heading1: '56px'
|
|
172
183
|
});
|
|
184
|
+
export const trackings = stylex.defineVars({
|
|
185
|
+
hero: '-0.04em',
|
|
186
|
+
display: '-0.03em',
|
|
187
|
+
section: '-0.02em',
|
|
188
|
+
heading: '-0.01em'
|
|
189
|
+
});
|
|
173
190
|
export const lineHeights = stylex.defineVars({
|
|
174
191
|
captionXs: '12px',
|
|
175
192
|
caption: '16px',
|
|
176
193
|
title: '18px',
|
|
177
194
|
bodySm: '20px',
|
|
178
195
|
body: '24px',
|
|
196
|
+
bodyLg: '28px',
|
|
179
197
|
heading6: '24px',
|
|
180
198
|
heading5: '32px',
|
|
181
199
|
heading4: '36px',
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { Plugin } from 'vite';
|
|
2
2
|
import type { Seed, Tables } from '../table.ts';
|
|
3
|
+
import type { StarterPhotoFile } from '../cli/starter.ts';
|
|
4
|
+
export declare function putLocalPhotos(root: string, photos: readonly StarterPhotoFile[]): void;
|
|
3
5
|
export declare function resetLocalDatabase(root: string, tables: Tables, seed: Seed<Tables>): void;
|
|
4
6
|
export declare function localWorker<Group extends Tables>(tables: Group, seed: Seed<Group>, title: string): Plugin;
|
package/dist/vite/localWorker.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { randomBytes } from 'node:crypto';
|
|
3
|
-
import { appendFileSync, existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
+
import { IMMUTABLE } from "../server/media.js";
|
|
5
6
|
import { seedTitleSql } from "../server/settings.js";
|
|
6
7
|
import { seedSql } from "../server/tables.js";
|
|
7
8
|
import { snakeCase } from "../table.js";
|
|
9
|
+
import { readStarter, withStarter } from "../cli/starter.js";
|
|
8
10
|
import { toolBin } from "../cli/tools.js";
|
|
11
|
+
const NO_BUCKET = 'starter/ has photos, but wrangler.jsonc binds no R2 bucket as MEDIA. Add "r2_buckets": [{ "binding": "MEDIA", "bucket_name": "media" }].';
|
|
9
12
|
function ensureLocalSessionSecret(root) {
|
|
10
13
|
const DEV_VARS = join(root, '.dev.vars');
|
|
11
14
|
const current = existsSync(DEV_VARS) ? readFileSync(DEV_VARS, 'utf8') : '';
|
|
@@ -21,6 +24,46 @@ function wrangler(root, args) {
|
|
|
21
24
|
env: { ...process.env, CI: '1' }
|
|
22
25
|
});
|
|
23
26
|
}
|
|
27
|
+
function localMediaBucket(root) {
|
|
28
|
+
try {
|
|
29
|
+
const config = JSON.parse(readFileSync(join(root, 'wrangler.jsonc'), 'utf8'));
|
|
30
|
+
const buckets = Reflect.get(Object(config), 'r2_buckets');
|
|
31
|
+
const bound = (Array.isArray(buckets) ? buckets : []).find((bucket) => Reflect.get(Object(bucket), 'binding') === 'MEDIA');
|
|
32
|
+
const name = Reflect.get(Object(bound), 'bucket_name');
|
|
33
|
+
return typeof name === 'string' ? name : undefined;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function putLocalPhotos(root, photos) {
|
|
40
|
+
if (photos.length === 0)
|
|
41
|
+
return;
|
|
42
|
+
const bucket = localMediaBucket(root);
|
|
43
|
+
if (bucket === undefined)
|
|
44
|
+
throw new Error(NO_BUCKET);
|
|
45
|
+
const directory = join(root, '.dev');
|
|
46
|
+
mkdirSync(directory, { recursive: true });
|
|
47
|
+
const list = join(directory, 'starter-photos.json');
|
|
48
|
+
for (const type of new Set(photos.map((photo) => photo.type))) {
|
|
49
|
+
writeFileSync(list, JSON.stringify(photos
|
|
50
|
+
.filter((photo) => photo.type === type)
|
|
51
|
+
.map((photo) => ({ key: photo.key, file: join(root, photo.path) }))));
|
|
52
|
+
wrangler(root, [
|
|
53
|
+
'r2',
|
|
54
|
+
'bulk',
|
|
55
|
+
'put',
|
|
56
|
+
bucket,
|
|
57
|
+
'--local',
|
|
58
|
+
'--filename',
|
|
59
|
+
list,
|
|
60
|
+
'--content-type',
|
|
61
|
+
type,
|
|
62
|
+
'--cache-control',
|
|
63
|
+
IMMUTABLE
|
|
64
|
+
]);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
24
67
|
export function resetLocalDatabase(root, tables, seed) {
|
|
25
68
|
wrangler(root, ['d1', 'migrations', 'apply', 'DB', '--local']);
|
|
26
69
|
wrangler(root, [
|
|
@@ -49,12 +92,14 @@ function prepareLocalDatabase(root, tables, seed, title) {
|
|
|
49
92
|
export function localWorker(tables, seed, title) {
|
|
50
93
|
return {
|
|
51
94
|
name: 'pattern-local-worker',
|
|
52
|
-
config(config, { command }) {
|
|
95
|
+
async config(config, { command }) {
|
|
53
96
|
if (command !== 'serve')
|
|
54
97
|
return;
|
|
55
98
|
const root = config.root ?? process.cwd();
|
|
56
99
|
ensureLocalSessionSecret(root);
|
|
57
|
-
|
|
100
|
+
const starter = await readStarter(root, tables);
|
|
101
|
+
prepareLocalDatabase(root, tables, withStarter(seed, starter), title);
|
|
102
|
+
putLocalPhotos(root, starter?.photos ?? []);
|
|
58
103
|
}
|
|
59
104
|
};
|
|
60
105
|
}
|
package/dist/vite/vite.js
CHANGED
|
@@ -5,6 +5,7 @@ import { cloudflare } from '@cloudflare/vite-plugin';
|
|
|
5
5
|
import stylex from '@stylexjs/unplugin';
|
|
6
6
|
import react from '@vitejs/plugin-react';
|
|
7
7
|
import { defineConfig } from 'vitest/config';
|
|
8
|
+
import { hostnameOf } from "../publishWire.js";
|
|
8
9
|
import { commandBridge } from "./commandBridge.js";
|
|
9
10
|
import { designManifest } from "./designManifest.js";
|
|
10
11
|
import { localWorker } from "./localWorker.js";
|
|
@@ -57,7 +58,7 @@ const reactCompilerSkippingUnchangedRendersAndLoggingWhereItCannot = {
|
|
|
57
58
|
const FLOW_SETUP = fileURLToPath(new URL(`../flows/flowSetup${extname(import.meta.filename)}`, import.meta.url));
|
|
58
59
|
export function pattern({ tables, admins, seed = {}, world = {} }) {
|
|
59
60
|
const isVitest = process.env['VITEST'] !== undefined;
|
|
60
|
-
const { title } = readProjectFile(process.cwd());
|
|
61
|
+
const { name, title } = readProjectFile(process.cwd());
|
|
61
62
|
const isVisualFlowRun = process.env['PATTERN_VISUAL_URL'] !== undefined;
|
|
62
63
|
const stylexPlugin = stylex.vite({
|
|
63
64
|
useCSSLayers: { before: CSS_LAYERS_BEFORE_STYLEX },
|
|
@@ -87,6 +88,9 @@ export function pattern({ tables, admins, seed = {}, world = {} }) {
|
|
|
87
88
|
designFile()
|
|
88
89
|
],
|
|
89
90
|
clearScreen: false,
|
|
91
|
+
define: {
|
|
92
|
+
'import.meta.env.PATTERN_LIVE_ORIGIN': JSON.stringify(`https://${hostnameOf(name)}`)
|
|
93
|
+
},
|
|
90
94
|
optimizeDeps: sdkCompiledByStyleXButItsDependenciesPrebundled,
|
|
91
95
|
build: {
|
|
92
96
|
cssTarget: ['chrome123', 'firefox120', 'safari17.5']
|