@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 +3 -1
- package/package.json +1 -1
- package/src/commands/connect.ts +10 -1
- package/src/commands/create.ts +6 -1
- package/src/commands/login.ts +6 -0
- package/src/commands/publish.ts +5 -0
- package/src/utils/version.ts +66 -0
- package/template/package.json.template +1 -0
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
package/src/commands/connect.ts
CHANGED
|
@@ -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(
|
|
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) {
|
package/src/commands/create.ts
CHANGED
|
@@ -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
|
package/src/commands/login.ts
CHANGED
|
@@ -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());
|
package/src/commands/publish.ts
CHANGED
|
@@ -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
|
+
}
|