@thegetty/quire-cli 1.0.0-rc.35 → 1.0.0-rc.36

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.
Files changed (158) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +9 -0
  3. package/bin/cli.js +19 -1
  4. package/package.json +21 -8
  5. package/patches/README.md +19 -0
  6. package/patches/install-npm-version+1.0.9.patch +12119 -0
  7. package/schemas/objects.schema.json +12 -38
  8. package/schemas/publication.schema.json +0 -22
  9. package/schemas/references.schema.json +0 -1
  10. package/src/Command.js +26 -6
  11. package/src/Command.spec.js +99 -0
  12. package/src/commands/README.md +213 -122
  13. package/src/commands/build.js +46 -37
  14. package/src/commands/build.spec.js +113 -0
  15. package/src/commands/build.test.js +402 -0
  16. package/src/commands/clean.js +25 -19
  17. package/src/commands/clean.spec.js +108 -0
  18. package/src/commands/clean.test.js +260 -0
  19. package/src/commands/config.js +251 -0
  20. package/src/commands/config.spec.js +107 -0
  21. package/src/commands/config.test.js +715 -0
  22. package/src/commands/create.js +42 -14
  23. package/src/commands/create.spec.js +112 -0
  24. package/src/commands/create.test.js +415 -0
  25. package/src/commands/epub.js +59 -30
  26. package/src/commands/epub.spec.js +114 -0
  27. package/src/commands/epub.test.js +503 -0
  28. package/src/commands/index.js +9 -2
  29. package/src/commands/info.js +18 -24
  30. package/src/commands/info.spec.js +64 -0
  31. package/src/commands/info.test.js +415 -0
  32. package/src/commands/pdf.js +58 -84
  33. package/src/commands/pdf.spec.js +114 -0
  34. package/src/commands/pdf.test.js +464 -0
  35. package/src/commands/preview.js +26 -31
  36. package/src/commands/preview.spec.js +97 -0
  37. package/src/commands/preview.test.js +250 -0
  38. package/src/commands/use.js +56 -0
  39. package/src/commands/use.spec.js +61 -0
  40. package/src/commands/use.test.js +280 -0
  41. package/src/commands/validate.js +31 -19
  42. package/src/commands/validate.spec.js +82 -0
  43. package/src/commands/validate.test.js +234 -0
  44. package/src/commands/workflows.js +70 -0
  45. package/src/errors/build/build-failed-error.js +19 -0
  46. package/src/errors/build/config-field-missing-error.js +20 -0
  47. package/src/errors/build/config-file-not-found-error.js +20 -0
  48. package/src/errors/build/index.js +11 -0
  49. package/src/errors/index.js +48 -0
  50. package/src/errors/install/dependency-install-error.js +19 -0
  51. package/src/errors/install/directory-not-empty-error.js +25 -0
  52. package/src/errors/install/index.js +12 -0
  53. package/src/errors/install/invalid-path-error.js +31 -0
  54. package/src/errors/install/invalid-starter-error.js +27 -0
  55. package/src/errors/install/version-not-found-error.js +35 -0
  56. package/src/errors/output/epub-generation-error.js +19 -0
  57. package/src/errors/output/index.js +14 -0
  58. package/src/errors/output/invalid-epub-library-error.js +20 -0
  59. package/src/errors/output/invalid-pdf-library-error.js +20 -0
  60. package/src/errors/output/missing-build-output-error.js +21 -0
  61. package/src/errors/output/pdf-generation-error.js +24 -0
  62. package/src/errors/output/tool-not-found-error.js +37 -0
  63. package/src/errors/project/index.js +10 -0
  64. package/src/errors/project/not-in-project-error.js +20 -0
  65. package/src/errors/project/project-create-error.js +21 -0
  66. package/src/errors/quire-error.js +27 -0
  67. package/src/errors/validation/validation-error.js +20 -11
  68. package/src/helpers/clean.js +1 -1
  69. package/src/helpers/docs-url.js +32 -0
  70. package/src/helpers/test-cwd.js +5 -6
  71. package/src/helpers/test-cwd.test.js +192 -0
  72. package/src/helpers/which.js +10 -4
  73. package/src/lib/11ty/README.md +135 -19
  74. package/src/lib/11ty/api.js +176 -93
  75. package/src/lib/11ty/cli.js +77 -35
  76. package/src/lib/11ty/index.js +64 -5
  77. package/src/lib/11ty/index.test.js +655 -0
  78. package/src/lib/README.md +275 -0
  79. package/src/lib/commander/index.js +100 -0
  80. package/src/lib/commander/index.test.js +86 -0
  81. package/src/lib/commander/options.js +195 -0
  82. package/src/lib/commander/options.test.js +109 -0
  83. package/src/lib/conf/README.md +84 -73
  84. package/src/lib/conf/config.js +5 -3
  85. package/src/lib/conf/config.test.js +281 -0
  86. package/src/lib/conf/defaults.js +44 -0
  87. package/src/lib/conf/format.js +60 -0
  88. package/src/lib/conf/format.test.js +106 -0
  89. package/src/lib/conf/helpers.js +91 -0
  90. package/src/lib/conf/helpers.test.js +136 -0
  91. package/src/lib/conf/index.js +22 -0
  92. package/src/lib/conf/schema.js +53 -8
  93. package/src/lib/epub/README.md +133 -2
  94. package/src/lib/epub/engines.js +46 -0
  95. package/src/lib/epub/epub.js +36 -11
  96. package/src/lib/epub/index.js +111 -21
  97. package/src/lib/epub/index.test.js +518 -0
  98. package/src/lib/epub/pandoc.js +33 -4
  99. package/src/lib/epub/pandoc.test.js +122 -0
  100. package/src/lib/epub/schema.js +21 -0
  101. package/src/lib/error/README.md +170 -0
  102. package/src/lib/error/handler.js +107 -0
  103. package/src/lib/git/README.md +151 -2
  104. package/src/lib/git/index.js +217 -13
  105. package/src/lib/git/index.spec.js +80 -0
  106. package/src/lib/git/index.test.js +453 -0
  107. package/src/lib/installer/index.js +309 -0
  108. package/src/lib/installer/index.spec.js +83 -0
  109. package/src/lib/installer/index.test.js +545 -0
  110. package/src/lib/logger/README.md +424 -0
  111. package/src/lib/logger/debug.js +90 -0
  112. package/src/lib/logger/debug.spec.js +130 -0
  113. package/src/lib/logger/index.js +228 -0
  114. package/src/lib/logger/index.spec.js +131 -0
  115. package/src/lib/logger/index.test.js +477 -0
  116. package/src/lib/npm/README.md +127 -0
  117. package/src/lib/npm/index.js +198 -0
  118. package/src/lib/npm/index.spec.js +60 -0
  119. package/src/lib/npm/index.test.js +355 -0
  120. package/src/lib/pdf/README.md +131 -0
  121. package/src/lib/pdf/engines.js +46 -0
  122. package/src/lib/pdf/index.js +124 -21
  123. package/src/lib/pdf/index.test.js +708 -0
  124. package/src/lib/pdf/paged.js +100 -52
  125. package/src/lib/pdf/paged.test.js +366 -0
  126. package/src/lib/pdf/prince.js +115 -37
  127. package/src/lib/pdf/prince.test.js +202 -0
  128. package/src/lib/pdf/schema.js +21 -0
  129. package/src/lib/pdf/split.js +61 -33
  130. package/src/lib/pdf/split.test.js +445 -0
  131. package/src/lib/process/manager.js +110 -0
  132. package/src/lib/process/manager.test.js +55 -0
  133. package/src/lib/project/build.js +143 -0
  134. package/src/lib/project/build.test.js +253 -0
  135. package/src/lib/project/config.js +48 -0
  136. package/src/lib/project/config.test.js +134 -0
  137. package/src/{helpers/is-quire.js → lib/project/detect.js} +5 -3
  138. package/src/lib/project/detect.test.js +157 -0
  139. package/src/lib/project/index.js +40 -0
  140. package/src/lib/project/paths.js +224 -0
  141. package/src/lib/project/version.js +110 -0
  142. package/src/lib/project/version.test.js +350 -0
  143. package/src/lib/reporter/README.md +211 -2
  144. package/src/lib/reporter/index.js +512 -0
  145. package/src/lib/reporter/index.test.js +593 -0
  146. package/src/main.js +134 -47
  147. package/src/main.spec.js +61 -0
  148. package/src/main.test.js +347 -0
  149. package/src/validators/utils.js +2 -1
  150. package/src/commands/conf.js +0 -43
  151. package/src/commands/version.js +0 -43
  152. package/src/lib/11ty/paths.js +0 -103
  153. package/src/lib/i18n/README.md +0 -3
  154. package/src/lib/i18n/config.js +0 -53
  155. package/src/lib/i18n/index.js +0 -43
  156. package/src/lib/i18n/localeService.js +0 -58
  157. package/src/lib/quire/README.md +0 -19
  158. package/src/lib/quire/index.js +0 -350
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Helper module for constructing documentation URLs
3
+ * @module helpers/docs-url
4
+ */
5
+
6
+ /**
7
+ * Base URL for Quire documentation
8
+ */
9
+ const DOCS_BASE = 'https://quire.getty.edu/docs-v1/'
10
+
11
+ /**
12
+ * Export the base URL for cases where direct access is needed
13
+ */
14
+ export { DOCS_BASE }
15
+
16
+ /**
17
+ * Construct a full documentation URL from a path segment
18
+ *
19
+ * Uses the URL constructor for automatic path normalization.
20
+ *
21
+ * @param {string} path - Documentation path segment (e.g., 'install-uninstall')
22
+ * @returns {string} Full documentation URL
23
+ *
24
+ * @example
25
+ * docsUrl('install-uninstall')
26
+ * // => 'https://quire.getty.edu/docs-v1/install-uninstall'
27
+ *
28
+ * @example
29
+ * docsUrl('quire-commands')
30
+ * // => 'https://quire.getty.edu/docs-v1/quire-commands'
31
+ */
32
+ export const docsUrl = (path) => new URL(path, DOCS_BASE).href
@@ -1,19 +1,18 @@
1
1
  /**
2
2
  * A helper module to test the current working directory
3
- * @module test-csd
3
+ * @module test-cwd
4
4
  */
5
- import isQuire from '#helpers/is-quire.js'
5
+ import { detect as isQuire } from '#lib/project/index.js'
6
+ import { NotInProjectError } from '#src/errors/index.js'
6
7
 
7
8
  /**
8
9
  * Test current working directory is a Quire project directory
9
10
  *
10
11
  * @param {Command} command the command from which testcwd was called
12
+ * @throws {NotInProjectError} if not in a Quire project directory
11
13
  */
12
14
  export default (command) => {
13
- const message = `[CLI] ${ command ? command.name() : '' } command must be run while in a Quire project directory. Use 'cd' to navigate to your project directory and re-run the 'quire ${ command ? command.name() : '' }' command.`
14
-
15
15
  if (!isQuire(process.cwd())) {
16
- console.error(message)
17
- process.exit(1)
16
+ throw new NotInProjectError(command?.name() || '')
18
17
  }
19
18
  }
@@ -0,0 +1,192 @@
1
+ import test from 'ava'
2
+ import { Volume, createFsFromVolume } from 'memfs'
3
+ import sinon from 'sinon'
4
+ import esmock from 'esmock'
5
+ import { NotInProjectError } from '#src/errors/index.js'
6
+
7
+ test.beforeEach((t) => {
8
+ // Create in-memory file system
9
+ t.context.vol = new Volume()
10
+ t.context.fs = createFsFromVolume(t.context.vol)
11
+ })
12
+
13
+ test.afterEach.always((t) => {
14
+ // Restore all mocks if sandbox exists
15
+ if (t.context.sandbox) {
16
+ t.context.sandbox.restore()
17
+ }
18
+
19
+ // Clear in-memory file system
20
+ t.context.vol.reset()
21
+ })
22
+
23
+ test.serial('testcwd should not error when called in a Quire project directory', async (t) => {
24
+ const { fs, vol } = t.context
25
+
26
+ // Create sandbox for this test
27
+ const sandbox = sinon.createSandbox()
28
+ t.context.sandbox = sandbox
29
+
30
+ // Setup a Quire project directory with .eleventy.js
31
+ vol.fromJSON({
32
+ '/quire-project/.eleventy.js': 'module.exports = function() {}',
33
+ '/quire-project/content/_data/config.yaml': 'title: Test Project',
34
+ '/quire-project/package.json': JSON.stringify({ name: 'test-project' })
35
+ })
36
+
37
+ // Create stubs
38
+ const cwdStub = sandbox.stub(process, 'cwd').returns('/quire-project')
39
+
40
+ // Create mock process object with stubbed methods
41
+ const mockProcess = {
42
+ cwd: cwdStub
43
+ }
44
+
45
+ // Mock detect module with memfs
46
+ const mockDetect = await esmock('../lib/project/detect.js', {
47
+ 'node:fs': fs
48
+ })
49
+
50
+ // Mock node:fs and node:process to use memfs and stubbed process
51
+ const testcwd = await esmock('./test-cwd.js', {
52
+ 'node:process': mockProcess,
53
+ '#lib/project/index.js': {
54
+ detect: mockDetect
55
+ }
56
+ })
57
+
58
+ // Create mock command
59
+ const mockCommand = {
60
+ name: sandbox.stub().returns('build')
61
+ }
62
+
63
+ // Call testcwd - should not throw
64
+ t.notThrows(() => testcwd(mockCommand))
65
+ })
66
+
67
+ test.serial('testcwd should throw NotInProjectError when called outside a Quire project directory', async (t) => {
68
+ const { fs, vol } = t.context
69
+
70
+ // Create sandbox for this test
71
+ const sandbox = sinon.createSandbox()
72
+ t.context.sandbox = sandbox
73
+
74
+ // Setup a non-Quire directory (no .eleventy.js or other Quire marker files)
75
+ vol.fromJSON({
76
+ '/not-quire/some-file.txt': 'not a quire project',
77
+ '/not-quire/package.json': JSON.stringify({ name: 'not-a-quire-project' })
78
+ })
79
+
80
+ // Create stubs
81
+ const cwdStub = sandbox.stub(process, 'cwd').returns('/not-quire')
82
+
83
+ // Create mock process object with stubbed methods
84
+ const mockProcess = {
85
+ cwd: cwdStub
86
+ }
87
+
88
+ // Mock detect module with memfs
89
+ const mockDetect = await esmock('../lib/project/detect.js', {
90
+ 'node:fs': fs
91
+ })
92
+
93
+ // Mock node:fs and node:process to use memfs and stubbed process
94
+ const testcwd = await esmock('./test-cwd.js', {
95
+ 'node:process': mockProcess,
96
+ '#lib/project/index.js': {
97
+ detect: mockDetect
98
+ }
99
+ })
100
+
101
+ // Create mock command
102
+ const mockCommand = {
103
+ name: sandbox.stub().returns('build')
104
+ }
105
+
106
+ // Call testcwd - should throw NotInProjectError
107
+ const error = t.throws(() => testcwd(mockCommand), { instanceOf: NotInProjectError })
108
+ t.is(error.code, 'NOT_IN_PROJECT')
109
+ t.is(error.exitCode, 2)
110
+ t.true(error.message.includes('build'))
111
+ })
112
+
113
+ test.serial('testcwd should include command name in error message', async (t) => {
114
+ const { fs, vol } = t.context
115
+
116
+ // Create sandbox for this test
117
+ const sandbox = sinon.createSandbox()
118
+ t.context.sandbox = sandbox
119
+
120
+ // Setup a non-Quire directory
121
+ vol.fromJSON({
122
+ '/non-quire-directory/file.txt': 'test'
123
+ })
124
+
125
+ // Create stubs
126
+ const cwdStub = sandbox.stub(process, 'cwd').returns('/non-quire-directory')
127
+
128
+ // Create mock process object with stubbed methods
129
+ const mockProcess = {
130
+ cwd: cwdStub
131
+ }
132
+
133
+ // Mock detect module with memfs
134
+ const mockDetect = await esmock('../lib/project/detect.js', {
135
+ 'node:fs': fs
136
+ })
137
+
138
+ // Mock node:fs and node:process to use memfs and stubbed process
139
+ const testcwd = await esmock('./test-cwd.js', {
140
+ 'node:process': mockProcess,
141
+ '#lib/project/index.js': {
142
+ detect: mockDetect
143
+ }
144
+ })
145
+
146
+ // Create mock command with specific name
147
+ const mockCommand = {
148
+ name: sandbox.stub().returns('clean')
149
+ }
150
+
151
+ // Call testcwd - should throw with command name in message
152
+ const error = t.throws(() => testcwd(mockCommand), { instanceOf: NotInProjectError })
153
+ t.true(error.message.includes('clean'), 'error message should include command name')
154
+ })
155
+
156
+ test.serial('testcwd should work when command is null', async (t) => {
157
+ const { fs, vol } = t.context
158
+
159
+ // Create sandbox for this test
160
+ const sandbox = sinon.createSandbox()
161
+ t.context.sandbox = sandbox
162
+
163
+ // Setup a non-Quire directory
164
+ vol.fromJSON({
165
+ '/not-a-quire-directory/file.txt': 'test'
166
+ })
167
+
168
+ // Create stubs
169
+ const cwdStub = sandbox.stub(process, 'cwd').returns('/not-a-quire-directory')
170
+
171
+ // Create mock process object with stubbed methods
172
+ const mockProcess = {
173
+ cwd: cwdStub
174
+ }
175
+
176
+ // Mock detect module with memfs
177
+ const mockDetect = await esmock('../lib/project/detect.js', {
178
+ 'node:fs': fs
179
+ })
180
+
181
+ // Mock node:fs and node:process to use memfs and stubbed process
182
+ const testcwd = await esmock('./test-cwd.js', {
183
+ 'node:process': mockProcess,
184
+ '#lib/project/index.js': {
185
+ detect: mockDetect
186
+ }
187
+ })
188
+
189
+ // Call testcwd with null command - should throw but not crash
190
+ const error = t.throws(() => testcwd(null), { instanceOf: NotInProjectError })
191
+ t.is(error.exitCode, 2)
192
+ })
@@ -3,19 +3,25 @@
3
3
  * @module which
4
4
  */
5
5
  import which from 'which'
6
+ import { ToolNotFoundError } from '#src/errors/index.js'
6
7
 
7
8
  /**
8
9
  * A simple wrapper around the `which` utility
9
10
  * to find the first instance of an executable in the shell environment PATH
10
11
  *
11
12
  * @param {String} executable shell executable to find
13
+ * @param {Object} [toolInfo] optional metadata for error messages
14
+ * @param {String} [toolInfo.displayName] human-readable tool name
15
+ * @param {String} [toolInfo.installUrl] URL where tool can be downloaded
16
+ * @param {String} [toolInfo.docsUrl] Quire docs URL for this tool
17
+ * @param {String} [toolInfo.fallback] alternative suggestion
18
+ * @throws {ToolNotFoundError} When executable is not found in PATH
19
+ * @returns {String} Path to the executable
12
20
  */
13
- export default (executable) => {
21
+ export default (executable, toolInfo) => {
14
22
  const result = which.sync(executable, { nothrow: true })
15
23
  if (!result) {
16
- console.error(`Unable to locate executable '${executable}'\n
17
- Ensure that the '${executable}' executable is installed and available in the shell environment PATH.
18
- `)
24
+ throw new ToolNotFoundError(executable, toolInfo)
19
25
  }
20
26
  return result
21
27
  }
@@ -1,35 +1,151 @@
1
1
  ## CLI 11ty Module
2
2
 
3
- The `quire-cli/lib/11ty` module is a façade for interacting with Eleventy, the static site generator for Quire projects.
3
+ The `lib/11ty` module is a façade for interacting with Eleventy, the static site generator for Quire projects. It follows the singleton pattern established by `lib/npm` and `lib/git`.
4
4
 
5
- ### 11ty/API module
5
+ ### Usage
6
6
 
7
- The `api` module allows the Quire CLI to programmatical configure an instance of Eleventy on which it can call methods.
7
+ **Recommended: Default export**
8
8
 
9
- See [Eleventy Documentation: Programmatic API](https://www.11ty.dev/docs/programmatic/)
9
+ ```javascript
10
+ import eleventy from '#lib/11ty/index.js'
10
11
 
11
- ### 11ty/CLI module
12
+ // Run production build
13
+ await eleventy.build({ debug: true })
12
14
 
13
- The `cli` module is a wrapper around the Eleventy CLI to run `@11ty/eleventy` commands.
15
+ // Run development server
16
+ await eleventy.serve({ port: 8080 })
14
17
 
15
- See [Eleventy Documentation: Command Line Usage](https://www.11ty.dev/docs/usage/#command-line-usage)
18
+ // Access paths through the façade
19
+ const outputDir = eleventy.paths.getOutputDir()
20
+ ```
16
21
 
17
- ### Paths module
22
+ **Path-only consumers**
18
23
 
19
- The `paths` module returns paths for a Quire project to configure the Eleventy instance:
24
+ ```javascript
25
+ import { paths } from '#lib/11ty/index.js'
20
26
 
21
- - _absolute_ path to the [Eleventy configuration](https://www.11ty.dev/docs/config/) file
22
- - _relative_ path *from* the `quire-11ty` source *to* the `input` and `output` directories for the project
23
- - _relative_ path *from* the project `input` *to* the Eleventy `includes` and `layouts` directories
27
+ const projectRoot = paths.getProjectRoot()
28
+ const outputDir = paths.getOutputDir()
29
+ ```
24
30
 
31
+ **Legacy usage (deprecated)**
25
32
 
26
- Set environment variables for paths relative to eleventy `input` dir,
27
- * allowing a project agnostic `quire-11ty` eleventy configuration file.
28
- * Nota bene: environment variables read into the eleventy configuration
29
- * file _must_ be set before the eleventy configuration file is parsed.
33
+ ```javascript
34
+ // Deprecated - use default export instead
35
+ import { api, cli, paths } from '#lib/11ty/index.js'
36
+ await api.build(options)
37
+ ```
30
38
 
31
- `Eleventy` does not expose an API to set the relative `includes` and `layouts` paths on the instance. In order to decouple the Eleventy configuration file from a specific project and its path on the system the exported `paths` are used by the `api` and `cli` modules to set environment variables that can be used in the `.eleventy.js` configuration.
39
+ ### Test Mocking
32
40
 
33
- These environment variables **must be set before** the `Eleventy` instance is created and the `.eleventy.js` configuration is parsed.
41
+ ```javascript
42
+ const mockEleventy = {
43
+ build: sandbox.stub().resolves(),
44
+ serve: sandbox.stub().resolves(),
45
+ paths: {
46
+ getProjectRoot: () => '/project',
47
+ getOutputDir: () => '_site'
48
+ }
49
+ }
50
+ const MyCommand = await esmock('./mycommand.js', {
51
+ '#lib/11ty/index.js': { default: mockEleventy }
52
+ })
53
+ ```
34
54
 
35
- **Nota bene** The current implementation of the `Eleventy` `TemplatePathResolver` assumes that the `layouts` directory is a child of the `input` directory and prevents decoupling the Quire project content from the `quire-11ty` code; see [eleventy#2655](https://github.com/11ty/eleventy/issues/2655).
55
+ ### Quire11ty Façade
56
+
57
+ The `Quire11ty` class provides abstracted Eleventy operations:
58
+
59
+ | Method | Description |
60
+ |--------|-------------|
61
+ | `build(options)` | Run Eleventy production build |
62
+ | `serve(options)` | Run Eleventy development server |
63
+ | `paths` | Access to `Paths` instance for path resolution |
64
+
65
+ **Build options:**
66
+ - `debug` - Enable debug output
67
+ - `dryRun` - Perform dry run without writing files
68
+ - `quiet` - Suppress output
69
+
70
+ **Serve options:**
71
+ - `debug` - Enable debug output
72
+ - `port` - Server port
73
+ - `quiet` - Suppress output
74
+
75
+ ### Paths Module
76
+
77
+ The `paths` property provides a `Paths` instance with accessor methods for Eleventy path configuration. Values are computed on access to reflect the current working directory state.
78
+
79
+ #### Naming Convention
80
+
81
+ Method names encode the return type:
82
+
83
+ | Suffix | Returns |
84
+ |--------|---------|
85
+ | `Path` or `Root` | Absolute filesystem path |
86
+ | `Dir` | Relative directory name |
87
+
88
+ #### API Reference
89
+
90
+ **Absolute paths:**
91
+
92
+ | Method | Description |
93
+ |--------|-------------|
94
+ | `getConfigPath()` | Absolute path to `.eleventy.js` config |
95
+ | `getEleventyRoot()` | Absolute path to Eleventy root directory |
96
+ | `getInputPath()` | Absolute path to content input directory |
97
+ | `getLibQuirePath()` | Absolute path to quire-11ty installation |
98
+ | `getProjectRoot()` | Absolute path to project root (`process.cwd()`) |
99
+
100
+ **Relative directories:**
101
+
102
+ | Method | Description |
103
+ |--------|-------------|
104
+ | `getDataDir()` | Data directory relative to input (`_computed`) |
105
+ | `getEpubDir()` | EPUB output directory relative to Eleventy root |
106
+ | `getIncludesDir()` | Includes directory relative to input |
107
+ | `getInputDir()` | Input directory relative to Eleventy root |
108
+ | `getLayoutsDir()` | Layouts directory relative to input |
109
+ | `getOutputDir()` | Output directory relative to Eleventy root (`_site`) |
110
+ | `getPublicDir()` | Public assets directory (`./public`) |
111
+
112
+ **Utility:**
113
+
114
+ | Method | Description |
115
+ |--------|-------------|
116
+ | `toObject()` | Returns all paths as an object (for logging/debugging) |
117
+
118
+ ### Environment Variables
119
+
120
+ The façade sets environment variables before creating the Eleventy instance:
121
+
122
+ | Variable | Source |
123
+ |----------|--------|
124
+ | `ELEVENTY_DATA` | `paths.getDataDir()` |
125
+ | `ELEVENTY_ENV` | `'production'` or `'development'` |
126
+ | `ELEVENTY_INCLUDES` | `paths.getIncludesDir()` |
127
+ | `ELEVENTY_LAYOUTS` | `paths.getLayoutsDir()` |
128
+
129
+ These environment variables **must be set before** the Eleventy instance is created and the `.eleventy.js` configuration is parsed.
130
+
131
+ ### Design Notes
132
+
133
+ **Why a unified façade?**
134
+
135
+ The façade pattern provides:
136
+ 1. **Single point of control** - Eleventy configuration in one place
137
+ 2. **Consistent logging** - All operations use `[CLI:lib/11ty]` prefix
138
+ 3. **Easy mocking** - Single object to mock in tests
139
+ 4. **Implementation hiding** - Consumers don't need to know if we use API or CLI
140
+
141
+ **Why programmatic API instead of CLI subprocess?**
142
+
143
+ The façade uses Eleventy's programmatic API internally rather than spawning a subprocess:
144
+ 1. **Event access** - Direct access to `eleventy.after` and other hooks
145
+ 2. **Better error handling** - Native try/catch with full stack traces
146
+ 3. **No spawn overhead** - Eliminates ~100-200ms process startup time
147
+ 4. **Debugging** - Easier to trace issues in-process
148
+
149
+ The `cli.js` module is retained for backwards compatibility but is deprecated.
150
+
151
+ **Known limitation:** The Eleventy `TemplatePathResolver` requires layouts and includes directories to be within the input directory, which prevents fully decoupling Quire project content from `quire-11ty` code. See [eleventy#2655](https://github.com/11ty/eleventy/issues/2655) (still open).