ultimate-jekyll-manager 1.1.9 โ†’ 1.2.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/CLAUDE.md +138 -1775
  3. package/README.md +49 -20
  4. package/dist/build.js +3 -0
  5. package/dist/cli.js +1 -0
  6. package/dist/commands/test.js +56 -0
  7. package/dist/defaults/CLAUDE.md +72 -6
  8. package/dist/gulp/tasks/defaults.js +20 -4
  9. package/dist/gulp/tasks/distribute.js +29 -26
  10. package/dist/gulp/tasks/jsonToHtml.js +86 -80
  11. package/dist/gulp/tasks/minifyHtml.js +55 -51
  12. package/dist/gulp/tasks/sass.js +7 -6
  13. package/dist/gulp/tasks/utils/template-transform.js +35 -35
  14. package/dist/index.js +4 -0
  15. package/dist/test/assert.js +120 -0
  16. package/dist/test/fixtures/consumer-site/_site/about.html +13 -0
  17. package/dist/test/fixtures/consumer-site/_site/assets/css/main.bundle.css +2 -0
  18. package/dist/test/fixtures/consumer-site/_site/assets/js/main.bundle.js +6 -0
  19. package/dist/test/fixtures/consumer-site/_site/build.json +11 -0
  20. package/dist/test/fixtures/consumer-site/_site/index.html +28 -0
  21. package/dist/test/fixtures/consumer-site/_site/service-worker.js +29 -0
  22. package/dist/test/fixtures/consumer-site/package.json +6 -0
  23. package/dist/test/harness/page/index.html +51 -0
  24. package/dist/test/index.js +63 -0
  25. package/dist/test/runner.js +402 -0
  26. package/dist/test/runners/boot.js +109 -0
  27. package/dist/test/runners/chromium.js +255 -0
  28. package/dist/test/server.js +127 -0
  29. package/dist/test/suites/boot/service-worker.test.js +84 -0
  30. package/dist/test/suites/boot/site-loads.test.js +65 -0
  31. package/dist/test/suites/build/cli.test.js +49 -0
  32. package/dist/test/suites/build/collect-text-nodes.test.js +37 -0
  33. package/dist/test/suites/build/dictionary.test.js +17 -0
  34. package/dist/test/suites/build/expect.test.js +59 -0
  35. package/dist/test/suites/build/exports.test.js +32 -0
  36. package/dist/test/suites/build/logger.test.js +62 -0
  37. package/dist/test/suites/build/manager.test.js +186 -0
  38. package/dist/test/suites/build/merge-jekyll-configs.test.js +95 -0
  39. package/dist/test/suites/build/mode-helpers.test.js +65 -0
  40. package/dist/test/suites/build/template-transform.test.js +94 -0
  41. package/dist/test/suites/build/templating-brackets.test.js +46 -0
  42. package/dist/test/suites/build/validate-yaml.test.js +60 -0
  43. package/dist/test/suites/page/dom-baseline.test.js +34 -0
  44. package/dist/test/suites/page/harness-globals.test.js +38 -0
  45. package/dist/test/suites/page/prerendered-icons.test.js +32 -0
  46. package/dist/utils/mode-helpers.js +84 -0
  47. package/docs/_legacy-claude-md.md +1832 -0
  48. package/docs/cross-context-helpers.md +75 -0
  49. package/docs/test-boot-layer.md +110 -0
  50. package/docs/test-framework.md +183 -0
  51. package/package.json +18 -16
package/README.md CHANGED
@@ -28,6 +28,7 @@
28
28
  * **SEO Optimized**: Ultimate Jekyll is fully SEO optimized.
29
29
  * **Blazingly Fast**: Ultimate Jekyll is blazingly fast.
30
30
  * **NPM & Gulp**: Ultimate Jekyll is fueled by an intuitive incorporation of npm and gulp.
31
+ * **Built-in test framework**: three layers (`build` / `page` / `boot`) โ€” plain Node, headless Chromium tab, headless Chromium against real `_site/` with SW registration verification.
31
32
 
32
33
  ## ๐Ÿš€ Getting started
33
34
  1. [Create a repo](https://github.com/itw-creative-works/ultimate-jekyll/generate) from the **Ultimate Jekyll** template.
@@ -37,6 +38,49 @@
37
38
  npm start
38
39
  ```
39
40
 
41
+ ## ๐Ÿงช Testing
42
+
43
+ UJM ships a built-in three-layer test harness. Write tests under `test/<layer>/*.test.js` and run with:
44
+
45
+ ```bash
46
+ npx mgr test # all layers
47
+ npx mgr test --layer build # plain Node, fast
48
+ npx mgr test --layer page # headless Chromium tab against harness HTML
49
+ npx mgr test --layer boot # headless Chromium against built _site/
50
+ ```
51
+
52
+ Test files use Jest-compatible matchers:
53
+
54
+ ```js
55
+ // test/build/config.test.js
56
+ const Manager = require('ultimate-jekyll-manager/build');
57
+
58
+ module.exports = {
59
+ layer: 'build',
60
+ description: 'config has brand.id',
61
+ run: async (ctx) => {
62
+ const cfg = Manager.getConfig('project');
63
+ ctx.expect(cfg.brand.id).toBeTruthy();
64
+ },
65
+ };
66
+ ```
67
+
68
+ Boot tests run against your actually-built `_site/` after `npm run build`:
69
+
70
+ ```js
71
+ // test/boot/site.test.js
72
+ module.exports = {
73
+ layer: 'boot',
74
+ description: 'home renders + SW registers',
75
+ inspect: async ({ site, page, expect }) => {
76
+ await page.goto(site.baseUrl + '/');
77
+ expect((await page.title()).length).toBeGreaterThan(0);
78
+ },
79
+ };
80
+ ```
81
+
82
+ Full guide: [docs/test-framework.md](docs/test-framework.md). Boot layer deep-dive: [docs/test-boot-layer.md](docs/test-boot-layer.md).
83
+
40
84
  ## ๐Ÿ“ฆ How to sync with the template
41
85
  1. Simply run `npm start` in Terminal to get all the latest updates from the **Ultimate Jekyll template** and launch your website in the browser.
42
86
 
@@ -761,23 +805,8 @@ Add this to any js file to ONLY run in development mode (it will be excluded in
761
805
  /* @dev-only:end */
762
806
  ```
763
807
 
764
- > This project is "ultimate-jekyll", an NPM module that helps โ”‚
765
- โ”‚ streamline development of Jekyll websites. A "consuming โ”‚
766
- โ”‚ project" will require this NPM module to take advantage of โ”‚
767
- โ”‚ its features like automatic folder structure setup, themes, โ”‚
768
- โ”‚ and default pages to get a website up and running in โ”‚
769
- โ”‚ seconds, while allowing further customization down the line. โ”‚
770
- โ”‚ Right now i am struggling on the theme portion of this โ”‚
771
- โ”‚ project. I want the user to be able to define the theme in โ”‚
772
- โ”‚ their _config.yml (which currently they do by setting โ”‚
773
- โ”‚ theme.id). I have some themes from the official bootstrap โ”‚
774
- โ”‚ team. usually a theme comes with a frontend, a backend/admin โ”‚
775
- โ”‚ dashboard, and docs. these 3 subparts of the theme have โ”‚
776
- โ”‚ different html structure and css and js requirements. so i โ”‚
777
- โ”‚ need a super easy system that allows me to make a file in โ”‚
778
- โ”‚ the consuming project, say its the index.html for example, โ”‚
779
- โ”‚ and i should easily be able to put which subseciton (or โ”‚
780
- โ”‚ target as i call it) of the theme to use. so for an agency โ”‚
781
- โ”‚ website i will probably use the frontend target, while for a โ”‚
782
- โ”‚ chat app i will probably use the backend target. however, i โ”‚
783
- โ”‚ need to be able to use
808
+ ## ๐Ÿงฐ Sister projects
809
+
810
+ - [Electron Manager (EM)](https://github.com/itw-creative-works/electron-manager) โ€” same patterns, but for Electron desktop apps
811
+ - [Browser Extension Manager (BXM)](https://github.com/itw-creative-works/browser-extension-manager) โ€” same patterns, but for cross-browser MV3 extensions
812
+ - [Backend Manager (BEM)](https://github.com/itw-creative-works/backend-manager) โ€” Firebase Functions backend framework
package/dist/build.js CHANGED
@@ -351,5 +351,8 @@ Manager.prototype.processBatches = Manager.processBatches;
351
351
  // };
352
352
  // };
353
353
 
354
+ // Mix in cross-context mode helpers (isDevelopment/isProduction/isTesting/getVersion).
355
+ require('./utils/mode-helpers.js').attachTo(Manager);
356
+
354
357
  // Export
355
358
  module.exports = Manager;
package/dist/cli.js CHANGED
@@ -17,6 +17,7 @@ const ALIASES = {
17
17
  // Tasks
18
18
  audit: ['-a', '--audit'],
19
19
  translation: ['-t', '--translation'],
20
+ test: ['--test'],
20
21
  };
21
22
 
22
23
  // Function to resolve command name from aliases
@@ -0,0 +1,56 @@
1
+ // Libraries
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+ const Manager = require('../build.js');
5
+ const mgr = new Manager();
6
+ const logger = mgr.logger('test');
7
+ const { run } = require('../test/runner.js');
8
+
9
+ module.exports = async function (options) {
10
+ const layer = options.layer || 'all';
11
+ const filter = options.filter || null;
12
+ const reporter = options.reporter || 'pretty';
13
+ const integration = options.integration === true || options.integration === 'true';
14
+
15
+ if (integration) {
16
+ process.env.UJ_TEST_INTEGRATION = '1';
17
+ }
18
+
19
+ // Canonical signal โ€” every Manager picks this up via isTesting().
20
+ process.env.UJ_TEST_MODE = 'true';
21
+
22
+ // When UJM itself runs its own boot-layer tests (the cwd's package.json is
23
+ // UJM's package.json), there's no real consumer site to target. Point the
24
+ // boot runner at the fixture under dist/test/fixtures/consumer-site unless
25
+ // the caller has already set UJ_TEST_BOOT_PROJECT explicitly.
26
+ if (!process.env.UJ_TEST_BOOT_PROJECT) {
27
+ try {
28
+ const cwdPkg = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8'));
29
+ if (cwdPkg.name === 'ultimate-jekyll-manager') {
30
+ process.env.UJ_TEST_BOOT_PROJECT = path.join(__dirname, '..', 'test', 'fixtures', 'consumer-site');
31
+ }
32
+ } catch (_) { /* no package.json โ€” leave unset */ }
33
+ }
34
+
35
+ if (reporter !== 'json') {
36
+ logger.log(`Running tests (layer=${layer}${filter ? ` filter="${filter}"` : ''}${integration ? ' +integration' : ''})`);
37
+ }
38
+
39
+ const result = await run({ layer, filter, reporter });
40
+
41
+ if (reporter === 'json') {
42
+ // Final machine-readable summary.
43
+ process.stdout.write(JSON.stringify({
44
+ event: 'summary',
45
+ passed: result.passed,
46
+ failed: result.failed,
47
+ skipped: result.skipped,
48
+ total: result.passed + result.failed + result.skipped,
49
+ }) + '\n');
50
+ }
51
+
52
+ if (result.failed > 0) {
53
+ process.exitCode = 1;
54
+ throw new Error(`${result.failed} test(s) failed`);
55
+ }
56
+ };
@@ -1,8 +1,74 @@
1
- # Identity
2
- This is a jekyll site that is "consuming" a jekyll template project called Ultimate Jekyll--a collection of components that can be used to build a Jekyll site quickly and efficiently.
1
+ # ========== Default Values ==========
2
+ # Ultimate Jekyll Manager (UJM) โ€” consumer project
3
3
 
4
- ## UJ Documentation
5
- You should have a full understanding of Ultimate Jekyll before editing this project, which can be found at: node_modules/ultimate-jekyll-manager/CLAUDE.md
4
+ > **Auto-managed file.** Everything between `# ========== Default Values ==========` and `# ========== Custom Values ==========` is owned by `ultimate-jekyll-manager` and rewritten on every `npx mgr setup`. Put your own project-specific notes BELOW the `Custom Values` marker โ€” that section is preserved verbatim across setups.
6
5
 
7
- ## Project-specific Notes
8
- Add any notes specific to this project here.
6
+ ## Framework
7
+
8
+ This project consumes **Ultimate Jekyll Manager** (UJM) โ€” a comprehensive framework for building modern Jekyll-powered static sites. UJM provides one-line bootstrap per context (build / frontend / service-worker), a multi-stage gulp pipeline (defaults / distribute / webpack / sass / imagemin / jekyll / audit / translation / minifyHtml / serve), default Jekyll layouts + themes, a frontend ES-module Manager with dynamic per-page module loading, a service worker with Firebase Messaging + cache management, and a built-in three-layer test framework.
9
+
10
+ **Framework's own docs** (read these for deep-dives; both paths point to the same files, the absolute path works regardless of working directory):
11
+ - Top-level overview: `/Users/ian/Developer/Repositories/ITW-Creative-Works/ultimate-jekyll-manager/CLAUDE.md` (or `node_modules/ultimate-jekyll-manager/CLAUDE.md`)
12
+ - Subsystem references: `/Users/ian/Developer/Repositories/ITW-Creative-Works/ultimate-jekyll-manager/docs/` (or `node_modules/ultimate-jekyll-manager/docs/`)
13
+
14
+ ## Quick start
15
+
16
+ ```bash
17
+ npm start # dev: clean โ†’ setup โ†’ bundle exec gulp serve (Jekyll + BrowserSync + livereload)
18
+ npm run build # production build (UJ_BUILD_MODE=true): clean โ†’ setup โ†’ full gulp pipeline โ†’ _site/
19
+ npm run deploy # build โ†’ `npu sync --message='Deploy'` (publishes _site/)
20
+ npx mgr test # run framework + project test suites (build / page / boot layers)
21
+ npx mgr audit # HTML validation + spellcheck + optional Lighthouse
22
+ ```
23
+
24
+ ## Where things live
25
+
26
+ - `src/_config.yml` โ€” Jekyll config: brand, theme, meta, web_manager (Firebase). `Manager.getConfig('project')` reads this. **`brand.id` + `theme.id` are required.**
27
+ - `config/ultimate-jekyll-manager.json` โ€” UJM-specific config (JSON5): purgecss safelist, webpack target, imagemin options, distribute glob patterns. `Manager.getUJMConfig()` reads this.
28
+ - `src/pages/<name>.html` โ€” your custom pages. May contain frontmatter only (and use a UJM `blueprint/*` layout) to customize a default page without writing HTML.
29
+ - `src/_layouts/`, `src/_includes/` โ€” custom layouts / includes that override UJM's defaults.
30
+ - `src/assets/css/main.scss` โ€” shared SCSS. Theme load paths resolve via `__theme__` webpack alias.
31
+ - `src/assets/css/pages/<page>/index.scss` โ€” page-specific styles (compile to `dist/assets/css/pages/<page>/...bundle.css`).
32
+ - `src/assets/js/main.js` โ€” main JS entry.
33
+ - `src/assets/js/pages/<page>/index.js` โ€” page-specific JS. UJM's frontend Manager loads these dynamically based on `data-page-path`.
34
+ - `src/_includes/frontend/sections/{nav,footer}.json` โ€” JSON-driven nav/footer config. UJM renders these into HTML at build time.
35
+ - `hooks/build/{pre,post}.js`, `hooks/middleware/request.js` โ€” optional lifecycle hooks.
36
+ - `_site/` โ€” Jekyll output (the deployable site). Not committed.
37
+ - `dist/` โ€” intermediate compile output (webpack bundles, sass, processed images) before Jekyll merges them into `_site/`.
38
+ - `test/**/*.js` โ€” your project test suites (framework auto-runs them alongside its own).
39
+
40
+ ## Per-context imports
41
+
42
+ ```js
43
+ // Frontend (browser ES module) โ€” every consumer page gets one
44
+ import Manager from 'ultimate-jekyll-manager';
45
+ new Manager().initialize();
46
+
47
+ // Service worker โ€” at the top of src/service-worker.js
48
+ importScripts('/build.js'); // exposes UJ_BUILD_JSON
49
+ // ...then construct your service-worker Manager
50
+
51
+ // Build-time / gulp / commands
52
+ const Manager = require('ultimate-jekyll-manager/build');
53
+ ```
54
+
55
+ ## Available APIs at runtime
56
+
57
+ After `new Manager().initialize()`, the frontend Manager exposes:
58
+ - `manager.webManager` โ€” Web Manager singleton (Firebase, auth, analytics, reactive `data-wm-bind` directives)
59
+ - `manager.isDevelopment()` / `isProduction()` / `isTesting()` / `getVersion()` โ€” cross-context helpers
60
+
61
+ At build time, `require('ultimate-jekyll-manager/build')` exposes:
62
+ - `Manager.getConfig(type)` โ€” read `_config.yml` (`'project'` or `'main'`)
63
+ - `Manager.getPackage(type)` โ€” read `package.json` (`'project'` or `'main'`)
64
+ - `Manager.getUJMConfig()` โ€” read `config/ultimate-jekyll-manager.json`
65
+ - `Manager.getEnvironment()` โ€” `'development'` or `'production'`
66
+ - `Manager.isBuildMode()` / `isQuickMode()` / `isServer()` / `actLikeProduction()` โ€” env-gated flags
67
+ - `Manager.logger(name)` โ€” timestamped logger instance
68
+ - `Manager.require(path)` โ€” escape hatch for UJM transitive deps (use sparingly)
69
+
70
+ # ========== Custom Values ==========
71
+
72
+ ## Project-specific notes
73
+
74
+ Add anything specific to THIS project here. Edits below this line are preserved across `npx mgr setup` runs.
@@ -2,7 +2,7 @@
2
2
  const Manager = new (require('../../build.js'));
3
3
  const logger = Manager.logger('defaults');
4
4
  const { src, dest, watch, series } = require('gulp');
5
- const through2 = require('through2');
5
+ const { Transform } = require('node:stream');
6
6
  const jetpack = require('fs-jetpack');
7
7
  const path = require('path');
8
8
  const { minimatch } = require('minimatch');
@@ -98,6 +98,15 @@ const FILE_MAP = {
98
98
  mergeLines: true, // Merge line-by-line instead of overwriting
99
99
  },
100
100
 
101
+ // Consumer CLAUDE.md uses the same marker-based merge as .env/.gitignore.
102
+ // Framework owns the Default section; consumer owns everything below the Custom marker.
103
+ // Must come AFTER `**/*.md` (which sets overwrite: false) โ€” `getFileOptions` does
104
+ // last-match-wins, so this rule's `mergeLines: true` activates the merge path even though
105
+ // the catch-all would otherwise skip.
106
+ 'CLAUDE.md': {
107
+ mergeLines: true,
108
+ },
109
+
101
110
  // Config file with smart merging
102
111
  'config/ultimate-jekyll-manager.json': {
103
112
  overwrite: true,
@@ -319,8 +328,12 @@ function mergeLineBasedFiles(existingContent, newContent, fileName) {
319
328
  result.push(DEFAULT_SECTION_MARKER);
320
329
  result.push(...mergedDefaultSection);
321
330
 
322
- // Add custom section
323
- result.push('');
331
+ // Add custom section. Insert a single blank line before the marker, but only if the
332
+ // merged default doesn't already end with one โ€” otherwise we'd accumulate an extra blank
333
+ // line on every merge, breaking idempotency after the first re-run.
334
+ if (mergedDefaultSection.length === 0 || mergedDefaultSection[mergedDefaultSection.length - 1].trim() !== '') {
335
+ result.push('');
336
+ }
324
337
  result.push(CUSTOM_SECTION_MARKER);
325
338
 
326
339
  // First add any user lines that were in default section (moved to custom)
@@ -406,7 +419,9 @@ function defaults(complete, changedFile) {
406
419
  }
407
420
 
408
421
  function customTransform() {
409
- return through2.obj(function (file, _, callback) {
422
+ return new Transform({
423
+ objectMode: true,
424
+ transform(file, _, callback) {
410
425
  // Skip if it's a directory
411
426
  if (file.isDirectory()) {
412
427
  return callback(null, file);
@@ -539,6 +554,7 @@ function customTransform() {
539
554
 
540
555
  // Complete
541
556
  return callback();
557
+ },
542
558
  });
543
559
  }
544
560
  function defaultsWatcher(complete) {
@@ -2,7 +2,7 @@
2
2
  const Manager = new (require('../../build.js'));
3
3
  const logger = Manager.logger('distribute');
4
4
  const { src, dest, watch, series } = require('gulp');
5
- const through2 = require('through2');
5
+ const { Transform } = require('node:stream');
6
6
  const path = require('path');
7
7
  const jetpack = require('fs-jetpack');
8
8
  const { template } = require('node-powertools');
@@ -180,38 +180,41 @@ function getFilesRecursive(dir) {
180
180
  }
181
181
 
182
182
  function customTransform() {
183
- return through2.obj(function (file, _, callback) {
184
- // Skip if it's a directory
185
- if (file.isDirectory()) {
186
- return callback(null, file);
187
- }
183
+ return new Transform({
184
+ objectMode: true,
185
+ transform(file, _, callback) {
186
+ // Skip if it's a directory
187
+ if (file.isDirectory()) {
188
+ return callback(null, file);
189
+ }
188
190
 
189
- // Get relative path from src base
190
- const relativePath = path.relative(file.base, file.path).replace(/\\/g, '/');
191
+ // Get relative path from src base
192
+ const relativePath = path.relative(file.base, file.path).replace(/\\/g, '/');
191
193
 
192
- // Log
193
- if (LOUD) {
194
- logger.log(`Processing file: ${relativePath}`);
195
- }
194
+ // Log
195
+ if (LOUD) {
196
+ logger.log(`Processing file: ${relativePath}`);
197
+ }
196
198
 
197
- // Change path if it starts with 'pages/'
198
- // if (relativePath.startsWith('pages/')) {
199
- // // Remove 'pages/' prefix
200
- // const newRelativePath = relativePath.replace(/^pages\//, '');
199
+ // Change path if it starts with 'pages/'
200
+ // if (relativePath.startsWith('pages/')) {
201
+ // // Remove 'pages/' prefix
202
+ // const newRelativePath = relativePath.replace(/^pages\//, '');
201
203
 
202
- // // Update file path to remove pages directory
203
- // // This will make src/pages/index.html -> dist/index.html
204
- // file.path = path.join(file.base, newRelativePath);
204
+ // // Update file path to remove pages directory
205
+ // // This will make src/pages/index.html -> dist/index.html
206
+ // file.path = path.join(file.base, newRelativePath);
205
207
 
206
- // // Log
207
- // logger.log(` -> Moving from pages/ to root: ${newRelativePath}`);
208
- // }
208
+ // // Log
209
+ // logger.log(` -> Moving from pages/ to root: ${newRelativePath}`);
210
+ // }
209
211
 
210
- // Push the file
211
- this.push(file);
212
+ // Push the file
213
+ this.push(file);
212
214
 
213
- // Continue
214
- callback();
215
+ // Continue
216
+ callback();
217
+ },
215
218
  });
216
219
  }
217
220
 
@@ -2,7 +2,7 @@
2
2
  const Manager = new (require('../../build.js'));
3
3
  const logger = Manager.logger('json-to-html');
4
4
  const { src, dest, watch, series } = require('gulp');
5
- const through2 = require('through2');
5
+ const { Transform } = require('node:stream');
6
6
  const path = require('path');
7
7
  const jetpack = require('fs-jetpack');
8
8
  const { template } = require('node-powertools');
@@ -42,90 +42,96 @@ function jsonToHtml(complete) {
42
42
 
43
43
  // First, copy JSON files to _data directory
44
44
  const jsonCopy = src(input)
45
- .pipe(through2.obj(function (file, _, callback) {
46
- if (file.isDirectory()) {
47
- return callback(null, file);
48
- }
49
-
50
- try {
51
- // Parse JSON5 content
52
- const json5Content = file.contents.toString();
53
- const parsedData = JSON5.parse(json5Content);
54
-
55
- // Convert to regular JSON (pretty printed for readability)
56
- const regularJson = JSON.stringify(parsedData, null, 2);
57
-
58
- // Also write to _data directory for Jekyll to pick up
59
- const relativePath = path.relative(file.base, file.path);
60
- const dataPath = path.join(dataOutput, relativePath);
61
- const dataDir = path.dirname(dataPath);
62
-
63
- // Ensure directory exists
64
- jetpack.dir(dataDir);
65
-
66
- // Write regular JSON file to _data
67
- jetpack.write(dataPath, regularJson);
68
-
69
- // Track compiled files
70
- compiled[dataPath] = true;
71
-
72
- // Update file contents with regular JSON
73
- file.contents = Buffer.from(regularJson);
74
-
75
- callback(null, file);
76
- } catch (err) {
77
- logger.error(`Error parsing JSON5 file ${file.path}: ${err.message}`);
78
- callback(err);
79
- }
45
+ .pipe(new Transform({
46
+ objectMode: true,
47
+ transform(file, _, callback) {
48
+ if (file.isDirectory()) {
49
+ return callback(null, file);
50
+ }
51
+
52
+ try {
53
+ // Parse JSON5 content
54
+ const json5Content = file.contents.toString();
55
+ const parsedData = JSON5.parse(json5Content);
56
+
57
+ // Convert to regular JSON (pretty printed for readability)
58
+ const regularJson = JSON.stringify(parsedData, null, 2);
59
+
60
+ // Also write to _data directory for Jekyll to pick up
61
+ const relativePath = path.relative(file.base, file.path);
62
+ const dataPath = path.join(dataOutput, relativePath);
63
+ const dataDir = path.dirname(dataPath);
64
+
65
+ // Ensure directory exists
66
+ jetpack.dir(dataDir);
67
+
68
+ // Write regular JSON file to _data
69
+ jetpack.write(dataPath, regularJson);
70
+
71
+ // Track compiled files
72
+ compiled[dataPath] = true;
73
+
74
+ // Update file contents with regular JSON
75
+ file.contents = Buffer.from(regularJson);
76
+
77
+ callback(null, file);
78
+ } catch (err) {
79
+ logger.error(`Error parsing JSON5 file ${file.path}: ${err.message}`);
80
+ callback(err);
81
+ }
82
+ },
80
83
  }))
81
84
  .pipe(dest(dataOutput));
82
85
 
83
86
  // Then create HTML wrapper files
84
87
  const htmlGeneration = src(input)
85
- .pipe(through2.obj(function (file, _, callback) {
86
- // Skip if it's a directory
87
- if (file.isDirectory()) {
88
- return callback(null, file);
89
- }
90
-
91
- // Get relative path from src/_includes
92
- const relativePath = path.relative(file.base, file.path);
93
- const relativeDir = path.dirname(relativePath);
94
- const basename = path.basename(file.path, '.json');
95
-
96
- // Convert path for Jekyll data reference
97
- // _includes/frontend/sections/nav.json -> _includes.frontend.sections.nav
98
- const dataPath = '_includes.' + relativePath
99
- .replace(/\\/g, '/')
100
- .replace('.json', '')
101
- .split('/')
102
- .join('.');
103
-
104
- // Determine the template path
105
- // Look for corresponding template in themes/{theme}/{target}/...
106
- const templatePath = `themes/${config.theme.id}/${relativeDir}/${basename}.html`;
107
-
108
- // Create the HTML content
109
- const htmlContent = template(INCLUDE_TEMPLATE, {
110
- dataPath: dataPath,
111
- templatePath: templatePath
112
- });
113
-
114
- // Update file contents and extension
115
- file.contents = Buffer.from(htmlContent);
116
- file.path = file.path.replace('.json', '.html');
117
-
118
- // Log transformation
119
- logger.log(`Converting: ${relativePath} -> ${path.basename(file.path)}`);
120
- logger.log(` Data path: site.data.${dataPath}`);
121
- logger.log(` Template: ${templatePath}`);
122
-
123
- // Track the full output path
124
- const fullPath = path.resolve(output, path.relative(file.base, file.path));
125
- compiled[fullPath] = true;
126
-
127
- // Continue
128
- callback(null, file);
88
+ .pipe(new Transform({
89
+ objectMode: true,
90
+ transform(file, _, callback) {
91
+ // Skip if it's a directory
92
+ if (file.isDirectory()) {
93
+ return callback(null, file);
94
+ }
95
+
96
+ // Get relative path from src/_includes
97
+ const relativePath = path.relative(file.base, file.path);
98
+ const relativeDir = path.dirname(relativePath);
99
+ const basename = path.basename(file.path, '.json');
100
+
101
+ // Convert path for Jekyll data reference
102
+ // _includes/frontend/sections/nav.json -> _includes.frontend.sections.nav
103
+ const dataPath = '_includes.' + relativePath
104
+ .replace(/\\/g, '/')
105
+ .replace('.json', '')
106
+ .split('/')
107
+ .join('.');
108
+
109
+ // Determine the template path
110
+ // Look for corresponding template in themes/{theme}/{target}/...
111
+ const templatePath = `themes/${config.theme.id}/${relativeDir}/${basename}.html`;
112
+
113
+ // Create the HTML content
114
+ const htmlContent = template(INCLUDE_TEMPLATE, {
115
+ dataPath: dataPath,
116
+ templatePath: templatePath
117
+ });
118
+
119
+ // Update file contents and extension
120
+ file.contents = Buffer.from(htmlContent);
121
+ file.path = file.path.replace('.json', '.html');
122
+
123
+ // Log transformation
124
+ logger.log(`Converting: ${relativePath} -> ${path.basename(file.path)}`);
125
+ logger.log(` Data path: site.data.${dataPath}`);
126
+ logger.log(` Template: ${templatePath}`);
127
+
128
+ // Track the full output path
129
+ const fullPath = path.resolve(output, path.relative(file.base, file.path));
130
+ compiled[fullPath] = true;
131
+
132
+ // Continue
133
+ callback(null, file);
134
+ },
129
135
  }))
130
136
  .pipe(dest(output));
131
137