@trypattern/sdk 0.1.22 → 0.1.23

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 CHANGED
@@ -23,6 +23,7 @@ import { addLiveMedia, login, publish, pull, restore, versions as listVersions }
23
23
  import { CHANGE_SET_ROWS } from "../publishWire.js";
24
24
  import { DATA_USAGE, data } from "./data.js";
25
25
  import { toolBin } from "./tools.js";
26
+ import { isNewer, latestPattern, newerPattern, updateNote } from "./updates.js";
26
27
  function dependsOnPattern(directory) {
27
28
  try {
28
29
  const manifest = JSON.parse(readFileSync(join(directory, 'package.json'), 'utf8'));
@@ -80,6 +81,7 @@ const CONTACT_SHEET = join(SCREENSHOTS_DIR, 'contact-sheet.png');
80
81
  const SCREENSHOT_WORKER = join(SDK_SOURCE, 'cli', `screenshot${SDK_EXTENSION}`);
81
82
  const VISUAL_URL_ENV = 'PATTERN_VISUAL_URL';
82
83
  const GLOBAL_FLAGS = ['--json', '--help', '-h'];
84
+ const NUDGED_COMMANDS = new Set(['start', 'check', 'publish', 'data']);
83
85
  const VALUE_FLAGS = new Set([
84
86
  '--commands',
85
87
  '--name',
@@ -373,6 +375,15 @@ collected changes. Up to ${CHANGE_SET_ROWS} changes per request. For this comput
373
375
  argumentCount: 3,
374
376
  flags: ['--row', '--set', '--file', '--asked']
375
377
  },
378
+ update: {
379
+ summary: 'Bring this project to the newest Pattern',
380
+ details: `Installs the newest @trypattern/sdk from npm into this project (pnpm add, exact version).
381
+ start, check, publish and data say when a newer Pattern is out. The live site keeps the old one until
382
+ the next app publish, so run app check, then publish it (straight away when nothing else is in
383
+ progress). New Pattern features on the live site, such as the owner approving app data changes,
384
+ need that publish.`,
385
+ run: update
386
+ },
376
387
  versions: {
377
388
  summary: 'List the versions that went live, newest first',
378
389
  details: 'Each version with its number, whether it went live, when, and who published it. app restore <number> puts one back.',
@@ -1492,6 +1503,43 @@ function installShim() {
1492
1503
  path: SHIM_PATH
1493
1504
  };
1494
1505
  }
1506
+ async function update() {
1507
+ const current = sdkVersion();
1508
+ const latest = await latestPattern(true);
1509
+ if (latest === undefined) {
1510
+ return {
1511
+ code: 1,
1512
+ data: { current, error: 'npm did not answer' },
1513
+ text: '✗ Couldn’t reach npm to find the newest Pattern. Check the internet connection and try again.'
1514
+ };
1515
+ }
1516
+ if (!isNewer(latest, current)) {
1517
+ return {
1518
+ code: 0,
1519
+ data: { current, latest, updated: false },
1520
+ text: `✓ Pattern is up to date (${current})`
1521
+ };
1522
+ }
1523
+ say(`Updating Pattern ${current} → ${latest}`);
1524
+ const installed = await capture('pnpm', ['add', `@trypattern/sdk@${latest}`]);
1525
+ if (installed.code !== 0) {
1526
+ return {
1527
+ code: 1,
1528
+ data: {
1529
+ current,
1530
+ latest,
1531
+ error: 'pnpm add failed',
1532
+ output: installed.output
1533
+ },
1534
+ text: `✗ pnpm couldn’t install Pattern ${latest}:\n${installed.output.trim().split('\n').slice(-LOG_TAIL_LINES).join('\n')}`
1535
+ };
1536
+ }
1537
+ return {
1538
+ code: 0,
1539
+ data: { current, latest, updated: true },
1540
+ text: `✓ Updated Pattern ${current} → ${latest}\n next run app check, then app publish --message 'Updated Pattern' (straight away if nothing else is in progress) so the live site gets it`
1541
+ };
1542
+ }
1495
1543
  async function setup() {
1496
1544
  const pins = readPins();
1497
1545
  const haveNode = process.versions.node;
@@ -1635,6 +1683,16 @@ async function main(argv) {
1635
1683
  }
1636
1684
  else {
1637
1685
  outcome = await command.run(positional[1], options, positional.slice(1));
1686
+ if (NUDGED_COMMANDS.has(name)) {
1687
+ const latest = await newerPattern(sdkVersion());
1688
+ if (latest !== undefined) {
1689
+ outcome = {
1690
+ ...outcome,
1691
+ data: { ...outcome.data, newerPattern: latest },
1692
+ text: `${outcome.text}\n${updateNote(latest, sdkVersion())}`
1693
+ };
1694
+ }
1695
+ }
1638
1696
  }
1639
1697
  if (jsonMode) {
1640
1698
  const payload = {
@@ -0,0 +1,4 @@
1
+ export declare function isNewer(candidate: string, current: string): boolean;
2
+ export declare function latestPattern(fresh?: boolean): Promise<string | undefined>;
3
+ export declare function newerPattern(current: string): Promise<string | undefined>;
4
+ export declare function updateNote(latest: string, current: string): string;
@@ -0,0 +1,68 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { dirname, join } from 'node:path';
4
+ import { z } from 'zod';
5
+ const LATEST_URL = 'https://registry.npmjs.org/@trypattern/sdk/latest';
6
+ const CACHE_FILE = join(homedir(), '.config', 'pattern', 'latest-sdk.json');
7
+ const CHECK_MS = 6 * 3_600_000;
8
+ const TIMEOUT_MS = 1500;
9
+ const cached = z.object({ version: z.string(), checkedAt: z.number() });
10
+ const published = z.object({ version: z.string() });
11
+ function partsOf(version) {
12
+ return (version
13
+ .split('-')[0]
14
+ ?.split('.')
15
+ .map((part) => Number.parseInt(part, 10) || 0) ?? [0]);
16
+ }
17
+ export function isNewer(candidate, current) {
18
+ const next = partsOf(candidate);
19
+ const now = partsOf(current);
20
+ for (let index = 0; index < Math.max(next.length, now.length); index += 1) {
21
+ const difference = (next[index] ?? 0) - (now[index] ?? 0);
22
+ if (difference !== 0)
23
+ return difference > 0;
24
+ }
25
+ return false;
26
+ }
27
+ function readCache() {
28
+ if (!existsSync(CACHE_FILE))
29
+ return undefined;
30
+ try {
31
+ const parsed = cached.safeParse(JSON.parse(readFileSync(CACHE_FILE, 'utf8')));
32
+ return parsed.success ? parsed.data : undefined;
33
+ }
34
+ catch {
35
+ return undefined;
36
+ }
37
+ }
38
+ export async function latestPattern(fresh = false) {
39
+ const remembered = readCache();
40
+ if (!fresh &&
41
+ remembered !== undefined &&
42
+ Date.now() - remembered.checkedAt < CHECK_MS) {
43
+ return remembered.version;
44
+ }
45
+ try {
46
+ const response = await fetch(LATEST_URL, {
47
+ signal: AbortSignal.timeout(TIMEOUT_MS)
48
+ });
49
+ const body = published.safeParse(await response.json());
50
+ if (!response.ok || !body.success)
51
+ return remembered?.version;
52
+ mkdirSync(dirname(CACHE_FILE), { recursive: true });
53
+ writeFileSync(CACHE_FILE, JSON.stringify({ version: body.data.version, checkedAt: Date.now() }));
54
+ return body.data.version;
55
+ }
56
+ catch {
57
+ return remembered?.version;
58
+ }
59
+ }
60
+ export async function newerPattern(current) {
61
+ if (process.env['PATTERN_NO_UPDATE_CHECK'] !== undefined)
62
+ return undefined;
63
+ const latest = await latestPattern();
64
+ return latest !== undefined && isNewer(latest, current) ? latest : undefined;
65
+ }
66
+ export function updateNote(latest, current) {
67
+ return ` update Pattern ${latest} is out (this project has ${current}). Run app update now, then app check. Publish it straight away if nothing else is in progress, otherwise with the next publish, so the live site gets it too. Tell the owner in a few words.`;
68
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trypattern/sdk",
3
- "version": "0.1.22",
3
+ "version": "0.1.23",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",