@glasshome/widget-cli 0.4.13 → 0.4.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glasshome/widget-cli",
3
- "version": "0.4.13",
3
+ "version": "0.4.14",
4
4
  "description": "CLI for creating and managing GlassHome widgets",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,6 +10,7 @@ import {
10
10
  } from "node:fs";
11
11
  import { dirname, join, resolve } from "node:path";
12
12
  import { cancel, confirm, isCancel, log, spinner, text } from "@clack/prompts";
13
+ import { getCliVersion } from "../utils/version";
13
14
  import { promptWidgetDetails, scaffoldWidget } from "./add";
14
15
 
15
16
  const templateDir = resolve(import.meta.dir, "../../template");
@@ -114,11 +115,15 @@ export async function runCreate() {
114
115
  const projectDesc =
115
116
  (projectDescription as string) || `A GlassHome widget project: ${projectName}`;
116
117
 
117
- // Replace project-level placeholders in package.json
118
+ // Replace project-level placeholders in package.json. Pin the CLI to the
119
+ // version that scaffolded the project (caret) so `bun widget` resolves the
120
+ // project-local binary and `bun update` keeps it current, instead of a
121
+ // stale global/bunx copy.
118
122
  const pkgPath = join(targetDir, "package.json");
119
123
  let pkgContent = readFileSync(pkgPath, "utf-8");
120
124
  pkgContent = pkgContent.replace(/PROJECT_NAME/g, projectName as string);
121
125
  pkgContent = pkgContent.replace(/PROJECT_DESCRIPTION/g, projectDesc);
126
+ pkgContent = pkgContent.replace(/CLI_VERSION/g, `^${getCliVersion()}`);
122
127
  writeFileSync(pkgPath, pkgContent);
123
128
 
124
129
  // Replace project-level placeholders in README.md
@@ -3,6 +3,7 @@ import { createServer, type IncomingMessage, type ServerResponse } from "node:ht
3
3
  import { log, spinner } from "@clack/prompts";
4
4
  import open from "open";
5
5
  import { getHubUrl, storeToken } from "../utils/auth";
6
+ import { enforceCliVersion, notifyCliUpdate } from "../utils/version";
6
7
 
7
8
  // Public client: PKCE authenticates it, no client secret. The redirect is an
8
9
  // IP-loopback URI (RFC 8252) registered on the hub.
@@ -19,6 +20,11 @@ function base64url(buf: Buffer): string {
19
20
  export async function runLogin(hubUrl?: string): Promise<void> {
20
21
  const hub = hubUrl ?? getHubUrl();
21
22
 
23
+ // Refuse to start the OAuth dance if the hub no longer supports this CLI;
24
+ // otherwise a stale client fails mid-flow with an opaque redirect error.
25
+ await enforceCliVersion(hub);
26
+ await notifyCliUpdate();
27
+
22
28
  // Generate PKCE parameters
23
29
  const codeVerifier = base64url(randomBytes(32));
24
30
  const codeChallenge = base64url(createHash("sha256").update(codeVerifier).digest());
@@ -12,6 +12,7 @@ import {
12
12
  readManifest,
13
13
  writeManifest,
14
14
  } from "../utils/manifest";
15
+ import { enforceCliVersion } from "../utils/version";
15
16
  import { runLogin } from "./login";
16
17
 
17
18
  interface PublishOptions {
@@ -38,6 +39,10 @@ export async function runPublish(
38
39
  // Step 2: Authenticate
39
40
  const hubUrl = hubUrlOverride ?? getHubUrl();
40
41
  if (hubUrlOverride) log.info(`Hub: ${hubUrl}`);
42
+
43
+ // Stop an unsupported CLI before it uploads against a protocol it predates.
44
+ await enforceCliVersion(hubUrl);
45
+
41
46
  let token = await getToken(hubUrl);
42
47
 
43
48
  if (!token) {
@@ -0,0 +1,66 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { log } from "@clack/prompts";
4
+ import semver from "semver";
5
+
6
+ /** This CLI's own version, read from the package.json it ships with. */
7
+ export function getCliVersion(): string {
8
+ try {
9
+ const pkgPath = resolve(import.meta.dir, "../../package.json");
10
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")) as { version?: string };
11
+ return pkg.version ?? "0.0.0";
12
+ } catch {
13
+ return "0.0.0";
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Ask the hub for the minimum CLI version its protocol still accepts and stop
19
+ * with an actionable message when this CLI is below it. Older hub-facing breaks
20
+ * (e.g. the 0.4.11 OAuth redirect change) surfaced as opaque errors; this turns
21
+ * them into "update your CLI". Network/parse failures are non-fatal: the check
22
+ * never blocks publishing when the hub can't be reached.
23
+ */
24
+ export async function enforceCliVersion(hubUrl: string): Promise<void> {
25
+ const current = getCliVersion();
26
+
27
+ let minSupported: string | undefined;
28
+ try {
29
+ const res = await fetch(`${hubUrl}/api/widgets/cli-version`);
30
+ if (res.ok) {
31
+ const data = (await res.json()) as { minSupported?: string };
32
+ minSupported = data.minSupported;
33
+ }
34
+ } catch {
35
+ return;
36
+ }
37
+
38
+ if (minSupported && semver.valid(minSupported) && semver.lt(current, minSupported)) {
39
+ throw new Error(
40
+ `This widget CLI (${current}) is no longer supported by ${hubUrl} (minimum ${minSupported}).\n` +
41
+ "Update it: bun add -g @glasshome/widget-cli@latest\n" +
42
+ "Or run once without installing: bunx @glasshome/widget-cli@latest <command>",
43
+ );
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Best-effort nudge: warn when a newer CLI is published on npm. Never blocks;
49
+ * any failure (offline, registry hiccup) is swallowed.
50
+ */
51
+ export async function notifyCliUpdate(): Promise<void> {
52
+ const current = getCliVersion();
53
+ try {
54
+ const res = await fetch("https://registry.npmjs.org/@glasshome/widget-cli/latest");
55
+ if (!res.ok) return;
56
+ const data = (await res.json()) as { version?: string };
57
+ const latest = data.version;
58
+ if (latest && semver.valid(latest) && semver.gt(latest, current)) {
59
+ log.warn(
60
+ `A newer widget CLI is available (${current} → ${latest}). Update: bun add -g @glasshome/widget-cli@latest`,
61
+ );
62
+ }
63
+ } catch {
64
+ // ignore
65
+ }
66
+ }
@@ -16,6 +16,7 @@
16
16
  "@glasshome/widget-sdk": "^0.2.0"
17
17
  },
18
18
  "devDependencies": {
19
+ "@glasshome/widget-cli": "CLI_VERSION",
19
20
  "@types/bun": "latest",
20
21
  "typescript": "^5.0.0",
21
22
  "vite": "^7.3.1",