Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 140 additions & 14 deletions dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,20 @@
from pathlib import Path

THIS_DIRECTORY = Path(__file__).parent.absolute()
EXAMPLE_DIRECTORIES = [d for d in (THIS_DIRECTORY / "examples").iterdir() if d.is_dir()]
VERSIONS = ["v1", "v2"]
EXAMPLE_DIRECTORIES = [
d
for v in VERSIONS
if (THIS_DIRECTORY / "examples" / v).is_dir()
for d in (THIS_DIRECTORY / "examples" / v).iterdir()
if d.is_dir()
]
TEMPLATE_DIRECTORIES = [
THIS_DIRECTORY / "template",
THIS_DIRECTORY / "template-reactless",
d
for v in VERSIONS
if (THIS_DIRECTORY / "templates" / v).is_dir()
for d in (THIS_DIRECTORY / "templates" / v).iterdir()
if d.is_dir()
]


Expand All @@ -35,6 +45,97 @@ def run_verbose(cmd_args, *args, **kwargs):
subprocess.run(cmd_args, *args, **kwargs)


def create_sanitized_copies_excluding_gitignored(
source_dir_a: Path,
source_dir_b: Path,
repo_root: Path,
destination_base_dir: Path,
) -> typing.Tuple[Path, Path]:
"""
Create sanitized copies of two directories while excluding files ignored by Git.

This function is intended for robust, cross-platform directory comparisons (e.g.,
git diff --no-index) where we want to ignore anything matched by the repository's
ignore rules (.gitignore, global and excludesfile), but still compare the remaining
content 1:1.

Rationale:
- git diff --no-index does not honor .gitignore rules directly for arbitrary
directory arguments.
- Filtering via Git pathspecs is not supported with --no-index in a portable way.
- By asking Git for the ignored files under source_dir_b (the path inside the
repo) and excluding those relative paths from BOTH copies, we avoid false
positives from OS metadata and any user-ignored files.

Parameters:
- source_dir_a: First directory to compare (e.g., freshly rendered output).
- source_dir_b: Second directory to compare (must be within repo_root).
- repo_root: Root of the Git repository (used to query ignored files).
- destination_base_dir: Directory where sanitized copies will be created.

Returns:
- Tuple[Path, Path]: (sanitized_copy_of_a, sanitized_copy_of_b)
"""
# Compute the list of ignored files under source_dir_b using Git
try:
rel_path_in_repo = source_dir_b.relative_to(repo_root)
ls_cmd = [
"git",
"-C",
str(repo_root),
"ls-files",
"-i", # list ignored files
"--exclude-standard", # honor .gitignore, global ignores, etc.
"--others", # show untracked files as well
"-z", # null-terminate paths to be robust to special characters
"--",
str(rel_path_in_repo),
]
result = subprocess.run(ls_cmd, check=True, stdout=subprocess.PIPE, text=False)
ignored_absolute_paths = [
repo_root / p.decode() for p in result.stdout.split(b"\0") if p
]
except (ValueError, subprocess.CalledProcessError):
# ValueError if source_dir_b is not under repo_root; fallback to no ignores
ignored_absolute_paths = []

ignored_relative_to_b = set(
str(p.relative_to(source_dir_b).as_posix())
for p in ignored_absolute_paths
if hasattr(p, "is_relative_to")
and p.is_relative_to(source_dir_b)
or (str(p).startswith(str(source_dir_b) + os.sep))
)

def build_ignore_filter(source_root: Path):
def _ignore(dirpath: str, names: typing.List[str]) -> typing.List[str]:
rel_dir = Path(dirpath).relative_to(source_root)
to_ignore: typing.List[str] = []
for name in names:
candidate = (rel_dir / name).as_posix()
if candidate in ignored_relative_to_b:
to_ignore.append(name)
return to_ignore

return _ignore

sanitized_a = destination_base_dir / "sanitized-output"
sanitized_b = destination_base_dir / "sanitized-repo"

shutil.copytree(
source_dir_a,
sanitized_a,
ignore=build_ignore_filter(source_dir_a),
)
shutil.copytree(
source_dir_b,
sanitized_b,
ignore=build_ignore_filter(source_dir_b),
)

return sanitized_a, sanitized_b


# Commands
def cmd_all_npm_install(args):
"""Install all node dependencies for all examples"""
Expand Down Expand Up @@ -134,10 +235,17 @@ def cmd_all_python_build_package(args):
final_dist_directory = THIS_DIRECTORY / "dist"
final_dist_directory.mkdir(exist_ok=True)
for project_dir in EXAMPLE_DIRECTORIES + TEMPLATE_DIRECTORIES:
run_verbose(
[sys.executable, "setup.py", "bdist_wheel", "--universal", "sdist"],
cwd=str(project_dir),
)
pyproject_file = project_dir / "pyproject.toml"
if pyproject_file.exists():
run_verbose(
[sys.executable, "-m", "build", "--wheel", "--sdist"],
cwd=str(project_dir),
)
else:
run_verbose(
[sys.executable, "setup.py", "bdist_wheel", "--universal", "sdist"],
cwd=str(project_dir),
)

wheel_file = next(project_dir.glob("dist/*.whl"))
shutil.copy(wheel_file, final_dist_directory)
Expand Down Expand Up @@ -172,7 +280,13 @@ def cmd_example_check_deps(args):
"""Checks that dependencies of examples match the template"""
template_deps = json.loads(
(
THIS_DIRECTORY / "template" / "my_component" / "frontend" / "package.json"
THIS_DIRECTORY
/ "templates"
/ "v1"
/ "template"
/ "my_component"
/ "frontend"
/ "package.json"
).read_text()
)
examples_package_jsons = sorted(
Expand Down Expand Up @@ -220,19 +334,22 @@ def cmd_check_test_utils(args):
class CookiecutterVariant(typing.NamedTuple):
replay_file: Path
repo_directory: Path
cookiecutter_dir: Path


COOKIECUTTER_VARIANTS = [
CookiecutterVariant(
replay_file=THIS_DIRECTORY / ".github" / "replay-files" / "template.json",
repo_directory=THIS_DIRECTORY / "template",
repo_directory=THIS_DIRECTORY / "templates" / "v1" / "template",
cookiecutter_dir=THIS_DIRECTORY / "cookiecutter" / "v1",
),
CookiecutterVariant(
replay_file=THIS_DIRECTORY
/ ".github"
/ "replay-files"
/ "template-reactless.json",
repo_directory=THIS_DIRECTORY / "template-reactless",
repo_directory=THIS_DIRECTORY / "templates" / "v1" / "template-reactless",
cookiecutter_dir=THIS_DIRECTORY / "cookiecutter" / "v1",
),
]

Expand All @@ -256,7 +373,7 @@ def cmd_check_templates_using_cookiecutter(args):
str(cookiecutter_variant.replay_file),
"--output-dir",
str(output_dir),
str(THIS_DIRECTORY / "cookiecutter" / "v1"),
str(cookiecutter_variant.cookiecutter_dir),
]
)
try:
Expand All @@ -267,14 +384,23 @@ def cmd_check_templates_using_cookiecutter(args):
Path(output_dir)
/ replay_file_content["cookiecutter"]["package_name"]
)
# Create sanitized copies excluding all files ignored by git in the repo
sanitized_output, sanitized_repo = (
create_sanitized_copies_excluding_gitignored(
output_template,
cookiecutter_variant.repo_directory,
THIS_DIRECTORY,
Path(output_dir),
)
)
run_verbose(
[
"git",
"--no-pager",
"diff",
"--no-index",
str(output_template),
str(cookiecutter_variant.repo_directory),
str(sanitized_output),
str(sanitized_repo),
]
)
except subprocess.CalledProcessError:
Expand Down Expand Up @@ -311,7 +437,7 @@ def cmd_update_templates(args):
str(cookiecutter_variant.replay_file),
"--output-dir",
str(output_dir),
str(THIS_DIRECTORY / "cookiecutter" / "v1"),
str(cookiecutter_variant.cookiecutter_dir),
]
)
print(
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Empty file added templates/v2/.gitkeep
Empty file.