fune/toolkit/content/widgets/mach_commands.py
Tim Giles 5814664326 Bug 1888952 - Add 'buildtokens' mach command. r=reusable-components-reviewers,mach-reviewers,hjones
This new command will make it easier to work with the JSON source of
truth for our design tokens. Unfortunately the noisy output when
building the tokens is expected. We can fix this issue by upgrading
to Style Dictionary v4 when it is available and changing the logging
level.[1]

[1] https://bugzilla.mozilla.org/show_bug.cgi?id=1889645

Differential Revision: https://phabricator.services.mozilla.com/D206561
2024-04-05 17:43:11 +00:00

221 lines
6.8 KiB
Python

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import os
import re
from mach.decorators import Command, CommandArgument
FIXME_COMMENT = "// FIXME: replace with path to your reusable widget\n"
LICENSE_HEADER = """/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"""
JS_HEADER = """{license}
import {{ html }} from "../vendor/lit.all.mjs";
import {{ MozLitElement }} from "../lit-utils.mjs";
/**
* Component description goes here.
*
* @tagname {element_name}
* @property {{string}} variant - Property description goes here
*/
export default class {class_name} extends MozLitElement {{
static properties = {{
variant: {{ type: String }},
}};
constructor() {{
super();
this.variant = "default";
}}
render() {{
return html`
<link rel="stylesheet" href="chrome://global/content/elements/{element_name}.css" />
<div>Variant type: ${{this.variant}}</div>
`;
}}
}}
customElements.define("{element_name}", {class_name});
"""
STORY_HEADER = """{license}
{html_lit_import}
// eslint-disable-next-line import/no-unassigned-import
{fixme_comment}import "{element_path}";
export default {{
title: "{story_prefix}/{story_name}",
component: "{element_name}",
argTypes: {{
variant: {{
options: ["default", "other"],
control: {{ type: "select" }},
}},
}},
}};
const Template = ({{ variant }}) => html`
<{element_name} .variant=${{variant}}></{element_name}>
`;
export const Default = Template.bind({{}});
Default.args = {{
variant: "default",
}};
"""
def run_mach(command_context, cmd, **kwargs):
return command_context._mach_context.commands.dispatch(
cmd, command_context._mach_context, **kwargs
)
def run_npm(command_context, args):
return run_mach(
command_context, "npm", args=[*args, "--prefix=browser/components/storybook"]
)
@Command(
"addwidget",
category="misc",
description="Scaffold a front-end component.",
)
@CommandArgument(
"names",
nargs="+",
help="Component names to create in kebab-case, eg. my-card.",
)
def addwidget(command_context, names):
story_prefix = "UI Widgets"
html_lit_import = 'import { html } from "../vendor/lit.all.mjs";'
for name in names:
component_dir = "toolkit/content/widgets/{0}".format(name)
try:
os.mkdir(component_dir)
except FileExistsError:
pass
with open("{0}/{1}.mjs".format(component_dir, name), "w", newline="\n") as f:
class_name = "".join(p.capitalize() for p in name.split("-"))
f.write(
JS_HEADER.format(
license=LICENSE_HEADER,
element_name=name,
class_name=class_name,
)
)
with open("{0}/{1}.css".format(component_dir, name), "w", newline="\n") as f:
f.write(LICENSE_HEADER)
test_name = name.replace("-", "_")
test_path = "toolkit/content/tests/widgets/test_{0}.html".format(test_name)
jar_path = "toolkit/content/jar.mn"
jar_lines = None
with open(jar_path, "r") as f:
jar_lines = f.readlines()
elements_startswith = " content/global/elements/"
new_css_line = "{0}{1}.css (widgets/{1}/{1}.css)\n".format(
elements_startswith, name
)
new_js_line = "{0}{1}.mjs (widgets/{1}/{1}.mjs)\n".format(
elements_startswith, name
)
new_jar_lines = []
found_elements_section = False
added_widget = False
for line in jar_lines:
if line.startswith(elements_startswith):
found_elements_section = True
if found_elements_section and not added_widget and line > new_css_line:
added_widget = True
new_jar_lines.append(new_css_line)
new_jar_lines.append(new_js_line)
new_jar_lines.append(line)
with open(jar_path, "w", newline="\n") as f:
f.write("".join(new_jar_lines))
story_path = "{0}/{1}.stories.mjs".format(component_dir, name)
element_path = "./{0}.mjs".format(name)
with open(story_path, "w", newline="\n") as f:
story_name = " ".join(
name for name in re.findall(r"[A-Z][a-z]+", class_name) if name != "Moz"
)
f.write(
STORY_HEADER.format(
license=LICENSE_HEADER,
element_name=name,
story_name=story_name,
story_prefix=story_prefix,
fixme_comment="",
element_path=element_path,
html_lit_import=html_lit_import,
)
)
run_mach(
command_context, "addtest", argv=[test_path, "--suite", "mochitest-chrome"]
)
@Command(
"addstory",
category="misc",
description="Scaffold a front-end Storybook story.",
)
@CommandArgument(
"name",
help="Story to create in kebab-case, eg. my-card.",
)
@CommandArgument(
"project_name",
type=str,
help='Name of the project or team for the new component to keep stories organized. Eg. "Credential Management"',
)
@CommandArgument(
"--path",
help="Path to the widget source, eg. /browser/components/my-module.mjs or chrome://browser/content/my-module.mjs",
)
def addstory(command_context, name, project_name, path):
html_lit_import = 'import { html } from "lit.all.mjs";'
story_path = "browser/components/storybook/stories/{0}.stories.mjs".format(name)
project_name = project_name.split()
project_name = " ".join(p.capitalize() for p in project_name)
story_prefix = "Domain-specific UI Widgets/{0}".format(project_name)
with open(story_path, "w", newline="\n") as f:
print(f"Creating new story {name} in {story_path}")
story_name = " ".join(p.capitalize() for p in name.split("-"))
f.write(
STORY_HEADER.format(
license=LICENSE_HEADER,
element_name=name,
story_name=story_name,
element_path=path,
fixme_comment="" if path else FIXME_COMMENT,
project_name=project_name,
story_prefix=story_prefix,
html_lit_import=html_lit_import,
)
)
@Command(
"buildtokens",
category="misc",
description="Build the design tokens CSS files",
)
def buildtokens(command_context):
run_mach(
command_context,
"npm",
args=["run", "build", "--prefix=toolkit/themes/shared/design-system"],
)