fxcss 0.7.0__tar.gz → 0.8.0__tar.gz
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.
- {fxcss-0.7.0 → fxcss-0.8.0}/PKG-INFO +47 -9
- {fxcss-0.7.0 → fxcss-0.8.0}/README.md +46 -8
- {fxcss-0.7.0 → fxcss-0.8.0}/fxcss/__init__.py +1 -1
- {fxcss-0.7.0 → fxcss-0.8.0}/fxcss/cli.py +51 -1
- {fxcss-0.7.0 → fxcss-0.8.0}/fxcss/compare.py +13 -3
- fxcss-0.8.0/fxcss/scaffold.py +150 -0
- fxcss-0.8.0/fxcss/templates/firefox-watch.yml +180 -0
- fxcss-0.8.0/fxcss/templates/pr-preview-cleanup.yml +46 -0
- fxcss-0.8.0/fxcss/templates/pr-preview-publish.yml +305 -0
- fxcss-0.8.0/fxcss/templates/pr-preview.yml +148 -0
- fxcss-0.8.0/fxcss/templates/showcase.yml +106 -0
- {fxcss-0.7.0 → fxcss-0.8.0}/fxcss.egg-info/PKG-INFO +47 -9
- {fxcss-0.7.0 → fxcss-0.8.0}/fxcss.egg-info/SOURCES.txt +6 -0
- {fxcss-0.7.0 → fxcss-0.8.0}/pyproject.toml +3 -0
- {fxcss-0.7.0 → fxcss-0.8.0}/tests/test_units.py +57 -0
- {fxcss-0.7.0 → fxcss-0.8.0}/LICENSE +0 -0
- {fxcss-0.7.0 → fxcss-0.8.0}/fxcss/__main__.py +0 -0
- {fxcss-0.7.0 → fxcss-0.8.0}/fxcss/audit.py +0 -0
- {fxcss-0.7.0 → fxcss-0.8.0}/fxcss/catalogue.py +0 -0
- {fxcss-0.7.0 → fxcss-0.8.0}/fxcss/core.py +0 -0
- {fxcss-0.7.0 → fxcss-0.8.0}/fxcss/fetch.py +0 -0
- {fxcss-0.7.0 → fxcss-0.8.0}/fxcss/probe.py +0 -0
- {fxcss-0.7.0 → fxcss-0.8.0}/fxcss.egg-info/dependency_links.txt +0 -0
- {fxcss-0.7.0 → fxcss-0.8.0}/fxcss.egg-info/entry_points.txt +0 -0
- {fxcss-0.7.0 → fxcss-0.8.0}/fxcss.egg-info/requires.txt +0 -0
- {fxcss-0.7.0 → fxcss-0.8.0}/fxcss.egg-info/top_level.txt +0 -0
- {fxcss-0.7.0 → fxcss-0.8.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fxcss
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Live-reload, inspect and screenshot-test Firefox userChrome.css themes
|
|
5
5
|
Author: AdamXweb
|
|
6
6
|
License: MIT
|
|
@@ -29,6 +29,14 @@ Edit your CSS and see it live, click any part of the UI to get its selector,
|
|
|
29
29
|
and screenshot-test changes in CI.
|
|
30
30
|
</p>
|
|
31
31
|
|
|
32
|
+
**Three ways in:**
|
|
33
|
+
|
|
34
|
+
| You are… | Start with |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| **Building a theme** | `fxcss watch` — edit CSS, see it live in ~50ms. `fxcss pick` names any element you click. |
|
|
37
|
+
| **Trying someone's theme** | `fxcss try owner/repo` — test-drive it in a throwaway profile; close the window and nothing remains. |
|
|
38
|
+
| **Maintaining a theme repo** | `fxcss init` — before/after screenshots on every pull request, on macOS, Windows and Linux. |
|
|
39
|
+
|
|
32
40
|
## Description
|
|
33
41
|
|
|
34
42
|
Working on a Firefox theme normally means: edit CSS, restart Firefox, squint,
|
|
@@ -41,10 +49,10 @@ gives you a live-reload loop, an element picker, and a screenshot differ.
|
|
|
41
49
|
|
|
42
50
|
Your real Firefox profile is never touched.
|
|
43
51
|
|
|
44
|
-

|
|
45
53
|
|
|
46
|
-
<p align="center"><sub
|
|
47
|
-
README was generated by fxcss itself.</sub></p>
|
|
54
|
+
<p align="center"><sub>Three saves in <code>fxcss watch</code> — the window updates in ~50ms.
|
|
55
|
+
Every image in this README was generated by fxcss itself.</sub></p>
|
|
48
56
|
|
|
49
57
|
## Requirements
|
|
50
58
|
|
|
@@ -93,6 +101,7 @@ at it with `--theme /path/to/theme`.
|
|
|
93
101
|
| [`watch`](#fxcss-watch) | Edit CSS and see it live, no restart |
|
|
94
102
|
| [`pick`](#fxcss-pick) | Click any part of the UI to get its CSS selector |
|
|
95
103
|
| [`inspect`](#fxcss-inspect) | Look up a selector you already have |
|
|
104
|
+
| [`init`](#fxcss-init) | Add PR previews and CI checks to your theme repo |
|
|
96
105
|
| [`audit`](#fxcss-audit) | Find every selector that no longer matches, and suggest fixes |
|
|
97
106
|
| [`changelog`](#fxcss-changelog) | Diff two Firefox builds to see what chrome changed |
|
|
98
107
|
| [`snapshot`](#fxcss-changelog) | Record a Firefox's chrome names, to diff against later |
|
|
@@ -171,6 +180,8 @@ a file in your editor and the running window updates in about 50ms.
|
|
|
171
180
|
The window is yours to drive — open menus, resize it, type in the address bar,
|
|
172
181
|
right-click things. Nothing is scripted.
|
|
173
182
|
|
|
183
|
+

|
|
184
|
+
|
|
174
185
|
| flag | effect |
|
|
175
186
|
| --- | --- |
|
|
176
187
|
| `--dark` | start in dark mode, for testing `prefers-color-scheme` rules |
|
|
@@ -233,6 +244,32 @@ styled `#urlbar-background` by id, which many older themes still do. The id was
|
|
|
233
244
|
replaced by a class, so the rule silently did nothing and the address bar
|
|
234
245
|
rendered unstyled. One command found it; the fix was `.urlbar-background`.
|
|
235
246
|
|
|
247
|
+
### fxcss init
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
fxcss init # before/after previews on every PR
|
|
251
|
+
fxcss init --watch --showcase # plus the weekly Firefox audit and
|
|
252
|
+
# release screenshots
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Turn any theme repository into one with CI.** Run it from your theme's root
|
|
256
|
+
and it writes the preview workflows into `.github/workflows/`, ready to commit:
|
|
257
|
+
every pull request then gets a comment showing the browser chrome before and
|
|
258
|
+
after the change, with changed pixels highlighted — rendered on macOS, Windows
|
|
259
|
+
and Linux, across sixteen views and every variant stylesheet you ship.
|
|
260
|
+
|
|
261
|
+
The generation is the point, not a convenience: the fxcss version is pinned to
|
|
262
|
+
the one doing the generating, and the publish allowlist is enumerated from
|
|
263
|
+
*your* theme's variant folder — the two things that had to be hand-edited, and
|
|
264
|
+
the second one silently drops views when forgotten. Existing files are never
|
|
265
|
+
overwritten without `--force`, and the output tells you the things that
|
|
266
|
+
otherwise surprise people (the comment starts after the workflows reach your
|
|
267
|
+
default branch; first-time contributors need one approval click).
|
|
268
|
+
|
|
269
|
+
If you'd like people to know:
|
|
270
|
+
|
|
271
|
+
[](https://github.com/AdamXweb/fxcss)
|
|
272
|
+
|
|
236
273
|
### fxcss audit
|
|
237
274
|
|
|
238
275
|
```bash
|
|
@@ -480,11 +517,12 @@ Two things to know before wiring this up:
|
|
|
480
517
|
posted as a comment, build the images in the `pull_request` job (no write
|
|
481
518
|
permissions, no secrets) and publish from a separate `workflow_run` job.
|
|
482
519
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
520
|
+
Don't copy workflow files by hand — `fxcss init` generates them for your theme,
|
|
521
|
+
allowlist and version pin included. [`examples/README.md`](examples/README.md)
|
|
522
|
+
explains the shape of what it writes, most importantly why the preview is two
|
|
523
|
+
workflows (fork PRs get a read-only token, so the half that runs their code
|
|
524
|
+
cannot be the half that posts the comment). This repo's own CI runs the full
|
|
525
|
+
pipeline against `examples/minimal-theme` on macOS, Windows and Linux.
|
|
488
526
|
|
|
489
527
|
## Things worth knowing
|
|
490
528
|
|
|
@@ -8,6 +8,14 @@ Edit your CSS and see it live, click any part of the UI to get its selector,
|
|
|
8
8
|
and screenshot-test changes in CI.
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
|
+
**Three ways in:**
|
|
12
|
+
|
|
13
|
+
| You are… | Start with |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| **Building a theme** | `fxcss watch` — edit CSS, see it live in ~50ms. `fxcss pick` names any element you click. |
|
|
16
|
+
| **Trying someone's theme** | `fxcss try owner/repo` — test-drive it in a throwaway profile; close the window and nothing remains. |
|
|
17
|
+
| **Maintaining a theme repo** | `fxcss init` — before/after screenshots on every pull request, on macOS, Windows and Linux. |
|
|
18
|
+
|
|
11
19
|
## Description
|
|
12
20
|
|
|
13
21
|
Working on a Firefox theme normally means: edit CSS, restart Firefox, squint,
|
|
@@ -20,10 +28,10 @@ gives you a live-reload loop, an element picker, and a screenshot differ.
|
|
|
20
28
|
|
|
21
29
|
Your real Firefox profile is never touched.
|
|
22
30
|
|
|
23
|
-

|
|
24
32
|
|
|
25
|
-
<p align="center"><sub
|
|
26
|
-
README was generated by fxcss itself.</sub></p>
|
|
33
|
+
<p align="center"><sub>Three saves in <code>fxcss watch</code> — the window updates in ~50ms.
|
|
34
|
+
Every image in this README was generated by fxcss itself.</sub></p>
|
|
27
35
|
|
|
28
36
|
## Requirements
|
|
29
37
|
|
|
@@ -72,6 +80,7 @@ at it with `--theme /path/to/theme`.
|
|
|
72
80
|
| [`watch`](#fxcss-watch) | Edit CSS and see it live, no restart |
|
|
73
81
|
| [`pick`](#fxcss-pick) | Click any part of the UI to get its CSS selector |
|
|
74
82
|
| [`inspect`](#fxcss-inspect) | Look up a selector you already have |
|
|
83
|
+
| [`init`](#fxcss-init) | Add PR previews and CI checks to your theme repo |
|
|
75
84
|
| [`audit`](#fxcss-audit) | Find every selector that no longer matches, and suggest fixes |
|
|
76
85
|
| [`changelog`](#fxcss-changelog) | Diff two Firefox builds to see what chrome changed |
|
|
77
86
|
| [`snapshot`](#fxcss-changelog) | Record a Firefox's chrome names, to diff against later |
|
|
@@ -150,6 +159,8 @@ a file in your editor and the running window updates in about 50ms.
|
|
|
150
159
|
The window is yours to drive — open menus, resize it, type in the address bar,
|
|
151
160
|
right-click things. Nothing is scripted.
|
|
152
161
|
|
|
162
|
+

|
|
163
|
+
|
|
153
164
|
| flag | effect |
|
|
154
165
|
| --- | --- |
|
|
155
166
|
| `--dark` | start in dark mode, for testing `prefers-color-scheme` rules |
|
|
@@ -212,6 +223,32 @@ styled `#urlbar-background` by id, which many older themes still do. The id was
|
|
|
212
223
|
replaced by a class, so the rule silently did nothing and the address bar
|
|
213
224
|
rendered unstyled. One command found it; the fix was `.urlbar-background`.
|
|
214
225
|
|
|
226
|
+
### fxcss init
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
fxcss init # before/after previews on every PR
|
|
230
|
+
fxcss init --watch --showcase # plus the weekly Firefox audit and
|
|
231
|
+
# release screenshots
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Turn any theme repository into one with CI.** Run it from your theme's root
|
|
235
|
+
and it writes the preview workflows into `.github/workflows/`, ready to commit:
|
|
236
|
+
every pull request then gets a comment showing the browser chrome before and
|
|
237
|
+
after the change, with changed pixels highlighted — rendered on macOS, Windows
|
|
238
|
+
and Linux, across sixteen views and every variant stylesheet you ship.
|
|
239
|
+
|
|
240
|
+
The generation is the point, not a convenience: the fxcss version is pinned to
|
|
241
|
+
the one doing the generating, and the publish allowlist is enumerated from
|
|
242
|
+
*your* theme's variant folder — the two things that had to be hand-edited, and
|
|
243
|
+
the second one silently drops views when forgotten. Existing files are never
|
|
244
|
+
overwritten without `--force`, and the output tells you the things that
|
|
245
|
+
otherwise surprise people (the comment starts after the workflows reach your
|
|
246
|
+
default branch; first-time contributors need one approval click).
|
|
247
|
+
|
|
248
|
+
If you'd like people to know:
|
|
249
|
+
|
|
250
|
+
[](https://github.com/AdamXweb/fxcss)
|
|
251
|
+
|
|
215
252
|
### fxcss audit
|
|
216
253
|
|
|
217
254
|
```bash
|
|
@@ -459,11 +496,12 @@ Two things to know before wiring this up:
|
|
|
459
496
|
posted as a comment, build the images in the `pull_request` job (no write
|
|
460
497
|
permissions, no secrets) and publish from a separate `workflow_run` job.
|
|
461
498
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
499
|
+
Don't copy workflow files by hand — `fxcss init` generates them for your theme,
|
|
500
|
+
allowlist and version pin included. [`examples/README.md`](examples/README.md)
|
|
501
|
+
explains the shape of what it writes, most importantly why the preview is two
|
|
502
|
+
workflows (fork PRs get a read-only token, so the half that runs their code
|
|
503
|
+
cannot be the half that posts the comment). This repo's own CI runs the full
|
|
504
|
+
pipeline against `examples/minimal-theme` on macOS, Windows and Linux.
|
|
467
505
|
|
|
468
506
|
## Things worth knowing
|
|
469
507
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"""fxcss - a testing toolkit for Firefox userChrome.css themes.
|
|
3
3
|
|
|
4
4
|
fxcss try download a theme from GitHub and test-drive it
|
|
5
|
+
fxcss init add PR previews and CI checks to your theme repo
|
|
5
6
|
fxcss watch edit CSS and see it live, no restart
|
|
6
7
|
fxcss pick click any part of the UI to get its CSS selector
|
|
7
8
|
fxcss inspect look up a selector you already have
|
|
@@ -26,6 +27,22 @@ from pathlib import Path
|
|
|
26
27
|
from . import core
|
|
27
28
|
from . import __version__
|
|
28
29
|
|
|
30
|
+
LANDING = """fxcss - a testing toolkit for Firefox userChrome.css themes
|
|
31
|
+
|
|
32
|
+
Building a theme?
|
|
33
|
+
fxcss watch edit CSS, see it live, no restart
|
|
34
|
+
fxcss pick click any part of the UI to get its selector
|
|
35
|
+
|
|
36
|
+
Trying someone else's theme?
|
|
37
|
+
fxcss try owner/repo test-drive it in a throwaway profile
|
|
38
|
+
|
|
39
|
+
Maintaining a theme repository?
|
|
40
|
+
fxcss init add before/after PR previews and CI checks
|
|
41
|
+
fxcss audit find selectors Firefox has renamed
|
|
42
|
+
|
|
43
|
+
Run `fxcss --help` for the full command list, or `fxcss <command> --help`
|
|
44
|
+
for one command. Nothing here ever touches your real Firefox profile."""
|
|
45
|
+
|
|
29
46
|
TOOLBOX_KEY = {
|
|
30
47
|
"darwin": "Cmd+Opt+Shift+I",
|
|
31
48
|
}.get(sys.platform, "Ctrl+Alt+Shift+I")
|
|
@@ -159,6 +176,20 @@ def cmd_try(args):
|
|
|
159
176
|
shutil.rmtree(workdir, ignore_errors=True)
|
|
160
177
|
|
|
161
178
|
|
|
179
|
+
def cmd_init(args):
|
|
180
|
+
from . import scaffold
|
|
181
|
+
theme = args.theme.resolve()
|
|
182
|
+
if not (theme / "chrome" / "userChrome.css").exists():
|
|
183
|
+
print(f"error: no chrome/userChrome.css under {theme} - run this from "
|
|
184
|
+
f"your theme's root, or pass --theme", file=sys.stderr)
|
|
185
|
+
return 2
|
|
186
|
+
slugs = sorted(core.find_variant_sheets(theme))
|
|
187
|
+
written, skipped = scaffold.write_workflows(
|
|
188
|
+
theme, slugs, watch=args.watch, showcase=args.showcase, force=args.force)
|
|
189
|
+
print(scaffold.next_steps(written, skipped, slugs, args.watch, args.showcase))
|
|
190
|
+
return 0
|
|
191
|
+
|
|
192
|
+
|
|
162
193
|
def cmd_watch(args):
|
|
163
194
|
theme = args.theme.resolve()
|
|
164
195
|
firefox = core.find_firefox(args.firefox)
|
|
@@ -442,6 +473,8 @@ def cmd_doctor(args):
|
|
|
442
473
|
print(f"\ntheme: {len(sheets)} stylesheets, {total:,} bytes")
|
|
443
474
|
missing = [p for p in ("chrome/userChrome.css",) if not (theme / p).exists()]
|
|
444
475
|
print("missing expected files: " + (", ".join(missing) if missing else "none"))
|
|
476
|
+
print("\nnew here? `fxcss try owner/repo` test-drives a theme; `fxcss init`")
|
|
477
|
+
print("adds PR previews to yours.")
|
|
445
478
|
return 0
|
|
446
479
|
|
|
447
480
|
|
|
@@ -468,7 +501,7 @@ def main(argv=None):
|
|
|
468
501
|
prog="fxcss", description=__doc__,
|
|
469
502
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
470
503
|
ap.add_argument("--version", action="version", version=f"fxcss {__version__}")
|
|
471
|
-
sub = ap.add_subparsers(dest="cmd", required=
|
|
504
|
+
sub = ap.add_subparsers(dest="cmd", required=False)
|
|
472
505
|
|
|
473
506
|
for verb, helptext in (("try", "download a theme from GitHub and test-drive it"),
|
|
474
507
|
("install", "alias for try")):
|
|
@@ -492,6 +525,17 @@ def main(argv=None):
|
|
|
492
525
|
_menus(tr)
|
|
493
526
|
tr.set_defaults(func=cmd_try)
|
|
494
527
|
|
|
528
|
+
ini = sub.add_parser("init", help="add PR previews and CI checks to your theme repo")
|
|
529
|
+
ini.add_argument("--theme", type=Path, default=Path.cwd(),
|
|
530
|
+
help="theme root, the folder containing chrome/ (default: cwd)")
|
|
531
|
+
ini.add_argument("--watch", action="store_true",
|
|
532
|
+
help="also add the weekly Firefox release/beta/nightly audit")
|
|
533
|
+
ini.add_argument("--showcase", action="store_true",
|
|
534
|
+
help="also add the on-release showcase screenshot workflow")
|
|
535
|
+
ini.add_argument("--force", action="store_true",
|
|
536
|
+
help="replace workflow files that already exist")
|
|
537
|
+
ini.set_defaults(func=cmd_init)
|
|
538
|
+
|
|
495
539
|
w = sub.add_parser("watch", help="live-reload the theme as you edit")
|
|
496
540
|
_common(w); _menus(w)
|
|
497
541
|
w.add_argument("--dark", action="store_true", help="start in dark mode")
|
|
@@ -577,6 +621,12 @@ def main(argv=None):
|
|
|
577
621
|
d.set_defaults(func=cmd_doctor)
|
|
578
622
|
|
|
579
623
|
args = ap.parse_args(argv)
|
|
624
|
+
if args.cmd is None:
|
|
625
|
+
# Bare `fxcss` used to be an argparse error. Greet by task instead:
|
|
626
|
+
# the three reasons someone installs this are not obvious from a
|
|
627
|
+
# subcommand list.
|
|
628
|
+
print(LANDING)
|
|
629
|
+
return 0
|
|
580
630
|
# Long-running commands are a live log; keep them readable when piped.
|
|
581
631
|
try:
|
|
582
632
|
sys.stdout.reconfigure(line_buffering=True)
|
|
@@ -73,9 +73,19 @@ def normalise(im, width=PANEL_WIDTH):
|
|
|
73
73
|
|
|
74
74
|
|
|
75
75
|
def diff_stats(base, head):
|
|
76
|
-
"""Return (changed_pixel_count, total, mask) ignoring sub-threshold noise.
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
"""Return (changed_pixel_count, total, mask) ignoring sub-threshold noise.
|
|
77
|
+
|
|
78
|
+
Thresholds the per-channel *maximum* difference, not the luminance of the
|
|
79
|
+
difference. Luminance averages the channels, so a chroma-only shift -- a
|
|
80
|
+
toolbar tinted from blue-grey to peach, say -- can slide under the
|
|
81
|
+
threshold while being plainly visible. Recolouring is exactly the kind of
|
|
82
|
+
change a theme differ exists to catch.
|
|
83
|
+
"""
|
|
84
|
+
delta = ImageChops.difference(base, head)
|
|
85
|
+
channel_max = delta.split()[0]
|
|
86
|
+
for channel in delta.split()[1:]:
|
|
87
|
+
channel_max = ImageChops.lighter(channel_max, channel)
|
|
88
|
+
mask = channel_max.point(lambda v: 255 if v >= NOISE_THRESHOLD else 0)
|
|
79
89
|
changed = mask.histogram()[255]
|
|
80
90
|
return changed, base.width * base.height, mask
|
|
81
91
|
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Generate CI workflows for a theme repository: the machinery behind
|
|
3
|
+
`fxcss init`.
|
|
4
|
+
|
|
5
|
+
The templates ship inside the package, so a `pipx install fxcss` user gets
|
|
6
|
+
them without ever visiting the repository. Three things are substituted per
|
|
7
|
+
theme, which is exactly the hand-editing that used to trip people up:
|
|
8
|
+
|
|
9
|
+
* the fxcss version pin, set to the version doing the generating
|
|
10
|
+
* the publish allowlist's variant names, enumerated from the theme's own
|
|
11
|
+
optional stylesheets -- an allowlist someone has to remember to extend is
|
|
12
|
+
an allowlist that silently drops views
|
|
13
|
+
* the showcase URL, taken from the repository's origin remote
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import re
|
|
17
|
+
import subprocess
|
|
18
|
+
from importlib import resources
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from . import __version__
|
|
22
|
+
|
|
23
|
+
PREVIEW_FILES = ("pr-preview.yml", "pr-preview-publish.yml", "pr-preview-cleanup.yml")
|
|
24
|
+
WATCH_FILE = "firefox-watch.yml"
|
|
25
|
+
SHOWCASE_FILE = "showcase.yml"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def variant_alternation(slugs):
|
|
29
|
+
"""The regex fragment that admits this theme's variant captures.
|
|
30
|
+
|
|
31
|
+
Empty when there are no variants, so the allowlist stays as tight as the
|
|
32
|
+
theme is simple.
|
|
33
|
+
"""
|
|
34
|
+
names = sorted(s for s in slugs if re.fullmatch(r"[a-z0-9-]+", s))
|
|
35
|
+
if not names:
|
|
36
|
+
return ""
|
|
37
|
+
return "|variant-(?:" + "|".join(names) + ")"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def https_repo_url(remote):
|
|
41
|
+
"""Normalise a git remote to a browsable https URL, or None."""
|
|
42
|
+
if not remote:
|
|
43
|
+
return None
|
|
44
|
+
remote = remote.strip()
|
|
45
|
+
match = re.match(r"^git@([^:]+):(.+?)(?:\.git)?$", remote)
|
|
46
|
+
if match:
|
|
47
|
+
return f"https://{match.group(1)}/{match.group(2)}"
|
|
48
|
+
match = re.match(r"^(https?://[^ ]+?)(?:\.git)?/?$", remote)
|
|
49
|
+
if match:
|
|
50
|
+
return match.group(1)
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def detect_repo_url(theme: Path):
|
|
55
|
+
try:
|
|
56
|
+
raw = subprocess.run(
|
|
57
|
+
["git", "-C", str(theme), "config", "--get", "remote.origin.url"],
|
|
58
|
+
capture_output=True, text=True, timeout=10).stdout
|
|
59
|
+
except (OSError, subprocess.SubprocessError):
|
|
60
|
+
raw = ""
|
|
61
|
+
return https_repo_url(raw)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def render_template(text, version, variant_alt, repo_url):
|
|
65
|
+
text = text.replace("__FXCSS_VERSION__", version)
|
|
66
|
+
text = text.replace("__FXCSS_VARIANT_ALT__", variant_alt)
|
|
67
|
+
text = text.replace("__FXCSS_REPO_URL__", repo_url or "https://example.com")
|
|
68
|
+
return text
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _template(name):
|
|
72
|
+
return (resources.files("fxcss") / "templates" / name).read_text(encoding="utf-8")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def write_workflows(theme: Path, variant_slugs, watch=False, showcase=False,
|
|
76
|
+
force=False, version=None):
|
|
77
|
+
"""Write the chosen workflows into <theme>/.github/workflows.
|
|
78
|
+
|
|
79
|
+
Returns (written, skipped) lists of paths relative to the theme. Existing
|
|
80
|
+
files are never overwritten unless force is set -- a repo's workflows may
|
|
81
|
+
carry local edits, and clobbering them silently would be worse than making
|
|
82
|
+
someone pass --force.
|
|
83
|
+
"""
|
|
84
|
+
version = version or f"v{__version__}"
|
|
85
|
+
alt = variant_alternation(variant_slugs)
|
|
86
|
+
repo_url = detect_repo_url(theme)
|
|
87
|
+
|
|
88
|
+
wanted = list(PREVIEW_FILES)
|
|
89
|
+
if watch:
|
|
90
|
+
wanted.append(WATCH_FILE)
|
|
91
|
+
if showcase:
|
|
92
|
+
wanted.append(SHOWCASE_FILE)
|
|
93
|
+
|
|
94
|
+
outdir = theme / ".github" / "workflows"
|
|
95
|
+
outdir.mkdir(parents=True, exist_ok=True)
|
|
96
|
+
|
|
97
|
+
written, skipped = [], []
|
|
98
|
+
for name in wanted:
|
|
99
|
+
target = outdir / name
|
|
100
|
+
rel = target.relative_to(theme)
|
|
101
|
+
if target.exists() and not force:
|
|
102
|
+
skipped.append(rel)
|
|
103
|
+
continue
|
|
104
|
+
target.write_text(render_template(_template(name), version, alt, repo_url),
|
|
105
|
+
encoding="utf-8")
|
|
106
|
+
written.append(rel)
|
|
107
|
+
return written, skipped
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
BADGE = ("[![theme previews by fxcss]"
|
|
111
|
+
"(https://img.shields.io/badge/theme%20previews-fxcss-ff7139)]"
|
|
112
|
+
"(https://github.com/AdamXweb/fxcss)")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def next_steps(written, skipped, variant_slugs, watch, showcase):
|
|
116
|
+
"""Human instructions for what was generated. Returned, not printed, so it
|
|
117
|
+
is unit-testable and the CLI owns all output."""
|
|
118
|
+
lines = []
|
|
119
|
+
if written:
|
|
120
|
+
lines.append("Wrote:")
|
|
121
|
+
lines += [f" {p}" for p in written]
|
|
122
|
+
if skipped:
|
|
123
|
+
lines.append("Left alone (already exist; rerun with --force to replace):")
|
|
124
|
+
lines += [f" {p}" for p in skipped]
|
|
125
|
+
if variant_slugs:
|
|
126
|
+
lines.append("")
|
|
127
|
+
lines.append(f"Publish allowlist covers {len(variant_slugs)} variant "
|
|
128
|
+
f"stylesheet(s): {', '.join(sorted(variant_slugs))}.")
|
|
129
|
+
lines.append("Adding a variant later? Re-run `fxcss init --force`, or add it "
|
|
130
|
+
"to the NAME regex by hand.")
|
|
131
|
+
lines += [
|
|
132
|
+
"",
|
|
133
|
+
"Worth knowing before the first pull request:",
|
|
134
|
+
" - The preview comment only starts appearing once these files are on",
|
|
135
|
+
" your default branch: workflow_run always uses the default-branch copy.",
|
|
136
|
+
" - A first-time contributor's run waits for your 'Approve and run",
|
|
137
|
+
" workflows' click. That is GitHub policy, not a misconfiguration.",
|
|
138
|
+
" - Images are published to an orphan ci-previews branch and cleaned up",
|
|
139
|
+
" when each pull request closes.",
|
|
140
|
+
]
|
|
141
|
+
if watch:
|
|
142
|
+
lines.append(" - firefox-watch runs Mondays and needs no further setup.")
|
|
143
|
+
if showcase:
|
|
144
|
+
lines.append(" - showcase publishes to a `showcase` branch on each release.")
|
|
145
|
+
lines += [
|
|
146
|
+
"",
|
|
147
|
+
"If you would like to say so in your README:",
|
|
148
|
+
f" {BADGE}",
|
|
149
|
+
]
|
|
150
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
name: Firefox watch
|
|
2
|
+
|
|
3
|
+
# Firefox ships every few weeks and quietly renames parts of its UI. A theme
|
|
4
|
+
# does not break loudly when that happens -- the rule simply stops matching and
|
|
5
|
+
# that piece of styling disappears, with nothing logged anywhere.
|
|
6
|
+
#
|
|
7
|
+
# This runs fxcss audit against each Firefox channel on a schedule. Beta and
|
|
8
|
+
# Nightly are the point of the exercise: they show what is coming weeks before
|
|
9
|
+
# it reaches the people using the theme.
|
|
10
|
+
#
|
|
11
|
+
# When there are confident fixes it opens a pull request with them. When there
|
|
12
|
+
# are findings it cannot fix for you, it opens (or updates) one issue per
|
|
13
|
+
# channel instead, and closes it again once the channel comes back clean.
|
|
14
|
+
|
|
15
|
+
on:
|
|
16
|
+
schedule:
|
|
17
|
+
# Mondays, 06:20 UTC. Off the hour because scheduled jobs bunch there.
|
|
18
|
+
- cron: '20 6 * * 1'
|
|
19
|
+
workflow_dispatch:
|
|
20
|
+
inputs:
|
|
21
|
+
channel:
|
|
22
|
+
description: 'Single channel to check (blank = all)'
|
|
23
|
+
required: false
|
|
24
|
+
|
|
25
|
+
permissions:
|
|
26
|
+
contents: write
|
|
27
|
+
pull-requests: write
|
|
28
|
+
issues: write
|
|
29
|
+
|
|
30
|
+
env:
|
|
31
|
+
FXCSS_VERSION: __FXCSS_VERSION__
|
|
32
|
+
|
|
33
|
+
jobs:
|
|
34
|
+
check:
|
|
35
|
+
name: ${{ matrix.channel }}
|
|
36
|
+
runs-on: macos-latest
|
|
37
|
+
timeout-minutes: 30
|
|
38
|
+
strategy:
|
|
39
|
+
fail-fast: false
|
|
40
|
+
matrix:
|
|
41
|
+
channel: [release, beta, nightly]
|
|
42
|
+
|
|
43
|
+
steps:
|
|
44
|
+
- uses: actions/checkout@v4
|
|
45
|
+
|
|
46
|
+
- uses: actions/setup-python@v5
|
|
47
|
+
with:
|
|
48
|
+
python-version: '3.12'
|
|
49
|
+
|
|
50
|
+
- name: Install fxcss
|
|
51
|
+
run: >-
|
|
52
|
+
python -m pip install --disable-pip-version-check
|
|
53
|
+
"fxcss[images] @ git+https://github.com/AdamXweb/fxcss@${{ env.FXCSS_VERSION }}"
|
|
54
|
+
|
|
55
|
+
- name: Install Firefox (${{ matrix.channel }})
|
|
56
|
+
if: ${{ github.event.inputs.channel == '' || github.event.inputs.channel == matrix.channel }}
|
|
57
|
+
run: |
|
|
58
|
+
set -euo pipefail
|
|
59
|
+
case "${{ matrix.channel }}" in
|
|
60
|
+
release) product=firefox-latest-ssl ;;
|
|
61
|
+
beta) product=firefox-beta-latest-ssl ;;
|
|
62
|
+
nightly) product=firefox-nightly-latest-ssl ;;
|
|
63
|
+
esr) product=firefox-esr-latest-ssl ;;
|
|
64
|
+
*) echo "unknown channel"; exit 1 ;;
|
|
65
|
+
esac
|
|
66
|
+
curl -sSL -o firefox.dmg \
|
|
67
|
+
"https://download.mozilla.org/?product=${product}&os=osx&lang=en-US"
|
|
68
|
+
hdiutil attach -nobrowse -quiet -mountpoint /Volumes/ffwatch firefox.dmg
|
|
69
|
+
cp -R /Volumes/ffwatch/*.app "$RUNNER_TEMP/Firefox.app"
|
|
70
|
+
hdiutil detach /Volumes/ffwatch -quiet
|
|
71
|
+
echo "FIREFOX_BIN=$RUNNER_TEMP/Firefox.app/Contents/MacOS/firefox" >> "$GITHUB_ENV"
|
|
72
|
+
|
|
73
|
+
- name: Audit
|
|
74
|
+
id: audit
|
|
75
|
+
if: ${{ github.event.inputs.channel == '' || github.event.inputs.channel == matrix.channel }}
|
|
76
|
+
run: |
|
|
77
|
+
set +e
|
|
78
|
+
# Note the redirect rather than a pipe into tee: after a pipeline $?
|
|
79
|
+
# is tee's status, which is always 0, so findings would never be seen.
|
|
80
|
+
fxcss audit --no-colour --no-unused --strict --patch fxcss-fix.patch > audit.txt 2>&1
|
|
81
|
+
status=$?
|
|
82
|
+
cat audit.txt
|
|
83
|
+
echo "status=$status" >> "$GITHUB_OUTPUT"
|
|
84
|
+
|
|
85
|
+
version=$(fxcss doctor 2>/dev/null \
|
|
86
|
+
| sed -n 's/^version: *\([0-9][^ ]*\).*/\1/p' | head -1)
|
|
87
|
+
echo "version=${version:-unknown}" >> "$GITHUB_OUTPUT"
|
|
88
|
+
if [ -s fxcss-fix.patch ]; then echo "patch=yes" >> "$GITHUB_OUTPUT"; fi
|
|
89
|
+
|
|
90
|
+
- name: Open a pull request with the confident fixes
|
|
91
|
+
if: steps.audit.outputs.status != '0' && steps.audit.outputs.patch == 'yes'
|
|
92
|
+
env:
|
|
93
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
94
|
+
CHANNEL: ${{ matrix.channel }}
|
|
95
|
+
VERSION: ${{ steps.audit.outputs.version }}
|
|
96
|
+
run: |
|
|
97
|
+
set -euo pipefail
|
|
98
|
+
branch="fxcss/firefox-${CHANNEL}-${VERSION}"
|
|
99
|
+
if git ls-remote --exit-code --heads origin "$branch" >/dev/null 2>&1; then
|
|
100
|
+
echo "branch $branch already exists; nothing new to open"
|
|
101
|
+
exit 0
|
|
102
|
+
fi
|
|
103
|
+
git config user.name 'github-actions[bot]'
|
|
104
|
+
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
|
|
105
|
+
git checkout -b "$branch"
|
|
106
|
+
git apply fxcss-fix.patch
|
|
107
|
+
git add -A ':!audit.txt' ':!fxcss-fix.patch'
|
|
108
|
+
git commit -m "fix(css): update selectors renamed in Firefox ${VERSION}"
|
|
109
|
+
git push origin "$branch"
|
|
110
|
+
{
|
|
111
|
+
echo "Firefox **${CHANNEL}** is now **${VERSION}**, and some selectors this"
|
|
112
|
+
echo "theme uses no longer match anything in it."
|
|
113
|
+
echo
|
|
114
|
+
echo "The changes here are the ones fxcss is certain about — a name that moved"
|
|
115
|
+
echo "between an id and a class. Anything it was less sure of is listed below"
|
|
116
|
+
echo "but **not** changed."
|
|
117
|
+
echo
|
|
118
|
+
echo '```'
|
|
119
|
+
cat audit.txt
|
|
120
|
+
echo '```'
|
|
121
|
+
echo
|
|
122
|
+
echo "Worth previewing before merging: the PR preview workflow will render"
|
|
123
|
+
echo "before and after on this pull request."
|
|
124
|
+
} > body.md
|
|
125
|
+
gh pr create --base "${GITHUB_REF_NAME}" --head "$branch" \
|
|
126
|
+
--title "Firefox ${CHANNEL} ${VERSION}: update renamed selectors" \
|
|
127
|
+
--body-file body.md
|
|
128
|
+
|
|
129
|
+
- name: Open or update the heads-up issue
|
|
130
|
+
if: steps.audit.outputs.status != '0' && steps.audit.outputs.patch != 'yes'
|
|
131
|
+
env:
|
|
132
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
133
|
+
CHANNEL: ${{ matrix.channel }}
|
|
134
|
+
VERSION: ${{ steps.audit.outputs.version }}
|
|
135
|
+
run: |
|
|
136
|
+
set -euo pipefail
|
|
137
|
+
title="Firefox ${CHANNEL}: selectors need attention"
|
|
138
|
+
{
|
|
139
|
+
echo "Firefox **${CHANNEL}** is now **${VERSION}**. Some selectors this theme"
|
|
140
|
+
echo "uses no longer match anything, and fxcss was not confident enough to"
|
|
141
|
+
echo "propose a replacement automatically."
|
|
142
|
+
echo
|
|
143
|
+
echo '```'
|
|
144
|
+
cat audit.txt
|
|
145
|
+
echo '```'
|
|
146
|
+
echo
|
|
147
|
+
echo "_Updated automatically. This closes itself when the channel is clean._"
|
|
148
|
+
} > body.md
|
|
149
|
+
existing=$(gh issue list --state open --search "$title in:title" \
|
|
150
|
+
--json number,title --jq "[.[] | select(.title == \"$title\")][0].number")
|
|
151
|
+
if [ -n "$existing" ] && [ "$existing" != "null" ]; then
|
|
152
|
+
gh issue edit "$existing" --body-file body.md
|
|
153
|
+
else
|
|
154
|
+
gh issue create --title "$title" --body-file body.md
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
- name: Close the issue if this channel is clean again
|
|
158
|
+
if: steps.audit.outputs.status == '0'
|
|
159
|
+
env:
|
|
160
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
161
|
+
CHANNEL: ${{ matrix.channel }}
|
|
162
|
+
run: |
|
|
163
|
+
set -euo pipefail
|
|
164
|
+
title="Firefox ${CHANNEL}: selectors need attention"
|
|
165
|
+
existing=$(gh issue list --state open --search "$title in:title" \
|
|
166
|
+
--json number,title --jq "[.[] | select(.title == \"$title\")][0].number")
|
|
167
|
+
if [ -n "$existing" ] && [ "$existing" != "null" ]; then
|
|
168
|
+
gh issue close "$existing" \
|
|
169
|
+
--comment "Firefox ${CHANNEL} audits clean again — closing."
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
- uses: actions/upload-artifact@v4
|
|
173
|
+
if: always()
|
|
174
|
+
with:
|
|
175
|
+
name: audit-${{ matrix.channel }}
|
|
176
|
+
path: |
|
|
177
|
+
audit.txt
|
|
178
|
+
fxcss-fix.patch
|
|
179
|
+
if-no-files-found: ignore
|
|
180
|
+
retention-days: 30
|