@anydigital/eleventy-bricks 0.23.2 → 0.25.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/.prettierrc.json CHANGED
@@ -1,3 +1,12 @@
1
1
  {
2
- "printWidth": 120
2
+ "printWidth": 120,
3
+ "plugins": ["prettier-plugin-jinja-template"],
4
+ "overrides": [
5
+ {
6
+ "files": ["*.njk", "*.html"],
7
+ "options": {
8
+ "parser": "jinja-template"
9
+ }
10
+ }
11
+ ]
3
12
  }
package/README.md CHANGED
@@ -27,7 +27,7 @@ export default function (eleventyConfig) {
27
27
  mdAutoNl2br: true,
28
28
  mdAutoLinkFavicons: true,
29
29
  siteData: true,
30
- filters: ["attr", "where_in", "merge", "remove_tag", "if", "attr_concat"],
30
+ filters: ["attr", "where_in", "merge", "remove_tag", "if", "attr_concat", "fetch"],
31
31
  });
32
32
 
33
33
  // Your other configuration...
@@ -45,7 +45,7 @@ module.exports = function (eleventyConfig) {
45
45
  mdAutoNl2br: true,
46
46
  mdAutoLinkFavicons: true,
47
47
  siteData: true,
48
- filters: ["attr", "where_in", "merge", "remove_tag", "if", "attr_concat"],
48
+ filters: ["attr", "where_in", "merge", "remove_tag", "if", "attr_concat", "fetch"],
49
49
  });
50
50
 
51
51
  // Your other configuration...
@@ -71,6 +71,7 @@ import {
71
71
  removeTagFilter,
72
72
  ifFilter,
73
73
  attrConcatFilter,
74
+ fetchFilter,
74
75
  siteData,
75
76
  } from "@anydigital/eleventy-bricks";
76
77
 
@@ -84,6 +85,10 @@ export default function (eleventyConfig) {
84
85
  removeTagFilter(eleventyConfig);
85
86
  ifFilter(eleventyConfig);
86
87
  attrConcatFilter(eleventyConfig);
88
+ // fetchFilter is only available if @11ty/eleventy-fetch is installed
89
+ if (fetchFilter) {
90
+ fetchFilter(eleventyConfig);
91
+ }
87
92
  siteData(eleventyConfig);
88
93
 
89
94
  // Your other configuration...
@@ -103,6 +108,7 @@ const {
103
108
  removeTagFilter,
104
109
  ifFilter,
105
110
  attrConcatFilter,
111
+ fetchFilter,
106
112
  siteData,
107
113
  } = require("@anydigital/eleventy-bricks");
108
114
 
@@ -116,6 +122,10 @@ module.exports = async function (eleventyConfig) {
116
122
  await removeTagFilter(eleventyConfig);
117
123
  await ifFilter(eleventyConfig);
118
124
  await attrConcatFilter(eleventyConfig);
125
+ // fetchFilter is only available if @11ty/eleventy-fetch is installed
126
+ if (fetchFilter) {
127
+ await fetchFilter(eleventyConfig);
128
+ }
119
129
  await siteData(eleventyConfig);
120
130
 
121
131
  // Your other configuration...
@@ -144,6 +154,7 @@ When using the plugin (Option 1), you can configure which helpers to enable:
144
154
  - `'remove_tag'` - Remove HTML elements from content
145
155
  - `'if'` - Inline conditional/ternary operator
146
156
  - `'attr_concat'` - Concatenate values to an attribute array
157
+ - `'fetch'` - Fetch remote URLs or local files (requires `@11ty/eleventy-fetch`)
147
158
 
148
159
  **Example:**
149
160
 
@@ -153,7 +164,7 @@ eleventyConfig.addPlugin(eleventyBricks, {
153
164
  mdAutoNl2br: true,
154
165
  mdAutoLinkFavicons: true,
155
166
  siteData: true,
156
- filters: ["attr", "where_in", "merge", "remove_tag", "if", "attr_concat"],
167
+ filters: ["attr", "where_in", "merge", "remove_tag", "if", "attr_concat", "fetch"],
157
168
  });
158
169
  ```
159
170
 
@@ -789,6 +800,139 @@ A new object with the specified attribute containing the combined unique array.
789
800
  {% endfor %}
790
801
  ```
791
802
 
803
+ ### fetch
804
+
805
+ A filter that fetches content from remote URLs or local files. For remote URLs, it uses `@11ty/eleventy-fetch` to download and cache files. For local paths, it reads files relative to the input directory.
806
+
807
+ **Why use this?**
808
+
809
+ When building static sites, you often need to include content from external sources or reuse content from local files. The `fetch` filter provides a unified way to retrieve content from both remote URLs and local files, with automatic caching for remote resources to improve build performance.
810
+
811
+ **Requirements:**
812
+
813
+ This filter requires the `@11ty/eleventy-fetch` package to be installed:
814
+
815
+ ```bash
816
+ npm install @11ty/eleventy-fetch
817
+ ```
818
+
819
+ > **Note:** If `@11ty/eleventy-fetch` is not installed, this filter will not be available. The plugin automatically detects whether the package is installed and only enables the filter if it's present.
820
+
821
+ **Usage:**
822
+
823
+ 1. Install the required dependency:
824
+
825
+ ```bash
826
+ npm install @11ty/eleventy-fetch
827
+ ```
828
+
829
+ 2. Enable the `fetch` filter in your Eleventy config:
830
+
831
+ ```javascript
832
+ import { fetchFilter } from "@anydigital/eleventy-bricks";
833
+
834
+ export default function (eleventyConfig) {
835
+ fetchFilter(eleventyConfig);
836
+ // Or use as plugin:
837
+ // eleventyConfig.addPlugin(eleventyBricks, { filters: ['fetch'] });
838
+ }
839
+ ```
840
+
841
+ 3. Use the filter in your templates:
842
+
843
+ **Fetch remote URLs:**
844
+
845
+ ```njk
846
+ {# Fetch content from a remote URL #}
847
+ {% set externalContent = "https://example.com/data.json" | fetch %}
848
+ {{ externalContent }}
849
+
850
+ {# Fetch and parse JSON #}
851
+ {% set apiData = "https://api.example.com/posts" | fetch %}
852
+ {% set posts = apiData | fromJson %}
853
+ ```
854
+
855
+ **Fetch local files:**
856
+
857
+ ```njk
858
+ {# Fetch content from a local file (relative to input directory) #}
859
+ {% set localData = "_data/content.txt" | fetch %}
860
+ {{ localData }}
861
+
862
+ {# Include content from another file #}
863
+ {% set snippet = "_includes/snippets/example.md" | fetch %}
864
+ {{ snippet | markdown | safe }}
865
+ ```
866
+
867
+ **Parameters:**
868
+
869
+ - `url`: A URL (starting with `http://` or `https://`) or a local file path (relative to the input directory)
870
+
871
+ **Features:**
872
+
873
+ - **Remote URLs**: Downloads and caches content using `@11ty/eleventy-fetch`
874
+ - Caches files for 1 day by default
875
+ - Stores cached files in `[input-dir]/_downloads/` directory
876
+ - Automatically revalidates after cache expires
877
+ - **Local files**: Reads files relative to the Eleventy input directory
878
+ - No caching needed for local files
879
+ - Supports any file type that can be read as text
880
+ - **Error handling**: Throws descriptive errors if fetching fails
881
+ - **Conditional loading**: Only available when `@11ty/eleventy-fetch` is installed
882
+
883
+ **Examples:**
884
+
885
+ ```njk
886
+ {# Fetch and display remote content #}
887
+ {% set readme = "https://raw.githubusercontent.com/user/repo/main/README.md" | fetch %}
888
+ <div class="readme">
889
+ {{ readme | markdown | safe }}
890
+ </div>
891
+
892
+ {# Fetch JSON data from API #}
893
+ {% set data = "https://api.example.com/data.json" | fetch %}
894
+ {% set items = data | fromJson %}
895
+ {% for item in items %}
896
+ <p>{{ item.title }}</p>
897
+ {% endfor %}
898
+
899
+ {# Include local file content #}
900
+ {% set changelog = "CHANGELOG.md" | fetch %}
901
+ {{ changelog | markdown | safe }}
902
+
903
+ {# Fetch CSS from CDN and inline it #}
904
+ <style>
905
+ {{ "https://cdn.example.com/styles.css" | fetch }}
906
+ </style>
907
+
908
+ {# Reuse content across pages #}
909
+ {% set sharedContent = "_includes/shared/footer.html" | fetch %}
910
+ {{ sharedContent | safe }}
911
+ ```
912
+
913
+ **Cache Directory:**
914
+
915
+ Remote files are cached in the `_downloads` folder within your input directory:
916
+
917
+ ```
918
+ your-project/
919
+ ├── src/ (or your input directory)
920
+ │ ├── _downloads/ (cached remote files)
921
+ │ ├── index.njk
922
+ │ └── ...
923
+ ```
924
+
925
+ **Use Cases:**
926
+
927
+ - Fetch content from external APIs during build time
928
+ - Include README files from GitHub repositories
929
+ - Reuse content from local files across multiple pages
930
+ - Download and inline external CSS or JavaScript
931
+ - Fetch data from headless CMS or external data sources
932
+ - Include shared content snippets without using Eleventy's include syntax
933
+
934
+ **Note:** The filter returns raw text content. Use Eleventy's built-in filters like `| safe`, `| markdown`, or `| fromJson` to process the content as needed.
935
+
792
936
  ### siteData
793
937
 
794
938
  Adds global site data to your Eleventy project, providing commonly needed values that can be accessed in all templates.
@@ -892,6 +1036,7 @@ A fully-configured Eleventy config file with:
892
1036
 
893
1037
  - All eleventy-bricks plugins enabled
894
1038
  - Eleventy Navigation plugin
1039
+ - Table of Contents plugin (conditionally loaded if installed)
895
1040
  - Markdown-it with anchors and attributes
896
1041
  - YAML data support
897
1042
  - CLI input directory support
@@ -903,6 +1048,16 @@ A fully-configured Eleventy config file with:
903
1048
  npm install @11ty/eleventy-navigation markdown-it markdown-it-anchor markdown-it-attrs js-yaml minimist
904
1049
  ```
905
1050
 
1051
+ **Optional dependencies:**
1052
+
1053
+ ```bash
1054
+ # For the fetch filter
1055
+ npm install @11ty/eleventy-fetch
1056
+
1057
+ # For table of contents generation
1058
+ npm install @uncenter/eleventy-plugin-toc
1059
+ ```
1060
+
906
1061
  **Symlink to your project:**
907
1062
 
908
1063
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anydigital/eleventy-bricks",
3
- "version": "0.23.2",
3
+ "version": "0.25.0",
4
4
  "description": "A collection of helpful utilities and filters for Eleventy (11ty)",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -10,11 +10,8 @@
10
10
  "require": "./src/index.cjs"
11
11
  }
12
12
  },
13
- "bin": {
14
- "download-files": "src/cli/download-files.js"
15
- },
16
13
  "scripts": {
17
- "build": "npx download-files",
14
+ "build": "curl -O https://raw.githubusercontent.com/anydigital/bricks/refs/heads/main/.prettierrc.json",
18
15
  "test": "node --test src/**/*.test.js"
19
16
  },
20
17
  "keywords": [
@@ -41,9 +38,5 @@
41
38
  },
42
39
  "engines": {
43
40
  "node": ">=18.0.0"
44
- },
45
- "_downloadFiles": {
46
- "https://raw.githubusercontent.com/anydigital/bricks/refs/heads/main/.prettierrc.json": ".prettierrc.json",
47
- "https://raw.githubusercontent.com/danurbanowicz/eleventy-sveltia-cms-starter/refs/heads/master/admin/index.html": "src/admin/index.html"
48
41
  }
49
42
  }
@@ -4,6 +4,13 @@ import minimist from "minimist";
4
4
  import { RenderPlugin } from "@11ty/eleventy";
5
5
  import eleventyNavigationPlugin from "@11ty/eleventy-navigation";
6
6
  import eleventyBricksPlugin from "@anydigital/eleventy-bricks";
7
+ /* Conditional imports */
8
+ let pluginTOC;
9
+ try {
10
+ pluginTOC = (await import('@uncenter/eleventy-plugin-toc')).default;
11
+ } catch (e) {
12
+ // @uncenter/eleventy-plugin-toc not installed
13
+ }
7
14
  /* Libraries */
8
15
  import markdownIt from "markdown-it";
9
16
  import markdownItAnchor from "markdown-it-anchor";
@@ -29,8 +36,14 @@ export default function (eleventyConfig) {
29
36
  mdAutoRawTags: true,
30
37
  mdAutoLinkFavicons: true,
31
38
  siteData: true,
32
- filters: ["attr", "where_in", "merge", "remove_tag", "if", "attr_concat"],
39
+ filters: ["attr", "where_in", "merge", "remove_tag", "if", "attr_concat", "fetch"],
33
40
  });
41
+ if (pluginTOC) {
42
+ eleventyConfig.addPlugin(pluginTOC, {
43
+ ignoredElements: ["sub", ".header-anchor"],
44
+ ul: true,
45
+ });
46
+ }
34
47
 
35
48
  /* Libraries */
36
49
  eleventyConfig.setLibrary(
@@ -15,16 +15,14 @@ export function attrConcat(obj, attr, values) {
15
15
 
16
16
  // Check if existing value is an array, convert if not
17
17
  if (!Array.isArray(existingArray)) {
18
- console.error(
19
- `attrConcat: Expected ${attr} to be an array, got ${typeof existingArray}`
20
- );
18
+ console.error(`attrConcat: Expected ${attr} to be an array, got ${typeof existingArray}`);
21
19
  }
22
20
 
23
21
  // Process the values argument
24
22
  let valuesToAdd = [];
25
23
  if (Array.isArray(values)) {
26
24
  valuesToAdd = values;
27
- } else if (typeof values === "string") {
25
+ } else if (typeof values === "string" && values.length >= 2 && values.at(0) == "[" && values.at(-1) == "]") {
28
26
  // Try to parse as JSON array
29
27
  try {
30
28
  const parsed = JSON.parse(values);
@@ -0,0 +1,46 @@
1
+ import EleventyFetch from "@11ty/eleventy-fetch";
2
+ import path from "path";
3
+ import fs from "fs/promises";
4
+
5
+ /**
6
+ * fetch filter - Fetch a URL or local file and return its raw content
7
+ *
8
+ * This filter takes a URL or local file path. For URLs, it downloads them
9
+ * using eleventy-fetch to the input directory's _downloads folder.
10
+ * For local paths, it reads them relative to the input directory.
11
+ *
12
+ * @param {Object} eleventyConfig - The Eleventy configuration object
13
+ */
14
+ export function fetchFilter(eleventyConfig) {
15
+ eleventyConfig.addFilter("fetch", async function (url) {
16
+ if (!url) {
17
+ throw new Error("fetch filter requires a URL or path");
18
+ }
19
+
20
+ // Get the input directory from Eleventy config
21
+ const inputDir = eleventyConfig.dir?.input || ".";
22
+
23
+ // Check if it's a URL or local path
24
+ const isUrl = url.startsWith("http://") || url.startsWith("https://");
25
+
26
+ try {
27
+ if (isUrl) {
28
+ // Handle remote URLs with eleventy-fetch
29
+ const cacheDirectory = path.join(inputDir, "_downloads");
30
+ const content = await EleventyFetch(url, {
31
+ duration: "1d", // Cache for 1 day by default
32
+ type: "text", // Return as text
33
+ directory: cacheDirectory,
34
+ });
35
+ return content;
36
+ } else {
37
+ // Handle local file paths relative to input directory
38
+ const filePath = path.join(inputDir, url);
39
+ const content = await fs.readFile(filePath, "utf-8");
40
+ return content;
41
+ }
42
+ } catch (error) {
43
+ throw new Error(`Failed to fetch ${url}: ${error.message}`);
44
+ }
45
+ });
46
+ }
@@ -16,26 +16,22 @@ const { get } = lodash;
16
16
  * @returns {Array} Filtered collection
17
17
  */
18
18
  export function whereIn(collection, attrName, targetValue) {
19
- if (!collection || !Array.isArray(collection)) {
20
- return [];
19
+ // If no targetValue, return original collection
20
+ if (!targetValue) {
21
+ return collection;
21
22
  }
22
23
 
23
24
  return collection.filter((item) => {
24
25
  // Get the attribute value from the item (supports nested paths like "data.tags")
25
26
  const attrValue = get(item, attrName);
26
27
 
27
- // If attribute doesn't exist, skip this item
28
- if (attrValue === undefined || attrValue === null) {
29
- return false;
30
- }
31
-
32
28
  // If the attribute is an array, check if it includes the target value
33
29
  if (Array.isArray(attrValue)) {
34
30
  return attrValue.includes(targetValue);
35
31
  }
36
32
 
37
- // Otherwise, do a direct comparison
38
- return attrValue === targetValue;
33
+ // Otherwise skip this item
34
+ return false;
39
35
  });
40
36
  }
41
37
 
package/src/index.js CHANGED
@@ -17,6 +17,16 @@ import { ifFilter, iff } from "./filters/if.js";
17
17
  import { attrConcatFilter, attrConcat } from "./filters/attr_concat.js";
18
18
  import { siteData } from "./siteData.js";
19
19
 
20
+ // Conditionally import fetchFilter only if @11ty/eleventy-fetch is available
21
+ let fetchFilter = null;
22
+ try {
23
+ await import("@11ty/eleventy-fetch");
24
+ const fetchModule = await import("./filters/fetch.js");
25
+ fetchFilter = fetchModule.fetchFilter;
26
+ } catch (error) {
27
+ // @11ty/eleventy-fetch not available, fetch filter will be disabled
28
+ }
29
+
20
30
  /**
21
31
  * 11ty Bricks Plugin
22
32
  *
@@ -28,7 +38,7 @@ import { siteData } from "./siteData.js";
28
38
  * @param {boolean} options.mdAutoRawTags - Enable mdAutoRawTags preprocessor (default: false)
29
39
  * @param {boolean} options.mdAutoNl2br - Enable mdAutoNl2br for \n to <br> conversion (default: false)
30
40
  * @param {boolean} options.mdAutoLinkFavicons - Enable mdAutoLinkFavicons to add favicons to plain text links (default: false)
31
- * @param {Array<string>} options.filters - Array of filter names to enable: 'attr', 'where_in', 'merge', 'remove_tag', 'if', 'attr_concat' (default: [])
41
+ * @param {Array<string>} options.filters - Array of filter names to enable: 'attr', 'where_in', 'merge', 'remove_tag', 'if', 'attr_concat', 'fetch' (default: [])
32
42
  * @param {boolean} options.siteData - Enable site.year and site.prod global data (default: false)
33
43
  */
34
44
  export default function eleventyBricksPlugin(eleventyConfig, options = {}) {
@@ -46,6 +56,7 @@ export default function eleventyBricksPlugin(eleventyConfig, options = {}) {
46
56
  remove_tag: removeTagFilter,
47
57
  if: ifFilter,
48
58
  attr_concat: attrConcatFilter,
59
+ ...(fetchFilter && { fetch: fetchFilter }),
49
60
  };
50
61
 
51
62
  // Handle individual plugin options
@@ -66,6 +77,7 @@ export default function eleventyBricksPlugin(eleventyConfig, options = {}) {
66
77
  }
67
78
 
68
79
  // Export individual helpers for granular usage
80
+ // Note: fetchFilter will be null/undefined if @11ty/eleventy-fetch is not installed
69
81
  export {
70
82
  mdAutoRawTags,
71
83
  mdAutoNl2br,
@@ -76,6 +88,7 @@ export {
76
88
  removeTagFilter,
77
89
  ifFilter,
78
90
  attrConcatFilter,
91
+ fetchFilter,
79
92
  siteData,
80
93
  };
81
94
 
package/src/markdown.js CHANGED
@@ -76,11 +76,12 @@ export function isPlainUrlText(linkText, domain) {
76
76
  * @returns {string} The cleaned text
77
77
  */
78
78
  export function cleanLinkText(linkText, domain) {
79
- return linkText
79
+ const cleanedText = linkText
80
80
  .trim()
81
81
  .replace(/^https?:\/\//, "")
82
- .replace(domain, "")
83
82
  .replace(/\/$/, "");
83
+ const withoutDomain = cleanedText.replace(domain, "");
84
+ return withoutDomain.length > 2 ? withoutDomain : cleanedText;
84
85
  }
85
86
 
86
87
  /**
@@ -128,15 +129,10 @@ export function transformLink(match, attrs, url, linkText) {
128
129
 
129
130
  // Only add favicon if link text looks like a plain URL/domain
130
131
  if (isPlainUrlText(linkText, domain)) {
131
- // Remove domain from link text
132
132
  const cleanedText = cleanLinkText(linkText, domain);
133
-
134
- // Only apply if there are at least 2 letters remaining after domain
135
- if (cleanedText.length > 2) {
136
- return buildFaviconLink(attrs, domain, cleanedText);
137
- }
133
+ return buildFaviconLink(attrs, domain, cleanedText);
138
134
  }
139
- return match;
135
+ return match; // @TODO: throw?
140
136
  } catch (e) {
141
137
  // If URL parsing fails, return original match
142
138
  return match;
@@ -1,136 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { readFile, writeFile, mkdir } from 'fs/promises';
4
- import { dirname, resolve, join } from 'path';
5
-
6
- /**
7
- * Parse command line arguments
8
- *
9
- * @returns {Object} Parsed arguments
10
- */
11
- function parseArgs() {
12
- const args = process.argv.slice(2);
13
- const parsed = {
14
- outputDir: null
15
- };
16
-
17
- for (let i = 0; i < args.length; i++) {
18
- const arg = args[i];
19
-
20
- if (arg === '--output' || arg === '-o') {
21
- if (i + 1 < args.length) {
22
- parsed.outputDir = args[i + 1];
23
- i++; // Skip next argument
24
- } else {
25
- throw new Error(`${arg} requires a directory path`);
26
- }
27
- }
28
- }
29
-
30
- return parsed;
31
- }
32
-
33
- /**
34
- * Downloads files specified in package.json's _downloadFiles field
35
- *
36
- * @param {string|null} outputDir - Optional output directory to prepend to all paths
37
- * @returns {Promise<boolean>} True if all downloads succeeded, false if any failed
38
- */
39
- async function download(outputDir = null) {
40
- try {
41
- // Find and read package.json from the current working directory
42
- const packageJsonPath = resolve(process.cwd(), 'package.json');
43
- const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf-8'));
44
-
45
- const downloadFiles = packageJson._downloadFiles;
46
-
47
- if (!downloadFiles || typeof downloadFiles !== 'object') {
48
- console.log('No _downloadFiles field found in package.json');
49
- return true;
50
- }
51
-
52
- const entries = Object.entries(downloadFiles);
53
-
54
- if (entries.length === 0) {
55
- console.log('No files to download (_downloadFiles is empty)');
56
- return true;
57
- }
58
-
59
- console.log(`Starting download of ${entries.length} file(s)...\n`);
60
-
61
- let hasErrors = false;
62
-
63
- // Process all downloads
64
- for (const entry of entries) {
65
- const url = entry[0];
66
- let localPath = entry[1];
67
-
68
- try {
69
- console.log(`Downloading: ${url}`);
70
- console.log(` To: ${localPath}`);
71
-
72
- // Download the file
73
- const response = await fetch(url);
74
-
75
- if (!response.ok) {
76
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
77
- }
78
-
79
- // Get the file content
80
- const content = await response.arrayBuffer();
81
- const buffer = Buffer.from(content);
82
-
83
- // Prepend output directory to local path if specified
84
- if (outputDir) {
85
- localPath = join(outputDir, localPath);
86
- }
87
-
88
- // Resolve the full path
89
- const fullPath = resolve(process.cwd(), localPath);
90
-
91
- // Create directory if it doesn't exist
92
- const dir = dirname(fullPath);
93
- await mkdir(dir, { recursive: true });
94
-
95
- // Write the file
96
- await writeFile(fullPath, buffer);
97
-
98
- console.log(` Success: ${localPath}\n`);
99
- } catch (error) {
100
- hasErrors = true;
101
- console.error(` Error: ${error.message}`);
102
- console.error(` URL: ${url}\n`);
103
- }
104
- }
105
-
106
- // Summary
107
- if (hasErrors) {
108
- console.log('Download completed with errors');
109
- return false;
110
- } else {
111
- console.log('All downloads completed successfully');
112
- return true;
113
- }
114
-
115
- } catch (error) {
116
- console.error(`Fatal error: ${error.message}`);
117
- return false;
118
- }
119
- }
120
-
121
- /**
122
- * CLI entry point
123
- */
124
- async function main() {
125
- try {
126
- const args = parseArgs();
127
- const success = await download(args.outputDir);
128
- process.exit(success ? 0 : 1);
129
- } catch (error) {
130
- console.error(`Error: ${error.message}`);
131
- process.exit(1);
132
- }
133
- }
134
-
135
- main();
136
-