@glasshome/widget-cli 0.1.0 → 0.2.0
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/README.md +16 -8
- package/bin/cli.ts +21 -3
- package/package.json +1 -3
- package/src/commands/create.ts +211 -0
- package/bin/create.ts +0 -220
package/README.md
CHANGED
|
@@ -11,20 +11,28 @@ bun add -g @glasshome/widget-cli
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
# Create a new widget project (interactive)
|
|
15
|
+
bunx @glasshome/widget-cli
|
|
16
|
+
|
|
17
|
+
# Or explicitly
|
|
18
|
+
bunx @glasshome/widget-cli create
|
|
18
19
|
```
|
|
19
20
|
|
|
20
21
|
## Commands
|
|
21
22
|
|
|
22
23
|
| Command | Description |
|
|
23
24
|
|---------|-------------|
|
|
24
|
-
| `create
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
25
|
+
| `create` | Create a new widget project (default when no project found) |
|
|
26
|
+
| `add` | Add a new widget to an existing project |
|
|
27
|
+
| `build` | Build all widgets |
|
|
28
|
+
| `connect <url>` | Connect to a running dashboard for live testing |
|
|
29
|
+
| `validate [name]` | Validate all widgets or a specific one |
|
|
30
|
+
| `publish` | Build and publish to GlassHome Hub |
|
|
31
|
+
| `login [hub-url]` | Authenticate with GlassHome Hub |
|
|
32
|
+
| `info [name]` | Show widget metadata and bundle info |
|
|
33
|
+
| `upgrade` | Upgrade @glasshome/widget-sdk to latest version |
|
|
34
|
+
|
|
35
|
+
Running `glasshome-widget` with no command inside a widget project shows help. Outside a widget project, it starts the create wizard.
|
|
28
36
|
|
|
29
37
|
## Documentation
|
|
30
38
|
|
package/bin/cli.ts
CHANGED
|
@@ -21,6 +21,7 @@ const HELP = `
|
|
|
21
21
|
glasshome-widget <command> [--dir <path>] [options]
|
|
22
22
|
|
|
23
23
|
Commands:
|
|
24
|
+
create Create a new widget project (default when no project found)
|
|
24
25
|
add Add a new widget to the project
|
|
25
26
|
build Build all widgets (per-widget, self-contained bundles)
|
|
26
27
|
connect <url> Connect to a running dashboard for live testing
|
|
@@ -32,6 +33,8 @@ Commands:
|
|
|
32
33
|
help Show this help message
|
|
33
34
|
|
|
34
35
|
Examples:
|
|
36
|
+
glasshome-widget Create a new widget project
|
|
37
|
+
glasshome-widget create Create a new widget project
|
|
35
38
|
glasshome-widget add
|
|
36
39
|
glasshome-widget build
|
|
37
40
|
glasshome-widget connect http://localhost:3333
|
|
@@ -55,14 +58,29 @@ if (dirFlagIdx !== -1) {
|
|
|
55
58
|
}
|
|
56
59
|
const [command, ...args] = rawArgs;
|
|
57
60
|
|
|
58
|
-
if (
|
|
61
|
+
if (command === "help" || command === "--help" || command === "-h") {
|
|
59
62
|
console.log(HELP);
|
|
60
63
|
process.exit(0);
|
|
61
64
|
}
|
|
62
65
|
|
|
63
|
-
|
|
66
|
+
// No command: if inside a widget project show help, otherwise run create
|
|
67
|
+
const effectiveCommand = command ?? (existsSync(resolve(process.cwd(), "src")) ? "help" : "create");
|
|
68
|
+
|
|
69
|
+
if (effectiveCommand === "help") {
|
|
70
|
+
console.log(HELP);
|
|
71
|
+
process.exit(0);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
intro(`glasshome-widget ${effectiveCommand}`);
|
|
75
|
+
|
|
76
|
+
switch (effectiveCommand) {
|
|
77
|
+
case "create": {
|
|
78
|
+
const { runCreate } = await import("../src/commands/create");
|
|
79
|
+
await runCreate();
|
|
80
|
+
outro("Happy building!");
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
64
83
|
|
|
65
|
-
switch (command) {
|
|
66
84
|
case "add": {
|
|
67
85
|
const { runAdd } = await import("../src/commands/add");
|
|
68
86
|
await runAdd(resolveWidgetDir());
|
package/package.json
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glasshome/widget-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "CLI for creating and managing GlassHome widgets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"create-glasshome-widget": "bin/create.ts",
|
|
8
7
|
"glasshome-widget": "bin/cli.ts"
|
|
9
8
|
},
|
|
10
9
|
"scripts": {
|
|
11
|
-
"create": "bun bin/create.ts",
|
|
12
10
|
"cli": "bun bin/cli.ts"
|
|
13
11
|
},
|
|
14
12
|
"files": [
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cpSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
readdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
renameSync,
|
|
7
|
+
rmSync,
|
|
8
|
+
statSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from "node:fs";
|
|
11
|
+
import { dirname, join, resolve } from "node:path";
|
|
12
|
+
import { cancel, confirm, isCancel, log, spinner, text } from "@clack/prompts";
|
|
13
|
+
import { promptWidgetDetails, scaffoldWidget } from "./add";
|
|
14
|
+
|
|
15
|
+
const templateDir = resolve(import.meta.dir, "../../template");
|
|
16
|
+
|
|
17
|
+
/** Walk up from cwd looking for a package.json with a `workspaces` field. */
|
|
18
|
+
function findMonorepoRoot(from: string): string | null {
|
|
19
|
+
let dir = resolve(from);
|
|
20
|
+
const root = (dir.match(/^[A-Za-z]:\\/) ?? ["/"])[0];
|
|
21
|
+
while (true) {
|
|
22
|
+
const pkgPath = join(dir, "package.json");
|
|
23
|
+
if (existsSync(pkgPath)) {
|
|
24
|
+
try {
|
|
25
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
26
|
+
if (pkg.workspaces) return dir;
|
|
27
|
+
} catch {}
|
|
28
|
+
}
|
|
29
|
+
const parent = dirname(dir);
|
|
30
|
+
if (parent === dir || parent === root) break;
|
|
31
|
+
dir = parent;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Check if cwd is inside an existing git repository. */
|
|
37
|
+
function isInsideGitRepo(from: string): boolean {
|
|
38
|
+
const result = Bun.spawnSync(["git", "rev-parse", "--is-inside-work-tree"], {
|
|
39
|
+
cwd: from,
|
|
40
|
+
stderr: "pipe",
|
|
41
|
+
});
|
|
42
|
+
return result.exitCode === 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function runCreate() {
|
|
46
|
+
const projectName = await text({
|
|
47
|
+
message: "Project name",
|
|
48
|
+
placeholder: "my-widgets",
|
|
49
|
+
defaultValue: "my-widgets",
|
|
50
|
+
validate(value) {
|
|
51
|
+
if (!value) return "Name is required";
|
|
52
|
+
if (/\s/.test(value)) return "Name cannot contain spaces";
|
|
53
|
+
if (!/^[a-z]/.test(value)) return "Name must start with a lowercase letter";
|
|
54
|
+
if (!/^[a-z][a-z0-9-]*$/.test(value))
|
|
55
|
+
return "Name must be lowercase alphanumeric with optional hyphens";
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (isCancel(projectName)) {
|
|
60
|
+
cancel("Operation cancelled.");
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const projectDescription = await text({
|
|
65
|
+
message: "Project description (optional)",
|
|
66
|
+
placeholder: "My GlassHome widget collection",
|
|
67
|
+
defaultValue: "",
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (isCancel(projectDescription)) {
|
|
71
|
+
cancel("Operation cancelled.");
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
log.info("Let's set up your first widget:");
|
|
76
|
+
|
|
77
|
+
const widgetDetails = await promptWidgetDetails();
|
|
78
|
+
if (!widgetDetails) process.exit(0);
|
|
79
|
+
|
|
80
|
+
const targetDir = resolve(process.cwd(), projectName as string);
|
|
81
|
+
|
|
82
|
+
if (existsSync(targetDir)) {
|
|
83
|
+
cancel(`Directory "${projectName}" already exists.`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const s = spinner();
|
|
88
|
+
s.start("Scaffolding widget project...");
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
cpSync(templateDir, targetDir, { recursive: true });
|
|
92
|
+
|
|
93
|
+
// Strip .template suffixes (used to prevent IDE type-checking of template files)
|
|
94
|
+
function processDir(dir: string) {
|
|
95
|
+
for (const entry of readdirSync(dir)) {
|
|
96
|
+
const fullPath = join(dir, entry);
|
|
97
|
+
if (statSync(fullPath).isDirectory()) {
|
|
98
|
+
processDir(fullPath);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (entry.endsWith(".template")) {
|
|
102
|
+
renameSync(fullPath, join(dir, entry.replace(/\.template$/, "")));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
processDir(targetDir);
|
|
107
|
+
|
|
108
|
+
// Remove the template's src/index.tsx (it will be placed into a widget subdir)
|
|
109
|
+
const oldSrcIndex = join(targetDir, "src/index.tsx");
|
|
110
|
+
if (existsSync(oldSrcIndex)) {
|
|
111
|
+
rmSync(oldSrcIndex);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const projectDesc =
|
|
115
|
+
(projectDescription as string) || `A GlassHome widget project: ${projectName}`;
|
|
116
|
+
|
|
117
|
+
// Replace project-level placeholders in package.json
|
|
118
|
+
const pkgPath = join(targetDir, "package.json");
|
|
119
|
+
let pkgContent = readFileSync(pkgPath, "utf-8");
|
|
120
|
+
pkgContent = pkgContent.replace(/PROJECT_NAME/g, projectName as string);
|
|
121
|
+
pkgContent = pkgContent.replace(/PROJECT_DESCRIPTION/g, projectDesc);
|
|
122
|
+
writeFileSync(pkgPath, pkgContent);
|
|
123
|
+
|
|
124
|
+
// Replace project-level placeholders in README.md
|
|
125
|
+
const readmePath = join(targetDir, "README.md");
|
|
126
|
+
let readmeContent = readFileSync(readmePath, "utf-8");
|
|
127
|
+
readmeContent = readmeContent.replace(/PROJECT_NAME/g, projectName as string);
|
|
128
|
+
readmeContent = readmeContent.replace(/PROJECT_DESCRIPTION/g, projectDesc);
|
|
129
|
+
writeFileSync(readmePath, readmeContent);
|
|
130
|
+
|
|
131
|
+
// Create the first widget in src/{widgetName}/
|
|
132
|
+
scaffoldWidget(targetDir, widgetDetails);
|
|
133
|
+
|
|
134
|
+
s.stop("Widget project created!");
|
|
135
|
+
|
|
136
|
+
// Post-scaffold: auto-install
|
|
137
|
+
const monorepoRoot = findMonorepoRoot(process.cwd());
|
|
138
|
+
const installCwd = monorepoRoot ?? targetDir;
|
|
139
|
+
const installHint = monorepoRoot ? " (from monorepo root)" : "";
|
|
140
|
+
|
|
141
|
+
const shouldInstall = await confirm({
|
|
142
|
+
message: `Install dependencies?${installHint}`,
|
|
143
|
+
initialValue: true,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (!isCancel(shouldInstall) && shouldInstall) {
|
|
147
|
+
const installSpinner = spinner();
|
|
148
|
+
installSpinner.start("Installing dependencies...");
|
|
149
|
+
const installProc = Bun.spawnSync(["bun", "install"], { cwd: installCwd });
|
|
150
|
+
if (installProc.exitCode === 0) {
|
|
151
|
+
installSpinner.stop("Dependencies installed");
|
|
152
|
+
} else {
|
|
153
|
+
installSpinner.stop(
|
|
154
|
+
monorepoRoot
|
|
155
|
+
? "Install failed — run `bun install` from the monorepo root"
|
|
156
|
+
: "Install failed — you can run `bun install` manually",
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Post-scaffold: git init (skip when already inside a git repo)
|
|
162
|
+
const inGitRepo = isInsideGitRepo(process.cwd());
|
|
163
|
+
|
|
164
|
+
if (inGitRepo) {
|
|
165
|
+
log.info("Skipping git init — already in a git repository");
|
|
166
|
+
} else {
|
|
167
|
+
const shouldGit = await confirm({
|
|
168
|
+
message: "Initialize git repository?",
|
|
169
|
+
initialValue: true,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
if (!isCancel(shouldGit) && shouldGit) {
|
|
173
|
+
const gitSpinner = spinner();
|
|
174
|
+
gitSpinner.start("Initializing git repository...");
|
|
175
|
+
const initProc = Bun.spawnSync(["git", "init"], { cwd: targetDir });
|
|
176
|
+
if (initProc.exitCode === 0) {
|
|
177
|
+
Bun.spawnSync(["git", "add", "-A"], { cwd: targetDir });
|
|
178
|
+
Bun.spawnSync(["git", "commit", "-m", "Initial widget scaffold"], { cwd: targetDir });
|
|
179
|
+
gitSpinner.stop("Git repository initialized");
|
|
180
|
+
} else {
|
|
181
|
+
gitSpinner.stop("Git init failed — you can run `git init` manually");
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Next-steps guide
|
|
187
|
+
log.info("");
|
|
188
|
+
log.step("Next steps:");
|
|
189
|
+
if (monorepoRoot) {
|
|
190
|
+
log.info(" bun install Run from monorepo root to link workspace packages");
|
|
191
|
+
log.info(` cd ${projectName}`);
|
|
192
|
+
} else {
|
|
193
|
+
log.info(` cd ${projectName}`);
|
|
194
|
+
if (isCancel(shouldInstall) || !shouldInstall) {
|
|
195
|
+
log.info(" bun install");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
log.info(" glasshome-widget connect <url> Connect to a running dashboard for live testing");
|
|
199
|
+
log.info(" glasshome-widget add Add another widget to the project");
|
|
200
|
+
log.info(" glasshome-widget build Build all widgets");
|
|
201
|
+
log.info(" glasshome-widget validate Validate all widgets");
|
|
202
|
+
log.info(" glasshome-widget publish Build & publish to GlassHome Hub");
|
|
203
|
+
log.info(" glasshome-widget info Show widget metadata");
|
|
204
|
+
log.info(" glasshome-widget upgrade Upgrade @glasshome/widget-sdk");
|
|
205
|
+
} catch (err) {
|
|
206
|
+
s.stop("Failed to scaffold widget project.");
|
|
207
|
+
rmSync(targetDir, { recursive: true, force: true });
|
|
208
|
+
cancel(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
}
|
package/bin/create.ts
DELETED
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
import {
|
|
3
|
-
cpSync,
|
|
4
|
-
existsSync,
|
|
5
|
-
readdirSync,
|
|
6
|
-
readFileSync,
|
|
7
|
-
renameSync,
|
|
8
|
-
rmSync,
|
|
9
|
-
statSync,
|
|
10
|
-
writeFileSync,
|
|
11
|
-
} from "node:fs";
|
|
12
|
-
import { dirname, join, resolve } from "node:path";
|
|
13
|
-
import { cancel, confirm, intro, isCancel, log, outro, spinner, text } from "@clack/prompts";
|
|
14
|
-
import { promptWidgetDetails, scaffoldWidget } from "../src/commands/add";
|
|
15
|
-
|
|
16
|
-
const templateDir = resolve(import.meta.dir, "../template");
|
|
17
|
-
|
|
18
|
-
/** Walk up from cwd looking for a package.json with a `workspaces` field. */
|
|
19
|
-
function findMonorepoRoot(from: string): string | null {
|
|
20
|
-
let dir = resolve(from);
|
|
21
|
-
const root = (dir.match(/^[A-Za-z]:\\/) ?? ["/"])[0];
|
|
22
|
-
while (true) {
|
|
23
|
-
const pkgPath = join(dir, "package.json");
|
|
24
|
-
if (existsSync(pkgPath)) {
|
|
25
|
-
try {
|
|
26
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
27
|
-
if (pkg.workspaces) return dir;
|
|
28
|
-
} catch {}
|
|
29
|
-
}
|
|
30
|
-
const parent = dirname(dir);
|
|
31
|
-
if (parent === dir || parent === root) break;
|
|
32
|
-
dir = parent;
|
|
33
|
-
}
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** Check if cwd is inside an existing git repository. */
|
|
38
|
-
function isInsideGitRepo(from: string): boolean {
|
|
39
|
-
const result = Bun.spawnSync(["git", "rev-parse", "--is-inside-work-tree"], {
|
|
40
|
-
cwd: from,
|
|
41
|
-
stderr: "pipe",
|
|
42
|
-
});
|
|
43
|
-
return result.exitCode === 0;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
intro("Create GlassHome Widget Project");
|
|
47
|
-
|
|
48
|
-
// ---- Project-level prompts ----
|
|
49
|
-
|
|
50
|
-
const projectName = await text({
|
|
51
|
-
message: "Project name",
|
|
52
|
-
placeholder: "my-widgets",
|
|
53
|
-
defaultValue: "my-widgets",
|
|
54
|
-
validate(value) {
|
|
55
|
-
if (!value) return "Name is required";
|
|
56
|
-
if (/\s/.test(value)) return "Name cannot contain spaces";
|
|
57
|
-
if (!/^[a-z]/.test(value)) return "Name must start with a lowercase letter";
|
|
58
|
-
if (!/^[a-z][a-z0-9-]*$/.test(value))
|
|
59
|
-
return "Name must be lowercase alphanumeric with optional hyphens";
|
|
60
|
-
},
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
if (isCancel(projectName)) {
|
|
64
|
-
cancel("Operation cancelled.");
|
|
65
|
-
process.exit(0);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const projectDescription = await text({
|
|
69
|
-
message: "Project description (optional)",
|
|
70
|
-
placeholder: "My GlassHome widget collection",
|
|
71
|
-
defaultValue: "",
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
if (isCancel(projectDescription)) {
|
|
75
|
-
cancel("Operation cancelled.");
|
|
76
|
-
process.exit(0);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// ---- First widget prompts ----
|
|
80
|
-
|
|
81
|
-
log.info("Let's set up your first widget:");
|
|
82
|
-
|
|
83
|
-
const widgetDetails = await promptWidgetDetails();
|
|
84
|
-
if (!widgetDetails) process.exit(0);
|
|
85
|
-
|
|
86
|
-
const targetDir = resolve(process.cwd(), projectName as string);
|
|
87
|
-
|
|
88
|
-
if (existsSync(targetDir)) {
|
|
89
|
-
cancel(`Directory "${projectName}" already exists.`);
|
|
90
|
-
process.exit(1);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const s = spinner();
|
|
94
|
-
s.start("Scaffolding widget project...");
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
// Copy template to target
|
|
98
|
-
cpSync(templateDir, targetDir, { recursive: true });
|
|
99
|
-
|
|
100
|
-
// Strip .template suffixes (used to prevent IDE type-checking of template files)
|
|
101
|
-
function processDir(dir: string) {
|
|
102
|
-
for (const entry of readdirSync(dir)) {
|
|
103
|
-
const fullPath = join(dir, entry);
|
|
104
|
-
if (statSync(fullPath).isDirectory()) {
|
|
105
|
-
processDir(fullPath);
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
if (entry.endsWith(".template")) {
|
|
109
|
-
renameSync(fullPath, join(dir, entry.replace(/\.template$/, "")));
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
processDir(targetDir);
|
|
114
|
-
|
|
115
|
-
// Remove the template's src/index.tsx (it will be placed into a widget subdir)
|
|
116
|
-
const oldSrcIndex = join(targetDir, "src/index.tsx");
|
|
117
|
-
if (existsSync(oldSrcIndex)) {
|
|
118
|
-
rmSync(oldSrcIndex);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const projectDesc =
|
|
122
|
-
(projectDescription as string) || `A GlassHome widget project: ${projectName}`;
|
|
123
|
-
|
|
124
|
-
// Replace project-level placeholders in package.json
|
|
125
|
-
const pkgPath = join(targetDir, "package.json");
|
|
126
|
-
let pkgContent = readFileSync(pkgPath, "utf-8");
|
|
127
|
-
pkgContent = pkgContent.replace(/PROJECT_NAME/g, projectName as string);
|
|
128
|
-
pkgContent = pkgContent.replace(/PROJECT_DESCRIPTION/g, projectDesc);
|
|
129
|
-
writeFileSync(pkgPath, pkgContent);
|
|
130
|
-
|
|
131
|
-
// Replace project-level placeholders in README.md
|
|
132
|
-
const readmePath = join(targetDir, "README.md");
|
|
133
|
-
let readmeContent = readFileSync(readmePath, "utf-8");
|
|
134
|
-
readmeContent = readmeContent.replace(/PROJECT_NAME/g, projectName as string);
|
|
135
|
-
readmeContent = readmeContent.replace(/PROJECT_DESCRIPTION/g, projectDesc);
|
|
136
|
-
writeFileSync(readmePath, readmeContent);
|
|
137
|
-
|
|
138
|
-
// Create the first widget in src/{widgetName}/
|
|
139
|
-
scaffoldWidget(targetDir, widgetDetails);
|
|
140
|
-
|
|
141
|
-
s.stop("Widget project created!");
|
|
142
|
-
|
|
143
|
-
// Post-scaffold: auto-install
|
|
144
|
-
const monorepoRoot = findMonorepoRoot(process.cwd());
|
|
145
|
-
const installCwd = monorepoRoot ?? targetDir;
|
|
146
|
-
const installHint = monorepoRoot ? " (from monorepo root)" : "";
|
|
147
|
-
|
|
148
|
-
const shouldInstall = await confirm({
|
|
149
|
-
message: `Install dependencies?${installHint}`,
|
|
150
|
-
initialValue: true,
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
if (!isCancel(shouldInstall) && shouldInstall) {
|
|
154
|
-
const installSpinner = spinner();
|
|
155
|
-
installSpinner.start("Installing dependencies...");
|
|
156
|
-
const installProc = Bun.spawnSync(["bun", "install"], { cwd: installCwd });
|
|
157
|
-
if (installProc.exitCode === 0) {
|
|
158
|
-
installSpinner.stop("Dependencies installed");
|
|
159
|
-
} else {
|
|
160
|
-
installSpinner.stop(
|
|
161
|
-
monorepoRoot
|
|
162
|
-
? "Install failed — run `bun install` from the monorepo root"
|
|
163
|
-
: "Install failed — you can run `bun install` manually",
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Post-scaffold: git init (skip when already inside a git repo)
|
|
169
|
-
const inGitRepo = isInsideGitRepo(process.cwd());
|
|
170
|
-
|
|
171
|
-
if (inGitRepo) {
|
|
172
|
-
log.info("Skipping git init — already in a git repository");
|
|
173
|
-
} else {
|
|
174
|
-
const shouldGit = await confirm({
|
|
175
|
-
message: "Initialize git repository?",
|
|
176
|
-
initialValue: true,
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
if (!isCancel(shouldGit) && shouldGit) {
|
|
180
|
-
const gitSpinner = spinner();
|
|
181
|
-
gitSpinner.start("Initializing git repository...");
|
|
182
|
-
const initProc = Bun.spawnSync(["git", "init"], { cwd: targetDir });
|
|
183
|
-
if (initProc.exitCode === 0) {
|
|
184
|
-
Bun.spawnSync(["git", "add", "-A"], { cwd: targetDir });
|
|
185
|
-
Bun.spawnSync(["git", "commit", "-m", "Initial widget scaffold"], { cwd: targetDir });
|
|
186
|
-
gitSpinner.stop("Git repository initialized");
|
|
187
|
-
} else {
|
|
188
|
-
gitSpinner.stop("Git init failed — you can run `git init` manually");
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Enhanced outro with next-steps guide
|
|
194
|
-
log.info("");
|
|
195
|
-
log.step("Next steps:");
|
|
196
|
-
if (monorepoRoot) {
|
|
197
|
-
log.info(" bun install Run from monorepo root to link workspace packages");
|
|
198
|
-
log.info(` cd ${projectName}`);
|
|
199
|
-
} else {
|
|
200
|
-
log.info(` cd ${projectName}`);
|
|
201
|
-
if (isCancel(shouldInstall) || !shouldInstall) {
|
|
202
|
-
log.info(" bun install");
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
log.info(" bun widget connect <url> Connect to a running dashboard for live testing");
|
|
206
|
-
log.info(" bun widget add Add another widget to the project");
|
|
207
|
-
log.info(" bun run build Build all widgets + generate registry.json");
|
|
208
|
-
log.info(" bun widget validate Validate all widgets");
|
|
209
|
-
log.info(" bun widget publish Build & publish to GlassHome Hub");
|
|
210
|
-
log.info(" bun widget info Show widget metadata");
|
|
211
|
-
log.info(" bun widget upgrade Upgrade @glasshome/widget-sdk");
|
|
212
|
-
|
|
213
|
-
outro("Happy building!");
|
|
214
|
-
} catch (err) {
|
|
215
|
-
s.stop("Failed to scaffold widget project.");
|
|
216
|
-
// Rollback: remove partially created directory
|
|
217
|
-
rmSync(targetDir, { recursive: true, force: true });
|
|
218
|
-
cancel(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
219
|
-
process.exit(1);
|
|
220
|
-
}
|