blockend-cli 2.0.2 → 2.1.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.
@@ -29,6 +29,8 @@ export interface RegistryManifest {
29
29
  blocks?: Record<string, BlockManifest>;
30
30
  [blockKey: string]: unknown;
31
31
  }
32
+ export declare function selectVariant(variantKeys: string[], preferredVariant?: string): string;
33
+ export declare function resolvePreferredVariant(variantKeys: string[], recordedVariant: string | undefined, targetFolder: string): Promise<string | undefined>;
32
34
  export declare function parseDepSpec(dep: string): {
33
35
  name: string;
34
36
  versionRange: string | null;
@@ -8,6 +8,7 @@ export interface InstalledBlockRecord {
8
8
  installedAt: string;
9
9
  files: string[];
10
10
  contentHash: string;
11
+ variant?: string;
11
12
  }
12
13
  export type configPayloadType = {
13
14
  $schema: string;
package/dist/index.js CHANGED
@@ -20,6 +20,34 @@ function blockSupportsEnv(block, envKey) {
20
20
  }
21
21
  return block.adapters?.[envKey] !== void 0 || block.environments?.[envKey] !== void 0;
22
22
  }
23
+ function selectVariant(variantKeys, preferredVariant) {
24
+ if (preferredVariant && variantKeys.includes(preferredVariant)) {
25
+ return preferredVariant;
26
+ }
27
+ if (variantKeys.includes("memory")) return "memory";
28
+ if (variantKeys.includes("default")) return "default";
29
+ return variantKeys[0];
30
+ }
31
+ async function resolvePreferredVariant(variantKeys, recordedVariant, targetFolder) {
32
+ if (recordedVariant && variantKeys.includes(recordedVariant)) {
33
+ return recordedVariant;
34
+ }
35
+ if (variantKeys.includes("redis")) {
36
+ const markers = [
37
+ join(targetFolder, "variants", "redis-store.ts"),
38
+ join(targetFolder, "variants", "redis-store.test.ts"),
39
+ join(targetFolder, "variants", "redis.test.ts")
40
+ ];
41
+ for (const marker of markers) {
42
+ try {
43
+ await fs.access(marker);
44
+ return "redis";
45
+ } catch {
46
+ }
47
+ }
48
+ }
49
+ return void 0;
50
+ }
23
51
  function parseDepSpec(dep) {
24
52
  const atIndex = dep.lastIndexOf("@");
25
53
  if (atIndex > 0) {
@@ -355,7 +383,8 @@ async function addSingleBlock(targetBlock, blockMap, config, rootDir, physicalBl
355
383
  version: blockVersion,
356
384
  installedAt: (/* @__PURE__ */ new Date()).toISOString(),
357
385
  files: filesWritten.map((f) => path.relative(targetFolder, f).replace(/\\/g, "/")),
358
- contentHash: contentHash.toString(16)
386
+ contentHash: contentHash.toString(16),
387
+ variant: selectedVariant
359
388
  };
360
389
  try {
361
390
  const blockendPath = join(rootDir, "blockend.json");
@@ -1451,9 +1480,15 @@ async function diffCommand(blockName, options = {}) {
1451
1480
  }
1452
1481
  return { block: blockName, files: [], error };
1453
1482
  }
1454
- const selectedVariant = variantKeys[0];
1455
- const variantMeta = adapterContext.variants[selectedVariant];
1456
1483
  const targetFolder = path4.resolve(blocksRootAbsolute, blockName);
1484
+ const installedRecord = config.installed?.find((b) => b.name === blockName);
1485
+ const preferred = await resolvePreferredVariant(
1486
+ variantKeys,
1487
+ installedRecord?.variant,
1488
+ targetFolder
1489
+ );
1490
+ const selectedVariant = selectVariant(variantKeys, preferred);
1491
+ const variantMeta = adapterContext.variants[selectedVariant];
1457
1492
  const filesToDownload = [];
1458
1493
  if (blockMeta.baseFiles?.length) {
1459
1494
  filesToDownload.push(...blockMeta.baseFiles);
@@ -1909,12 +1944,16 @@ function outputUpdateResult(json, result) {
1909
1944
  outro8(pc9.green("All tracked blocks are up to date."));
1910
1945
  }
1911
1946
  }
1912
- function resolveBlockFiles(blockMeta, envKey) {
1947
+ async function resolveBlockFiles(blockMeta, envKey, preferredVariant, targetFolder) {
1913
1948
  const adapterContext = resolveAdapterContext3(blockMeta, envKey);
1914
1949
  if (!adapterContext) return null;
1915
1950
  const variantKeys = Object.keys(adapterContext.variants ?? {});
1916
1951
  if (variantKeys.length === 0) return null;
1917
- const selectedVariant = variantKeys[0];
1952
+ let preferred = preferredVariant;
1953
+ if (targetFolder && (!preferred || !variantKeys.includes(preferred))) {
1954
+ preferred = await resolvePreferredVariant(variantKeys, preferred, targetFolder) ?? preferred;
1955
+ }
1956
+ const selectedVariant = selectVariant(variantKeys, preferred);
1918
1957
  const variantMeta = adapterContext.variants[selectedVariant];
1919
1958
  const expectedFiles = [];
1920
1959
  if (blockMeta.baseFiles?.length) expectedFiles.push(...blockMeta.baseFiles);
@@ -2000,9 +2039,9 @@ async function updateCommand(options = {}) {
2000
2039
  status: hasUpdate ? "update-available" : "up-to-date"
2001
2040
  });
2002
2041
  if (hasUpdate && diff2) {
2003
- const resolved = resolveBlockFiles(blockMeta, envKey);
2004
- if (!resolved) continue;
2005
2042
  const targetFolder = path6.resolve(blocksRootAbsolute, record.name);
2043
+ const resolved = await resolveBlockFiles(blockMeta, envKey, record.variant, targetFolder);
2044
+ if (!resolved) continue;
2006
2045
  const fileDiffs = [];
2007
2046
  for (const fm of resolved.files) {
2008
2047
  const localPath = join12(targetFolder, fm.target);
@@ -2087,9 +2126,10 @@ async function updateCommand(options = {}) {
2087
2126
  for (const blockName of selectedNames) {
2088
2127
  const blockMeta = blockMap[blockName];
2089
2128
  if (!blockMeta) continue;
2090
- const resolved = resolveBlockFiles(blockMeta, envKey);
2091
- if (!resolved) continue;
2129
+ const record = installedRecords.find((r) => r.name === blockName);
2092
2130
  const targetFolder = path6.resolve(blocksRootAbsolute, blockName);
2131
+ const resolved = await resolveBlockFiles(blockMeta, envKey, record?.variant, targetFolder);
2132
+ if (!resolved) continue;
2093
2133
  const downloadedTargets = /* @__PURE__ */ new Set();
2094
2134
  try {
2095
2135
  const fileBatches = [];
@@ -2122,7 +2162,8 @@ async function updateCommand(options = {}) {
2122
2162
  version: blockVersion,
2123
2163
  installedAt: (/* @__PURE__ */ new Date()).toISOString(),
2124
2164
  files: resolved.files.map((f) => f.target),
2125
- contentHash: contentHash.toString(16)
2165
+ contentHash: contentHash.toString(16),
2166
+ variant: resolved.variant
2126
2167
  };
2127
2168
  try {
2128
2169
  const cfgRaw = await fs7.readFile(configPath, "utf-8");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blockend-cli",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "description": "Blockend CLI — shadcn/ui-style backend blocks for TypeScript. Generate production-ready rate limiting, validation, logging, and error handling as source code into Express, Fastify, Hono, or Next.js projects. You own the code. Ships an MCP server for AI coding assistants.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -82,6 +82,6 @@
82
82
  "devDependencies": {
83
83
  "@types/node": "^25.9.3",
84
84
  "tsup": "^8.5.1",
85
- "vitest": "^1.6.1"
85
+ "vitest": "^4.1.11"
86
86
  }
87
87
  }