blockend-cli 1.4.6 → 1.5.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.
Files changed (2) hide show
  1. package/dist/index.js +78 -30
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { defineCommand, runMain } from "citty";
5
5
 
6
6
  // src/commands/add.ts
7
7
  import path, { join, dirname } from "path";
8
- import fs from "fs/promises";
8
+ import fs, { readFile } from "fs/promises";
9
9
  import { exec } from "child_process";
10
10
  import { intro, outro, select, spinner, confirm, isCancel, log } from "@clack/prompts";
11
11
  import pc from "picocolors";
@@ -20,21 +20,30 @@ function blockSupportsEnv(block, envKey) {
20
20
  }
21
21
  return block.adapters?.[envKey] !== void 0 || block.environments?.[envKey] !== void 0;
22
22
  }
23
+ var getMissingDependencies = (deps, installed) => [...new Set(deps)].filter((dep) => !(dep in installed));
23
24
  function resolveAdapterContext(block, envKey) {
24
25
  return block.adapters?.[envKey] ?? block.adapters?.["*"] ?? block.environments?.[envKey] ?? block.environments?.["*"] ?? null;
25
26
  }
26
- function rewriteFileImports(fileContent, writtenFilePath, blocksRoot, aliasBase) {
27
- if (!aliasBase || aliasBase.startsWith(".")) {
28
- return fileContent;
29
- }
27
+ function rewriteFileImports(fileContent, writtenFilePath, blocksRoot, aliasBase, strategy) {
30
28
  const importRegex = /((?:from|import)\s*)(['"`])(\.\.?\/[^'"` \n\r]+)(['"`])/g;
31
29
  const fileDir = dirname(writtenFilePath);
32
30
  return fileContent.replace(importRegex, (match, keyword, openQuote, relativePath, closeQuote) => {
33
- const absoluteTarget = path.resolve(fileDir, relativePath);
34
- const fromBlocksRoot = path.relative(blocksRoot, absoluteTarget).replace(/\\/g, "/");
35
- if (fromBlocksRoot.startsWith("..")) return match;
36
- const aliased = `${aliasBase}/${fromBlocksRoot}`.replace(/\/+/g, "/");
37
- return `${keyword}${openQuote}${aliased}${closeQuote}`;
31
+ let targetPath = relativePath;
32
+ if (aliasBase && !aliasBase.startsWith(".")) {
33
+ const absoluteTarget = path.resolve(fileDir, relativePath);
34
+ const fromBlocksRoot = path.relative(blocksRoot, absoluteTarget).replace(/\\/g, "/");
35
+ if (!fromBlocksRoot.startsWith("..")) {
36
+ targetPath = `${aliasBase}/${fromBlocksRoot}`.replace(/\/+/g, "/");
37
+ }
38
+ }
39
+ const hasJsExt = targetPath.endsWith(".js");
40
+ if (strategy === "rewrite" && !hasJsExt) {
41
+ targetPath = targetPath.replace(/\.(ts|tsx|mts|cts)$/, "");
42
+ targetPath = `${targetPath}.js`;
43
+ } else if (strategy === "remove" && hasJsExt) {
44
+ targetPath = targetPath.slice(0, -3);
45
+ }
46
+ return `${keyword}${openQuote}${targetPath}${closeQuote}`;
38
47
  });
39
48
  }
40
49
  function outputError(json, message) {
@@ -101,6 +110,7 @@ async function addCommand(blockName, options = {}) {
101
110
  const blocksRootAbsolute = path.resolve(rootDir, physicalBlocksPath);
102
111
  const aliasBase = config.aliases.blocks;
103
112
  const envKey = config.environment;
113
+ const rewriteStrategy = config.importRewriteStrategy ?? "remove";
104
114
  const s = spinner();
105
115
  if (!json) s.start("Fetching registry...");
106
116
  let registry;
@@ -201,22 +211,28 @@ async function addCommand(blockName, options = {}) {
201
211
  ...packageJson.dependencies ?? {},
202
212
  ...packageJson.devDependencies ?? {}
203
213
  };
204
- const missingProd = [
205
- ...blockMeta.dependencies ?? [],
206
- ...adapterContext.dependencies ?? [],
207
- ...variantMeta?.dependencies ?? []
208
- ].filter((d) => !(d in installedDeps));
209
- const missingDev = [
210
- ...blockMeta.dependencies ?? [],
211
- ...adapterContext.devDependencies ?? [],
212
- ...variantMeta?.devDependencies ?? []
213
- ].filter((d) => !(d in installedDeps));
214
+ const missingProd = getMissingDependencies(
215
+ [
216
+ ...blockMeta.dependencies ?? [],
217
+ ...adapterContext.dependencies ?? [],
218
+ ...variantMeta?.dependencies ?? []
219
+ ],
220
+ installedDeps
221
+ );
222
+ const missingDev = getMissingDependencies(
223
+ [
224
+ ...blockMeta.devDependencies ?? [],
225
+ ...adapterContext.devDependencies ?? [],
226
+ ...variantMeta?.devDependencies ?? []
227
+ ],
228
+ installedDeps
229
+ );
214
230
  if (missingProd.length > 0 || missingDev.length > 0) {
215
231
  if (!json) log.warn(`Missing: ${pc.cyan([...missingProd, ...missingDev].join(", "))}`);
216
232
  const shouldInstall = yes ? true : await confirm({ message: "Install missing dependencies?", initialValue: true });
217
233
  if (!yes) handleCancel(shouldInstall);
218
234
  if (shouldInstall) {
219
- const pm = await fs.access(join(packageJsonDir, "pnpm-lock.yaml")).then(() => "pnpm").catch(() => "npm");
235
+ const pm = await readFile(join(packageJsonDir, "blockend.json"), "utf8").then((data) => JSON.parse(data).packageManager).catch(() => "npm");
220
236
  if (!json) s.start(`Installing via ${pm}...`);
221
237
  const tasks = [];
222
238
  if (missingProd.length > 0) {
@@ -297,11 +313,17 @@ async function addCommand(blockName, options = {}) {
297
313
  const filesWritten = [];
298
314
  try {
299
315
  for (const fm of filesToDownload) {
300
- const res = await fetch(`${RAW_CDN_BASE}/${fm.source}`);
316
+ const res = await fetch(`${MANIFEST_URL}/${fm.source}`);
301
317
  if (!res.ok) throw new Error(`Download failed: ${fm.source} (HTTP ${res.status})`);
302
318
  let content = await res.text();
303
319
  const writtenFilePath = path.join(targetFolder, fm.target);
304
- content = rewriteFileImports(content, writtenFilePath, blocksRootAbsolute, aliasBase);
320
+ content = rewriteFileImports(
321
+ content,
322
+ writtenFilePath,
323
+ blocksRootAbsolute,
324
+ aliasBase,
325
+ rewriteStrategy
326
+ );
305
327
  await fs.mkdir(dirname(writtenFilePath), { recursive: true });
306
328
  await fs.writeFile(writtenFilePath, content, "utf-8");
307
329
  filesWritten.push(writtenFilePath);
@@ -333,12 +355,12 @@ import pc3 from "picocolors";
333
355
  import { join as join6 } from "path";
334
356
 
335
357
  // src/detectors/readers/package.ts
336
- import { readFile } from "fs/promises";
358
+ import { readFile as readFile2 } from "fs/promises";
337
359
  import { join as join2 } from "path";
338
360
  async function readPackageJson(cwd) {
339
361
  const pkgPath = join2(cwd, "package.json");
340
362
  try {
341
- const content = await readFile(pkgPath, "utf-8");
363
+ const content = await readFile2(pkgPath, "utf-8");
342
364
  return JSON.parse(content);
343
365
  } catch {
344
366
  throw new Error(`No package.json found at ${pkgPath}. Run blockend from your project root.`);
@@ -346,14 +368,14 @@ async function readPackageJson(cwd) {
346
368
  }
347
369
 
348
370
  // src/detectors/readers/tsconfig.ts
349
- import { readFile as readFile2 } from "fs/promises";
371
+ import { readFile as readFile3 } from "fs/promises";
350
372
  import { existsSync } from "fs";
351
373
  import { join as join3 } from "path";
352
374
  async function readTsConfig(cwd) {
353
375
  const tsconfigPath = join3(cwd, "tsconfig.json");
354
376
  if (!existsSync(tsconfigPath)) return null;
355
377
  try {
356
- const content = await readFile2(tsconfigPath, "utf-8");
378
+ const content = await readFile3(tsconfigPath, "utf-8");
357
379
  const cleanLines = content.split(/\r?\n/).filter((line) => {
358
380
  const trimmed = line.trim();
359
381
  return !trimmed.startsWith("//") && !trimmed.startsWith("/*");
@@ -393,9 +415,22 @@ function detectRuntime(deps) {
393
415
  }
394
416
 
395
417
  // src/detectors/package-manager.ts
396
- import { existsSync as existsSync3 } from "fs";
418
+ import { existsSync as existsSync3, readFileSync } from "fs";
397
419
  import { join as join5 } from "path";
398
420
  function detectPackageManager(cwd) {
421
+ const pkgJsonPath = join5(cwd, "package.json");
422
+ if (existsSync3(pkgJsonPath)) {
423
+ try {
424
+ const packageJson = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
425
+ if (typeof packageJson.packageManager === "string") {
426
+ const manager = packageJson.packageManager.split("@")[0];
427
+ if (manager === "npm" || manager === "pnpm" || manager === "yarn" || manager === "bun") {
428
+ return manager;
429
+ }
430
+ }
431
+ } catch {
432
+ }
433
+ }
399
434
  if (existsSync3(join5(cwd, "pnpm-lock.yaml"))) return "pnpm";
400
435
  if (existsSync3(join5(cwd, "bun.lockb"))) return "bun";
401
436
  if (existsSync3(join5(cwd, "yarn.lock"))) return "yarn";
@@ -411,6 +446,12 @@ async function detectProject(cwd) {
411
446
  ...pkg.peerDependencies
412
447
  };
413
448
  const srcDir = await inferSrcDir(cwd);
449
+ const moduleOpt = tsConfig?.compilerOptions?.module?.toLocaleLowerCase();
450
+ const resolutionOpt = tsConfig?.compilerOptions?.moduleResolution?.toLocaleLowerCase();
451
+ let importRewriteStrategy = "remove";
452
+ if (moduleOpt === "nodenext" || moduleOpt === "node16" || resolutionOpt === "nodenext" || resolutionOpt === "node16" || pkg.type === "module" && !resolutionOpt) {
453
+ importRewriteStrategy = "rewrite";
454
+ }
414
455
  return {
415
456
  root: cwd,
416
457
  language: tsConfig !== null ? "typescript" : "javascript",
@@ -423,7 +464,8 @@ async function detectProject(cwd) {
423
464
  aliasMap: tsConfig?.compilerOptions?.paths ? flattenTsPaths(tsConfig.compilerOptions.paths) : {},
424
465
  srcDir,
425
466
  // Default blocks directory: srcDir/lib/blocks
426
- blocksDir: join6(srcDir, "lib", "blocks")
467
+ blocksDir: join6(srcDir, "lib", "blocks"),
468
+ importRewriteStrategy
427
469
  };
428
470
  }
429
471
  function flattenTsPaths(paths) {
@@ -620,6 +662,8 @@ async function initCommand(options = {}) {
620
662
  log2.info(
621
663
  `Import alias: ${theme.state.info(finalBlockAlias === void 0 ? " relative imports" : finalBlockAlias)}`
622
664
  );
665
+ const strategyDisplay = context.importRewriteStrategy === "rewrite" ? "NodeNext (append .js)" : "Bundler / Extensionless";
666
+ log2.info(`Import strategy: ${theme.state.info(strategyDisplay)}`);
623
667
  }
624
668
  let includeRedis = false;
625
669
  if (context.hasRedis) {
@@ -639,13 +683,15 @@ async function initCommand(options = {}) {
639
683
  $schema: "https://blockend.noorulhassan.com/schema.json",
640
684
  environment: framework || "express",
641
685
  language: context.language || "typescript",
686
+ packageManager: context.packageManager,
687
+ importRewriteStrategy: context.importRewriteStrategy,
688
+ // PERSIST STRATEGY TO DISK
642
689
  includeRedis,
643
690
  aliases: {
644
691
  ...finalBlockAlias ? { blocks: finalBlockAlias } : {}
645
692
  },
646
693
  paths: {
647
694
  blocks: finalPath
648
- // e.g. "./src/blocks" — always a real physical path
649
695
  }
650
696
  };
651
697
  if (!json) s.start("Writing configuration...");
@@ -685,6 +731,8 @@ async function detectCommand(options = {}) {
685
731
  console.log(` Language: ${pc4.cyan(context.language)}`);
686
732
  console.log(` Package manager: ${pc4.cyan(context.packageManager)}`);
687
733
  console.log(` Source dir: ${pc4.dim(context.srcDir)}`);
734
+ const strategyDisplay = context.importRewriteStrategy === "rewrite" ? pc4.yellow("NodeNext (append .js)") : pc4.magenta("Bundler / Extensionless");
735
+ console.log(` Import strategy: ${strategyDisplay}`);
688
736
  console.log(
689
737
  ` Redis: ${context.hasRedis ? pc4.green("detected") : pc4.dim("not found")}`
690
738
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blockend-cli",
3
- "version": "1.4.6",
3
+ "version": "1.5.0",
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",