rahman-resources 1.14.2 → 1.16.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/bin/cli.js +65 -15
- package/lib/manifest.json +709 -488
- package/lib/rr-schema.json +1 -0
- package/lib/rr.mjs +2 -1
- package/lib/slice-schema.json +36 -0
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -169,7 +169,7 @@ ${kleur.dim("Consumer's components/ui/ + lib/utils.ts (shadcn) are never touched
|
|
|
169
169
|
|
|
170
170
|
// Flags that take a value (`--flag x`). Anything else is boolean, so a boolean
|
|
171
171
|
// flag placed before a positional no longer swallows that positional.
|
|
172
|
-
const VALUE_FLAGS = new Set(["target", "template", "category", "at", "skills", "features"]);
|
|
172
|
+
const VALUE_FLAGS = new Set(["target", "template", "category", "at", "skills", "features", "variant"]);
|
|
173
173
|
|
|
174
174
|
function parseFlags(rest) {
|
|
175
175
|
const positional = [];
|
|
@@ -486,7 +486,7 @@ function runShell(cmd, args, cwd) {
|
|
|
486
486
|
|
|
487
487
|
async function runAdd(rest) {
|
|
488
488
|
const { positional, flags } = parseFlags(rest);
|
|
489
|
-
const [slug,
|
|
489
|
+
const [slug, ...restPos] = positional;
|
|
490
490
|
if (!slug) {
|
|
491
491
|
console.error(kleur.red("Missing slug."));
|
|
492
492
|
printHelp();
|
|
@@ -495,6 +495,22 @@ async function runAdd(rest) {
|
|
|
495
495
|
const found = findEntry(slug);
|
|
496
496
|
if (!found) throw new Error(`"${slug}" not found. Run ${kleur.cyan("npx rahman-resources list")}.`);
|
|
497
497
|
const { kind, entry } = found;
|
|
498
|
+
|
|
499
|
+
// Variant disambiguation: a 2nd positional is a variant iff the slice
|
|
500
|
+
// declares it (shadcn-style). `--variant` wins; anything else is the target
|
|
501
|
+
// dir. Non-variant slices have empty `variantIds`, so positional[1] stays
|
|
502
|
+
// the target exactly as before → byte-for-byte back-compat.
|
|
503
|
+
const variantIds = (entry.variants?.items ?? []).map((v) => v.id);
|
|
504
|
+
let variant = typeof flags.variant === "string" ? flags.variant : undefined;
|
|
505
|
+
let targetArg = typeof flags.target === "string" ? flags.target : ".";
|
|
506
|
+
for (const p of restPos) {
|
|
507
|
+
if (!variant && variantIds.includes(p)) variant = p;
|
|
508
|
+
else if (targetArg === ".") targetArg = p;
|
|
509
|
+
}
|
|
510
|
+
if (variant && !variantIds.includes(variant)) {
|
|
511
|
+
console.error(kleur.red(`"${slug}" has no variant "${variant}". Available: ${variantIds.join(", ") || "(none — this slice has no variants)"}`));
|
|
512
|
+
process.exit(1);
|
|
513
|
+
}
|
|
498
514
|
const target = path.resolve(process.cwd(), targetArg);
|
|
499
515
|
|
|
500
516
|
// ── Pre-flight compose check (Phase B). Skipped with --force. ───────────
|
|
@@ -540,11 +556,15 @@ async function runAdd(rest) {
|
|
|
540
556
|
|
|
541
557
|
if (kind === "slice") {
|
|
542
558
|
console.log(
|
|
543
|
-
kleur.bold(`\n→ Adding slice ${kleur.cyan(entry.slug)} `) +
|
|
559
|
+
kleur.bold(`\n→ Adding slice ${kleur.cyan(entry.slug)}${variant ? kleur.magenta(`:${variant}`) : ""} `) +
|
|
544
560
|
kleur.dim(`[SLICE — drop-in feature]`) +
|
|
545
561
|
kleur.bold(` to ${kleur.dim(target)}\n`),
|
|
546
562
|
);
|
|
547
|
-
await runLift([
|
|
563
|
+
await runLift([
|
|
564
|
+
`rahman:${entry.slug}`,
|
|
565
|
+
...(targetArg !== "." ? ["--target", targetArg] : []),
|
|
566
|
+
...(variant ? ["--variant", variant] : []),
|
|
567
|
+
]);
|
|
548
568
|
// Augment consumer .env.example with this slice's env requirements.
|
|
549
569
|
// Idempotent — re-running `add` does not duplicate entries.
|
|
550
570
|
try {
|
|
@@ -1132,11 +1152,12 @@ async function runLift(rest) {
|
|
|
1132
1152
|
}
|
|
1133
1153
|
const target = path.resolve(process.cwd(), typeof flags.target === "string" ? flags.target : ".");
|
|
1134
1154
|
const dryRun = !!flags["dry-run"];
|
|
1155
|
+
const variant = typeof flags.variant === "string" ? flags.variant : undefined;
|
|
1135
1156
|
|
|
1136
1157
|
const parsed = parseLiftSource(src);
|
|
1137
|
-
console.log(kleur.bold(`\n→ Lift ${kleur.cyan(src)} ${dryRun ? kleur.yellow("(dry-run)") : ""}\n`));
|
|
1158
|
+
console.log(kleur.bold(`\n→ Lift ${kleur.cyan(src)}${variant ? kleur.magenta(` :${variant}`) : ""} ${dryRun ? kleur.yellow("(dry-run)") : ""}\n`));
|
|
1138
1159
|
|
|
1139
|
-
const plan = await resolveLiftPlan(parsed, target);
|
|
1160
|
+
const plan = await resolveLiftPlan(parsed, target, variant);
|
|
1140
1161
|
|
|
1141
1162
|
for (const step of plan.steps) {
|
|
1142
1163
|
console.log(` ${kleur.dim(step.from)} → ${kleur.cyan(step.toRel)}`);
|
|
@@ -1189,9 +1210,9 @@ async function runLift(rest) {
|
|
|
1189
1210
|
const slice = (manifest.slices ?? []).find((s) => s.slug === parsed.slug);
|
|
1190
1211
|
if (slice) {
|
|
1191
1212
|
const rr = readRr(target);
|
|
1192
|
-
rrAddSlice(rr, parsed.slug, { version: slice.version, category: slice.category });
|
|
1213
|
+
rrAddSlice(rr, parsed.slug, { version: slice.version, category: slice.category, variant });
|
|
1193
1214
|
writeRr(rr, target);
|
|
1194
|
-
console.log(kleur.dim(` rr.json: slices += ${parsed.slug}@${slice.version}`));
|
|
1215
|
+
console.log(kleur.dim(` rr.json: slices += ${parsed.slug}@${slice.version}${variant ? `:${variant}` : ""}`));
|
|
1195
1216
|
}
|
|
1196
1217
|
}
|
|
1197
1218
|
|
|
@@ -1219,7 +1240,7 @@ function parseLiftSource(src) {
|
|
|
1219
1240
|
return { kind: "github", owner: gh[1], repo: gh[2], subPath: gh[3] };
|
|
1220
1241
|
}
|
|
1221
1242
|
|
|
1222
|
-
async function resolveLiftPlan(parsed, target) {
|
|
1243
|
+
async function resolveLiftPlan(parsed, target, variant) {
|
|
1223
1244
|
const steps = [];
|
|
1224
1245
|
const peers = [];
|
|
1225
1246
|
const npm = [];
|
|
@@ -1231,12 +1252,41 @@ async function resolveLiftPlan(parsed, target) {
|
|
|
1231
1252
|
if (!slice) {
|
|
1232
1253
|
throw new Error(`Slice not found in manifest: ${parsed.slug}. Run 'list slices'.`);
|
|
1233
1254
|
}
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1255
|
+
// Variant install (shadcn-style): copy only variants/<id>/ (flattened into
|
|
1256
|
+
// the slice root so imports resolve at @/features/<slug> exactly like a
|
|
1257
|
+
// non-variant slice) + an optional shared/ folder. Without a variant, the
|
|
1258
|
+
// whole slice tree is pulled (all variants + the root switcher) unchanged.
|
|
1259
|
+
const variants = slice.variants;
|
|
1260
|
+
if (variant && variants) {
|
|
1261
|
+
if (!(variants.items ?? []).some((v) => v.id === variant)) {
|
|
1262
|
+
throw new Error(`Slice "${parsed.slug}" has no variant "${variant}". Available: ${(variants.items ?? []).map((v) => v.id).join(", ")}`);
|
|
1263
|
+
}
|
|
1264
|
+
steps.push({
|
|
1265
|
+
from: `${slice.slicePath}/variants/${variant}`,
|
|
1266
|
+
toRel: slice.slicePath,
|
|
1267
|
+
toAbs: path.join(target, slice.slicePath),
|
|
1268
|
+
});
|
|
1269
|
+
if (variants.shared) {
|
|
1270
|
+
steps.push({
|
|
1271
|
+
from: `${slice.slicePath}/${variants.shared}`,
|
|
1272
|
+
toRel: `${slice.slicePath}/${variants.shared}`,
|
|
1273
|
+
toAbs: path.join(target, slice.slicePath, variants.shared),
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
} else {
|
|
1277
|
+
steps.push({
|
|
1278
|
+
from: slice.slicePath,
|
|
1279
|
+
toRel: slice.slicePath,
|
|
1280
|
+
toAbs: path.join(target, slice.slicePath),
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
// Per-variant convex gating: when a variant is installed and it declares
|
|
1284
|
+
// its own `convex` roots, pull ONLY those (not the slice-level union) — so
|
|
1285
|
+
// `add admin shell` doesn't drag the console variant's backend. Add-all and
|
|
1286
|
+
// variants without a `convex` field fall back to the slice union (as today).
|
|
1287
|
+
const variantDef = variant && variants ? (variants.items ?? []).find((v) => v.id === variant) : null;
|
|
1288
|
+
const convexToPull = variantDef?.convex ?? slice.convexPaths ?? [];
|
|
1289
|
+
for (const cp of convexToPull) {
|
|
1240
1290
|
steps.push({ from: cp, toRel: cp, toAbs: path.join(target, cp) });
|
|
1241
1291
|
}
|
|
1242
1292
|
npm.push(...(slice.npm ?? []));
|