@anydigital/eleventy-bricks 0.23.2 → 0.24.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 +10 -1
- package/README.md +141 -3
- package/package.json +2 -9
- package/src/eleventy.config.js +1 -1
- package/src/filters/attr_concat.js +2 -4
- package/src/filters/fetch.js +46 -0
- package/src/filters/where_in.js +5 -9
- package/src/index.js +14 -1
- package/src/markdown.js +5 -9
- package/src/cli/download-files.js +0 -136
package/.prettierrc.json
CHANGED
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,7 @@ export default function (eleventyConfig) {
|
|
|
84
85
|
removeTagFilter(eleventyConfig);
|
|
85
86
|
ifFilter(eleventyConfig);
|
|
86
87
|
attrConcatFilter(eleventyConfig);
|
|
88
|
+
fetchFilter(eleventyConfig); // Only if @11ty/eleventy-fetch is installed
|
|
87
89
|
siteData(eleventyConfig);
|
|
88
90
|
|
|
89
91
|
// Your other configuration...
|
|
@@ -103,6 +105,7 @@ const {
|
|
|
103
105
|
removeTagFilter,
|
|
104
106
|
ifFilter,
|
|
105
107
|
attrConcatFilter,
|
|
108
|
+
fetchFilter,
|
|
106
109
|
siteData,
|
|
107
110
|
} = require("@anydigital/eleventy-bricks");
|
|
108
111
|
|
|
@@ -116,6 +119,7 @@ module.exports = async function (eleventyConfig) {
|
|
|
116
119
|
await removeTagFilter(eleventyConfig);
|
|
117
120
|
await ifFilter(eleventyConfig);
|
|
118
121
|
await attrConcatFilter(eleventyConfig);
|
|
122
|
+
await fetchFilter(eleventyConfig); // Only if @11ty/eleventy-fetch is installed
|
|
119
123
|
await siteData(eleventyConfig);
|
|
120
124
|
|
|
121
125
|
// Your other configuration...
|
|
@@ -144,6 +148,7 @@ When using the plugin (Option 1), you can configure which helpers to enable:
|
|
|
144
148
|
- `'remove_tag'` - Remove HTML elements from content
|
|
145
149
|
- `'if'` - Inline conditional/ternary operator
|
|
146
150
|
- `'attr_concat'` - Concatenate values to an attribute array
|
|
151
|
+
- `'fetch'` - Fetch remote URLs or local files (requires `@11ty/eleventy-fetch`)
|
|
147
152
|
|
|
148
153
|
**Example:**
|
|
149
154
|
|
|
@@ -153,7 +158,7 @@ eleventyConfig.addPlugin(eleventyBricks, {
|
|
|
153
158
|
mdAutoNl2br: true,
|
|
154
159
|
mdAutoLinkFavicons: true,
|
|
155
160
|
siteData: true,
|
|
156
|
-
filters: ["attr", "where_in", "merge", "remove_tag", "if", "attr_concat"],
|
|
161
|
+
filters: ["attr", "where_in", "merge", "remove_tag", "if", "attr_concat", "fetch"],
|
|
157
162
|
});
|
|
158
163
|
```
|
|
159
164
|
|
|
@@ -789,6 +794,139 @@ A new object with the specified attribute containing the combined unique array.
|
|
|
789
794
|
{% endfor %}
|
|
790
795
|
```
|
|
791
796
|
|
|
797
|
+
### fetch
|
|
798
|
+
|
|
799
|
+
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.
|
|
800
|
+
|
|
801
|
+
**Why use this?**
|
|
802
|
+
|
|
803
|
+
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.
|
|
804
|
+
|
|
805
|
+
**Requirements:**
|
|
806
|
+
|
|
807
|
+
This filter requires the `@11ty/eleventy-fetch` package to be installed:
|
|
808
|
+
|
|
809
|
+
```bash
|
|
810
|
+
npm install @11ty/eleventy-fetch
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
> **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.
|
|
814
|
+
|
|
815
|
+
**Usage:**
|
|
816
|
+
|
|
817
|
+
1. Install the required dependency:
|
|
818
|
+
|
|
819
|
+
```bash
|
|
820
|
+
npm install @11ty/eleventy-fetch
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
2. Enable the `fetch` filter in your Eleventy config:
|
|
824
|
+
|
|
825
|
+
```javascript
|
|
826
|
+
import { fetchFilter } from "@anydigital/eleventy-bricks";
|
|
827
|
+
|
|
828
|
+
export default function (eleventyConfig) {
|
|
829
|
+
fetchFilter(eleventyConfig);
|
|
830
|
+
// Or use as plugin:
|
|
831
|
+
// eleventyConfig.addPlugin(eleventyBricks, { filters: ['fetch'] });
|
|
832
|
+
}
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
3. Use the filter in your templates:
|
|
836
|
+
|
|
837
|
+
**Fetch remote URLs:**
|
|
838
|
+
|
|
839
|
+
```njk
|
|
840
|
+
{# Fetch content from a remote URL #}
|
|
841
|
+
{% set externalContent = "https://example.com/data.json" | fetch %}
|
|
842
|
+
{{ externalContent }}
|
|
843
|
+
|
|
844
|
+
{# Fetch and parse JSON #}
|
|
845
|
+
{% set apiData = "https://api.example.com/posts" | fetch %}
|
|
846
|
+
{% set posts = apiData | fromJson %}
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
**Fetch local files:**
|
|
850
|
+
|
|
851
|
+
```njk
|
|
852
|
+
{# Fetch content from a local file (relative to input directory) #}
|
|
853
|
+
{% set localData = "_data/content.txt" | fetch %}
|
|
854
|
+
{{ localData }}
|
|
855
|
+
|
|
856
|
+
{# Include content from another file #}
|
|
857
|
+
{% set snippet = "_includes/snippets/example.md" | fetch %}
|
|
858
|
+
{{ snippet | markdown | safe }}
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
**Parameters:**
|
|
862
|
+
|
|
863
|
+
- `url`: A URL (starting with `http://` or `https://`) or a local file path (relative to the input directory)
|
|
864
|
+
|
|
865
|
+
**Features:**
|
|
866
|
+
|
|
867
|
+
- **Remote URLs**: Downloads and caches content using `@11ty/eleventy-fetch`
|
|
868
|
+
- Caches files for 1 day by default
|
|
869
|
+
- Stores cached files in `[input-dir]/_downloads/` directory
|
|
870
|
+
- Automatically revalidates after cache expires
|
|
871
|
+
- **Local files**: Reads files relative to the Eleventy input directory
|
|
872
|
+
- No caching needed for local files
|
|
873
|
+
- Supports any file type that can be read as text
|
|
874
|
+
- **Error handling**: Throws descriptive errors if fetching fails
|
|
875
|
+
- **Conditional loading**: Only available when `@11ty/eleventy-fetch` is installed
|
|
876
|
+
|
|
877
|
+
**Examples:**
|
|
878
|
+
|
|
879
|
+
```njk
|
|
880
|
+
{# Fetch and display remote content #}
|
|
881
|
+
{% set readme = "https://raw.githubusercontent.com/user/repo/main/README.md" | fetch %}
|
|
882
|
+
<div class="readme">
|
|
883
|
+
{{ readme | markdown | safe }}
|
|
884
|
+
</div>
|
|
885
|
+
|
|
886
|
+
{# Fetch JSON data from API #}
|
|
887
|
+
{% set data = "https://api.example.com/data.json" | fetch %}
|
|
888
|
+
{% set items = data | fromJson %}
|
|
889
|
+
{% for item in items %}
|
|
890
|
+
<p>{{ item.title }}</p>
|
|
891
|
+
{% endfor %}
|
|
892
|
+
|
|
893
|
+
{# Include local file content #}
|
|
894
|
+
{% set changelog = "CHANGELOG.md" | fetch %}
|
|
895
|
+
{{ changelog | markdown | safe }}
|
|
896
|
+
|
|
897
|
+
{# Fetch CSS from CDN and inline it #}
|
|
898
|
+
<style>
|
|
899
|
+
{{ "https://cdn.example.com/styles.css" | fetch }}
|
|
900
|
+
</style>
|
|
901
|
+
|
|
902
|
+
{# Reuse content across pages #}
|
|
903
|
+
{% set sharedContent = "_includes/shared/footer.html" | fetch %}
|
|
904
|
+
{{ sharedContent | safe }}
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
**Cache Directory:**
|
|
908
|
+
|
|
909
|
+
Remote files are cached in the `_downloads` folder within your input directory:
|
|
910
|
+
|
|
911
|
+
```
|
|
912
|
+
your-project/
|
|
913
|
+
├── src/ (or your input directory)
|
|
914
|
+
│ ├── _downloads/ (cached remote files)
|
|
915
|
+
│ ├── index.njk
|
|
916
|
+
│ └── ...
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
**Use Cases:**
|
|
920
|
+
|
|
921
|
+
- Fetch content from external APIs during build time
|
|
922
|
+
- Include README files from GitHub repositories
|
|
923
|
+
- Reuse content from local files across multiple pages
|
|
924
|
+
- Download and inline external CSS or JavaScript
|
|
925
|
+
- Fetch data from headless CMS or external data sources
|
|
926
|
+
- Include shared content snippets without using Eleventy's include syntax
|
|
927
|
+
|
|
928
|
+
**Note:** The filter returns raw text content. Use Eleventy's built-in filters like `| safe`, `| markdown`, or `| fromJson` to process the content as needed.
|
|
929
|
+
|
|
792
930
|
### siteData
|
|
793
931
|
|
|
794
932
|
Adds global site data to your Eleventy project, providing commonly needed values that can be accessed in all templates.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anydigital/eleventy-bricks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.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": "
|
|
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
|
}
|
package/src/eleventy.config.js
CHANGED
|
@@ -29,7 +29,7 @@ export default function (eleventyConfig) {
|
|
|
29
29
|
mdAutoRawTags: true,
|
|
30
30
|
mdAutoLinkFavicons: true,
|
|
31
31
|
siteData: true,
|
|
32
|
-
filters: ["attr", "where_in", "merge", "remove_tag", "if", "attr_concat"],
|
|
32
|
+
filters: ["attr", "where_in", "merge", "remove_tag", "if", "attr_concat", "fetch"],
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
/* Libraries */
|
|
@@ -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
|
+
}
|
package/src/filters/where_in.js
CHANGED
|
@@ -16,26 +16,22 @@ const { get } = lodash;
|
|
|
16
16
|
* @returns {Array} Filtered collection
|
|
17
17
|
*/
|
|
18
18
|
export function whereIn(collection, attrName, targetValue) {
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
38
|
-
return
|
|
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
|
-
|
|
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
|
-
|