blockend-cli 1.2.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.
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,9 +48,9 @@ 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
- \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
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
147
55
  \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551
148
56
  \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551
@@ -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: [
@@ -197,18 +105,6 @@ async function initCommand() {
197
105
  outro(format.muted("Initialization cancelled."));
198
106
  return;
199
107
  }
200
- const language = await select({
201
- message: "Confirm primary language",
202
- initialValue: context.language === "typescript" ? "typescript" : "javascript",
203
- options: [
204
- { value: "typescript", label: "TypeScript" },
205
- { value: "javascript", label: "JavaScript" }
206
- ]
207
- });
208
- if (isCancel(language)) {
209
- outro(format.muted("Initialization cancelled."));
210
- return;
211
- }
212
108
  const availableAliases = Object.keys(context.aliasMap || {});
213
109
  const baseAliasToken = availableAliases.length > 0 ? availableAliases[0] : "@/";
214
110
  const blockAliasInput = await text({
@@ -224,7 +120,7 @@ async function initCommand() {
224
120
  const aliasPrefix = availableAliases.find((a) => blockAlias.startsWith(a)) || "";
225
121
  const physicalPrefix = aliasPrefix ? context.aliasMap[aliasPrefix] : "./";
226
122
  const resolvedSubDir = blockAlias.replace(aliasPrefix, "");
227
- const assumedPhysicalDir = join6(physicalPrefix, resolvedSubDir);
123
+ const assumedPhysicalDir = join(physicalPrefix, resolvedSubDir);
228
124
  const relativeBlocksPath = path.relative(cwd, path.resolve(cwd, assumedPhysicalDir));
229
125
  const normalizedPath = relativeBlocksPath.replace(/\\/g, "/");
230
126
  const finalPath = normalizedPath.startsWith(".") ? normalizedPath : `./${normalizedPath}`;
@@ -241,7 +137,7 @@ async function initCommand() {
241
137
  const configPayload = {
242
138
  $schema: "https://blockend.dev/schema.json",
243
139
  environment: framework,
244
- language,
140
+ language: "typescript",
245
141
  includeRedis,
246
142
  aliases: {
247
143
  blocks: blockAlias
@@ -262,7 +158,7 @@ async function initCommand() {
262
158
  }
263
159
 
264
160
  // src/commands/add.ts
265
- import path2, { join as join7 } from "path";
161
+ import path2, { join as join2, dirname } from "path";
266
162
  import fs2 from "fs/promises";
267
163
  import { exec } from "child_process";
268
164
  import { intro as intro2, outro as outro2, select as select2, spinner as spinner2, confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
@@ -270,7 +166,6 @@ import pc2 from "picocolors";
270
166
  var REPO_OWNER = "codewithnuh";
271
167
  var REPO_NAME = "blockend";
272
168
  var BRANCH = "master";
273
- var LOCAL_DEV_URL = "http://localhost:5000";
274
169
  var RAW_CDN_BASE = `https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}/${BRANCH}`;
275
170
  var MANIFEST_URL = `${RAW_CDN_BASE}/registry/index.json`;
276
171
  function handleCancel(value) {
@@ -279,16 +174,47 @@ function handleCancel(value) {
279
174
  process.exit(0);
280
175
  }
281
176
  }
177
+ async function findUp(filename, startDir) {
178
+ let dir = startDir;
179
+ while (true) {
180
+ const checkPath = join2(dir, filename);
181
+ try {
182
+ await fs2.access(checkPath);
183
+ return checkPath;
184
+ } catch {
185
+ const parent = dirname(dir);
186
+ if (parent === dir) break;
187
+ dir = parent;
188
+ }
189
+ }
190
+ return null;
191
+ }
282
192
  async function addCommand(blockName) {
283
193
  intro2(pc2.bgBlack(pc2.magenta(" Blockend Component Ingestion ")));
284
194
  const cwd = process.cwd();
285
- const configPath = join7(cwd, "blockend.json");
195
+ const configPath = await findUp("blockend.json", cwd);
196
+ if (!configPath) {
197
+ outro2(pc2.red("\u2716 blockend.json not found. Run 'npx blockend init' first."));
198
+ return;
199
+ }
200
+ const rootDir = dirname(configPath);
286
201
  let config;
287
202
  try {
288
203
  const configFile = await fs2.readFile(configPath, "utf-8");
289
204
  config = JSON.parse(configFile);
290
205
  } catch {
291
- outro2(pc2.red("\u2716 blockend.json not found. Run 'npx blockend init' first."));
206
+ outro2(pc2.red("\u2716 Failed to parse blockend.json layout configuration."));
207
+ return;
208
+ }
209
+ if (config.language !== "typescript") {
210
+ outro2(
211
+ pc2.yellow(
212
+ `
213
+ \u2139 Blockend forces modern architectural standards.
214
+ To ensure strict type safety and absolute code ownership, the registry exclusively supports TypeScript.
215
+ Please migrate your project configuration to TypeScript to ingest blocks.`
216
+ )
217
+ );
292
218
  return;
293
219
  }
294
220
  const s = spinner2();
@@ -303,14 +229,19 @@ async function addCommand(blockName) {
303
229
  s.stop(pc2.red("\u2716 Failed to fetch the component registry from GitHub network paths."));
304
230
  return;
305
231
  }
232
+ const envKey = String(config.environment);
306
233
  let targetBlock = blockName;
307
234
  if (!targetBlock) {
308
- const availableOptions = Object.keys(registry).map((key) => ({
235
+ const availableOptions = Object.keys(registry).filter((key) => registry[key].environments[envKey] !== void 0).map((key) => ({
309
236
  value: key,
310
237
  label: `${key} - ${pc2.dim(registry[key].description)}`
311
238
  }));
312
239
  if (availableOptions.length === 0) {
313
- outro2(pc2.yellow("\u26A0 The remote block registry is currently empty."));
240
+ outro2(
241
+ pc2.yellow(
242
+ `\u26A0 No backend blocks are currently available for your framework layer: [${envKey}].`
243
+ )
244
+ );
314
245
  return;
315
246
  }
316
247
  const selectBlockPrompt = await select2({
@@ -325,7 +256,6 @@ async function addCommand(blockName) {
325
256
  outro2(pc2.red(`\u2716 Block "${targetBlock}" does not exist in the remote registry.`));
326
257
  return;
327
258
  }
328
- const envKey = String(config.environment);
329
259
  const envConfig = blockMeta.environments[envKey];
330
260
  if (!envConfig) {
331
261
  outro2(
@@ -352,13 +282,17 @@ async function addCommand(blockName) {
352
282
  }
353
283
  variantMeta = envConfig.variants[selectedVariant];
354
284
  }
285
+ const packageJsonPath = await findUp("package.json", rootDir);
286
+ if (!packageJsonPath) {
287
+ outro2(pc2.red("\u2716 Could not locate package.json in your current directory layout hierarchy."));
288
+ return;
289
+ }
290
+ const packageJsonDir = dirname(packageJsonPath);
355
291
  let packageJson;
356
292
  try {
357
- packageJson = JSON.parse(
358
- await fs2.readFile(join7(cwd, "package.json"), "utf-8")
359
- );
293
+ packageJson = JSON.parse(await fs2.readFile(packageJsonPath, "utf-8"));
360
294
  } catch {
361
- outro2(pc2.red("\u2716 Could not locate package.json in your current workspace directory."));
295
+ outro2(pc2.red("\u2716 Failed parsing file data configurations from target package.json location."));
362
296
  return;
363
297
  }
364
298
  const installedDeps = {
@@ -378,14 +312,14 @@ async function addCommand(blockName) {
378
312
  });
379
313
  handleCancel(shouldInstallPrompt);
380
314
  if (shouldInstallPrompt) {
381
- const packageManager = await fs2.access(join7(cwd, "pnpm-lock.yaml")).then(() => "pnpm").catch(() => "npm");
315
+ const packageManager = await fs2.access(join2(packageJsonDir, "pnpm-lock.yaml")).then(() => "pnpm").catch(() => "npm");
382
316
  s.start(`Preparing native workspace via ${packageManager}...`);
383
317
  try {
384
318
  const installCmd = packageManager === "pnpm" ? `pnpm add ${missingDeps.join(" ")}` : `npm install ${missingDeps.join(" ")}`;
385
319
  s.stop(pc2.cyan(`Executing: ${installCmd}
386
320
  `));
387
321
  await new Promise((resolve, reject) => {
388
- const child = exec(installCmd, { cwd });
322
+ const child = exec(installCmd, { cwd: packageJsonDir });
389
323
  child.stdout?.on("data", (data) => {
390
324
  process.stdout.write(pc2.dim(data));
391
325
  });
@@ -407,36 +341,56 @@ async function addCommand(blockName) {
407
341
  }
408
342
  s.start(`Downloading clean production template block [${targetBlock}]...`);
409
343
  try {
410
- const BASE_URL = MANIFEST_URL.includes("localhost") ? LOCAL_DEV_URL : RAW_CDN_BASE;
411
- const coreFileUrl = `${BASE_URL}/${envConfig.core}`;
412
- const coreFetchResponse = await fetch(coreFileUrl);
413
- if (!coreFetchResponse.ok) {
414
- 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);
415
350
  }
416
- const coreCodeTemplate = await coreFetchResponse.text();
417
351
  let physicalPath = config.paths.blocks;
418
352
  if (physicalPath.startsWith("@")) {
419
353
  physicalPath = "./src/blocks";
420
354
  }
421
- let targetFolder = path2.resolve(cwd, physicalPath);
355
+ let targetFolder = path2.resolve(rootDir, physicalPath);
422
356
  if (variantMeta && selectedVariant) {
423
- targetFolder = join7(targetFolder, targetBlock);
357
+ targetFolder = join2(targetFolder, targetBlock);
358
+ }
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) {
371
+ s.stop();
372
+ const overwritePrompt = await confirm2({
373
+ message: `\u26A0 Components in [${targetBlock}] already exist. Overwrite custom revisions?`,
374
+ initialValue: false
375
+ });
376
+ handleCancel(overwritePrompt);
377
+ if (!overwritePrompt) {
378
+ outro2(pc2.yellow("\u2139 Operation aborted safely. Local code modifications preserved."));
379
+ return;
380
+ }
381
+ s.start(`Re-downloading template block files [${targetBlock}]...`);
424
382
  }
425
383
  await fs2.mkdir(targetFolder, { recursive: true });
426
- const fileExtension = config.language === "typescript" ? "ts" : "js";
427
- const coreOutputFilename = `${targetBlock}.${fileExtension}`;
428
- await fs2.writeFile(join7(targetFolder, coreOutputFilename), coreCodeTemplate, "utf-8");
429
- if (variantMeta && selectedVariant) {
430
- const variantFileUrl = `${BASE_URL}/${variantMeta.path}`;
431
- const variantFetchResponse = await fetch(variantFileUrl);
432
- if (!variantFetchResponse.ok) {
433
- throw new Error(
434
- `Failed downloading storage variant file: ${variantFetchResponse.statusText}`
435
- );
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})`);
436
389
  }
437
- const variantCodeTemplate = await variantFetchResponse.text();
438
- const variantOutputFilename = `store-${selectedVariant}.${fileExtension}`;
439
- 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");
440
394
  }
441
395
  const cleanDisplayPath = variantMeta && selectedVariant ? `${physicalPath.replace(/\\/g, "/")}/${targetBlock}` : physicalPath.replace(/\\/g, "/");
442
396
  s.stop(pc2.green("\u2714 Component isolation structures written smoothly."));
@@ -445,9 +399,11 @@ async function addCommand(blockName) {
445
399
  `\u2728 Source blocks written to ${cleanDisplayPath}/ layout. Code ownership transferred!`
446
400
  )
447
401
  );
402
+ process.exit(0);
448
403
  } catch (error) {
449
404
  s.stop(pc2.red("\u2716 Fatal crash occurred while downloading or transferring file layouts."));
450
405
  console.error(error);
406
+ process.exit(1);
451
407
  }
452
408
  }
453
409
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blockend-cli",
3
- "version": "1.2.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",
@@ -12,7 +12,8 @@
12
12
  "readme.md"
13
13
  ],
14
14
  "scripts": {
15
- "build": "tsup src/index.ts --format esm --dts --out-dir dist"
15
+ "build": "tsup src/index.ts --format esm --dts --out-dir dist",
16
+ "typecheck": "tsc --noEmit"
16
17
  },
17
18
  "keywords": [
18
19
  "cli",
@@ -25,11 +26,13 @@
25
26
  "license": "ISC",
26
27
  "packageManager": "pnpm@10.33.2",
27
28
  "dependencies": {
29
+ "@blockend/detector": "workspace:*",
28
30
  "@clack/prompts": "^1.5.1",
29
31
  "commander": "^15.0.0",
30
32
  "picocolors": "^1.1.1"
31
33
  },
32
34
  "devDependencies": {
33
- "@types/node": "^25.9.3"
35
+ "@types/node": "^25.9.3",
36
+ "tsup": "^8.5.1"
34
37
  }
35
38
  }
package/dist/index.mjs DELETED
@@ -1,49 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/index.ts
4
- import { Command } from "commander";
5
- import { intro, outro, select, text } from "@clack/prompts";
6
- import pc from "picocolors";
7
- var program = new Command();
8
- program.name("blockend").description("Zero-dependency production backend blocks CLI").version("0.1.0");
9
- program.command("init").description("Initialize blockend configuration in your project").action(async () => {
10
- intro(pc.bgBlack(pc.cyan(" Blockend Initialization ")));
11
- const environment = await select({
12
- message: "Select your backend framework environment:",
13
- options: [
14
- { value: "express", label: "Express.js" },
15
- { value: "fastify", label: "Fastify" },
16
- { value: "nextjs", label: "Next.js (App Router)" }
17
- ]
18
- });
19
- const blockPath = await text({
20
- message: "Where should we save your backend blocks?",
21
- initialValue: "./src/blocks",
22
- placeholder: "./src/blocks"
23
- });
24
- console.log("\nWriting configuration target data...");
25
- console.log(
26
- pc.green(`\u2714 Targets set: Environment -> ${String(environment)}, Path -> ${String(blockPath)}`)
27
- );
28
- outro(pc.cyan("blockend.json configured mock-successfully."));
29
- });
30
- program.command("add [block]").description("Add a backend block to your project").action(async (block) => {
31
- intro(pc.bgBlack(pc.magenta(" Blockend Add Tool ")));
32
- if (!block) {
33
- const selectedBlock = await select({
34
- message: "Which backend block would you like to add?",
35
- options: [
36
- { value: "rate-limiter", label: "rate-limiter (Redis/Memory)" },
37
- { value: "auth-handler", label: "auth-handler (JWT/Session)" },
38
- { value: "error-handler", label: "global-error-handler" }
39
- ]
40
- });
41
- block = selectedBlock;
42
- }
43
- console.log(pc.yellow(`
44
- [Mock Run]: Fetching metadata manifest for "${block}"...`));
45
- console.log(pc.green(`\u2714 [Mock Run]: Injected missing dependencies into your package context.`));
46
- console.log(pc.green(`\u2714 [Mock Run]: Written file cleanly into your target blocks path.`));
47
- outro(pc.magenta(`Successfully added ${block} block!`));
48
- });
49
- program.parse(process.argv);
@@ -1,13 +0,0 @@
1
- export interface SessionUser {
2
- id: string;
3
- email: string;
4
- roles: string[];
5
- }
6
-
7
- export function requireRole(user: SessionUser | null, role: string): SessionUser {
8
- if (!user || !user.roles.includes(role)) {
9
- throw new Error("Unauthorized");
10
- }
11
-
12
- return user;
13
- }
@@ -1,12 +0,0 @@
1
- {
2
- "name": "auth",
3
- "version": "0.1.0",
4
- "dependencies": [],
5
- "prompts": [],
6
- "files": [
7
- {
8
- "template": "index.hbs",
9
- "output": "src/lib/blocks/{{name}}.ts"
10
- }
11
- ]
12
- }
@@ -1,38 +0,0 @@
1
- export interface RateLimitOptions {
2
- limit: number;
3
- windowMs: number;
4
- }
5
-
6
- export interface RateLimitResult {
7
- allowed: boolean;
8
- remaining: number;
9
- resetAt: number;
10
- }
11
-
12
- const buckets = new Map<string, { count: number; resetAt: number }>();
13
-
14
- export function createRateLimiter(options: RateLimitOptions) {
15
- return function checkRateLimit(key: string): RateLimitResult {
16
- const now = Date.now();
17
- const current = buckets.get(key);
18
-
19
- if (!current || current.resetAt <= now) {
20
- const resetAt = now + options.windowMs;
21
- buckets.set(key, { count: 1, resetAt });
22
- return { allowed: true, remaining: options.limit - 1, resetAt };
23
- }
24
-
25
- if (current.count >= options.limit) {
26
- return { allowed: false, remaining: 0, resetAt: current.resetAt };
27
- }
28
-
29
- current.count += 1;
30
- return {
31
- allowed: true,
32
- remaining: Math.max(options.limit - current.count, 0),
33
- resetAt: current.resetAt
34
- };
35
- };
36
- }
37
-
38
- export const rateLimitDriver = "{{driver}}";
@@ -1,22 +0,0 @@
1
- {
2
- "name": "rate-limiter",
3
- "version": "0.1.0",
4
- "dependencies": [],
5
- "prompts": [
6
- {
7
- "id": "driver",
8
- "type": "select",
9
- "message": "Select your storage backend:",
10
- "options": [
11
- { "value": "in-memory", "label": "In-Memory (Zero dependencies)" },
12
- { "value": "redis", "label": "Redis (Scalable cluster)" }
13
- ]
14
- }
15
- ],
16
- "files": [
17
- {
18
- "template": "index.hbs",
19
- "output": "src/lib/blocks/{{name}}.ts"
20
- }
21
- ]
22
- }