ultimate-jekyll-manager 1.4.3 → 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 +14 -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/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/_includes/core/head.html +17 -0
- package/dist/defaults/dist/_includes/themes/classy/frontend/sections/footer.html +4 -4
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/download.html +2 -0
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/feedback.html +7 -3
- 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/pages/test/libraries/layers.html +57 -0
- 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/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/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
package/docs/test-framework.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
UJM ships a built-in three-layer test harness. `npx mgr test` discovers framework suites from `<ujm>/dist/test/suites/**/*.js` and consumer suites from `<cwd>/test/**/*.js`, partitions by `layer`, and runs each layer in the right environment. Same shape as the sister harnesses in EM (electron-manager) and BXM (browser-extension-manager).
|
|
4
4
|
|
|
5
|
+
## 🚫 NEVER mock — test against the real harness (HARD RULE)
|
|
6
|
+
|
|
7
|
+
**Do NOT hand-roll fake/stub/mock objects.** Every test runs against a real environment, and the harness hands the test the real thing — use it:
|
|
8
|
+
|
|
9
|
+
- **`build` layer** gets the **real** `Manager` from `require('ultimate-jekyll-manager/build')` — call its real API (`getConfig`, `getUJMConfig`, `getPackage`, `isTesting`, the gulp pure helpers). Never fake a `Manager` whose `getConfig()` returns canned data.
|
|
10
|
+
- **`page` layer** runs in a **real** headless Chromium tab with real `window`/`document` and the harness-provided `window.Configuration`. Drive the real frontend Manager surface; don't stub DOM globals in your test.
|
|
11
|
+
- **`boot` layer** runs against the **real** built `_site/` served over a **real** HTTP origin — exercise the actually-shipped site through Puppeteer.
|
|
12
|
+
- **Pure functions (zero I/O) are the ONLY thing you call directly** — e.g. `mergeJekyllConfigs`, `validateYAMLFrontMatter`, `createTemplateTransform`, `collectTextNodes`. `require()` them and pass plain inputs. That is NOT mocking — there is nothing to mock. The moment a function touches real I/O (config files, the DOM, the HTTP server, an external API), it MUST run against the real harness/build, not a stub.
|
|
13
|
+
|
|
14
|
+
If you find yourself writing `const mockX = {...}` to satisfy code under test, STOP — use the real context the layer already provides, or (if it's genuinely pure) call it with plain data.
|
|
15
|
+
|
|
16
|
+
### The ONLY two exceptions where a narrow stub is allowed
|
|
17
|
+
|
|
18
|
+
Mock **nothing** by default. There are exactly two cases where the real dependency genuinely cannot run in the test environment — and even then, stub the *smallest possible seam* (one method / one object), restore it immediately, and comment *why*:
|
|
19
|
+
|
|
20
|
+
1. **A side effect that would destroy the test run itself.** If the real call would kill or corrupt the harness — a process-exit, a destructive `_site/` wipe, a recursive re-invocation of the build/test command — stub *that one call* to a no-op, assert the surrounding logic, then restore. You are preventing the harness from terminating mid-assertion, not faking behavior.
|
|
21
|
+
2. **A real dependency the test environment can't provide.** When the real thing only exists from infra you can't stand up in the current layer (an external service with no local equivalent, a second running instance), a unit test may hand minimal inputs to exercise the logic in isolation — but a real-harness test (`page`/`boot`) MUST still cover the wired path where one exists.
|
|
22
|
+
|
|
23
|
+
If you can run it for real, you must. These exceptions are not a license to unit-test in isolation when a real-harness layer would work.
|
|
24
|
+
|
|
25
|
+
**External APIs are skipped in-source, NOT mocked.** UJM build/gulp code that would hit the network (e.g. fetching Firebase auth files) short-circuits in its own source when `Manager.isTesting()` is true — it returns early, it does not return canned/mocked data. See [environment-detection.md](environment-detection.md). If a suite has slower live-integration tests, gate them behind the `--integration` flag (`UJ_TEST_INTEGRATION=1`) and run the real path; anything such a test creates externally MUST be cleaned up by the test (`cleanup`/`inspect` teardown) — the runner does not clean external systems.
|
|
26
|
+
|
|
5
27
|
## Quick start
|
|
6
28
|
|
|
7
29
|
```bash
|
|
@@ -135,7 +157,7 @@ The public surface exposed by `require('ultimate-jekyll-manager/build')` include
|
|
|
135
157
|
- `Manager.logger(name)` — returns a `Logger` instance
|
|
136
158
|
- `Manager.require(path)` — escape hatch when you really need a UJM transitive dep
|
|
137
159
|
|
|
138
|
-
See [docs/
|
|
160
|
+
See [docs/environment-detection.md](environment-detection.md) for `isTesting`/`isDevelopment` semantics.
|
|
139
161
|
|
|
140
162
|
## Reporter contract — `__UJM_TEST__` JSON-line events
|
|
141
163
|
|
|
@@ -162,12 +184,35 @@ Same protocol as EM (`__EM_TEST__`) and BXM (`__BXM_TEST__`). One marker per fra
|
|
|
162
184
|
- **Excluded**: any directory starting with `_` (handy for shared helpers).
|
|
163
185
|
- **Framework boot suites** are excluded when the cwd's `package.json#name` is not `ultimate-jekyll-manager` — they target UJM's fixture site, not the consumer's. Consumers write their own boot tests in `<cwd>/test/boot/`.
|
|
164
186
|
|
|
187
|
+
## `test/_init.js` — pre-test lifecycle hook
|
|
188
|
+
|
|
189
|
+
The runner loads an optional `test/_init.js` from **both** test roots — the framework (`<UJM>/test/_init.js`) and the consumer project (`<cwd>/test/_init.js`) — and runs it **once, before any suite** (it is NOT itself run as a test; the `_`-prefix keeps it out of discovery). Mirrors the same hook in BEM/EM/BXM so all four frameworks share one shape.
|
|
190
|
+
|
|
191
|
+
The module **must export a function** — `module.exports = (ctx) => ({ ... })` — called with `{ projectRoot }` and returning the hook object. It may declare:
|
|
192
|
+
|
|
193
|
+
- `async setup({ projectRoot })` — runs once before the suites, e.g. to scaffold a fixture file the boot layer needs.
|
|
194
|
+
|
|
195
|
+
There is **no `cleanup` hook** and **no `accounts` field** (unlike BEM — these frameworks have no auth/user system): tests clean up after themselves, so there is nothing project-level to tear down.
|
|
196
|
+
|
|
197
|
+
```javascript
|
|
198
|
+
// <cwd>/test/_init.js
|
|
199
|
+
const fs = require('fs');
|
|
200
|
+
const path = require('path');
|
|
201
|
+
|
|
202
|
+
module.exports = ({ projectRoot }) => ({
|
|
203
|
+
async setup() {
|
|
204
|
+
// Seed any fixture a suite needs before it runs.
|
|
205
|
+
fs.mkdirSync(path.join(projectRoot, '.temp'), { recursive: true });
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
165
210
|
## Env vars
|
|
166
211
|
|
|
167
212
|
| Env | Set by | Purpose |
|
|
168
213
|
|---|---|---|
|
|
169
214
|
| `UJ_TEST_MODE=true` | `npx mgr test` always | Canonical test signal. `Manager.isTesting()` reads this. Use it to short-circuit network calls / prompts / long timers in code that runs during tests. |
|
|
170
|
-
| `UJ_TEST_INTEGRATION=1` | `--integration` flag | Opt-in flag for slower integration tests if your suite has them |
|
|
215
|
+
| `UJ_TEST_INTEGRATION=1` | `--integration` flag | Opt-in flag for slower live-integration tests if your suite has them. These run the **real** external path (NOT mocked); anything they create externally MUST be cleaned up by the test. |
|
|
171
216
|
| `UJ_TEST_BOOT_PROJECT` | Auto-set when UJM tests itself; else manual | Project root the boot runner uses (its `_site/` is the boot target) |
|
|
172
217
|
| `UJ_TEST_BOOT_DIR` | Manual | Absolute override for the `_site/` directory. Wins over `UJ_TEST_BOOT_PROJECT/_site` and `<cwd>/_site` |
|
|
173
218
|
| `UJ_TEST_DEBUG=1` | Manual | Verbose Puppeteer console output piped to the parent stdout |
|
|
@@ -179,5 +224,5 @@ Puppeteer is a `devDependency` of UJM itself. Consumers don't get it unless they
|
|
|
179
224
|
## See also
|
|
180
225
|
|
|
181
226
|
- [test-boot-layer.md](test-boot-layer.md) — deep dive on boot layer (`_site/` discovery, HTTP server, fixture vs consumer)
|
|
182
|
-
- [
|
|
227
|
+
- [environment-detection.md](environment-detection.md) — `Manager.isTesting()` / `isDevelopment()` semantics
|
|
183
228
|
- [cli.md](cli.md) — CLI surface, env-var conventions
|
package/docs/themes.md
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
# Themes
|
|
2
|
+
|
|
3
|
+
How UJM's theme system works, and how to author a theme — either **inside UJM**
|
|
4
|
+
(shipped to every consumer) or **in a consumer project** (that project only).
|
|
5
|
+
|
|
6
|
+
A theme controls the **visual language** (colors, type, borders, shadows,
|
|
7
|
+
component styling) and optionally the **markup** of specific pages. It does NOT
|
|
8
|
+
need to re-implement the framework's shared behavior — that is injected for every
|
|
9
|
+
theme automatically (see [Shared vs per-theme](#shared-vs-per-theme)).
|
|
10
|
+
|
|
11
|
+
Shipped themes live in [src/assets/themes/](../src/assets/themes/):
|
|
12
|
+
|
|
13
|
+
- **`bootstrap/`** — the base layer: Bootstrap 5 SCSS/JS + universal `overrides/`
|
|
14
|
+
every theme inherits. Not selected directly.
|
|
15
|
+
- **`classy/`** — the default, full-featured frontend theme. Also the **layout
|
|
16
|
+
fallback source** (see below).
|
|
17
|
+
- **`neobrutalism/`** — bold high-contrast theme (hard borders, offset shadows,
|
|
18
|
+
zero radius). A worked example of a second theme.
|
|
19
|
+
- **`_template/`** — a copy-paste starter for new themes (the `_` prefix excludes
|
|
20
|
+
it from selection).
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## How a theme is selected and loaded
|
|
25
|
+
|
|
26
|
+
A consumer picks a theme with one field in `src/_config.yml`:
|
|
27
|
+
|
|
28
|
+
```yaml
|
|
29
|
+
theme:
|
|
30
|
+
id: "neobrutalism" # folder name under assets/themes/
|
|
31
|
+
appearance: "light" # light | dark | system → sets <html data-bs-theme>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Three resolution mechanisms turn that id into a built site:
|
|
35
|
+
|
|
36
|
+
### 1. SCSS (`__theme__` via loadPaths)
|
|
37
|
+
|
|
38
|
+
The consumer's `src/assets/css/main.scss` does `@use 'ultimate-jekyll-manager' as *;`.
|
|
39
|
+
That entry point ([src/assets/css/ultimate-jekyll-manager.scss](../src/assets/css/ultimate-jekyll-manager.scss))
|
|
40
|
+
does `@forward 'theme'`. The SASS task resolves the bare `theme` import via
|
|
41
|
+
`loadPaths`, in priority order
|
|
42
|
+
([src/gulp/tasks/sass.js](../src/gulp/tasks/sass.js)):
|
|
43
|
+
|
|
44
|
+
1. `<project>/src/assets/themes/<id>/` ← **project shadows package**
|
|
45
|
+
2. `<package>/dist/assets/themes/<id>/` ← UJM's built-in theme
|
|
46
|
+
3. `<package>/dist/assets/themes/` ← lets a theme `@import '../bootstrap/...'`
|
|
47
|
+
|
|
48
|
+
So `_theme.scss` is found in the project's theme dir first, else UJM's. You never
|
|
49
|
+
import a theme by hard path — let loadPaths resolve it.
|
|
50
|
+
|
|
51
|
+
### 2. JS (`__theme__` webpack alias)
|
|
52
|
+
|
|
53
|
+
`ultimate-jekyll-manager.js` does `import('__theme__/_theme.js')`. The webpack
|
|
54
|
+
alias ([src/gulp/tasks/webpack.js](../src/gulp/tasks/webpack.js)) resolves
|
|
55
|
+
`__theme__` to the project theme dir if it exists, else UJM's package theme dir —
|
|
56
|
+
same project-shadows-package rule as SCSS.
|
|
57
|
+
|
|
58
|
+
### 3. Layouts & includes (the classy fallback)
|
|
59
|
+
|
|
60
|
+
This is the key to **not duplicating ~40 page layouts**. Theme HTML lives under:
|
|
61
|
+
|
|
62
|
+
- `src/defaults/dist/_layouts/themes/<id>/...`
|
|
63
|
+
- `src/defaults/dist/_includes/themes/<id>/...`
|
|
64
|
+
|
|
65
|
+
During build, `copyFallbackThemeFiles()`
|
|
66
|
+
([src/gulp/tasks/distribute.js](../src/gulp/tasks/distribute.js)) copies every
|
|
67
|
+
layout/include from the **`classy`** theme that the selected theme hasn't defined,
|
|
68
|
+
rewriting `themes/classy/` → `themes/<id>/` in the content. Layouts also use the
|
|
69
|
+
`themes/[ site.theme.id ]/...` template variable, which distribute resolves to the
|
|
70
|
+
active theme.
|
|
71
|
+
|
|
72
|
+
**Result:** a new theme inherits all of classy's pages for free and overrides
|
|
73
|
+
**only** the files whose *markup* must differ. You saw this in the build log:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
Copied 48 fallback files from 'classy' to 'neobrutalism' theme
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
> Because classy is the fallback source, **keep classy's layouts theme-agnostic**
|
|
80
|
+
> (semantic Bootstrap classes, data-driven includes). Every other theme inherits
|
|
81
|
+
> them.
|
|
82
|
+
|
|
83
|
+
#### When to override a layout vs just restyle with CSS
|
|
84
|
+
|
|
85
|
+
Most of the time you do NOT override layouts — you restyle Bootstrap's classes
|
|
86
|
+
(see [Shared vs per-theme](#shared-vs-per-theme)) and the inherited classy markup
|
|
87
|
+
adopts your look. Override a layout only when the **structure itself** must differ.
|
|
88
|
+
|
|
89
|
+
The `neobrutalism` theme demonstrates both. It restyles classy's markup everywhere
|
|
90
|
+
EXCEPT the homepage and pricing page, where it ships genuinely different structure
|
|
91
|
+
(asymmetric split hero, offset showcase rows, oversized color-block stats) at
|
|
92
|
+
`_layouts/themes/neobrutalism/frontend/pages/{index,pricing}.html`. **Critically,
|
|
93
|
+
those overrides reuse the SAME `page.resolved.*` frontmatter data contract as
|
|
94
|
+
classy's versions** — same `hero`, `pricing`, `stats`, `cta` keys, same
|
|
95
|
+
price-resolution Liquid — so a consumer's existing page frontmatter keeps working;
|
|
96
|
+
only the HTML that renders the data changes. When you override a page layout, copy
|
|
97
|
+
classy's frontmatter block and preserve any data-resolution Liquid; restructure
|
|
98
|
+
only the markup below the front matter.
|
|
99
|
+
|
|
100
|
+
#### No theme-prefixed classes — use universal class names
|
|
101
|
+
|
|
102
|
+
**Markup must never use theme-prefixed classes** (no `nb-*`, `classy-*`, etc.). A
|
|
103
|
+
theme-prefixed class hardcodes the markup to one theme and breaks swappability. When a
|
|
104
|
+
theme writes its own layout, it uses:
|
|
105
|
+
|
|
106
|
+
1. **Standard Bootstrap classes** wherever one fits — every theme already styles these:
|
|
107
|
+
- `.card` (+ `.card-body`) — the canonical box for stat/step/plan/feature blocks
|
|
108
|
+
- `.btn` / `.btn-primary` / `.btn-warning` / `.btn-outline-*` — buttons (theme picks the semantic color; `.btn-warning` = the yellow accent in neobrutalism)
|
|
109
|
+
- `.text-bg-{primary,secondary,success,info,warning,danger}` — full color-block fills
|
|
110
|
+
- `.border`, `.shadow`, `.accordion`, `.badge`, grid/flex utilities
|
|
111
|
+
2. **Universal semantic layout classes** for structures with no Bootstrap equivalent —
|
|
112
|
+
shared *names*, each theme supplies its own *styling*:
|
|
113
|
+
|
|
114
|
+
| Class | Represents |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `.section-hero` / `.hero-title` / `.hero-actions` | a page hero block |
|
|
117
|
+
| `.action-block` (`--ink` / `--surface`) | large stacked call-to-action blocks |
|
|
118
|
+
| `.logo-strip` (`-box`, `-label`) | a "trusted by" logo marquee strip |
|
|
119
|
+
| `.showcase` / `.showcase-row` (`--flip`) / `.showcase-num` / `.showcase-body` | alternating feature showcase rows |
|
|
120
|
+
| `.steps` / `.step-card` (`-num`, `-icon`, `-title`, `-desc`) | numbered "how it works" steps |
|
|
121
|
+
| `.stats` / `.stats-grid` / `.stat-block` (`-num`, `-label`, `-sub`) | stat / social-proof cells |
|
|
122
|
+
| `.cta` / `.cta-panel` / `.cta-title` / `.cta-desc` / `.cta-actions` | closing CTA panel |
|
|
123
|
+
| `.pricing-hero` / `.pricing-title` / `.pricing-plans` / `.pricing-plan` (`--popular`) / `.pricing-plan-*` | pricing page structures |
|
|
124
|
+
| `.billing-toggle` / `.billing-option` / `.billing-save` | monthly/annual billing switch |
|
|
125
|
+
| `.enterprise-panel` (`-title`) / `.faq` | enterprise strip, FAQ section |
|
|
126
|
+
| `.section-head` / `.section-title` / `.kicker` (`--invert`) / `.highlight` / `.font-mono` | shared section heading + label/highlight helpers |
|
|
127
|
+
|
|
128
|
+
A new theme that overrides these pages styles **the same class names** its own way.
|
|
129
|
+
This is the contract that keeps custom layouts swappable.
|
|
130
|
+
|
|
131
|
+
> The `nb-`-style prefix survives ONLY on a theme's SCSS internals — its `$theme-*`
|
|
132
|
+
> config tokens, `--theme-*` CSS variables, and `@mixin` helpers. Those never appear in
|
|
133
|
+
> HTML, so they don't affect swappability. Keep prefixes out of markup, not out of SCSS.
|
|
134
|
+
|
|
135
|
+
### 4. Page-specific CSS (theme-aware, additive)
|
|
136
|
+
|
|
137
|
+
Every page links a per-path CSS bundle (`/assets/css/pages/<path>/index.bundle.css`,
|
|
138
|
+
resolved in [_includes/core/head.html](../src/defaults/dist/_includes/core/head.html)).
|
|
139
|
+
That bundle composes UJM's base page CSS (`assets/css/pages/<path>/index.scss`) and
|
|
140
|
+
the consumer's same-path file. Themes add a **third, additive layer**:
|
|
141
|
+
|
|
142
|
+
- Put theme page CSS at **`themes/<id>/pages/<path>/index.scss`**.
|
|
143
|
+
- The SASS task ([src/gulp/tasks/sass.js](../src/gulp/tasks/sass.js)) compiles it to a
|
|
144
|
+
separate bundle **`pages/<path>/index.<id>.bundle.css`**.
|
|
145
|
+
- `head.html` links that bundle **in addition to** the base bundle, loaded *after* it
|
|
146
|
+
(so theme page CSS can override base), gated by `{% iffile %}`.
|
|
147
|
+
|
|
148
|
+
**The fallback is the absence of a file** — and that's the whole elegance:
|
|
149
|
+
|
|
150
|
+
- If the theme has **no** page CSS for a path (e.g. signin, which almost no theme
|
|
151
|
+
customizes), the `<id>` bundle simply doesn't exist, `{% iffile %}` skips the link,
|
|
152
|
+
and the page is styled entirely by the theme's component/general CSS in the main
|
|
153
|
+
bundle. **No fallback mechanism needed** — missing = nothing extra loads.
|
|
154
|
+
- If the theme **does** ship page CSS (e.g. neobrutalism's `pages/index.scss` for its
|
|
155
|
+
custom homepage structure), it loads and layers on top.
|
|
156
|
+
|
|
157
|
+
This asymmetry vs. HTML layouts is deliberate: a page must always have *some* layout
|
|
158
|
+
(hence the classy copy-fallback), but page CSS is purely additive, so a missing file
|
|
159
|
+
is the correct no-op. Theme page CSS compiles standalone, so it pulls in the theme's
|
|
160
|
+
tokens + mixins via loadPaths (`@use 'config' as *;` + `@import 'css/base/mixins';`).
|
|
161
|
+
|
|
162
|
+
**Path shape must match the base bundle.** The theme file's path mirrors the base
|
|
163
|
+
page-CSS path for that page — which is NOT always `pages/<path>/index.scss`:
|
|
164
|
+
|
|
165
|
+
| Page | Base page CSS | Theme page CSS |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| Homepage (`/`) | `pages/index.scss` (flat) | `pages/index.scss` (flat) |
|
|
168
|
+
| Other (`/pricing`) | `pages/pricing/index.scss` (nested) | `pages/pricing/index.scss` (nested) |
|
|
169
|
+
|
|
170
|
+
The homepage is the one special case — its bundle is the flat `pages/index.bundle.css`,
|
|
171
|
+
so the theme file is the flat `themes/<id>/pages/index.scss`, NOT `pages/index/index.scss`.
|
|
172
|
+
If the shapes don't match, `{% iffile %}` looks for a bundle that was compiled under a
|
|
173
|
+
different name and silently skips the link. (The same flat-vs-nested rule applies to
|
|
174
|
+
theme page JS below.)
|
|
175
|
+
|
|
176
|
+
### 5. Page-specific JS (theme-aware, additive — mirrors page CSS)
|
|
177
|
+
|
|
178
|
+
Page JS works exactly like page CSS — three additive layers, same no-op-on-missing
|
|
179
|
+
rule. The frontend Manager ([src/index.js](../src/index.js)) dynamically imports a
|
|
180
|
+
page module from each layer and runs them **in order**:
|
|
181
|
+
|
|
182
|
+
1. `#main` — `__main_assets__/js/pages/<path>/index.js` (framework default)
|
|
183
|
+
2. `#theme` — `__theme__/pages/<path>/index.js` (active theme) ← the theme layer
|
|
184
|
+
3. `#project` — `__project_assets__/js/pages/<path>/index.js` (consumer)
|
|
185
|
+
|
|
186
|
+
- Put theme page JS at **`themes/<id>/pages/<path>/index.js`** (same path shape as the
|
|
187
|
+
CSS — flat `pages/index.js` for the homepage, nested otherwise).
|
|
188
|
+
- A module exports `default ({ manager, options }) => { ... }`. Missing at any layer is
|
|
189
|
+
a graceful no-op (logged as `module missing: #<layer>/…`, execution continues).
|
|
190
|
+
- Execution order is **main → theme → project**, matching the CSS cascade: framework
|
|
191
|
+
default first, theme second, consumer last (consumer always wins).
|
|
192
|
+
- The theme import uses a `/* webpackInclude: /\.js$/ */` magic comment so webpack's
|
|
193
|
+
dynamic-import context only scans `.js` — the theme's `pages/` dir also holds page
|
|
194
|
+
CSS (`.scss`), which must NOT be pulled into the JS context.
|
|
195
|
+
|
|
196
|
+
So a theme can ship a page's structure (layout override), its styling (theme page CSS),
|
|
197
|
+
AND its behavior (theme page JS) — all three keyed off the same `pages/<path>` path,
|
|
198
|
+
all three no-ops when absent.
|
|
199
|
+
|
|
200
|
+
The three layers, named by **source** (the `#main`/`#theme`/`#project` tags in the
|
|
201
|
+
console logs map to these), always load in this order:
|
|
202
|
+
|
|
203
|
+
1. **Global** — the framework's own page file (`#main`)
|
|
204
|
+
2. **Theme** — the active theme's page file (`#theme`)
|
|
205
|
+
3. **Consumer** — the consuming project's page file (`#project`)
|
|
206
|
+
|
|
207
|
+
Later layers win (Consumer overrides Theme overrides Global) — the same cascade for
|
|
208
|
+
both CSS and JS.
|
|
209
|
+
|
|
210
|
+
### Asset-layer test panel
|
|
211
|
+
|
|
212
|
+
The built-in **`/test/libraries/layers`** page renders a live status panel for exactly
|
|
213
|
+
this cascade: six dots (CSS ×3, JS ×3), one per layer. Each dot starts **red** and a
|
|
214
|
+
layer turns **its own** dot green when it loads (CSS via a selector, JS by setting the
|
|
215
|
+
dot color). A **red** dot means that layer has no file for this page — the normal state
|
|
216
|
+
for a layer nobody customized. The panel reflects what *actually* loads.
|
|
217
|
+
|
|
218
|
+
- **Global** and **Theme** dots are populated by files shipped in the framework:
|
|
219
|
+
`assets/{css,js}/pages/test/libraries/layers/index.*` (global) and the active theme's
|
|
220
|
+
`pages/test/libraries/layers/index.*` (theme). These are green out of the box.
|
|
221
|
+
- **Consumer** dots require a real consumer file at
|
|
222
|
+
`src/assets/{css,js}/pages/test/libraries/layers/index.*`. By default a project has
|
|
223
|
+
none, so they're **red** — the honest "this layer is available but unused" signal.
|
|
224
|
+
|
|
225
|
+
**Proving the Consumer layer without committing files (`UJ_TEST_LAYERS`).** To light the
|
|
226
|
+
Consumer dots green on demand, run the dev server with the flag — there's a ready-made
|
|
227
|
+
script:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
npm run start:test-layers # ≡ UJ_TEST_LAYERS=true npm start
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
When set, the `defaults` task ([utils/manage-test-layers.js](../src/gulp/tasks/utils/manage-test-layers.js))
|
|
234
|
+
generates a real consumer page file into the project's `src/` **at build start** (before
|
|
235
|
+
sass + jekyll), so it loads through the genuine `__project_assets__` / consumer-page-CSS
|
|
236
|
+
path — no shims. The generated CSS `@use`s the framework base so it *composes* (the same
|
|
237
|
+
contract every real consumer page file follows). The files carry a `GENERATED — UJ_TEST_LAYERS`
|
|
238
|
+
marker and are **auto-removed at the start of the next run** (and never on a normal run),
|
|
239
|
+
so they never persist or get committed. (`.temp`/`dist` are also cleaned by `npx mgr clean`.)
|
|
240
|
+
|
|
241
|
+
> **Build-order note (dev only).** Theme page bundles are produced by the SASS task,
|
|
242
|
+
> then Jekyll's `{% iffile %}` checks for them in its source tree (`dist/`). On the
|
|
243
|
+
> *very first* dev render, Jekyll may render a page before its theme bundle has been
|
|
244
|
+
> written, so the link is missing until that page is re-rendered (touch the page source
|
|
245
|
+
> or save it again). A production `npm run build` runs sass before jekyll, so there's no
|
|
246
|
+
> race. If a theme page's CSS/JS isn't applying in dev, re-trigger that page's render.
|
|
247
|
+
|
|
248
|
+
> Editing `sass.js` / `index.js` (or any gulp/build task)? The consumer's running
|
|
249
|
+
> `gulp serve` loaded the task at startup — **fully restart the consumer's `npm start`**
|
|
250
|
+
> for task-code changes to take effect (webpack picks up `src/index.js` on its own watch,
|
|
251
|
+
> but gulp task definitions like the sass globs only reload on a fresh process).
|
|
252
|
+
> Layout/SCSS/`head.html` *content* changes are picked up live.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Shared vs per-theme
|
|
257
|
+
|
|
258
|
+
Get this distinction right or you'll either duplicate plumbing or fight overrides.
|
|
259
|
+
|
|
260
|
+
### Shared — do NOT re-implement in a theme
|
|
261
|
+
|
|
262
|
+
| Layer | Where | What |
|
|
263
|
+
|---|---|---|
|
|
264
|
+
| Core behavior CSS | [src/assets/css/core/](../src/assets/css/core/) | animations, alerts, lazy-loading shimmer, cookie consent, bindings skeletons, social sharing. Injected for **every** theme by `ultimate-jekyll-manager.scss`. |
|
|
265
|
+
| Bootstrap extensions | [src/assets/themes/bootstrap/overrides/](../src/assets/themes/bootstrap/overrides/) | avatars, color-shades, soft-colors, adaptive buttons, spacing, link/typography utilities. Each theme pulls these in via `@import '../bootstrap/overrides'` at the end of its `_theme.scss`. |
|
|
266
|
+
| Page layouts/includes | classy theme + fallback copy | the ~40 frontend/backend/admin layouts and nav/footer/account includes. |
|
|
267
|
+
| Bootstrap class contract | `bootstrap/scss` | the markup + class names (`.btn`, `.card`, `.navbar`, `.form-control`). Themes **restyle** these classes; they don't invent new markup. |
|
|
268
|
+
|
|
269
|
+
### Per-theme — this IS the theme's job (and SHOULD differ between themes)
|
|
270
|
+
|
|
271
|
+
- `_config.scss` — design tokens (`!default`), Bootstrap forward.
|
|
272
|
+
- `_root.scss` — SCSS → CSS-variable bridge for light/dark.
|
|
273
|
+
- Component SCSS — how `.btn`/`.card`/`.form-control`/`.navbar` actually look.
|
|
274
|
+
- `_theme.js` — expose Bootstrap + run theme behaviors on DOM ready.
|
|
275
|
+
- *(optional)* Page-layout overrides under `_layouts/themes/<id>/...` — for pages
|
|
276
|
+
whose structure must differ (reuse classy's frontmatter data contract).
|
|
277
|
+
- *(optional)* Theme page CSS at `pages/<path>/index.scss` — additive per-page
|
|
278
|
+
styles for those overridden layouts.
|
|
279
|
+
- *(optional)* Theme page JS at `pages/<path>/index.js` — additive per-page behavior
|
|
280
|
+
(the `#theme` layer between `#main` and `#project`).
|
|
281
|
+
|
|
282
|
+
A neobrutalist button and a classy button share only the `.btn` class — restyling
|
|
283
|
+
it is the theme's whole purpose. **Do not try to "share" component styling between
|
|
284
|
+
themes**; that produces an override-fighting mess. Share *plumbing*, not *looks*.
|
|
285
|
+
|
|
286
|
+
> Themes share the **class-name contract** (standard Bootstrap classes + the universal
|
|
287
|
+
> semantic layout classes — see [No theme-prefixed classes](#no-theme-prefixed-classes--use-universal-class-names)),
|
|
288
|
+
> not the styling. Same names, different looks → markup stays swappable.
|
|
289
|
+
|
|
290
|
+
### Structural vs visual components (gotcha)
|
|
291
|
+
|
|
292
|
+
A few "components" in classy are **structural behavior**, not just styling — e.g.
|
|
293
|
+
`_infinite-scroll.scss` (the trusted-by logo marquee + testimonial scroll). The
|
|
294
|
+
shared layouts emit `.infinite-scroll-track` markup, so any theme using those
|
|
295
|
+
layouts needs the matching CSS or the content renders broken (giant unstyled
|
|
296
|
+
logos). Until these are promoted to the shared layer, **port structural components
|
|
297
|
+
you actually use** into your theme (neobrutalism ships its own
|
|
298
|
+
`css/components/_infinite-scroll.scss` — mostly the same flex/marquee rules with
|
|
299
|
+
theme-tuned cosmetics). If a page using shared markup looks broken, check whether
|
|
300
|
+
a structural component is missing.
|
|
301
|
+
|
|
302
|
+
### Adaptive buttons need an explicit theme override (gotcha)
|
|
303
|
+
|
|
304
|
+
The shared `bootstrap/overrides/_buttons-adaptive.scss` defines `.btn-adaptive` /
|
|
305
|
+
`.btn-adaptive-inverse` (mode-flipping solids used by the nav CTA, auth buttons,
|
|
306
|
+
redirect, etc.) **only at single-class specificity** (`.btn-adaptive`, `0,1,0`) and
|
|
307
|
+
via the `--bs-btn-*` custom properties. A theme that restyles `.btn` will hit two
|
|
308
|
+
problems with these classes if it ignores them:
|
|
309
|
+
|
|
310
|
+
1. **Transparent resting fill.** Bootstrap's base `.btn { --bs-btn-bg: transparent }`
|
|
311
|
+
can tie/beat the `.btn-adaptive` rule on source order, so the button renders
|
|
312
|
+
*transparent* instead of solid. Fix: restate the resting fill at **doubled
|
|
313
|
+
specificity** (`.btn.btn-adaptive`, `0,2,0`) in the theme's `_buttons.scss`, with a
|
|
314
|
+
`[data-bs-theme="dark"]` pair for the mode flip.
|
|
315
|
+
2. **Hover color flash.** The adaptive defaults darken on hover (`--bs-btn-hover-bg:
|
|
316
|
+
var(--bs-dark-hover)`), so an adaptive button animates differently from the theme's
|
|
317
|
+
other solids. Fix: add `.btn-adaptive` / `.btn-adaptive-inverse` to the same
|
|
318
|
+
hover/active **freeze** list the theme uses for `.btn-primary` etc.
|
|
319
|
+
(`--bs-btn-hover-bg: var(--bs-btn-bg)`), so the press is pure transform+shadow.
|
|
320
|
+
|
|
321
|
+
See `themes/neobrutalism/css/components/_buttons.scss` for the reference treatment.
|
|
322
|
+
The `[class*="btn-outline-"]` selector already catches the *outline* adaptive
|
|
323
|
+
variants, so only the two **solid** adaptive classes need this.
|
|
324
|
+
|
|
325
|
+
### One token for interactive hovers — and `$primary` ≠ `--bs-primary` (gotcha)
|
|
326
|
+
|
|
327
|
+
Give every interactive hover/active fill (nav links, footer links, dropdown items,
|
|
328
|
+
in-content links, mobile toggler) a **single** token so they retune from one line and
|
|
329
|
+
never drift. Neobrutalism defines `--nb-accent-interactive` (blue) in `_root.scss`,
|
|
330
|
+
distinct from the yellow signature accent (`--nb-accent-yellow`) reserved for static
|
|
331
|
+
blocks (kicker tags, highlight marker, hero, badges). Every interactive `:hover`/
|
|
332
|
+
`.active` reads `var(--nb-accent-interactive)`; nothing hardcodes the color.
|
|
333
|
+
|
|
334
|
+
**The trap:** point that token at the runtime **`var(--bs-primary)`**, NOT the SCSS
|
|
335
|
+
`$primary` token. A consumer can set them to *different* values (UJM consumers
|
|
336
|
+
frequently do — the SCSS `$primary` and the rendered `--bs-primary` diverged, so a
|
|
337
|
+
rule written as `background: $primary` compiled a purple that didn't match the blue
|
|
338
|
+
`.btn-primary` buttons on the page). Any rule that should match a rendered Bootstrap
|
|
339
|
+
color must use the **CSS variable** (`var(--bs-primary)`), because SCSS values are
|
|
340
|
+
frozen at compile time while the `--bs-*` vars reflect the live theme.
|
|
341
|
+
|
|
342
|
+
One more: a generic `a:hover { color: … }` also paints **button** labels (buttons are
|
|
343
|
+
`<a>`). Guard it with `a.btn:hover { color: var(--bs-btn-color); }` so buttons keep
|
|
344
|
+
their own (frozen) text color. Nav/dropdown/footer links already win on specificity.
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## Authoring conventions (both paths)
|
|
349
|
+
|
|
350
|
+
1. **Every token is `!default`** so consumers can override without forking.
|
|
351
|
+
2. **Bridge to CSS variables** in `_root.scss`; components read `var(--*)`, not raw
|
|
352
|
+
SCSS. Dark mode then becomes one `[data-bs-theme="dark"]` override block.
|
|
353
|
+
3. **Restyle Bootstrap's own classes** so inherited markup adopts your look with no
|
|
354
|
+
HTML edits. Add your own classes only for net-new patterns.
|
|
355
|
+
4. **Namespace your own classes** (`.nb-*`, `.recipe-*`) to avoid collisions.
|
|
356
|
+
5. **Match classy's `$avatar-sizes` map** in `_config.scss` — the shared includes
|
|
357
|
+
(nav/account) reference it.
|
|
358
|
+
6. **Fonts** load via the base layout's `theme.head.content`, NOT a CSS `@import`
|
|
359
|
+
(avoids render-blocking duplicate loads). To use custom fonts, override
|
|
360
|
+
`frontend/core/base.html` (see below).
|
|
361
|
+
7. **Validate live, then document.** UJM can't run a dev server — build in a
|
|
362
|
+
consumer and screenshot (see [Validating](#validating-a-theme)).
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Path A — author a theme INSIDE UJM (shipped to all consumers)
|
|
367
|
+
|
|
368
|
+
Use this for first-party themes like `neobrutalism`.
|
|
369
|
+
|
|
370
|
+
1. **Copy the template** to your theme id:
|
|
371
|
+
```
|
|
372
|
+
src/assets/themes/_template/ → src/assets/themes/my-theme/
|
|
373
|
+
```
|
|
374
|
+
2. **Edit the SCSS:**
|
|
375
|
+
- `_config.scss` — your tokens + the `@forward '../bootstrap/scss/bootstrap.scss' with (...)` block.
|
|
376
|
+
- `_root.scss` — CSS-variable bridge (light + dark).
|
|
377
|
+
- add `css/base/`, `css/layout/`, `css/components/` partials and `@import` them
|
|
378
|
+
in `_theme.scss`. End `_theme.scss` with `@import '../bootstrap/overrides';`.
|
|
379
|
+
- If you use shared mixins across partials, `@import` a `_mixins.scss` first so
|
|
380
|
+
they're in the global scope the other `@import`s share (neobrutalism does this).
|
|
381
|
+
3. **Edit `_theme.js`** — `import bootstrap from '__main_assets__/themes/bootstrap/js/index.umd.js'`,
|
|
382
|
+
`window.bootstrap = bootstrap`, run behaviors inside `domReady()`. Keep multiple
|
|
383
|
+
behaviors in small `js/` files.
|
|
384
|
+
4. **Override only the HTML that must differ** under
|
|
385
|
+
`src/defaults/dist/_layouts/themes/my-theme/...` (and `_includes/...`). The
|
|
386
|
+
classy fallback supplies the rest. The most common override is
|
|
387
|
+
`frontend/core/base.html` to load your fonts (neobrutalism overrides just this
|
|
388
|
+
one file).
|
|
389
|
+
5. **Add a `README.md`** in the theme folder (customization quickstart).
|
|
390
|
+
6. `npm run prepare` (copies `src/`→`dist/`) so consumers see it. Then test in a
|
|
391
|
+
consumer (Path: [Validating](#validating-a-theme)).
|
|
392
|
+
|
|
393
|
+
## Path B — author a theme IN A CONSUMER PROJECT (that project only)
|
|
394
|
+
|
|
395
|
+
Use this when one site needs a bespoke look that shouldn't ship in UJM (e.g. Sweet
|
|
396
|
+
Saucy's `recipe` theme).
|
|
397
|
+
|
|
398
|
+
1. **Copy the template** from UJM into your project:
|
|
399
|
+
```
|
|
400
|
+
node_modules/ultimate-jekyll-manager/dist/assets/themes/_template/
|
|
401
|
+
→ <project>/src/assets/themes/my-theme/
|
|
402
|
+
```
|
|
403
|
+
2. **Select it** in `src/_config.yml`: `theme: { id: "my-theme" }`.
|
|
404
|
+
3. **Edit the SCSS/JS** exactly as Path A. The `../bootstrap/...` imports still
|
|
405
|
+
resolve — UJM's loadPaths include the package themes root.
|
|
406
|
+
4. **Override HTML** (only if needed) under
|
|
407
|
+
`<project>/src/_layouts/themes/my-theme/...` and `src/_includes/themes/my-theme/...`.
|
|
408
|
+
The fallback still copies classy's defaults into your theme id at build time.
|
|
409
|
+
5. **PurgeCSS safelist:** if you add custom classes used only in JS-injected DOM,
|
|
410
|
+
add them to `config/ultimate-jekyll-manager.json` → `sass.purgecss.safelist` so
|
|
411
|
+
production builds don't strip them.
|
|
412
|
+
6. **`npm start`** and verify.
|
|
413
|
+
|
|
414
|
+
> To merely *recolor* an existing theme (not build a new one), don't create a
|
|
415
|
+
> theme — override its `!default` tokens in `main.scss` before
|
|
416
|
+
> `@use 'ultimate-jekyll-manager'`. See classy's README. To change a theme's
|
|
417
|
+
> component styles or a layout, **shadow it**: create
|
|
418
|
+
> `src/assets/themes/<id>/` (SCSS) or `src/_layouts/themes/<id>/<file>` (HTML) in
|
|
419
|
+
> your project — loadPaths/fallback resolve your copy first.
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## Validating a theme
|
|
424
|
+
|
|
425
|
+
UJM cannot run a dev server itself (it runs inside a consumer). To verify a theme:
|
|
426
|
+
|
|
427
|
+
1. In UJM: `npm run prepare` (or `npm start` for watch) to publish `src/`→`dist/`.
|
|
428
|
+
2. In a consumer wired to local UJM (`"ultimate-jekyll-manager": "file:../ultimate-jekyll-manager"`),
|
|
429
|
+
set `theme.id` and run `npm start`. Capture the BrowserSync URL (e.g.
|
|
430
|
+
`https://localhost:4000`).
|
|
431
|
+
3. Screenshot the key pages (home, pricing, signin, signup) in **both** light and
|
|
432
|
+
dark (`document.documentElement.setAttribute('data-bs-theme','dark')`) — e.g.
|
|
433
|
+
via the chrome-devtools MCP. Check the console for errors and that your theme's
|
|
434
|
+
"loaded" log appears.
|
|
435
|
+
4. Iterate on SCSS — the consumer's gulp watcher recompiles when UJM's `dist/`
|
|
436
|
+
changes (if UJM is running `npm start`), or re-run `npm run prepare`.
|
|
437
|
+
|
|
438
|
+
**Do not declare a theme done without looking at rendered screenshots.** Verified
|
|
439
|
+
example: `neobrutalism` built into `ultimate-jekyll-website`, screenshotted across
|
|
440
|
+
all four pages in light + dark.
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## Reference
|
|
445
|
+
|
|
446
|
+
- Theme tokens example: [src/assets/themes/neobrutalism/_config.scss](../src/assets/themes/neobrutalism/_config.scss)
|
|
447
|
+
- CSS-variable bridge example: [src/assets/themes/neobrutalism/css/base/_root.scss](../src/assets/themes/neobrutalism/css/base/_root.scss)
|
|
448
|
+
- Starter: [src/assets/themes/_template/](../src/assets/themes/_template/)
|
|
449
|
+
- Fallback mechanism: [src/gulp/tasks/distribute.js](../src/gulp/tasks/distribute.js) (`copyFallbackThemeFiles`)
|
|
450
|
+
- Resolution: [src/gulp/tasks/sass.js](../src/gulp/tasks/sass.js), [src/gulp/tasks/webpack.js](../src/gulp/tasks/webpack.js)
|
|
451
|
+
- Related: [docs/assets.md](assets.md) (file layout), [docs/css.md](css.md) (section/theme-adaptive classes), [docs/appearance.md](appearance.md) (dark/light switching)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultimate-jekyll-manager",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Ultimate Jekyll dependency manager",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"projectScripts": {
|
|
33
33
|
"start": "npx mgr clean && npx mgr setup && bundle exec npm run gulp --",
|
|
34
|
+
"start:test-layers": "UJ_TEST_LAYERS=true npm start",
|
|
34
35
|
"gulp": "gulp --cwd ./ --gulpfile ./node_modules/ultimate-jekyll-manager/dist/gulp/main.js",
|
|
35
36
|
"build": "npx mgr clean && npx mgr setup && UJ_BUILD_MODE=true bundle exec npm run gulp -- build",
|
|
36
37
|
"deploy": "npx mgr deploy",
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# Cross-context Helpers
|
|
2
|
-
|
|
3
|
-
`src/utils/mode-helpers.js` exposes four shared helpers mixed into every UJM Manager via `attachTo(Manager)`. Mirrors the same pattern in EM and BXM. Used when behavior should differ by *what kind of process* you're in — short-circuit network probes in tests, suppress dev-only banners in production, etc.
|
|
4
|
-
|
|
5
|
-
## API
|
|
6
|
-
|
|
7
|
-
| Method | Returns | Purpose |
|
|
8
|
-
|---|---|---|
|
|
9
|
-
| `Manager.isTesting()` | `boolean` | True when UJM's test framework is running this process. Set by `npx mgr test` (`UJ_TEST_MODE=true`) and consumer test setups that want the same signal. |
|
|
10
|
-
| `Manager.isDevelopment()` | `boolean` | True when running in dev mode (not a production build). Reads `UJ_BUILD_MODE` / `NODE_ENV` / `UJ_IS_SERVER` / `window.Configuration.uj.environment` depending on context. |
|
|
11
|
-
| `Manager.isProduction()` | `boolean` | Inverse of `isDevelopment()`. |
|
|
12
|
-
| `Manager.getVersion()` | `string \| null` | UJM's version from `<cwd>/package.json#version`. Null if no `package.json` (e.g. shipped browser bundle). |
|
|
13
|
-
|
|
14
|
-
All four are available both **statically** on the Manager constructor and on **`Manager.prototype`**, so these all work:
|
|
15
|
-
|
|
16
|
-
```js
|
|
17
|
-
const Manager = require('ultimate-jekyll-manager/build');
|
|
18
|
-
Manager.isTesting(); // static
|
|
19
|
-
new Manager().isTesting(); // instance
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## When to use
|
|
23
|
-
|
|
24
|
-
```js
|
|
25
|
-
// In a build helper that fetches Firebase auth files:
|
|
26
|
-
async function fetchFirebaseAuthFiles() {
|
|
27
|
-
if (Manager.isTesting()) {
|
|
28
|
-
return; // short-circuit — tests provide their own stubs
|
|
29
|
-
}
|
|
30
|
-
// ...real fetch
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// In a gulp task that opens the dev browser:
|
|
34
|
-
function maybeOpenBrowser() {
|
|
35
|
-
if (Manager.isBuildMode() || Manager.isTesting()) return;
|
|
36
|
-
// ...exec `open` etc.
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// In a frontend module that logs verbose debug info:
|
|
40
|
-
if (manager.isDevelopment()) {
|
|
41
|
-
console.log('[dev] webManager loaded with', cfg);
|
|
42
|
-
}
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
**Don't use these for "should I hit dev or prod backends"** — that's a config concern. Use `Manager.getEnvironment()` (returns `'development'` or `'production'` strings) for that distinction.
|
|
46
|
-
|
|
47
|
-
## How `isDevelopment` is detected
|
|
48
|
-
|
|
49
|
-
Order of checks:
|
|
50
|
-
|
|
51
|
-
1. **Build-time Node**: `process.env.UJ_BUILD_MODE === 'true'` → production. `process.env.NODE_ENV === 'development'` → development. `process.env.UJ_IS_SERVER === 'true'` → production.
|
|
52
|
-
2. **Browser**: `window.Configuration.uj.environment` if present (`'development'` or `'production'`).
|
|
53
|
-
3. Default → `false`.
|
|
54
|
-
|
|
55
|
-
## How `isTesting` is detected
|
|
56
|
-
|
|
57
|
-
Two checks (either is sufficient):
|
|
58
|
-
|
|
59
|
-
1. **Node**: `process.env.UJ_TEST_MODE === 'true'`. Set automatically by `npx mgr test`.
|
|
60
|
-
2. **Browser**: `globalThis.UJ_TEST_MODE === true`. Set automatically by UJM's `page`-layer harness HTML before any consumer code runs.
|
|
61
|
-
|
|
62
|
-
This means consumer code that calls `Manager.isTesting()` from a tab context gets the right answer — Node-only check would always return false in a browser.
|
|
63
|
-
|
|
64
|
-
## Adding new helpers
|
|
65
|
-
|
|
66
|
-
If you find yourself writing the same `if (process.env.UJ_FOO === 'true') ...` check more than twice, factor it into `mode-helpers.js`. Things to consider:
|
|
67
|
-
|
|
68
|
-
- Does the helper need to work in **all** contexts (Node build-time + browser page + service worker)? If yes, gate every check with `typeof process !== 'undefined'` / `typeof window !== 'undefined'` so the same code runs everywhere.
|
|
69
|
-
- Should it be mixed into static AND prototype? Almost always yes — the static form is for build-time CLI usage, the instance form for runtime use.
|
|
70
|
-
- Add a test in `src/test/suites/build/mode-helpers.test.js` that toggles the underlying env var and asserts both states.
|
|
71
|
-
|
|
72
|
-
## See also
|
|
73
|
-
|
|
74
|
-
- [test-framework.md](test-framework.md) — the test harness that sets `UJ_TEST_MODE`
|
|
75
|
-
- [cli.md](cli.md) — full env-var matrix
|