eleventy-test 1.1.0 → 2.0.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/README.md CHANGED
@@ -1,44 +1,113 @@
1
1
  # eleventy-test
2
- Eleventy is hands-down my favourite static site generator. I also make [plugins for it](https://github.com/search?q=owner%3ADenperidge%20topic%3Aeleventy-plugin&type=repositories)! So [after a while of brute forcing my Eleventy testing](https://github.com/Denperidge/eleventy-auto-cache-buster/tree/14787bebe3bb73f4c6bd971196f3bec87812044f/tests), I thought an easier solution should exist. Hopefully this is that!
3
- - Build using multiple Eleventy versions & configurations for your tests (called scenarios) seperately. Returns the file contents per scenario output for easy access during testing.
4
- - Compatible with any test framework or method.
5
- - Automatic installation of latest/specific Eleventy version (if not found locally).
6
- - Aside from the Eleventy versions used to test, this package itself has **zero** dependencies when installing.
2
+ Multi-configuration testing for Eleventy plugins!
3
+
4
+ - Build using **multiple Eleventy versions & configurations** for your tests (called *scenarios*) seperately
5
+ - Returns the **file contents per scenario** output for easy access during testing, with **caching** for repeated usage.
6
+ - Compatible with **any test framework or method**: [configure your directory and use one function](#using-the-plugin), that's it!
7
+ - **Automatic installation** of either the **latest/specific or specific Eleventy versions** (*unless* they are found *locally*).
8
+ - Aside from the Eleventy versions used to test, this package itself has **zero dependencies** when installing.
7
9
 
8
- Want to see how it is in action? For the dogfooding fans, you can see this library in action in this [library's tests](tests/)!
10
+ Want to see how it is in action? For you [dogfooding](https://en.wikipedia.org/wiki/Eating_your_own_dog_food) fans, you can see this library in action in this [library's tests](tests/)!
9
11
 
10
12
  ## How-to
11
- ### Use the plugin
13
+ ### Using the plugin
14
+ Important concepts:
15
+ - **Input** refers to an --input dir for Eleventy. For example, your tests' index.njk and stylesheet.css will be in an input dir
16
+ - **Scenario** refers to a specific test scenario: with its own Eleventy version & config files (.eleventy.js). Optionally, a scenario can have its own input dir
17
+
12
18
  1. Install the plugin
13
- ```bash
19
+ ```sh
14
20
  npm install --save-dev eleventy-test
15
21
  yarn add -D eleventy-test
16
22
  ```
17
- 2. Create `${projectRoot}/tests/scenarios/`
18
- 3. Create a subdirectory for each scenario you wish to set up. The directory name should start with the *exact* (1.0.2) or *major* (1) version. The format should be one of the following:
19
- - `${projectRoot}/tests/scenarios/${eleventyVersion}--${label}/`
20
- - `${projectRoot}/tests/scenarios/${eleventyVersion}/`
21
- 3. Add an Eleventy configuration file to the scenario
22
- 4. Use the buildScenarios function and its output as needed
23
+ 2. Create a `tests/` directory in your package root
24
+ ```sh
25
+ # Example structure:
26
+ {project root}/
27
+ package.json
28
+ tests/
29
+ ```
30
+ 2. Optionally, create the `tests/input/` directory: a global input that scenarios will use if they don't have their own input dir
31
+ ```sh
32
+ # Example structure:
33
+ {project root}/
34
+ package.json
35
+ tests/
36
+ input/
37
+ index.njk
38
+ stylesheet.css
39
+ ```
40
+ 3. Create the `tests/scenarios/` dir. This is where you'll configure your different scenarios
41
+ ```sh
42
+ # Example structure:
43
+ {project root}/
44
+ package.json
45
+ tests/
46
+ input/
47
+ index.njk
48
+ stylesheet.css
49
+ scenarios/
50
+ ```
51
+ 4. Create a subdirectory for each scenario you wish to set up:
52
+ - The directory name should start with the *exact* (1.0.2) or *major* (1) version of Eleventy you want to use
53
+ - Optionally, a label can be added
54
+ - The format should be one of the following (or see [this library for examples](tests/scenarios/)):
55
+ - `3.1.2--custom-label/`
56
+ - `3.0.0/`
57
+ - `2--example-label/`
58
+ - `3.1.2/`
59
+ ```sh
60
+ # Example structure:
61
+ {project root}/
62
+ package.json
63
+ tests/
64
+ input/
65
+ index.njk
66
+ stylesheet.css
67
+ scenarios/
68
+ # Will use Eleventy 3.1.2
69
+ 3.1.2/
70
+ # Will use latest Eleventy 2.X
71
+ 2--no-njk/
72
+ ```
73
+ 5. Add an Eleventy configuration file to each scenario, and optionally* an `input/` directory
74
+ ```
75
+ # Example structure:
76
+ {project root}/
77
+ package.json
78
+ tests/
79
+ input/
80
+ index.njk
81
+ stylesheet.css
82
+ scenarios/
83
+ 3.1.2/
84
+ .eleventy.js
85
+ 2--no-njk/
86
+ # This scenario will use its own input dir instead of the global one
87
+ input/
88
+ index.html
89
+ index.css
90
+ .eleventy.js
91
+ ```
92
+ *In case you don't have a global input dir configured, each scenario will need to have its own
93
+
94
+ 6. Within your testing framework of choice, simply run the `buildScenarios` function. This will handle installing the right Eleventy version(s) and reading + caching the file contents back to you.
23
95
  ```js
24
96
  import { buildScenarios } from "eleventy-test";
25
97
  import test from "ava";
26
98
 
27
- const resultsAsDict = await buildScenarios({
28
- projectRoot: cwd(),
29
- returnArray: false
99
+ const results = await buildScenarios({
100
+ returnArray: false, // return as dict instead of array
101
+ enableDebug: false
30
102
  });
31
103
 
32
104
  test("Check if index.html is consistent across builds", async t => {
33
105
  t.deepEqual(
34
- await results["3--example"].getFileContent("/index.html"),
35
- await results["3.0.0--identical-example"].getFileContent("/index.html"));
106
+ await results["3.1.2"].getFileContent("/index.html"),
107
+ await results["2--no-njk"].getFileContent("/index.html"));
36
108
  });
37
109
  ```
38
110
 
39
- And that's it! eleventy-test will handle installing the right versons and reading (and caching) the file contents back to you.
40
-
41
-
42
111
  > Note: you might want to add `eleventy-test-out/` to your .gitignore file!
43
112
 
44
113
  ### Run/develop/test locally
@@ -48,11 +117,15 @@ git clone https://github.com/Denperidge/eleventy-test
48
117
  cd eleventy-test
49
118
  yarn install
50
119
 
51
- yarn watch # Watch for changes
120
+ yarn watch # Build & watch for changes. Should be active during development/testing of eleventy-test
52
121
  yarn build # Build
53
122
  yarn start # Run built js as module (see bottom of index.ts require.main === module)
54
- yarn test # Run the tests from tests/test.mjs
123
+ yarn test # Using the built js, run the tests from tests/test.mjs
55
124
  ```
56
125
 
126
+ ## Explanation
127
+ ### Why?
128
+ Eleventy is hands-down my favourite static site generator. I also make [plugins for it](https://github.com/search?q=owner%3ADenperidge%20topic%3Aeleventy-plugin&type=repositories)! So [after a while of brute forcing my Eleventy testing for different setups](https://github.com/Denperidge/eleventy-auto-cache-buster/blob/14787bebe3bb73f4c6bd971196f3bec87812044f/tests/.eleventy.js), I thought an easier solution should exist. Hopefully this is that!
129
+
57
130
  ## License
58
131
  This project is licensed under the [MIT License](LICENSE).
@@ -1,12 +1,36 @@
1
- export declare function recursiveFindFiles(dir: string, files?: string[]): string[];
2
- export default class ScenarioOutput {
1
+ /**
2
+ *
3
+ * @param dir directory to search in
4
+ * @param files currently found files
5
+ * @returns promise for array of filepaths found in dir
6
+ */
7
+ export declare function _recursiveFindFiles(dir: string, files?: string[]): Promise<string[]>;
8
+ /**
9
+ * @class easy listing & reading of scenario outputs/eleventy-test-out files
10
+ */
11
+ export declare class ScenarioOutput {
12
+ /** path to this scenario's eleventy-test-out */
3
13
  eleventyOutputDir: string;
14
+ /** title of this scenario */
4
15
  title: string;
5
- private _files;
16
+ private _filepaths;
6
17
  private cache;
7
18
  constructor(pEleventyOutputDir: string, pTitle: string);
8
- get files(): {
9
- [key: string]: () => string;
10
- };
11
- getFileContent(filename: any): Promise<string>;
19
+ /**
20
+ * @param pEleventyOutputDir path to "eleventy-test-out"
21
+ * @param pTitle scenario title
22
+ */
23
+ static create(pEleventyOutputDir: string, pTitle: string): Promise<ScenarioOutput>;
24
+ /**
25
+ * @returns set of relative paths to output files
26
+ */
27
+ get filepaths(): Set<string>;
28
+ /**
29
+ * Gets file contents from internal cache,
30
+ * reading file contents into the cache if needed
31
+ *
32
+ * @param filepath relative path to file to read
33
+ * @returns promise for text contents
34
+ */
35
+ getFileContent(filepath: string): Promise<string>;
12
36
  }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Functions for debug logging
3
+ * - enable/disable debug logs: @see _setDebug
4
+ * - log to console if debugging is enabled: @see debug
5
+ */
6
+ /**
7
+ * @param enabled if true, enabled debug logging
8
+ */
9
+ export declare function _setDebug(enabled: boolean): void;
10
+ /**
11
+ * @param message If debug is enabled (@see setDebug), log messages to console
12
+ */
13
+ export declare function debug(...message: Array<String>): void;
@@ -1,12 +1,77 @@
1
- import ScenarioOutput from "./ScenarioOutput";
2
- export declare function determineInstalledEleventyVersions(projectRoot?: string): Promise<{
1
+ import { ScenarioOutput } from "./ScenarioOutput";
2
+ import { IgitHubApiTags } from "./githubApi";
3
+ /**
4
+ * Asynchronously check if passed filepath exists
5
+ *
6
+ * @param filepath filepath to check
7
+ * @returns true if file exists, false if it doesn't
8
+ * @throws any non-ENOENT errors from fsPromises.stat
9
+ */
10
+ export declare function _exists(filepath: string): Promise<Boolean>;
11
+ /**
12
+ *
13
+ * @param projectRoot project root directory of which to check the node_modules/ dir.
14
+ * @default process.cwd()
15
+ * @returns promise for a dictionary in the style of {"3.0.0": "/path/to/@11ty/eleventy3.0.0"}
16
+ */
17
+ export declare function _determineInstalledEleventyVersions(projectRoot?: string): Promise<{
3
18
  [key: string]: string;
4
19
  }>;
5
- export declare function ensureEleventyExists(projectRoot: string, eleventyVersion: string): Promise<string>;
6
- export declare function buildEleventy({ eleventyVersion, scenarioDir, scenarioName, projectRoot, globalInputDir }: {
7
- eleventyVersion: any;
8
- scenarioDir: any;
9
- scenarioName: any;
20
+ /**
21
+ * Return first/latest version using the major version
22
+ *
23
+ * @param majorVersion major version to lookup (example: 2)
24
+ * @param allVersions array including all Eleventy versions
25
+ * @returns latest semantic Eleventy version that is not an alpha/beta (example: 2.0.1)
26
+ * @throws {Error} if no major versions can be found
27
+ * @see _getReleasedEleventyVersions
28
+ * @see IgitHubApiTags
29
+ */
30
+ export declare function _majorToSemanticEleventyVersion(majorVersion: string, allVersions: Array<IgitHubApiTags>): string;
31
+ /**
32
+ *
33
+ * @param dirname directory name from the scenario
34
+ * @param versions array of Eleventy release tags
35
+ * @returns promise for a string of the extracted eleventy version; even if only a major number is provided
36
+ * @see _getReleasedEleventyVersions
37
+ * @see IgitHubApiTags
38
+ */
39
+ export declare function _dirnameToEleventyVersion(dirname: string, versions: Array<IgitHubApiTags>): string;
40
+ /**
41
+ *
42
+ * @param eleventyVersion semantic version to look for (for example: "3", "3.1.2")
43
+ * @param projectRoot project root directory
44
+ * @default process.cwd()
45
+ *
46
+ * @param filename if filename exists, use install command below (for example: "package-lock.json", "yarn.lock")
47
+ * @param command command to prefix to install eleventy (for example: "yarn add", "npm install")
48
+ * @returns promise for false/true, depending on whether eleventy install was succesful
49
+ */
50
+ export declare function _installEleventyIfPkgManagerFound(eleventyVersion: string, projectRoot: string, filename: string, command: string): Promise<boolean>;
51
+ /**
52
+ *
53
+ * @param eleventyVersion semantic version to look for (for example "3", "3.0.0")
54
+ * @param projectRoot project root directory
55
+ * @default process.cwd()
56
+ * @returns promise for the install directory of specified eleventy version
57
+ */
58
+ export declare function _ensureEleventyExists(eleventyVersion: string, projectRoot?: string): Promise<string>;
59
+ interface IbuildEleventyArgs {
10
60
  projectRoot?: string;
11
- globalInputDir: any;
12
- }): Promise<ScenarioOutput>;
61
+ globalInputDir?: string;
62
+ scenarioDir: string;
63
+ scenarioName: string;
64
+ }
65
+ /**
66
+ * **Note:** the arguments below need to be passed in an object. @see IbuildEleventyArgs
67
+ *
68
+ * @param scenarioDir path towards directory that holds all test scenarios
69
+ * @param scenarioName name of the test scenario to run
70
+ * @param projectRoot project root directory
71
+ * @default process.cwd()
72
+ * @param globalInputDir path to the input directory to be used if scenarios do not provide their own
73
+ *
74
+ * @returns promise for the resulting @see ScenarioOutput
75
+ */
76
+ export declare function buildEleventy({ projectRoot, globalInputDir, scenarioDir, scenarioName }: IbuildEleventyArgs): Promise<ScenarioOutput>;
77
+ export {};
@@ -0,0 +1,39 @@
1
+ export declare const DEFAULT_CACHE_PATH = "tests/eleventy-test-out/cache.json";
2
+ /**
3
+ *
4
+ * @param dataToSave variable you want to save in cache
5
+ * @param filepath path to json file the cache should be written to
6
+ * @default tests/eleventy-test-out/cache.json
7
+ * @returns void promise
8
+ */
9
+ export declare function _cacheWrite(dataToSave: any, filepath?: string): Promise<void>;
10
+ /**
11
+ * This function works like as follows:
12
+ * - Check for an existing cache
13
+ * - If the cache exists, check if its outdated (not from today)
14
+ * - If it's not outdated, return cache data
15
+ * - If the cache doesn't exist, run passed function async
16
+ * - Store returned passed function data in cache & return data
17
+ *
18
+ * @param fetchData async function to run to get new cache data
19
+ * @param filepath path to cache json file
20
+ * @returns promise for the cache data
21
+ */
22
+ export declare function _cache(fetchData: Function, filepath?: string): Promise<any>;
23
+ export interface IgitHubApiTags {
24
+ name: string;
25
+ zipball_url: string;
26
+ tarball_url: string;
27
+ node_id: string;
28
+ commit: {
29
+ sha: string;
30
+ url: string;
31
+ };
32
+ }
33
+ /**
34
+ * Return data from GitHub API, from the passed ?page= parameter
35
+ *
36
+ * @returns GitHub tag info. @see IgitHubApiTags
37
+ */
38
+ export declare function _requestReleasedEleventyVersions(page?: number): Promise<Array<IgitHubApiTags>>;
39
+ export declare function _getReleasedEleventyVersions(): Promise<Array<IgitHubApiTags>>;
package/dist/index.d.ts CHANGED
@@ -1,10 +1,13 @@
1
- import ScenarioOutput from "./ScenarioOutput";
1
+ import { ScenarioOutput } from "./ScenarioOutput";
2
2
  export * from "./eleventyUtils";
3
+ export * from "./ScenarioOutput";
4
+ export * from "./githubApi";
3
5
  interface IbuildScenariosArgs {
4
- projectRoot: string;
6
+ projectRoot?: string;
5
7
  returnArray?: boolean;
6
8
  scenariosDir?: string;
7
9
  globalInputDir?: string;
10
+ enableDebug?: boolean;
8
11
  }
9
12
  interface IbuildScenariosArrayArgs extends IbuildScenariosArgs {
10
13
  returnArray?: true;
@@ -12,6 +15,18 @@ interface IbuildScenariosArrayArgs extends IbuildScenariosArgs {
12
15
  interface IbuildScenariosDictArgs extends IbuildScenariosArgs {
13
16
  returnArray?: false;
14
17
  }
18
+ /**
19
+ * **Note:** the arguments below need to be passed in an object. @see IbuildScenariosArgs
20
+ * @param projectRoot project root directory
21
+ * @default process.cwd()
22
+ * @param returnArray if set to true, return array
23
+ * @param scenariosDir path to directory that holds all scenarios
24
+ * @param globalInputDir path to the input directory to be used if scenarios do not provide their own
25
+ * @param enableDebug enable debug logging if true
26
+ *
27
+ * @returns (if returnArray=true) return scenario outputs as array
28
+ * @returns (if returnArray=false) return scenario outputs in a dict, using scenario names as key
29
+ */
15
30
  export declare function buildScenarios(opts: IbuildScenariosArrayArgs): Promise<ScenarioOutput[]>;
16
31
  export declare function buildScenarios(opts: IbuildScenariosDictArgs): Promise<{
17
32
  [key: string]: ScenarioOutput;
package/dist/index.js CHANGED
@@ -17,98 +17,261 @@ var __copyProps = (to, from, except, desc) => {
17
17
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
18
 
19
19
  // src/index.ts
20
- var src_exports = {};
21
- __export(src_exports, {
20
+ var index_exports = {};
21
+ __export(index_exports, {
22
+ DEFAULT_CACHE_PATH: () => DEFAULT_CACHE_PATH,
23
+ ScenarioOutput: () => ScenarioOutput,
24
+ _cache: () => _cache,
25
+ _cacheWrite: () => _cacheWrite,
26
+ _determineInstalledEleventyVersions: () => _determineInstalledEleventyVersions,
27
+ _dirnameToEleventyVersion: () => _dirnameToEleventyVersion,
28
+ _ensureEleventyExists: () => _ensureEleventyExists,
29
+ _exists: () => _exists,
30
+ _getReleasedEleventyVersions: () => _getReleasedEleventyVersions,
31
+ _installEleventyIfPkgManagerFound: () => _installEleventyIfPkgManagerFound,
32
+ _majorToSemanticEleventyVersion: () => _majorToSemanticEleventyVersion,
33
+ _recursiveFindFiles: () => _recursiveFindFiles,
34
+ _requestReleasedEleventyVersions: () => _requestReleasedEleventyVersions,
22
35
  buildEleventy: () => buildEleventy,
23
- buildScenarios: () => buildScenarios,
24
- determineInstalledEleventyVersions: () => determineInstalledEleventyVersions,
25
- ensureEleventyExists: () => ensureEleventyExists
36
+ buildScenarios: () => buildScenarios
26
37
  });
27
- module.exports = __toCommonJS(src_exports);
38
+ module.exports = __toCommonJS(index_exports);
39
+ var import_path4 = require("path");
28
40
  var import_process2 = require("process");
29
- var import_path3 = require("path");
30
- var import_promises2 = require("fs/promises");
31
- var import_https = require("https");
41
+ var import_promises4 = require("fs/promises");
32
42
 
33
43
  // src/eleventyUtils.ts
34
44
  var import_child_process = require("child_process");
35
- var import_fs2 = require("fs");
36
- var import_promises = require("fs/promises");
37
- var import_path2 = require("path");
45
+ var import_promises3 = require("fs/promises");
46
+ var import_path3 = require("path");
38
47
  var import_process = require("process");
39
48
 
49
+ // src/debug.ts
50
+ var debugEnabled = false;
51
+ function _setDebug(enabled) {
52
+ debugEnabled = enabled;
53
+ }
54
+ function debug(...message) {
55
+ if (debugEnabled) {
56
+ message.forEach((msg) => console.log(msg));
57
+ }
58
+ }
59
+
40
60
  // src/ScenarioOutput.ts
41
61
  var import_path = require("path");
42
- var import_fs = require("fs");
43
- function recursiveFindFiles(dir, files = []) {
62
+ var import_promises = require("fs/promises");
63
+ async function _recursiveFindFiles(dir, files = []) {
44
64
  const foundDirs = [];
45
- (0, import_fs.readdirSync)(dir).forEach((name) => {
65
+ const entries = await (0, import_promises.readdir)(dir);
66
+ await Promise.all(entries.map(async (name) => {
46
67
  const path = (0, import_path.join)(dir, name);
47
- const stat = (0, import_fs.lstatSync)(path);
48
- if (stat.isDirectory()) {
68
+ const stat2 = await (0, import_promises.lstat)(path);
69
+ if (stat2.isDirectory()) {
49
70
  foundDirs.push(path);
50
- } else if (stat.isFile()) {
71
+ } else if (stat2.isFile()) {
51
72
  files.push(path);
52
73
  }
53
- });
54
- foundDirs.forEach((dir2) => {
55
- files = recursiveFindFiles(dir2, files);
56
- });
74
+ }));
75
+ for (const subDir of foundDirs) {
76
+ files = await _recursiveFindFiles(subDir, files);
77
+ }
57
78
  return files;
58
79
  }
59
- var ScenarioOutput = class {
80
+ var ScenarioOutput = class _ScenarioOutput {
81
+ /** path to this scenario's eleventy-test-out */
60
82
  eleventyOutputDir;
83
+ /** title of this scenario */
61
84
  title;
62
- _files;
85
+ _filepaths;
63
86
  cache;
64
87
  constructor(pEleventyOutputDir, pTitle) {
65
- this._files = {};
88
+ this._filepaths = /* @__PURE__ */ new Set();
66
89
  this.cache = {};
67
90
  this.title = pTitle;
68
91
  this.eleventyOutputDir = pEleventyOutputDir;
69
- recursiveFindFiles(this.eleventyOutputDir).forEach((filepath) => {
70
- this._files[filepath.replace(this.eleventyOutputDir, "")] = function() {
71
- return (0, import_fs.readFileSync)(filepath, { encoding: "utf-8" });
72
- };
73
- });
74
92
  }
75
- get files() {
76
- return this._files;
93
+ /**
94
+ * @param pEleventyOutputDir path to "eleventy-test-out"
95
+ * @param pTitle scenario title
96
+ */
97
+ static async create(pEleventyOutputDir, pTitle) {
98
+ const instance = new _ScenarioOutput(pEleventyOutputDir, pTitle);
99
+ const allFiles = await _recursiveFindFiles(instance.eleventyOutputDir);
100
+ for (const filepath of allFiles) {
101
+ instance._filepaths.add(filepath.replace(instance.eleventyOutputDir, ""));
102
+ }
103
+ return instance;
77
104
  }
78
- getFileContent(filename) {
79
- return new Promise(async (resolve, reject) => {
80
- if (!Object.keys(this.cache).includes(filename)) {
81
- this.cache[filename] = this._files[filename]();
82
- }
83
- resolve(this.cache[filename]);
84
- });
105
+ /**
106
+ * @returns set of relative paths to output files
107
+ */
108
+ get filepaths() {
109
+ return this._filepaths;
110
+ }
111
+ /**
112
+ * Gets file contents from internal cache,
113
+ * reading file contents into the cache if needed
114
+ *
115
+ * @param filepath relative path to file to read
116
+ * @returns promise for text contents
117
+ */
118
+ async getFileContent(filepath) {
119
+ if (!this._filepaths.has(filepath)) {
120
+ throw new Error(`Can't find "${filepath}" in files. Available files: ${Array.from(this._filepaths).join(", ")}`);
121
+ }
122
+ if (!(filepath in this.cache)) {
123
+ this.cache[filepath] = await (0, import_promises.readFile)((0, import_path.join)(this.eleventyOutputDir, filepath), { encoding: "utf-8" });
124
+ }
125
+ return this.cache[filepath];
85
126
  }
86
127
  };
87
128
 
129
+ // src/githubApi.ts
130
+ var import_promises2 = require("fs/promises");
131
+ var import_path2 = require("path");
132
+ var import_https = require("https");
133
+ var DEFAULT_CACHE_PATH = "tests/eleventy-test-out/cache.json";
134
+ async function _cacheWrite(dataToSave, filepath = DEFAULT_CACHE_PATH) {
135
+ const data = {
136
+ "datetime": Date.now() - 100,
137
+ // -100 to fix race condition for tests
138
+ "data": dataToSave
139
+ };
140
+ const dir = (0, import_path2.dirname)(filepath);
141
+ if (!await _exists(dir)) {
142
+ await (0, import_promises2.mkdir)(dir, { recursive: true });
143
+ }
144
+ return (0, import_promises2.writeFile)(filepath, JSON.stringify(data));
145
+ }
146
+ async function _cache(fetchData, filepath = DEFAULT_CACHE_PATH) {
147
+ return new Promise(async (resolve, reject) => {
148
+ if (await _exists(filepath)) {
149
+ const cache = JSON.parse(await (0, import_promises2.readFile)(filepath, { encoding: "utf-8" }));
150
+ const cacheDatetime = new Date(cache.datetime);
151
+ const today = /* @__PURE__ */ new Date();
152
+ if (cacheDatetime.toDateString() == today.toDateString()) {
153
+ resolve(cache.data);
154
+ return;
155
+ }
156
+ }
157
+ const cacheData = await fetchData();
158
+ await _cacheWrite(cacheData, filepath);
159
+ resolve(cacheData);
160
+ });
161
+ }
162
+ async function _requestReleasedEleventyVersions(page = 1) {
163
+ debug("Pulling Eleventy tags...");
164
+ return new Promise((resolve, reject) => {
165
+ (0, import_https.get)({
166
+ hostname: "api.github.com",
167
+ path: `/repos/11ty/eleventy/tags?per_page=100&page=${page}`,
168
+ headers: {
169
+ "User-Agent": "Mozilla/5.0"
170
+ }
171
+ }, (res) => {
172
+ let data = [];
173
+ res.on("data", (chunk) => {
174
+ data.push(chunk);
175
+ }).on("end", async () => {
176
+ debug("Parsing Eleventy tags API response...");
177
+ let tags = JSON.parse(
178
+ Buffer.concat(data).toString("utf-8")
179
+ );
180
+ resolve(tags);
181
+ }).on("error", (err) => {
182
+ throw err;
183
+ });
184
+ });
185
+ });
186
+ }
187
+ async function _getReleasedEleventyVersions() {
188
+ return new Promise((resolve, reject) => {
189
+ let page = 1;
190
+ let out = [];
191
+ const timer = setInterval(async () => {
192
+ const req = await _requestReleasedEleventyVersions(page);
193
+ page++;
194
+ out = out.concat(req);
195
+ if (req.length < 100) {
196
+ clearInterval(timer);
197
+ resolve(out);
198
+ }
199
+ }, 1500);
200
+ });
201
+ }
202
+
88
203
  // src/eleventyUtils.ts
89
- async function determineInstalledEleventyVersions(projectRoot = (0, import_process.cwd)()) {
90
- let eleventyPkgs = await (0, import_promises.readdir)((0, import_path2.join)(projectRoot, "node_modules/@11ty/"));
91
- const eleventyRegex = new RegExp(/eleventy(\d|$)/m);
92
- eleventyPkgs = eleventyPkgs.filter((name) => eleventyRegex.test(name));
93
- const versions2 = {};
94
- for (let i = 0; i < eleventyPkgs.length; i++) {
95
- const eleventyPkg = eleventyPkgs[i];
96
- const eleventyPkgDir = (0, import_path2.join)(projectRoot, "node_modules/@11ty/", eleventyPkg);
97
- const version = JSON.parse(
98
- await (0, import_promises.readFile)(
99
- (0, import_path2.join)(eleventyPkgDir, "package.json"),
100
- { encoding: "utf-8" }
101
- )
102
- ).version;
103
- versions2[version] = eleventyPkgDir;
204
+ async function _exists(filepath) {
205
+ return new Promise((resolve, reject) => {
206
+ (0, import_promises3.stat)(filepath).then((stats) => {
207
+ resolve(true);
208
+ }).catch((err) => {
209
+ if (err.code && err.code == "ENOENT") {
210
+ resolve(false);
211
+ } else {
212
+ reject(err);
213
+ }
214
+ });
215
+ });
216
+ }
217
+ async function _determineInstalledEleventyVersions(projectRoot = (0, import_process.cwd)()) {
218
+ const eleventyPkgsDir = (0, import_path3.join)(projectRoot, "node_modules/@11ty/");
219
+ const versions = {};
220
+ debug("Determining installed Eleventy versions in " + eleventyPkgsDir);
221
+ if (await _exists(eleventyPkgsDir)) {
222
+ let eleventyPkgs = await (0, import_promises3.readdir)(eleventyPkgsDir);
223
+ debug(`Found the following installed packages from @11ty: ${eleventyPkgs}`);
224
+ const eleventyRegex = new RegExp(/eleventy(\d|$)/m);
225
+ eleventyPkgs = eleventyPkgs.filter((name) => eleventyRegex.test(name));
226
+ debug(`Filtered non-main-eleventy packages. Results: ${eleventyPkgs}`);
227
+ for (let i = 0; i < eleventyPkgs.length; i++) {
228
+ const eleventyPkg = eleventyPkgs[i];
229
+ const eleventyPkgDir = (0, import_path3.join)(projectRoot, "node_modules/@11ty/", eleventyPkg);
230
+ const version = JSON.parse(
231
+ await (0, import_promises3.readFile)(
232
+ (0, import_path3.join)(eleventyPkgDir, "package.json"),
233
+ { encoding: "utf-8" }
234
+ )
235
+ ).version;
236
+ versions[version] = eleventyPkgDir;
237
+ debug(`Found ${version} at ${eleventyPkgDir}`);
238
+ }
104
239
  }
105
- return versions2;
240
+ return versions;
106
241
  }
107
- async function installEleventyIfPkgManagerFound(eleventyVersion, projectRoot, filename, command) {
242
+ function _majorToSemanticEleventyVersion(majorVersion, allVersions) {
243
+ for (let i = 0; i < allVersions.length; i++) {
244
+ const version = allVersions[i];
245
+ debug("Checking " + version);
246
+ if (!version.name.includes("-") && version.name[1] == majorVersion) {
247
+ const eleventyVersion = version.name.substring(1);
248
+ debug("Determined latest of relevant major version for: " + eleventyVersion);
249
+ return eleventyVersion;
250
+ }
251
+ }
252
+ const versions = allVersions.map((version) => {
253
+ return version.name;
254
+ });
255
+ throw new Error(`Couldn't determine Eleventy version from ${majorVersion}
256
+ versions: ${versions.join(",")}`);
257
+ }
258
+ function _dirnameToEleventyVersion(dirname2, versions) {
259
+ let eleventyVersion = dirname2.includes("@") ? dirname2.substring(dirname2.lastIndexOf("@") + 1) : dirname2;
260
+ debug(`eleventyVersion from dirname: ${eleventyVersion}`);
261
+ if (eleventyVersion.length < 5) {
262
+ debug("eleventyVersion length is under 5, and as such not a full semantic version. Determining latest...");
263
+ eleventyVersion = _majorToSemanticEleventyVersion(eleventyVersion[0], versions);
264
+ }
265
+ return eleventyVersion;
266
+ }
267
+ async function _installEleventyIfPkgManagerFound(eleventyVersion, projectRoot = (0, import_process.cwd)(), filename, command) {
268
+ debug(`Attempting to find a package manager to install Eleventy ${eleventyVersion} with`);
108
269
  return new Promise(async (resolve, reject) => {
109
- if ((0, import_fs2.existsSync)((0, import_path2.join)(projectRoot, filename))) {
270
+ if (await _exists((0, import_path3.join)(projectRoot, filename))) {
110
271
  try {
272
+ debug("Running Eleventy " + eleventyVersion);
111
273
  (0, import_child_process.execSync)(`${command} @11ty/eleventy${eleventyVersion}@npm:@11ty/eleventy@${eleventyVersion}`, { cwd: projectRoot });
274
+ debug("Done running Eleventy " + eleventyVersion);
112
275
  resolve(true);
113
276
  } catch (e) {
114
277
  throw e;
@@ -119,22 +282,23 @@ async function installEleventyIfPkgManagerFound(eleventyVersion, projectRoot, fi
119
282
  }
120
283
  });
121
284
  }
122
- async function ensureEleventyExists(projectRoot, eleventyVersion) {
285
+ async function _ensureEleventyExists(eleventyVersion, projectRoot = (0, import_process.cwd)()) {
286
+ debug(`Ensuring Eleventy ${eleventyVersion} exists`);
123
287
  return new Promise(async (resolve, reject) => {
124
- const versions2 = await determineInstalledEleventyVersions(projectRoot);
125
- if (Object.keys(versions2).includes(eleventyVersion)) {
126
- resolve(versions2[eleventyVersion]);
288
+ const versions = await _determineInstalledEleventyVersions(projectRoot);
289
+ if (Object.keys(versions).includes(eleventyVersion)) {
290
+ resolve(versions[eleventyVersion]);
127
291
  } else {
128
292
  console.log(`Eleventy version ${eleventyVersion} could not be found. Installing...`);
129
- const eleventyDir = (0, import_path2.join)(projectRoot, "node_modules/@11ty/eleventy" + eleventyVersion);
130
- if (await installEleventyIfPkgManagerFound(
293
+ const eleventyDir = (0, import_path3.join)(projectRoot, "node_modules/@11ty/eleventy" + eleventyVersion);
294
+ if (await _installEleventyIfPkgManagerFound(
131
295
  eleventyVersion,
132
296
  projectRoot,
133
297
  "package-lock.json",
134
298
  "npm install --save-dev"
135
299
  )) {
136
300
  resolve(eleventyDir);
137
- } else if (await installEleventyIfPkgManagerFound(
301
+ } else if (await _installEleventyIfPkgManagerFound(
138
302
  eleventyVersion,
139
303
  projectRoot,
140
304
  "yarn.lock",
@@ -148,39 +312,47 @@ async function ensureEleventyExists(projectRoot, eleventyVersion) {
148
312
  });
149
313
  }
150
314
  async function buildEleventy({
151
- eleventyVersion,
152
- scenarioDir,
153
- scenarioName,
154
315
  projectRoot = (0, import_process.cwd)(),
155
- globalInputDir
316
+ globalInputDir,
317
+ scenarioDir,
318
+ scenarioName
156
319
  }) {
320
+ const versions = await _cache(_getReleasedEleventyVersions);
321
+ debug("Parsing Eleventy version of scenario " + scenarioDir);
322
+ let eleventyVersion = _dirnameToEleventyVersion(scenarioName, versions);
323
+ debug("Running buildEleventy", "scenarioDir: " + scenarioDir, "Eleventy version: " + eleventyVersion);
157
324
  return new Promise(async (resolve, reject) => {
158
- const eleventyDir = await ensureEleventyExists(projectRoot, eleventyVersion);
325
+ debug("Finding package for Eleventy " + eleventyVersion);
326
+ const eleventyDir = await _ensureEleventyExists(eleventyVersion, projectRoot);
327
+ debug("Found package for Eleventy " + eleventyVersion);
159
328
  const bin = JSON.parse(
160
- await (0, import_promises.readFile)(
161
- (0, import_path2.join)(eleventyDir, "package.json"),
329
+ await (0, import_promises3.readFile)(
330
+ (0, import_path3.join)(eleventyDir, "package.json"),
162
331
  { encoding: "utf-8" }
163
332
  )
164
333
  ).bin.eleventy;
165
- const pathToBin = (0, import_path2.join)(eleventyDir, bin);
166
- const scenarioInputDir = (0, import_path2.join)(scenarioDir, "input");
334
+ const pathToBin = (0, import_path3.join)(eleventyDir, bin);
335
+ debug(`Found entrypoint for Eleventy ${eleventyVersion} at ${pathToBin}`);
336
+ const scenarioInputDir = (0, import_path3.join)(scenarioDir, "input");
167
337
  let inputDir;
168
- try {
169
- await (0, import_promises.access)(scenarioInputDir);
338
+ debug(`Checking whether to use scenario (${scenarioInputDir}) or global input (${globalInputDir})...`);
339
+ if (await _exists(scenarioInputDir)) {
340
+ debug("Using scenario input");
170
341
  inputDir = scenarioInputDir;
171
- } catch {
172
- try {
173
- await (0, import_promises.access)(globalInputDir);
174
- inputDir = globalInputDir;
175
- } catch {
176
- }
342
+ } else if (globalInputDir && await _exists(globalInputDir)) {
343
+ debug("Using global input dir");
344
+ inputDir = globalInputDir;
177
345
  }
346
+ debug("inputDir: " + inputDir);
178
347
  if (inputDir == void 0) {
179
- throw Error("inputDir is undefined!");
348
+ throw new Error("inputDir is undefined! Either create a global input dir or one for the scenario specifically");
180
349
  }
181
- const outputDir = (0, import_path2.join)(scenarioDir, "eleventy-test-out");
182
- await (0, import_promises.rm)(outputDir, { force: true, recursive: true });
350
+ const outputDir = (0, import_path3.join)(scenarioDir, "eleventy-test-out");
351
+ debug("Cleaning old test output");
352
+ await (0, import_promises3.rm)(outputDir, { force: true, recursive: true });
353
+ debug("Cleaned");
183
354
  try {
355
+ debug("Creating Eleventy process...");
184
356
  const out = (0, import_child_process.fork)(
185
357
  pathToBin,
186
358
  ["--input", inputDir, "--output", outputDir],
@@ -190,7 +362,8 @@ async function buildEleventy({
190
362
  console.log(msg);
191
363
  });
192
364
  out.on("close", async (code) => {
193
- resolve(new ScenarioOutput(outputDir, scenarioName));
365
+ debug(`Eleventy ${eleventyVersion}/${scenarioName} finished`);
366
+ resolve(await ScenarioOutput.create(outputDir, scenarioName));
194
367
  });
195
368
  } catch (e) {
196
369
  throw e;
@@ -199,74 +372,34 @@ async function buildEleventy({
199
372
  }
200
373
 
201
374
  // src/index.ts
202
- var versions;
203
- async function scenarioDirnameToEleventyVersion(scenarioDirname) {
204
- let eleventyVersion = scenarioDirname.includes("--") ? scenarioDirname.split("--")[0] : scenarioDirname;
205
- if (eleventyVersion.length < 5) {
206
- const scenarioMajorVersion = scenarioDirname[0];
207
- if (versions == void 0) {
208
- console.log("Pulling Eleventy tags...");
209
- versions = await new Promise((resolve, reject) => {
210
- (0, import_https.get)({
211
- hostname: "api.github.com",
212
- path: "/repos/11ty/eleventy/tags",
213
- headers: {
214
- "User-Agent": "Mozilla/5.0"
215
- }
216
- }, (res) => {
217
- let data = [];
218
- res.on("data", (chunk) => {
219
- data.push(chunk);
220
- }).on("end", () => {
221
- console.log("Parsing API response...");
222
- resolve(
223
- JSON.parse(
224
- Buffer.concat(data).toString("utf-8")
225
- )
226
- );
227
- }).on("error", (err) => {
228
- throw err;
229
- });
230
- });
231
- });
232
- }
233
- for (let i = 0; i < versions.length; i++) {
234
- const version = versions[i];
235
- if (!version.name.includes("-") && version.name[1] == scenarioMajorVersion) {
236
- eleventyVersion = version.name.substring(1);
237
- break;
238
- }
239
- }
240
- }
241
- return eleventyVersion;
242
- }
243
- async function buildScenarios({ projectRoot = (0, import_process2.cwd)(), returnArray = true, scenariosDir = "tests/scenarios/", globalInputDir = "tests/input" }) {
375
+ async function buildScenarios({ projectRoot = (0, import_process2.cwd)(), returnArray = true, scenariosDir = "tests/scenarios/", globalInputDir = "tests/input", enableDebug = false }) {
376
+ _setDebug(enableDebug);
377
+ debug("If you can see this, debugging has been enabled. Starting buildScenarios");
244
378
  return new Promise(async (resolve, reject) => {
245
- scenariosDir = (0, import_path3.isAbsolute)(scenariosDir) ? scenariosDir : (0, import_path3.join)(projectRoot, scenariosDir);
246
- globalInputDir = (0, import_path3.isAbsolute)(globalInputDir) ? globalInputDir : (0, import_path3.join)(projectRoot, globalInputDir);
247
- try {
248
- await (0, import_promises2.access)(globalInputDir);
249
- } catch {
250
- globalInputDir = "undefined";
379
+ scenariosDir = (0, import_path4.isAbsolute)(scenariosDir) ? scenariosDir : (0, import_path4.join)(projectRoot, scenariosDir);
380
+ if (globalInputDir) {
381
+ globalInputDir = (0, import_path4.isAbsolute)(globalInputDir) ? globalInputDir : (0, import_path4.join)(projectRoot, globalInputDir);
251
382
  }
383
+ debug(`scenariosDir: ${scenariosDir}`, `globalInputDir: ${globalInputDir}`);
252
384
  try {
253
- const scenarioDirs = await (0, import_promises2.readdir)(scenariosDir, { recursive: false, encoding: "utf-8" });
385
+ const scenarioDirs = await (0, import_promises4.readdir)(scenariosDir, { recursive: false, encoding: "utf-8" });
254
386
  const scenarioOutputs = [];
387
+ debug(`Found scenario dirs: ${scenarioDirs}`);
255
388
  for (let i = 0; i < scenarioDirs.length; i++) {
256
389
  const scenarioDirname = scenarioDirs[i];
257
- const scenarioDir = (0, import_path3.join)(scenariosDir, scenarioDirname);
258
- let scenarioEleventyVersion = await scenarioDirnameToEleventyVersion(scenarioDirname);
390
+ const scenarioDir = (0, import_path4.join)(scenariosDir, scenarioDirname);
259
391
  scenarioOutputs.push(await buildEleventy({
260
- eleventyVersion: scenarioEleventyVersion,
261
- scenarioName: scenarioDirname,
262
- globalInputDir,
263
392
  projectRoot,
393
+ globalInputDir,
394
+ scenarioName: scenarioDirname,
264
395
  scenarioDir
265
396
  }));
266
397
  }
267
398
  if (returnArray) {
399
+ debug("Returning as array...");
268
400
  resolve(scenarioOutputs);
269
401
  } else {
402
+ debug("Returning as object...");
270
403
  const returnDict = {};
271
404
  scenarioOutputs.forEach((scenarioOutput) => {
272
405
  returnDict[scenarioOutput.title] = scenarioOutput;
@@ -280,13 +413,25 @@ async function buildScenarios({ projectRoot = (0, import_process2.cwd)(), return
280
413
  }
281
414
  if (require.main === module) {
282
415
  buildScenarios({
283
- projectRoot: (0, import_process2.cwd)()
416
+ projectRoot: (0, import_process2.cwd)(),
417
+ enableDebug: true
284
418
  });
285
419
  }
286
420
  // Annotate the CommonJS export names for ESM import in node:
287
421
  0 && (module.exports = {
422
+ DEFAULT_CACHE_PATH,
423
+ ScenarioOutput,
424
+ _cache,
425
+ _cacheWrite,
426
+ _determineInstalledEleventyVersions,
427
+ _dirnameToEleventyVersion,
428
+ _ensureEleventyExists,
429
+ _exists,
430
+ _getReleasedEleventyVersions,
431
+ _installEleventyIfPkgManagerFound,
432
+ _majorToSemanticEleventyVersion,
433
+ _recursiveFindFiles,
434
+ _requestReleasedEleventyVersions,
288
435
  buildEleventy,
289
- buildScenarios,
290
- determineInstalledEleventyVersions,
291
- ensureEleventyExists
436
+ buildScenarios
292
437
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eleventy-test",
3
- "version": "1.1.0",
3
+ "version": "2.0.0",
4
4
  "description": "Multi-configuration testing for Eleventy plugins",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/Denperidge/eleventy-test.git",
@@ -15,19 +15,23 @@
15
15
  "build": "npm-run-all --serial build:js build:types",
16
16
  "build:js": "esbuild src/index.ts --bundle --outfile=dist/index.js --platform=node",
17
17
  "build:types": "tsc --declaration src/index.ts --emitDeclarationOnly --outDir ./dist",
18
- "test": "ava tests/test.mjs --timeout=90s"
18
+ "test": "ava tests/test.*.mjs --timeout=90s",
19
+ "test:unit": "ava tests/test.unit.mjs",
20
+ "test:system": "ava tests/test.system.mjs --timeout=90s"
19
21
  },
20
22
  "devDependencies": {
21
- "@11ty/eleventy": "^3.0.0",
23
+ "@11ty/eleventy": "^3.1.2",
22
24
  "@11ty/eleventy1.0.2": "npm:@11ty/eleventy@1.0.2",
25
+ "@11ty/eleventy2": "npm:@11ty/eleventy@2",
23
26
  "@11ty/eleventy2.0.0-canary.8": "npm:@11ty/eleventy@2.0.0-canary.8",
24
27
  "@11ty/eleventy2.0.1": "npm:@11ty/eleventy@2.0.1",
25
- "@types/node": "^22.10.1",
26
- "ava": "^6.2.0",
27
- "esbuild": "^0.24.0",
28
- "jsdom": "^25.0.1",
28
+ "@11ty/eleventy3.1.2": "npm:@11ty/eleventy@3.1.2",
29
+ "@types/node": "^25.3.3",
30
+ "ava": "^7.0.0",
31
+ "esbuild": "^0.27.3",
32
+ "jsdom": "^28.1.0",
29
33
  "npm-run-all": "^4.1.5",
30
- "typescript": "^5.7.2"
34
+ "typescript": "^5.9.3"
31
35
  },
32
36
  "dependencies": {}
33
37
  }