ultimate-jekyll-manager 1.7.2 → 1.8.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/.claude/scheduled_tasks.lock +1 -0
- package/CHANGELOG.md +61 -1
- package/CLAUDE.md +36 -15
- package/README.md +4 -2
- package/TODO-AUTH-TESTING.md +1 -1
- package/dist/assets/themes/newsflash/README.md +58 -0
- package/dist/assets/themes/newsflash/_config.scss +138 -0
- package/dist/assets/themes/newsflash/_theme.js +27 -0
- package/dist/assets/themes/newsflash/_theme.scss +37 -0
- package/dist/assets/themes/newsflash/css/base/_mixins.scss +50 -0
- package/dist/assets/themes/newsflash/css/base/_root.scss +134 -0
- package/dist/assets/themes/newsflash/css/base/_typography.scss +49 -0
- package/dist/assets/themes/newsflash/css/base/_utilities.scss +58 -0
- package/dist/assets/themes/newsflash/css/components/_badges.scss +65 -0
- package/dist/assets/themes/newsflash/css/components/_buttons.scss +139 -0
- package/dist/assets/themes/newsflash/css/components/_cards.scss +52 -0
- package/dist/assets/themes/newsflash/css/components/_editorial.scss +182 -0
- package/dist/assets/themes/newsflash/css/components/_forms.scss +75 -0
- package/dist/assets/themes/newsflash/css/components/_infinite-scroll.scss +102 -0
- package/dist/assets/themes/newsflash/css/components/_panels.scss +91 -0
- package/dist/assets/themes/newsflash/css/components/_ticker.scss +70 -0
- package/dist/assets/themes/newsflash/css/layout/_general.scss +264 -0
- package/dist/assets/themes/newsflash/css/layout/_navigation.scss +164 -0
- package/dist/assets/themes/newsflash/js/initialize-tooltips.js +20 -0
- package/dist/assets/themes/newsflash/js/masthead-scroll.js +29 -0
- package/dist/assets/themes/newsflash/pages/404/index.scss +27 -0
- package/dist/assets/themes/newsflash/pages/about/index.scss +70 -0
- package/dist/assets/themes/newsflash/pages/blog/index.scss +17 -0
- package/dist/assets/themes/newsflash/pages/blog/post.js +29 -0
- package/dist/assets/themes/newsflash/pages/blog/post.scss +164 -0
- package/dist/assets/themes/newsflash/pages/index.scss +159 -0
- package/dist/assets/themes/newsflash/pages/pricing/index.scss +194 -0
- package/dist/assets/themes/newsflash/pages/test/libraries/layers/index.js +9 -0
- package/dist/assets/themes/newsflash/pages/test/libraries/layers/index.scss +7 -0
- package/dist/commands/blogify.js +6 -3
- package/dist/commands/test.js +34 -5
- package/dist/defaults/CLAUDE.md +17 -4
- package/dist/defaults/dist/_includes/core/pricing/resolve-plan.html +59 -0
- package/dist/defaults/dist/_includes/themes/classy/frontend/sections/footer.html +20 -3
- package/dist/defaults/dist/_layouts/themes/classy/admin/core/minimal-viewport-locked.html +1 -1
- package/dist/defaults/dist/_layouts/themes/classy/admin/core/minimal.html +1 -1
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/pricing.html +5 -40
- package/dist/defaults/dist/_layouts/themes/neobrutalism/frontend/pages/pricing.html +33 -34
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/core/base.html +61 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/404.html +86 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/about.html +353 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/categories/category.html +105 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/categories/index.html +93 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/index.html +373 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/post.html +289 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/tags/index.html +90 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/tags/tag.html +107 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/contact.html +340 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/index.html +522 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/pricing.html +485 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/team/index.html +207 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/team/member.html +134 -0
- package/dist/defaults/test/README.md +4 -0
- package/dist/gulp/tasks/jekyll.js +4 -2
- package/dist/test/runner.js +50 -3
- package/dist/test/suites/build/attach-log-file.test.js +102 -0
- package/dist/test/suites/build/theme-contract.test.js +173 -0
- package/dist/test/utils/extended-mode-warning.js +13 -0
- package/dist/utils/attach-log-file.js +70 -43
- package/docs/appearance.md +1 -0
- package/docs/assets.md +9 -0
- package/docs/audit.md +27 -7
- package/docs/build-system.md +57 -0
- package/docs/common-mistakes.md +15 -0
- package/docs/{project-structure.md → directory-structure.md} +1 -1
- package/docs/environment-detection.md +1 -1
- package/docs/javascript-libraries.md +38 -1
- package/docs/layouts-and-pages.md +146 -0
- package/docs/local-development.md +1 -8
- package/docs/logging.md +30 -0
- package/docs/migration.md +131 -0
- package/docs/no-inline-scripts.md +304 -0
- package/docs/purgecss.md +164 -0
- package/docs/seo.md +131 -4
- package/docs/templating.md +23 -0
- package/docs/test-boot-layer.md +1 -1
- package/docs/test-framework.md +56 -8
- package/docs/themes.md +254 -13
- package/logs/test.log +111 -0
- package/package.json +1 -1
package/docs/purgecss.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# PurgeCSS Safelist
|
|
2
|
+
|
|
3
|
+
PurgeCSS removes unused CSS in production builds. Classes added dynamically via JavaScript (e.g., `classList.add()`, `innerHTML` templates) won't be found in static HTML, so they get purged.
|
|
4
|
+
|
|
5
|
+
## IMPORTANT: Update Safelist When Writing Dynamic JS
|
|
6
|
+
|
|
7
|
+
**When writing JavaScript that dynamically adds CSS classes** (via `classList.add()`, `classList.toggle()`, `className =`, or `innerHTML` with class attributes), you MUST check if the class is already safelisted. If not, add it to the appropriate safelist:
|
|
8
|
+
|
|
9
|
+
- **UJM-internal classes** → `src/gulp/tasks/sass.js` safelist
|
|
10
|
+
- **Consuming project classes** → `config/ultimate-jekyll-manager.json` → `sass.purgecss.safelist`
|
|
11
|
+
|
|
12
|
+
**NEVER edit `dist/` or `node_modules/`.** Those are build outputs. Edit `src/` only.
|
|
13
|
+
|
|
14
|
+
## Two Safelist Locations
|
|
15
|
+
|
|
16
|
+
### 1. UJM Internal Safelist (sass.js)
|
|
17
|
+
|
|
18
|
+
**File:** `src/gulp/tasks/sass.js`
|
|
19
|
+
|
|
20
|
+
This is where UJM's own safelist lives — patterns for Bootstrap utilities, UJM core classes, and third-party injected elements. Uses native RegExp objects.
|
|
21
|
+
|
|
22
|
+
**Changes to sass.js require a dev server restart** — gulp tasks are loaded once at startup.
|
|
23
|
+
|
|
24
|
+
### 2. Consuming Project Safelist (JSON config)
|
|
25
|
+
|
|
26
|
+
**File:** `config/ultimate-jekyll-manager.json` → `sass.purgecss.safelist`
|
|
27
|
+
|
|
28
|
+
For consuming project-specific classes. Uses regex strings (converted to `new RegExp(s)` by UJM).
|
|
29
|
+
|
|
30
|
+
```json5
|
|
31
|
+
{
|
|
32
|
+
sass: {
|
|
33
|
+
purgecss: {
|
|
34
|
+
safelist: {
|
|
35
|
+
standard: [], // Class names (converted to RegExp)
|
|
36
|
+
deep: [], // Pseudo-selector patterns
|
|
37
|
+
greedy: [], // Broader patterns (matches substrings)
|
|
38
|
+
keyframes: [], // @keyframes animation names
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## PurgeCSS Gotchas
|
|
46
|
+
|
|
47
|
+
### `!` Negation patterns DON'T work in the content array
|
|
48
|
+
|
|
49
|
+
PurgeCSS passes each content pattern to `fast-glob` **individually** (not as a batch), so `!path/**` returns 0 files instead of excluding anything. Use `skippedContentGlobs` for exclusions instead.
|
|
50
|
+
|
|
51
|
+
### Option naming is counterintuitive
|
|
52
|
+
|
|
53
|
+
`true` = "yes, **purge** this category", `false` = "leave it alone":
|
|
54
|
+
|
|
55
|
+
| Option | `true` | `false` |
|
|
56
|
+
|--------|--------|---------|
|
|
57
|
+
| `variables` | Remove unused CSS variables | Don't touch variables |
|
|
58
|
+
| `keyframes` | Remove unused @keyframes | Don't touch keyframes |
|
|
59
|
+
| `fontFace` | Remove unused @font-face | Don't touch font-face |
|
|
60
|
+
|
|
61
|
+
### `variables` must stay `false`
|
|
62
|
+
|
|
63
|
+
PurgeCSS processes each CSS file independently. Page-specific CSS sets CSS custom properties (e.g., `--bs-btn-bg` in `.btn-paypal`) that are consumed by the base `.btn` class in the main bundle. With `variables: true`, these cross-file variable references are falsely flagged as unused and removed.
|
|
64
|
+
|
|
65
|
+
### PurgeCSS processes per-file, not per-bundle
|
|
66
|
+
|
|
67
|
+
Each CSS file is processed against the content files independently. A class defined in a page CSS file must be found in the content files on its own — it can't rely on being "seen" in the context of the main bundle.
|
|
68
|
+
|
|
69
|
+
## All Entries Are RegExp
|
|
70
|
+
|
|
71
|
+
Every entry in the consuming project safelist arrays is wrapped in `new RegExp(s)`. This means:
|
|
72
|
+
|
|
73
|
+
- `'dot'` becomes `/dot/` — matches ANY class containing "dot" (e.g., `dotted`, `polkadot`)
|
|
74
|
+
- **Always anchor exact class names** with `^...$`
|
|
75
|
+
|
|
76
|
+
### Correct Usage
|
|
77
|
+
|
|
78
|
+
```json5
|
|
79
|
+
{
|
|
80
|
+
standard: ['^fw-semibold$'], // Top-level exact matches
|
|
81
|
+
deep: ['^dot$'], // Nested selectors (.chat-typing .dot)
|
|
82
|
+
greedy: ['^chat-'], // Prefix match (all chat-* classes)
|
|
83
|
+
keyframes: ['chat-typing-bounce'], // Specific enough, no anchor needed
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Current UJM Safelist (sass.js)
|
|
88
|
+
|
|
89
|
+
Before adding classes, check what UJM already safelists in `src/gulp/tasks/sass.js`:
|
|
90
|
+
|
|
91
|
+
### Bootstrap Utilities
|
|
92
|
+
- `/^btn-/` — button variants
|
|
93
|
+
- `/^d-/` — display utilities
|
|
94
|
+
- `/^text-/` — text utilities
|
|
95
|
+
- `/^bg-/` — background utilities
|
|
96
|
+
- `/^flex-/` — flex utilities
|
|
97
|
+
- `/^justify-/`, `/^align-/` — alignment
|
|
98
|
+
- `/^position-/` — positioning
|
|
99
|
+
- `/^[mp][trblxy]?-[0-9]+$/` — margin/padding
|
|
100
|
+
- `/^w-/`, `/^h-/`, `/^mw-/`, `/^mh-/`, `/^min-/`, `/^max-/` — sizing
|
|
101
|
+
- `/^border-/`, `/^rounded-/`, `/^shadow-/` — borders/shadows
|
|
102
|
+
- `/^overflow-/`, `/^order-/` — overflow/order
|
|
103
|
+
- `/^fw-/` — font weight
|
|
104
|
+
- `/^ratio-/`, `/^object-/` — aspect ratio/object-fit
|
|
105
|
+
- `/^filter-/` — filter utilities
|
|
106
|
+
|
|
107
|
+
### Bootstrap Components
|
|
108
|
+
- `/^modal-/`, `/^carousel-/`, `/^dropdown-/`, `/^offcanvas-/`
|
|
109
|
+
- `/^tooltip-/`, `/^popover-/`, `/^toast-/`
|
|
110
|
+
- `/^accordion/`, `/^collapse/`, `/^collapsed$/`, `/^collapsing$/`
|
|
111
|
+
- `/^show$/`, `/^showing$/`, `/^hide$/`, `/^fade$/`, `/^active$/`, `/^disabled$/`
|
|
112
|
+
- `/^bs-/`, `/^data-bs-/`
|
|
113
|
+
|
|
114
|
+
### State & Dynamic Classes
|
|
115
|
+
- `/^is-/`, `/^has-/`, `/^was-/` — state classes
|
|
116
|
+
- `/^animation-/` — all animation-* classes
|
|
117
|
+
|
|
118
|
+
### Form & Validation
|
|
119
|
+
- `/^invalid-feedback$/` — form validation messages
|
|
120
|
+
- `/^spinner-/` — loading spinners
|
|
121
|
+
- `/^file-drop-/` — file drop zone states
|
|
122
|
+
|
|
123
|
+
### Libraries & Third-Party
|
|
124
|
+
- `/^fa-/` — Font Awesome
|
|
125
|
+
- `/^lazy-/` — lazy loading
|
|
126
|
+
- `/^cookie-consent-/` — cookie consent
|
|
127
|
+
- `/^social-share-/` — social sharing
|
|
128
|
+
- `/^grecaptcha/` — Google reCAPTCHA
|
|
129
|
+
- `/^adsbygoogle$/` — Google AdSense
|
|
130
|
+
- `/^uj-vert-unit$/` — UJM ad units
|
|
131
|
+
- `/^uptime-tooltip$/` — status page tooltip
|
|
132
|
+
|
|
133
|
+
### Also Safe: Classes in Static HTML
|
|
134
|
+
|
|
135
|
+
Classes that appear in any HTML file (includes, layouts, pages) are automatically found by PurgeCSS and won't be purged. Only classes created exclusively in JavaScript need safelisting.
|
|
136
|
+
|
|
137
|
+
## When to Use Each Category
|
|
138
|
+
|
|
139
|
+
| Category | Use For | Example |
|
|
140
|
+
|----------|---------|---------|
|
|
141
|
+
| `standard` | Top-level class selectors only in JS | `'^fw-semibold$'` or `/^fw-/` |
|
|
142
|
+
| `deep` | Classes used as **nested/descendant** selectors | `'^dot$'` (for `.chat-typing .dot`) |
|
|
143
|
+
| `greedy` | Prefix-based families of classes | `'^chat-'` (matches chat-row, chat-bubble, etc.) |
|
|
144
|
+
| `keyframes` | Custom @keyframes animation names | `'chat-typing-bounce'` |
|
|
145
|
+
|
|
146
|
+
### standard vs deep
|
|
147
|
+
|
|
148
|
+
- **standard**: Only preserves top-level selectors. If a class is ONLY used as a descendant (e.g., `.chat-typing .dot`), `standard` won't preserve the rule.
|
|
149
|
+
- **deep**: Preserves the entire rule tree when any part of the selector matches.
|
|
150
|
+
|
|
151
|
+
## Workflow
|
|
152
|
+
|
|
153
|
+
1. **Find dynamic classes**: Search JS files for `classList.add()`, `classList.toggle()`, `className =`, `innerHTML` with `class=`, template literals with classes
|
|
154
|
+
2. **Check safelist**: Cross-reference against the patterns above — don't add what's already covered
|
|
155
|
+
3. **Check static HTML**: If a class appears in any HTML file, it doesn't need safelisting
|
|
156
|
+
4. **Add to correct location**: UJM core classes → `sass.js`, consuming project classes → `config/ultimate-jekyll-manager.json`
|
|
157
|
+
5. **Group by prefix**: If multiple classes share a prefix, use a prefix pattern like `/^chat-/`
|
|
158
|
+
6. **Restart required**: Changes to `sass.js` require a dev server restart (gulp tasks load once at startup)
|
|
159
|
+
|
|
160
|
+
## See also
|
|
161
|
+
|
|
162
|
+
- [css.md](css.md) — CSS guidelines, theme-adaptive classes
|
|
163
|
+
- [build-system.md](build-system.md) — where the sass/purge task runs in the pipeline
|
|
164
|
+
- [themes.md](themes.md) — theme SCSS structure (theme classes ship in static CSS, not JS)
|
package/docs/seo.md
CHANGED
|
@@ -1,9 +1,125 @@
|
|
|
1
|
-
# SEO
|
|
1
|
+
# SEO & Content
|
|
2
2
|
|
|
3
|
-
This doc covers
|
|
3
|
+
This doc covers UJM's SEO and content subsystems:
|
|
4
4
|
|
|
5
|
-
1. **
|
|
6
|
-
2. **
|
|
5
|
+
1. **Content writing rules** — headlines, sentence case, keywords (applies to ALL pages).
|
|
6
|
+
2. **Content page types** — Services (main offerings), Solutions (SEO landing pages), Alternatives (competitor comparisons).
|
|
7
|
+
3. **Schema / Structured Data (JSON-LD)** — `SoftwareApplication` and `FAQPage` JSON-LD blocks.
|
|
8
|
+
|
|
9
|
+
## Content Writing Rules (applies to ALL pages)
|
|
10
|
+
|
|
11
|
+
### H1 Headlines
|
|
12
|
+
|
|
13
|
+
- **Start with an action verb** — tell the user what they're getting immediately
|
|
14
|
+
- "Grow your business with social media ads" NOT "Social Media Advertising Services"
|
|
15
|
+
- "Automate your workflow in minutes" NOT "Workflow Automation Platform"
|
|
16
|
+
|
|
17
|
+
### Sentence Case (ALL headings)
|
|
18
|
+
|
|
19
|
+
- First word capitalized, rest lowercase
|
|
20
|
+
- "Grow your business with social media ads" NOT "Grow Your Business With Social Media Ads"
|
|
21
|
+
|
|
22
|
+
### Keywords
|
|
23
|
+
|
|
24
|
+
- Use primary keywords naturally throughout the page — in headings, subheadings, and body text
|
|
25
|
+
- Keep content readable and natural — don't keyword stuff but still use primary and secondary keywords where relevant
|
|
26
|
+
|
|
27
|
+
### Headline Structure (headline / headline_accent)
|
|
28
|
+
|
|
29
|
+
**Section headlines (non-hero):**
|
|
30
|
+
|
|
31
|
+
- Maximum 5-6 words total
|
|
32
|
+
- ONLY the LAST word should be accented (placed in `headline_accent`)
|
|
33
|
+
|
|
34
|
+
```yaml
|
|
35
|
+
# CORRECT — Last word only
|
|
36
|
+
headline: "Compare all"
|
|
37
|
+
headline_accent: "plans"
|
|
38
|
+
|
|
39
|
+
headline: "Frequently asked"
|
|
40
|
+
headline_accent: "questions"
|
|
41
|
+
|
|
42
|
+
# WRONG — Multiple words accented or wrong position
|
|
43
|
+
headline: "Compare"
|
|
44
|
+
headline_accent: "all plans" # Too many words accented
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Hero section headlines (exception):**
|
|
48
|
+
|
|
49
|
+
- Hero sections may accent MORE than just the last word — up to half of the phrase for emphasis
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
hero:
|
|
53
|
+
headline: "The right plans for"
|
|
54
|
+
headline_accent: "your business" # Multiple words OK in hero
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Homepage hero (special case):** more flexible — longer phrases with creative accent placement; focus on impact and brand messaging.
|
|
58
|
+
|
|
59
|
+
### Frontmatter SEO Rules
|
|
60
|
+
|
|
61
|
+
- **Default pages** (blueprint layouts): Do NOT include `meta.title` or `meta.description` — these are already set in the layout
|
|
62
|
+
- **Custom pages**: MUST include `meta.title` and `meta.description` in frontmatter
|
|
63
|
+
|
|
64
|
+
### Superheadline Rules
|
|
65
|
+
|
|
66
|
+
- Homepage: Do NOT change (keep default icon AND text)
|
|
67
|
+
- Other pages: Can customize `text` but keep default `icon`
|
|
68
|
+
|
|
69
|
+
### General Content Guidelines
|
|
70
|
+
|
|
71
|
+
- Keep copy concise and scannable; use active voice
|
|
72
|
+
- Focus on benefits, not features
|
|
73
|
+
- Match the brand tone from `_config.yml`
|
|
74
|
+
- Maintain consistency across all pages
|
|
75
|
+
|
|
76
|
+
## Services Pages (Main Offerings)
|
|
77
|
+
|
|
78
|
+
Services (or Features) pages represent the MAIN OFFERINGS of the business — the core products, services, or capabilities the business publicly promotes (e.g. an agency's "Social Media Advertising", "SEO Services", "Web Design").
|
|
79
|
+
|
|
80
|
+
**Structure:**
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
src/pages/services/
|
|
84
|
+
├── index.md # Lists all services
|
|
85
|
+
├── [service-1].md # layout: blueprint/index
|
|
86
|
+
├── [service-2].md
|
|
87
|
+
└── [service-3].md
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Page strategy — exactly 3 service pages** based on the business's core offerings:
|
|
91
|
+
|
|
92
|
+
- Featured prominently in nav dropdown AND the services index page
|
|
93
|
+
- Linked throughout the site (footer, CTAs, internal links)
|
|
94
|
+
- Fully customized with tailored features, testimonials, case studies, etc.
|
|
95
|
+
|
|
96
|
+
Each service page uses `layout: blueprint/index` with a hero like:
|
|
97
|
+
|
|
98
|
+
```yaml
|
|
99
|
+
hero:
|
|
100
|
+
tagline: "{{ site.brand.name }}"
|
|
101
|
+
headline: "Professional"
|
|
102
|
+
headline_accent: "[Service Name]"
|
|
103
|
+
subheadline: "[Value proposition for this service]"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Navigation:** add a `Services` dropdown to `src/_includes/frontend/sections/nav.json` (3 services + divider + "All Services" link) and a `Services` column to `footer.json` — see [assets.md](assets.md) for the JSON section system.
|
|
107
|
+
|
|
108
|
+
## Solutions Pages (SEO Landing Pages)
|
|
109
|
+
|
|
110
|
+
Solutions pages are SEO-focused landing pages that target SPECIFIC keywords, niches, or audience segments. They are NOT publicly linked on the site — they LINK BACK to the main Services pages (e.g. for a "Social Media Advertising" service: "TikTok Ads Agency", "Facebook Ads for E-commerce").
|
|
111
|
+
|
|
112
|
+
**Purpose:** drive organic traffic via long-tail keywords; target specific industries/platforms/use cases; funnel visitors to main service pages via CTAs and internal links.
|
|
113
|
+
|
|
114
|
+
**Structure:** individual pages only (`src/pages/solutions/*.md`, `layout: blueprint/index`) — **NO index page**; solutions are discovered via search engines, not site navigation.
|
|
115
|
+
|
|
116
|
+
**Guidelines:**
|
|
117
|
+
|
|
118
|
+
- **NOT linked in nav, footer, or services index** — search-engine discovery only
|
|
119
|
+
- **Always link back** to the related main service page (CTAs, body content, related-services section)
|
|
120
|
+
- Can be lighter customizations (hero + key sections) since they're keyword-focused
|
|
121
|
+
- Focus on specific search intent (e.g., "[Platform] + [Service] + [Location/Industry]")
|
|
122
|
+
- Any number of pages — use keyword research to identify opportunities
|
|
7
123
|
|
|
8
124
|
## Alternatives Collection (SEO Competitor Comparison Pages)
|
|
9
125
|
|
|
@@ -96,6 +212,17 @@ Which renders as "Can I import my data from ExampleApp?" for an ExampleApp compe
|
|
|
96
212
|
| CSS | `src/assets/css/pages/alternatives/alternative/index.scss` |
|
|
97
213
|
| JS | `src/assets/js/pages/alternatives/alternative/index.js` |
|
|
98
214
|
|
|
215
|
+
### Content Guidelines (alternatives)
|
|
216
|
+
|
|
217
|
+
- **Comparison features:** include 8-12 features for a thorough comparison
|
|
218
|
+
- **Values:** use `true`/`false` for binary features, strings for nuanced values ("Unlimited", "Limited", "Basic")
|
|
219
|
+
- **Tone:** confident but not aggressive — focus on our strengths, not bashing competitors
|
|
220
|
+
- **Shared sections:** do NOT repeat testimonials, stats, FAQs, CTA, or why_switch per page — they're inherited from the layout; override only when a specific competitor needs unique content
|
|
221
|
+
- **Video:** optional — set `video.youtube_id` only if a comparison video exists for that competitor
|
|
222
|
+
- **Up to 20 pages**
|
|
223
|
+
- The `/alternatives` index page is auto-generated — do NOT create it manually
|
|
224
|
+
- Do NOT add alternatives to the main nav or footer — discovered via search engines, like Solutions pages
|
|
225
|
+
|
|
99
226
|
## Schema / Structured Data (JSON-LD)
|
|
100
227
|
|
|
101
228
|
UJ automatically generates JSON-LD structured data in `foot.html`. The SoftwareApplication schema with AggregateRating is opt-in via frontmatter.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Templating
|
|
2
|
+
|
|
3
|
+
UJM uses node-powertools' `template()` for build-time token replacement, alongside (and deliberately distinct from) Jekyll's own Liquid templating.
|
|
4
|
+
|
|
5
|
+
## How it works
|
|
6
|
+
|
|
7
|
+
Two bracket conventions, chosen per call site so node-powertools tokens never collide with Liquid:
|
|
8
|
+
|
|
9
|
+
| Brackets | Used by | Files |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `{ x }` (node-powertools default) | Any `template()` call without a `brackets:` option — e.g. `defaults.js` Gemfile templating | Gemfile, scaffolded defaults |
|
|
12
|
+
| `[ x ]` | `distribute.js` theme fallback + [template-transform.js](../src/gulp/tasks/utils/template-transform.js) | `.html` / `.md` / `.liquid` / `.json` |
|
|
13
|
+
|
|
14
|
+
## Liquid coexistence
|
|
15
|
+
|
|
16
|
+
Jekyll's Liquid `{{ }}` / `{% %}` is processed by **Jekyll itself**, NOT by node-powertools — those placeholders pass through node-powertools untouched. The square-bracket convention exists precisely so the two engines can template the same file without fighting.
|
|
17
|
+
|
|
18
|
+
Corollary for consumer JS: Jekyll does NOT process `src/assets/js/**/*.js` — never leave Liquid tokens inside JS modules; bridge values via `data-*` attributes or `<template>` elements instead. See [common-mistakes.md](common-mistakes.md).
|
|
19
|
+
|
|
20
|
+
## See also
|
|
21
|
+
|
|
22
|
+
- [build-system.md](build-system.md) — where the templating passes run in the pipeline
|
|
23
|
+
- [layouts-and-pages.md](layouts-and-pages.md) — Liquid-side layout/frontmatter reference
|
package/docs/test-boot-layer.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Boot Layer
|
|
1
|
+
# Test Framework — Boot Layer
|
|
2
2
|
|
|
3
3
|
The boot layer runs Puppeteer against a real built `_site/` served by a tiny embedded HTTP server. It's the integration smoke that catches "did my actually-shipped site boot?" regressions — things plain-Node build tests can't see.
|
|
4
4
|
|
package/docs/test-framework.md
CHANGED
|
@@ -22,7 +22,19 @@ Mock **nothing** by default. There are exactly two cases where the real dependen
|
|
|
22
22
|
|
|
23
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
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
|
|
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 [extended mode](#extended-mode-test_extended_mode) (`--extended` / `TEST_EXTENDED_MODE=true`) 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
|
+
|
|
27
|
+
## Test coverage — every surface gets a test (HARD RULE)
|
|
28
|
+
|
|
29
|
+
A feature is not done when it works — it's done when every surface it exposes is covered in the layer that owns that surface:
|
|
30
|
+
|
|
31
|
+
| Coverage | Layer | Proves |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| **Logic** | `build` / `page` | The feature's functions do the right thing when called directly (real build Manager, real frontend Manager surface) |
|
|
34
|
+
| **UI** | `page` | The feature's interface is WIRED — a real event on the real DOM triggers the behavior and the visible result appears |
|
|
35
|
+
| **End-to-end** | `boot` | The feature survives in the consumer's actual built `_site/` (extend the boot suite's `inspect` assertions) |
|
|
36
|
+
|
|
37
|
+
**Skipping a layer is the exception, not the default.** A layer may be skipped ONLY when the feature genuinely has no surface there — a pure build-time utility has no UI; a CSS-only tweak has no logic to call. Convenience is never a reason: "the logic test already covers it" does NOT excuse the UI test — logic tests prove the logic, UI tests prove the wiring (a button can come unhooked while every logic test stays green), boot tests prove the built site. When in doubt, write the test.
|
|
26
38
|
|
|
27
39
|
## Quick start
|
|
28
40
|
|
|
@@ -37,25 +49,46 @@ npx mgr test --reporter json # machine-readable __UJM_TEST__ events
|
|
|
37
49
|
|
|
38
50
|
`npm test` works too — added to consumer `package.json#scripts.test` on `npx mgr setup`.
|
|
39
51
|
|
|
52
|
+
All test output is also teed (ANSI-stripped) to `<projectRoot>/logs/test.log`, truncated fresh on each run — same pattern as `dev.log`/`build.log` and EM's/BEM's `test.log`. Skipped on CI (`isServer()`). Grep it after a run instead of scrolling terminal output.
|
|
53
|
+
|
|
40
54
|
### Filtering tests
|
|
41
55
|
|
|
42
|
-
Pass a path (relative to `test/`) as a positional
|
|
56
|
+
Pass a path (relative to `test/`) as a positional **target** to select which test FILES run:
|
|
43
57
|
|
|
44
58
|
```bash
|
|
45
|
-
# Run a single test file
|
|
59
|
+
# Run a single test file (matches both framework + project)
|
|
46
60
|
npx mgr test pages/home
|
|
47
61
|
|
|
48
|
-
# Run
|
|
49
|
-
npx mgr test
|
|
62
|
+
# Run ONLY consumer project tests (no framework suites at all)
|
|
63
|
+
npx mgr test project:
|
|
50
64
|
|
|
51
|
-
# Run
|
|
65
|
+
# Run a single project test file
|
|
52
66
|
npx mgr test project:custom-test
|
|
53
67
|
|
|
68
|
+
# Run ONLY framework tests (universal cross-framework alias)
|
|
69
|
+
npx mgr test mgr:
|
|
70
|
+
|
|
71
|
+
# Run ONLY UJM framework tests (UJM-specific aliases, equivalent to mgr:)
|
|
72
|
+
npx mgr test ujm:
|
|
73
|
+
npx mgr test framework:
|
|
74
|
+
|
|
75
|
+
# Run framework tests matching a path
|
|
76
|
+
npx mgr test mgr:pages/home
|
|
77
|
+
npx mgr test ujm:pages/home
|
|
78
|
+
|
|
54
79
|
# Combine with extended mode
|
|
55
80
|
TEST_EXTENDED_MODE=true npx mgr test pages/boot-test
|
|
56
81
|
```
|
|
57
82
|
|
|
58
|
-
The
|
|
83
|
+
The target matches against the test file path. The source prefix scopes selection to framework-only or project-only tests — a prefixed target excludes the other source entirely:
|
|
84
|
+
|
|
85
|
+
- `mgr:` — the **universal cross-framework alias** for "the manager's own tests" (framework-only). Works identically in UJM, EM, BXM, and BEM.
|
|
86
|
+
- `ujm:` / `framework:` — UJM-specific aliases for framework-only tests, equivalent to `mgr:`.
|
|
87
|
+
- `project:` — consumer project tests only.
|
|
88
|
+
|
|
89
|
+
A bare prefix (`mgr:` / `ujm:` / `project:` with no path) runs every test in that source. A bare path (no prefix) searches both sources by path.
|
|
90
|
+
|
|
91
|
+
> **Target vs `--filter`.** The positional target selects test FILES (by path + source). The `--filter=<substring>` flag is orthogonal: it matches test NAMES/descriptions within the selected files. They compose, e.g. `npx mgr test project: --filter=foo`. The `--layer=build|page|boot` flag further narrows to a single layer.
|
|
59
92
|
|
|
60
93
|
## Layers
|
|
61
94
|
|
|
@@ -227,12 +260,27 @@ module.exports = ({ projectRoot }) => ({
|
|
|
227
260
|
});
|
|
228
261
|
```
|
|
229
262
|
|
|
263
|
+
## Extended mode (`TEST_EXTENDED_MODE`)
|
|
264
|
+
|
|
265
|
+
By default `npx mgr test` is fast and offline-safe: tests that would hit real external services are **skipped** (the code short-circuits in-source — it does NOT mock). Extended mode opts those tests in to run against the real path.
|
|
266
|
+
|
|
267
|
+
- **Turn it on:** `npx mgr test --extended` or `TEST_EXTENDED_MODE=true npx mgr test`.
|
|
268
|
+
- **Shared, unprefixed name.** `TEST_EXTENDED_MODE` is the SAME env var across BEM, BXM, UJM, and EM — cross-framework parity. Setting it in CI (or your shell) flips every framework's extended suites on.
|
|
269
|
+
- **Propagates to spawned environments.** Once the test command sets `process.env.TEST_EXTENDED_MODE`, it reaches every child it spawns — the Jekyll build (via inherited `process.env`) and the boot HTTP server / Puppeteer browsers — automatically.
|
|
270
|
+
- **A warning prints** when extended mode is on (also teed to `logs/test.log`), since it makes real network calls against live backends.
|
|
271
|
+
- **Tests gate on `process.env.TEST_EXTENDED_MODE`.** Skip the live path unless it's set, e.g. `if (process.env.TEST_EXTENDED_MODE !== 'true') return ctx.skip('extended mode only');`. Anything an extended test creates in a real external system MUST be cleaned up by the test (`cleanup`/`inspect` teardown) — the runner never resets external systems.
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
TEST_EXTENDED_MODE=true npx mgr test # all extended suites, all layers
|
|
275
|
+
npx mgr test --extended mgr:build # extended + a specific target
|
|
276
|
+
```
|
|
277
|
+
|
|
230
278
|
## Env vars
|
|
231
279
|
|
|
232
280
|
| Env | Set by | Purpose |
|
|
233
281
|
|---|---|---|
|
|
234
282
|
| `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. |
|
|
235
|
-
| `
|
|
283
|
+
| `TEST_EXTENDED_MODE=true` | `--extended` flag, or set in the env | Opt into tests that hit **real** external services (network fetches, Firebase via web-manager, live APIs). Off by default. Unprefixed + shared across BEM/BXM/UJM/EM. Propagates to every spawned child (Jekyll build, boot HTTP server / Puppeteer). Gate such tests on `process.env.TEST_EXTENDED_MODE`; anything they create externally MUST be cleaned up by the test. See [Extended mode](#extended-mode-test_extended_mode). |
|
|
236
284
|
| `UJ_TEST_BOOT_PROJECT` | Auto-set when UJM tests itself; else manual | Project root the boot runner uses (its `_site/` is the boot target) |
|
|
237
285
|
| `UJ_TEST_BOOT_DIR` | Manual | Absolute override for the `_site/` directory. Wins over `UJ_TEST_BOOT_PROJECT/_site` and `<cwd>/_site` |
|
|
238
286
|
| `UJ_TEST_DEBUG=1` | Manual | Verbose Puppeteer console output piped to the parent stdout |
|