blockend-cli 1.4.1 → 1.4.3

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.
Files changed (3) hide show
  1. package/dist/index.js +241 -156
  2. package/package.json +11 -10
  3. package/LICENSE +0 -21
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import { defineCommand, runMain } from "citty";
7
7
  import path, { join, dirname } from "path";
8
8
  import fs from "fs/promises";
9
9
  import { exec } from "child_process";
10
- import { intro, outro, select, spinner, confirm, isCancel } from "@clack/prompts";
10
+ import { intro, outro, select, spinner, confirm, isCancel, log } from "@clack/prompts";
11
11
  import pc from "picocolors";
12
12
  var REPO_OWNER = "codewithnuh";
13
13
  var REPO_NAME = "blockend";
@@ -18,7 +18,7 @@ function outputError(json, message) {
18
18
  if (json) {
19
19
  process.stdout.write(JSON.stringify({ success: false, error: message }) + "\n");
20
20
  } else {
21
- outro(pc.red(`\u2716 ${message}`));
21
+ log.error(message);
22
22
  }
23
23
  }
24
24
  function outputResult(json, result) {
@@ -26,7 +26,7 @@ function outputResult(json, result) {
26
26
  process.stdout.write(JSON.stringify(result) + "\n");
27
27
  } else {
28
28
  if (result.success) {
29
- outro(pc.cyan(`\u2728 ${result.message}`));
29
+ outro(pc.green(`\u2728 ${result.message}`));
30
30
  } else {
31
31
  outro(pc.yellow(`\u2139 ${result.message}`));
32
32
  }
@@ -34,7 +34,7 @@ function outputResult(json, result) {
34
34
  }
35
35
  function handleCancel(value) {
36
36
  if (isCancel(value)) {
37
- outro(pc.yellow("\u26A0 Operation cancelled. Exiting Blockend CLI cleanly."));
37
+ outro(pc.dim("Operation cancelled."));
38
38
  process.exit(0);
39
39
  }
40
40
  }
@@ -56,7 +56,8 @@ async function findUp(filename, startDir) {
56
56
  async function addCommand(blockName, options = {}) {
57
57
  const { yes = false, json = false } = options;
58
58
  if (!json) {
59
- intro(pc.bgBlack(pc.magenta(" Blockend Component Ingestion ")));
59
+ console.log("");
60
+ intro(`${pc.bgCyan(pc.black(" blockend "))} ${pc.dim("add")}`);
60
61
  }
61
62
  const cwd = process.cwd();
62
63
  const configPath = await findUp("blockend.json", cwd);
@@ -70,42 +71,35 @@ async function addCommand(blockName, options = {}) {
70
71
  const configFile = await fs.readFile(configPath, "utf-8");
71
72
  config = JSON.parse(configFile);
72
73
  } catch (error) {
73
- outputError(json, "Failed to parse blockend.json layout configuration.");
74
- if (!json) console.error(pc.dim(String(error)));
74
+ outputError(json, "Failed to parse blockend.json.");
75
+ if (!json) log.error(pc.dim(String(error)));
75
76
  return;
76
77
  }
77
78
  if (config.language !== "typescript") {
78
79
  if (json) {
79
- outputError(
80
- json,
81
- "Blockend forces modern architectural standards. Registry exclusively supports TypeScript."
82
- );
80
+ outputError(json, "Registry exclusively supports TypeScript projects.");
83
81
  } else {
84
- outro(
85
- pc.yellow(
86
- `
87
- \u2139 Blockend forces modern architectural standards. Registry exclusively supports TypeScript.`
88
- )
89
- );
82
+ log.warn("Blockend currently only supports TypeScript projects.");
83
+ outro(pc.dim("Exiting."));
90
84
  }
91
85
  return;
92
86
  }
93
87
  const s = spinner();
94
88
  if (!json) {
95
- s.start("Connecting to remote Blockend Registry manifest...");
89
+ s.start("Fetching registry...");
96
90
  }
97
91
  let registry;
98
92
  try {
99
93
  const response = await fetch(MANIFEST_URL);
100
94
  if (!response.ok) throw new Error(`HTTP Error Status: ${response.status}`);
101
95
  registry = await response.json();
102
- if (!json) s.stop(pc.green("\u2714 Remote manifest synchronization complete."));
96
+ if (!json) s.stop("Registry synced.");
103
97
  } catch (error) {
104
98
  if (!json) {
105
- s.stop(pc.red("\u2716 Failed to fetch the component registry from GitHub network paths."));
106
- console.error(pc.dim(String(error)));
99
+ s.stop("Failed to fetch registry.");
100
+ log.error(pc.dim(String(error)));
107
101
  } else {
108
- outputError(json, "Failed to fetch the component registry from GitHub network paths.");
102
+ outputError(json, "Failed to fetch the component registry from GitHub.");
109
103
  }
110
104
  return;
111
105
  }
@@ -125,32 +119,34 @@ async function addCommand(blockName, options = {}) {
125
119
  return;
126
120
  }
127
121
  if (!targetBlock) {
128
- const availableOptions = Object.entries(blockMap).filter(([_, block]) => {
122
+ const filteredBlocks = Object.entries(blockMap).filter(([_, block]) => {
129
123
  if (!block) return false;
130
124
  const hasAdapter = block.adapters?.[envKey] !== void 0;
131
125
  const hasEnvironment = block.environments?.[envKey] !== void 0;
132
126
  return hasAdapter || hasEnvironment;
133
- }).map(([key, block]) => ({
134
- value: key,
135
- label: `${key} - ${pc.dim(block.description)}`
136
- }));
137
- if (availableOptions.length === 0) {
127
+ });
128
+ if (filteredBlocks.length === 0) {
129
+ const msg = `No backend blocks are currently available for: ${envKey}.`;
138
130
  if (json) {
139
- outputError(
140
- json,
141
- `No backend blocks are currently available for your framework layer: [${envKey}].`
142
- );
131
+ outputError(json, msg);
143
132
  } else {
144
- outro(
145
- pc.yellow(
146
- `\u26A0 No backend blocks are currently available for your framework layer: [${envKey}].`
147
- )
148
- );
133
+ log.warn(msg);
134
+ outro(pc.dim("Exiting."));
149
135
  }
150
136
  return;
151
137
  }
138
+ const maxKeyLength = Math.max(...filteredBlocks.map(([key]) => key.length), 0);
139
+ const availableOptions = filteredBlocks.map(([key, block], index) => {
140
+ const paddedKey = key.padEnd(maxKeyLength + 4, " ");
141
+ const maxDescWidth = 60;
142
+ const optimizedDescription = block.description.length > maxDescWidth ? `${block.description.slice(0, maxDescWidth)}...` : block.description;
143
+ return {
144
+ value: key,
145
+ label: `${pc.dim(`${index + 1}.`)} ${pc.bold(pc.cyan(paddedKey))}${pc.dim(optimizedDescription)}`
146
+ };
147
+ });
152
148
  const selectBlockPrompt = await select({
153
- message: "Which backend block would you like to inject?",
149
+ message: "Which block would you like to add?",
154
150
  options: availableOptions
155
151
  });
156
152
  handleCancel(selectBlockPrompt);
@@ -158,24 +154,18 @@ async function addCommand(blockName, options = {}) {
158
154
  }
159
155
  const blockMeta = blockMap[targetBlock];
160
156
  if (!blockMeta) {
161
- outputError(json, `Block "${targetBlock}" does not exist in the remote registry.`);
157
+ outputError(json, `Block "${targetBlock}" does not exist in the registry.`);
162
158
  return;
163
159
  }
164
160
  const adapterContext = blockMeta.adapters?.[envKey] ?? blockMeta.environments?.[envKey];
165
161
  if (!adapterContext) {
166
- outputError(
167
- json,
168
- `The block "${targetBlock}" does not support your environment layout: ${envKey}`
169
- );
162
+ outputError(json, `The block "${targetBlock}" does not support your environment: ${envKey}`);
170
163
  return;
171
164
  }
172
165
  let selectedVariant;
173
166
  const variantKeys = Object.keys(adapterContext.variants || {});
174
167
  if (variantKeys.length === 0) {
175
- outputError(
176
- json,
177
- `No architecture storage layout variants found for block framework: ${envKey}`
178
- );
168
+ outputError(json, `No storage variants found for block environment: ${envKey}`);
179
169
  return;
180
170
  }
181
171
  if (yes) {
@@ -186,7 +176,7 @@ async function addCommand(blockName, options = {}) {
186
176
  selectedVariant = variantKeys[0];
187
177
  } else {
188
178
  const selectVariantPrompt = await select({
189
- message: "Which architectural storage variant do you want to back this block?",
179
+ message: "Select a storage variant:",
190
180
  options: variantKeys.map((vKey) => ({
191
181
  value: vKey,
192
182
  label: vKey.toUpperCase()
@@ -203,7 +193,7 @@ async function addCommand(blockName, options = {}) {
203
193
  const targetFolder = path.resolve(rootDir, physicalPath, targetBlock);
204
194
  const packageJsonPath = await findUp("package.json", rootDir);
205
195
  if (!packageJsonPath) {
206
- outputError(json, "Could not locate package.json in your current directory layout hierarchy.");
196
+ outputError(json, "Could not locate package.json in your current directory.");
207
197
  return;
208
198
  }
209
199
  const packageJsonDir = dirname(packageJsonPath);
@@ -212,8 +202,8 @@ async function addCommand(blockName, options = {}) {
212
202
  const packageJsonContent = await fs.readFile(packageJsonPath, "utf-8");
213
203
  packageJson = JSON.parse(packageJsonContent);
214
204
  } catch (error) {
215
- outputError(json, "Failed parsing file data configurations from target package.json location.");
216
- if (!json) console.error(pc.dim(String(error)));
205
+ outputError(json, "Failed parsing package.json.");
206
+ if (!json) log.error(pc.dim(String(error)));
217
207
  return;
218
208
  }
219
209
  const installedDeps = {
@@ -226,19 +216,16 @@ async function addCommand(blockName, options = {}) {
226
216
  if (hasMissingDeps) {
227
217
  const allMissingNames = [...missingProdDeps, ...missingDevDeps];
228
218
  if (!json) {
229
- console.log(
230
- pc.yellow(`
231
- \u26A0\uFE0F Missing required infrastructure packages: ${allMissingNames.join(", ")}`)
232
- );
219
+ log.warn(`Missing dependencies: ${pc.cyan(allMissingNames.join(", "))}`);
233
220
  }
234
221
  const shouldInstallPrompt = yes ? true : await confirm({
235
- message: "Would you like the CLI to automatically install these dependencies?",
222
+ message: "Install missing dependencies?",
236
223
  initialValue: true
237
224
  });
238
225
  if (!yes) handleCancel(shouldInstallPrompt);
239
226
  if (shouldInstallPrompt) {
240
227
  const packageManager = await fs.access(join(packageJsonDir, "pnpm-lock.yaml")).then(() => "pnpm").catch(() => "npm");
241
- if (!json) s.start(`Preparing native workspace via ${packageManager}...`);
228
+ if (!json) s.start(`Installing via ${packageManager}...`);
242
229
  try {
243
230
  const installTasks = [];
244
231
  if (missingProdDeps.length > 0) {
@@ -252,8 +239,7 @@ async function addCommand(blockName, options = {}) {
252
239
  );
253
240
  }
254
241
  for (const installCmd of installTasks) {
255
- if (!json) s.stop(pc.cyan(`Executing: ${installCmd}
256
- `));
242
+ if (!json) s.message(`Executing: ${installCmd}`);
257
243
  await new Promise((resolve, reject) => {
258
244
  const child = exec(installCmd, { cwd: packageJsonDir });
259
245
  child.stdout?.on("data", (data) => {
@@ -269,13 +255,12 @@ async function addCommand(blockName, options = {}) {
269
255
  });
270
256
  }
271
257
  if (!json) {
272
- s.start(pc.green("\u2714 All dependencies synchronized successfully."));
273
- s.stop();
258
+ s.stop("Dependencies installed.");
274
259
  }
275
260
  } catch (error) {
276
261
  if (!json) {
277
- s.stop(pc.red("\u2716 Automated dependency installation failed. Please run setup manually."));
278
- console.error(pc.dim(String(error)));
262
+ s.stop("Installation failed.");
263
+ log.error(pc.dim(String(error)));
279
264
  } else {
280
265
  outputError(json, "Automated dependency installation failed.");
281
266
  }
@@ -317,7 +302,7 @@ async function addCommand(blockName, options = {}) {
317
302
  }
318
303
  if (fileExistsConflict) {
319
304
  const overwritePrompt = yes ? true : await confirm({
320
- message: `\u26A0 Components in [${targetBlock}] already exist. Overwrite custom revisions?`,
305
+ message: `Files for "${targetBlock}" already exist. Overwrite?`,
321
306
  initialValue: false
322
307
  });
323
308
  if (!yes) handleCancel(overwritePrompt);
@@ -331,7 +316,7 @@ async function addCommand(blockName, options = {}) {
331
316
  }
332
317
  }
333
318
  if (!json) {
334
- s.start(`Downloading and building template adapter blocks [${targetBlock}]...`);
319
+ s.start(`Downloading ${targetBlock}...`);
335
320
  }
336
321
  try {
337
322
  for (const fileMap of filesToDownload) {
@@ -343,36 +328,143 @@ async function addCommand(blockName, options = {}) {
343
328
  await fs.mkdir(dirname(localWriteLocation), { recursive: true });
344
329
  await fs.writeFile(localWriteLocation, fileContent, "utf-8");
345
330
  }
346
- if (!json) s.stop(pc.green("\u2714 Component isolation structures written smoothly."));
331
+ if (!json) s.stop("Files copied.");
347
332
  outputResult(json, {
348
333
  success: true,
349
334
  block: targetBlock,
350
335
  filesWritten: filesToDownload.map((f) => join(targetFolder, f.target)),
351
336
  dependenciesInstalled: [...missingProdDeps, ...missingDevDeps],
352
- message: `Source blocks written to ${physicalPath}/${targetBlock}/. Code ownership transferred!`
337
+ message: `Block added to ${physicalPath}/${targetBlock}`
353
338
  });
354
339
  } catch (error) {
355
340
  if (!json) {
356
- s.stop(pc.red("\u2716 Fatal error occurred while assembling file mappings."));
357
- console.error(pc.dim(String(error)));
341
+ s.stop("Failed to write files.");
342
+ log.error(pc.dim(String(error)));
358
343
  } else {
359
- outputError(json, "Fatal error occurred while assembling file mappings.");
344
+ outputError(json, "Fatal error occurred while writing block files.");
360
345
  }
361
346
  }
362
347
  }
363
348
 
364
349
  // src/commands/init.ts
365
- import path2, { join as join2 } from "path";
350
+ import path2, { join as join7 } from "path";
366
351
  import fs2 from "fs/promises";
352
+ import { existsSync as existsSync4 } from "fs";
353
+ import { intro as intro2, outro as outro2, select as select2, text, confirm as confirm2, spinner as spinner2, isCancel as isCancel2, log as log2 } from "@clack/prompts";
354
+ import pc3 from "picocolors";
355
+
356
+ // src/detectors/index.ts
357
+ import { join as join6 } from "path";
358
+
359
+ // src/detectors/readers/package.ts
360
+ import { readFile } from "fs/promises";
361
+ import { join as join2 } from "path";
362
+ async function readPackageJson(cwd) {
363
+ const pkgPath = join2(cwd, "package.json");
364
+ try {
365
+ const content = await readFile(pkgPath, "utf-8");
366
+ return JSON.parse(content);
367
+ } catch {
368
+ throw new Error(`No package.json found at ${pkgPath}. Run blockend from your project root.`);
369
+ }
370
+ }
371
+
372
+ // src/detectors/readers/tsconfig.ts
373
+ import { readFile as readFile2 } from "fs/promises";
367
374
  import { existsSync } from "fs";
368
- import { intro as intro2, outro as outro2, select as select2, text, confirm as confirm2, spinner as spinner2, isCancel as isCancel2 } from "@clack/prompts";
369
- import { detectProject } from "@blockend/detector";
375
+ import { join as join3 } from "path";
376
+ async function readTsConfig(cwd) {
377
+ const tsconfigPath = join3(cwd, "tsconfig.json");
378
+ if (!existsSync(tsconfigPath)) return null;
379
+ try {
380
+ const content = await readFile2(tsconfigPath, "utf-8");
381
+ const cleanLines = content.split(/\r?\n/).filter((line) => {
382
+ const trimmed = line.trim();
383
+ return !trimmed.startsWith("//") && !trimmed.startsWith("/*");
384
+ }).join("\n").replace(/,(\s*[}\]])/g, "$1");
385
+ return JSON.parse(cleanLines);
386
+ } catch {
387
+ return null;
388
+ }
389
+ }
390
+
391
+ // src/detectors/readers/filesystem.ts
392
+ import { existsSync as existsSync2 } from "fs";
393
+ import { join as join4 } from "path";
394
+ async function inferSrcDir(cwd) {
395
+ const candidates = ["src", "app", "lib"];
396
+ for (const dir of candidates) {
397
+ if (existsSync2(join4(cwd, dir))) {
398
+ return join4(cwd, dir);
399
+ }
400
+ }
401
+ return cwd;
402
+ }
403
+
404
+ // src/detectors/framework.ts
405
+ function detectFramework(deps) {
406
+ if ("fastify" in deps) return "fastify";
407
+ if ("hono" in deps) return "hono";
408
+ if ("express" in deps) return "express";
409
+ if ("next" in deps) return "next";
410
+ return "none";
411
+ }
412
+
413
+ // src/detectors/runtime.ts
414
+ function detectRuntime(deps) {
415
+ if ("@types/bun" in deps || "bun-types" in deps) return "bun";
416
+ return "node";
417
+ }
418
+
419
+ // src/detectors/package-manager.ts
420
+ import { existsSync as existsSync3 } from "fs";
421
+ import { join as join5 } from "path";
422
+ function detectPackageManager(cwd) {
423
+ if (existsSync3(join5(cwd, "pnpm-lock.yaml"))) return "pnpm";
424
+ if (existsSync3(join5(cwd, "bun.lockb"))) return "bun";
425
+ if (existsSync3(join5(cwd, "yarn.lock"))) return "yarn";
426
+ return "npm";
427
+ }
428
+
429
+ // src/detectors/index.ts
430
+ async function detectProject(cwd) {
431
+ const [pkg, tsConfig] = await Promise.all([readPackageJson(cwd), readTsConfig(cwd)]);
432
+ const allDeps = {
433
+ ...pkg.dependencies,
434
+ ...pkg.devDependencies,
435
+ ...pkg.peerDependencies
436
+ };
437
+ const srcDir = await inferSrcDir(cwd);
438
+ return {
439
+ root: cwd,
440
+ language: tsConfig !== null ? "typescript" : "javascript",
441
+ runtime: detectRuntime(allDeps),
442
+ framework: detectFramework(allDeps),
443
+ packageManager: detectPackageManager(cwd),
444
+ hasRedis: "ioredis" in allDeps || "redis" in allDeps,
445
+ hasPrisma: "@prisma/client" in allDeps,
446
+ hasDrizzle: "drizzle-orm" in allDeps,
447
+ aliasMap: tsConfig?.compilerOptions?.paths ? flattenTsPaths(tsConfig.compilerOptions.paths) : {},
448
+ srcDir,
449
+ // Default blocks directory: srcDir/lib/blocks
450
+ blocksDir: join6(srcDir, "lib", "blocks")
451
+ };
452
+ }
453
+ function flattenTsPaths(paths) {
454
+ const result = {};
455
+ for (const [alias, targets] of Object.entries(paths)) {
456
+ if (targets[0]) {
457
+ result[alias.replace("/*", "/")] = targets[0].replace("/*", "/");
458
+ }
459
+ }
460
+ return result;
461
+ }
370
462
 
371
463
  // src/ui/theme.ts
372
464
  import pc2 from "picocolors";
373
465
  var theme = {
374
466
  brand: {
375
- primary: pc2.blue,
467
+ primary: pc2.cyan,
376
468
  title: pc2.bold,
377
469
  logo: pc2.white
378
470
  },
@@ -385,7 +477,7 @@ var theme = {
385
477
  success: pc2.green,
386
478
  warning: pc2.yellow,
387
479
  error: pc2.red,
388
- info: pc2.blue
480
+ info: pc2.cyan
389
481
  },
390
482
  emphasis: {
391
483
  strong: pc2.bold,
@@ -396,18 +488,19 @@ var theme = {
396
488
  // src/ui/format.ts
397
489
  var format = {
398
490
  title: (text2) => theme.brand.primary(theme.brand.title(text2)),
399
- success: (text2) => theme.state.success(`\u2714 ${text2}`),
400
- error: (text2) => theme.state.error(`\u2716 ${text2}`),
491
+ success: (text2) => theme.state.success(text2),
492
+ error: (text2) => theme.state.error(text2),
401
493
  warning: (text2) => theme.state.warning(text2),
402
494
  muted: (text2) => theme.text.muted(text2),
403
- info: (text2) => theme.state.info(text2)
495
+ info: (text2) => theme.state.info(text2),
496
+ highlight: (text2) => theme.brand.primary(text2)
404
497
  };
405
498
 
406
499
  // src/commands/init.ts
407
500
  async function resolveTsConfigPaths(cwd) {
408
- const possiblePaths = [join2(cwd, "tsconfig.json"), join2(cwd, "jsconfig.json")];
501
+ const possiblePaths = [join7(cwd, "tsconfig.json"), join7(cwd, "jsconfig.json")];
409
502
  for (const configPath of possiblePaths) {
410
- if (existsSync(configPath)) {
503
+ if (existsSync4(configPath)) {
411
504
  try {
412
505
  const rawContent = await fs2.readFile(configPath, "utf-8");
413
506
  const cleanJsonContent = rawContent.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, "$1");
@@ -425,7 +518,8 @@ function outputInitError(json, message) {
425
518
  if (json) {
426
519
  process.stdout.write(JSON.stringify({ success: false, error: message }) + "\n");
427
520
  } else {
428
- outro2(format.error(message));
521
+ log2.error(format.error(message));
522
+ process.exit(1);
429
523
  }
430
524
  }
431
525
  function outputInitResult(json, result) {
@@ -438,39 +532,32 @@ function outputInitResult(json, result) {
438
532
  async function initCommand(options = {}) {
439
533
  const { yes = false, json = false } = options;
440
534
  const cwd = process.cwd();
441
- const configPath = join2(cwd, "blockend.json");
535
+ const configPath = join7(cwd, "blockend.json");
442
536
  if (!json) {
443
- console.log(`
444
- \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557
445
- \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
446
- \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551
447
- \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551
448
- \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
449
- \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D
450
- `);
451
- intro2(theme.brand.primary(" Blockend \xB7 Intelligent Backend Blocks Setup "));
452
- }
453
- if (existsSync(configPath)) {
537
+ console.log("");
538
+ intro2(`${pc3.bgCyan(pc3.black(" blockend "))} ${theme.text.muted("setup")}`);
539
+ }
540
+ if (existsSync4(configPath)) {
454
541
  let action;
455
542
  if (yes) {
456
543
  action = "overwrite";
457
544
  } else {
458
545
  const actionPrompt = await select2({
459
- message: "blockend.json already exists. What do you want to do?",
546
+ message: "A blockend.json configuration already exists. What would you like to do?",
460
547
  options: [
461
- { value: "keep", label: "Keep existing config (cancel init)" },
462
- { value: "overwrite", label: "Overwrite config" },
463
- { value: "regenerate", label: "Delete and regenerate" }
548
+ { value: "keep", label: "Keep existing (cancel setup)" },
549
+ { value: "overwrite", label: "Overwrite current configuration" },
550
+ { value: "regenerate", label: "Delete and regenerate from scratch" }
464
551
  ]
465
552
  });
466
553
  if (isCancel2(actionPrompt) || actionPrompt === "keep") {
467
554
  if (json) {
468
555
  outputInitResult(json, {
469
556
  success: false,
470
- message: "Initialization cancelled. Existing config preserved."
557
+ message: "Setup cancelled. Existing config preserved."
471
558
  });
472
559
  } else {
473
- outro2(format.muted("Initialization cancelled. Existing config preserved."));
560
+ outro2(format.muted("Setup cancelled. Existing config preserved."));
474
561
  }
475
562
  return;
476
563
  }
@@ -478,22 +565,22 @@ async function initCommand(options = {}) {
478
565
  }
479
566
  if (action === "regenerate") {
480
567
  await fs2.unlink(configPath);
481
- if (!json) console.log(format.error("Existing config deleted"));
568
+ if (!json) log2.warn("Existing configuration deleted.");
482
569
  }
483
570
  }
484
571
  const s = spinner2();
485
- if (!json) s.start("Scanning project layout...");
572
+ if (!json) s.start("Scanning project architecture...");
486
573
  const context = await detectProject(cwd);
487
- const hasSrcDir = existsSync(join2(cwd, "src"));
574
+ const hasSrcDir = existsSync4(join7(cwd, "src"));
488
575
  const tsConfig = await resolveTsConfigPaths(cwd);
489
- if (!json) s.stop(format.success("Project architecture scanned"));
576
+ if (!json) s.stop("Project scanned.");
490
577
  let framework = context.framework;
491
578
  if (!framework) {
492
579
  if (yes) {
493
580
  framework = "express";
494
581
  } else {
495
582
  const frameworkSelect = await select2({
496
- message: "Framework could not be auto-detected. Select framework environment manually:",
583
+ message: "Which framework does this project use?",
497
584
  options: [
498
585
  { value: "express", label: "Express.js" },
499
586
  { value: "fastify", label: "Fastify" },
@@ -503,32 +590,30 @@ async function initCommand(options = {}) {
503
590
  });
504
591
  if (isCancel2(frameworkSelect)) {
505
592
  if (json) {
506
- outputInitResult(json, { success: false, message: "Initialization cancelled." });
593
+ outputInitResult(json, { success: false, message: "Setup cancelled." });
507
594
  } else {
508
- outro2(format.muted("Initialization cancelled."));
595
+ outro2(format.muted("Setup cancelled."));
509
596
  }
510
597
  return;
511
598
  }
512
599
  framework = frameworkSelect;
513
600
  }
514
601
  } else if (!json) {
515
- console.log(
516
- `${format.success("\u2714")} Framework environment detected: ${theme.state.info(framework)}`
517
- );
602
+ log2.info(`Framework detected: ${theme.state.info(framework)}`);
518
603
  }
519
604
  const defaultDir = hasSrcDir ? "src/blocks" : "blocks";
520
605
  let rawPhysicalInput = defaultDir;
521
606
  if (!yes) {
522
607
  const directoryPrompt = await text({
523
- message: "Configure the targeted physical directory destination for blocks:",
608
+ message: "Where would you like to install blocks?",
524
609
  placeholder: defaultDir,
525
610
  initialValue: defaultDir,
526
611
  validate(value) {
527
- if (value?.trim().length === 0) return "Physical path location directory cannot be empty.";
612
+ if (value?.trim().length === 0) return "Directory path cannot be empty.";
528
613
  }
529
614
  });
530
615
  if (isCancel2(directoryPrompt)) {
531
- outro2(format.muted("Initialization cancelled."));
616
+ outro2(format.muted("Setup cancelled."));
532
617
  return;
533
618
  }
534
619
  rawPhysicalInput = String(directoryPrompt).trim();
@@ -571,7 +656,7 @@ async function initCommand(options = {}) {
571
656
  includeRedis = true;
572
657
  } else {
573
658
  const redisConfirm = await confirm2({
574
- message: "Redis detected. Enable Redis-backed block variants automatically?",
659
+ message: "Redis detected in project. Enable Redis-backed variants?",
575
660
  initialValue: true
576
661
  });
577
662
  if (!isCancel2(redisConfirm)) {
@@ -591,67 +676,67 @@ async function initCommand(options = {}) {
591
676
  blocks: finalPath
592
677
  }
593
678
  };
594
- if (!json) s.start("Finalizing configuration...");
679
+ if (!json) s.start("Writing configuration...");
595
680
  try {
596
681
  await fs2.writeFile(configPath, JSON.stringify(configPayload, null, 2), "utf-8");
597
- if (!json) s.stop(format.success("blockend.json ready"));
682
+ if (!json) s.stop("Configuration saved.");
598
683
  outputInitResult(json, {
599
684
  success: true,
600
- message: "\u2728 Blockend initialized successfully. Run: npx blockend add <block>",
685
+ message: "Blockend initialized successfully! Run: npx blockend add <block>",
601
686
  config: configPayload
602
687
  });
603
688
  } catch {
604
689
  if (!json) {
605
- s.stop(format.error("Failed to write configuration"));
690
+ s.stop("Failed");
691
+ outputInitError(false, "Failed to write configuration file.");
606
692
  } else {
607
- outputInitError(json, "Failed to write architectural layout configuration map.");
693
+ outputInitError(true, "Failed to write architectural layout configuration map.");
608
694
  }
609
695
  }
610
696
  }
611
697
 
612
698
  // src/commands/detect.ts
613
- import { detectProject as detectProject2 } from "@blockend/detector";
614
699
  import { outro as outro3 } from "@clack/prompts";
615
- import pc3 from "picocolors";
700
+ import pc4 from "picocolors";
616
701
  async function detectCommand(options = {}) {
617
702
  const { json = false } = options;
618
703
  try {
619
- const context = await detectProject2(process.cwd());
704
+ const context = await detectProject(process.cwd());
620
705
  if (json) {
621
706
  process.stdout.write(JSON.stringify(context, null, 2) + "\n");
622
707
  return;
623
708
  }
624
709
  console.log();
625
- console.log(pc3.bold(" Detected project configuration:"));
710
+ console.log(pc4.bold(" Detected project configuration:"));
626
711
  console.log();
627
- console.log(` Framework: ${pc3.cyan(context.framework)}`);
628
- console.log(` Language: ${pc3.cyan(context.language)}`);
629
- console.log(` Package manager: ${pc3.cyan(context.packageManager)}`);
630
- console.log(` Source dir: ${pc3.dim(context.srcDir)}`);
712
+ console.log(` Framework: ${pc4.cyan(context.framework)}`);
713
+ console.log(` Language: ${pc4.cyan(context.language)}`);
714
+ console.log(` Package manager: ${pc4.cyan(context.packageManager)}`);
715
+ console.log(` Source dir: ${pc4.dim(context.srcDir)}`);
631
716
  console.log(
632
- ` Redis: ${context.hasRedis ? pc3.green("detected") : pc3.dim("not found")}`
717
+ ` Redis: ${context.hasRedis ? pc4.green("detected") : pc4.dim("not found")}`
633
718
  );
634
719
  console.log(
635
- ` Prisma: ${context.hasPrisma ? pc3.green("detected") : pc3.dim("not found")}`
720
+ ` Prisma: ${context.hasPrisma ? pc4.green("detected") : pc4.dim("not found")}`
636
721
  );
637
722
  console.log(
638
- ` Drizzle: ${context.hasDrizzle ? pc3.green("detected") : pc3.dim("not found")}`
723
+ ` Drizzle: ${context.hasDrizzle ? pc4.green("detected") : pc4.dim("not found")}`
639
724
  );
640
725
  console.log();
641
726
  } catch (error) {
642
727
  if (json) {
643
728
  process.stdout.write(JSON.stringify({ success: false, error: String(error) }) + "\n");
644
729
  } else {
645
- outro3(pc3.red(`\u2716 Detection failed: ${String(error)}`));
730
+ outro3(pc4.red(`\u2716 Detection failed: ${String(error)}`));
646
731
  }
647
732
  process.exit(1);
648
733
  }
649
734
  }
650
735
 
651
736
  // src/commands/list.ts
652
- import { dirname as dirname2, join as join3 } from "path";
737
+ import { dirname as dirname2, join as join8 } from "path";
653
738
  import fs3 from "fs/promises";
654
- import pc4 from "picocolors";
739
+ import pc5 from "picocolors";
655
740
  import { outro as outro4, spinner as spinner3 } from "@clack/prompts";
656
741
  var REPO_OWNER2 = "codewithnuh";
657
742
  var REPO_NAME2 = "blockend";
@@ -660,7 +745,7 @@ var MANIFEST_URL2 = `https://raw.githubusercontent.com/${REPO_OWNER2}/${REPO_NAM
660
745
  async function findUp2(filename, startDir) {
661
746
  let dir = startDir;
662
747
  while (true) {
663
- const checkPath = join3(dir, filename);
748
+ const checkPath = join8(dir, filename);
664
749
  try {
665
750
  await fs3.access(checkPath);
666
751
  return checkPath;
@@ -682,7 +767,7 @@ async function listCommand(options = {}) {
682
767
  JSON.stringify({ success: false, error: "blockend.json not found." }) + "\n"
683
768
  );
684
769
  } else {
685
- outro4(pc4.red("\u2716 blockend.json not found. Run 'npx blockend init' first."));
770
+ outro4(pc5.red("\u2716 blockend.json not found. Run 'npx blockend init' first."));
686
771
  }
687
772
  return;
688
773
  }
@@ -696,7 +781,7 @@ async function listCommand(options = {}) {
696
781
  JSON.stringify({ success: false, error: "Failed to parse configuration matrix." }) + "\n"
697
782
  );
698
783
  } else {
699
- outro4(pc4.red("\u2716 Failed to parse blockend.json layout configuration."));
784
+ outro4(pc5.red("\u2716 Failed to parse blockend.json layout configuration."));
700
785
  }
701
786
  return;
702
787
  }
@@ -710,14 +795,14 @@ async function listCommand(options = {}) {
710
795
  const response = await fetch(MANIFEST_URL2);
711
796
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
712
797
  registry = await response.json();
713
- if (!json) s.stop(pc4.green("\u2714 Synced available block registries."));
798
+ if (!json) s.stop(pc5.green("\u2714 Synced available block registries."));
714
799
  } catch {
715
800
  if (json) {
716
801
  process.stdout.write(
717
802
  JSON.stringify({ success: false, error: "Network sync failure." }) + "\n"
718
803
  );
719
804
  } else {
720
- s.stop(pc4.red("\u2716 Failed to fetch the component registry from GitHub network paths."));
805
+ s.stop(pc5.red("\u2716 Failed to fetch the component registry from GitHub network paths."));
721
806
  }
722
807
  return;
723
808
  }
@@ -746,15 +831,15 @@ async function listCommand(options = {}) {
746
831
  );
747
832
  } else {
748
833
  console.log(`
749
- Available backend blocks for ${pc4.magenta(envKey)}:`);
834
+ Available backend blocks for ${pc5.magenta(envKey)}:`);
750
835
  if (blocksForEnv.length === 0) {
751
- console.log(pc4.dim(" No blocks found for this framework."));
836
+ console.log(pc5.dim(" No blocks found for this framework."));
752
837
  } else {
753
838
  blocksForEnv.forEach((b) => {
754
839
  console.log(`
755
- ${pc4.cyan(b.name)}`);
756
- console.log(` ${pc4.dim(b.description)}`);
757
- console.log(` Storage Variants: ${b.variants.map((v) => pc4.yellow(v)).join(", ")}`);
840
+ ${pc5.cyan(b.name)}`);
841
+ console.log(` ${pc5.dim(b.description)}`);
842
+ console.log(` Storage Variants: ${b.variants.map((v) => pc5.yellow(v)).join(", ")}`);
758
843
  });
759
844
  console.log();
760
845
  }
@@ -762,9 +847,9 @@ Available backend blocks for ${pc4.magenta(envKey)}:`);
762
847
  }
763
848
 
764
849
  // src/commands/mcp.ts
765
- import path3, { join as join4 } from "path";
850
+ import path3, { join as join9 } from "path";
766
851
  import fs4 from "fs/promises";
767
- import pc5 from "picocolors";
852
+ import pc6 from "picocolors";
768
853
  import { outro as outro5, spinner as spinner4, select as select3, confirm as confirm3 } from "@clack/prompts";
769
854
  var CLIENT_MATRIX = {
770
855
  claude: {
@@ -887,22 +972,22 @@ async function mcpInitCommand(options) {
887
972
  }
888
973
  const meta = CLIENT_MATRIX[chosenClient];
889
974
  if (!meta) {
890
- outro5(pc5.red(`\u2716 Unsupported client identifier profile: ${options.client}`));
975
+ outro5(pc6.red(`\u2716 Unsupported client identifier profile: ${options.client}`));
891
976
  return;
892
977
  }
893
- const absoluteConfigTarget = join4(cwd, meta.relativePath);
978
+ const absoluteConfigTarget = join9(cwd, meta.relativePath);
894
979
  if (options.dryRun) {
895
980
  console.log(
896
- pc5.cyan(`
897
- \u2139 Dry-run mode active. Intended path location: ${pc5.dim(meta.relativePath)}`)
981
+ pc6.cyan(`
982
+ \u2139 Dry-run mode active. Intended path location: ${pc6.dim(meta.relativePath)}`)
898
983
  );
899
984
  if (meta.format === "json") {
900
985
  const jsonConfig = meta.isCustomSchema ? { "mcp.mcpServers": { blockend: BLOCKEND_SERVER } } : { mcpServers: { blockend: BLOCKEND_SERVER } };
901
- console.log(pc5.gray(JSON.stringify(jsonConfig, null, 2)));
986
+ console.log(pc6.gray(JSON.stringify(jsonConfig, null, 2)));
902
987
  } else {
903
- console.log(pc5.gray(generateToml(BLOCKEND_SERVER)));
988
+ console.log(pc6.gray(generateToml(BLOCKEND_SERVER)));
904
989
  }
905
- outro5(pc5.green("\u2714 Dry run evaluation complete."));
990
+ outro5(pc6.green("\u2714 Dry run evaluation complete."));
906
991
  return;
907
992
  }
908
993
  let fileConflict = false;
@@ -917,12 +1002,12 @@ async function mcpInitCommand(options) {
917
1002
  initialValue: false
918
1003
  });
919
1004
  if (!shouldOverwrite || typeof shouldOverwrite === "symbol") {
920
- outro5(pc5.yellow("\u2139 Operations halted. Project layout revisions preserved."));
1005
+ outro5(pc6.yellow("\u2139 Operations halted. Project layout revisions preserved."));
921
1006
  return;
922
1007
  }
923
1008
  }
924
1009
  const s = spinner4();
925
- s.start(`Writing localized project configurations inside ${pc5.dim(meta.relativePath)}...`);
1010
+ s.start(`Writing localized project configurations inside ${pc6.dim(meta.relativePath)}...`);
926
1011
  try {
927
1012
  await fs4.mkdir(path3.dirname(absoluteConfigTarget), { recursive: true });
928
1013
  if (meta.format === "json") {
@@ -947,11 +1032,11 @@ async function mcpInitCommand(options) {
947
1032
  await fs4.writeFile(absoluteConfigTarget, generateToml(BLOCKEND_SERVER), "utf-8");
948
1033
  }
949
1034
  s.stop(
950
- pc5.green(`\u2714 Local workspace integration fully complete! File target: ${meta.relativePath}`)
1035
+ pc6.green(`\u2714 Local workspace integration fully complete! File target: ${meta.relativePath}`)
951
1036
  );
952
1037
  } catch (error) {
953
- s.stop(pc5.red("\u2716 Fatal crash reading or creating local configuration maps."));
954
- console.error(pc5.dim(String(error)));
1038
+ s.stop(pc6.red("\u2716 Fatal crash reading or creating local configuration maps."));
1039
+ console.error(pc6.dim(String(error)));
955
1040
  }
956
1041
  }
957
1042
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blockend-cli",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
4
4
  "description": "CLI for installing production-ready backend blocks into Next.js, Express, Hono, NestJS, and other Node.js applications.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -14,6 +14,13 @@
14
14
  "dist",
15
15
  "README.md"
16
16
  ],
17
+ "scripts": {
18
+ "build": "tsup src/index.ts --format esm --dts --clean --out-dir dist",
19
+ "typecheck": "tsc --noEmit",
20
+ "test": "vitest run",
21
+ "test:watch": "vitest",
22
+ "prepublishOnly": "pnpm build"
23
+ },
17
24
  "keywords": [
18
25
  "blockend",
19
26
  "backend",
@@ -59,6 +66,7 @@
59
66
  "engines": {
60
67
  "node": ">=20"
61
68
  },
69
+ "packageManager": "pnpm@10.33.2",
62
70
  "dependencies": {
63
71
  "@clack/prompts": "^1.5.1",
64
72
  "@modelcontextprotocol/sdk": "^1.29.0",
@@ -66,18 +74,11 @@
66
74
  "commander": "^15.0.0",
67
75
  "execa": "^9.6.1",
68
76
  "picocolors": "^1.1.1",
69
- "zod": "^4.4.3",
70
- "@blockend/detector": "0.1.0"
77
+ "zod": "^4.4.3"
71
78
  },
72
79
  "devDependencies": {
73
80
  "@types/node": "^25.9.3",
74
81
  "tsup": "^8.5.1",
75
82
  "vitest": "^1.6.1"
76
- },
77
- "scripts": {
78
- "build": "tsup src/index.ts --format esm --dts --clean --out-dir dist",
79
- "typecheck": "tsc --noEmit",
80
- "test": "vitest run",
81
- "test:watch": "vitest"
82
83
  }
83
- }
84
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Noor ul Hassan
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.