@glasshome/widget-cli 0.10.6 → 0.11.1
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 +4 -2
- package/package.json +2 -2
- package/src/commands/connect.ts +10 -2
- package/src/commands/preview.ts +1 -7
- package/src/commands/upgrade.test.ts +20 -0
- package/src/commands/upgrade.ts +62 -28
- package/src/utils/quiet.ts +12 -8
package/bin/cli.ts
CHANGED
|
@@ -33,7 +33,7 @@ Commands:
|
|
|
33
33
|
publish [hub-url] Build and publish a widget to Hub
|
|
34
34
|
login [hub-url] Authenticate with GlassHome Hub
|
|
35
35
|
info [name] Show widget metadata and bundle info
|
|
36
|
-
upgrade Upgrade @glasshome/widget-sdk to
|
|
36
|
+
upgrade Upgrade @glasshome/widget-sdk (latest, or --to <version>) and sync manifests
|
|
37
37
|
migrate config Rewrite widgets' raw-zod config to defineConfig + field.*
|
|
38
38
|
--name <widget> targets one; --dry previews without writing
|
|
39
39
|
help Show this help message
|
|
@@ -56,6 +56,7 @@ Examples:
|
|
|
56
56
|
glasshome-widget login
|
|
57
57
|
glasshome-widget login https://my-hub.example.com
|
|
58
58
|
glasshome-widget upgrade
|
|
59
|
+
glasshome-widget upgrade --to 1.10.3
|
|
59
60
|
glasshome-widget publish --dir packages/widgets
|
|
60
61
|
`.trim();
|
|
61
62
|
|
|
@@ -68,6 +69,7 @@ const { values: flags, positionals } = parseArgs({
|
|
|
68
69
|
name: { type: "string" },
|
|
69
70
|
bump: { type: "string" },
|
|
70
71
|
scope: { type: "string" },
|
|
72
|
+
to: { type: "string" },
|
|
71
73
|
"re-auth": { type: "boolean" },
|
|
72
74
|
isolate: { type: "boolean" },
|
|
73
75
|
dry: { type: "boolean" },
|
|
@@ -193,7 +195,7 @@ switch (effectiveCommand) {
|
|
|
193
195
|
|
|
194
196
|
case "upgrade": {
|
|
195
197
|
const { runUpgrade } = await import("../src/commands/upgrade");
|
|
196
|
-
await runUpgrade(resolveWidgetDir());
|
|
198
|
+
await runUpgrade(resolveWidgetDir(), { to: str(flags.to) });
|
|
197
199
|
outro("Done");
|
|
198
200
|
break;
|
|
199
201
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glasshome/widget-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "CLI for creating and managing GlassHome widgets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"@fontsource-variable/geist-mono": "^5.2.8",
|
|
31
31
|
"@glasshome/sync-layer": "0.7.2",
|
|
32
32
|
"@glasshome/widget-contract": "0.2.0",
|
|
33
|
-
"@glasshome/widget-sdk": "1.
|
|
33
|
+
"@glasshome/widget-sdk": "1.11.0",
|
|
34
34
|
"@iconify-json/lucide": "^1.2.0",
|
|
35
35
|
"@iconify-json/mdi": "^1.2.3",
|
|
36
36
|
"@tailwindcss/vite": "^4.1.13",
|
package/src/commands/connect.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, readdirSync, readFileSync, statSync, watch } from "node:fs"
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { log, note, spinner } from "@clack/prompts";
|
|
4
4
|
import color from "picocolors";
|
|
5
|
-
import { buildWidgets } from "@glasshome/widget-sdk/vite";
|
|
5
|
+
import { buildWidgets, createIntrospectSession } from "@glasshome/widget-sdk/vite";
|
|
6
6
|
import { trpcMutate, trpcQuery } from "../utils/api";
|
|
7
7
|
import { clearHostToken, extractHost, getHostToken, storeHostToken } from "../utils/auth";
|
|
8
8
|
import { lintAndReport } from "../utils/lint-source";
|
|
@@ -348,6 +348,9 @@ export async function runConnect(
|
|
|
348
348
|
const pending = new Set<string>();
|
|
349
349
|
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
350
350
|
let buildInFlight = false;
|
|
351
|
+
// One worker for the whole session: a fresh introspection process per save is
|
|
352
|
+
// the boot tax this avoids (docs/superpowers/specs/2026-08-18-widget-sdk-persistent-introspect-worker-design.md).
|
|
353
|
+
const introspectSession = createIntrospectSession();
|
|
351
354
|
|
|
352
355
|
async function flush(): Promise<void> {
|
|
353
356
|
debounceTimer = null;
|
|
@@ -362,7 +365,9 @@ export async function runConnect(
|
|
|
362
365
|
const origCwd = process.cwd();
|
|
363
366
|
process.chdir(cwd);
|
|
364
367
|
try {
|
|
365
|
-
await withQuietStdout(() =>
|
|
368
|
+
await withQuietStdout(() =>
|
|
369
|
+
buildWidgets({ ...buildOpts, only: widgets, session: introspectSession }),
|
|
370
|
+
);
|
|
366
371
|
} finally {
|
|
367
372
|
process.chdir(origCwd);
|
|
368
373
|
}
|
|
@@ -421,6 +426,9 @@ export async function runConnect(
|
|
|
421
426
|
log.info("Disconnecting...");
|
|
422
427
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
423
428
|
watcher.close();
|
|
429
|
+
// connect never exits normally, so without this the worker only dies via
|
|
430
|
+
// its stdin-EOF watchdog.
|
|
431
|
+
await introspectSession.dispose();
|
|
424
432
|
|
|
425
433
|
for (const slug of registeredTags) {
|
|
426
434
|
try {
|
package/src/commands/preview.ts
CHANGED
|
@@ -21,14 +21,8 @@ export async function runPreview(cwd: string, names: string[], isolate: boolean)
|
|
|
21
21
|
process.exit(1);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
// Capture the REAL stdout writer before withQuietStdout no-ops
|
|
25
|
-
// process.stdout.write. A clack spinner writes through that same override and
|
|
26
|
-
// would freeze inside the quiet block, so progress goes out this bound
|
|
27
|
-
// reference (which the override cannot intercept) as plain lines. Build/vite
|
|
28
|
-
// noise stays silenced; only these phase lines get through.
|
|
29
|
-
const realWrite = process.stdout.write.bind(process.stdout);
|
|
30
24
|
const progress = (m: string) => {
|
|
31
|
-
|
|
25
|
+
process.stdout.write(`${color.gray("│")} ${color.dim(m)}\n`);
|
|
32
26
|
};
|
|
33
27
|
|
|
34
28
|
log.info(names.length ? `Previewing ${names.join(", ")}` : "Previewing all widgets");
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { sdkDependencySection } from "./upgrade";
|
|
3
|
+
|
|
4
|
+
describe("sdkDependencySection", () => {
|
|
5
|
+
test("finds the SDK in whichever section declares it", () => {
|
|
6
|
+
expect(sdkDependencySection({ peerDependencies: { "@glasshome/widget-sdk": "^1.10.2" } })).toBe(
|
|
7
|
+
"peerDependencies",
|
|
8
|
+
);
|
|
9
|
+
expect(sdkDependencySection({ devDependencies: { "@glasshome/widget-sdk": "^1.10.2" } })).toBe(
|
|
10
|
+
"devDependencies",
|
|
11
|
+
);
|
|
12
|
+
expect(sdkDependencySection({ dependencies: { "@glasshome/widget-sdk": "^1.10.2" } })).toBe(
|
|
13
|
+
"dependencies",
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("returns null when the SDK is not declared", () => {
|
|
18
|
+
expect(sdkDependencySection({ dependencies: { zod: "^4" } })).toBeNull();
|
|
19
|
+
});
|
|
20
|
+
});
|
package/src/commands/upgrade.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
|
-
import { log,
|
|
3
|
+
import { log, spinner } from "@clack/prompts";
|
|
4
4
|
import { discoverWidgets, readManifest, writeManifest } from "../utils/manifest";
|
|
5
5
|
import { getInstalledSdkVersion } from "../utils/sdk-version";
|
|
6
6
|
import { runValidate } from "./validate";
|
|
@@ -58,20 +58,60 @@ function syncManifestSdkVersions(cwd: string, newVersion: string): void {
|
|
|
58
58
|
log.info(`Updated sdkVersion to ${sdkRange} in ${widgets.length} manifest(s)`);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
const SDK_PKG = "@glasshome/widget-sdk";
|
|
62
|
+
|
|
63
|
+
type DependencySection = "peerDependencies" | "devDependencies" | "dependencies";
|
|
64
|
+
type PackageManifest = Partial<Record<DependencySection, Record<string, string>>>;
|
|
65
|
+
|
|
66
|
+
const SECTION_FLAG: Record<DependencySection, string[]> = {
|
|
67
|
+
peerDependencies: ["--peer"],
|
|
68
|
+
devDependencies: ["--dev"],
|
|
69
|
+
dependencies: [],
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/** Which section declares the SDK, so the bump lands where the author put it. */
|
|
73
|
+
export function sdkDependencySection(pkg: PackageManifest): DependencySection | null {
|
|
74
|
+
const sections: DependencySection[] = ["peerDependencies", "devDependencies", "dependencies"];
|
|
75
|
+
return sections.find((section) => pkg[section]?.[SDK_PKG] !== undefined) ?? null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Standalone project: `bun add` the SDK at the requested version (default
|
|
80
|
+
* latest) into the section that already declares it. Bun installs the SDK's
|
|
81
|
+
* required peers alongside, so @glasshome/ui follows without a second step.
|
|
82
|
+
*/
|
|
83
|
+
function bumpSdkDependency(cwd: string, section: DependencySection, target: string): boolean {
|
|
84
|
+
const s = spinner();
|
|
85
|
+
s.start(`Installing ${SDK_PKG}@${target}...`);
|
|
86
|
+
const proc = Bun.spawnSync(["bun", "add", ...SECTION_FLAG[section], `${SDK_PKG}@${target}`], {
|
|
87
|
+
cwd,
|
|
88
|
+
});
|
|
89
|
+
if (proc.exitCode !== 0) {
|
|
90
|
+
s.stop("Install failed");
|
|
91
|
+
log.error(proc.stderr.toString());
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
s.stop(`Installed ${SDK_PKG}@${target}`);
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface UpgradeOptions {
|
|
99
|
+
/** Version or dist-tag to move to in a standalone project. Default "latest". */
|
|
100
|
+
to?: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function runUpgrade(cwd: string, options: UpgradeOptions = {}): Promise<void> {
|
|
62
104
|
const pkgPath = resolve(cwd, "package.json");
|
|
63
105
|
if (!existsSync(pkgPath)) {
|
|
64
106
|
log.error("No package.json found in current directory.");
|
|
65
107
|
process.exit(1);
|
|
66
108
|
}
|
|
67
109
|
|
|
68
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
pkg.devDependencies?.["@glasshome/widget-sdk"] ??
|
|
72
|
-
pkg.dependencies?.["@glasshome/widget-sdk"];
|
|
110
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")) as PackageManifest;
|
|
111
|
+
const section = sdkDependencySection(pkg);
|
|
112
|
+
const currentVersion = section ? pkg[section]?.[SDK_PKG] : undefined;
|
|
73
113
|
|
|
74
|
-
if (!currentVersion) {
|
|
114
|
+
if (!section || !currentVersion) {
|
|
75
115
|
log.error("@glasshome/widget-sdk is not listed in dependencies or peerDependencies.");
|
|
76
116
|
process.exit(1);
|
|
77
117
|
}
|
|
@@ -125,27 +165,21 @@ export async function runUpgrade(cwd: string): Promise<void> {
|
|
|
125
165
|
return;
|
|
126
166
|
}
|
|
127
167
|
|
|
128
|
-
// Standalone project
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
|
|
168
|
+
// Standalone project: bump the dependency, then sync every manifest to what
|
|
169
|
+
// is now installed. A manifest range that excludes the installed SDK fails
|
|
170
|
+
// validation, and this is the command that error tells people to run.
|
|
171
|
+
if (!bumpSdkDependency(cwd, section, options.to ?? "latest")) process.exit(1);
|
|
172
|
+
|
|
132
173
|
const installed = getInstalledSdkVersion(cwd);
|
|
133
|
-
if (installed) {
|
|
134
|
-
syncManifestSdkVersions(cwd, installed);
|
|
135
|
-
const valid = await runValidate(cwd);
|
|
136
|
-
if (!valid) log.warn(`Synced manifests to ${installed}, but validation found problems above.`);
|
|
137
|
-
} else {
|
|
174
|
+
if (!installed) {
|
|
138
175
|
log.warn("No @glasshome/widget-sdk installed here, so manifests were left alone.");
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
syncManifestSdkVersions(cwd, installed);
|
|
179
|
+
const valid = await runValidate(cwd);
|
|
180
|
+
if (valid) {
|
|
181
|
+
log.success(`Upgraded to @glasshome/widget-sdk@${installed}`);
|
|
182
|
+
} else {
|
|
183
|
+
log.warn(`Upgraded to ${installed}, but validation found problems above.`);
|
|
139
184
|
}
|
|
140
|
-
|
|
141
|
-
log.info("To move to a different SDK version:");
|
|
142
|
-
note(
|
|
143
|
-
[
|
|
144
|
-
"1. Bump @glasshome/widget-sdk in package.json peerDependencies",
|
|
145
|
-
"2. bun install",
|
|
146
|
-
"3. bun widget upgrade (sync manifest files)",
|
|
147
|
-
"4. bun widget validate (check compatibility)",
|
|
148
|
-
].join("\n"),
|
|
149
|
-
"To upgrade",
|
|
150
|
-
);
|
|
151
185
|
}
|
package/src/utils/quiet.ts
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Run `fn` with
|
|
2
|
+
* Run `fn` with console logging swallowed. `buildWidgets()` (SDK) logs a
|
|
3
3
|
* "[registry] Generated..." line mid-build; when the caller drives a clack
|
|
4
|
-
* spinner that
|
|
5
|
-
*
|
|
4
|
+
* spinner that line lands on the spinner's line and garbles it.
|
|
5
|
+
*
|
|
6
|
+
* Only the console methods are silenced, never `process.stdout.write`: a clack
|
|
7
|
+
* spinner renders its frames through that write, so overriding it left every
|
|
8
|
+
* caller's spinner frozen for the whole build. Bun's console writes to fd 1
|
|
9
|
+
* natively and bypasses a `process.stdout.write` override anyway, so the
|
|
10
|
+
* console patch is the one that ever did the work. Stderr is left alone so real
|
|
11
|
+
* warnings and errors still surface.
|
|
6
12
|
*/
|
|
7
13
|
export async function withQuietStdout<T>(fn: () => Promise<T>): Promise<T> {
|
|
8
|
-
const originalWrite = process.stdout.write.bind(process.stdout);
|
|
9
14
|
const originalLog = console.log;
|
|
10
|
-
|
|
11
|
-
// override, so both paths are silenced.
|
|
12
|
-
process.stdout.write = (() => true) as typeof process.stdout.write;
|
|
15
|
+
const originalInfo = console.info;
|
|
13
16
|
console.log = () => {};
|
|
17
|
+
console.info = () => {};
|
|
14
18
|
try {
|
|
15
19
|
return await fn();
|
|
16
20
|
} finally {
|
|
17
|
-
process.stdout.write = originalWrite;
|
|
18
21
|
console.log = originalLog;
|
|
22
|
+
console.info = originalInfo;
|
|
19
23
|
}
|
|
20
24
|
}
|