jsrepo 1.3.1 → 1.4.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
@@ -12,7 +12,7 @@ import color7 from "chalk";
12
12
  import { Command, program as program2 } from "commander";
13
13
  import { resolveCommand as resolveCommand2 } from "package-manager-detector/commands";
14
14
  import { detect } from "package-manager-detector/detect";
15
- import * as v4 from "valibot";
15
+ import * as v5 from "valibot";
16
16
 
17
17
  // src/utils/ascii.ts
18
18
  import color from "chalk";
@@ -680,19 +680,20 @@ var mapToArray = (map, fn) => {
680
680
 
681
681
  // src/utils/git-providers.ts
682
682
  import { Octokit } from "octokit";
683
- import * as v2 from "valibot";
683
+ import * as v3 from "valibot";
684
684
 
685
685
  // src/utils/build.ts
686
686
  import fs3 from "node:fs";
687
687
  import path3 from "node:path";
688
688
  import color3 from "chalk";
689
689
  import { program } from "commander";
690
- import * as v from "valibot";
690
+ import * as v2 from "valibot";
691
691
 
692
692
  // src/utils/language-support.ts
693
693
  import fs2 from "node:fs";
694
694
  import { builtinModules } from "node:module";
695
695
  import path2 from "node:path";
696
+ import * as v from "@vue/compiler-sfc";
696
697
  import color2 from "chalk";
697
698
  import { walk } from "estree-walker";
698
699
  import * as sv from "svelte/compiler";
@@ -726,7 +727,7 @@ var parsePackageName = (input) => {
726
727
  // src/utils/language-support.ts
727
728
  var typescript = {
728
729
  matches: (fileName) => fileName.endsWith(".ts") || fileName.endsWith(".js") || fileName.endsWith(".tsx") || fileName.endsWith(".jsx"),
729
- resolveDependencies: (filePath, category, isSubDir) => {
730
+ resolveDependencies: ({ filePath, category, isSubDir, excludeDeps }) => {
730
731
  const project = new Project();
731
732
  const blockFile = project.addSourceFileAtPath(filePath);
732
733
  const imports = blockFile.getImportDeclarations();
@@ -740,7 +741,11 @@ var typescript = {
740
741
  if (localDep) localDeps.add(localDep);
741
742
  }
742
743
  const deps = imports.filter((declaration) => !declaration.getModuleSpecifierValue().startsWith(".")).map((declaration) => declaration.getModuleSpecifierValue());
743
- const { devDependencies, dependencies } = resolveRemoteDeps(Array.from(deps), filePath);
744
+ const { devDependencies, dependencies } = resolveRemoteDeps(
745
+ Array.from(deps),
746
+ filePath,
747
+ excludeDeps
748
+ );
744
749
  return Ok({
745
750
  local: Array.from(localDeps),
746
751
  dependencies,
@@ -753,7 +758,7 @@ ${content}
753
758
  };
754
759
  var svelte = {
755
760
  matches: (fileName) => fileName.endsWith(".svelte"),
756
- resolveDependencies: (filePath, category, isSubDir) => {
761
+ resolveDependencies: ({ filePath, category, isSubDir, excludeDeps }) => {
757
762
  const sourceCode = fs2.readFileSync(filePath).toString();
758
763
  const root = sv.parse(sourceCode, { modern: true });
759
764
  if (!root.instance) return Ok({ dependencies: [], devDependencies: [], local: [] });
@@ -777,7 +782,44 @@ var svelte = {
777
782
  }
778
783
  }
779
784
  });
780
- const { devDependencies, dependencies } = resolveRemoteDeps(Array.from(deps), filePath);
785
+ const { devDependencies, dependencies } = resolveRemoteDeps(Array.from(deps), filePath, [
786
+ "svelte",
787
+ ...excludeDeps
788
+ ]);
789
+ return Ok({
790
+ dependencies,
791
+ devDependencies,
792
+ local: Array.from(localDeps)
793
+ });
794
+ },
795
+ comment: (content) => `<!--
796
+ ${content}
797
+ -->`
798
+ };
799
+ var vue = {
800
+ matches: (fileName) => fileName.endsWith(".vue"),
801
+ resolveDependencies: ({ filePath, category, isSubDir, excludeDeps }) => {
802
+ const sourceCode = fs2.readFileSync(filePath).toString();
803
+ const parsed = v.parse(sourceCode);
804
+ if (!parsed.descriptor.script?.content && !parsed.descriptor.scriptSetup?.content)
805
+ return Ok({ dependencies: [], devDependencies: [], local: [] });
806
+ const localDeps = /* @__PURE__ */ new Set();
807
+ const deps = /* @__PURE__ */ new Set();
808
+ const compiled = v.compileScript(parsed.descriptor, { id: "shut-it" });
809
+ if (!compiled.imports) return Ok({ dependencies: [], devDependencies: [], local: [] });
810
+ const imports = Object.values(compiled.imports);
811
+ for (const imp of imports) {
812
+ if (imp.source.startsWith(".")) {
813
+ const localDep = resolveLocalImport(imp.source, category, isSubDir);
814
+ if (localDep) localDeps.add(localDep);
815
+ } else {
816
+ deps.add(imp.source);
817
+ }
818
+ }
819
+ const { devDependencies, dependencies } = resolveRemoteDeps(Array.from(deps), filePath, [
820
+ "vue",
821
+ ...excludeDeps
822
+ ]);
781
823
  return Ok({
782
824
  dependencies,
783
825
  devDependencies,
@@ -797,10 +839,11 @@ var resolveLocalImport = (mod, category, isSubDir) => {
797
839
  return `${category}/${path2.parse(path2.basename(mod)).name}`;
798
840
  }
799
841
  const segments = mod.replaceAll("../", "").split("/");
800
- if (segments.length !== 2) return void 0;
842
+ if (segments.length < 2) return void 0;
801
843
  return `${segments[0]}/${segments[1]}`;
802
844
  };
803
- var resolveRemoteDeps = (deps, filePath) => {
845
+ var resolveRemoteDeps = (deps, filePath, doNotInstall = []) => {
846
+ const exemptDeps = new Set(doNotInstall);
804
847
  const filteredDeps = deps.filter(
805
848
  (dep) => !builtinModules.includes(dep) && !dep.startsWith("node:")
806
849
  );
@@ -824,6 +867,7 @@ var resolveRemoteDeps = (deps, filePath) => {
824
867
  );
825
868
  continue;
826
869
  }
870
+ if (exemptDeps.has(depInfo.name)) continue;
827
871
  let version2 = void 0;
828
872
  if (packageDependencies !== void 0) {
829
873
  version2 = packageDependencies[depInfo.name];
@@ -847,28 +891,28 @@ var resolveRemoteDeps = (deps, filePath) => {
847
891
  devDependencies: Array.from(devDependencies)
848
892
  };
849
893
  };
850
- var languages = [typescript, svelte];
894
+ var languages = [typescript, svelte, vue];
851
895
 
852
896
  // src/utils/build.ts
853
- var blockSchema = v.object({
854
- name: v.string(),
855
- category: v.string(),
856
- localDependencies: v.array(v.string()),
857
- dependencies: v.array(v.string()),
858
- devDependencies: v.array(v.string()),
859
- tests: v.boolean(),
897
+ var blockSchema = v2.object({
898
+ name: v2.string(),
899
+ category: v2.string(),
900
+ localDependencies: v2.array(v2.string()),
901
+ dependencies: v2.array(v2.string()),
902
+ devDependencies: v2.array(v2.string()),
903
+ tests: v2.boolean(),
860
904
  /** Where to find the block relative to root */
861
- directory: v.string(),
862
- subdirectory: v.boolean(),
863
- files: v.array(v.string())
905
+ directory: v2.string(),
906
+ subdirectory: v2.boolean(),
907
+ files: v2.array(v2.string())
864
908
  });
865
- var categorySchema = v.object({
866
- name: v.string(),
867
- blocks: v.array(blockSchema)
909
+ var categorySchema = v2.object({
910
+ name: v2.string(),
911
+ blocks: v2.array(blockSchema)
868
912
  });
869
913
  var TEST_SUFFIXES = [".test.ts", "_test.ts", ".test.js", "_test.js"];
870
914
  var isTestFile = (file) => TEST_SUFFIXES.find((suffix) => file.endsWith(suffix)) !== void 0;
871
- var buildBlocksDirectory = (blocksPath, cwd) => {
915
+ var buildBlocksDirectory = (blocksPath, { cwd, excludeDeps }) => {
872
916
  let paths;
873
917
  try {
874
918
  paths = fs3.readdirSync(blocksPath);
@@ -902,7 +946,12 @@ var buildBlocksDirectory = (blocksPath, cwd) => {
902
946
  const testsPath = files.find(
903
947
  (f) => TEST_SUFFIXES.find((suffix) => f === `${name2}${suffix}`)
904
948
  );
905
- const { dependencies, devDependencies, local } = lang.resolveDependencies(blockDir, categoryName, false).match(
949
+ const { dependencies, devDependencies, local } = lang.resolveDependencies({
950
+ filePath: blockDir,
951
+ category: categoryName,
952
+ isSubDir: false,
953
+ excludeDeps
954
+ }).match(
906
955
  (val) => val,
907
956
  (err) => {
908
957
  program.error(color3.red(err));
@@ -941,7 +990,12 @@ var buildBlocksDirectory = (blocksPath, cwd) => {
941
990
  );
942
991
  continue;
943
992
  }
944
- const { local, dependencies, devDependencies } = lang.resolveDependencies(path3.join(blockDir, f), categoryName, true).match(
993
+ const { local, dependencies, devDependencies } = lang.resolveDependencies({
994
+ filePath: path3.join(blockDir, f),
995
+ category: categoryName,
996
+ isSubDir: true,
997
+ excludeDeps
998
+ }).match(
945
999
  (val) => val,
946
1000
  (err) => {
947
1001
  program.error(color3.red(err));
@@ -1049,7 +1103,7 @@ var getManifest = async (url) => {
1049
1103
  if (!response.ok) {
1050
1104
  return errorMessage(`${response.status} ${response.text}`);
1051
1105
  }
1052
- const categories = v2.parse(v2.array(categorySchema), await response.json());
1106
+ const categories = v3.parse(v3.array(categorySchema), await response.json());
1053
1107
  return Ok(categories);
1054
1108
  } catch (err) {
1055
1109
  return errorMessage(`${err}`);
@@ -1147,20 +1201,20 @@ var getInstalled = (blocks, config, cwd) => {
1147
1201
  // src/utils/config.ts
1148
1202
  import fs5 from "node:fs";
1149
1203
  import path5 from "node:path";
1150
- import * as v3 from "valibot";
1204
+ import * as v4 from "valibot";
1151
1205
  var CONFIG_NAME = "jsrepo.json";
1152
- var schema = v3.object({
1153
- $schema: v3.string(),
1154
- repos: v3.optional(v3.array(v3.string()), []),
1155
- includeTests: v3.boolean(),
1156
- path: v3.pipe(v3.string(), v3.minLength(1)),
1157
- watermark: v3.optional(v3.boolean(), true)
1206
+ var schema = v4.object({
1207
+ $schema: v4.string(),
1208
+ repos: v4.optional(v4.array(v4.string()), []),
1209
+ includeTests: v4.boolean(),
1210
+ path: v4.pipe(v4.string(), v4.minLength(1)),
1211
+ watermark: v4.optional(v4.boolean(), true)
1158
1212
  });
1159
1213
  var getConfig = (cwd) => {
1160
1214
  if (!fs5.existsSync(path5.join(cwd, CONFIG_NAME))) {
1161
1215
  return Err("Could not find your configuration file! Please run `init`.");
1162
1216
  }
1163
- const config = v3.safeParse(
1217
+ const config = v4.safeParse(
1164
1218
  schema,
1165
1219
  JSON.parse(fs5.readFileSync(path5.join(cwd, CONFIG_NAME)).toString())
1166
1220
  );
@@ -1281,18 +1335,18 @@ var nextSteps = (steps) => {
1281
1335
  var _intro = (version2) => intro(`${color6.bgHex("#f7df1e").black(" jsrepo ")}${color6.gray(` v${version2} `)}`);
1282
1336
 
1283
1337
  // src/commands/add.ts
1284
- var schema2 = v4.object({
1285
- repo: v4.optional(v4.string()),
1286
- allow: v4.boolean(),
1287
- yes: v4.boolean(),
1288
- verbose: v4.boolean(),
1289
- cwd: v4.string()
1338
+ var schema2 = v5.object({
1339
+ repo: v5.optional(v5.string()),
1340
+ allow: v5.boolean(),
1341
+ yes: v5.boolean(),
1342
+ verbose: v5.boolean(),
1343
+ cwd: v5.string()
1290
1344
  });
1291
1345
  var add = new Command("add").argument(
1292
1346
  "[blocks...]",
1293
1347
  "Names of the blocks you want to add to your project. ex: (utils/math, github/ieedan/std/utils/math)"
1294
1348
  ).option("--repo <repo>", "Repository to download the blocks from.").option("-A, --allow", "Allow jsrepo to download code from the provided repo.", false).option("-y, --yes", "Skip confirmation prompt.", false).option("--verbose", "Include debug logs.", false).option("--cwd <path>", "The current working directory.", process.cwd()).action(async (blockNames, opts) => {
1295
- const options = v4.parse(schema2, opts);
1349
+ const options = v5.parse(schema2, opts);
1296
1350
  _intro(context.package.version);
1297
1351
  await _add(blockNames, options);
1298
1352
  outro(color7.green("All done!"));
@@ -1572,15 +1626,16 @@ import path7 from "node:path";
1572
1626
  import { outro as outro2, spinner as spinner3 } from "@clack/prompts";
1573
1627
  import color8 from "chalk";
1574
1628
  import { Command as Command2 } from "commander";
1575
- import * as v5 from "valibot";
1576
- var schema3 = v5.object({
1577
- dirs: v5.array(v5.string()),
1578
- output: v5.boolean(),
1579
- verbose: v5.boolean(),
1580
- cwd: v5.string()
1629
+ import * as v6 from "valibot";
1630
+ var schema3 = v6.object({
1631
+ dirs: v6.array(v6.string()),
1632
+ excludeDeps: v6.array(v6.string()),
1633
+ output: v6.boolean(),
1634
+ verbose: v6.boolean(),
1635
+ cwd: v6.string()
1581
1636
  });
1582
- 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("--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) => {
1583
- const options = v5.parse(schema3, opts);
1637
+ 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) => {
1638
+ const options = v6.parse(schema3, opts);
1584
1639
  _intro(context.package.version);
1585
1640
  await _build(options);
1586
1641
  outro2(color8.green("All done!"));
@@ -1593,7 +1648,9 @@ var _build = async (options) => {
1593
1648
  const dirPath = path7.join(options.cwd, dir);
1594
1649
  loading.start(`Building ${color8.cyan(dirPath)}`);
1595
1650
  if (options.output && fs7.existsSync(outFile)) fs7.rmSync(outFile);
1596
- categories.push(...buildBlocksDirectory(dirPath, options.cwd));
1651
+ categories.push(
1652
+ ...buildBlocksDirectory(dirPath, { cwd: options.cwd, excludeDeps: options.excludeDeps })
1653
+ );
1597
1654
  loading.stop(`Built ${color8.cyan(dirPath)}`);
1598
1655
  }
1599
1656
  const categoriesMap = /* @__PURE__ */ new Map();
@@ -1620,7 +1677,7 @@ import { cancel as cancel2, confirm as confirm2, isCancel as isCancel2, outro as
1620
1677
  import color10 from "chalk";
1621
1678
  import { Command as Command3, program as program3 } from "commander";
1622
1679
  import { diffLines } from "diff";
1623
- import * as v6 from "valibot";
1680
+ import * as v7 from "valibot";
1624
1681
 
1625
1682
  // src/utils/diff.ts
1626
1683
  import color9 from "chalk";
@@ -1814,12 +1871,12 @@ var formatDiff = ({
1814
1871
  };
1815
1872
 
1816
1873
  // src/commands/diff.ts
1817
- var schema4 = v6.object({
1818
- expand: v6.boolean(),
1819
- maxUnchanged: v6.number(),
1820
- repo: v6.optional(v6.string()),
1821
- allow: v6.boolean(),
1822
- cwd: v6.string()
1874
+ var schema4 = v7.object({
1875
+ expand: v7.boolean(),
1876
+ maxUnchanged: v7.number(),
1877
+ repo: v7.optional(v7.string()),
1878
+ allow: v7.boolean(),
1879
+ cwd: v7.string()
1823
1880
  });
1824
1881
  var diff = new Command3("diff").description("Compares local blocks to the blocks in the provided repository.").option("-E, --expand", "Expands the diff so you see everything.", false).option(
1825
1882
  "--max-unchanged <number>",
@@ -1828,7 +1885,7 @@ var diff = new Command3("diff").description("Compares local blocks to the blocks
1828
1885
  // this is such a dumb api thing
1829
1886
  3
1830
1887
  ).option("--repo <repo>", "Repository to download the blocks from.").option("-A, --allow", "Allow jsrepo to download code from the provided repo.", false).option("--cwd <path>", "The current working directory.", process.cwd()).action(async (opts) => {
1831
- const options = v6.parse(schema4, opts);
1888
+ const options = v7.parse(schema4, opts);
1832
1889
  _intro(context.package.version);
1833
1890
  await _diff(options);
1834
1891
  outro3(color10.green("All done!"));
@@ -1949,19 +2006,19 @@ import path9 from "node:path";
1949
2006
  import { cancel as cancel3, confirm as confirm3, isCancel as isCancel3, outro as outro4, spinner as spinner5, text } from "@clack/prompts";
1950
2007
  import color11 from "chalk";
1951
2008
  import { Command as Command4 } from "commander";
1952
- import * as v7 from "valibot";
1953
- var schema5 = v7.object({
1954
- path: v7.optional(v7.string()),
1955
- repos: v7.optional(v7.array(v7.string())),
1956
- watermark: v7.boolean(),
1957
- tests: v7.optional(v7.boolean()),
1958
- cwd: v7.string()
2009
+ import * as v8 from "valibot";
2010
+ var schema5 = v8.object({
2011
+ path: v8.optional(v8.string()),
2012
+ repos: v8.optional(v8.array(v8.string())),
2013
+ watermark: v8.boolean(),
2014
+ tests: v8.optional(v8.boolean()),
2015
+ cwd: v8.string()
1959
2016
  });
1960
2017
  var init = new Command4("init").description("Initializes your project with a configuration file.").option("--path <path>", "Path to install the blocks.").option("--repos [repos...]", "Repository to install the blocks from.").option(
1961
2018
  "--no-watermark",
1962
2019
  "Will not add a watermark to each file upon adding it to your project."
1963
2020
  ).option("--tests", "Will include tests with the blocks.").option("--cwd <path>", "The current working directory.", process.cwd()).action(async (opts) => {
1964
- const options = v7.parse(schema5, opts);
2021
+ const options = v8.parse(schema5, opts);
1965
2022
  _intro(context.package.version);
1966
2023
  await _init(options);
1967
2024
  outro4(color11.green("All done!"));
@@ -2039,16 +2096,16 @@ import { execa as execa2 } from "execa";
2039
2096
  import { resolveCommand as resolveCommand3 } from "package-manager-detector/commands";
2040
2097
  import { detect as detect2 } from "package-manager-detector/detect";
2041
2098
  import { Project as Project2 } from "ts-morph";
2042
- import * as v8 from "valibot";
2043
- var schema6 = v8.object({
2044
- repo: v8.optional(v8.string()),
2045
- allow: v8.boolean(),
2046
- debug: v8.boolean(),
2047
- verbose: v8.boolean(),
2048
- cwd: v8.string()
2099
+ import * as v9 from "valibot";
2100
+ var schema6 = v9.object({
2101
+ repo: v9.optional(v9.string()),
2102
+ allow: v9.boolean(),
2103
+ debug: v9.boolean(),
2104
+ verbose: v9.boolean(),
2105
+ cwd: v9.string()
2049
2106
  });
2050
2107
  var test = new Command5("test").description("Tests local blocks against most recent remote tests.").addArgument(new Argument("[blocks...]", "The blocks you want to test.").default([])).option("--repo <repo>", "Repository to download the blocks from.").option("-A, --allow", "Allow jsrepo to download code from the provided repo.", false).option("--debug", "Leaves the temp test file around for debugging upon failure.", false).option("--verbose", "Include debug logs.", false).option("--cwd <path>", "The current working directory.", process.cwd()).action(async (blockNames, opts) => {
2051
- const options = v8.parse(schema6, opts);
2108
+ const options = v9.parse(schema6, opts);
2052
2109
  _intro(context.package.version);
2053
2110
  await _test(blockNames, options);
2054
2111
  outro5(color12.green("All done!"));
@@ -2098,7 +2155,7 @@ var _test = async (blockNames, options) => {
2098
2155
  )
2099
2156
  );
2100
2157
  }
2101
- const categories = v8.parse(v8.array(categorySchema), await response.json());
2158
+ const categories = v9.parse(v9.array(categorySchema), await response.json());
2102
2159
  for (const category of categories) {
2103
2160
  for (const block of category.blocks) {
2104
2161
  blocksMap.set(
@@ -2294,16 +2351,16 @@ import { Command as Command6, program as program5 } from "commander";
2294
2351
  import { diffLines as diffLines2 } from "diff";
2295
2352
  import { resolveCommand as resolveCommand4 } from "package-manager-detector/commands";
2296
2353
  import { detect as detect3 } from "package-manager-detector/detect";
2297
- import * as v9 from "valibot";
2298
- var schema7 = v9.object({
2299
- all: v9.boolean(),
2300
- expand: v9.boolean(),
2301
- maxUnchanged: v9.number(),
2302
- repo: v9.optional(v9.string()),
2303
- allow: v9.boolean(),
2304
- yes: v9.boolean(),
2305
- verbose: v9.boolean(),
2306
- cwd: v9.string()
2354
+ import * as v10 from "valibot";
2355
+ var schema7 = v10.object({
2356
+ all: v10.boolean(),
2357
+ expand: v10.boolean(),
2358
+ maxUnchanged: v10.number(),
2359
+ repo: v10.optional(v10.string()),
2360
+ allow: v10.boolean(),
2361
+ yes: v10.boolean(),
2362
+ verbose: v10.boolean(),
2363
+ cwd: v10.string()
2307
2364
  });
2308
2365
  var update = new Command6("update").argument("[blocks...]", "Names of the blocks you want to update. ex: (utils/math)").option("--all", "Update all installed components.", false).option("-E, --expand", "Expands the diff so you see everything.", false).option(
2309
2366
  "--max-unchanged <number>",
@@ -2312,7 +2369,7 @@ var update = new Command6("update").argument("[blocks...]", "Names of the blocks
2312
2369
  // this is such a dumb api thing
2313
2370
  3
2314
2371
  ).option("--repo <repo>", "Repository to download the blocks from.").option("-A, --allow", "Allow jsrepo to download code from the provided repo.", false).option("-y, --yes", "Skip confirmation prompt.", false).option("--verbose", "Include debug logs.", false).option("--cwd <path>", "The current working directory.", process.cwd()).action(async (blockNames, opts) => {
2315
- const options = v9.parse(schema7, opts);
2372
+ const options = v10.parse(schema7, opts);
2316
2373
  _intro(context.package.version);
2317
2374
  await _update(blockNames, options);
2318
2375
  outro6(color13.green("All done!"));
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.3.1",
4
+ "version": "1.4.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/ieedan/jsrepo"
@@ -39,6 +39,7 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@clack/prompts": "^0.8.1",
42
+ "@vue/compiler-sfc": "^3.5.13",
42
43
  "ansi-regex": "^6.1.0",
43
44
  "chalk": "^5.3.0",
44
45
  "commander": "^12.1.0",
@@ -11,6 +11,7 @@ import { intro } from '../utils/prompts';
11
11
 
12
12
  const schema = v.object({
13
13
  dirs: v.array(v.string()),
14
+ excludeDeps: v.array(v.string()),
14
15
  output: v.boolean(),
15
16
  verbose: v.boolean(),
16
17
  cwd: v.string(),
@@ -21,6 +22,7 @@ type Options = v.InferInput<typeof schema>;
21
22
  const build = new Command('build')
22
23
  .description(`Builds the provided --dirs in the project root into a \`${OUTPUT_FILE}\` file.`)
23
24
  .option('--dirs [dirs...]', 'The directories containing the blocks.', ['./blocks'])
25
+ .option('--exclude-deps [deps...]', 'Dependencies that should not be added.', [])
24
26
  .option('--no-output', `Do not output a \`${OUTPUT_FILE}\` file.`)
25
27
  .option('--verbose', 'Include debug logs.', false)
26
28
  .option('--cwd <path>', 'The current working directory.', process.cwd())
@@ -48,7 +50,9 @@ const _build = async (options: Options) => {
48
50
 
49
51
  if (options.output && fs.existsSync(outFile)) fs.rmSync(outFile);
50
52
 
51
- categories.push(...buildBlocksDirectory(dirPath, options.cwd));
53
+ categories.push(
54
+ ...buildBlocksDirectory(dirPath, { cwd: options.cwd, excludeDeps: options.excludeDeps })
55
+ );
52
56
 
53
57
  loading.stop(`Built ${color.cyan(dirPath)}`);
54
58
  }
@@ -33,12 +33,17 @@ const TEST_SUFFIXES = ['.test.ts', '_test.ts', '.test.js', '_test.js'] as const;
33
33
  const isTestFile = (file: string): boolean =>
34
34
  TEST_SUFFIXES.find((suffix) => file.endsWith(suffix)) !== undefined;
35
35
 
36
+ type Options = {
37
+ cwd: string;
38
+ excludeDeps: string[];
39
+ };
40
+
36
41
  /** Using the provided path to the blocks folder builds the blocks into categories and also resolves dependencies
37
42
  *
38
43
  * @param blocksPath
39
44
  * @returns
40
45
  */
41
- const buildBlocksDirectory = (blocksPath: string, cwd: string): Category[] => {
46
+ const buildBlocksDirectory = (blocksPath: string, { cwd, excludeDeps }: Options): Category[] => {
42
47
  let paths: string[];
43
48
 
44
49
  try {
@@ -88,7 +93,12 @@ const buildBlocksDirectory = (blocksPath: string, cwd: string): Category[] => {
88
93
  );
89
94
 
90
95
  const { dependencies, devDependencies, local } = lang
91
- .resolveDependencies(blockDir, categoryName, false)
96
+ .resolveDependencies({
97
+ filePath: blockDir,
98
+ category: categoryName,
99
+ isSubDir: false,
100
+ excludeDeps,
101
+ })
92
102
  .match(
93
103
  (val) => val,
94
104
  (err) => {
@@ -140,7 +150,12 @@ const buildBlocksDirectory = (blocksPath: string, cwd: string): Category[] => {
140
150
  }
141
151
 
142
152
  const { local, dependencies, devDependencies } = lang
143
- .resolveDependencies(path.join(blockDir, f), categoryName, true)
153
+ .resolveDependencies({
154
+ filePath: path.join(blockDir, f),
155
+ category: categoryName,
156
+ isSubDir: true,
157
+ excludeDeps,
158
+ })
144
159
  .match(
145
160
  (val) => val,
146
161
  (err) => {
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import { builtinModules } from 'node:module';
3
3
  import path from 'node:path';
4
+ import * as v from '@vue/compiler-sfc';
4
5
  import color from 'chalk';
5
6
  import { walk } from 'estree-walker';
6
7
  import * as sv from 'svelte/compiler';
@@ -17,15 +18,18 @@ export type ResolvedDependencies = {
17
18
  dependencies: string[];
18
19
  };
19
20
 
21
+ export type ResolveDependencyOptions = {
22
+ filePath: string;
23
+ category: string;
24
+ isSubDir: boolean;
25
+ excludeDeps: string[];
26
+ };
27
+
20
28
  export type Lang = {
21
29
  /** Matches the supported file types */
22
30
  matches: (fileName: string) => boolean;
23
31
  /** Reads the file and gets any dependencies from its imports */
24
- resolveDependencies: (
25
- filePath: string,
26
- category: string,
27
- isSubDir: boolean
28
- ) => Result<ResolvedDependencies, string>;
32
+ resolveDependencies: (opts: ResolveDependencyOptions) => Result<ResolvedDependencies, string>;
29
33
  /** Returns a multiline comment containing the content */
30
34
  comment: (content: string) => string;
31
35
  };
@@ -36,7 +40,7 @@ const typescript: Lang = {
36
40
  fileName.endsWith('.js') ||
37
41
  fileName.endsWith('.tsx') ||
38
42
  fileName.endsWith('.jsx'),
39
- resolveDependencies: (filePath, category, isSubDir) => {
43
+ resolveDependencies: ({ filePath, category, isSubDir, excludeDeps }) => {
40
44
  const project = new Project();
41
45
 
42
46
  const blockFile = project.addSourceFileAtPath(filePath);
@@ -61,7 +65,11 @@ const typescript: Lang = {
61
65
  .filter((declaration) => !declaration.getModuleSpecifierValue().startsWith('.'))
62
66
  .map((declaration) => declaration.getModuleSpecifierValue());
63
67
 
64
- const { devDependencies, dependencies } = resolveRemoteDeps(Array.from(deps), filePath);
68
+ const { devDependencies, dependencies } = resolveRemoteDeps(
69
+ Array.from(deps),
70
+ filePath,
71
+ excludeDeps
72
+ );
65
73
 
66
74
  return Ok({
67
75
  local: Array.from(localDeps),
@@ -74,7 +82,7 @@ const typescript: Lang = {
74
82
 
75
83
  const svelte: Lang = {
76
84
  matches: (fileName) => fileName.endsWith('.svelte'),
77
- resolveDependencies: (filePath, category, isSubDir) => {
85
+ resolveDependencies: ({ filePath, category, isSubDir, excludeDeps }) => {
78
86
  const sourceCode = fs.readFileSync(filePath).toString();
79
87
 
80
88
  const root = sv.parse(sourceCode, { modern: true });
@@ -106,7 +114,53 @@ const svelte: Lang = {
106
114
  },
107
115
  });
108
116
 
109
- const { devDependencies, dependencies } = resolveRemoteDeps(Array.from(deps), filePath);
117
+ const { devDependencies, dependencies } = resolveRemoteDeps(Array.from(deps), filePath, [
118
+ 'svelte',
119
+ ...excludeDeps,
120
+ ]);
121
+
122
+ return Ok({
123
+ dependencies,
124
+ devDependencies,
125
+ local: Array.from(localDeps),
126
+ } satisfies ResolvedDependencies);
127
+ },
128
+ comment: (content) => `<!--\n${content}\n-->`,
129
+ };
130
+
131
+ const vue: Lang = {
132
+ matches: (fileName) => fileName.endsWith('.vue'),
133
+ resolveDependencies: ({ filePath, category, isSubDir, excludeDeps }) => {
134
+ const sourceCode = fs.readFileSync(filePath).toString();
135
+
136
+ const parsed = v.parse(sourceCode);
137
+
138
+ if (!parsed.descriptor.script?.content && !parsed.descriptor.scriptSetup?.content)
139
+ return Ok({ dependencies: [], devDependencies: [], local: [] });
140
+
141
+ const localDeps = new Set<string>();
142
+ const deps = new Set<string>();
143
+
144
+ const compiled = v.compileScript(parsed.descriptor, { id: 'shut-it' }); // you need this id to remove a warning
145
+
146
+ if (!compiled.imports) return Ok({ dependencies: [], devDependencies: [], local: [] });
147
+
148
+ const imports = Object.values(compiled.imports);
149
+
150
+ for (const imp of imports) {
151
+ if (imp.source.startsWith('.')) {
152
+ const localDep = resolveLocalImport(imp.source, category, isSubDir);
153
+
154
+ if (localDep) localDeps.add(localDep);
155
+ } else {
156
+ deps.add(imp.source);
157
+ }
158
+ }
159
+
160
+ const { devDependencies, dependencies } = resolveRemoteDeps(Array.from(deps), filePath, [
161
+ 'vue',
162
+ ...excludeDeps,
163
+ ]);
110
164
 
111
165
  return Ok({
112
166
  dependencies,
@@ -136,7 +190,7 @@ const resolveLocalImport = (
136
190
  const segments = mod.replaceAll('../', '').split('/');
137
191
 
138
192
  // invalid path
139
- if (segments.length !== 2) return undefined;
193
+ if (segments.length < 2) return undefined;
140
194
 
141
195
  return `${segments[0]}/${segments[1]}`;
142
196
  };
@@ -148,7 +202,9 @@ const resolveLocalImport = (
148
202
  * @param filePath
149
203
  * @returns
150
204
  */
151
- const resolveRemoteDeps = (deps: string[], filePath: string) => {
205
+ const resolveRemoteDeps = (deps: string[], filePath: string, doNotInstall: string[] = []) => {
206
+ const exemptDeps = new Set(doNotInstall);
207
+
152
208
  const filteredDeps = deps.filter(
153
209
  (dep) => !builtinModules.includes(dep) && !dep.startsWith('node:')
154
210
  );
@@ -181,6 +237,8 @@ const resolveRemoteDeps = (deps: string[], filePath: string) => {
181
237
  continue;
182
238
  }
183
239
 
240
+ if (exemptDeps.has(depInfo.name)) continue;
241
+
184
242
  let version: string | undefined = undefined;
185
243
  if (packageDependencies !== undefined) {
186
244
  version = packageDependencies[depInfo.name];
@@ -211,6 +269,6 @@ const resolveRemoteDeps = (deps: string[], filePath: string) => {
211
269
  };
212
270
  };
213
271
 
214
- const languages: Lang[] = [typescript, svelte];
272
+ const languages: Lang[] = [typescript, svelte, vue];
215
273
 
216
274
  export { typescript, languages };