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
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
---
|
|
2
|
+
### ALL PAGES ###
|
|
3
|
+
layout: themes/[ site.theme.id ]/frontend/core/base
|
|
4
|
+
|
|
5
|
+
### PAGE CONFIG ###
|
|
6
|
+
# Newsroom principles — news voice: a theme's frontmatter defaults are part
|
|
7
|
+
# of its identity (see docs/themes.md). The company_values key is universal.
|
|
8
|
+
company_values:
|
|
9
|
+
- title: "Accuracy first"
|
|
10
|
+
description: "Get it right before getting it first. Every fact is checked, every correction is published."
|
|
11
|
+
icon: "shield-check"
|
|
12
|
+
- title: "Independent always"
|
|
13
|
+
description: "No advertiser, owner, or party gets a say in what we publish. Readers fund the reporting."
|
|
14
|
+
icon: "scale-balanced"
|
|
15
|
+
- title: "Show our work"
|
|
16
|
+
description: "We cite sources, link documents, and explain how we know what we know."
|
|
17
|
+
icon: "magnifying-glass"
|
|
18
|
+
- title: "Readers before clicks"
|
|
19
|
+
description: "We write to inform, not to bait. If a headline overpromises, we've failed."
|
|
20
|
+
icon: "heart"
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
{% comment %}
|
|
24
|
+
NEWSFLASH TEAM — the masthead page
|
|
25
|
+
Same data contract as classy's team index (site.team loop, member.position/
|
|
26
|
+
description/links, company_values, CEO-resolution Liquid for the mission
|
|
27
|
+
attribution), presented as the newsroom: byline cards, an ink-slab charter,
|
|
28
|
+
and principle cards.
|
|
29
|
+
{% endcomment %}
|
|
30
|
+
|
|
31
|
+
<!-- ============================================ -->
|
|
32
|
+
<!-- HERO -->
|
|
33
|
+
<!-- ============================================ -->
|
|
34
|
+
<section class="pb-4">
|
|
35
|
+
<div class="container text-center" data-lazy="@class animation-slide-up">
|
|
36
|
+
<span class="kicker mb-3">{% uj_icon "pen-nib", "me-1" %}The masthead</span>
|
|
37
|
+
<h1 class="display-4 my-3">
|
|
38
|
+
The people behind <span class="text-accent">the bylines</span>
|
|
39
|
+
</h1>
|
|
40
|
+
<p class="lead mb-0">
|
|
41
|
+
Reporters, editors, and producers — the newsroom that puts {{ site.brand.name }} out every day.
|
|
42
|
+
</p>
|
|
43
|
+
</div>
|
|
44
|
+
</section>
|
|
45
|
+
|
|
46
|
+
<!-- ============================================ -->
|
|
47
|
+
<!-- THE NEWSROOM — byline cards -->
|
|
48
|
+
<!-- ============================================ -->
|
|
49
|
+
<section class="pt-0">
|
|
50
|
+
<div class="container">
|
|
51
|
+
<div class="section-head" data-lazy="@class animation-slide-up">
|
|
52
|
+
<h2>The newsroom</h2>
|
|
53
|
+
<span class="rule"></span>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div class="row g-4 justify-content-center">
|
|
57
|
+
{% for member in site.team %}
|
|
58
|
+
<div class="col-lg-4 col-md-6" data-lazy="@class animation-slide-up">
|
|
59
|
+
<div class="card h-100 p-4 text-center d-flex flex-column">
|
|
60
|
+
<a href="{{ site.url }}{{ member.url }}" class="text-decoration-none d-inline-block mx-auto mb-3">
|
|
61
|
+
<div class="avatar avatar-2xl">
|
|
62
|
+
{%- uj_member member.id, "image-tag", class="rounded-circle object-fit-cover d-block team-member-image" -%}
|
|
63
|
+
</div>
|
|
64
|
+
</a>
|
|
65
|
+
|
|
66
|
+
<h3 class="h4 mb-1">
|
|
67
|
+
<a href="{{ site.url }}{{ member.url }}" class="text-decoration-none text-body">
|
|
68
|
+
{%- uj_member member.id, "name" -%}
|
|
69
|
+
</a>
|
|
70
|
+
</h3>
|
|
71
|
+
<span class="kicker justify-content-center mb-3">{{ member.member.position }}</span>
|
|
72
|
+
<p class="text-body-secondary small mb-0 flex-grow-1">{{ member.member.description }}</p>
|
|
73
|
+
|
|
74
|
+
{% iftruthy member.member.links %}
|
|
75
|
+
<div class="d-flex justify-content-center gap-2 mt-3">
|
|
76
|
+
{% for link in member.member.links %}
|
|
77
|
+
{% assign link_icon = link.id %}
|
|
78
|
+
{% if link.id == "website" %}{% assign link_icon = "globe" %}{% endif %}
|
|
79
|
+
<a href="{{ link.url }}" class="btn btn-outline-dark btn-sm avatar avatar-md rounded-circle d-flex align-items-center justify-content-center" target="_blank" rel="nofollow noopener" title="{{ link.title }}">
|
|
80
|
+
{% uj_icon link_icon, "" %}
|
|
81
|
+
</a>
|
|
82
|
+
{% endfor %}
|
|
83
|
+
</div>
|
|
84
|
+
{% endiftruthy %}
|
|
85
|
+
|
|
86
|
+
<div class="mt-3">
|
|
87
|
+
<a href="{{ site.url }}{{ member.url }}" class="btn btn-outline-dark btn-sm">
|
|
88
|
+
Read their work
|
|
89
|
+
</a>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
{% endfor %}
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</section>
|
|
97
|
+
|
|
98
|
+
<!-- ============================================ -->
|
|
99
|
+
<!-- THE CHARTER — ink slab -->
|
|
100
|
+
<!-- ============================================ -->
|
|
101
|
+
<section class="pt-0">
|
|
102
|
+
<div class="container">
|
|
103
|
+
<div class="cta-panel" data-lazy="@class animation-slide-up">
|
|
104
|
+
<div class="cta-rings" aria-hidden="true"></div>
|
|
105
|
+
<div class="position-relative">
|
|
106
|
+
<span class="kicker mb-3">{% uj_icon "quote-left", "me-1" %}The charter</span>
|
|
107
|
+
<h2 class="cta-title">Report it right. <span class="text-accent">Write it sharp.</span></h2>
|
|
108
|
+
<p class="cta-desc mb-4">
|
|
109
|
+
"Our job is simple to say and hard to do: find out what's true, explain why it matters, and answer to no one but the reader."
|
|
110
|
+
</p>
|
|
111
|
+
{% assign mission_member = nil %}
|
|
112
|
+
{% for member in site.team %}
|
|
113
|
+
{% assign position_lower = member.member.position | downcase %}
|
|
114
|
+
{% if position_lower == "ceo" or position_lower == "chief executive officer" %}
|
|
115
|
+
{% assign mission_member = member %}
|
|
116
|
+
{% break %}
|
|
117
|
+
{% endif %}
|
|
118
|
+
{% endfor %}
|
|
119
|
+
{% unless mission_member %}
|
|
120
|
+
{% for member in site.team %}
|
|
121
|
+
{% assign position_lower = member.member.position | downcase %}
|
|
122
|
+
{% if position_lower == "owner" %}
|
|
123
|
+
{% assign mission_member = member %}
|
|
124
|
+
{% break %}
|
|
125
|
+
{% endif %}
|
|
126
|
+
{% endfor %}
|
|
127
|
+
{% endunless %}
|
|
128
|
+
{% unless mission_member %}
|
|
129
|
+
{% for member in site.team %}
|
|
130
|
+
{% assign position_lower = member.member.position | downcase %}
|
|
131
|
+
{% if position_lower == "president" %}
|
|
132
|
+
{% assign mission_member = member %}
|
|
133
|
+
{% break %}
|
|
134
|
+
{% endif %}
|
|
135
|
+
{% endfor %}
|
|
136
|
+
{% endunless %}
|
|
137
|
+
{% unless mission_member %}
|
|
138
|
+
{% assign mission_member = site.team | first %}
|
|
139
|
+
{% endunless %}
|
|
140
|
+
{% if mission_member %}
|
|
141
|
+
<div class="d-flex align-items-center gap-3">
|
|
142
|
+
<div class="avatar avatar-lg">
|
|
143
|
+
{%- uj_member mission_member.id, "image-tag", class="rounded-circle object-fit-cover d-block" -%}
|
|
144
|
+
</div>
|
|
145
|
+
<div>
|
|
146
|
+
<div class="fw-bold">{%- uj_member mission_member.id, "name" -%}</div>
|
|
147
|
+
<div class="opacity-75">{{ mission_member.member.position }}</div>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
{% endif %}
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</section>
|
|
155
|
+
|
|
156
|
+
<!-- ============================================ -->
|
|
157
|
+
<!-- PRINCIPLES -->
|
|
158
|
+
<!-- ============================================ -->
|
|
159
|
+
<section class="pt-0">
|
|
160
|
+
<div class="container">
|
|
161
|
+
<div class="section-head" data-lazy="@class animation-slide-up">
|
|
162
|
+
<h2>How we work</h2>
|
|
163
|
+
<span class="rule"></span>
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
<div class="row g-4">
|
|
167
|
+
{% for value in page.resolved.company_values %}
|
|
168
|
+
<div class="col-lg-3 col-md-6" data-lazy="@class animation-slide-up">
|
|
169
|
+
<div class="card h-100 p-4">
|
|
170
|
+
<span class="feature-icon d-inline-flex align-items-center justify-content-center rounded-circle text-bg-primary mb-3">
|
|
171
|
+
{% uj_icon value.icon %}
|
|
172
|
+
</span>
|
|
173
|
+
<h3 class="h4 mb-2">{{ value.title }}</h3>
|
|
174
|
+
<p class="text-body-secondary mb-0">{{ value.description }}</p>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
{% endfor %}
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</section>
|
|
181
|
+
|
|
182
|
+
<!-- ============================================ -->
|
|
183
|
+
<!-- JOIN CTA -->
|
|
184
|
+
<!-- ============================================ -->
|
|
185
|
+
<section class="pt-0">
|
|
186
|
+
<div class="container text-center" data-lazy="@class animation-slide-up">
|
|
187
|
+
<div class="row justify-content-center">
|
|
188
|
+
<div class="col-lg-8">
|
|
189
|
+
<span class="kicker justify-content-center mb-3">{% uj_icon "briefcase", "me-1" %}Join us</span>
|
|
190
|
+
<h2 class="h1 mb-3">Want your name on <span class="text-accent">the masthead?</span></h2>
|
|
191
|
+
<p class="lead mb-4">
|
|
192
|
+
We're always looking for sharp reporters, ruthless editors, and people who sweat the details.
|
|
193
|
+
</p>
|
|
194
|
+
<div class="d-flex flex-column flex-sm-row gap-3 justify-content-center">
|
|
195
|
+
<a href="/careers" class="btn btn-primary btn-lg">
|
|
196
|
+
{% uj_icon "rocket", "me-2" %}Apply now
|
|
197
|
+
</a>
|
|
198
|
+
<a href="/contact" class="btn btn-outline-dark btn-lg">
|
|
199
|
+
{% uj_icon "envelope", "me-2" %}Get in touch
|
|
200
|
+
</a>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
</section>
|
|
206
|
+
|
|
207
|
+
{{ content | uj_content_format }}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
### ALL PAGES ###
|
|
3
|
+
layout: themes/[ site.theme.id ]/frontend/core/base
|
|
4
|
+
|
|
5
|
+
### PAGE CONFIG ###
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
{% comment %}
|
|
9
|
+
NEWSFLASH TEAM MEMBER — the byline page
|
|
10
|
+
Same data contract as classy's member page (page.member.id/position/links/
|
|
11
|
+
description/location/joined/skills/hobbies + page content as the bio),
|
|
12
|
+
presented as a reporter profile: byline head, beat facts, and the bio.
|
|
13
|
+
{% endcomment %}
|
|
14
|
+
|
|
15
|
+
<!-- ============================================ -->
|
|
16
|
+
<!-- BYLINE HEAD -->
|
|
17
|
+
<!-- ============================================ -->
|
|
18
|
+
<section class="pb-4">
|
|
19
|
+
<div class="container text-center" data-lazy="@class animation-slide-up">
|
|
20
|
+
<div class="avatar avatar-5xl mb-4 mx-auto">
|
|
21
|
+
{%- uj_member page.member.id, "image-tag", class="rounded-circle object-fit-cover team-member-image" -%}
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<span class="kicker d-flex justify-content-center mb-2">{{ page.member.position }}</span>
|
|
25
|
+
<h1 class="display-4 my-2">
|
|
26
|
+
{%- uj_member page.member.id, "name" -%}
|
|
27
|
+
</h1>
|
|
28
|
+
|
|
29
|
+
{% iftruthy page.member.description %}
|
|
30
|
+
<p class="lead mb-4">{{ page.member.description }}</p>
|
|
31
|
+
{% endiftruthy %}
|
|
32
|
+
|
|
33
|
+
{% iftruthy page.member.links %}
|
|
34
|
+
<div class="d-flex justify-content-center gap-2">
|
|
35
|
+
{% for link in page.member.links %}
|
|
36
|
+
{% assign link_icon = link.id %}
|
|
37
|
+
{% if link.id == "website" %}{% assign link_icon = "globe" %}{% endif %}
|
|
38
|
+
<a href="{{ link.url }}" class="btn btn-outline-dark avatar avatar-lg rounded-circle d-flex align-items-center justify-content-center" target="_blank" rel="nofollow noopener" title="{{ link.title }}">
|
|
39
|
+
{% uj_icon link_icon, "" %}
|
|
40
|
+
</a>
|
|
41
|
+
{% endfor %}
|
|
42
|
+
</div>
|
|
43
|
+
{% endiftruthy %}
|
|
44
|
+
</div>
|
|
45
|
+
</section>
|
|
46
|
+
|
|
47
|
+
<!-- ============================================ -->
|
|
48
|
+
<!-- THE FACTS — beat card strip -->
|
|
49
|
+
<!-- ============================================ -->
|
|
50
|
+
<section class="pt-0">
|
|
51
|
+
<div class="container">
|
|
52
|
+
<div class="row g-4 justify-content-center">
|
|
53
|
+
{% iftruthy page.member.location %}
|
|
54
|
+
<div class="col-lg-3 col-md-6" data-lazy="@class animation-slide-up">
|
|
55
|
+
<div class="card h-100 p-4 text-center">
|
|
56
|
+
<span class="feature-icon d-inline-flex align-items-center justify-content-center rounded-circle text-bg-primary mb-3 mx-auto">
|
|
57
|
+
{% uj_icon "map-marker-alt" %}
|
|
58
|
+
</span>
|
|
59
|
+
<span class="kicker justify-content-center mb-2">Based in</span>
|
|
60
|
+
<p class="fw-bold mb-0">{{ page.member.location }}</p>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
{% endiftruthy %}
|
|
64
|
+
|
|
65
|
+
{% iftruthy page.member.joined %}
|
|
66
|
+
<div class="col-lg-3 col-md-6" data-lazy="@class animation-slide-up">
|
|
67
|
+
<div class="card h-100 p-4 text-center">
|
|
68
|
+
<span class="feature-icon d-inline-flex align-items-center justify-content-center rounded-circle text-bg-primary mb-3 mx-auto">
|
|
69
|
+
{% uj_icon "calendar" %}
|
|
70
|
+
</span>
|
|
71
|
+
<span class="kicker justify-content-center mb-2">On the masthead since</span>
|
|
72
|
+
<p class="fw-bold mb-0">{{ page.member.joined | date: "%B %Y" }}</p>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
{% endiftruthy %}
|
|
76
|
+
|
|
77
|
+
{% iftruthy page.member.skills %}
|
|
78
|
+
<div class="col-lg-3 col-md-6" data-lazy="@class animation-slide-up">
|
|
79
|
+
<div class="card h-100 p-4 text-center">
|
|
80
|
+
<span class="feature-icon d-inline-flex align-items-center justify-content-center rounded-circle text-bg-primary mb-3 mx-auto">
|
|
81
|
+
{% uj_icon "briefcase" %}
|
|
82
|
+
</span>
|
|
83
|
+
<span class="kicker justify-content-center mb-2">The beat</span>
|
|
84
|
+
<p class="fw-bold mb-0">{{ page.member.skills | join: ", " }}</p>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
{% endiftruthy %}
|
|
88
|
+
|
|
89
|
+
{% iftruthy page.member.hobbies %}
|
|
90
|
+
<div class="col-lg-3 col-md-6" data-lazy="@class animation-slide-up">
|
|
91
|
+
<div class="card h-100 p-4 text-center">
|
|
92
|
+
<span class="feature-icon d-inline-flex align-items-center justify-content-center rounded-circle text-bg-primary mb-3 mx-auto">
|
|
93
|
+
{% uj_icon "heart" %}
|
|
94
|
+
</span>
|
|
95
|
+
<span class="kicker justify-content-center mb-2">Off the clock</span>
|
|
96
|
+
<p class="fw-bold mb-0">{{ page.member.hobbies | join: ", " }}</p>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
{% endiftruthy %}
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</section>
|
|
103
|
+
|
|
104
|
+
<!-- ============================================ -->
|
|
105
|
+
<!-- BIO -->
|
|
106
|
+
<!-- ============================================ -->
|
|
107
|
+
<section class="pt-0">
|
|
108
|
+
<div class="container">
|
|
109
|
+
<div class="row justify-content-center">
|
|
110
|
+
<div class="col-lg-9">
|
|
111
|
+
{%- capture member_name -%}{%- uj_member page.member.id, "name" -%}{%- endcapture -%}
|
|
112
|
+
{%- assign first_name = member_name | split: ' ' | first -%}
|
|
113
|
+
<div class="section-head" data-lazy="@class animation-slide-up">
|
|
114
|
+
<h2>About {{ first_name }}</h2>
|
|
115
|
+
<span class="rule"></span>
|
|
116
|
+
</div>
|
|
117
|
+
<div class="fs-5" data-lazy="@class animation-slide-up">
|
|
118
|
+
{{ content | uj_content_format }}
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
</section>
|
|
124
|
+
|
|
125
|
+
<!-- ============================================ -->
|
|
126
|
+
<!-- BACK TO THE NEWSROOM -->
|
|
127
|
+
<!-- ============================================ -->
|
|
128
|
+
<section class="pt-0">
|
|
129
|
+
<div class="container text-center" data-lazy="@class animation-slide-up">
|
|
130
|
+
<a href="/team" class="btn btn-outline-dark btn-lg">
|
|
131
|
+
{% uj_icon "arrow-left", "me-2" %}Back to the newsroom
|
|
132
|
+
</a>
|
|
133
|
+
</div>
|
|
134
|
+
</section>
|
|
@@ -12,6 +12,10 @@ Match the framework's three layers — Ultimate Jekyll Manager's test runner dis
|
|
|
12
12
|
| `test/page/` | Browser page served from a local HTTP server | DOM, frontend Manager, page-specific scripts, `data-wm-bind` directives |
|
|
13
13
|
| `test/boot/` | Consumer's actual built `_site/` | End-to-end smoke tests (does the site boot, does the service worker register, do dynamic pages load) |
|
|
14
14
|
|
|
15
|
+
## Coverage
|
|
16
|
+
|
|
17
|
+
Every feature ships with tests at every layer it has a surface in — logic (`build`/`page`), UI (`page`), end-to-end (`boot`). Skip a layer only when the feature genuinely has no surface there; "the logic test covers it" does not excuse the UI test.
|
|
18
|
+
|
|
15
19
|
## Quick example
|
|
16
20
|
|
|
17
21
|
```js
|
|
@@ -115,8 +115,10 @@ async function jekyll(complete) {
|
|
|
115
115
|
].filter(Boolean).join(','),
|
|
116
116
|
'--incremental',
|
|
117
117
|
(Manager.isBuildMode() || argv.profile) ? ' --profile' : '',
|
|
118
|
-
// Limit posts in development for faster builds (use --all-posts to disable)
|
|
119
|
-
|
|
118
|
+
// Limit posts in development for faster builds (use --all-posts to disable).
|
|
119
|
+
// 18 keeps a fully-populated newsflash front page visible in dev (its
|
|
120
|
+
// deduplicated slots consume 17 distinct posts).
|
|
121
|
+
(!Manager.isBuildMode() && !argv['all-posts']) ? '--limit_posts 18' : '',
|
|
120
122
|
// '--disable-disk-cache',
|
|
121
123
|
]
|
|
122
124
|
|
package/dist/test/runner.js
CHANGED
|
@@ -40,12 +40,13 @@ class SkipError extends Error {
|
|
|
40
40
|
|
|
41
41
|
async function run(options = {}) {
|
|
42
42
|
options.layer = options.layer || 'all';
|
|
43
|
+
options.target = options.target || null;
|
|
43
44
|
options.filter = options.filter || null;
|
|
44
45
|
options.reporter = options.reporter || 'pretty';
|
|
45
46
|
|
|
46
47
|
const startTime = Date.now();
|
|
47
48
|
|
|
48
|
-
const sources = discoverTestFiles();
|
|
49
|
+
const sources = discoverTestFiles(options.target);
|
|
49
50
|
|
|
50
51
|
console.log('');
|
|
51
52
|
console.log(chalk.bold(' Ultimate Jekyll Manager Tests'));
|
|
@@ -356,7 +357,50 @@ function reportResults(results, durationMs) {
|
|
|
356
357
|
console.log(chalk.gray(`\n Total: ${total} tests in ${durationMs}ms\n`));
|
|
357
358
|
}
|
|
358
359
|
|
|
359
|
-
|
|
360
|
+
// Parse a positional test target into a source filter + path part.
|
|
361
|
+
// Source prefixes (standardized across all OMEGA frameworks):
|
|
362
|
+
// 'mgr:' / 'ujm:' / 'framework:' → framework tests ('mgr:' is the universal alias)
|
|
363
|
+
// 'project:' → project tests
|
|
364
|
+
// no prefix → both sources, matched by path
|
|
365
|
+
function parseTarget(target) {
|
|
366
|
+
if (!target) {
|
|
367
|
+
return { source: null, pathPart: null };
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const m = String(target).match(/^(project|mgr|ujm|framework):(.*)$/);
|
|
371
|
+
if (m) {
|
|
372
|
+
const source = m[1] === 'project' ? 'project' : 'framework';
|
|
373
|
+
return { source, pathPart: m[2] || null };
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return { source: null, pathPart: target };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Narrow a source's file list by the parsed target. A source-prefixed target
|
|
380
|
+
// excludes the other source entirely; the path part (if any) matches by
|
|
381
|
+
// relative path prefix.
|
|
382
|
+
function filterBySource(source, files, sourceFilter, pathPart) {
|
|
383
|
+
if (sourceFilter && sourceFilter !== source) {
|
|
384
|
+
return [];
|
|
385
|
+
}
|
|
386
|
+
if (!pathPart) {
|
|
387
|
+
return files;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return files.filter((file) => {
|
|
391
|
+
const rel = relativizePath(file, source);
|
|
392
|
+
const relNoExt = rel.replace(/\.js$/, '').replace(/\.test$/, '');
|
|
393
|
+
const partNoExt = pathPart.replace(/\.js$/, '').replace(/\.test$/, '');
|
|
394
|
+
return rel.startsWith(pathPart)
|
|
395
|
+
|| relNoExt === partNoExt
|
|
396
|
+
|| relNoExt.startsWith(partNoExt + '/')
|
|
397
|
+
|| rel.includes(pathPart);
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function discoverTestFiles(target) {
|
|
402
|
+
const { source: sourceFilter, pathPart } = parseTarget(target);
|
|
403
|
+
|
|
360
404
|
const framework = [];
|
|
361
405
|
const project = [];
|
|
362
406
|
|
|
@@ -393,7 +437,10 @@ function discoverTestFiles() {
|
|
|
393
437
|
});
|
|
394
438
|
}
|
|
395
439
|
|
|
396
|
-
return {
|
|
440
|
+
return {
|
|
441
|
+
framework: filterBySource('framework', framework, sourceFilter, pathPart),
|
|
442
|
+
project: filterBySource('project', project, sourceFilter, pathPart),
|
|
443
|
+
};
|
|
397
444
|
}
|
|
398
445
|
|
|
399
446
|
function relativizePath(file, source) {
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Build-layer tests for src/utils/attach-log-file.js (shipped to dist/utils/attach-log-file.js)
|
|
2
|
+
// — tee process.stdout/stderr to logs/<name>.log with ANSI stripping. Each test attaches,
|
|
3
|
+
// writes, detaches, then inspects the file.
|
|
4
|
+
//
|
|
5
|
+
// CRITICAL: these tests run INSIDE a live `npx mgr test` process whose own output is being
|
|
6
|
+
// teed to logs/test.log by the singleton. So they must NOT touch the singleton — exercising
|
|
7
|
+
// attach()/detach() on it would detach the live tee mid-run and truncate logs/test.log. Each
|
|
8
|
+
// test uses its OWN `createTee()` instance, which stacks under the live singleton tee and
|
|
9
|
+
// restores it cleanly on detach.
|
|
10
|
+
//
|
|
11
|
+
// UJM's tee is NAME-based (attach('<name>') → <cwd>/logs/<name>.log) and SYNCHRONOUS (detach
|
|
12
|
+
// closes the fd immediately; writes are fs.writeSync so the tail is already on disk). The
|
|
13
|
+
// throwaway log files are cleaned up in each test's finally block.
|
|
14
|
+
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
|
|
18
|
+
// Resolve the SAME path the tee writes to: <cwd>/logs/<name>.log.
|
|
19
|
+
function logPath(name) {
|
|
20
|
+
return path.resolve(process.cwd(), 'logs', `${name}.log`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = {
|
|
24
|
+
type: 'suite',
|
|
25
|
+
layer: 'build',
|
|
26
|
+
description: 'attach-log-file — tee stdout/stderr to a file',
|
|
27
|
+
tests: [
|
|
28
|
+
{
|
|
29
|
+
name: 'exports the expected surface',
|
|
30
|
+
run: (ctx) => {
|
|
31
|
+
const mod = require(path.join(__dirname, '..', '..', '..', 'utils', 'attach-log-file.js'));
|
|
32
|
+
ctx.expect(typeof mod).toBe('function');
|
|
33
|
+
ctx.expect(typeof mod.detach).toBe('function');
|
|
34
|
+
ctx.expect(typeof mod.stripAnsi).toBe('function');
|
|
35
|
+
ctx.expect(typeof mod.createTee).toBe('function');
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'stripAnsi removes color escape codes',
|
|
40
|
+
run: (ctx) => {
|
|
41
|
+
const { stripAnsi } = require(path.join(__dirname, '..', '..', '..', 'utils', 'attach-log-file.js'));
|
|
42
|
+
const colored = '\x1B[31mred\x1B[0m and \x1B[32mgreen\x1B[0m';
|
|
43
|
+
ctx.expect(stripAnsi(colored)).toBe('red and green');
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'attach + stdout.write + detach: file contains the writes',
|
|
48
|
+
run: (ctx) => {
|
|
49
|
+
const attach = require(path.join(__dirname, '..', '..', '..', 'utils', 'attach-log-file.js'));
|
|
50
|
+
// Isolated instance — stacks under the live test.log tee, never clobbers it.
|
|
51
|
+
const tee = attach.createTee();
|
|
52
|
+
const name = `ujm-log-${Date.now()}`;
|
|
53
|
+
const file = logPath(name);
|
|
54
|
+
try {
|
|
55
|
+
const fd = tee.attach(name);
|
|
56
|
+
// attach() returns null when skipped (isServer()) — guard so the test is meaningful.
|
|
57
|
+
if (fd === null) return ctx.skip('attach skipped (isServer)');
|
|
58
|
+
|
|
59
|
+
process.stdout.write('hello world\n');
|
|
60
|
+
process.stdout.write('\x1B[31mcolored\x1B[0m line\n');
|
|
61
|
+
// Synchronous writes — already flushed to disk. detach() just closes the fd.
|
|
62
|
+
tee.detach();
|
|
63
|
+
|
|
64
|
+
const contents = fs.readFileSync(file, 'utf8');
|
|
65
|
+
ctx.expect(contents).toContain('hello world');
|
|
66
|
+
ctx.expect(contents).toContain('colored line');
|
|
67
|
+
ctx.expect(contents).not.toContain('\x1B[');
|
|
68
|
+
} finally {
|
|
69
|
+
tee.detach();
|
|
70
|
+
try { fs.unlinkSync(file); } catch (e) {}
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'idempotent: attaching twice with same name returns same fd',
|
|
76
|
+
run: (ctx) => {
|
|
77
|
+
const attach = require(path.join(__dirname, '..', '..', '..', 'utils', 'attach-log-file.js'));
|
|
78
|
+
const tee = attach.createTee();
|
|
79
|
+
const name = `ujm-log-idem-${Date.now()}`;
|
|
80
|
+
const file = logPath(name);
|
|
81
|
+
try {
|
|
82
|
+
const fd1 = tee.attach(name);
|
|
83
|
+
if (fd1 === null) return ctx.skip('attach skipped (isServer)');
|
|
84
|
+
const fd2 = tee.attach(name);
|
|
85
|
+
ctx.expect(fd1).toBe(fd2);
|
|
86
|
+
} finally {
|
|
87
|
+
tee.detach();
|
|
88
|
+
try { fs.unlinkSync(file); } catch (e) {}
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'attach with falsy name returns null and does nothing',
|
|
94
|
+
run: (ctx) => {
|
|
95
|
+
const attach = require(path.join(__dirname, '..', '..', '..', 'utils', 'attach-log-file.js'));
|
|
96
|
+
const tee = attach.createTee();
|
|
97
|
+
ctx.expect(tee.attach(null)).toBe(null);
|
|
98
|
+
ctx.expect(tee.attach('')).toBe(null);
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
};
|