@glasshome/widget-cli 0.4.6 → 0.4.9
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 +26 -11
- package/package.json +2 -2
- package/src/commands/connect.ts +10 -3
- package/src/commands/publish.ts +72 -37
- package/src/commands/validate.ts +10 -0
package/bin/cli.ts
CHANGED
|
@@ -43,22 +43,33 @@ Examples:
|
|
|
43
43
|
glasshome-widget info
|
|
44
44
|
glasshome-widget info clock
|
|
45
45
|
glasshome-widget publish
|
|
46
|
+
glasshome-widget publish --name light --bump patch
|
|
47
|
+
glasshome-widget publish --name light --bump minor --scope glasshome
|
|
46
48
|
glasshome-widget login
|
|
47
49
|
glasshome-widget login https://my-hub.example.com
|
|
48
50
|
glasshome-widget upgrade
|
|
49
51
|
glasshome-widget publish --dir packages/widgets
|
|
50
52
|
`.trim();
|
|
51
53
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
54
|
+
import { parseArgs } from "node:util";
|
|
55
|
+
|
|
56
|
+
const { values: flags, positionals } = parseArgs({
|
|
57
|
+
args: process.argv.slice(2),
|
|
58
|
+
options: {
|
|
59
|
+
dir: { type: "string" },
|
|
60
|
+
name: { type: "string" },
|
|
61
|
+
bump: { type: "string" },
|
|
62
|
+
scope: { type: "string" },
|
|
63
|
+
help: { type: "boolean", short: "h" },
|
|
64
|
+
},
|
|
65
|
+
allowPositionals: true,
|
|
66
|
+
strict: false,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const explicitDir = flags.dir;
|
|
70
|
+
const [command, ...args] = positionals;
|
|
71
|
+
|
|
72
|
+
if (command === "help" || flags.help) {
|
|
62
73
|
console.log(HELP);
|
|
63
74
|
process.exit(0);
|
|
64
75
|
}
|
|
@@ -132,7 +143,11 @@ switch (effectiveCommand) {
|
|
|
132
143
|
case "publish": {
|
|
133
144
|
const hubUrlArg = args[0];
|
|
134
145
|
const { runPublish } = await import("../src/commands/publish");
|
|
135
|
-
await runPublish(resolveWidgetDir(), hubUrlArg
|
|
146
|
+
await runPublish(resolveWidgetDir(), hubUrlArg, {
|
|
147
|
+
name: flags.name,
|
|
148
|
+
bump: flags.bump as "keep" | "patch" | "minor" | "major" | undefined,
|
|
149
|
+
scope: flags.scope,
|
|
150
|
+
});
|
|
136
151
|
outro("Done");
|
|
137
152
|
break;
|
|
138
153
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glasshome/widget-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
4
4
|
"description": "CLI for creating and managing GlassHome widgets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@clack/prompts": "^0.10.0",
|
|
24
|
-
"@glasshome/widget-sdk": "0.
|
|
24
|
+
"@glasshome/widget-sdk": "^0.4.0",
|
|
25
25
|
"open": "^11.0.0",
|
|
26
26
|
"semver": "^7.6.0",
|
|
27
27
|
"vite-plugin-solid": "^2.11.0"
|
package/src/commands/connect.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { resolve } from "node:path";
|
|
|
3
3
|
import { log, spinner } from "@clack/prompts";
|
|
4
4
|
import { buildWidgets } from "@glasshome/widget-sdk/vite";
|
|
5
5
|
import { trpcMutate, trpcQuery } from "../utils/api";
|
|
6
|
-
import { extractHost, getHostToken, storeHostToken } from "../utils/auth";
|
|
6
|
+
import { clearHostToken, extractHost, getHostToken, storeHostToken } from "../utils/auth";
|
|
7
7
|
|
|
8
8
|
interface RegistryWidget {
|
|
9
9
|
name: string;
|
|
@@ -127,9 +127,16 @@ export async function runConnect(apiUrl: string, cwd: string): Promise<void> {
|
|
|
127
127
|
headers: { Authorization: `Bearer ${existingToken}` },
|
|
128
128
|
});
|
|
129
129
|
if (check.ok) {
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
const body = (await check.json()) as { session?: unknown } | null;
|
|
131
|
+
if (body?.session) {
|
|
132
|
+
token = existingToken;
|
|
133
|
+
log.info("Using stored credentials for dashboard.");
|
|
134
|
+
} else {
|
|
135
|
+
clearHostToken(host);
|
|
136
|
+
log.warn("Stored credentials expired — re-authenticating.");
|
|
137
|
+
}
|
|
132
138
|
} else {
|
|
139
|
+
clearHostToken(host);
|
|
133
140
|
log.warn("Stored credentials expired — re-authenticating.");
|
|
134
141
|
}
|
|
135
142
|
} catch {
|
package/src/commands/publish.ts
CHANGED
|
@@ -14,7 +14,17 @@ import {
|
|
|
14
14
|
} from "../utils/manifest";
|
|
15
15
|
import { runLogin } from "./login";
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
interface PublishOptions {
|
|
18
|
+
name?: string;
|
|
19
|
+
bump?: "keep" | "patch" | "minor" | "major";
|
|
20
|
+
scope?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function runPublish(
|
|
24
|
+
cwd: string,
|
|
25
|
+
hubUrlOverride?: string,
|
|
26
|
+
options?: PublishOptions,
|
|
27
|
+
): Promise<void> {
|
|
18
28
|
const s = spinner();
|
|
19
29
|
|
|
20
30
|
// Step 1: Validate (quiet)
|
|
@@ -53,7 +63,15 @@ export async function runPublish(cwd: string, hubUrlOverride?: string): Promise<
|
|
|
53
63
|
}
|
|
54
64
|
|
|
55
65
|
let scope: string;
|
|
56
|
-
if (
|
|
66
|
+
if (options?.scope) {
|
|
67
|
+
const match = scopes.find((s) => s.name === options.scope);
|
|
68
|
+
if (!match) {
|
|
69
|
+
log.error(`Scope @${options.scope} not found. Available: ${scopes.map((s) => s.name).join(", ")}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
scope = match.name;
|
|
73
|
+
log.info(`Publishing as @${scope} (${match.type})`);
|
|
74
|
+
} else if (scopes.length === 1) {
|
|
57
75
|
const only = scopes[0]!;
|
|
58
76
|
scope = only.name;
|
|
59
77
|
log.info(`Publishing as @${scope} (${only.type})`);
|
|
@@ -76,48 +94,65 @@ export async function runPublish(cwd: string, hubUrlOverride?: string): Promise<
|
|
|
76
94
|
|
|
77
95
|
// Step 4: Select widget
|
|
78
96
|
const widgets = discoverWidgets(cwd);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
|
|
98
|
+
let widgetName: string;
|
|
99
|
+
if (options?.name) {
|
|
100
|
+
if (!widgets.includes(options.name)) {
|
|
101
|
+
log.error(`Widget "${options.name}" not found. Available: ${widgets.join(", ")}`);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
widgetName = options.name;
|
|
105
|
+
} else {
|
|
106
|
+
const widgetOptions = widgets.map((name) => {
|
|
107
|
+
const manifest = readManifest(cwd, name);
|
|
108
|
+
let sizeInfo = "";
|
|
109
|
+
try {
|
|
110
|
+
const info = getWidgetBundleInfo(cwd, name);
|
|
111
|
+
sizeInfo = ` — ${formatBytes(info.raw)}`;
|
|
112
|
+
} catch {}
|
|
113
|
+
return {
|
|
114
|
+
value: name,
|
|
115
|
+
label: `${manifest.name}${sizeInfo}`,
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const selected = await select({
|
|
120
|
+
message: "Select widget to publish:",
|
|
121
|
+
options: widgetOptions,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (isCancel(selected)) {
|
|
125
|
+
cancel("Publish cancelled.");
|
|
126
|
+
process.exit(0);
|
|
127
|
+
}
|
|
128
|
+
widgetName = selected as string;
|
|
100
129
|
}
|
|
101
130
|
|
|
102
|
-
const widgetName = selected as string;
|
|
103
131
|
const manifest = readManifest(cwd, widgetName);
|
|
104
132
|
|
|
105
133
|
// Step 5: Version bump (per-widget, from manifest)
|
|
106
134
|
const currentVersion = manifest.version ?? "0.0.0";
|
|
107
135
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
options
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
136
|
+
let bump: string;
|
|
137
|
+
if (options?.bump) {
|
|
138
|
+
bump = options.bump;
|
|
139
|
+
log.info(`Version: ${currentVersion} → ${bump === "keep" ? currentVersion : semver.inc(currentVersion, bump as semver.ReleaseType) ?? currentVersion}`);
|
|
140
|
+
} else {
|
|
141
|
+
const choice = await select({
|
|
142
|
+
message: `${manifest.name} version: ${currentVersion}. Bump?`,
|
|
143
|
+
options: [
|
|
144
|
+
{ value: "keep", label: `Keep ${currentVersion}`, hint: "fails if already published" },
|
|
145
|
+
{ value: "patch", label: `Patch (${semver.inc(currentVersion, "patch")})` },
|
|
146
|
+
{ value: "minor", label: `Minor (${semver.inc(currentVersion, "minor")})` },
|
|
147
|
+
{ value: "major", label: `Major (${semver.inc(currentVersion, "major")})` },
|
|
148
|
+
],
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
if (isCancel(choice)) {
|
|
152
|
+
cancel("Publish cancelled.");
|
|
153
|
+
process.exit(0);
|
|
154
|
+
}
|
|
155
|
+
bump = choice as string;
|
|
121
156
|
}
|
|
122
157
|
|
|
123
158
|
let version = currentVersion;
|
package/src/commands/validate.ts
CHANGED
|
@@ -32,6 +32,16 @@ function validateManifest(manifest: WidgetManifest): ValidationResult {
|
|
|
32
32
|
if (!manifest.name) errors.push("Missing required field: name");
|
|
33
33
|
if (!manifest.sdkVersion) errors.push("Missing required field: sdkVersion");
|
|
34
34
|
|
|
35
|
+
// INS-05: "*" sdkVersion is rejected at publish time — authors must declare
|
|
36
|
+
// a real range (e.g. "^0.3.0"). Installed widgets that already ship "*" are
|
|
37
|
+
// still tolerated by the loader with a warn-and-mount, but new publishes
|
|
38
|
+
// are blocked here.
|
|
39
|
+
if (manifest.sdkVersion === "*") {
|
|
40
|
+
errors.push(
|
|
41
|
+
`sdkVersion "*" is not allowed — declare a semver range (e.g. "^0.3.0") to opt into host compatibility checks`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
// Size validation
|
|
36
46
|
if (!hasValidMinSize) {
|
|
37
47
|
errors.push("minSize must be an object with numeric w and h properties");
|