@glasshome/widget-cli 0.4.12 → 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/bin/cli.ts CHANGED
@@ -25,6 +25,7 @@ Commands:
25
25
  add Add a new widget to the project
26
26
  build Build all widgets (per-widget, self-contained bundles)
27
27
  connect <url> Connect to a running dashboard for live testing
28
+ --re-auth discards stored credentials and re-runs device approval
28
29
  validate [name] Validate all widgets or a specific one
29
30
  publish [hub-url] Build and publish a widget to Hub
30
31
  login [hub-url] Authenticate with GlassHome Hub
@@ -60,6 +61,7 @@ const { values: flags, positionals } = parseArgs({
60
61
  name: { type: "string" },
61
62
  bump: { type: "string" },
62
63
  scope: { type: "string" },
64
+ "re-auth": { type: "boolean" },
63
65
  help: { type: "boolean", short: "h" },
64
66
  },
65
67
  allowPositionals: true,
@@ -119,7 +121,7 @@ switch (effectiveCommand) {
119
121
  process.exit(1);
120
122
  }
121
123
  const { runConnect } = await import("../src/commands/connect");
122
- await runConnect(url, resolveWidgetDir());
124
+ await runConnect(url, resolveWidgetDir(), { reAuth: flags["re-auth"] === true });
123
125
  break;
124
126
  }
125
127
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glasshome/widget-cli",
3
- "version": "0.4.12",
3
+ "version": "0.4.14",
4
4
  "description": "CLI for creating and managing GlassHome widgets",
5
5
  "type": "module",
6
6
  "bin": {
@@ -87,7 +87,11 @@ async function uploadAllWidgets(apiUrl: string, distDir: string, token: string):
87
87
  return slugs;
88
88
  }
89
89
 
90
- export async function runConnect(apiUrl: string, cwd: string): Promise<void> {
90
+ export async function runConnect(
91
+ apiUrl: string,
92
+ cwd: string,
93
+ opts: { reAuth?: boolean } = {},
94
+ ): Promise<void> {
91
95
  const distDir = resolve(cwd, "dist");
92
96
  const solid = (await import("vite-plugin-solid")).default;
93
97
  const buildOpts = { srcDir: "src", outDir: "dist", plugins: [solid()] };
@@ -119,6 +123,11 @@ export async function runConnect(apiUrl: string, cwd: string): Promise<void> {
119
123
  const host = extractHost(api);
120
124
  let token = "";
121
125
 
126
+ if (opts.reAuth) {
127
+ clearHostToken(host);
128
+ log.info("Discarded stored credentials (--re-auth).");
129
+ }
130
+
122
131
  // Validate stored token before using it
123
132
  const existingToken = getHostToken(host);
124
133
  if (existingToken) {
@@ -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",