@trypattern/sdk 0.1.21 → 0.1.22
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/cli/app.js +25 -1
- package/dist/cli/data.d.ts +5 -0
- package/dist/cli/data.js +273 -0
- package/dist/cli/publishing.d.ts +9 -2
- package/dist/cli/publishing.js +7 -7
- package/dist/components/Badge.d.ts +1 -1
- package/dist/components/Badge.js +18 -0
- package/dist/components/ButtonBody.d.ts +1 -1
- package/dist/components/ButtonBody.js +43 -0
- package/dist/patternAccess.d.ts +2 -0
- package/dist/patternAccess.js +2 -1
- package/dist/publishWire.d.ts +355 -1
- package/dist/publishWire.js +66 -1
- package/dist/server/changes.d.ts +18 -0
- package/dist/server/changes.js +113 -0
- package/dist/server/createServer.d.ts +38 -0
- package/dist/server/database.d.ts +38 -0
- package/dist/server/database.js +2 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/patternViewer.js +4 -1
- package/dist/server/session.d.ts +1 -0
- package/dist/server/session.js +3 -0
- package/package.json +1 -1
package/dist/cli/app.js
CHANGED
|
@@ -20,6 +20,8 @@ import { putLocalPhotos, resetLocalDatabase } from "../vite/localWorker.js";
|
|
|
20
20
|
import { readProjectFile } from "../vite/projectFile.js";
|
|
21
21
|
import { readStarter, starterContentOf, withStarter } from "./starter.js";
|
|
22
22
|
import { addLiveMedia, login, publish, pull, restore, versions as listVersions } from "./publishing.js";
|
|
23
|
+
import { CHANGE_SET_ROWS } from "../publishWire.js";
|
|
24
|
+
import { DATA_USAGE, data } from "./data.js";
|
|
23
25
|
import { toolBin } from "./tools.js";
|
|
24
26
|
function dependsOnPattern(directory) {
|
|
25
27
|
try {
|
|
@@ -84,7 +86,11 @@ const VALUE_FLAGS = new Set([
|
|
|
84
86
|
'--as',
|
|
85
87
|
'--code',
|
|
86
88
|
'--message',
|
|
87
|
-
'--alt'
|
|
89
|
+
'--alt',
|
|
90
|
+
'--row',
|
|
91
|
+
'--set',
|
|
92
|
+
'--file',
|
|
93
|
+
'--asked'
|
|
88
94
|
]);
|
|
89
95
|
const PHOTO_TYPES = {
|
|
90
96
|
'.jpg': 'image/jpeg',
|
|
@@ -349,6 +355,24 @@ own POST /api/media, then the rows. Later publishes never touch them; they are t
|
|
|
349
355
|
},
|
|
350
356
|
flags: ['--starter', '--message']
|
|
351
357
|
},
|
|
358
|
+
data: {
|
|
359
|
+
summary: 'Change the live site’s data, with the owner’s approval (app data --help)',
|
|
360
|
+
details: `${DATA_USAGE}
|
|
361
|
+
The live database belongs to the owner: changes to it go to them for approval, never straight in.
|
|
362
|
+
tables and list read the live data (columns, ids and values) through onpattern.app. add, update
|
|
363
|
+
and remove only collect changes on this computer (.dev/data-changes.json); nothing live changes.
|
|
364
|
+
Values use the table's own columns and types, as tables shows them: a price stored in cents is 1400.
|
|
365
|
+
send puts every collected change in front of the owner as one request on onpattern.app (Database)
|
|
366
|
+
and emails them. --asked is required: their request in their own words, quoted, which they see
|
|
367
|
+
beside Approve. Only the owner can approve, in their browser; send then waits for their decision
|
|
368
|
+
and prints what went live. A row the owner changed after you read it is skipped, never
|
|
369
|
+
overwritten: list it again and send a new change. The live site must run a Pattern with app data:
|
|
370
|
+
after updating @trypattern/sdk, app publish once first. status waits on the latest request; clear drops
|
|
371
|
+
collected changes. Up to ${CHANGE_SET_ROWS} changes per request. For this computer's data, edit seed.ts and run app reset.`,
|
|
372
|
+
run: (_argument, options, rest) => data(publishingIo(), ROOT, rest, options),
|
|
373
|
+
argumentCount: 3,
|
|
374
|
+
flags: ['--row', '--set', '--file', '--asked']
|
|
375
|
+
},
|
|
352
376
|
versions: {
|
|
353
377
|
summary: 'List the versions that went live, newest first',
|
|
354
378
|
details: 'Each version with its number, whether it went live, when, and who published it. app restore <number> puts one back.',
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Outcome, PublishingIo } from './publishing.ts';
|
|
2
|
+
export declare const DATA_USAGE = "app data tables\napp data list <table>\napp data add <table> --row '{\"name\":\"Gold chair\",\"price\":8}' (or --file rows.json, a JSON array)\napp data update <table> <id> --set '{\"price\":14}'\napp data remove <table> <id>\napp data send --asked \"<the owner's request, in their words>\"\napp data status\napp data clear";
|
|
3
|
+
type DataOptions = ReadonlyMap<string, string | true>;
|
|
4
|
+
export declare function data(io: PublishingIo, root: string, [action, table, id]: readonly (string | undefined)[], options: DataOptions): Promise<Outcome>;
|
|
5
|
+
export {};
|
package/dist/cli/data.js
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { CHANGE_SET_ROWS, DASHBOARD_URL, changeSet, dataChange, databaseRow, databaseRows, databaseSchema } from "../publishWire.js";
|
|
5
|
+
import { readProjectFile } from "../vite/projectFile.js";
|
|
6
|
+
import { callJson, failure, isOutcome, sayHost, signedIn, sleep } from "./publishing.js";
|
|
7
|
+
const STAGED = join('.dev', 'data-changes.json');
|
|
8
|
+
const WAIT_MS = 90_000;
|
|
9
|
+
const POLL_MS = 3000;
|
|
10
|
+
const MAX_PAGES = 50;
|
|
11
|
+
const staged = z.array(dataChange);
|
|
12
|
+
export const DATA_USAGE = `app data tables
|
|
13
|
+
app data list <table>
|
|
14
|
+
app data add <table> --row '{"name":"Gold chair","price":8}' (or --file rows.json, a JSON array)
|
|
15
|
+
app data update <table> <id> --set '{"price":14}'
|
|
16
|
+
app data remove <table> <id>
|
|
17
|
+
app data send --asked "<the owner's request, in their words>"
|
|
18
|
+
app data status
|
|
19
|
+
app data clear`;
|
|
20
|
+
function stagedPath(root) {
|
|
21
|
+
return join(root, STAGED);
|
|
22
|
+
}
|
|
23
|
+
function readStaged(root) {
|
|
24
|
+
const path = stagedPath(root);
|
|
25
|
+
if (!existsSync(path))
|
|
26
|
+
return [];
|
|
27
|
+
const parsed = staged.safeParse(JSON.parse(readFileSync(path, 'utf8')));
|
|
28
|
+
return parsed.success ? parsed.data : [];
|
|
29
|
+
}
|
|
30
|
+
function writeStaged(root, changes) {
|
|
31
|
+
const path = stagedPath(root);
|
|
32
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
33
|
+
writeFileSync(path, `${JSON.stringify(changes, null, 2)}\n`);
|
|
34
|
+
}
|
|
35
|
+
function refusal(text, code = 1) {
|
|
36
|
+
return { code, data: { error: text }, text: `✗ ${text}` };
|
|
37
|
+
}
|
|
38
|
+
function valueOf(options, flag) {
|
|
39
|
+
const value = options.get(flag);
|
|
40
|
+
return typeof value === 'string' ? value : undefined;
|
|
41
|
+
}
|
|
42
|
+
function jsonOf(text, what) {
|
|
43
|
+
try {
|
|
44
|
+
return JSON.parse(text);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
throw new Error(`${what} is not JSON: ${text.slice(0, 80)}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function rowsGiven(root, options) {
|
|
51
|
+
const row = valueOf(options, '--row');
|
|
52
|
+
const file = valueOf(options, '--file');
|
|
53
|
+
if (row !== undefined)
|
|
54
|
+
return [databaseRow.parse(jsonOf(row, '--row'))];
|
|
55
|
+
if (file === undefined)
|
|
56
|
+
throw new Error('Give the row with --row or --file');
|
|
57
|
+
const path = resolve(root, file);
|
|
58
|
+
if (!existsSync(path))
|
|
59
|
+
throw new Error(`There is no file at ${file}`);
|
|
60
|
+
return z.array(databaseRow).parse(jsonOf(readFileSync(path, 'utf8'), file));
|
|
61
|
+
}
|
|
62
|
+
function pendingLines(changes) {
|
|
63
|
+
return changes.map((change) => {
|
|
64
|
+
if (change.op === 'add')
|
|
65
|
+
return ` + ${change.table} ${JSON.stringify(change.row)}`;
|
|
66
|
+
if (change.op === 'remove')
|
|
67
|
+
return ` − ${change.table} ${change.id}`;
|
|
68
|
+
return ` ~ ${change.table} ${change.id} ${JSON.stringify(change.after)}`;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
async function liveRows(io, name, table) {
|
|
72
|
+
const rows = [];
|
|
73
|
+
let after = null;
|
|
74
|
+
for (let page = 0; page < MAX_PAGES; page += 1) {
|
|
75
|
+
const query = after === null ? '' : `?after=${encodeURIComponent(after)}`;
|
|
76
|
+
const result = await signedIn(io, `app data list ${table}`, () => callJson(`/api/apps/${name}/database/${table}${query}`, databaseRows));
|
|
77
|
+
if (isOutcome(result))
|
|
78
|
+
return result;
|
|
79
|
+
rows.push(...result.rows);
|
|
80
|
+
after = result.next;
|
|
81
|
+
if (after === null)
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
return rows;
|
|
85
|
+
}
|
|
86
|
+
async function tables(io, name) {
|
|
87
|
+
const result = await signedIn(io, 'app data tables', () => callJson(`/api/apps/${name}/database`, databaseSchema));
|
|
88
|
+
if (isOutcome(result))
|
|
89
|
+
return result;
|
|
90
|
+
if (result.status === 'outdated') {
|
|
91
|
+
return refusal('Publish once with the latest Pattern to see the live data');
|
|
92
|
+
}
|
|
93
|
+
const lines = result.tables
|
|
94
|
+
.filter((table) => !table.isBuiltIn)
|
|
95
|
+
.map((table) => {
|
|
96
|
+
const properties = Object.keys(Object(Reflect.get(table.schema, 'properties')));
|
|
97
|
+
return ` ${table.name} ${table.rowCount} rows columns: ${properties.join(', ')}`;
|
|
98
|
+
});
|
|
99
|
+
return {
|
|
100
|
+
code: 0,
|
|
101
|
+
data: { name, tables: result.tables },
|
|
102
|
+
text: [`Live tables of ${name}`, ...lines].join('\n')
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
async function list(io, name, table) {
|
|
106
|
+
const rows = await liveRows(io, name, table);
|
|
107
|
+
if (isOutcome(rows))
|
|
108
|
+
return rows;
|
|
109
|
+
return {
|
|
110
|
+
code: 0,
|
|
111
|
+
data: { name, table, rows },
|
|
112
|
+
text: [
|
|
113
|
+
`${rows.length} live ${table} rows on ${name}`,
|
|
114
|
+
...rows.map((row) => ` ${JSON.stringify(row)}`)
|
|
115
|
+
].join('\n')
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function stage(root, added) {
|
|
119
|
+
const changes = [...readStaged(root), ...added];
|
|
120
|
+
if (changes.length > CHANGE_SET_ROWS) {
|
|
121
|
+
return refusal(`That's more than ${CHANGE_SET_ROWS} changes. Send these first with app data send, then add the rest.`);
|
|
122
|
+
}
|
|
123
|
+
writeStaged(root, changes);
|
|
124
|
+
return {
|
|
125
|
+
code: 0,
|
|
126
|
+
data: { staged: changes },
|
|
127
|
+
text: [
|
|
128
|
+
`✓ ${changes.length} change${changes.length === 1 ? '' : 's'} ready to send. Nothing on the live site has changed.`,
|
|
129
|
+
...pendingLines(changes),
|
|
130
|
+
' next add any other changes, then app data send --asked "<their request, in their words>"'
|
|
131
|
+
].join('\n')
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
async function before(io, name, table, id) {
|
|
135
|
+
const rows = await liveRows(io, name, table);
|
|
136
|
+
if (isOutcome(rows))
|
|
137
|
+
return rows;
|
|
138
|
+
const row = rows.find((candidate) => candidate['id'] === id);
|
|
139
|
+
if (row === undefined)
|
|
140
|
+
throw new Error(`There is no ${table} row with id ${id}`);
|
|
141
|
+
return row;
|
|
142
|
+
}
|
|
143
|
+
function decisionText(set, link) {
|
|
144
|
+
if (set.status === 'waiting') {
|
|
145
|
+
return {
|
|
146
|
+
code: 3,
|
|
147
|
+
data: { changeSet: set },
|
|
148
|
+
text: `… Still waiting for the owner to approve "${set.title}" at ${link}.\n next run app data status again: it waits for the decision`
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if (set.status === 'applied') {
|
|
152
|
+
const outcomes = set.outcomes ?? [];
|
|
153
|
+
const missed = outcomes
|
|
154
|
+
.map((outcome, index) => ({ outcome, change: set.changes[index] }))
|
|
155
|
+
.filter(({ outcome }) => outcome.status !== 'applied');
|
|
156
|
+
return {
|
|
157
|
+
code: missed.length === 0 ? 0 : 1,
|
|
158
|
+
data: { changeSet: set },
|
|
159
|
+
text: [
|
|
160
|
+
`✓ Approved: ${set.title}. ${outcomes.length - missed.length} of ${outcomes.length} changes are live.`,
|
|
161
|
+
...missed.map(({ outcome, change }) => ` ${outcome.status} ${change?.table ?? ''} ${change?.op ?? ''}: ${outcome.reason ?? ''}`)
|
|
162
|
+
].join('\n')
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
const words = {
|
|
166
|
+
declined: 'The owner chose Not yet. Nothing changed on the live site.',
|
|
167
|
+
expired: 'This request expired or a newer one replaced it. Nothing changed.',
|
|
168
|
+
undone: 'The owner approved this, then undid it.'
|
|
169
|
+
}[set.status];
|
|
170
|
+
return { code: 1, data: { changeSet: set }, text: `✗ ${words}` };
|
|
171
|
+
}
|
|
172
|
+
async function waitForDecision(io, name, id) {
|
|
173
|
+
const link = `${DASHBOARD_URL}/sites/${name}/database`;
|
|
174
|
+
const deadline = Date.now() + WAIT_MS;
|
|
175
|
+
for (;;) {
|
|
176
|
+
const set = await signedIn(io, 'app data status', () => callJson(`/api/apps/${name}/changes/${id}`, changeSet));
|
|
177
|
+
if (isOutcome(set))
|
|
178
|
+
return set;
|
|
179
|
+
if (set.status !== 'waiting' || Date.now() > deadline) {
|
|
180
|
+
return decisionText(set, link);
|
|
181
|
+
}
|
|
182
|
+
await sleep(POLL_MS);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async function send(io, root, name, asked) {
|
|
186
|
+
const changes = readStaged(root);
|
|
187
|
+
if (changes.length === 0) {
|
|
188
|
+
return refusal('There are no changes to send. Use app data add, update or remove first.');
|
|
189
|
+
}
|
|
190
|
+
if (asked === undefined || asked.trim() === '') {
|
|
191
|
+
return refusal('Say what the owner asked for with --asked "<their words>". They see it beside Approve.', 2);
|
|
192
|
+
}
|
|
193
|
+
const set = await signedIn(io, 'app data send', () => callJson(`/api/apps/${name}/changes`, changeSet, {
|
|
194
|
+
method: 'POST',
|
|
195
|
+
headers: { 'content-type': 'application/json' },
|
|
196
|
+
body: JSON.stringify({ asked: asked.trim(), changes })
|
|
197
|
+
}));
|
|
198
|
+
if (isOutcome(set))
|
|
199
|
+
return set;
|
|
200
|
+
rmSync(stagedPath(root), { force: true });
|
|
201
|
+
const link = `${DASHBOARD_URL}/sites/${name}/database`;
|
|
202
|
+
io.say(`✓ Sent "${set.title}" to the owner. They also got an email.`);
|
|
203
|
+
io.say(` next tell them: "I've sent the changes to your Pattern dashboard. Open ${link} and press Approve." This waits for their decision.`);
|
|
204
|
+
return waitForDecision(io, name, set.id);
|
|
205
|
+
}
|
|
206
|
+
async function status(io, root, name) {
|
|
207
|
+
const sets = await signedIn(io, 'app data status', () => callJson(`/api/apps/${name}/changes`, z.array(changeSet)));
|
|
208
|
+
if (isOutcome(sets))
|
|
209
|
+
return sets;
|
|
210
|
+
const latest = sets[0];
|
|
211
|
+
const pending = readStaged(root);
|
|
212
|
+
if (latest === undefined) {
|
|
213
|
+
return {
|
|
214
|
+
code: 0,
|
|
215
|
+
data: { staged: pending },
|
|
216
|
+
text: pending.length === 0
|
|
217
|
+
? 'Nothing has been sent for approval yet.'
|
|
218
|
+
: [
|
|
219
|
+
`${pending.length} changes ready to send:`,
|
|
220
|
+
...pendingLines(pending)
|
|
221
|
+
].join('\n')
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
return latest.status === 'waiting'
|
|
225
|
+
? waitForDecision(io, name, latest.id)
|
|
226
|
+
: decisionText(latest, `${DASHBOARD_URL}/sites/${name}/database`);
|
|
227
|
+
}
|
|
228
|
+
export async function data(io, root, [action, table, id], options) {
|
|
229
|
+
try {
|
|
230
|
+
const { name } = readProjectFile(root);
|
|
231
|
+
if (action === 'clear') {
|
|
232
|
+
rmSync(stagedPath(root), { force: true });
|
|
233
|
+
return {
|
|
234
|
+
code: 0,
|
|
235
|
+
data: { staged: [] },
|
|
236
|
+
text: '✓ Cleared the changes that were ready to send'
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
sayHost(io);
|
|
240
|
+
if (action === 'tables')
|
|
241
|
+
return await tables(io, name);
|
|
242
|
+
if (action === 'send')
|
|
243
|
+
return await send(io, root, name, valueOf(options, '--asked'));
|
|
244
|
+
if (action === 'status')
|
|
245
|
+
return await status(io, root, name);
|
|
246
|
+
if (table === undefined)
|
|
247
|
+
return refusal(`Say which table\n${DATA_USAGE}`, 2);
|
|
248
|
+
if (action === 'list')
|
|
249
|
+
return await list(io, name, table);
|
|
250
|
+
if (action === 'add') {
|
|
251
|
+
return stage(root, rowsGiven(root, options).map((row) => ({ op: 'add', table, row })));
|
|
252
|
+
}
|
|
253
|
+
if (id === undefined)
|
|
254
|
+
return refusal(`Say which row, by its id\n${DATA_USAGE}`, 2);
|
|
255
|
+
const current = await before(io, name, table, id);
|
|
256
|
+
if (isOutcome(current))
|
|
257
|
+
return current;
|
|
258
|
+
if (action === 'remove') {
|
|
259
|
+
return stage(root, [{ op: 'remove', table, id, before: current }]);
|
|
260
|
+
}
|
|
261
|
+
if (action === 'update') {
|
|
262
|
+
const set = valueOf(options, '--set');
|
|
263
|
+
if (set === undefined)
|
|
264
|
+
return refusal(`Give the new values with --set '{...}'`, 2);
|
|
265
|
+
const after = databaseRow.parse(jsonOf(set, '--set'));
|
|
266
|
+
return stage(root, [{ op: 'update', table, id, before: current, after }]);
|
|
267
|
+
}
|
|
268
|
+
return refusal(`Unknown data command: ${action ?? ''}\n${DATA_USAGE}`, 2);
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
return failure(error);
|
|
272
|
+
}
|
|
273
|
+
}
|
package/dist/cli/publishing.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
1
2
|
import type { StarterContent } from '../publishWire.ts';
|
|
2
|
-
type Outcome = {
|
|
3
|
+
export type Outcome = {
|
|
3
4
|
code: number;
|
|
4
5
|
data: Record<string, unknown>;
|
|
5
6
|
text: string;
|
|
@@ -14,10 +15,16 @@ export type PublishingIo = {
|
|
|
14
15
|
build: () => Promise<Outcome>;
|
|
15
16
|
sdkVersion: string;
|
|
16
17
|
};
|
|
18
|
+
export declare function dashboard(): string;
|
|
19
|
+
export declare function sayHost(io: PublishingIo): void;
|
|
20
|
+
export declare function callJson<Value>(path: string, schema: z.ZodType<Value>, init?: RequestInit): Promise<Value>;
|
|
21
|
+
export declare function sleep(ms: number): Promise<unknown>;
|
|
22
|
+
export declare function signedIn<Result>(io: PublishingIo, again: string, work: () => Promise<Result>): Promise<Result | Outcome>;
|
|
23
|
+
export declare function isOutcome(value: unknown): value is Outcome;
|
|
24
|
+
export declare function failure(error: unknown, data?: Record<string, unknown>): Outcome;
|
|
17
25
|
export declare function login(io: PublishingIo): Promise<Outcome>;
|
|
18
26
|
export declare function pull(io: PublishingIo, name: string | undefined, cwd: string, setupCode: string | undefined): Promise<Outcome>;
|
|
19
27
|
export declare function publish(io: PublishingIo, root: string, isStarter: boolean, message: string | undefined, starter: StarterContent | undefined): Promise<Outcome>;
|
|
20
28
|
export declare function addLiveMedia(io: PublishingIo, root: string, form: FormData, again: string): Promise<Outcome>;
|
|
21
29
|
export declare function versions(io: PublishingIo, root: string): Promise<Outcome>;
|
|
22
30
|
export declare function restore(io: PublishingIo, root: string, number: string | undefined): Promise<Outcome>;
|
|
23
|
-
export {};
|
package/dist/cli/publishing.js
CHANGED
|
@@ -48,10 +48,10 @@ const STEP_WORDS = {
|
|
|
48
48
|
'sign-in': 'Turning on sign-in',
|
|
49
49
|
publish: 'Publishing the site'
|
|
50
50
|
};
|
|
51
|
-
function dashboard() {
|
|
51
|
+
export function dashboard() {
|
|
52
52
|
return (process.env['PATTERN_DASHBOARD_URL'] ?? DASHBOARD_URL).replace(/\/$/, '');
|
|
53
53
|
}
|
|
54
|
-
function sayHost(io) {
|
|
54
|
+
export function sayHost(io) {
|
|
55
55
|
io.say(` via ${new URL(dashboard()).host}`);
|
|
56
56
|
}
|
|
57
57
|
const credentials = z.record(z.string(), z.object({ cookie: z.string() }));
|
|
@@ -114,7 +114,7 @@ async function call(path, init = {}) {
|
|
|
114
114
|
}
|
|
115
115
|
return response;
|
|
116
116
|
}
|
|
117
|
-
async function callJson(path, schema, init = {}) {
|
|
117
|
+
export async function callJson(path, schema, init = {}) {
|
|
118
118
|
const response = await call(path, init);
|
|
119
119
|
const body = await response.json().catch(() => null);
|
|
120
120
|
if (!response.ok) {
|
|
@@ -130,7 +130,7 @@ function labelOfThisMachine() {
|
|
|
130
130
|
const name = named.status === 0 ? named.stdout.trim() : '';
|
|
131
131
|
return (name === '' ? 'this computer' : name).slice(0, 80);
|
|
132
132
|
}
|
|
133
|
-
function sleep(ms) {
|
|
133
|
+
export function sleep(ms) {
|
|
134
134
|
return new Promise((done) => setTimeout(done, ms));
|
|
135
135
|
}
|
|
136
136
|
async function waitForApproval(secret) {
|
|
@@ -195,7 +195,7 @@ async function useSetupCode(io, code) {
|
|
|
195
195
|
}
|
|
196
196
|
io.say('✗ That setup code has expired or was already used');
|
|
197
197
|
}
|
|
198
|
-
async function signedIn(io, again, work) {
|
|
198
|
+
export async function signedIn(io, again, work) {
|
|
199
199
|
if (savedCookie() === undefined) {
|
|
200
200
|
const outcome = await signIn(io, again);
|
|
201
201
|
if (outcome.code !== 0)
|
|
@@ -213,10 +213,10 @@ async function signedIn(io, again, work) {
|
|
|
213
213
|
return work();
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
|
-
function isOutcome(value) {
|
|
216
|
+
export function isOutcome(value) {
|
|
217
217
|
return typeof value === 'object' && value !== null && 'text' in value;
|
|
218
218
|
}
|
|
219
|
-
function failure(error, data = {}) {
|
|
219
|
+
export function failure(error, data = {}) {
|
|
220
220
|
const message = error instanceof TypeError && error.message === 'fetch failed'
|
|
221
221
|
? `Couldn’t reach ${dashboard()}. Check the internet connection and try again.`
|
|
222
222
|
: error instanceof Error
|
|
@@ -3,7 +3,7 @@ import type { Placement } from './Placement.ts';
|
|
|
3
3
|
type BadgeProps = {
|
|
4
4
|
children: ReactNode;
|
|
5
5
|
variant?: 'primary' | 'tertiary' | 'outline' | 'transparent';
|
|
6
|
-
tone?: 'neutral' | 'brand' | 'positive' | 'danger';
|
|
6
|
+
tone?: 'neutral' | 'brand' | 'positive' | 'warning' | 'danger';
|
|
7
7
|
size?: 'sm' | 'md';
|
|
8
8
|
style?: Placement;
|
|
9
9
|
};
|
package/dist/components/Badge.js
CHANGED
|
@@ -8,6 +8,7 @@ const toneKeys = {
|
|
|
8
8
|
neutral: 'Neutral',
|
|
9
9
|
brand: 'Brand',
|
|
10
10
|
positive: 'Positive',
|
|
11
|
+
warning: 'Warning',
|
|
11
12
|
danger: 'Danger'
|
|
12
13
|
};
|
|
13
14
|
const styles = stylex.create({
|
|
@@ -46,6 +47,10 @@ const looks = stylex.create({
|
|
|
46
47
|
color: colors.onPositiveSolidForeground,
|
|
47
48
|
backgroundColor: colors.positiveSolid
|
|
48
49
|
},
|
|
50
|
+
primaryWarning: {
|
|
51
|
+
color: colors.onWarningSolidForeground,
|
|
52
|
+
backgroundColor: colors.warningSolid
|
|
53
|
+
},
|
|
49
54
|
primaryDanger: {
|
|
50
55
|
color: colors.onDangerSolidForeground,
|
|
51
56
|
backgroundColor: colors.dangerSolid
|
|
@@ -62,6 +67,10 @@ const looks = stylex.create({
|
|
|
62
67
|
color: colors.onPositiveSubtleForeground,
|
|
63
68
|
backgroundColor: colors.positiveSubtle
|
|
64
69
|
},
|
|
70
|
+
tertiaryWarning: {
|
|
71
|
+
color: colors.onWarningSubtleForeground,
|
|
72
|
+
backgroundColor: colors.warningSubtle
|
|
73
|
+
},
|
|
65
74
|
tertiaryDanger: {
|
|
66
75
|
color: colors.dangerText,
|
|
67
76
|
backgroundColor: colors.dangerSubtleHover
|
|
@@ -81,6 +90,11 @@ const looks = stylex.create({
|
|
|
81
90
|
backgroundColor: 'transparent',
|
|
82
91
|
boxShadow: `inset 0 0 0 ${borders.thin} ${colors.positiveBorderDefault}`
|
|
83
92
|
},
|
|
93
|
+
outlineWarning: {
|
|
94
|
+
color: colors.warningForeground,
|
|
95
|
+
backgroundColor: 'transparent',
|
|
96
|
+
boxShadow: `inset 0 0 0 ${borders.thin} ${colors.warningBorderDefault}`
|
|
97
|
+
},
|
|
84
98
|
outlineDanger: {
|
|
85
99
|
color: colors.dangerForeground,
|
|
86
100
|
backgroundColor: 'transparent',
|
|
@@ -98,6 +112,10 @@ const looks = stylex.create({
|
|
|
98
112
|
color: colors.positiveForeground,
|
|
99
113
|
backgroundColor: 'transparent'
|
|
100
114
|
},
|
|
115
|
+
transparentWarning: {
|
|
116
|
+
color: colors.warningForeground,
|
|
117
|
+
backgroundColor: 'transparent'
|
|
118
|
+
},
|
|
101
119
|
transparentDanger: {
|
|
102
120
|
color: colors.dangerForeground,
|
|
103
121
|
backgroundColor: 'transparent'
|
|
@@ -6,7 +6,7 @@ import type { Size } from './Size.ts';
|
|
|
6
6
|
export type ButtonProps = {
|
|
7
7
|
children: ReactNode;
|
|
8
8
|
variant?: 'primary' | 'secondary' | 'tertiary' | 'transparent';
|
|
9
|
-
tone?: 'neutral' | 'brand' | 'danger';
|
|
9
|
+
tone?: 'neutral' | 'brand' | 'positive' | 'danger';
|
|
10
10
|
size?: Size;
|
|
11
11
|
to?: AppPath;
|
|
12
12
|
href?: string;
|
|
@@ -27,6 +27,7 @@ export function ButtonBody({ children, variant = 'primary', tone = variant === '
|
|
|
27
27
|
const toneKeys = {
|
|
28
28
|
neutral: 'Neutral',
|
|
29
29
|
brand: 'Brand',
|
|
30
|
+
positive: 'Positive',
|
|
30
31
|
danger: 'Danger'
|
|
31
32
|
};
|
|
32
33
|
const styles = stylex.create({
|
|
@@ -130,6 +131,10 @@ const looks = stylex.create({
|
|
|
130
131
|
color: colors.onBrandSolidForeground,
|
|
131
132
|
backgroundColor: colors.brandSolid
|
|
132
133
|
},
|
|
134
|
+
primaryPositive: {
|
|
135
|
+
color: colors.onPositiveSolidForeground,
|
|
136
|
+
backgroundColor: colors.positiveSolid
|
|
137
|
+
},
|
|
133
138
|
primaryDanger: {
|
|
134
139
|
color: colors.onDangerSolidForeground,
|
|
135
140
|
backgroundColor: colors.dangerSolid
|
|
@@ -144,6 +149,11 @@ const looks = stylex.create({
|
|
|
144
149
|
backgroundColor: colors.componentOnContainerStrong,
|
|
145
150
|
boxShadow: shadows.default
|
|
146
151
|
},
|
|
152
|
+
secondaryPositive: {
|
|
153
|
+
color: colors.positiveForeground,
|
|
154
|
+
backgroundColor: colors.componentOnContainerStrong,
|
|
155
|
+
boxShadow: shadows.default
|
|
156
|
+
},
|
|
147
157
|
secondaryDanger: {
|
|
148
158
|
color: colors.dangerForeground,
|
|
149
159
|
backgroundColor: colors.componentOnContainerStrong,
|
|
@@ -157,6 +167,10 @@ const looks = stylex.create({
|
|
|
157
167
|
color: colors.onBrandSubtleForeground,
|
|
158
168
|
backgroundColor: colors.brandSubtle
|
|
159
169
|
},
|
|
170
|
+
tertiaryPositive: {
|
|
171
|
+
color: colors.onPositiveSubtleForeground,
|
|
172
|
+
backgroundColor: colors.positiveSubtle
|
|
173
|
+
},
|
|
160
174
|
tertiaryDanger: {
|
|
161
175
|
color: colors.dangerForeground,
|
|
162
176
|
backgroundColor: colors.dangerSubtle
|
|
@@ -169,6 +183,10 @@ const looks = stylex.create({
|
|
|
169
183
|
color: colors.brandForeground,
|
|
170
184
|
backgroundColor: 'transparent'
|
|
171
185
|
},
|
|
186
|
+
transparentPositive: {
|
|
187
|
+
color: colors.positiveForeground,
|
|
188
|
+
backgroundColor: 'transparent'
|
|
189
|
+
},
|
|
172
190
|
transparentDanger: {
|
|
173
191
|
color: colors.dangerForeground,
|
|
174
192
|
backgroundColor: 'transparent'
|
|
@@ -189,6 +207,13 @@ const hovers = stylex.create({
|
|
|
189
207
|
':active': colors.brandSolidActive
|
|
190
208
|
}
|
|
191
209
|
},
|
|
210
|
+
primaryPositive: {
|
|
211
|
+
backgroundColor: {
|
|
212
|
+
default: colors.positiveSolid,
|
|
213
|
+
':hover': colors.positiveSolidHover,
|
|
214
|
+
':active': colors.positiveSolidHover
|
|
215
|
+
}
|
|
216
|
+
},
|
|
192
217
|
primaryDanger: {
|
|
193
218
|
backgroundColor: {
|
|
194
219
|
default: colors.dangerSolid,
|
|
@@ -208,6 +233,12 @@ const hovers = stylex.create({
|
|
|
208
233
|
':hover': colors.componentOnContainerStrongHover
|
|
209
234
|
}
|
|
210
235
|
},
|
|
236
|
+
secondaryPositive: {
|
|
237
|
+
backgroundColor: {
|
|
238
|
+
default: colors.componentOnContainerStrong,
|
|
239
|
+
':hover': colors.componentOnContainerStrongHover
|
|
240
|
+
}
|
|
241
|
+
},
|
|
211
242
|
secondaryDanger: {
|
|
212
243
|
backgroundColor: {
|
|
213
244
|
default: colors.componentOnContainerStrong,
|
|
@@ -227,6 +258,12 @@ const hovers = stylex.create({
|
|
|
227
258
|
':hover': colors.brandSubtleHover
|
|
228
259
|
}
|
|
229
260
|
},
|
|
261
|
+
tertiaryPositive: {
|
|
262
|
+
backgroundColor: {
|
|
263
|
+
default: colors.positiveSubtle,
|
|
264
|
+
':hover': colors.positiveSubtleHover
|
|
265
|
+
}
|
|
266
|
+
},
|
|
230
267
|
tertiaryDanger: {
|
|
231
268
|
backgroundColor: {
|
|
232
269
|
default: colors.dangerSubtle,
|
|
@@ -250,6 +287,12 @@ const hovers = stylex.create({
|
|
|
250
287
|
':hover': colors.transparentHover
|
|
251
288
|
}
|
|
252
289
|
},
|
|
290
|
+
transparentPositive: {
|
|
291
|
+
backgroundColor: {
|
|
292
|
+
default: 'transparent',
|
|
293
|
+
':hover': colors.positiveSubtleHover
|
|
294
|
+
}
|
|
295
|
+
},
|
|
253
296
|
transparentDanger: {
|
|
254
297
|
backgroundColor: {
|
|
255
298
|
default: 'transparent',
|
package/dist/patternAccess.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export declare const TABLES_READ = "tables:read";
|
|
3
3
|
export declare const MEDIA_WRITE = "media:write";
|
|
4
|
+
export declare const TABLES_WRITE = "tables:write";
|
|
4
5
|
export declare const ASSERTION_SECONDS = 60;
|
|
5
6
|
export declare const NEXT_PAGE_HEADER = "x-pattern-next";
|
|
6
7
|
export declare const assertionKey: z.ZodObject<{
|
|
@@ -14,6 +15,7 @@ export type AssertionKey = z.output<typeof assertionKey>;
|
|
|
14
15
|
declare const assertionScope: z.ZodEnum<{
|
|
15
16
|
"tables:read": "tables:read";
|
|
16
17
|
"media:write": "media:write";
|
|
18
|
+
"tables:write": "tables:write";
|
|
17
19
|
}>;
|
|
18
20
|
export type AssertionScope = z.output<typeof assertionScope>;
|
|
19
21
|
export declare function signAssertion(key: AssertionKey, { sub, aud, scope }: {
|
package/dist/patternAccess.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export const TABLES_READ = 'tables:read';
|
|
3
3
|
export const MEDIA_WRITE = 'media:write';
|
|
4
|
+
export const TABLES_WRITE = 'tables:write';
|
|
4
5
|
export const ASSERTION_SECONDS = 60;
|
|
5
6
|
export const NEXT_PAGE_HEADER = 'x-pattern-next';
|
|
6
7
|
const CLOCK_SKEW_SECONDS = 30;
|
|
@@ -12,7 +13,7 @@ export const assertionKey = z.object({
|
|
|
12
13
|
x: z.string(),
|
|
13
14
|
d: z.string()
|
|
14
15
|
});
|
|
15
|
-
const assertionScope = z.enum([TABLES_READ, MEDIA_WRITE]);
|
|
16
|
+
const assertionScope = z.enum([TABLES_READ, TABLES_WRITE, MEDIA_WRITE]);
|
|
16
17
|
const assertionHeader = z.object({
|
|
17
18
|
alg: z.literal('EdDSA'),
|
|
18
19
|
typ: z.literal('JWT'),
|