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.
- package/dist/index.js +78 -30
- 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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
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(`${
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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