blockend-cli 1.3.0 → 1.3.1

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 (2) hide show
  1. package/dist/index.js +40 -127
  2. package/package.json +4 -2
package/dist/index.js CHANGED
@@ -4,103 +4,11 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // src/commands/init.ts
7
- import path, { join as join6 } from "path";
7
+ import path, { join } from "path";
8
8
  import fs from "fs/promises";
9
- import { existsSync as existsSync4 } from "fs";
10
- import { intro, outro, select, text, confirm, spinner, isCancel } from "@clack/prompts";
11
-
12
- // ../detector/dist/index.js
13
- import { join as join5 } from "path";
14
- import { readFile } from "fs/promises";
15
- import { join } from "path";
16
- import { readFile as readFile2 } from "fs/promises";
17
9
  import { existsSync } from "fs";
18
- import { join as join2 } from "path";
19
- import { existsSync as existsSync2 } from "fs";
20
- import { join as join3 } from "path";
21
- import { existsSync as existsSync3 } from "fs";
22
- import { join as join4 } from "path";
23
- async function readPackageJson(cwd) {
24
- const pkgPath = join(cwd, "package.json");
25
- try {
26
- const content = await readFile(pkgPath, "utf-8");
27
- return JSON.parse(content);
28
- } catch {
29
- throw new Error(`No package.json found at ${pkgPath}. Run blockend from your project root.`);
30
- }
31
- }
32
- async function readTsConfig(cwd) {
33
- const tsconfigPath = join2(cwd, "tsconfig.json");
34
- if (!existsSync(tsconfigPath)) return null;
35
- try {
36
- const content = await readFile2(tsconfigPath, "utf-8");
37
- const cleanLines = content.split(/\r?\n/).filter((line) => {
38
- const trimmed = line.trim();
39
- return !trimmed.startsWith("//") && !trimmed.startsWith("/*");
40
- }).join("\n").replace(/,(\s*[}\]])/g, "$1");
41
- return JSON.parse(cleanLines);
42
- } catch {
43
- return null;
44
- }
45
- }
46
- async function inferSrcDir(cwd) {
47
- const candidates = ["src", "app", "lib"];
48
- for (const dir of candidates) {
49
- if (existsSync2(join3(cwd, dir))) {
50
- return join3(cwd, dir);
51
- }
52
- }
53
- return cwd;
54
- }
55
- function detectFramework(deps) {
56
- if ("fastify" in deps) return "fastify";
57
- if ("hono" in deps) return "hono";
58
- if ("express" in deps) return "express";
59
- if ("next" in deps) return "next";
60
- return "none";
61
- }
62
- function detectRuntime(deps) {
63
- if ("@types/bun" in deps || "bun-types" in deps) return "bun";
64
- return "node";
65
- }
66
- function detectPackageManager(cwd) {
67
- if (existsSync3(join4(cwd, "pnpm-lock.yaml"))) return "pnpm";
68
- if (existsSync3(join4(cwd, "bun.lockb"))) return "bun";
69
- if (existsSync3(join4(cwd, "yarn.lock"))) return "yarn";
70
- return "npm";
71
- }
72
- async function detectProject(cwd) {
73
- const [pkg, tsConfig] = await Promise.all([readPackageJson(cwd), readTsConfig(cwd)]);
74
- const allDeps = {
75
- ...pkg.dependencies,
76
- ...pkg.devDependencies,
77
- ...pkg.peerDependencies
78
- };
79
- const srcDir = await inferSrcDir(cwd);
80
- return {
81
- root: cwd,
82
- language: tsConfig !== null ? "typescript" : "javascript",
83
- runtime: detectRuntime(allDeps),
84
- framework: detectFramework(allDeps),
85
- packageManager: detectPackageManager(cwd),
86
- hasRedis: "ioredis" in allDeps || "redis" in allDeps,
87
- hasPrisma: "@prisma/client" in allDeps,
88
- hasDrizzle: "drizzle-orm" in allDeps,
89
- aliasMap: tsConfig?.compilerOptions?.paths ? flattenTsPaths(tsConfig.compilerOptions.paths) : {},
90
- srcDir,
91
- // Default blocks directory: srcDir/lib/blocks
92
- blocksDir: join5(srcDir, "lib", "blocks")
93
- };
94
- }
95
- function flattenTsPaths(paths) {
96
- const result = {};
97
- for (const [alias, targets] of Object.entries(paths)) {
98
- if (targets[0]) {
99
- result[alias.replace("/*", "/")] = targets[0].replace("/*", "/");
100
- }
101
- }
102
- return result;
103
- }
10
+ import { intro, outro, select, text, confirm, spinner, isCancel } from "@clack/prompts";
11
+ import { detectProject } from "@blockend/detector";
104
12
 
105
13
  // src/ui/theme.ts
106
14
  import pc from "picocolors";
@@ -140,7 +48,7 @@ var format = {
140
48
  // src/commands/init.ts
141
49
  async function initCommand() {
142
50
  const cwd = process.cwd();
143
- const configPath = join6(cwd, "blockend.json");
51
+ const configPath = join(cwd, "blockend.json");
144
52
  console.log(`
145
53
  \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
146
54
  \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
@@ -150,7 +58,7 @@ async function initCommand() {
150
58
  \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
151
59
  `);
152
60
  intro(format.title("Blockend \xB7 Intelligent Backend Blocks Setup"));
153
- if (existsSync4(configPath)) {
61
+ if (existsSync(configPath)) {
154
62
  const action = await select({
155
63
  message: "blockend.json already exists. What do you want to do?",
156
64
  options: [
@@ -212,7 +120,7 @@ async function initCommand() {
212
120
  const aliasPrefix = availableAliases.find((a) => blockAlias.startsWith(a)) || "";
213
121
  const physicalPrefix = aliasPrefix ? context.aliasMap[aliasPrefix] : "./";
214
122
  const resolvedSubDir = blockAlias.replace(aliasPrefix, "");
215
- const assumedPhysicalDir = join6(physicalPrefix, resolvedSubDir);
123
+ const assumedPhysicalDir = join(physicalPrefix, resolvedSubDir);
216
124
  const relativeBlocksPath = path.relative(cwd, path.resolve(cwd, assumedPhysicalDir));
217
125
  const normalizedPath = relativeBlocksPath.replace(/\\/g, "/");
218
126
  const finalPath = normalizedPath.startsWith(".") ? normalizedPath : `./${normalizedPath}`;
@@ -250,7 +158,7 @@ async function initCommand() {
250
158
  }
251
159
 
252
160
  // src/commands/add.ts
253
- import path2, { join as join7, dirname } from "path";
161
+ import path2, { join as join2, dirname } from "path";
254
162
  import fs2 from "fs/promises";
255
163
  import { exec } from "child_process";
256
164
  import { intro as intro2, outro as outro2, select as select2, spinner as spinner2, confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
@@ -269,7 +177,7 @@ function handleCancel(value) {
269
177
  async function findUp(filename, startDir) {
270
178
  let dir = startDir;
271
179
  while (true) {
272
- const checkPath = join7(dir, filename);
180
+ const checkPath = join2(dir, filename);
273
181
  try {
274
182
  await fs2.access(checkPath);
275
183
  return checkPath;
@@ -404,7 +312,7 @@ async function addCommand(blockName) {
404
312
  });
405
313
  handleCancel(shouldInstallPrompt);
406
314
  if (shouldInstallPrompt) {
407
- const packageManager = await fs2.access(join7(packageJsonDir, "pnpm-lock.yaml")).then(() => "pnpm").catch(() => "npm");
315
+ const packageManager = await fs2.access(join2(packageJsonDir, "pnpm-lock.yaml")).then(() => "pnpm").catch(() => "npm");
408
316
  s.start(`Preparing native workspace via ${packageManager}...`);
409
317
  try {
410
318
  const installCmd = packageManager === "pnpm" ? `pnpm add ${missingDeps.join(" ")}` : `npm install ${missingDeps.join(" ")}`;
@@ -433,29 +341,36 @@ async function addCommand(blockName) {
433
341
  }
434
342
  s.start(`Downloading clean production template block [${targetBlock}]...`);
435
343
  try {
436
- const BASE_URL = RAW_CDN_BASE;
437
- const coreFileUrl = `${BASE_URL}/${envConfig.core}`;
438
- const coreFetchResponse = await fetch(coreFileUrl);
439
- if (!coreFetchResponse.ok) {
440
- throw new Error(`Failed downloading core component file: ${coreFetchResponse.statusText}`);
344
+ const remoteFilesToDownload = [];
345
+ if (envConfig.core) {
346
+ remoteFilesToDownload.push(envConfig.core);
347
+ }
348
+ if (variantMeta && Array.isArray(variantMeta.files)) {
349
+ remoteFilesToDownload.push(...variantMeta.files);
441
350
  }
442
- const coreCodeTemplate = await coreFetchResponse.text();
443
351
  let physicalPath = config.paths.blocks;
444
352
  if (physicalPath.startsWith("@")) {
445
353
  physicalPath = "./src/blocks";
446
354
  }
447
355
  let targetFolder = path2.resolve(rootDir, physicalPath);
448
356
  if (variantMeta && selectedVariant) {
449
- targetFolder = join7(targetFolder, targetBlock);
357
+ targetFolder = join2(targetFolder, targetBlock);
450
358
  }
451
- await fs2.mkdir(targetFolder, { recursive: true });
452
- const coreOutputFilename = `${targetBlock}.ts`;
453
- const coreTargetFileLocation = join7(targetFolder, coreOutputFilename);
454
- try {
455
- await fs2.access(coreTargetFileLocation);
359
+ let fileExistsConflict = false;
360
+ for (const remoteFile of remoteFilesToDownload) {
361
+ const parsedFilename = path2.basename(remoteFile, ".txt");
362
+ const checkFileLocation = join2(targetFolder, parsedFilename);
363
+ try {
364
+ await fs2.access(checkFileLocation);
365
+ fileExistsConflict = true;
366
+ break;
367
+ } catch {
368
+ }
369
+ }
370
+ if (fileExistsConflict) {
456
371
  s.stop();
457
372
  const overwritePrompt = await confirm2({
458
- message: `\u26A0 Block component file [${coreOutputFilename}] already exists. Overwrite custom code revisions?`,
373
+ message: `\u26A0 Components in [${targetBlock}] already exist. Overwrite custom revisions?`,
459
374
  initialValue: false
460
375
  });
461
376
  handleCancel(overwritePrompt);
@@ -463,21 +378,19 @@ async function addCommand(blockName) {
463
378
  outro2(pc2.yellow("\u2139 Operation aborted safely. Local code modifications preserved."));
464
379
  return;
465
380
  }
466
- s.start(`Re-downloading template block [${targetBlock}]...`);
467
- } catch {
381
+ s.start(`Re-downloading template block files [${targetBlock}]...`);
468
382
  }
469
- await fs2.writeFile(coreTargetFileLocation, coreCodeTemplate, "utf-8");
470
- if (variantMeta && selectedVariant) {
471
- const variantFileUrl = `${BASE_URL}/${variantMeta.path}`;
472
- const variantFetchResponse = await fetch(variantFileUrl);
473
- if (!variantFetchResponse.ok) {
474
- throw new Error(
475
- `Failed downloading storage variant file: ${variantFetchResponse.statusText}`
476
- );
383
+ await fs2.mkdir(targetFolder, { recursive: true });
384
+ for (const remoteFilePath of remoteFilesToDownload) {
385
+ const fileUrl = `${RAW_CDN_BASE}/${remoteFilePath}`;
386
+ const response = await fetch(fileUrl);
387
+ if (!response.ok) {
388
+ throw new Error(`Failed downloading file: ${remoteFilePath} (${response.statusText})`);
477
389
  }
478
- const variantCodeTemplate = await variantFetchResponse.text();
479
- const variantOutputFilename = `store-${selectedVariant}.ts`;
480
- await fs2.writeFile(join7(targetFolder, variantOutputFilename), variantCodeTemplate, "utf-8");
390
+ const rawCodeTemplate = await response.text();
391
+ const targetFilename = path2.basename(remoteFilePath, ".txt");
392
+ const localWriteLocation = join2(targetFolder, targetFilename);
393
+ await fs2.writeFile(localWriteLocation, rawCodeTemplate, "utf-8");
481
394
  }
482
395
  const cleanDisplayPath = variantMeta && selectedVariant ? `${physicalPath.replace(/\\/g, "/")}/${targetBlock}` : physicalPath.replace(/\\/g, "/");
483
396
  s.stop(pc2.green("\u2714 Component isolation structures written smoothly."));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blockend-cli",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "Intelligent, modular backend blocks right inside your terminal",
5
5
  "main": "./dist/index.js",
6
6
  "type": "module",
@@ -26,11 +26,13 @@
26
26
  "license": "ISC",
27
27
  "packageManager": "pnpm@10.33.2",
28
28
  "dependencies": {
29
+ "@blockend/detector": "workspace:*",
29
30
  "@clack/prompts": "^1.5.1",
30
31
  "commander": "^15.0.0",
31
32
  "picocolors": "^1.1.1"
32
33
  },
33
34
  "devDependencies": {
34
- "@types/node": "^25.9.3"
35
+ "@types/node": "^25.9.3",
36
+ "tsup": "^8.5.1"
35
37
  }
36
38
  }