rahman-resources 1.14.2 → 1.16.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/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, targetArg = "."] = positional;
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([`rahman:${entry.slug}`, ...(targetArg !== "." ? ["--target", targetArg] : [])]);
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
- steps.push({
1235
- from: slice.slicePath,
1236
- toRel: slice.slicePath,
1237
- toAbs: path.join(target, slice.slicePath),
1238
- });
1239
- for (const cp of slice.convexPaths ?? []) {
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 ?? []));