jsrepo 1.5.0 → 1.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/index.ts
2
2
  import fs12 from "node:fs";
3
3
  import { fileURLToPath } from "node:url";
4
- import { program as program7 } from "commander";
4
+ import { program as program8 } from "commander";
5
5
  import path12 from "pathe";
6
6
 
7
7
  // src/commands/add.ts
@@ -913,7 +913,7 @@ var categorySchema = v2.object({
913
913
  });
914
914
  var TEST_SUFFIXES = [".test.ts", "_test.ts", ".test.js", "_test.js"];
915
915
  var isTestFile = (file) => TEST_SUFFIXES.find((suffix) => file.endsWith(suffix)) !== void 0;
916
- var buildBlocksDirectory = (blocksPath, { cwd, excludeDeps }) => {
916
+ var buildBlocksDirectory = (blocksPath, { cwd, excludeDeps, includeBlocks, includeCategories, errorOnWarn }) => {
917
917
  let paths;
918
918
  try {
919
919
  paths = fs3.readdirSync(blocksPath);
@@ -925,6 +925,8 @@ var buildBlocksDirectory = (blocksPath, { cwd, excludeDeps }) => {
925
925
  const categoryDir = path3.join(blocksPath, categoryPath);
926
926
  if (fs3.statSync(categoryDir).isFile()) continue;
927
927
  const categoryName = path3.basename(categoryPath);
928
+ if (includeCategories.length > 0 && includeCategories.find((val) => val.trim() === categoryName.trim()) === void 0)
929
+ continue;
928
930
  const category = {
929
931
  name: categoryName,
930
932
  blocks: []
@@ -934,16 +936,29 @@ var buildBlocksDirectory = (blocksPath, { cwd, excludeDeps }) => {
934
936
  const blockDir = path3.join(categoryDir, file);
935
937
  if (fs3.statSync(blockDir).isFile()) {
936
938
  if (isTestFile(file)) continue;
939
+ const name2 = path3.parse(path3.basename(file)).name;
940
+ if (includeBlocks.length > 0 && includeBlocks.find((val) => val.trim() === name2.trim()) === void 0)
941
+ continue;
937
942
  const lang = languages.find((resolver) => resolver.matches(file));
938
943
  if (!lang) {
939
- console.warn(
940
- `${WARN} Skipped \`${color3.bold(blockDir)}\` \`${color3.bold(
941
- path3.parse(file).ext
942
- )}\` files are not currently supported!`
943
- );
944
+ const error = "files are not currently supported!";
945
+ if (errorOnWarn) {
946
+ program.error(
947
+ color3.red(
948
+ `Couldn't add \`${color3.bold(blockDir)}\` \`*${color3.bold(
949
+ path3.parse(file).ext
950
+ )}\` ${error}`
951
+ )
952
+ );
953
+ } else {
954
+ console.warn(
955
+ `${VERTICAL_LINE} ${WARN} Skipped \`${color3.bold(blockDir)}\` \`*${color3.bold(
956
+ path3.parse(file).ext
957
+ )}\` ${error}`
958
+ );
959
+ }
944
960
  continue;
945
961
  }
946
- const name2 = path3.parse(path3.basename(file)).name;
947
962
  const testsPath = files.find(
948
963
  (f) => TEST_SUFFIXES.find((suffix) => f === `${name2}${suffix}`)
949
964
  );
@@ -975,6 +990,8 @@ var buildBlocksDirectory = (blocksPath, { cwd, excludeDeps }) => {
975
990
  category.blocks.push(block);
976
991
  } else {
977
992
  const blockName = file;
993
+ if (includeBlocks.length > 0 && includeBlocks.find((val) => val.trim() === blockName.trim()) === void 0)
994
+ continue;
978
995
  const blockFiles = fs3.readdirSync(blockDir);
979
996
  const hasTests = blockFiles.findIndex((f) => isTestFile(f)) !== -1;
980
997
  const localDepsSet = /* @__PURE__ */ new Set();
@@ -983,18 +1000,38 @@ var buildBlocksDirectory = (blocksPath, { cwd, excludeDeps }) => {
983
1000
  for (const f of blockFiles) {
984
1001
  if (isTestFile(f)) continue;
985
1002
  if (fs3.statSync(path3.join(blockDir, f)).isDirectory()) {
986
- console.warn(
987
- `${VERTICAL_LINE} ${WARN} Skipped \`${color3.bold(path3.join(blockDir, f))}\` subdirectories are not currently supported!`
988
- );
1003
+ const error = "subdirectories are not currently supported!";
1004
+ if (errorOnWarn) {
1005
+ program.error(
1006
+ color3.red(
1007
+ `Couldn't add \`${color3.bold(path3.join(blockDir, f))}\` ${error}`
1008
+ )
1009
+ );
1010
+ } else {
1011
+ console.warn(
1012
+ `${VERTICAL_LINE} ${WARN} Skipped \`${color3.bold(path3.join(blockDir, f))}\` ${error}`
1013
+ );
1014
+ }
989
1015
  continue;
990
1016
  }
991
1017
  const lang = languages.find((resolver) => resolver.matches(f));
992
1018
  if (!lang) {
993
- console.warn(
994
- `${VERTICAL_LINE} ${WARN} Skipped \`${color3.bold(path3.join(blockDir, f))}\` \`*${color3.bold(
995
- path3.parse(f).ext
996
- )}\` files are not currently supported!`
997
- );
1019
+ const error = "files are not currently supported!";
1020
+ if (errorOnWarn) {
1021
+ program.error(
1022
+ color3.red(
1023
+ `Couldn't add \`${color3.bold(path3.join(blockDir, f))}\` \`*${color3.bold(
1024
+ path3.parse(f).ext
1025
+ )}\` ${error}`
1026
+ )
1027
+ );
1028
+ } else {
1029
+ console.warn(
1030
+ `${VERTICAL_LINE} ${WARN} Skipped \`${path3.join(blockDir, f)}\` \`*${color3.bold(
1031
+ path3.parse(f).ext
1032
+ )}\` ${error}`
1033
+ );
1034
+ }
998
1035
  continue;
999
1036
  }
1000
1037
  const { local, dependencies, devDependencies } = lang.resolveDependencies({
@@ -1631,17 +1668,28 @@ ${content}`;
1631
1668
  import fs7 from "node:fs";
1632
1669
  import { outro as outro2, spinner as spinner3 } from "@clack/prompts";
1633
1670
  import color8 from "chalk";
1634
- import { Command as Command2 } from "commander";
1671
+ import { Command as Command2, program as program3 } from "commander";
1635
1672
  import path7 from "pathe";
1636
1673
  import * as v6 from "valibot";
1637
1674
  var schema3 = v6.object({
1638
1675
  dirs: v6.array(v6.string()),
1676
+ includeBlocks: v6.array(v6.string()),
1677
+ includeCategories: v6.array(v6.string()),
1639
1678
  excludeDeps: v6.array(v6.string()),
1640
1679
  output: v6.boolean(),
1680
+ errorOnWarn: v6.boolean(),
1641
1681
  verbose: v6.boolean(),
1642
1682
  cwd: v6.string()
1643
1683
  });
1644
- var build = new Command2("build").description(`Builds the provided --dirs in the project root into a \`${OUTPUT_FILE}\` file.`).option("--dirs [dirs...]", "The directories containing the blocks.", ["./blocks"]).option("--exclude-deps [deps...]", "Dependencies that should not be added.", []).option("--no-output", `Do not output a \`${OUTPUT_FILE}\` file.`).option("--verbose", "Include debug logs.", false).option("--cwd <path>", "The current working directory.", process.cwd()).action(async (opts) => {
1684
+ var build = new Command2("build").description(`Builds the provided --dirs in the project root into a \`${OUTPUT_FILE}\` file.`).option("--dirs [dirs...]", "The directories containing the blocks.", ["./blocks"]).option("--include-blocks [blockNames...]", "Include only the blocks with these names.", []).option(
1685
+ "--include-categories [categoryNames...]",
1686
+ "Include only the categories with these names.",
1687
+ []
1688
+ ).option("--exclude-deps [deps...]", "Dependencies that should not be added.", []).option("--no-output", `Do not output a \`${OUTPUT_FILE}\` file.`).option(
1689
+ "--error-on-warn",
1690
+ "If there is a warning throw an error and do not allow build to complete.",
1691
+ false
1692
+ ).option("--verbose", "Include debug logs.", false).option("--cwd <path>", "The current working directory.", process.cwd()).action(async (opts) => {
1645
1693
  const options = v6.parse(schema3, opts);
1646
1694
  _intro(context.package.version);
1647
1695
  await _build(options);
@@ -1655,25 +1703,81 @@ var _build = async (options) => {
1655
1703
  const dirPath = path7.join(options.cwd, dir);
1656
1704
  loading.start(`Building ${color8.cyan(dirPath)}`);
1657
1705
  if (options.output && fs7.existsSync(outFile)) fs7.rmSync(outFile);
1658
- categories.push(
1659
- ...buildBlocksDirectory(dirPath, { cwd: options.cwd, excludeDeps: options.excludeDeps })
1660
- );
1706
+ const builtCategories = buildBlocksDirectory(dirPath, { ...options });
1707
+ for (const category of builtCategories) {
1708
+ if (categories.find((cat) => cat.name === category.name) !== void 0) {
1709
+ const error = "a category with the same name already exists!";
1710
+ if (options.errorOnWarn) {
1711
+ program3.error(
1712
+ color8.red(
1713
+ `\`${color8.bold(`${dir}/${category.name}`)}\` could not be added because ${error}`
1714
+ )
1715
+ );
1716
+ } else {
1717
+ console.warn(
1718
+ `${VERTICAL_LINE} ${WARN} Skipped adding \`${color8.cyan(`${dir}/${category.name}`)}\` because ${error}`
1719
+ );
1720
+ }
1721
+ continue;
1722
+ }
1723
+ categories.push(category);
1724
+ }
1661
1725
  loading.stop(`Built ${color8.cyan(dirPath)}`);
1662
1726
  }
1663
- const categoriesMap = /* @__PURE__ */ new Map();
1727
+ loading.start("Checking manifest");
1728
+ const warnings = [];
1664
1729
  for (const category of categories) {
1665
- const cat = categoriesMap.get(category.name);
1666
- if (!cat) {
1667
- categoriesMap.set(category.name, category);
1668
- continue;
1730
+ for (const block of category.blocks) {
1731
+ for (const dep of block.localDependencies) {
1732
+ const [depCategoryName, depBlockName] = dep.split("/");
1733
+ const depCategory = categories.find(
1734
+ (cat) => cat.name.trim() === depCategoryName.trim()
1735
+ );
1736
+ const invalidDependencyError = () => {
1737
+ const error = `depends on ${color8.bold(dep)} which doesn't exist!`;
1738
+ if (options.errorOnWarn) {
1739
+ warnings.push(
1740
+ color8.red(`${color8.bold(`${category.name}/${block.name}`)} ${error}`)
1741
+ );
1742
+ } else {
1743
+ warnings.push(
1744
+ `${VERTICAL_LINE} ${WARN} ${color8.bold(`${category.name}/${block.name}`)} ${error}`
1745
+ );
1746
+ }
1747
+ };
1748
+ if (!depCategory) {
1749
+ invalidDependencyError();
1750
+ continue;
1751
+ }
1752
+ if (depCategory.blocks.find((b) => b.name === depBlockName) === void 0) {
1753
+ invalidDependencyError();
1754
+ }
1755
+ }
1756
+ for (const dep of [...block.dependencies, ...block.devDependencies]) {
1757
+ if (!dep.includes("@")) {
1758
+ const error = `You haven't installed ${color8.bold(dep)} as a dependency so your users could get any version of it when they install your block!`;
1759
+ if (options.errorOnWarn) {
1760
+ warnings.push(color8.red(error));
1761
+ } else {
1762
+ warnings.push(`${VERTICAL_LINE} ${WARN} ${error}`);
1763
+ }
1764
+ }
1765
+ }
1766
+ }
1767
+ }
1768
+ loading.stop("Completed checking manifest.");
1769
+ if (warnings.length > 0) {
1770
+ for (const warning of warnings) {
1771
+ console.log(warning);
1772
+ }
1773
+ if (options.errorOnWarn) {
1774
+ program3.error("Had warnings while checking manifest.");
1669
1775
  }
1670
- cat.blocks = [...cat.blocks, ...category.blocks];
1671
- categoriesMap.set(cat.name, cat);
1672
1776
  }
1673
1777
  if (options.output) {
1778
+ loading.start(`Writing output to \`${color8.cyan(outFile)}\``);
1674
1779
  fs7.writeFileSync(outFile, JSON.stringify(categories, null, " "));
1675
- } else {
1676
- loading.stop("Built successfully!");
1780
+ loading.stop(`Wrote output to \`${color8.cyan(outFile)}\``);
1677
1781
  }
1678
1782
  };
1679
1783
 
@@ -1681,7 +1785,7 @@ var _build = async (options) => {
1681
1785
  import fs8 from "node:fs";
1682
1786
  import { cancel as cancel2, confirm as confirm2, isCancel as isCancel2, outro as outro3, spinner as spinner4 } from "@clack/prompts";
1683
1787
  import color10 from "chalk";
1684
- import { Command as Command3, program as program3 } from "commander";
1788
+ import { Command as Command3, program as program4 } from "commander";
1685
1789
  import { diffLines } from "diff";
1686
1790
  import path8 from "pathe";
1687
1791
  import * as v7 from "valibot";
@@ -1901,7 +2005,7 @@ var _diff = async (options) => {
1901
2005
  const loading = spinner4();
1902
2006
  const config = getConfig(options.cwd).match(
1903
2007
  (val) => val,
1904
- (err) => program3.error(color10.red(err))
2008
+ (err) => program4.error(color10.red(err))
1905
2009
  );
1906
2010
  let repoPaths = config.repos;
1907
2011
  if (options.repo) repoPaths = [options.repo];
@@ -1920,7 +2024,7 @@ var _diff = async (options) => {
1920
2024
  (val) => val,
1921
2025
  ({ repo, message }) => {
1922
2026
  loading.stop(`Failed fetching blocks from ${color10.cyan(repo)}`);
1923
- program3.error(color10.red(message));
2027
+ program4.error(color10.red(message));
1924
2028
  }
1925
2029
  );
1926
2030
  loading.stop(`Retrieved blocks from ${color10.cyan(repoPaths.join(", "))}`);
@@ -1946,7 +2050,7 @@ var _diff = async (options) => {
1946
2050
  const rawUrl = await providerInfo.provider.resolveRaw(providerInfo, sourcePath);
1947
2051
  const response = await fetch(rawUrl);
1948
2052
  if (!response.ok) {
1949
- program3.error(color10.red(`There was an error trying to get ${fullSpecifier}`));
2053
+ program4.error(color10.red(`There was an error trying to get ${fullSpecifier}`));
1950
2054
  }
1951
2055
  let remoteContent = await response.text();
1952
2056
  const directory = path8.join(options.cwd, config.path, block.category);
@@ -1999,7 +2103,7 @@ ${prefix?.() ?? ""}
1999
2103
  break;
2000
2104
  }
2001
2105
  if (!found) {
2002
- program3.error(
2106
+ program4.error(
2003
2107
  color10.red(`Invalid block! ${color10.bold(blockSpecifier)} does not exist!`)
2004
2108
  );
2005
2109
  }
@@ -2010,7 +2114,7 @@ ${prefix?.() ?? ""}
2010
2114
  import fs9 from "node:fs";
2011
2115
  import { cancel as cancel3, confirm as confirm3, isCancel as isCancel3, outro as outro4, select, spinner as spinner5, text } from "@clack/prompts";
2012
2116
  import color11 from "chalk";
2013
- import { Command as Command4, program as program4 } from "commander";
2117
+ import { Command as Command4, program as program5 } from "commander";
2014
2118
  import { detect as detect2, resolveCommand as resolveCommand3 } from "package-manager-detector";
2015
2119
  import path9 from "pathe";
2016
2120
  import * as v8 from "valibot";
@@ -2032,7 +2136,7 @@ var init = new Command4("init").description("Initializes your project with a con
2032
2136
  const options = v8.parse(schema5, opts);
2033
2137
  _intro(context.package.version);
2034
2138
  if (options.registry !== void 0 && options.project !== void 0) {
2035
- program4.error(
2139
+ program5.error(
2036
2140
  color11.red(
2037
2141
  `You cannot provide both ${color11.bold("--project")} and ${color11.bold("--registry")} at the same time.`
2038
2142
  )
@@ -2139,7 +2243,7 @@ var _initRegistry = async (options) => {
2139
2243
  }
2140
2244
  const packagePath = path9.join(options.cwd, "package.json");
2141
2245
  if (!fs9.existsSync(packagePath)) {
2142
- program4.error(color11.red(`Couldn't find your ${color11.bold("package.json")}!`));
2246
+ program5.error(color11.red(`Couldn't find your ${color11.bold("package.json")}!`));
2143
2247
  }
2144
2248
  const pkg = JSON.parse(fs9.readFileSync(packagePath).toString());
2145
2249
  const scriptAlreadyExists = pkg.scripts !== void 0 && pkg.scripts[options.script] !== void 0;
@@ -2188,7 +2292,7 @@ var _initRegistry = async (options) => {
2188
2292
  buildScript += "jsrepo build ";
2189
2293
  } else {
2190
2294
  const command = resolveCommand3(pm, "execute", ["jsrepo", "build"]);
2191
- if (!command) program4.error(color11.red(`Error resolving execute command for ${pm}`));
2295
+ if (!command) program5.error(color11.red(`Error resolving execute command for ${pm}`));
2192
2296
  buildScript += `${command.command} ${command.args.join(" ")} `;
2193
2297
  }
2194
2298
  if (options.path !== "./build") {
@@ -2202,7 +2306,7 @@ var _initRegistry = async (options) => {
2202
2306
  try {
2203
2307
  fs9.writeFileSync(packagePath, JSON.stringify(pkg, null, " "));
2204
2308
  } catch (err) {
2205
- program4.error(color11.red(`Error writing to \`${color11.bold(packagePath)}\`. Error: ${err}`));
2309
+ program5.error(color11.red(`Error writing to \`${color11.bold(packagePath)}\`. Error: ${err}`));
2206
2310
  }
2207
2311
  loading.stop(`Added \`${color11.cyan(options.script)}\` to scripts in package.json`);
2208
2312
  let installed = alreadyInstalled;
@@ -2231,7 +2335,7 @@ var _initRegistry = async (options) => {
2231
2335
  () => loading.stop(`Installed ${JSREPO}.`),
2232
2336
  (err) => {
2233
2337
  loading.stop(`Failed to install ${JSREPO}.`);
2234
- program4.error(err);
2338
+ program5.error(err);
2235
2339
  }
2236
2340
  );
2237
2341
  installed = true;
@@ -2258,7 +2362,7 @@ var _initRegistry = async (options) => {
2258
2362
  import fs10 from "node:fs";
2259
2363
  import { cancel as cancel4, confirm as confirm4, isCancel as isCancel4, outro as outro5, spinner as spinner6 } from "@clack/prompts";
2260
2364
  import color12 from "chalk";
2261
- import { Argument, Command as Command5, program as program5 } from "commander";
2365
+ import { Argument, Command as Command5, program as program6 } from "commander";
2262
2366
  import { execa as execa2 } from "execa";
2263
2367
  import { resolveCommand as resolveCommand4 } from "package-manager-detector/commands";
2264
2368
  import { detect as detect3 } from "package-manager-detector/detect";
@@ -2287,7 +2391,7 @@ var _test = async (blockNames, options) => {
2287
2391
  verbose(`Attempting to test ${JSON.stringify(blockNames)}`);
2288
2392
  const config = getConfig(options.cwd).match(
2289
2393
  (val) => val,
2290
- (err) => program5.error(color12.red(err))
2394
+ (err) => program6.error(color12.red(err))
2291
2395
  );
2292
2396
  const loading = spinner6();
2293
2397
  const blocksMap = /* @__PURE__ */ new Map();
@@ -2308,14 +2412,14 @@ var _test = async (blockNames, options) => {
2308
2412
  for (const repo of repoPaths) {
2309
2413
  const providerInfo = (await getProviderInfo(repo)).match(
2310
2414
  (info) => info,
2311
- (err) => program5.error(color12.red(err))
2415
+ (err) => program6.error(color12.red(err))
2312
2416
  );
2313
2417
  const manifestUrl = await providerInfo.provider.resolveRaw(providerInfo, OUTPUT_FILE);
2314
2418
  verbose(`Got info for provider ${color12.cyan(providerInfo.name)}`);
2315
2419
  const response = await fetch(manifestUrl);
2316
2420
  if (!response.ok) {
2317
2421
  if (!options.verbose) loading.stop(`Error fetching ${color12.cyan(manifestUrl.href)}`);
2318
- program5.error(
2422
+ program6.error(
2319
2423
  color12.red(
2320
2424
  `There was an error fetching the \`${OUTPUT_FILE}\` from the repository ${color12.cyan(
2321
2425
  repo
@@ -2355,7 +2459,7 @@ var _test = async (blockNames, options) => {
2355
2459
  }
2356
2460
  if (testingBlocks.length === 0) {
2357
2461
  cleanUp();
2358
- program5.error(color12.red("There were no blocks found in your project!"));
2462
+ program6.error(color12.red("There were no blocks found in your project!"));
2359
2463
  }
2360
2464
  const testingBlocksMapped = [];
2361
2465
  for (const blockSpecifier of testingBlocks) {
@@ -2381,7 +2485,7 @@ var _test = async (blockNames, options) => {
2381
2485
  }
2382
2486
  const providerInfo = (await getProviderInfo(repo)).match(
2383
2487
  (val) => val,
2384
- (err) => program5.error(color12.red(err))
2488
+ (err) => program6.error(color12.red(err))
2385
2489
  );
2386
2490
  const manifestUrl = await providerInfo.provider.resolveRaw(
2387
2491
  providerInfo,
@@ -2389,7 +2493,7 @@ var _test = async (blockNames, options) => {
2389
2493
  );
2390
2494
  const categories = (await getManifest(manifestUrl)).match(
2391
2495
  (val) => val,
2392
- (err) => program5.error(color12.red(err))
2496
+ (err) => program6.error(color12.red(err))
2393
2497
  );
2394
2498
  for (const category of categories) {
2395
2499
  for (const block2 of category.blocks) {
@@ -2406,7 +2510,7 @@ var _test = async (blockNames, options) => {
2406
2510
  block = blocksMap.get(blockSpecifier);
2407
2511
  }
2408
2512
  if (!block) {
2409
- program5.error(
2513
+ program6.error(
2410
2514
  color12.red(`Invalid block! ${color12.bold(blockSpecifier)} does not exist!`)
2411
2515
  );
2412
2516
  }
@@ -2426,7 +2530,7 @@ var _test = async (blockNames, options) => {
2426
2530
  const response = await fetch(rawUrl);
2427
2531
  if (!response.ok) {
2428
2532
  loading.stop(color12.red(`Error fetching ${color12.bold(rawUrl.href)}`));
2429
- program5.error(color12.red(`There was an error trying to get ${specifier}`));
2533
+ program6.error(color12.red(`There was an error trying to get ${specifier}`));
2430
2534
  }
2431
2535
  return await response.text();
2432
2536
  };
@@ -2477,11 +2581,11 @@ var _test = async (blockNames, options) => {
2477
2581
  verbose("Beginning testing");
2478
2582
  const pm = await detect3({ cwd: options.cwd });
2479
2583
  if (pm == null) {
2480
- program5.error(color12.red("Could not detect package manager"));
2584
+ program6.error(color12.red("Could not detect package manager"));
2481
2585
  }
2482
2586
  const resolved = resolveCommand4(pm.agent, "execute", ["vitest", "run", tempTestDirectory]);
2483
2587
  if (resolved == null) {
2484
- program5.error(color12.red(`Could not resolve add command for '${pm.agent}'.`));
2588
+ program6.error(color12.red(`Could not resolve add command for '${pm.agent}'.`));
2485
2589
  }
2486
2590
  const { command, args } = resolved;
2487
2591
  const testCommand = `${command} ${args.join(" ")}`;
@@ -2506,7 +2610,7 @@ var _test = async (blockNames, options) => {
2506
2610
  } else {
2507
2611
  cleanUp();
2508
2612
  }
2509
- program5.error(color12.red(`Tests failed! Error ${err}`));
2613
+ program6.error(color12.red(`Tests failed! Error ${err}`));
2510
2614
  }
2511
2615
  };
2512
2616
 
@@ -2514,7 +2618,7 @@ var _test = async (blockNames, options) => {
2514
2618
  import fs11 from "node:fs";
2515
2619
  import { cancel as cancel5, confirm as confirm5, isCancel as isCancel5, multiselect as multiselect2, outro as outro6, spinner as spinner7 } from "@clack/prompts";
2516
2620
  import color13 from "chalk";
2517
- import { Command as Command6, program as program6 } from "commander";
2621
+ import { Command as Command6, program as program7 } from "commander";
2518
2622
  import { diffLines as diffLines2 } from "diff";
2519
2623
  import { resolveCommand as resolveCommand5 } from "package-manager-detector/commands";
2520
2624
  import { detect as detect4 } from "package-manager-detector/detect";
@@ -2552,13 +2656,13 @@ var _update = async (blockNames, options) => {
2552
2656
  const loading = spinner7();
2553
2657
  const config = getConfig(options.cwd).match(
2554
2658
  (val) => val,
2555
- (err) => program6.error(color13.red(err))
2659
+ (err) => program7.error(color13.red(err))
2556
2660
  );
2557
2661
  let repoPaths = config.repos;
2558
2662
  if (options.repo) repoPaths = [options.repo];
2559
2663
  for (const blockSpecifier of blockNames) {
2560
2664
  if (blockSpecifier.startsWith("github")) {
2561
- program6.error(
2665
+ program7.error(
2562
2666
  color13.red(
2563
2667
  `Invalid value provided for block names \`${color13.bold(blockSpecifier)}\`. Block names are expected to be provided in the format of \`${color13.bold("<category>/<name>")}\``
2564
2668
  )
@@ -2581,7 +2685,7 @@ var _update = async (blockNames, options) => {
2581
2685
  (val) => val,
2582
2686
  ({ repo, message }) => {
2583
2687
  loading.stop(`Failed fetching blocks from ${color13.cyan(repo)}`);
2584
- program6.error(color13.red(message));
2688
+ program7.error(color13.red(message));
2585
2689
  }
2586
2690
  );
2587
2691
  if (!options.verbose) loading.stop(`Retrieved blocks from ${color13.cyan(repoPaths.join(", "))}`);
@@ -2611,7 +2715,7 @@ var _update = async (blockNames, options) => {
2611
2715
  verbose(`Preparing to update ${color13.cyan(updatingBlockNames.join(", "))}`);
2612
2716
  const updatingBlocks = (await resolveTree(updatingBlockNames, blocksMap, repoPaths)).match(
2613
2717
  (val) => val,
2614
- program6.error
2718
+ program7.error
2615
2719
  );
2616
2720
  const pm = (await detect4({ cwd: options.cwd }))?.agent ?? "npm";
2617
2721
  const tasks = [];
@@ -2629,7 +2733,7 @@ var _update = async (blockNames, options) => {
2629
2733
  const response = await fetch(rawUrl);
2630
2734
  if (!response.ok) {
2631
2735
  loading.stop(color13.red(`Error fetching ${color13.bold(rawUrl.href)}`));
2632
- program6.error(color13.red(`There was an error trying to get ${fullSpecifier}`));
2736
+ program7.error(color13.red(`There was an error trying to get ${fullSpecifier}`));
2633
2737
  }
2634
2738
  return await response.text();
2635
2739
  };
@@ -2772,7 +2876,7 @@ ${prefix?.() ?? ""}
2772
2876
  },
2773
2877
  (err) => {
2774
2878
  if (!options.verbose) loading.stop("Failed to install dependencies");
2775
- program6.error(err);
2879
+ program7.error(err);
2776
2880
  }
2777
2881
  );
2778
2882
  }
@@ -2791,7 +2895,7 @@ ${prefix?.() ?? ""}
2791
2895
  },
2792
2896
  (err) => {
2793
2897
  if (!options.verbose) loading.stop("Failed to install dev dependencies");
2794
- program6.error(err);
2898
+ program7.error(err);
2795
2899
  }
2796
2900
  );
2797
2901
  }
@@ -2838,8 +2942,8 @@ var context = {
2838
2942
  },
2839
2943
  resolveRelativeToRoot
2840
2944
  };
2841
- program7.name(name).description(description).version(version).addCommand(add).addCommand(init).addCommand(test).addCommand(build).addCommand(update).addCommand(diff);
2842
- program7.parse();
2945
+ program8.name(name).description(description).version(version).addCommand(add).addCommand(init).addCommand(test).addCommand(build).addCommand(update).addCommand(diff);
2946
+ program8.parse();
2843
2947
  export {
2844
2948
  context
2845
2949
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "jsrepo",
3
3
  "description": "A CLI to add shared code from remote repositories.",
4
- "version": "1.5.0",
4
+ "version": "1.6.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/ieedan/jsrepo"
@@ -1,18 +1,22 @@
1
1
  import fs from 'node:fs';
2
2
  import { outro, spinner } from '@clack/prompts';
3
3
  import color from 'chalk';
4
- import { Command } from 'commander';
4
+ import { Command, program } from 'commander';
5
5
  import path from 'pathe';
6
6
  import * as v from 'valibot';
7
7
  import { context } from '..';
8
+ import * as ascii from '../utils/ascii';
8
9
  import { type Category, buildBlocksDirectory } from '../utils/build';
9
10
  import { OUTPUT_FILE } from '../utils/context';
10
11
  import { intro } from '../utils/prompts';
11
12
 
12
13
  const schema = v.object({
13
14
  dirs: v.array(v.string()),
15
+ includeBlocks: v.array(v.string()),
16
+ includeCategories: v.array(v.string()),
14
17
  excludeDeps: v.array(v.string()),
15
18
  output: v.boolean(),
19
+ errorOnWarn: v.boolean(),
16
20
  verbose: v.boolean(),
17
21
  cwd: v.string(),
18
22
  });
@@ -22,8 +26,19 @@ type Options = v.InferInput<typeof schema>;
22
26
  const build = new Command('build')
23
27
  .description(`Builds the provided --dirs in the project root into a \`${OUTPUT_FILE}\` file.`)
24
28
  .option('--dirs [dirs...]', 'The directories containing the blocks.', ['./blocks'])
29
+ .option('--include-blocks [blockNames...]', 'Include only the blocks with these names.', [])
30
+ .option(
31
+ '--include-categories [categoryNames...]',
32
+ 'Include only the categories with these names.',
33
+ []
34
+ )
25
35
  .option('--exclude-deps [deps...]', 'Dependencies that should not be added.', [])
26
36
  .option('--no-output', `Do not output a \`${OUTPUT_FILE}\` file.`)
37
+ .option(
38
+ '--error-on-warn',
39
+ 'If there is a warning throw an error and do not allow build to complete.',
40
+ false
41
+ )
27
42
  .option('--verbose', 'Include debug logs.', false)
28
43
  .option('--cwd <path>', 'The current working directory.', process.cwd())
29
44
  .action(async (opts) => {
@@ -50,33 +65,102 @@ const _build = async (options: Options) => {
50
65
 
51
66
  if (options.output && fs.existsSync(outFile)) fs.rmSync(outFile);
52
67
 
53
- categories.push(
54
- ...buildBlocksDirectory(dirPath, { cwd: options.cwd, excludeDeps: options.excludeDeps })
55
- );
68
+ const builtCategories = buildBlocksDirectory(dirPath, { ...options });
69
+
70
+ for (const category of builtCategories) {
71
+ if (categories.find((cat) => cat.name === category.name) !== undefined) {
72
+ const error = 'a category with the same name already exists!';
73
+
74
+ if (options.errorOnWarn) {
75
+ program.error(
76
+ color.red(
77
+ `\`${color.bold(`${dir}/${category.name}`)}\` could not be added because ${error}`
78
+ )
79
+ );
80
+ } else {
81
+ console.warn(
82
+ `${ascii.VERTICAL_LINE} ${ascii.WARN} Skipped adding \`${color.cyan(`${dir}/${category.name}`)}\` because ${error}`
83
+ );
84
+ }
85
+ continue;
86
+ }
87
+
88
+ categories.push(category);
89
+ }
56
90
 
57
91
  loading.stop(`Built ${color.cyan(dirPath)}`);
58
92
  }
59
93
 
60
- const categoriesMap = new Map<string, Category>();
94
+ loading.start('Checking manifest');
61
95
 
62
- for (const category of categories) {
63
- const cat = categoriesMap.get(category.name);
96
+ const warnings: string[] = [];
64
97
 
65
- if (!cat) {
66
- categoriesMap.set(category.name, category);
67
- continue;
98
+ for (const category of categories) {
99
+ for (const block of category.blocks) {
100
+ // lookup local deps
101
+ for (const dep of block.localDependencies) {
102
+ const [depCategoryName, depBlockName] = dep.split('/');
103
+
104
+ const depCategory = categories.find(
105
+ (cat) => cat.name.trim() === depCategoryName.trim()
106
+ );
107
+
108
+ const invalidDependencyError = () => {
109
+ const error = `depends on ${color.bold(dep)} which doesn't exist!`;
110
+
111
+ if (options.errorOnWarn) {
112
+ warnings.push(
113
+ color.red(`${color.bold(`${category.name}/${block.name}`)} ${error}`)
114
+ );
115
+ } else {
116
+ warnings.push(
117
+ `${ascii.VERTICAL_LINE} ${ascii.WARN} ${color.bold(`${category.name}/${block.name}`)} ${error}`
118
+ );
119
+ }
120
+ };
121
+
122
+ if (!depCategory) {
123
+ invalidDependencyError();
124
+ continue;
125
+ }
126
+
127
+ if (depCategory.blocks.find((b) => b.name === depBlockName) === undefined) {
128
+ invalidDependencyError();
129
+ }
130
+ }
131
+
132
+ for (const dep of [...block.dependencies, ...block.devDependencies]) {
133
+ if (!dep.includes('@')) {
134
+ const error = `You haven't installed ${color.bold(dep)} as a dependency so your users could get any version of it when they install your block!`;
135
+
136
+ if (options.errorOnWarn) {
137
+ warnings.push(color.red(error));
138
+ } else {
139
+ warnings.push(`${ascii.VERTICAL_LINE} ${ascii.WARN} ${error}`);
140
+ }
141
+ }
142
+ }
68
143
  }
144
+ }
69
145
 
70
- // we aren't going to merge blocks hopefully people are smart enough not to overlap names
71
- cat.blocks = [...cat.blocks, ...category.blocks];
146
+ loading.stop('Completed checking manifest.');
72
147
 
73
- categoriesMap.set(cat.name, cat);
148
+ if (warnings.length > 0) {
149
+ for (const warning of warnings) {
150
+ console.log(warning);
151
+ }
152
+
153
+ if (options.errorOnWarn) {
154
+ program.error('Had warnings while checking manifest.');
155
+ }
74
156
  }
75
157
 
76
158
  if (options.output) {
159
+ loading.start(`Writing output to \`${color.cyan(outFile)}\``);
160
+
77
161
  fs.writeFileSync(outFile, JSON.stringify(categories, null, '\t'));
78
- } else {
79
- loading.stop('Built successfully!');
162
+
163
+ loading.stop(`Wrote output to \`${color.cyan(outFile)}\``);
80
164
  }
81
165
  };
82
166
 
@@ -36,6 +36,9 @@ const isTestFile = (file: string): boolean =>
36
36
  type Options = {
37
37
  cwd: string;
38
38
  excludeDeps: string[];
39
+ includeBlocks: string[];
40
+ includeCategories: string[];
41
+ errorOnWarn: boolean;
39
42
  };
40
43
 
41
44
  /** Using the provided path to the blocks folder builds the blocks into categories and also resolves dependencies
@@ -43,7 +46,10 @@ type Options = {
43
46
  * @param blocksPath
44
47
  * @returns
45
48
  */
46
- const buildBlocksDirectory = (blocksPath: string, { cwd, excludeDeps }: Options): Category[] => {
49
+ const buildBlocksDirectory = (
50
+ blocksPath: string,
51
+ { cwd, excludeDeps, includeBlocks, includeCategories, errorOnWarn }: Options
52
+ ): Category[] => {
47
53
  let paths: string[];
48
54
 
49
55
  try {
@@ -61,6 +67,13 @@ const buildBlocksDirectory = (blocksPath: string, { cwd, excludeDeps }: Options)
61
67
 
62
68
  const categoryName = path.basename(categoryPath);
63
69
 
70
+ // if includeCategories enabled and block is not part of includeCategories skip adding it
71
+ if (
72
+ includeCategories.length > 0 &&
73
+ includeCategories.find((val) => val.trim() === categoryName.trim()) === undefined
74
+ )
75
+ continue;
76
+
64
77
  const category: Category = {
65
78
  name: categoryName,
66
79
  blocks: [],
@@ -74,19 +87,39 @@ const buildBlocksDirectory = (blocksPath: string, { cwd, excludeDeps }: Options)
74
87
  if (fs.statSync(blockDir).isFile()) {
75
88
  if (isTestFile(file)) continue;
76
89
 
90
+ const name = path.parse(path.basename(file)).name;
91
+
92
+ // if includeBlocks enabled and block is not part of includeBlocks skip adding it
93
+ if (
94
+ includeBlocks.length > 0 &&
95
+ includeBlocks.find((val) => val.trim() === name.trim()) === undefined
96
+ )
97
+ continue;
98
+
77
99
  const lang = languages.find((resolver) => resolver.matches(file));
78
100
 
79
101
  if (!lang) {
80
- console.warn(
81
- `${ascii.WARN} Skipped \`${color.bold(blockDir)}\` \`${color.bold(
82
- path.parse(file).ext
83
- )}\` files are not currently supported!`
84
- );
102
+ const error = 'files are not currently supported!';
103
+
104
+ if (errorOnWarn) {
105
+ program.error(
106
+ color.red(
107
+ `Couldn't add \`${color.bold(blockDir)}\` \`*${color.bold(
108
+ path.parse(file).ext
109
+ )}\` ${error}`
110
+ )
111
+ );
112
+ } else {
113
+ console.warn(
114
+ `${ascii.VERTICAL_LINE} ${ascii.WARN} Skipped \`${color.bold(blockDir)}\` \`*${color.bold(
115
+ path.parse(file).ext
116
+ )}\` ${error}`
117
+ );
118
+ }
119
+
85
120
  continue;
86
121
  }
87
122
 
88
- const name = path.parse(path.basename(file)).name;
89
-
90
123
  // tries to find a test file with the same name as the file
91
124
  const testsPath = files.find((f) =>
92
125
  TEST_SUFFIXES.find((suffix) => f === `${name}${suffix}`)
@@ -126,6 +159,13 @@ const buildBlocksDirectory = (blocksPath: string, { cwd, excludeDeps }: Options)
126
159
  } else {
127
160
  const blockName = file;
128
161
 
162
+ // if includeBlocks enabled and block is not part of includeBlocks skip adding it
163
+ if (
164
+ includeBlocks.length > 0 &&
165
+ includeBlocks.find((val) => val.trim() === blockName.trim()) === undefined
166
+ )
167
+ continue;
168
+
129
169
  const blockFiles = fs.readdirSync(blockDir);
130
170
 
131
171
  const hasTests = blockFiles.findIndex((f) => isTestFile(f)) !== -1;
@@ -139,20 +179,42 @@ const buildBlocksDirectory = (blocksPath: string, { cwd, excludeDeps }: Options)
139
179
  if (isTestFile(f)) continue;
140
180
 
141
181
  if (fs.statSync(path.join(blockDir, f)).isDirectory()) {
142
- console.warn(
143
- `${ascii.VERTICAL_LINE} ${ascii.WARN} Skipped \`${color.bold(path.join(blockDir, f))}\` subdirectories are not currently supported!`
144
- );
182
+ const error = 'subdirectories are not currently supported!';
183
+
184
+ if (errorOnWarn) {
185
+ program.error(
186
+ color.red(
187
+ `Couldn't add \`${color.bold(path.join(blockDir, f))}\` ${error}`
188
+ )
189
+ );
190
+ } else {
191
+ console.warn(
192
+ `${ascii.VERTICAL_LINE} ${ascii.WARN} Skipped \`${color.bold(path.join(blockDir, f))}\` ${error}`
193
+ );
194
+ }
145
195
  continue;
146
196
  }
147
197
 
148
198
  const lang = languages.find((resolver) => resolver.matches(f));
149
199
 
150
200
  if (!lang) {
151
- console.warn(
152
- `${ascii.VERTICAL_LINE} ${ascii.WARN} Skipped \`${color.bold(path.join(blockDir, f))}\` \`*${color.bold(
153
- path.parse(f).ext
154
- )}\` files are not currently supported!`
155
- );
201
+ const error = 'files are not currently supported!';
202
+
203
+ if (errorOnWarn) {
204
+ program.error(
205
+ color.red(
206
+ `Couldn't add \`${color.bold(path.join(blockDir, f))}\` \`*${color.bold(
207
+ path.parse(f).ext
208
+ )}\` ${error}`
209
+ )
210
+ );
211
+ } else {
212
+ console.warn(
213
+ `${ascii.VERTICAL_LINE} ${ascii.WARN} Skipped \`${path.join(blockDir, f)}\` \`*${color.bold(
214
+ path.parse(f).ext
215
+ )}\` ${error}`
216
+ );
217
+ }
156
218
  continue;
157
219
  }
158
220