@roboticela/devkit 1.0.2
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/dist/commands/add.d.ts +6 -0
- package/dist/commands/add.js +27 -0
- package/dist/commands/create.d.ts +11 -0
- package/dist/commands/create.js +181 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +76 -0
- package/dist/commands/info.d.ts +1 -0
- package/dist/commands/info.js +53 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +65 -0
- package/dist/commands/list.d.ts +7 -0
- package/dist/commands/list.js +52 -0
- package/dist/commands/remove.d.ts +3 -0
- package/dist/commands/remove.js +22 -0
- package/dist/commands/theme.d.ts +6 -0
- package/dist/commands/theme.js +82 -0
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.js +45 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +155 -0
- package/dist/lib/config.d.ts +51 -0
- package/dist/lib/config.js +48 -0
- package/dist/lib/detector.d.ts +2 -0
- package/dist/lib/detector.js +18 -0
- package/dist/lib/installer.d.ts +2 -0
- package/dist/lib/installer.js +178 -0
- package/dist/lib/logger.d.ts +10 -0
- package/dist/lib/logger.js +15 -0
- package/dist/lib/registry.d.ts +26 -0
- package/dist/lib/registry.js +30 -0
- package/dist/lib/template.d.ts +1 -0
- package/dist/lib/template.js +56 -0
- package/dist/lib/theme.d.ts +2 -0
- package/dist/lib/theme.js +221 -0
- package/package.json +58 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import ora from "ora";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { readConfig, isComponentInstalled } from "../lib/config.js";
|
|
4
|
+
import { installComponent } from "../lib/installer.js";
|
|
5
|
+
import { log } from "../lib/logger.js";
|
|
6
|
+
export async function addCommand(names, opts, cwd = process.cwd()) {
|
|
7
|
+
const config = readConfig(cwd);
|
|
8
|
+
for (const nameAtVersion of names) {
|
|
9
|
+
const [name, version = "latest"] = nameAtVersion.split("@");
|
|
10
|
+
if (isComponentInstalled(name, cwd)) {
|
|
11
|
+
log.warn(`${name} is already installed. Run 'devkit update ${name}' to update.`);
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (opts.dryRun) {
|
|
15
|
+
log.info(`[dry-run] Would install: ${chalk.cyan(name)}@${version} (${config.template})`);
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const spinner = ora({ text: `Installing ${chalk.cyan(name)}…`, color: "cyan" }).start();
|
|
19
|
+
try {
|
|
20
|
+
await installComponent(name, config.template, version, opts.variant, cwd);
|
|
21
|
+
spinner.succeed(chalk.green(`${name}@${version} installed`));
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
spinner.fail(chalk.red(`Failed to install ${name}: ${e.message}`));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface CreateOptions {
|
|
2
|
+
template?: string;
|
|
3
|
+
preset?: string;
|
|
4
|
+
primary?: string;
|
|
5
|
+
git?: boolean;
|
|
6
|
+
install?: boolean;
|
|
7
|
+
add?: string;
|
|
8
|
+
yes?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function createCommand(projectName: string | undefined, opts: CreateOptions): Promise<void>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { intro, outro, text, select, multiselect, confirm, spinner } from "@clack/prompts";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { execSync } from "child_process";
|
|
4
|
+
import { writeFileSync, mkdirSync, existsSync, readFileSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { downloadTemplate } from "../lib/template.js";
|
|
7
|
+
import { writeConfig, writeLock, defaultConfig } from "../lib/config.js";
|
|
8
|
+
import { generateThemeCSS } from "../lib/theme.js";
|
|
9
|
+
import { installComponent } from "../lib/installer.js";
|
|
10
|
+
import { log } from "../lib/logger.js";
|
|
11
|
+
const TEMPLATES = [
|
|
12
|
+
{
|
|
13
|
+
value: "nextjs-compact",
|
|
14
|
+
label: "NextJS Compact",
|
|
15
|
+
hint: "Next.js 16 · React 19 · TailwindCSS 4 — Web only, SSR/RSC",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
value: "vite-express-tauri",
|
|
19
|
+
label: "Wide Express (Vite + Express + Tauri)",
|
|
20
|
+
hint: "Vite 7 · React 19 · Express 5 · Tauri 2 — Web + Desktop",
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
const PRESETS = [
|
|
24
|
+
{ value: "default", label: "Default — Indigo + Amber — Modern SaaS" },
|
|
25
|
+
{ value: "minimal", label: "Minimal — Slate + Sky — Clean, editorial" },
|
|
26
|
+
{ value: "bold", label: "Bold — Violet + Pink — Vibrant, expressive" },
|
|
27
|
+
{ value: "playful", label: "Playful — Emerald + Orange — Friendly, rounded" },
|
|
28
|
+
{ value: "corporate", label: "Corporate — Blue + Gray — Formal, enterprise" },
|
|
29
|
+
];
|
|
30
|
+
const COMPONENTS = [
|
|
31
|
+
{ value: "auth", label: "auth — Full authentication system" },
|
|
32
|
+
{ value: "hero-section", label: "hero-section — Landing page hero banner" },
|
|
33
|
+
{ value: "profile", label: "profile — User profile pages" },
|
|
34
|
+
{ value: "pricing", label: "pricing — Pricing table" },
|
|
35
|
+
{ value: "dashboard", label: "dashboard — App dashboard layout" },
|
|
36
|
+
{ value: "contact-form", label: "contact-form — Contact / support form" },
|
|
37
|
+
];
|
|
38
|
+
export async function createCommand(projectName, opts) {
|
|
39
|
+
console.log();
|
|
40
|
+
console.log(chalk.bold.white(" ┌─────────────────────────────────────────────────────┐"));
|
|
41
|
+
console.log(chalk.bold.white(" │ Roboticela DevKit — Create New Project │"));
|
|
42
|
+
console.log(chalk.bold.white(" │ v1.0.0 │"));
|
|
43
|
+
console.log(chalk.bold.white(" └─────────────────────────────────────────────────────┘"));
|
|
44
|
+
console.log();
|
|
45
|
+
intro(chalk.cyan("Let's scaffold your project"));
|
|
46
|
+
// ── Collect answers ──────────────────────────────────────────────────────
|
|
47
|
+
const name = projectName ?? (opts.yes ? "my-app" : await text({
|
|
48
|
+
message: "Project name",
|
|
49
|
+
placeholder: "my-app",
|
|
50
|
+
validate: (v) => v.length < 1 ? "Name is required" : undefined,
|
|
51
|
+
}));
|
|
52
|
+
if (!name || typeof name !== "string") {
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
const template = (opts.template ?? (opts.yes ? "nextjs-compact" : await select({
|
|
56
|
+
message: "Select a template",
|
|
57
|
+
options: TEMPLATES,
|
|
58
|
+
})));
|
|
59
|
+
const siteName = opts.yes ? name : await text({
|
|
60
|
+
message: "Site display name",
|
|
61
|
+
initialValue: name,
|
|
62
|
+
});
|
|
63
|
+
const siteUrl = opts.yes ? `https://${name}.com` : await text({
|
|
64
|
+
message: "Site URL",
|
|
65
|
+
placeholder: "https://myapp.com",
|
|
66
|
+
});
|
|
67
|
+
const preset = opts.preset ?? (opts.yes ? "default" : await select({
|
|
68
|
+
message: "Theme preset",
|
|
69
|
+
options: PRESETS,
|
|
70
|
+
}));
|
|
71
|
+
const primaryColor = opts.primary ?? (opts.yes ? undefined : await text({
|
|
72
|
+
message: "Primary brand color (hex, leave blank to use preset)",
|
|
73
|
+
placeholder: "(leave blank for preset default)",
|
|
74
|
+
validate: (v) => v && !/^#[0-9a-fA-F]{6}$/.test(v) ? "Enter a valid hex like #6366f1 or leave blank" : undefined,
|
|
75
|
+
}));
|
|
76
|
+
const doGit = opts.git ?? (opts.yes ? true : await confirm({ message: "Initialize a git repository?" }));
|
|
77
|
+
const doInstall = opts.install ?? (opts.yes ? true : await confirm({ message: "Install npm dependencies now?" }));
|
|
78
|
+
const selectedComponents = opts.add
|
|
79
|
+
? opts.add.split(",").map((s) => s.trim())
|
|
80
|
+
: (opts.yes ? [] : await multiselect({
|
|
81
|
+
message: "Add components now? (space to select, enter to confirm — you can always run 'devkit add' later)",
|
|
82
|
+
options: COMPONENTS,
|
|
83
|
+
required: false,
|
|
84
|
+
}));
|
|
85
|
+
// ── Build ────────────────────────────────────────────────────────────────
|
|
86
|
+
console.log();
|
|
87
|
+
log.header("Creating project…");
|
|
88
|
+
const destDir = join(process.cwd(), name);
|
|
89
|
+
if (existsSync(destDir)) {
|
|
90
|
+
log.error(`Directory '${name}' already exists.`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
mkdirSync(destDir, { recursive: true });
|
|
94
|
+
// 1. Download template
|
|
95
|
+
const s = spinner();
|
|
96
|
+
s.start(`Fetching template: ${chalk.cyan(template)}`);
|
|
97
|
+
try {
|
|
98
|
+
await downloadTemplate(template, destDir);
|
|
99
|
+
s.stop(chalk.green(`✓ Template fetched: ${template}`));
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
s.stop(chalk.red(`✗ Failed to fetch template`));
|
|
103
|
+
log.error(e.message);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
// 2. Write devkit.config.json
|
|
107
|
+
const config = defaultConfig(template, siteName, siteUrl);
|
|
108
|
+
config.theme.preset = preset;
|
|
109
|
+
if (primaryColor)
|
|
110
|
+
config.theme.colors = { ...config.theme.colors, primary: primaryColor };
|
|
111
|
+
writeConfig(config, destDir);
|
|
112
|
+
log.success("Created devkit.config.json");
|
|
113
|
+
// 3. Write devkit.lock.json
|
|
114
|
+
writeLock({ lockVersion: 1, template, components: {} }, destDir);
|
|
115
|
+
log.success("Created devkit.lock.json");
|
|
116
|
+
// 4. Generate theme CSS
|
|
117
|
+
const themeCss = generateThemeCSS(config);
|
|
118
|
+
const cssPath = template === "nextjs-compact"
|
|
119
|
+
? join(destDir, "app", "globals.css")
|
|
120
|
+
: join(destDir, "src", "index.css");
|
|
121
|
+
if (existsSync(cssPath)) {
|
|
122
|
+
const existing = readFileSync(cssPath, "utf-8");
|
|
123
|
+
const withoutOldBlock = existing.replace(/\/\* ── DevKit Managed Theme Block[\s\S]*?── End DevKit Managed Theme Block ── \*\//, "").trimStart();
|
|
124
|
+
writeFileSync(cssPath, themeCss + withoutOldBlock);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
mkdirSync(join(destDir, template === "nextjs-compact" ? "app" : "src"), { recursive: true });
|
|
128
|
+
writeFileSync(cssPath, themeCss);
|
|
129
|
+
}
|
|
130
|
+
log.success(`Applied theme preset: ${preset}`);
|
|
131
|
+
// 5. npm install
|
|
132
|
+
if (doInstall) {
|
|
133
|
+
s.start("Installing npm dependencies…");
|
|
134
|
+
try {
|
|
135
|
+
execSync("npm install", { cwd: destDir, stdio: "pipe" });
|
|
136
|
+
s.stop(chalk.green("✓ npm install complete"));
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
s.stop(chalk.yellow("⚠ npm install failed — run it manually"));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// 6. git init
|
|
143
|
+
if (doGit) {
|
|
144
|
+
try {
|
|
145
|
+
execSync("git init", { cwd: destDir, stdio: "pipe" });
|
|
146
|
+
execSync('git add . && git commit -m "chore: init project with DevKit"', { cwd: destDir, stdio: "pipe" });
|
|
147
|
+
log.success("Initialized git repository");
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
log.warn("git init failed — git may not be installed");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// 7. Add selected components
|
|
154
|
+
for (const comp of selectedComponents) {
|
|
155
|
+
s.start(`Adding component: ${chalk.cyan(comp)}`);
|
|
156
|
+
try {
|
|
157
|
+
await installComponent(comp, template, "latest", undefined, destDir);
|
|
158
|
+
s.stop(chalk.green(`✓ Added: ${comp}`));
|
|
159
|
+
}
|
|
160
|
+
catch (e) {
|
|
161
|
+
s.stop(chalk.yellow(`⚠ Skipped ${comp}: ${e.message}`));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// ── Done ─────────────────────────────────────────────────────────────────
|
|
165
|
+
console.log();
|
|
166
|
+
outro(chalk.green("Project ready!"));
|
|
167
|
+
console.log();
|
|
168
|
+
console.log(` ${chalk.bold("Location")} ${name}/`);
|
|
169
|
+
console.log(` ${chalk.bold("Template")} ${template}`);
|
|
170
|
+
console.log(` ${chalk.bold("Theme")} ${preset}`);
|
|
171
|
+
if (selectedComponents.length > 0) {
|
|
172
|
+
console.log(` ${chalk.bold("Components")} ${selectedComponents.join(", ")}`);
|
|
173
|
+
}
|
|
174
|
+
console.log();
|
|
175
|
+
console.log(chalk.bold(" Next steps:"));
|
|
176
|
+
console.log(` ${chalk.cyan("cd")} ${name}`);
|
|
177
|
+
console.log(` ${chalk.cyan("cp")} .env.example .env ${chalk.gray("# Fill in your secrets")}`);
|
|
178
|
+
console.log(` ${chalk.cyan("devkit doctor")} ${chalk.gray("# Check for missing config")}`);
|
|
179
|
+
console.log(` ${chalk.cyan("npm run dev")} ${chalk.gray("# Start the dev server")}`);
|
|
180
|
+
console.log();
|
|
181
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function doctorCommand(cwd?: string): Promise<void>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { existsSync, readFileSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { readConfig, readLock } from "../lib/config.js";
|
|
5
|
+
import { log } from "../lib/logger.js";
|
|
6
|
+
import { healthCheck } from "../lib/registry.js";
|
|
7
|
+
export async function doctorCommand(cwd = process.cwd()) {
|
|
8
|
+
let ok = true;
|
|
9
|
+
log.blank();
|
|
10
|
+
log.header("DevKit Doctor");
|
|
11
|
+
// Config check
|
|
12
|
+
const configPath = join(cwd, "devkit.config.json");
|
|
13
|
+
if (existsSync(configPath)) {
|
|
14
|
+
log.success("devkit.config.json found");
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
log.error("devkit.config.json not found — run 'devkit init'");
|
|
18
|
+
ok = false;
|
|
19
|
+
}
|
|
20
|
+
// Template check
|
|
21
|
+
try {
|
|
22
|
+
const config = readConfig(cwd);
|
|
23
|
+
log.success(`Template: ${config.template}`);
|
|
24
|
+
// Site config
|
|
25
|
+
if (!config.site.name) {
|
|
26
|
+
log.warn("site.name is empty");
|
|
27
|
+
ok = false;
|
|
28
|
+
}
|
|
29
|
+
if (!config.site.url) {
|
|
30
|
+
log.warn("site.url is empty");
|
|
31
|
+
ok = false;
|
|
32
|
+
}
|
|
33
|
+
// Installed components env vars
|
|
34
|
+
const lock = readLock(cwd);
|
|
35
|
+
for (const [name, entry] of Object.entries(lock.components)) {
|
|
36
|
+
const manifestPath = join(cwd, ".devkit", `${name}.manifest.json`);
|
|
37
|
+
if (!existsSync(manifestPath)) {
|
|
38
|
+
log.warn(`${name}@${entry.version} — manifest missing (reinstall with 'devkit add ${name}')`);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
42
|
+
const envVars = manifest.envVars ?? [];
|
|
43
|
+
let compOk = true;
|
|
44
|
+
for (const envVar of envVars) {
|
|
45
|
+
if (!process.env[envVar]) {
|
|
46
|
+
log.warn(`${name} — Missing env var: ${chalk.yellow(envVar)}`);
|
|
47
|
+
compOk = false;
|
|
48
|
+
ok = false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (compOk) {
|
|
52
|
+
log.success(`${name}@${entry.version} — OK`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
log.error(e.message);
|
|
58
|
+
ok = false;
|
|
59
|
+
}
|
|
60
|
+
// Registry reachability
|
|
61
|
+
const registryOk = await healthCheck();
|
|
62
|
+
if (registryOk) {
|
|
63
|
+
log.success("Registry server reachable");
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
log.warn("Registry server not reachable (offline mode)");
|
|
67
|
+
}
|
|
68
|
+
log.blank();
|
|
69
|
+
if (ok) {
|
|
70
|
+
log.success("Everything looks good!");
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
log.warn("Some issues found — see above.");
|
|
74
|
+
}
|
|
75
|
+
log.blank();
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function infoCommand(name: string): Promise<void>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { getComponentInfo } from "../lib/registry.js";
|
|
3
|
+
import { log } from "../lib/logger.js";
|
|
4
|
+
export async function infoCommand(name) {
|
|
5
|
+
try {
|
|
6
|
+
const info = await getComponentInfo(name);
|
|
7
|
+
const versions = info["versions"];
|
|
8
|
+
const variants = info["variants"];
|
|
9
|
+
log.blank();
|
|
10
|
+
console.log(` ${chalk.bold.white(info["displayName"])} ${chalk.dim(`(${name})`)}`);
|
|
11
|
+
console.log(` ${chalk.dim(info["description"])}`);
|
|
12
|
+
log.blank();
|
|
13
|
+
console.log(` ${chalk.bold("Version")} ${versions.latest}`);
|
|
14
|
+
console.log(` ${chalk.bold("Category")} ${info["category"]}`);
|
|
15
|
+
console.log(` ${chalk.bold("Templates")} ${info["templates"].join(", ")}`);
|
|
16
|
+
console.log(` ${chalk.bold("Platforms")} ${info["platforms"].join(", ")}`);
|
|
17
|
+
if (variants?.length) {
|
|
18
|
+
log.blank();
|
|
19
|
+
console.log(` ${chalk.bold("Variants:")}`);
|
|
20
|
+
for (const v of variants) {
|
|
21
|
+
console.log(` ${chalk.cyan(v.id.padEnd(18))} ${chalk.dim(v.description)}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const req = info["requiredConfig"];
|
|
25
|
+
const opt = info["optionalConfig"];
|
|
26
|
+
if (req?.length) {
|
|
27
|
+
log.blank();
|
|
28
|
+
console.log(` ${chalk.bold("Required config:")}`);
|
|
29
|
+
for (const k of req)
|
|
30
|
+
console.log(` ${chalk.yellow(k)}`);
|
|
31
|
+
}
|
|
32
|
+
if (opt?.length) {
|
|
33
|
+
console.log(` ${chalk.bold("Optional config:")}`);
|
|
34
|
+
for (const k of opt)
|
|
35
|
+
console.log(` ${chalk.dim(k)}`);
|
|
36
|
+
}
|
|
37
|
+
log.blank();
|
|
38
|
+
console.log(` ${chalk.bold("Version history:")}`);
|
|
39
|
+
for (const v of versions.history) {
|
|
40
|
+
const tag = v.breaking ? chalk.red(" [breaking]") : "";
|
|
41
|
+
console.log(` ${chalk.cyan(v.version)} ${chalk.dim(v.releaseDate)}${tag}`);
|
|
42
|
+
}
|
|
43
|
+
log.blank();
|
|
44
|
+
console.log(` ${chalk.dim("Install:")} devkit add ${name}`);
|
|
45
|
+
if (variants?.length) {
|
|
46
|
+
console.log(` ${chalk.dim("With variant:")} devkit add ${name} --variant=${variants[0].id}`);
|
|
47
|
+
}
|
|
48
|
+
log.blank();
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
log.error(`Component '${name}' not found: ${e.message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function initCommand(cwd?: string): Promise<void>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { intro, outro, text, select } from "@clack/prompts";
|
|
2
|
+
import { writeFileSync, mkdirSync, existsSync, readFileSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { detectTemplate } from "../lib/detector.js";
|
|
5
|
+
import { writeConfig, writeLock, defaultConfig, configExists } from "../lib/config.js";
|
|
6
|
+
import { generateThemeCSS } from "../lib/theme.js";
|
|
7
|
+
import { log } from "../lib/logger.js";
|
|
8
|
+
export async function initCommand(cwd = process.cwd()) {
|
|
9
|
+
if (configExists(cwd)) {
|
|
10
|
+
log.warn("DevKit is already initialized in this project.");
|
|
11
|
+
log.info("Run 'devkit doctor' to check configuration.");
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
intro("DevKit Init");
|
|
15
|
+
const detected = detectTemplate(cwd);
|
|
16
|
+
const template = detected !== "unknown"
|
|
17
|
+
? detected
|
|
18
|
+
: await select({
|
|
19
|
+
message: "Could not auto-detect template. Select manually:",
|
|
20
|
+
options: [
|
|
21
|
+
{ value: "nextjs-compact", label: "NextJS Compact" },
|
|
22
|
+
{ value: "vite-express-tauri", label: "Wide Express (Vite + Express + Tauri)" },
|
|
23
|
+
],
|
|
24
|
+
});
|
|
25
|
+
if (detected !== "unknown") {
|
|
26
|
+
log.success(`Detected template: ${template}`);
|
|
27
|
+
}
|
|
28
|
+
const siteName = await text({ message: "Site name", placeholder: "My App" });
|
|
29
|
+
const siteUrl = await text({ message: "Site URL", placeholder: "https://myapp.com" });
|
|
30
|
+
const preset = await select({
|
|
31
|
+
message: "Theme preset",
|
|
32
|
+
options: [
|
|
33
|
+
{ value: "default", label: "Default — Indigo" },
|
|
34
|
+
{ value: "minimal", label: "Minimal — Slate" },
|
|
35
|
+
{ value: "bold", label: "Bold — Violet" },
|
|
36
|
+
{ value: "playful", label: "Playful — Emerald" },
|
|
37
|
+
{ value: "corporate", label: "Corporate — Blue" },
|
|
38
|
+
],
|
|
39
|
+
});
|
|
40
|
+
// Write config
|
|
41
|
+
const config = defaultConfig(template, siteName, siteUrl);
|
|
42
|
+
config.theme.preset = preset;
|
|
43
|
+
writeConfig(config, cwd);
|
|
44
|
+
log.success("Created devkit.config.json");
|
|
45
|
+
// Write lock
|
|
46
|
+
writeLock({ lockVersion: 1, template, components: {} }, cwd);
|
|
47
|
+
log.success("Created devkit.lock.json");
|
|
48
|
+
// Generate theme CSS
|
|
49
|
+
const themeCss = generateThemeCSS(config);
|
|
50
|
+
const cssFile = template === "nextjs-compact" ? "app/globals.css" : "src/index.css";
|
|
51
|
+
const cssPath = join(cwd, cssFile);
|
|
52
|
+
if (existsSync(cssPath)) {
|
|
53
|
+
const old = readFileSync(cssPath, "utf-8");
|
|
54
|
+
const clean = old.replace(/\/\* ── DevKit Managed Theme Block[\s\S]*?── End DevKit Managed Theme Block ── \*\//, "").trimStart();
|
|
55
|
+
writeFileSync(cssPath, themeCss + clean);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
mkdirSync(join(cwd, template === "nextjs-compact" ? "app" : "src"), { recursive: true });
|
|
59
|
+
writeFileSync(cssPath, themeCss);
|
|
60
|
+
}
|
|
61
|
+
log.success(`Applied theme to ${cssFile}`);
|
|
62
|
+
// .devkit dir
|
|
63
|
+
mkdirSync(join(cwd, ".devkit"), { recursive: true });
|
|
64
|
+
outro("DevKit initialized! Run 'devkit list' to browse components.");
|
|
65
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { listComponents } from "../lib/registry.js";
|
|
3
|
+
import { readConfig, readLock } from "../lib/config.js";
|
|
4
|
+
import { log } from "../lib/logger.js";
|
|
5
|
+
export async function listCommand(opts, cwd = process.cwd()) {
|
|
6
|
+
let template = opts.template;
|
|
7
|
+
if (!template) {
|
|
8
|
+
try {
|
|
9
|
+
template = readConfig(cwd).template;
|
|
10
|
+
}
|
|
11
|
+
catch { /* no config */ }
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const components = await listComponents(template);
|
|
15
|
+
const lock = (() => { try {
|
|
16
|
+
return readLock(cwd);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return { lockVersion: 1, template: "", components: {} };
|
|
20
|
+
} })();
|
|
21
|
+
const filtered = opts.installed
|
|
22
|
+
? components.filter((c) => c.name in lock.components)
|
|
23
|
+
: opts.category
|
|
24
|
+
? components.filter((c) => c.category === opts.category)
|
|
25
|
+
: components;
|
|
26
|
+
if (filtered.length === 0) {
|
|
27
|
+
log.info("No components found.");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
log.blank();
|
|
31
|
+
const col = (s, w) => s.padEnd(w).slice(0, w);
|
|
32
|
+
console.log(chalk.gray(" " + col("Component", 20) + col("Category", 16) + col("Templates", 36) + "Installed"));
|
|
33
|
+
console.log(chalk.gray(" " + "─".repeat(80)));
|
|
34
|
+
for (const comp of filtered) {
|
|
35
|
+
const installed = lock.components[comp.name];
|
|
36
|
+
const installedStr = installed ? chalk.green(`v${installed.version}`) : chalk.gray("-");
|
|
37
|
+
const templateStr = comp.templates.join(", ");
|
|
38
|
+
console.log(" " +
|
|
39
|
+
chalk.bold(col(comp.name, 20)) +
|
|
40
|
+
chalk.dim(col(comp.category, 16)) +
|
|
41
|
+
col(templateStr, 36) +
|
|
42
|
+
installedStr);
|
|
43
|
+
}
|
|
44
|
+
log.blank();
|
|
45
|
+
log.info(`${filtered.length} component(s). Run ${chalk.cyan("devkit info <name>")} for details.`);
|
|
46
|
+
log.blank();
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
log.error(`Could not reach registry: ${e.message}`);
|
|
50
|
+
log.info("Is the registry server running? Start it with: cd registry && npm run dev");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { confirm } from "@clack/prompts";
|
|
2
|
+
import { isComponentInstalled } from "../lib/config.js";
|
|
3
|
+
import { removeComponent } from "../lib/installer.js";
|
|
4
|
+
import { log } from "../lib/logger.js";
|
|
5
|
+
export async function removeCommand(name, opts, cwd = process.cwd()) {
|
|
6
|
+
if (!isComponentInstalled(name, cwd)) {
|
|
7
|
+
log.error(`'${name}' is not installed.`);
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
const ok = await confirm({ message: `Remove ${name} and all its managed files?` });
|
|
11
|
+
if (!ok) {
|
|
12
|
+
log.info("Aborted.");
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
removeComponent(name, cwd);
|
|
17
|
+
log.success(`${name} removed.`);
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
log.error(e.message);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function themeApplyCommand(cwd?: string): void;
|
|
2
|
+
export declare function themePreviewCommand(cwd?: string): void;
|
|
3
|
+
export declare function themePresetCommand(preset: string, cwd?: string): void;
|
|
4
|
+
export declare function themeSetCommand(key: string, value: string, cwd?: string): void;
|
|
5
|
+
export declare function themeListCommand(cwd?: string): void;
|
|
6
|
+
export declare function themeAuditCommand(_cwd?: string): void;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { writeFileSync, readFileSync, existsSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { readConfig, writeConfig } from "../lib/config.js";
|
|
5
|
+
import { generateThemeCSS } from "../lib/theme.js";
|
|
6
|
+
import { log } from "../lib/logger.js";
|
|
7
|
+
function getCssPath(template, cwd) {
|
|
8
|
+
return template === "nextjs-compact"
|
|
9
|
+
? join(cwd, "app", "globals.css")
|
|
10
|
+
: join(cwd, "src", "index.css");
|
|
11
|
+
}
|
|
12
|
+
function patchCss(cssPath, newBlock) {
|
|
13
|
+
const existing = existsSync(cssPath) ? readFileSync(cssPath, "utf-8") : "";
|
|
14
|
+
const userSection = existing
|
|
15
|
+
.replace(/\/\* ── DevKit Managed Theme Block[\s\S]*?── End DevKit Managed Theme Block ── \*\//, "")
|
|
16
|
+
.trimStart();
|
|
17
|
+
writeFileSync(cssPath, newBlock + userSection);
|
|
18
|
+
}
|
|
19
|
+
export function themeApplyCommand(cwd = process.cwd()) {
|
|
20
|
+
const config = readConfig(cwd);
|
|
21
|
+
const css = generateThemeCSS(config);
|
|
22
|
+
patchCss(getCssPath(config.template, cwd), css);
|
|
23
|
+
log.success("Theme applied to CSS.");
|
|
24
|
+
}
|
|
25
|
+
export function themePreviewCommand(cwd = process.cwd()) {
|
|
26
|
+
const config = readConfig(cwd);
|
|
27
|
+
const css = generateThemeCSS(config);
|
|
28
|
+
log.blank();
|
|
29
|
+
console.log(chalk.dim(css.split("\n").map((l) => " " + l).join("\n")));
|
|
30
|
+
}
|
|
31
|
+
export function themePresetCommand(preset, cwd = process.cwd()) {
|
|
32
|
+
const VALID = ["default", "minimal", "bold", "playful", "corporate"];
|
|
33
|
+
if (!VALID.includes(preset)) {
|
|
34
|
+
log.error(`Unknown preset '${preset}'. Valid: ${VALID.join(", ")}`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
const config = readConfig(cwd);
|
|
38
|
+
config.theme = { ...config.theme, preset };
|
|
39
|
+
writeConfig(config, cwd);
|
|
40
|
+
themeApplyCommand(cwd);
|
|
41
|
+
log.success(`Switched to preset: ${preset}`);
|
|
42
|
+
}
|
|
43
|
+
export function themeSetCommand(key, value, cwd = process.cwd()) {
|
|
44
|
+
const config = readConfig(cwd);
|
|
45
|
+
// Only support: colors.primary, colors.secondary, fonts.sans, fonts.mono, radius, darkMode
|
|
46
|
+
if (key === "colors.primary")
|
|
47
|
+
config.theme.colors = { ...config.theme.colors, primary: value };
|
|
48
|
+
else if (key === "colors.secondary")
|
|
49
|
+
config.theme.colors = { ...config.theme.colors, secondary: value };
|
|
50
|
+
else if (key === "fonts.sans")
|
|
51
|
+
config.theme.fonts = { ...config.theme.fonts, sans: value };
|
|
52
|
+
else if (key === "fonts.mono")
|
|
53
|
+
config.theme.fonts = { ...config.theme.fonts, mono: value };
|
|
54
|
+
else if (key === "radius")
|
|
55
|
+
config.theme.radius = value;
|
|
56
|
+
else if (key === "darkMode")
|
|
57
|
+
config.theme.darkMode = value === "true";
|
|
58
|
+
else {
|
|
59
|
+
log.error(`Unknown theme key: ${key}`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
writeConfig(config, cwd);
|
|
63
|
+
themeApplyCommand(cwd);
|
|
64
|
+
log.success(`Set ${key} = ${chalk.cyan(value)}`);
|
|
65
|
+
}
|
|
66
|
+
export function themeListCommand(cwd = process.cwd()) {
|
|
67
|
+
const config = readConfig(cwd);
|
|
68
|
+
const t = config.theme ?? {};
|
|
69
|
+
log.blank();
|
|
70
|
+
console.log(` ${chalk.bold("Preset")} ${t.preset ?? "default"}`);
|
|
71
|
+
console.log(` ${chalk.bold("Primary")} ${chalk.hex(t.colors?.primary ?? "#6366f1")(t.colors?.primary ?? "#6366f1 (preset)")}`);
|
|
72
|
+
console.log(` ${chalk.bold("Secondary")} ${t.colors?.secondary ?? "(preset)"}`);
|
|
73
|
+
console.log(` ${chalk.bold("Font sans")} ${t.fonts?.sans ?? "Inter"}`);
|
|
74
|
+
console.log(` ${chalk.bold("Font mono")} ${t.fonts?.mono ?? "JetBrains Mono"}`);
|
|
75
|
+
console.log(` ${chalk.bold("Radius")} ${t.radius ?? "md"}`);
|
|
76
|
+
console.log(` ${chalk.bold("Dark mode")} ${t.darkMode !== false ? "enabled" : "disabled"}`);
|
|
77
|
+
log.blank();
|
|
78
|
+
}
|
|
79
|
+
export function themeAuditCommand(_cwd = process.cwd()) {
|
|
80
|
+
log.warn("Theme audit checks component files for hardcoded colors (not yet implemented in CLI).");
|
|
81
|
+
log.info("This runs automatically in registry CI when submitting a component.");
|
|
82
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import ora from "ora";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { readConfig, readLock, isComponentInstalled } from "../lib/config.js";
|
|
4
|
+
import { installComponent } from "../lib/installer.js";
|
|
5
|
+
import { log } from "../lib/logger.js";
|
|
6
|
+
export async function updateCommand(nameAtVersion, cwd = process.cwd()) {
|
|
7
|
+
const [name, version = "latest"] = nameAtVersion.split("@");
|
|
8
|
+
const config = readConfig(cwd);
|
|
9
|
+
if (!isComponentInstalled(name, cwd)) {
|
|
10
|
+
log.error(`'${name}' is not installed. Use 'devkit add ${name}' to install.`);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const lock = readLock(cwd);
|
|
14
|
+
const current = lock.components[name];
|
|
15
|
+
log.info(`Updating ${chalk.cyan(name)} from v${current.version} → ${version}`);
|
|
16
|
+
const spinner = ora({ text: `Updating ${name}…`, color: "cyan" }).start();
|
|
17
|
+
try {
|
|
18
|
+
await installComponent(name, config.template, version, current.variant ?? undefined, cwd);
|
|
19
|
+
spinner.succeed(chalk.green(`${name} updated`));
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
spinner.fail(chalk.red(`Update failed: ${e.message}`));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export async function upgradeAllCommand(cwd = process.cwd()) {
|
|
26
|
+
const config = readConfig(cwd);
|
|
27
|
+
const lock = readLock(cwd);
|
|
28
|
+
const names = Object.keys(lock.components);
|
|
29
|
+
if (names.length === 0) {
|
|
30
|
+
log.info("No components installed.");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
log.info(`Upgrading ${names.length} component(s)…`);
|
|
34
|
+
for (const name of names) {
|
|
35
|
+
const spinner = ora({ text: `Upgrading ${chalk.cyan(name)}…`, color: "cyan" }).start();
|
|
36
|
+
try {
|
|
37
|
+
const variant = lock.components[name].variant ?? undefined;
|
|
38
|
+
await installComponent(name, config.template, "latest", variant, cwd);
|
|
39
|
+
spinner.succeed(chalk.green(`${name} upgraded`));
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
spinner.fail(chalk.yellow(`${name}: ${e.message}`));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|