@trypattern/sdk 0.1.6 → 0.1.8
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/auth/user.d.ts +2 -1
- package/dist/auth/user.js +2 -1
- package/dist/cli/app.js +13 -7
- package/dist/cli/publishing.js +38 -7
- package/dist/components/Button.js +1 -1
- package/dist/components/Sidebar.js +2 -2
- package/dist/components/SignInScreen.js +2 -2
- package/dist/server/auth.js +3 -3
- package/dist/server/session.js +3 -4
- package/package.json +1 -1
package/dist/auth/user.d.ts
CHANGED
|
@@ -8,4 +8,5 @@ export declare const userSchema: z.ZodObject<{
|
|
|
8
8
|
}, z.core.$strip>;
|
|
9
9
|
export type User = z.output<typeof userSchema>;
|
|
10
10
|
export type StoredUser = Omit<User, 'isAdmin'>;
|
|
11
|
-
export declare const USER_COOKIE = "pattern_user";
|
|
11
|
+
export declare const USER_COOKIE = "__Host-pattern_user";
|
|
12
|
+
export declare const SESSION_COOKIE = "__Host-pattern_session";
|
package/dist/auth/user.js
CHANGED
|
@@ -6,4 +6,5 @@ export const userSchema = z.object({
|
|
|
6
6
|
avatarUrl: z.string().nullable(),
|
|
7
7
|
isAdmin: z.boolean().default(false)
|
|
8
8
|
});
|
|
9
|
-
export const USER_COOKIE = 'pattern_user';
|
|
9
|
+
export const USER_COOKIE = '__Host-pattern_user';
|
|
10
|
+
export const SESSION_COOKIE = '__Host-pattern_session';
|
package/dist/cli/app.js
CHANGED
|
@@ -313,8 +313,8 @@ every row and column added since, which version 3's code ignores.`,
|
|
|
313
313
|
takesArgument: true
|
|
314
314
|
},
|
|
315
315
|
setup: {
|
|
316
|
-
summary: 'Verify Node/pnpm major versions, pnpm install, install the global `app` shim',
|
|
317
|
-
details: `Idempotent. Fails
|
|
316
|
+
summary: 'Verify Node/pnpm are the pinned major versions or newer, pnpm install, install the global `app` shim',
|
|
317
|
+
details: `Idempotent. Fails when Node or pnpm is older than the major versions in .nvmrc and package.json devEngines.
|
|
318
318
|
Then runs \`pnpm install\` and installs ${SHIM_PATH}, a tiny shim that walks up from the
|
|
319
319
|
current directory to the nearest project launcher (an executable ./app containing the line
|
|
320
320
|
${LAUNCHER_MARKER_LINE}) and runs it, so each project's CLI lives in its repo and the shim
|
|
@@ -1234,6 +1234,9 @@ async function build() {
|
|
|
1234
1234
|
function majorOf(version) {
|
|
1235
1235
|
return version.split('.')[0] ?? '';
|
|
1236
1236
|
}
|
|
1237
|
+
function isAtLeastMajor(version, pinned) {
|
|
1238
|
+
return version !== '' && Number(majorOf(version)) >= Number(majorOf(pinned));
|
|
1239
|
+
}
|
|
1237
1240
|
const packagePins = z.object({
|
|
1238
1241
|
devEngines: z.object({
|
|
1239
1242
|
packageManager: z.object({ version: z.string() })
|
|
@@ -1276,11 +1279,11 @@ async function setup() {
|
|
|
1276
1279
|
});
|
|
1277
1280
|
const havePnpm = (pnpmVersion.stdout ?? '').trim();
|
|
1278
1281
|
const problems = [];
|
|
1279
|
-
if (
|
|
1280
|
-
problems.push(`Node ${haveNode} is
|
|
1282
|
+
if (!isAtLeastMajor(haveNode, pins.node)) {
|
|
1283
|
+
problems.push(`Node ${haveNode} is older than Node ${majorOf(pins.node)} (.nvmrc says ${pins.node})\n fix pnpm env use --global ${majorOf(pins.node)}`);
|
|
1281
1284
|
}
|
|
1282
|
-
if (
|
|
1283
|
-
problems.push(`pnpm ${havePnpm || '(not found)'} is
|
|
1285
|
+
if (!isAtLeastMajor(havePnpm, pins.pnpm)) {
|
|
1286
|
+
problems.push(`pnpm ${havePnpm || '(not found)'} is older than pnpm ${majorOf(pins.pnpm)} (package.json devEngines says ${pins.pnpm} or newer)\n fix npm install --global pnpm@12`);
|
|
1284
1287
|
}
|
|
1285
1288
|
const versions = { node: haveNode, pnpm: havePnpm, expected: pins };
|
|
1286
1289
|
if (problems.length > 0) {
|
|
@@ -1290,7 +1293,10 @@ async function setup() {
|
|
|
1290
1293
|
text: problems.map((p) => `✗ ${p}`).join('\n')
|
|
1291
1294
|
};
|
|
1292
1295
|
}
|
|
1293
|
-
|
|
1296
|
+
const tested = majorOf(haveNode) === majorOf(pins.node)
|
|
1297
|
+
? ''
|
|
1298
|
+
: ` (tested on Node ${majorOf(pins.node)})`;
|
|
1299
|
+
say(`✓ Node ${haveNode} and pnpm ${havePnpm}${tested}`);
|
|
1294
1300
|
const install = await capture('pnpm', ['install']);
|
|
1295
1301
|
if (install.code !== 0) {
|
|
1296
1302
|
return {
|
package/dist/cli/publishing.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import { homedir
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
4
|
import { basename, dirname, extname, join, matchesGlob, relative } from 'node:path';
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import { DASHBOARD_URL, UPLOAD, appSummary, isPrivatePath, loginPoll, loginStarted, nameProblem, publishMessage, publishSummary, setupCodeExchanged, sourcePathProblem } from "../publishWire.js";
|
|
7
7
|
import { appInfo } from "../appTitle.js";
|
|
8
|
+
import { SESSION_COOKIE } from "../auth/user.js";
|
|
8
9
|
import { PROJECT_FILE, readProjectFile } from "../vite/projectFile.js";
|
|
9
10
|
const CONFIG_DIR = join(homedir(), '.config', 'pattern');
|
|
10
11
|
const CREDENTIALS_FILE = join(CONFIG_DIR, 'credentials.json');
|
|
@@ -13,7 +14,6 @@ const LOGIN_MINUTES = 10;
|
|
|
13
14
|
const POLL_MS = 2000;
|
|
14
15
|
const LOGIN_WAIT_MS = 100_000;
|
|
15
16
|
const PUBLISH_WAIT_MS = 300_000;
|
|
16
|
-
const SESSION_COOKIE = 'pattern_session';
|
|
17
17
|
const LAUNCHER = 'app';
|
|
18
18
|
const STARTER_WORKSPACE = 'pnpm-workspace.yaml';
|
|
19
19
|
const STARTER_BUILD_SETTINGS = 'allowBuilds:\n esbuild: false\n workerd: false\n';
|
|
@@ -121,8 +121,11 @@ async function callJson(path, schema, init = {}) {
|
|
|
121
121
|
return schema.parse(body);
|
|
122
122
|
}
|
|
123
123
|
function labelOfThisMachine() {
|
|
124
|
-
const
|
|
125
|
-
|
|
124
|
+
const named = spawnSync('scutil', ['--get', 'ComputerName'], {
|
|
125
|
+
encoding: 'utf8'
|
|
126
|
+
});
|
|
127
|
+
const name = named.status === 0 ? named.stdout.trim() : '';
|
|
128
|
+
return (name === '' ? 'this computer' : name).slice(0, 80);
|
|
126
129
|
}
|
|
127
130
|
function sleep(ms) {
|
|
128
131
|
return new Promise((done) => setTimeout(done, ms));
|
|
@@ -248,6 +251,24 @@ function folderFor(name, cwd) {
|
|
|
248
251
|
return cwd;
|
|
249
252
|
return join(isInsideHome(cwd) ? cwd : homedir(), name);
|
|
250
253
|
}
|
|
254
|
+
async function hasPnpm(io, cwd) {
|
|
255
|
+
return (await io.run('pnpm', ['--version'], cwd)).code === 0;
|
|
256
|
+
}
|
|
257
|
+
async function ensurePnpm(io, cwd) {
|
|
258
|
+
if (await hasPnpm(io, cwd))
|
|
259
|
+
return true;
|
|
260
|
+
io.say('Installing pnpm');
|
|
261
|
+
const installed = await io.run('npm', ['install', '--global', 'pnpm@12'], cwd);
|
|
262
|
+
return installed.code === 0 && (await hasPnpm(io, cwd));
|
|
263
|
+
}
|
|
264
|
+
function firstChange(title, url, folder) {
|
|
265
|
+
return [
|
|
266
|
+
`cd into ${folder} and read AGENTS.md: every rule in it applies. Then run ./app start and open the address it prints, so they see their site running on this computer.`,
|
|
267
|
+
`Ask them one question: "In a sentence, what does ${title} do?" From their answer, make the home page welcome them with a line that says what ${title} does, and give src/theme/theme.ts a brand colour that suits the business.`,
|
|
268
|
+
`Run ./app check until it passes, then ./app publish --message '<what changed>' straight away: this first change goes live without asking, so they see their real site change at ${url}.`,
|
|
269
|
+
`Then tell them their site lives in ${folder}, that next time they can start Claude there, and that from now on you'll show changes on this computer first and publish when they say so.`
|
|
270
|
+
].join('\n ');
|
|
271
|
+
}
|
|
251
272
|
function commandLineToolsReady() {
|
|
252
273
|
return spawnSync('xcode-select', ['-p'], { stdio: 'ignore' }).status === 0;
|
|
253
274
|
}
|
|
@@ -276,12 +297,13 @@ export async function pull(io, name, cwd, setupCode) {
|
|
|
276
297
|
if (!response.ok)
|
|
277
298
|
throw new Error(`Couldn’t download ${name}`);
|
|
278
299
|
const number = response.headers.get('x-pattern-publish') ?? '?';
|
|
300
|
+
const isStarter = response.headers.get('x-pattern-starter') === 'true';
|
|
279
301
|
const live = await fetch(`${app.url}/api/app`)
|
|
280
302
|
.then((reply) => reply.json())
|
|
281
303
|
.then((body) => appInfo.parse(body).title)
|
|
282
304
|
.catch(() => '');
|
|
283
305
|
const title = live === '' ? app.title : live;
|
|
284
|
-
return { app, title, number, form: await response.formData() };
|
|
306
|
+
return { app, title, number, isStarter, form: await response.formData() };
|
|
285
307
|
});
|
|
286
308
|
if (isOutcome(result))
|
|
287
309
|
return result;
|
|
@@ -301,6 +323,13 @@ export async function pull(io, name, cwd, setupCode) {
|
|
|
301
323
|
chmodSync(join(target, LAUNCHER), 0o755);
|
|
302
324
|
writeFileSync(join(target, PROJECT_FILE), `${JSON.stringify({ name, title: result.title }, null, 2)}\n`);
|
|
303
325
|
io.say(`✓ Downloaded ${files} files of version ${result.number}`);
|
|
326
|
+
if (!(await ensurePnpm(io, target))) {
|
|
327
|
+
return {
|
|
328
|
+
code: 1,
|
|
329
|
+
data: { error: 'pnpm missing', folder: target },
|
|
330
|
+
text: `✗ pnpm isn’t installed, and npm couldn’t install it\n fix curl -fsSL https://get.pnpm.io/install.sh | sh -, then open a new terminal and run this again`
|
|
331
|
+
};
|
|
332
|
+
}
|
|
304
333
|
const install = await io.run('pnpm', ['install'], target);
|
|
305
334
|
if (install.code !== 0) {
|
|
306
335
|
return failure(new Error(`pnpm install failed\n${install.output}`));
|
|
@@ -314,11 +343,13 @@ export async function pull(io, name, cwd, setupCode) {
|
|
|
314
343
|
await io.run('git', ['add', '-A'], target);
|
|
315
344
|
await io.run('git', ['commit', '-q', '-m', `Version ${result.number} from onpattern.app`], target);
|
|
316
345
|
}
|
|
317
|
-
const
|
|
346
|
+
const next = result.isStarter
|
|
347
|
+
? firstChange(result.title, result.app.url, target)
|
|
348
|
+
: `cd into ${target}, read AGENTS.md, then ./app start`;
|
|
318
349
|
return {
|
|
319
350
|
code: 0,
|
|
320
351
|
data: { name, folder: target, version: result.number, files },
|
|
321
|
-
text: `✓ ${result.title} is on this computer${
|
|
352
|
+
text: `✓ ${result.title} is on this computer\n folder ${target}\n next ${next}`
|
|
322
353
|
};
|
|
323
354
|
}
|
|
324
355
|
catch (error) {
|
|
@@ -8,7 +8,7 @@ import { Icon } from "./Icon.js";
|
|
|
8
8
|
import { interact, textOf } from "./interaction.js";
|
|
9
9
|
import { Kbd } from "./Kbd.js";
|
|
10
10
|
import { Spinner } from "./Spinner.js";
|
|
11
|
-
export function Button({ children, variant = 'primary', tone = 'neutral', size = 'md', to, href, icon, trailingIcon, shortcut, type = 'button', disabled = false, isPending = false, isFullWidth = false, onClick, style }) {
|
|
11
|
+
export function Button({ children, variant = 'primary', tone = variant === 'primary' ? 'brand' : 'neutral', size = 'md', to, href, icon, trailingIcon, shortcut, type = 'button', disabled = false, isPending = false, isFullWidth = false, onClick, style }) {
|
|
12
12
|
const isDisabled = disabled || isPending;
|
|
13
13
|
const report = () => {
|
|
14
14
|
interact({ primitive: 'Button', event: 'click', label: textOf(children) });
|
|
@@ -89,8 +89,8 @@ const styles = stylex.create({
|
|
|
89
89
|
placeItems: 'center',
|
|
90
90
|
width: iconSizes.xl,
|
|
91
91
|
height: iconSizes.xl,
|
|
92
|
-
color: colors.
|
|
93
|
-
backgroundColor: colors.
|
|
92
|
+
color: colors.onBrandSolidForeground,
|
|
93
|
+
backgroundColor: colors.brandSolid,
|
|
94
94
|
borderRadius: radii.md
|
|
95
95
|
},
|
|
96
96
|
body: {
|
|
@@ -33,8 +33,8 @@ const styles = stylex.create({
|
|
|
33
33
|
alignSelf: 'center',
|
|
34
34
|
width: controls.heightLg,
|
|
35
35
|
height: controls.heightLg,
|
|
36
|
-
color: colors.
|
|
37
|
-
backgroundColor: colors.
|
|
36
|
+
color: colors.onBrandSolidForeground,
|
|
37
|
+
backgroundColor: colors.brandSolid,
|
|
38
38
|
borderRadius: radii.lg
|
|
39
39
|
}
|
|
40
40
|
});
|
package/dist/server/auth.js
CHANGED
|
@@ -6,10 +6,10 @@ import { forgetSession, readPass, startSession } from "./session.js";
|
|
|
6
6
|
import { readTitle } from "./settings.js";
|
|
7
7
|
import { deleteSession } from "./sessions.js";
|
|
8
8
|
import { saveUser } from "./users.js";
|
|
9
|
-
const GOOGLE_COOKIE = 'pattern_google';
|
|
10
|
-
const EMAIL_COOKIE = 'pattern_email';
|
|
9
|
+
const GOOGLE_COOKIE = '__Host-pattern_google';
|
|
10
|
+
const EMAIL_COOKIE = '__Host-pattern_email';
|
|
11
11
|
const signInCookie = {
|
|
12
|
-
path: '/
|
|
12
|
+
path: '/',
|
|
13
13
|
httpOnly: true,
|
|
14
14
|
secure: true,
|
|
15
15
|
sameSite: 'Lax',
|
package/dist/server/session.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { deleteCookie, getSignedCookie, setCookie, setSignedCookie } from 'hono/cookie';
|
|
2
2
|
import { serializeSigned } from 'hono/utils/cookie';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
-
import { USER_COOKIE } from "../auth/user.js";
|
|
4
|
+
import { SESSION_COOKIE, USER_COOKIE } from "../auth/user.js";
|
|
5
5
|
import { createSession } from "./sessions.js";
|
|
6
|
-
const SESSION_COOKIE = 'pattern_session';
|
|
7
6
|
const RECHECK_SECONDS = 15 * 60;
|
|
8
7
|
const SESSION_SECONDS = 30 * 86_400;
|
|
9
8
|
const cookieOptions = {
|
|
@@ -61,6 +60,6 @@ export async function readPass(c) {
|
|
|
61
60
|
}
|
|
62
61
|
export const needsRecheck = (pass) => pass.recheckAt <= nowInSeconds();
|
|
63
62
|
export function forgetSession(c) {
|
|
64
|
-
deleteCookie(c, SESSION_COOKIE, { path: '/' });
|
|
65
|
-
deleteCookie(c, USER_COOKIE, { path: '/' });
|
|
63
|
+
deleteCookie(c, SESSION_COOKIE, { path: '/', secure: true });
|
|
64
|
+
deleteCookie(c, USER_COOKIE, { path: '/', secure: true });
|
|
66
65
|
}
|