Skip to content

Commit 380a6fc

Browse files
authored
feat: add Paragon build-tokens command using tutor custom jobs (#34)
1 parent ac5f94f commit 380a6fc

File tree

8 files changed

+4996
-1
lines changed

8 files changed

+4996
-1
lines changed

plugins/tutor-contrib-paragon/README.rst

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ This structure is optimized for design token–based themes (see `Paragon Design
4444

4545
.. note::
4646

47-
Documentation for how to use extensions is not yet available.
4847
A link to the official Open edX or Paragon documentation will be added here once it is published.
4948

5049
Configuration
@@ -60,9 +59,107 @@ All configuration variables can be overridden via `tutor config save`:
6059
- theme-1
6160
- theme-2
6261
PARAGON_SERVE_COMPILED_THEMES: true
62+
PARAGON_BUILDER_IMAGE: "paragon-builder:latest"
6363
6464
You may customize paths or theme names to suit your deployment.
6565

66+
Usage
67+
*****
68+
69+
Prerequisites
70+
-------------
71+
72+
- A built Paragon CLI image:
73+
74+
.. code-block:: bash
75+
76+
tutor images build paragon-builder
77+
78+
- The ``PARAGON_THEME_SOURCES_PATH`` directory structured as follows:
79+
80+
.. code-block:: text
81+
82+
<PARAGON_THEME_SOURCES_PATH>/
83+
├── core/
84+
│ └── ... (token files)
85+
└── themes/
86+
├── light/ # example theme variant
87+
│ └── ... (light theme token files)
88+
└── dark/ # example theme variant
89+
└── ... (dark theme token files)
90+
91+
In this structure:
92+
93+
- The ``core/`` directory contains base design tokens common across all themes.
94+
- The ``themes/`` directory contains subdirectories for each theme variant (e.g., ``light``, ``dark``), each with tokens specific to that theme.
95+
96+
Building Themes
97+
---------------
98+
99+
Invoke the build process via Tutor:
100+
101+
.. code-block:: bash
102+
103+
tutor local do paragon-build-tokens [OPTIONS]
104+
105+
Available options:
106+
107+
- ``--source-tokens-only``
108+
Include only source design tokens in the build.
109+
110+
- ``--output-token-references``
111+
Include references for tokens with aliases to other tokens in the build output.
112+
113+
- ``--themes <theme1,theme2>``
114+
Comma-separated list of theme names to compile. Defaults to the list defined in ``PARAGON_ENABLED_THEMES`` if not provided.
115+
116+
- ``-v, --verbose``
117+
Enable verbose logging.
118+
119+
Examples
120+
--------
121+
122+
.. code-block:: bash
123+
124+
# Compile all themes listed in PARAGON_ENABLED_THEMES
125+
tutor local do paragon-build-tokens
126+
127+
# Compile only specific themes
128+
tutor local do paragon-build-tokens --themes theme-1,theme-2
129+
130+
# Compile with full debug logs
131+
tutor local do paragon-build-tokens --verbose
132+
133+
# Compile only source tokens for a single theme
134+
tutor local do paragon-build-tokens --themes theme-1 --source-tokens-only
135+
136+
Output
137+
------
138+
139+
Artifacts will be written to the directory specified by ``PARAGON_COMPILED_THEMES_PATH`` (default: ``env/plugins/paragon/compiled-themes``).
140+
141+
Troubleshooting
142+
***************
143+
144+
- **No custom themes built or only default tokens generated**
145+
Ensure that your custom theme directories exist under ``PARAGON_THEME_SOURCES_PATH`` and that their names exactly match those in ``PARAGON_ENABLED_THEMES`` or passed via ``--themes``. If no custom tokens are found, Paragon will fall back to its built-in defaults.
146+
147+
- **Themes are not picked up when using --themes:**
148+
The value for ``--themes`` must be a comma-separated list (no spaces), e.g. ``--themes theme-1,theme-2``.
149+
150+
- **Write permission denied**
151+
Verify that Docker and the Tutor process have write access to the path defined by ``PARAGON_COMPILED_THEMES_PATH``. Adjust filesystem permissions if necessary.
152+
153+
- **Error: "Expected at least 4 args"**
154+
This occurs when the build job is invoked directly inside the container. Always run via Tutor:
155+
156+
.. code-block:: bash
157+
158+
tutor local do paragon-build-tokens [OPTIONS]
159+
160+
- **Other issues**
161+
Re-run the build with ``--verbose`` to obtain detailed logs and identify misconfigurations or missing files.
162+
66163
License
67164
*******
68165

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""
2+
Custom job definitions for the Paragon Tutor plugin.
3+
4+
This module contains Click command definitions that will be registered
5+
as Tutor "do commands", this specialized jobs can be executed using
6+
`tutor local do <command>` or similar.
7+
"""
8+
9+
import click
10+
11+
12+
@click.command()
13+
@click.option(
14+
"--source-tokens-only",
15+
is_flag=True,
16+
default=False,
17+
help="Include only source design tokens in the build.",
18+
)
19+
@click.option(
20+
"--output-token-references",
21+
is_flag=True,
22+
default=False,
23+
help="Include references for tokens with aliases to other tokens in the build output.",
24+
)
25+
@click.option("--themes", help="Comma-separated list of themes to build.")
26+
@click.option(
27+
"-v", "--verbose", is_flag=True, default=False, help="Enable verbose logging."
28+
)
29+
def paragon_build_tokens(
30+
source_tokens_only: bool,
31+
output_token_references: bool,
32+
themes: str,
33+
verbose: bool,
34+
) -> list[tuple[str, str]]:
35+
"""
36+
Build theme token files using Paragon.
37+
38+
Args:
39+
source_tokens_only (bool): Only source design tokens.
40+
output_token_references (bool): Output token references.
41+
themes (str): Comma-separated list of themes.
42+
verbose (bool): Verbose logging.
43+
44+
Returns:
45+
list[tuple[str, str]]: List of commands to run.
46+
"""
47+
args = []
48+
if source_tokens_only:
49+
args.append("--source-tokens-only")
50+
if output_token_references:
51+
args.append("--output-token-references")
52+
if themes:
53+
args.append("--themes")
54+
args.append(themes)
55+
if verbose:
56+
args.append("--verbose")
57+
58+
return [("paragon-builder", " ".join(args))]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Paragon Builder
2+
paragon-builder-job:
3+
image: {{ PARAGON_BUILDER_IMAGE }}
4+
environment:
5+
- PARAGON_ENABLED_THEMES={{ PARAGON_ENABLED_THEMES | join(',') }}
6+
volumes:
7+
- "./../../{{ PARAGON_THEME_SOURCES_PATH }}:/theme-sources"
8+
- "./../../{{ PARAGON_COMPILED_THEMES_PATH }}:/compiled-themes"

plugins/tutor-contrib-paragon/tutorparagon/plugin.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from tutor import config as tutor_config
88

99
from .__about__ import __version__
10+
from .commands import paragon_build_tokens
1011

1112
########################################
1213
# CONFIGURATION
@@ -27,6 +28,9 @@
2728
("PARAGON_ENABLED_THEMES", []),
2829
# Whether Tutor should expose the compiled themes to be served (e.g. via nginx, cady or static server)
2930
("PARAGON_SERVE_COMPILED_THEMES", True),
31+
# Paragon Builder Docker image
32+
# This image is used to compile themes and should be built with `tutor images build paragon-builder`
33+
("PARAGON_BUILDER_IMAGE", "paragon-builder:latest"),
3034
]
3135
)
3236

@@ -55,6 +59,22 @@ def create_paragon_folders(project_root: str) -> None:
5559
print(f"[paragon] Created {label} folder at: {path}")
5660

5761

62+
########################################
63+
# DOCKER IMAGE MANAGEMENT
64+
########################################
65+
66+
hooks.Filters.IMAGES_BUILD.add_items(
67+
[
68+
(
69+
"paragon-builder", # Image name used with 'tutor images build myservice'
70+
("plugins", "paragon", "build", "paragon-builder"), # Path to Dockerfile
71+
"{{ PARAGON_BUILDER_IMAGE }}", # Docker image tag
72+
(), # Build arguments
73+
),
74+
]
75+
)
76+
77+
5878
########################################
5979
# TEMPLATE RENDERING
6080
# (It is safe & recommended to leave
@@ -92,3 +112,10 @@ def create_paragon_folders(project_root: str) -> None:
92112
for path in glob(str(importlib_resources.files("tutorparagon") / "patches" / "*")):
93113
with open(path, encoding="utf-8") as patch_file:
94114
hooks.Filters.ENV_PATCHES.add_item((os.path.basename(path), patch_file.read()))
115+
116+
117+
########################################
118+
# CUSTOM JOBS (a.k.a. "do-commands")
119+
########################################
120+
121+
hooks.Filters.CLI_DO_COMMANDS.add_item(paragon_build_tokens)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM node:20-alpine
2+
3+
WORKDIR /paragon-builder
4+
5+
COPY entrypoint.sh ./entrypoint.sh
6+
COPY package*.json ./
7+
8+
RUN npm ci && \
9+
chmod +x ./entrypoint.sh
10+
11+
ENTRYPOINT ["sh", "./entrypoint.sh"]
12+
13+
CMD []
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#!/bin/sh
2+
set -e
3+
4+
SOURCE_DIR="/theme-sources" # Volume mounted by Paragon plugin
5+
FINAL_BUILD_DIR="/compiled-themes" # Volume mounted by Paragon plugin
6+
TMP_BUILD_DIR="$(mktemp -d /tmp/paragon-build.XXXXXX)"
7+
8+
9+
has_themes() {
10+
# This function checks if the --themes flag is present in the arguments
11+
# Returns 0 if --themes is found, 1 otherwise
12+
# Usage: has_themes "$@"
13+
14+
for arg in "$@"; do
15+
[ "$arg" = "--themes" ] && return 0
16+
done
17+
return 1
18+
}
19+
20+
parse_args() {
21+
# This function parses arguments injected by Tutor.
22+
# Tutor runs jobs via `sh -e -c '…'`, reserving $1–$3 for its wrapper and
23+
# bundling all user flags into $4. The function then extracts the raw flags
24+
# string from $4, shifts off the wrapper args, and verifies—or appends—the
25+
# selected themes definition.
26+
27+
if [ "$#" -lt 4 ]; then
28+
echo "Error: Expected at least 4 args, got $#. Must run via tutor."
29+
exit 1
30+
fi
31+
32+
shift 3
33+
34+
if ! has_themes "$@"; then
35+
if [ -n "${PARAGON_ENABLED_THEMES:-}" ]; then
36+
echo "Using PARAGON_ENABLED_THEMES: ${PARAGON_ENABLED_THEMES}"
37+
set -- "$@" --themes "${PARAGON_ENABLED_THEMES}"
38+
fi
39+
fi
40+
41+
printf '%s\n' "$@"
42+
}
43+
44+
set -- $(parse_args "$@")
45+
46+
# Executes the Paragon CLI to build themes.
47+
# It uses a temporary directory to avoid volume permissions issues
48+
npx paragon build-tokens \
49+
--source "$SOURCE_DIR" \
50+
--build-dir "$TMP_BUILD_DIR" \
51+
"$@"
52+
53+
# Moves the built themes to the final volume directory.
54+
mkdir -p "$FINAL_BUILD_DIR"
55+
cp -a "$TMP_BUILD_DIR/." "$FINAL_BUILD_DIR/"
56+
57+
# Clean up
58+
rm -rf "$TMP_BUILD_DIR"
59+
60+
exit 0

0 commit comments

Comments
 (0)