@roboticela/devkit 3.0.0 → 4.0.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 +12 -0
- package/dist/commands/add.js +103 -4
- package/dist/commands/eject.d.ts +1 -0
- package/dist/commands/eject.js +60 -0
- package/dist/index.js +10 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -180,6 +180,12 @@ devkit init
|
|
|
180
180
|
Install one or more components into the current project.
|
|
181
181
|
|
|
182
182
|
```bash
|
|
183
|
+
# Interactive — browse the registry for your template, multi-select, then pick variants when needed
|
|
184
|
+
devkit add
|
|
185
|
+
|
|
186
|
+
# Same as above but only print what would be installed
|
|
187
|
+
devkit add --dry-run
|
|
188
|
+
|
|
183
189
|
# Single component
|
|
184
190
|
devkit add auth
|
|
185
191
|
|
|
@@ -196,6 +202,8 @@ devkit add auth@2.0.0
|
|
|
196
202
|
devkit add auth --dry-run
|
|
197
203
|
```
|
|
198
204
|
|
|
205
|
+
**Interactive mode** (no component names): DevKit loads components from the registry filtered by your template in `devkit.config.json`, excludes anything already in `devkit.lock.json`, and opens a terminal multiselect (space to toggle, enter to confirm). For each selected package that has variants, you get a follow-up prompt unless you passed `--variant=<id>` and that id exists for that component.
|
|
206
|
+
|
|
199
207
|
**What happens under the hood:**
|
|
200
208
|
|
|
201
209
|
1. Resolves the component version (latest unless pinned)
|
|
@@ -365,6 +373,10 @@ devkit install
|
|
|
365
373
|
Stop DevKit from tracking a component without deleting its files. After ejecting, the files become entirely yours — no more updates via `devkit update`.
|
|
366
374
|
|
|
367
375
|
```bash
|
|
376
|
+
# Interactive — multiselect from installed components (from devkit.lock.json)
|
|
377
|
+
devkit eject
|
|
378
|
+
|
|
379
|
+
# Eject one component by name
|
|
368
380
|
devkit eject auth
|
|
369
381
|
```
|
|
370
382
|
|
package/dist/commands/add.js
CHANGED
|
@@ -1,27 +1,126 @@
|
|
|
1
1
|
import ora from "ora";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
+
import { intro, outro, multiselect, select, isCancel } from "@clack/prompts";
|
|
3
4
|
import { readConfig, isComponentInstalled } from "../lib/config.js";
|
|
4
5
|
import { installComponent } from "../lib/installer.js";
|
|
6
|
+
import { listComponents } from "../lib/registry.js";
|
|
5
7
|
import { log } from "../lib/logger.js";
|
|
8
|
+
/** `undefined` = no variant; string = chosen id; `"cancel"` = user aborted */
|
|
9
|
+
async function pickVariantForComponent(meta, optsVariant) {
|
|
10
|
+
if (!meta.hasVariants || !meta.variants?.length)
|
|
11
|
+
return undefined;
|
|
12
|
+
if (meta.variants.length === 1)
|
|
13
|
+
return meta.variants[0].id;
|
|
14
|
+
if (optsVariant) {
|
|
15
|
+
const match = meta.variants.find((v) => v.id === optsVariant);
|
|
16
|
+
if (match)
|
|
17
|
+
return match.id;
|
|
18
|
+
}
|
|
19
|
+
const v = await select({
|
|
20
|
+
message: `Variant for ${meta.name}`,
|
|
21
|
+
options: meta.variants.map((x) => ({
|
|
22
|
+
value: x.id,
|
|
23
|
+
label: x.label,
|
|
24
|
+
hint: x.description,
|
|
25
|
+
})),
|
|
26
|
+
});
|
|
27
|
+
if (isCancel(v))
|
|
28
|
+
return "cancel";
|
|
29
|
+
return v;
|
|
30
|
+
}
|
|
31
|
+
async function interactiveAddSelections(config, opts, cwd) {
|
|
32
|
+
let all;
|
|
33
|
+
try {
|
|
34
|
+
all = await listComponents(config.template);
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
log.error(`Could not reach registry: ${e.message}`);
|
|
38
|
+
log.info("Check DEVKIT_REGISTRY or start the registry locally (see repo README).");
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const installable = all.filter((c) => !isComponentInstalled(c.name, cwd));
|
|
42
|
+
if (installable.length === 0) {
|
|
43
|
+
log.info("Every component from the registry for your template is already installed.");
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
console.log();
|
|
47
|
+
intro(chalk.cyan("Add components"));
|
|
48
|
+
const pickedRaw = await multiselect({
|
|
49
|
+
message: "Select components to install (space to toggle, enter to confirm)",
|
|
50
|
+
options: installable.map((c) => ({
|
|
51
|
+
value: c.name,
|
|
52
|
+
label: c.name,
|
|
53
|
+
hint: c.description.length > 64 ? `${c.description.slice(0, 61)}…` : c.description,
|
|
54
|
+
})),
|
|
55
|
+
required: false,
|
|
56
|
+
});
|
|
57
|
+
if (isCancel(pickedRaw)) {
|
|
58
|
+
outro("Cancelled.");
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
const picked = pickedRaw;
|
|
62
|
+
if (picked.length === 0) {
|
|
63
|
+
outro("No components selected.");
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
const results = [];
|
|
67
|
+
for (const compName of picked) {
|
|
68
|
+
const meta = installable.find((x) => x.name === compName);
|
|
69
|
+
const variant = await pickVariantForComponent(meta, opts.variant);
|
|
70
|
+
if (variant === "cancel") {
|
|
71
|
+
outro("Cancelled.");
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
results.push({ name: compName, version: "latest", variant });
|
|
75
|
+
}
|
|
76
|
+
return results;
|
|
77
|
+
}
|
|
6
78
|
export async function addCommand(names, opts, cwd = process.cwd()) {
|
|
7
79
|
const config = readConfig(cwd);
|
|
8
|
-
|
|
9
|
-
|
|
80
|
+
const usedInteractive = names.length === 0;
|
|
81
|
+
let todo;
|
|
82
|
+
if (usedInteractive) {
|
|
83
|
+
const picked = await interactiveAddSelections(config, opts, cwd);
|
|
84
|
+
if (picked === null)
|
|
85
|
+
return;
|
|
86
|
+
if (picked.length === 0)
|
|
87
|
+
return;
|
|
88
|
+
todo = picked;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
todo = names.map((nameAtVersion) => {
|
|
92
|
+
const [name, version = "latest"] = nameAtVersion.split("@");
|
|
93
|
+
return { name, version, variant: opts.variant };
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
let anyOk = false;
|
|
97
|
+
for (const { name, version, variant } of todo) {
|
|
10
98
|
if (isComponentInstalled(name, cwd)) {
|
|
11
99
|
log.warn(`${name} is already installed. Run 'devkit update ${name}' to update.`);
|
|
12
100
|
continue;
|
|
13
101
|
}
|
|
14
102
|
if (opts.dryRun) {
|
|
15
|
-
log.info(`[dry-run] Would install: ${chalk.cyan(name)}@${version} (${config.template})`
|
|
103
|
+
log.info(`[dry-run] Would install: ${chalk.cyan(name)}@${version} (${config.template})` +
|
|
104
|
+
(variant ? chalk.dim(` variant=${variant}`) : ""));
|
|
105
|
+
anyOk = true;
|
|
16
106
|
continue;
|
|
17
107
|
}
|
|
18
108
|
const spinner = ora({ text: `Installing ${chalk.cyan(name)}…`, color: "cyan" }).start();
|
|
19
109
|
try {
|
|
20
|
-
await installComponent(name, config.template, version,
|
|
110
|
+
await installComponent(name, config.template, version, variant, cwd);
|
|
21
111
|
spinner.succeed(chalk.green(`${name}@${version} installed`));
|
|
112
|
+
anyOk = true;
|
|
22
113
|
}
|
|
23
114
|
catch (e) {
|
|
24
115
|
spinner.fail(chalk.red(`Failed to install ${name}: ${e.message}`));
|
|
25
116
|
}
|
|
26
117
|
}
|
|
118
|
+
if (usedInteractive) {
|
|
119
|
+
if (anyOk) {
|
|
120
|
+
outro(opts.dryRun ? chalk.dim("Dry run complete.") : chalk.green("Done."));
|
|
121
|
+
}
|
|
122
|
+
else if (!opts.dryRun) {
|
|
123
|
+
outro(chalk.yellow("Nothing was installed."));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
27
126
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ejectCommand(name: string | undefined, cwd?: string): Promise<void>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { intro, outro, multiselect, isCancel } from "@clack/prompts";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { unlinkSync, existsSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { readLock, writeLock } from "../lib/config.js";
|
|
6
|
+
import { log } from "../lib/logger.js";
|
|
7
|
+
export async function ejectCommand(name, cwd = process.cwd()) {
|
|
8
|
+
const lock = readLock(cwd);
|
|
9
|
+
const installed = Object.keys(lock.components);
|
|
10
|
+
if (installed.length === 0) {
|
|
11
|
+
log.info("No installed components to eject.");
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
let toEject;
|
|
15
|
+
if (name) {
|
|
16
|
+
if (!(name in lock.components)) {
|
|
17
|
+
log.warn(`'${name}' is not an installed DevKit component.`);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
toEject = [name];
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
console.log();
|
|
24
|
+
intro(chalk.cyan("Eject components"));
|
|
25
|
+
const picked = await multiselect({
|
|
26
|
+
message: "Select components to eject (DevKit stops tracking them; files stay on disk — space to toggle, enter to confirm)",
|
|
27
|
+
options: installed.map((n) => ({
|
|
28
|
+
value: n,
|
|
29
|
+
label: n,
|
|
30
|
+
hint: `v${lock.components[n].version}`,
|
|
31
|
+
})),
|
|
32
|
+
required: false,
|
|
33
|
+
});
|
|
34
|
+
if (isCancel(picked)) {
|
|
35
|
+
outro("Cancelled.");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const list = picked;
|
|
39
|
+
if (list.length === 0) {
|
|
40
|
+
outro("No components selected.");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
toEject = list;
|
|
44
|
+
}
|
|
45
|
+
for (const n of toEject) {
|
|
46
|
+
if (!(n in lock.components)) {
|
|
47
|
+
log.warn(`Skipping '${n}' — not in lock file.`);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const manifestPath = join(cwd, ".devkit", `${n}.manifest.json`);
|
|
51
|
+
if (existsSync(manifestPath))
|
|
52
|
+
unlinkSync(manifestPath);
|
|
53
|
+
delete lock.components[n];
|
|
54
|
+
log.success(`${n} ejected. Files are yours — DevKit will no longer manage them.`);
|
|
55
|
+
}
|
|
56
|
+
writeLock(lock, cwd);
|
|
57
|
+
if (!name) {
|
|
58
|
+
outro(chalk.green("Done."));
|
|
59
|
+
}
|
|
60
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { listCommand } from "./commands/list.js";
|
|
|
9
9
|
import { infoCommand } from "./commands/info.js";
|
|
10
10
|
import { updateCommand, upgradeAllCommand } from "./commands/update.js";
|
|
11
11
|
import { doctorCommand } from "./commands/doctor.js";
|
|
12
|
+
import { ejectCommand } from "./commands/eject.js";
|
|
12
13
|
import { themeApplyCommand, themePreviewCommand, themePresetCommand, themeSetCommand, themeListCommand, themeAuditCommand, } from "./commands/theme.js";
|
|
13
14
|
const program = new Command();
|
|
14
15
|
program
|
|
@@ -36,11 +37,11 @@ program
|
|
|
36
37
|
.action(() => initCommand());
|
|
37
38
|
// ── devkit add ────────────────────────────────────────────────────────────────
|
|
38
39
|
program
|
|
39
|
-
.command("add
|
|
40
|
-
.description("Install
|
|
41
|
-
.option("--variant <id>", "Variant
|
|
40
|
+
.command("add [components...]")
|
|
41
|
+
.description("Install components — run with no args for an interactive picker")
|
|
42
|
+
.option("--variant <id>", "Variant for components with variants (non-interactive or as default when prompting)")
|
|
42
43
|
.option("--dry-run", "Preview what would be installed without writing files")
|
|
43
|
-
.action((names, opts) => addCommand(names, opts));
|
|
44
|
+
.action((names, opts) => addCommand(names ?? [], opts));
|
|
44
45
|
// ── devkit remove ─────────────────────────────────────────────────────────────
|
|
45
46
|
program
|
|
46
47
|
.command("remove <name>")
|
|
@@ -124,28 +125,18 @@ program
|
|
|
124
125
|
});
|
|
125
126
|
// ── devkit eject ──────────────────────────────────────────────────────────────
|
|
126
127
|
program
|
|
127
|
-
.command("eject
|
|
128
|
-
.description("Stop DevKit from tracking
|
|
129
|
-
.action(
|
|
130
|
-
const { readLock, writeLock } = await import("./lib/config.js");
|
|
131
|
-
const { unlinkSync, existsSync } = await import("fs");
|
|
132
|
-
const { join } = await import("path");
|
|
133
|
-
const { log } = await import("./lib/logger.js");
|
|
134
|
-
const manifestPath = join(process.cwd(), ".devkit", `${name}.manifest.json`);
|
|
135
|
-
if (existsSync(manifestPath))
|
|
136
|
-
unlinkSync(manifestPath);
|
|
137
|
-
const lock = readLock();
|
|
138
|
-
delete lock.components[name];
|
|
139
|
-
writeLock(lock);
|
|
140
|
-
log.success(`${name} ejected. Files are yours — DevKit will no longer manage them.`);
|
|
141
|
-
});
|
|
128
|
+
.command("eject [name]")
|
|
129
|
+
.description("Stop DevKit from tracking component(s) — run with no args to pick interactively")
|
|
130
|
+
.action((name) => ejectCommand(name));
|
|
142
131
|
// ── Help footer ───────────────────────────────────────────────────────────────
|
|
143
132
|
program.addHelpText("after", `
|
|
144
133
|
${chalk.bold("Examples:")}
|
|
145
134
|
${chalk.cyan("devkit create my-app")} Interactive project creation
|
|
146
135
|
${chalk.cyan("devkit create my-app --template=nextjs-compact --yes")} Non-interactive
|
|
136
|
+
${chalk.cyan("devkit add")} Interactive component picker
|
|
147
137
|
${chalk.cyan("devkit add auth")} Install auth component
|
|
148
138
|
${chalk.cyan("devkit add hero-section --variant=split-image")}
|
|
139
|
+
${chalk.cyan("devkit eject")} Pick component(s) to eject
|
|
149
140
|
${chalk.cyan("devkit theme set colors.primary #e11d48")} Change primary color
|
|
150
141
|
${chalk.cyan("devkit doctor")} Check configuration
|
|
151
142
|
${chalk.cyan("devkit list")} Browse all components
|