ultimate-jekyll-manager 1.4.2 → 1.5.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/CHANGELOG.md +29 -0
- package/CLAUDE-ATTRIBUTION.md +215 -0
- package/CLAUDE.md +7 -6
- package/README.md +1 -0
- package/dist/assets/css/pages/test/libraries/layers/index.scss +28 -0
- package/dist/assets/js/core/auth.js +24 -39
- package/dist/assets/js/modules/redirect.js +5 -4
- package/dist/assets/js/pages/download/index.js +1 -1
- package/dist/assets/js/pages/feedback/index.js +7 -1
- package/dist/assets/js/pages/test/libraries/layers/index.js +11 -0
- package/dist/assets/themes/_template/README.md +50 -0
- package/dist/assets/themes/_template/_config.scss +60 -0
- package/dist/assets/themes/_template/_theme.js +13 -4
- package/dist/assets/themes/_template/_theme.scss +16 -4
- package/dist/assets/themes/_template/css/base/_root.scss +19 -0
- package/dist/assets/themes/_template/css/components/_components.scss +23 -0
- package/dist/assets/themes/classy/README.md +18 -6
- package/dist/assets/themes/neobrutalism/README.md +98 -0
- package/dist/assets/themes/neobrutalism/_config.scss +139 -0
- package/dist/assets/themes/neobrutalism/_theme.js +27 -0
- package/dist/assets/themes/neobrutalism/_theme.scss +33 -0
- package/dist/assets/themes/neobrutalism/css/base/_mixins.scss +46 -0
- package/dist/assets/themes/neobrutalism/css/base/_root.scss +80 -0
- package/dist/assets/themes/neobrutalism/css/base/_typography.scss +77 -0
- package/dist/assets/themes/neobrutalism/css/base/_utilities.scss +25 -0
- package/dist/assets/themes/neobrutalism/css/components/_buttons.scss +148 -0
- package/dist/assets/themes/neobrutalism/css/components/_cards.scss +69 -0
- package/dist/assets/themes/neobrutalism/css/components/_forms.scss +88 -0
- package/dist/assets/themes/neobrutalism/css/components/_infinite-scroll.scss +94 -0
- package/dist/assets/themes/neobrutalism/css/layout/_general.scss +200 -0
- package/dist/assets/themes/neobrutalism/css/layout/_navigation.scss +153 -0
- package/dist/assets/themes/neobrutalism/js/initialize-tooltips.js +20 -0
- package/dist/assets/themes/neobrutalism/js/navbar-scroll.js +29 -0
- package/dist/assets/themes/neobrutalism/pages/index.scss +227 -0
- package/dist/assets/themes/neobrutalism/pages/pricing/index.scss +267 -0
- package/dist/assets/themes/neobrutalism/pages/test/libraries/layers/index.js +9 -0
- package/dist/assets/themes/neobrutalism/pages/test/libraries/layers/index.scss +7 -0
- package/dist/build.js +2 -5
- package/dist/commands/install.js +1 -1
- package/dist/commands/setup.js +41 -0
- package/dist/defaults/CLAUDE.md +5 -1
- package/dist/defaults/dist/_alternatives/example-competitor.md +6 -6
- package/dist/defaults/dist/_includes/admin/sections/sidebar.json +2 -2
- package/dist/defaults/dist/_includes/core/head.html +17 -0
- package/dist/defaults/dist/_includes/themes/classy/backend/sections/topbar.html +1 -1
- package/dist/defaults/dist/_includes/themes/classy/frontend/sections/footer.html +9 -6
- package/dist/defaults/dist/_layouts/blueprint/admin/calendar/index.html +13 -13
- package/dist/defaults/dist/_layouts/blueprint/admin/firebase/index.html +1 -1
- package/dist/defaults/dist/_layouts/blueprint/admin/users/index.html +1 -1
- package/dist/defaults/dist/_layouts/blueprint/admin/users/new.html +5 -5
- package/dist/defaults/dist/_layouts/blueprint/auth/oauth2.html +1 -1
- package/dist/defaults/dist/_layouts/themes/classy/backend/pages/dashboard/index.html +12 -12
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/about.html +1 -1
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/alternatives/alternative.html +4 -4
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/alternatives/index.html +5 -5
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/download.html +4 -2
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/feedback.html +7 -3
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/payment/confirmation.html +1 -1
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/pricing.html +3 -3
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/team/index.html +2 -2
- package/dist/defaults/dist/_layouts/themes/neobrutalism/frontend/core/base.html +31 -0
- package/dist/defaults/dist/_layouts/themes/neobrutalism/frontend/pages/index.html +345 -0
- package/dist/defaults/dist/_layouts/themes/neobrutalism/frontend/pages/pricing.html +483 -0
- package/dist/defaults/dist/_updates/v0.0.1.md +3 -0
- package/dist/defaults/dist/pages/test/account/dashboard.html +1 -1
- package/dist/defaults/dist/pages/test/libraries/ads.html +9 -9
- package/dist/defaults/dist/pages/test/libraries/bootstrap.html +6 -6
- package/dist/defaults/dist/pages/test/libraries/firestore.html +1 -1
- package/dist/defaults/dist/pages/test/libraries/form-manager.html +2 -2
- package/dist/defaults/dist/pages/test/libraries/layers.html +57 -0
- package/dist/defaults/dist/pages/test/libraries/lazy-loading.html +8 -8
- package/dist/defaults/dist/sitemap.html +2 -2
- package/dist/defaults/src/_config.yml +2 -0
- package/dist/defaults/test/_init.js +10 -0
- package/dist/gulp/tasks/defaults.js +8 -0
- package/dist/gulp/tasks/imagemin.js +30 -5
- package/dist/gulp/tasks/sass.js +43 -2
- package/dist/gulp/tasks/translation.js +11 -0
- package/dist/gulp/tasks/utils/manage-test-layers.js +97 -0
- package/dist/index.js +30 -4
- package/dist/test/runner.js +62 -0
- package/dist/test/suites/build/manager.test.js +11 -4
- package/dist/test/suites/build/mode-helpers.test.js +54 -2
- package/dist/utils/attach-log-file.js +24 -16
- package/dist/utils/mode-helpers.js +65 -40
- package/docs/assets.md +6 -1
- package/docs/environment-detection.md +85 -0
- package/docs/test-framework.md +48 -3
- package/docs/themes.md +451 -0
- package/package.json +2 -1
- package/docs/cross-context-helpers.md +0 -75
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
### ALL PAGES ###
|
|
3
|
+
layout: themes/[ site.theme.id ]/frontend/core/minimal
|
|
4
|
+
permalink: /test/libraries/layers
|
|
5
|
+
|
|
6
|
+
### REGULAR PAGES ###
|
|
7
|
+
sitemap:
|
|
8
|
+
include: false
|
|
9
|
+
meta:
|
|
10
|
+
title: "Asset layers"
|
|
11
|
+
description: "Live status of the Global → Theme → Consumer page-asset cascade (CSS + JS)."
|
|
12
|
+
breadcrumb: "Asset layers"
|
|
13
|
+
index: false
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<!--
|
|
17
|
+
ASSET-LAYER TEST PANEL
|
|
18
|
+
Shows the three-layer page-asset cascade (CSS and JS), loaded in this order:
|
|
19
|
+
1. Global — the framework's own page-specific file
|
|
20
|
+
2. Theme — the active theme's page-specific file
|
|
21
|
+
3. Consumer — the consuming project's page-specific file
|
|
22
|
+
Each dot starts RED. A layer turns ITS OWN dot green when it loads (CSS via a
|
|
23
|
+
selector, JS by setting the dot's color). A RED dot = that layer has no file
|
|
24
|
+
for this page (the normal state for layers nobody customized).
|
|
25
|
+
|
|
26
|
+
The Consumer layer only loads if the consuming project has its own files at
|
|
27
|
+
src/assets/{css,js}/pages/test/libraries/layers/index.*. To prove it live
|
|
28
|
+
without committing anything, run with the UJ_TEST_LAYERS flag (see docs/themes.md
|
|
29
|
+
→ "Asset-layer test panel"), which generates those files at build start and
|
|
30
|
+
auto-removes them on the next run.
|
|
31
|
+
-->
|
|
32
|
+
<section class="py-5">
|
|
33
|
+
<div class="container" style="max-width: 720px;">
|
|
34
|
+
<h1 class="mb-2">Asset layers</h1>
|
|
35
|
+
<p class="text-muted mb-4">
|
|
36
|
+
Each <strong>page-specific</strong> asset loads in three layers, in order:
|
|
37
|
+
<strong>Global → Theme → Consumer</strong> — all three are the same
|
|
38
|
+
<code>pages/<path></code> file, just from different sources. Each layer turns its
|
|
39
|
+
own dot <strong>green</strong> when it loads; a <strong>red</strong> dot means that
|
|
40
|
+
layer has no file for this page (the normal state for layers nobody customized).
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
<h2 class="h5 mb-3">Page-specific CSS</h2>
|
|
44
|
+
<ul class="layer-list list-unstyled mb-4">
|
|
45
|
+
<li class="layer-row"><span class="layer-dot" data-layer="css-global"></span> Global <span class="text-muted">(framework default)</span></li>
|
|
46
|
+
<li class="layer-row"><span class="layer-dot" data-layer="css-theme"></span> Theme <span class="text-muted">(active theme)</span></li>
|
|
47
|
+
<li class="layer-row"><span class="layer-dot" data-layer="css-consumer"></span> Consumer <span class="text-muted">(your project)</span></li>
|
|
48
|
+
</ul>
|
|
49
|
+
|
|
50
|
+
<h2 class="h5 mb-3">Page-specific JS</h2>
|
|
51
|
+
<ul class="layer-list list-unstyled mb-0">
|
|
52
|
+
<li class="layer-row"><span class="layer-dot" data-layer="js-global"></span> Global <span class="text-muted">(framework default)</span></li>
|
|
53
|
+
<li class="layer-row"><span class="layer-dot" data-layer="js-theme"></span> Theme <span class="text-muted">(active theme)</span></li>
|
|
54
|
+
<li class="layer-row"><span class="layer-dot" data-layer="js-consumer"></span> Consumer <span class="text-muted">(your project)</span></li>
|
|
55
|
+
</ul>
|
|
56
|
+
</div>
|
|
57
|
+
</section>
|
|
@@ -33,7 +33,7 @@ meta:
|
|
|
33
33
|
|
|
34
34
|
<!-- First visible image (should load immediately) -->
|
|
35
35
|
<section>
|
|
36
|
-
<h2 class="h3 mb-3">1. Above the
|
|
36
|
+
<h2 class="h3 mb-3">1. Above the fold image (lazy loading starts immediately)</h2>
|
|
37
37
|
<p>This image is visible on page load, so it should start loading immediately:</p>
|
|
38
38
|
<img data-lazy="@src https://placehold.co/800x400?text=Immediate+Load"
|
|
39
39
|
class="card-img-top lazy"
|
|
@@ -50,7 +50,7 @@ meta:
|
|
|
50
50
|
|
|
51
51
|
<!-- Lazy loaded images with data-src -->
|
|
52
52
|
<section>
|
|
53
|
-
<h2 class="h3 mb-3">2. Standard
|
|
53
|
+
<h2 class="h3 mb-3">2. Standard lazy loading (data-src)</h2>
|
|
54
54
|
<p>These images use <code>data-src</code> attribute and will load when scrolled into view:</p>
|
|
55
55
|
|
|
56
56
|
<div class="row g-4 mb-4">
|
|
@@ -153,7 +153,7 @@ meta:
|
|
|
153
153
|
|
|
154
154
|
<!-- Lazy Classes -->
|
|
155
155
|
<section>
|
|
156
|
-
<h2 class="h3 mb-3">6. Lazy
|
|
156
|
+
<h2 class="h3 mb-3">6. Lazy loaded classes</h2>
|
|
157
157
|
<p>These elements get classes added when they come into view:</p>
|
|
158
158
|
|
|
159
159
|
<div class="row g-4">
|
|
@@ -194,7 +194,7 @@ meta:
|
|
|
194
194
|
|
|
195
195
|
<!-- Iframes -->
|
|
196
196
|
<section>
|
|
197
|
-
<h2 class="h3 mb-3">7. Lazy
|
|
197
|
+
<h2 class="h3 mb-3">7. Lazy loaded iframes</h2>
|
|
198
198
|
<p>YouTube videos and other iframes that load on scroll:</p>
|
|
199
199
|
|
|
200
200
|
<div class="ratio ratio-16x9 mb-4">
|
|
@@ -242,7 +242,7 @@ meta:
|
|
|
242
242
|
|
|
243
243
|
<!-- Video -->
|
|
244
244
|
<section>
|
|
245
|
-
<h2 class="h3 mb-3">9. Lazy
|
|
245
|
+
<h2 class="h3 mb-3">9. Lazy loaded video</h2>
|
|
246
246
|
<p>HTML5 video that loads when scrolled into view:</p>
|
|
247
247
|
|
|
248
248
|
<video data-lazy="@src https://www.w3schools.com/html/mov_bbb.mp4"
|
|
@@ -257,7 +257,7 @@ meta:
|
|
|
257
257
|
|
|
258
258
|
<!-- Slow Loading Test with Picsum -->
|
|
259
259
|
<section>
|
|
260
|
-
<h2 class="h3 mb-3">10. Slow
|
|
260
|
+
<h2 class="h3 mb-3">10. Slow loading test (loading animation demo)</h2>
|
|
261
261
|
<p>These images from picsum.photos load slowly, so you can see the loading animation:</p>
|
|
262
262
|
|
|
263
263
|
<div class="row g-4">
|
|
@@ -325,7 +325,7 @@ meta:
|
|
|
325
325
|
|
|
326
326
|
<!-- Error handling test -->
|
|
327
327
|
<section>
|
|
328
|
-
<h2 class="h3 mb-3">12. Error
|
|
328
|
+
<h2 class="h3 mb-3">12. Error handling test</h2>
|
|
329
329
|
<p>These images have invalid URLs to test error handling:</p>
|
|
330
330
|
|
|
331
331
|
<div class="row g-4">
|
|
@@ -373,7 +373,7 @@ meta:
|
|
|
373
373
|
|
|
374
374
|
<!-- Dynamic content test -->
|
|
375
375
|
<section>
|
|
376
|
-
<h2 class="h3 mb-3">14. Dynamic
|
|
376
|
+
<h2 class="h3 mb-3">14. Dynamic content test</h2>
|
|
377
377
|
<p>Click the button below to dynamically add new lazy-loaded images to test MutationObserver:</p>
|
|
378
378
|
|
|
379
379
|
<button id="add-dynamic-images" class="btn btn-primary mb-4">Add dynamic images</button>
|
|
@@ -54,7 +54,7 @@ web_manager:
|
|
|
54
54
|
{%- endif -%}
|
|
55
55
|
|
|
56
56
|
{% assign url_parts = page.url | split: '/' %}
|
|
57
|
-
{% assign section_name = "Root
|
|
57
|
+
{% assign section_name = "Root pages" %}
|
|
58
58
|
|
|
59
59
|
{% if url_parts.size > 2 %}
|
|
60
60
|
{% assign section_name = url_parts[1] | replace: '-', ' ' | replace: '_', ' ' | capitalize %}
|
|
@@ -104,7 +104,7 @@ web_manager:
|
|
|
104
104
|
{%- endif -%}
|
|
105
105
|
|
|
106
106
|
{% assign url_parts = page.url | split: '/' %}
|
|
107
|
-
{% assign page_section = "Root
|
|
107
|
+
{% assign page_section = "Root pages" %}
|
|
108
108
|
|
|
109
109
|
{% if url_parts.size > 2 %}
|
|
110
110
|
{% assign page_section = url_parts[1] | replace: '-', ' ' | replace: '_', ' ' | capitalize %}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test lifecycle hook for this project. Runs once before any suite (not a test itself).
|
|
3
|
+
* See ultimate-jekyll-manager/docs/test-framework.md → "test/_init.js".
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = ({ projectRoot }) => ({
|
|
7
|
+
// Seed any fixture a suite needs before it runs.
|
|
8
|
+
async setup() {
|
|
9
|
+
},
|
|
10
|
+
});
|
|
@@ -8,6 +8,7 @@ const path = require('path');
|
|
|
8
8
|
const { minimatch } = require('minimatch');
|
|
9
9
|
const { template } = require('node-powertools');
|
|
10
10
|
const createTemplateTransform = require('./utils/template-transform');
|
|
11
|
+
const manageTestLayers = require('./utils/manage-test-layers');
|
|
11
12
|
const argv = require('yargs')(process.argv.slice(2)).parseSync();
|
|
12
13
|
const JSON5 = require('json5');
|
|
13
14
|
|
|
@@ -391,6 +392,13 @@ function defaults(complete, changedFile) {
|
|
|
391
392
|
logger.log('Starting...');
|
|
392
393
|
Manager.logMemory(logger, 'Start');
|
|
393
394
|
|
|
395
|
+
// Test-layer fixtures (dev only): clean any prior generated files, and generate
|
|
396
|
+
// fresh ones into the consumer src when UJ_TEST_LAYERS=true — at build START so
|
|
397
|
+
// sass + jekyll pick them up. Only on a full run, not per-changed-file in watch.
|
|
398
|
+
if (!changedFile) {
|
|
399
|
+
manageTestLayers(Manager, logger);
|
|
400
|
+
}
|
|
401
|
+
|
|
394
402
|
// Use changedFile if provided, otherwise use all inputs
|
|
395
403
|
const filesToProcess = changedFile ? [changedFile] : input;
|
|
396
404
|
logger.log('input', filesToProcess)
|
|
@@ -6,6 +6,7 @@ const glob = require('glob').globSync;
|
|
|
6
6
|
const responsive = require('gulp-responsive-modern');
|
|
7
7
|
const sharp = require('sharp');
|
|
8
8
|
const path = require('path');
|
|
9
|
+
const { Transform } = require('stream');
|
|
9
10
|
const jetpack = require('fs-jetpack');
|
|
10
11
|
const GitHubCache = require('./utils/github-cache');
|
|
11
12
|
|
|
@@ -188,6 +189,7 @@ async function imagemin(complete) {
|
|
|
188
189
|
// above (so `npm start` never blocks on this), letting BrowserSync reload as images land later.
|
|
189
190
|
await new Promise((resolve, reject) => {
|
|
190
191
|
src(filesToProcess, { base: 'src/assets/images' })
|
|
192
|
+
.pipe(lowercaseExtTransform())
|
|
191
193
|
.pipe(responsive({
|
|
192
194
|
[`**/${RESPONSIVE_GLOB}`]: responsiveConfigs
|
|
193
195
|
}, {
|
|
@@ -358,6 +360,24 @@ async function rewriteOversizedSources(files) {
|
|
|
358
360
|
}
|
|
359
361
|
}
|
|
360
362
|
|
|
363
|
+
// Lowercase the extension on each Vinyl file's path before piping into gulp-responsive-modern.
|
|
364
|
+
// gulp-responsive-modern's lib/format.js uses a case-sensitive switch on path.extname() and returns
|
|
365
|
+
// the string 'unsupported' for anything else, which then crashes sharp.toFormat(). Files saved
|
|
366
|
+
// straight off a camera (IMG_3119.JPG) hit this. Rewriting the Vinyl path in-stream keeps the
|
|
367
|
+
// on-disk file untouched while letting the plugin recognize the format.
|
|
368
|
+
function lowercaseExtTransform() {
|
|
369
|
+
return new Transform({
|
|
370
|
+
objectMode: true,
|
|
371
|
+
transform(file, _enc, cb) {
|
|
372
|
+
const ext = path.extname(file.path);
|
|
373
|
+
if (ext && ext !== ext.toLowerCase()) {
|
|
374
|
+
file.path = file.path.slice(0, -ext.length) + ext.toLowerCase();
|
|
375
|
+
}
|
|
376
|
+
cb(null, file);
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
361
381
|
// Build responsive configurations from PICTURE_SIZES
|
|
362
382
|
function getResponsiveConfigs() {
|
|
363
383
|
const configs = [];
|
|
@@ -552,21 +572,26 @@ function logImageStatistics(stats, startTime, endTime) {
|
|
|
552
572
|
// Size reduction stats
|
|
553
573
|
if (stats.sizeBefore > 0 && stats.sizeAfter > 0) {
|
|
554
574
|
const savedPercent = ((stats.savedBytes / stats.sizeBefore) * 100).toFixed(1);
|
|
575
|
+
const label = stats.savedBytes < 0 ? 'Total added' : 'Total saved';
|
|
555
576
|
logger.log('\n💾 Size Reduction:');
|
|
556
577
|
logger.log(` Original size: ${formatBytes(stats.sizeBefore)}`);
|
|
557
578
|
logger.log(` Optimized size: ${formatBytes(stats.sizeAfter)}`);
|
|
558
|
-
logger.log(`
|
|
579
|
+
logger.log(` ${label}: ${formatBytes(Math.abs(stats.savedBytes))} (${savedPercent}%)`);
|
|
559
580
|
}
|
|
560
581
|
|
|
561
582
|
logger.log('═══════════════════════════════════════\n');
|
|
562
583
|
}
|
|
563
584
|
|
|
564
|
-
// Helper to format bytes
|
|
585
|
+
// Helper to format bytes. Handles negative inputs — when responsive variants (8 per source)
|
|
586
|
+
// sum to more than the cached original, savedBytes goes negative; without the absolute-value
|
|
587
|
+
// guard, Math.log(negative) is NaN and the suffix index becomes NaN -> "NaN undefined".
|
|
565
588
|
function formatBytes(bytes, decimals = 2) {
|
|
566
589
|
if (bytes === 0) return '0 Bytes';
|
|
590
|
+
const sign = bytes < 0 ? '-' : '';
|
|
591
|
+
const abs = Math.abs(bytes);
|
|
567
592
|
const k = 1024;
|
|
568
593
|
const dm = decimals < 0 ? 0 : decimals;
|
|
569
|
-
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
570
|
-
const i = Math.floor(Math.log(
|
|
571
|
-
return parseFloat((
|
|
594
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
595
|
+
const i = Math.min(Math.floor(Math.log(abs) / Math.log(k)), sizes.length - 1);
|
|
596
|
+
return sign + parseFloat((abs / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
|
572
597
|
}
|
package/dist/gulp/tasks/sass.js
CHANGED
|
@@ -44,6 +44,24 @@ const bundleFiles = [
|
|
|
44
44
|
'src/assets/css/bundles/*.scss',
|
|
45
45
|
];
|
|
46
46
|
|
|
47
|
+
// Build the active theme's page-CSS globs, but ONLY for `pages` dirs that exist —
|
|
48
|
+
// gulp's src() throws ENOENT when it scandirs a missing directory. Project theme
|
|
49
|
+
// takes priority over the package theme (matches the __theme__ resolution rule).
|
|
50
|
+
function themePageGlobs() {
|
|
51
|
+
if (!config.theme.id) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const candidates = [
|
|
56
|
+
path.resolve(rootPathProject, 'src/assets/themes', config.theme.id, 'pages'),
|
|
57
|
+
path.resolve(rootPathPackage, 'dist/assets/themes', config.theme.id, 'pages'),
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
return candidates
|
|
61
|
+
.filter((dir) => jetpack.exists(dir))
|
|
62
|
+
.map((dir) => `${dir}/**/*.scss`);
|
|
63
|
+
}
|
|
64
|
+
|
|
47
65
|
// Glob
|
|
48
66
|
const input = [
|
|
49
67
|
// Bundle files (admin, and any future bundles)
|
|
@@ -56,10 +74,23 @@ const input = [
|
|
|
56
74
|
`${rootPathPackage}/dist/assets/css/pages/**/*.scss`,
|
|
57
75
|
'src/assets/css/pages/**/*.scss',
|
|
58
76
|
|
|
77
|
+
// Theme page-specific CSS (theme-aware page styles).
|
|
78
|
+
// Compiles to a SEPARATE bundle (pages/<path>/index.<themeId>.bundle.css)
|
|
79
|
+
// that head.html links IN ADDITION to the base + consumer page bundles.
|
|
80
|
+
// Missing = nothing loads (component styles handle it) — no fallback needed.
|
|
81
|
+
//
|
|
82
|
+
// Only include a glob whose base `pages` dir exists — gulp's src() throws ENOENT
|
|
83
|
+
// if it scandirs a non-existent directory. Most consumers don't shadow the theme,
|
|
84
|
+
// so the project-side path usually won't exist.
|
|
85
|
+
...themePageGlobs(),
|
|
86
|
+
|
|
59
87
|
// Files to exclude
|
|
60
88
|
// '!dist/**',
|
|
61
89
|
];
|
|
62
90
|
|
|
91
|
+
// Marker appended to theme page bundles so head.html can find them by theme id.
|
|
92
|
+
const THEME_PAGE_SUFFIX = config.theme.id ? `.${config.theme.id}` : '';
|
|
93
|
+
|
|
63
94
|
// Additional files to watch (but not compile as entry points)
|
|
64
95
|
const watchInput = [
|
|
65
96
|
// Watch the paths we're compiling
|
|
@@ -345,7 +376,17 @@ function sass(complete) {
|
|
|
345
376
|
.pipe(cleanCSS({
|
|
346
377
|
format: Manager.actLikeProduction() ? 'compressed' : 'beautify',
|
|
347
378
|
}))
|
|
348
|
-
.pipe(rename((file) => {
|
|
379
|
+
.pipe(rename((file, vinyl) => {
|
|
380
|
+
// Theme page CSS originates from .../themes/<id>/pages/... — tag the bundle
|
|
381
|
+
// with the theme id (e.g. index.neobrutalism.bundle.css) so it compiles to
|
|
382
|
+
// its own file that head.html links alongside the base + consumer bundles.
|
|
383
|
+
const sourcePath = (vinyl && vinyl.history[0]) || '';
|
|
384
|
+
const isThemePage = THEME_PAGE_SUFFIX
|
|
385
|
+
&& sourcePath.replace(/\\/g, '/').includes(`/themes/${config.theme.id}/pages/`);
|
|
386
|
+
if (isThemePage) {
|
|
387
|
+
file.basename += THEME_PAGE_SUFFIX;
|
|
388
|
+
}
|
|
389
|
+
|
|
349
390
|
// Add bundle to the name
|
|
350
391
|
file.basename += '.bundle';
|
|
351
392
|
|
|
@@ -355,7 +396,7 @@ function sass(complete) {
|
|
|
355
396
|
bundleNames.push('main'); // main.scss is always a root bundle
|
|
356
397
|
|
|
357
398
|
// Check if this is a root-level bundle
|
|
358
|
-
const baseName = file.basename.replace('.bundle', '');
|
|
399
|
+
const baseName = file.basename.replace('.bundle', '').replace(THEME_PAGE_SUFFIX, '');
|
|
359
400
|
const isBundle = bundleNames.includes(baseName);
|
|
360
401
|
|
|
361
402
|
// Check
|
|
@@ -995,6 +995,11 @@ function getIgnoredPages() {
|
|
|
995
995
|
const socials = config?.socials || {};
|
|
996
996
|
// const downloads = config?.downloads || {};
|
|
997
997
|
|
|
998
|
+
// User-configured excludes (translation.exclude). Each entry can be a folder
|
|
999
|
+
// (e.g. "blog" → /blog/**) or a single page path (e.g. "some-page"). We add
|
|
1000
|
+
// each to BOTH files and folders so it matches either way.
|
|
1001
|
+
const userExcludes = config?.translation?.exclude || [];
|
|
1002
|
+
|
|
998
1003
|
const redirectsDir = path.join('dist', 'redirects');
|
|
999
1004
|
const redirectFiles = glob(`${redirectsDir}/**/*.html`);
|
|
1000
1005
|
const redirectPermalinks = [];
|
|
@@ -1045,6 +1050,9 @@ function getIgnoredPages() {
|
|
|
1045
1050
|
|
|
1046
1051
|
// Redirects
|
|
1047
1052
|
...redirectPermalinks,
|
|
1053
|
+
|
|
1054
|
+
// User-configured excludes (treated as a page path)
|
|
1055
|
+
...userExcludes,
|
|
1048
1056
|
],
|
|
1049
1057
|
folders: [
|
|
1050
1058
|
// Languages
|
|
@@ -1055,6 +1063,9 @@ function getIgnoredPages() {
|
|
|
1055
1063
|
|
|
1056
1064
|
// Firestore auth pages
|
|
1057
1065
|
'__/auth',
|
|
1066
|
+
|
|
1067
|
+
// User-configured excludes (treated as a folder)
|
|
1068
|
+
...userExcludes,
|
|
1058
1069
|
],
|
|
1059
1070
|
};
|
|
1060
1071
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Test-layer manager (dev only)
|
|
2
|
+
// Powers the CONSUMER layer of the /test/libraries/layers asset-cascade panel.
|
|
3
|
+
//
|
|
4
|
+
// The Consumer dots only go green if the consuming project has its own page files
|
|
5
|
+
// at src/assets/{css,js}/pages/test/libraries/layers/index.*. To prove that layer
|
|
6
|
+
// live WITHOUT permanently adding files to the consumer, this is OPT-IN via the
|
|
7
|
+
// UJ_TEST_LAYERS=true env flag:
|
|
8
|
+
//
|
|
9
|
+
// - ALWAYS (every run): remove any previously-generated consumer test-layer files,
|
|
10
|
+
// so they never persist or get committed even if the flag is later unset.
|
|
11
|
+
// - WHEN UJ_TEST_LAYERS=true: (re)generate them into the consumer's src/ at build
|
|
12
|
+
// START — before sass + jekyll run — so the real __project_assets__ / consumer
|
|
13
|
+
// page-CSS path picks them up exactly like any other consumer page file. This is
|
|
14
|
+
// the honest mechanism (no aliases/shims); the files are just real, briefly.
|
|
15
|
+
//
|
|
16
|
+
// Generated files carry a GENERATED marker so the cleaner only ever deletes its own.
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const jetpack = require('fs-jetpack');
|
|
19
|
+
|
|
20
|
+
// Page path the panel lives at → where its consumer-layer assets must sit.
|
|
21
|
+
const REL_CSS = 'src/assets/css/pages/test/libraries/layers/index.scss';
|
|
22
|
+
const REL_JS = 'src/assets/js/pages/test/libraries/layers/index.js';
|
|
23
|
+
|
|
24
|
+
const MARKER = 'GENERATED — UJ_TEST_LAYERS';
|
|
25
|
+
|
|
26
|
+
// NOTE: a consumer page-CSS file shares the SAME output bundle as the framework's
|
|
27
|
+
// base page CSS, so it must @use the base (like every real consumer page file) to
|
|
28
|
+
// COMPOSE with it rather than replace it. The base lives at the same page path under
|
|
29
|
+
// UJM's css/, importable via the SASS loadPaths (which include UJM's dist/assets/css).
|
|
30
|
+
const CSS_CONTENT = `// ${MARKER} (auto-removed on the next build; do not commit)
|
|
31
|
+
// Consumer layer of the /test/libraries/layers panel → turns the "css-consumer" dot green.
|
|
32
|
+
@use 'pages/test/libraries/layers/index' as *;
|
|
33
|
+
|
|
34
|
+
.layer-dot[data-layer="css-consumer"] {
|
|
35
|
+
background: #30a46c; // green
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
const JS_CONTENT = `// ${MARKER} (auto-removed on the next build; do not commit)
|
|
40
|
+
// Consumer layer of the /test/libraries/layers panel → turns the "js-consumer" dot green.
|
|
41
|
+
export default ({ manager, options }) => {
|
|
42
|
+
const dot = document.querySelector('.layer-dot[data-layer="js-consumer"]');
|
|
43
|
+
if (dot) {
|
|
44
|
+
dot.style.background = '#30a46c';
|
|
45
|
+
}
|
|
46
|
+
console.log('[test-layer] consumer JS ran → js-consumer dot green');
|
|
47
|
+
};
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
// Delete a generated file only if it still carries our marker (never clobber a real
|
|
51
|
+
// consumer file someone legitimately created at this path).
|
|
52
|
+
function removeIfGenerated(absPath) {
|
|
53
|
+
if (!jetpack.exists(absPath)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
const contents = jetpack.read(absPath) || '';
|
|
57
|
+
if (contents.includes(MARKER)) {
|
|
58
|
+
jetpack.remove(absPath);
|
|
59
|
+
// Clean up now-empty generated dirs (best effort)
|
|
60
|
+
jetpack.remove(path.dirname(absPath) + '/.keep'); // no-op if absent
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Manage the consumer-layer test fixtures (dev only).
|
|
68
|
+
* Always cleans prior generated files; generates fresh ones when UJ_TEST_LAYERS=true.
|
|
69
|
+
* @param {object} Manager - UJM build Manager
|
|
70
|
+
* @param {object} logger - task logger (optional)
|
|
71
|
+
*/
|
|
72
|
+
function manageTestLayers(Manager, logger) {
|
|
73
|
+
// Never touch anything in a production build
|
|
74
|
+
if (Manager.isBuildMode()) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const cssPath = path.resolve(process.cwd(), REL_CSS);
|
|
79
|
+
const jsPath = path.resolve(process.cwd(), REL_JS);
|
|
80
|
+
|
|
81
|
+
// 1) Always clean previously-generated files
|
|
82
|
+
const removed = [removeIfGenerated(cssPath), removeIfGenerated(jsPath)].filter(Boolean).length;
|
|
83
|
+
|
|
84
|
+
// 2) Generate when explicitly requested
|
|
85
|
+
const enabled = process.env.UJ_TEST_LAYERS === 'true';
|
|
86
|
+
if (enabled) {
|
|
87
|
+
jetpack.write(cssPath, CSS_CONTENT);
|
|
88
|
+
jetpack.write(jsPath, JS_CONTENT);
|
|
89
|
+
if (logger) {
|
|
90
|
+
logger.log('UJ_TEST_LAYERS: generated consumer test-layer files (auto-removed next run)');
|
|
91
|
+
}
|
|
92
|
+
} else if (removed > 0 && logger) {
|
|
93
|
+
logger.log(`UJ_TEST_LAYERS: cleaned ${removed} stale generated test-layer file(s)`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = manageTestLayers;
|
package/dist/index.js
CHANGED
|
@@ -66,10 +66,17 @@ class Manager {
|
|
|
66
66
|
.catch(e => console.error('Failed to load ultimate-jekyll-manager.js:', e))
|
|
67
67
|
);
|
|
68
68
|
|
|
69
|
+
// Theme page module path (resolved via the __theme__ webpack alias)
|
|
70
|
+
const themeModulePathFull = `__theme__/pages/${pageModulePath}`;
|
|
71
|
+
|
|
69
72
|
console.log(`Page-specific module loading: #main/${pageModulePathFull}`);
|
|
73
|
+
console.log(`Page-specific module loading: #theme/${themeModulePathFull}`);
|
|
70
74
|
console.log(`Page-specific module loading: #project/${pageModulePathFull}`);
|
|
71
75
|
|
|
72
|
-
// Load page-specific scripts
|
|
76
|
+
// Load page-specific scripts.
|
|
77
|
+
// Three layers, executed in order: #main (framework default) → #theme
|
|
78
|
+
// (active theme) → #project (consumer). Mirrors the page-CSS cascade.
|
|
79
|
+
// A missing module at any layer is a no-op — no fallback needed.
|
|
73
80
|
modulePromises.push(
|
|
74
81
|
// Import the main page-specific script
|
|
75
82
|
import(`__main_assets__/js/pages/${pageModulePath}`)
|
|
@@ -85,11 +92,29 @@ class Manager {
|
|
|
85
92
|
})
|
|
86
93
|
);
|
|
87
94
|
|
|
95
|
+
modulePromises.push(
|
|
96
|
+
// Import the active theme's page-specific script.
|
|
97
|
+
// webpackInclude restricts the dynamic-import context to .js files — the
|
|
98
|
+
// theme's pages/ dir also contains page CSS (.scss), which must NOT be
|
|
99
|
+
// pulled into the JS context (webpack would try to parse it as JS).
|
|
100
|
+
import(/* webpackInclude: /\.js$/ */ `__theme__/pages/${pageModulePath}`)
|
|
101
|
+
.then(mod => {
|
|
102
|
+
modules[1] = { tag: 'theme', default: mod?.default };
|
|
103
|
+
})
|
|
104
|
+
.catch(e => {
|
|
105
|
+
if (this.isNotFound(e, pageModulePath)) {
|
|
106
|
+
console.warn(`Page-specific module missing: #theme/${themeModulePathFull}`);
|
|
107
|
+
} else {
|
|
108
|
+
console.error(`Page-specific module error: #theme/${themeModulePathFull}`, e);
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
);
|
|
112
|
+
|
|
88
113
|
modulePromises.push(
|
|
89
114
|
// Import the project page-specific script
|
|
90
115
|
import(`__project_assets__/js/pages/${pageModulePath}`)
|
|
91
116
|
.then(mod => {
|
|
92
|
-
modules[
|
|
117
|
+
modules[2] = { tag: 'project', default: mod?.default };
|
|
93
118
|
})
|
|
94
119
|
.catch(e => {
|
|
95
120
|
if (this.isNotFound(e, pageModulePath)) {
|
|
@@ -111,12 +136,13 @@ class Manager {
|
|
|
111
136
|
}
|
|
112
137
|
|
|
113
138
|
// Execute the module function
|
|
139
|
+
const modPathLabel = mod.tag === 'theme' ? themeModulePathFull : pageModulePathFull;
|
|
114
140
|
try {
|
|
115
|
-
console.log(`Page-specific module loaded: #${mod.tag}/${
|
|
141
|
+
console.log(`Page-specific module loaded: #${mod.tag}/${modPathLabel}`);
|
|
116
142
|
|
|
117
143
|
await mod.default({ manager: this, options });
|
|
118
144
|
} catch (e) {
|
|
119
|
-
console.error(`Page-specific module error: #${mod.tag}/${
|
|
145
|
+
console.error(`Page-specific module error: #${mod.tag}/${modPathLabel}`, e);
|
|
120
146
|
break; // Stop execution if any module fails
|
|
121
147
|
}
|
|
122
148
|
}
|
package/dist/test/runner.js
CHANGED
|
@@ -50,6 +50,10 @@ async function run(options = {}) {
|
|
|
50
50
|
console.log('');
|
|
51
51
|
console.log(chalk.bold(' Ultimate Jekyll Manager Tests'));
|
|
52
52
|
|
|
53
|
+
// Run the optional test/_init.js setup() hooks (framework + consumer) ONCE,
|
|
54
|
+
// before any suite. There is no cleanup hook — tests clean up after themselves.
|
|
55
|
+
await runInitSetups();
|
|
56
|
+
|
|
53
57
|
const results = { passed: 0, failed: 0, skipped: 0, tests: [] };
|
|
54
58
|
|
|
55
59
|
if (sources.framework.length > 0) {
|
|
@@ -399,4 +403,62 @@ function relativizePath(file, source) {
|
|
|
399
403
|
return path.relative(path.join(process.cwd(), 'test'), file);
|
|
400
404
|
}
|
|
401
405
|
|
|
406
|
+
// ---------------------------------------------------------------------------
|
|
407
|
+
// test/_init.js — pre-test lifecycle hook (setup only)
|
|
408
|
+
//
|
|
409
|
+
// Mirrors the backend framework's hook so all four frameworks share one shape.
|
|
410
|
+
// A project may add `<cwd>/test/_init.js` exporting a FUNCTION —
|
|
411
|
+
// `module.exports = (ctx) => ({ setup })` — called with `{ projectRoot }` and
|
|
412
|
+
// returning an object with an async `setup({ projectRoot })` that runs ONCE
|
|
413
|
+
// before any suite (e.g. to scaffold a fixture file the boot layer needs).
|
|
414
|
+
// There is no `cleanup` hook: tests clean up after themselves. Unlike the
|
|
415
|
+
// backend framework, there is no `accounts` field here — these frameworks have
|
|
416
|
+
// no auth/user system.
|
|
417
|
+
// ---------------------------------------------------------------------------
|
|
418
|
+
|
|
419
|
+
function loadInit(testDir, label) {
|
|
420
|
+
const initPath = path.join(testDir, '_init.js');
|
|
421
|
+
|
|
422
|
+
if (!jetpack.exists(initPath)) {
|
|
423
|
+
return {};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
try {
|
|
427
|
+
const fn = require(initPath);
|
|
428
|
+
|
|
429
|
+
if (typeof fn !== 'function') {
|
|
430
|
+
console.log(chalk.red(` ✗ ${label} test/_init.js must export a function: module.exports = (ctx) => ({ ... })`));
|
|
431
|
+
return {};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const mod = fn({ projectRoot: process.cwd() });
|
|
435
|
+
return mod && typeof mod === 'object' ? mod : {};
|
|
436
|
+
} catch (e) {
|
|
437
|
+
console.log(chalk.red(` ✗ Failed to load ${label} test/_init.js: ${e.message}`));
|
|
438
|
+
return {};
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
async function runInitSetups() {
|
|
443
|
+
const frameworkTestsDir = path.resolve(__dirname, '../../test');
|
|
444
|
+
const projectTestsDir = path.join(process.cwd(), 'test');
|
|
445
|
+
|
|
446
|
+
const hooks = [
|
|
447
|
+
loadInit(frameworkTestsDir, 'framework'),
|
|
448
|
+
loadInit(projectTestsDir, 'project'),
|
|
449
|
+
];
|
|
450
|
+
|
|
451
|
+
const setups = hooks.filter((h) => typeof h.setup === 'function').map((h) => h.setup);
|
|
452
|
+
|
|
453
|
+
for (const setup of setups) {
|
|
454
|
+
process.stdout.write(chalk.gray(' Running test/_init.js setup... '));
|
|
455
|
+
try {
|
|
456
|
+
await setup({ projectRoot: process.cwd() });
|
|
457
|
+
console.log(chalk.green('✓'));
|
|
458
|
+
} catch (e) {
|
|
459
|
+
console.log(chalk.red(`✗ (${e.message})`));
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
402
464
|
module.exports = { run, SkipError };
|
|
@@ -86,18 +86,25 @@ module.exports = {
|
|
|
86
86
|
},
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
|
-
name: 'getEnvironment maps
|
|
89
|
+
name: 'getEnvironment maps to development/testing/production',
|
|
90
90
|
run: async (ctx) => {
|
|
91
91
|
const Manager = require('../../../build.js');
|
|
92
|
-
const
|
|
92
|
+
const origServer = process.env.UJ_IS_SERVER;
|
|
93
|
+
const origTest = process.env.UJ_TEST_MODE;
|
|
93
94
|
try {
|
|
95
|
+
// Testing wins over everything.
|
|
96
|
+
process.env.UJ_TEST_MODE = 'true';
|
|
97
|
+
process.env.UJ_IS_SERVER = 'true';
|
|
98
|
+
ctx.expect(Manager.getEnvironment()).toBe('testing');
|
|
99
|
+
// With testing cleared, server flag → production; absent → development.
|
|
100
|
+
delete process.env.UJ_TEST_MODE;
|
|
94
101
|
process.env.UJ_IS_SERVER = 'true';
|
|
95
102
|
ctx.expect(Manager.getEnvironment()).toBe('production');
|
|
96
103
|
delete process.env.UJ_IS_SERVER;
|
|
97
104
|
ctx.expect(Manager.getEnvironment()).toBe('development');
|
|
98
105
|
} finally {
|
|
99
|
-
if (
|
|
100
|
-
else process.env.
|
|
106
|
+
if (origServer === undefined) delete process.env.UJ_IS_SERVER; else process.env.UJ_IS_SERVER = origServer;
|
|
107
|
+
if (origTest === undefined) delete process.env.UJ_TEST_MODE; else process.env.UJ_TEST_MODE = origTest;
|
|
101
108
|
}
|
|
102
109
|
},
|
|
103
110
|
},
|