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.
- package/dist/index.js +78 -28
- 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,20 +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
|
-
|
|
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
|
|
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(`${
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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