blockend-cli 1.4.5 → 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 -28
  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,20 +211,28 @@ async function addCommand(blockName, options = {}) {
201
211
  ...packageJson.dependencies ?? {},
202
212
  ...packageJson.devDependencies ?? {}
203
213
  };
204
- const missingProd = [
205
- ...adapterContext.dependencies ?? [],
206
- ...variantMeta?.dependencies ?? []
207
- ].filter((d) => !(d in installedDeps));
208
- const missingDev = [
209
- ...adapterContext.devDependencies ?? [],
210
- ...variantMeta?.devDependencies ?? []
211
- ].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
+ );
212
230
  if (missingProd.length > 0 || missingDev.length > 0) {
213
231
  if (!json) log.warn(`Missing: ${pc.cyan([...missingProd, ...missingDev].join(", "))}`);
214
232
  const shouldInstall = yes ? true : await confirm({ message: "Install missing dependencies?", initialValue: true });
215
233
  if (!yes) handleCancel(shouldInstall);
216
234
  if (shouldInstall) {
217
- 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");
218
236
  if (!json) s.start(`Installing via ${pm}...`);
219
237
  const tasks = [];
220
238
  if (missingProd.length > 0) {
@@ -295,11 +313,17 @@ async function addCommand(blockName, options = {}) {
295
313
  const filesWritten = [];
296
314
  try {
297
315
  for (const fm of filesToDownload) {
298
- const res = await fetch(`${RAW_CDN_BASE}/${fm.source}`);
316
+ const res = await fetch(`${MANIFEST_URL}/${fm.source}`);
299
317
  if (!res.ok) throw new Error(`Download failed: ${fm.source} (HTTP ${res.status})`);
300
318
  let content = await res.text();
301
319
  const writtenFilePath = path.join(targetFolder, fm.target);
302
- content = rewriteFileImports(content, writtenFilePath, blocksRootAbsolute, aliasBase);
320
+ content = rewriteFileImports(
321
+ content,
322
+ writtenFilePath,
323
+ blocksRootAbsolute,
324
+ aliasBase,
325
+ rewriteStrategy
326
+ );
303
327
  await fs.mkdir(dirname(writtenFilePath), { recursive: true });
304
328
  await fs.writeFile(writtenFilePath, content, "utf-8");
305
329
  filesWritten.push(writtenFilePath);
@@ -331,12 +355,12 @@ import pc3 from "picocolors";
331
355
  import { join as join6 } from "path";
332
356
 
333
357
  // src/detectors/readers/package.ts
334
- import { readFile } from "fs/promises";
358
+ import { readFile as readFile2 } from "fs/promises";
335
359
  import { join as join2 } from "path";
336
360
  async function readPackageJson(cwd) {
337
361
  const pkgPath = join2(cwd, "package.json");
338
362
  try {
339
- const content = await readFile(pkgPath, "utf-8");
363
+ const content = await readFile2(pkgPath, "utf-8");
340
364
  return JSON.parse(content);
341
365
  } catch {
342
366
  throw new Error(`No package.json found at ${pkgPath}. Run blockend from your project root.`);
@@ -344,14 +368,14 @@ async function readPackageJson(cwd) {
344
368
  }
345
369
 
346
370
  // src/detectors/readers/tsconfig.ts
347
- import { readFile as readFile2 } from "fs/promises";
371
+ import { readFile as readFile3 } from "fs/promises";
348
372
  import { existsSync } from "fs";
349
373
  import { join as join3 } from "path";
350
374
  async function readTsConfig(cwd) {
351
375
  const tsconfigPath = join3(cwd, "tsconfig.json");
352
376
  if (!existsSync(tsconfigPath)) return null;
353
377
  try {
354
- const content = await readFile2(tsconfigPath, "utf-8");
378
+ const content = await readFile3(tsconfigPath, "utf-8");
355
379
  const cleanLines = content.split(/\r?\n/).filter((line) => {
356
380
  const trimmed = line.trim();
357
381
  return !trimmed.startsWith("//") && !trimmed.startsWith("/*");
@@ -391,9 +415,22 @@ function detectRuntime(deps) {
391
415
  }
392
416
 
393
417
  // src/detectors/package-manager.ts
394
- import { existsSync as existsSync3 } from "fs";
418
+ import { existsSync as existsSync3, readFileSync } from "fs";
395
419
  import { join as join5 } from "path";
396
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
+ }
397
434
  if (existsSync3(join5(cwd, "pnpm-lock.yaml"))) return "pnpm";
398
435
  if (existsSync3(join5(cwd, "bun.lockb"))) return "bun";
399
436
  if (existsSync3(join5(cwd, "yarn.lock"))) return "yarn";
@@ -409,6 +446,12 @@ async function detectProject(cwd) {
409
446
  ...pkg.peerDependencies
410
447
  };
411
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
+ }
412
455
  return {
413
456
  root: cwd,
414
457
  language: tsConfig !== null ? "typescript" : "javascript",
@@ -421,7 +464,8 @@ async function detectProject(cwd) {
421
464
  aliasMap: tsConfig?.compilerOptions?.paths ? flattenTsPaths(tsConfig.compilerOptions.paths) : {},
422
465
  srcDir,
423
466
  // Default blocks directory: srcDir/lib/blocks
424
- blocksDir: join6(srcDir, "lib", "blocks")
467
+ blocksDir: join6(srcDir, "lib", "blocks"),
468
+ importRewriteStrategy
425
469
  };
426
470
  }
427
471
  function flattenTsPaths(paths) {
@@ -618,6 +662,8 @@ async function initCommand(options = {}) {
618
662
  log2.info(
619
663
  `Import alias: ${theme.state.info(finalBlockAlias === void 0 ? " relative imports" : finalBlockAlias)}`
620
664
  );
665
+ const strategyDisplay = context.importRewriteStrategy === "rewrite" ? "NodeNext (append .js)" : "Bundler / Extensionless";
666
+ log2.info(`Import strategy: ${theme.state.info(strategyDisplay)}`);
621
667
  }
622
668
  let includeRedis = false;
623
669
  if (context.hasRedis) {
@@ -637,13 +683,15 @@ async function initCommand(options = {}) {
637
683
  $schema: "https://blockend.noorulhassan.com/schema.json",
638
684
  environment: framework || "express",
639
685
  language: context.language || "typescript",
686
+ packageManager: context.packageManager,
687
+ importRewriteStrategy: context.importRewriteStrategy,
688
+ // PERSIST STRATEGY TO DISK
640
689
  includeRedis,
641
690
  aliases: {
642
691
  ...finalBlockAlias ? { blocks: finalBlockAlias } : {}
643
692
  },
644
693
  paths: {
645
694
  blocks: finalPath
646
- // e.g. "./src/blocks" — always a real physical path
647
695
  }
648
696
  };
649
697
  if (!json) s.start("Writing configuration...");
@@ -683,6 +731,8 @@ async function detectCommand(options = {}) {
683
731
  console.log(` Language: ${pc4.cyan(context.language)}`);
684
732
  console.log(` Package manager: ${pc4.cyan(context.packageManager)}`);
685
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}`);
686
736
  console.log(
687
737
  ` Redis: ${context.hasRedis ? pc4.green("detected") : pc4.dim("not found")}`
688
738
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blockend-cli",
3
- "version": "1.4.5",
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",