Skip to content

Enable requiring zipped modules ENG-168 - #287

Merged
NejcS merged 1 commit into
mainfrom
feat/require_zipped_modules
Aug 21, 2026
Merged

Enable requiring zipped modules ENG-168#287
NejcS merged 1 commit into
mainfrom
feat/require_zipped_modules

Conversation

@NejcS

@NejcS NejcS commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Purpose

Let a rendered module's build output be distributed and consumed as a single <module>.module zip archive instead of an unpacked plain_modules/<module>/ folder.

Changes and design decisions

  • PlainModule.materialize() extracts an archive-only module to a scratch temp dir for read-only consumption (the archive is kept and no folder is created); ensure_module_unpacked() unpacks in place and deletes the archive when a module is (re)rendered. Reading from scratch honors "don't unzip" for consumption; unpack-in-place happens only on change.
  • Eager materialize() over the whole module tree at render start, with scratch cleanup in main()'s finally. Chosen over a lazy side-effecting property so timing is deterministic (before change detection and before RenderContext freezes the build path) and cleanup is reliable on every exit path.
  • ConformanceTests resolves each module's tests folder through an injected resolver, so archived required modules resolve to their scratch extraction.
  • conformance_tests.json stores folder_name relative to the module's tests folder (resolved to absolute on read). This is scratch-safe and portable.
  • The module code hash is content-only (the absolute build-folder path was dropped), so it is stable across directories and machines.
  • tests/ is optional in an archive. A module rendered without a conformance-tests script has no tests/ folder, so its .module has none either. Validation requires only code/ (with .git); tests/ is validated only when present. When conformance testing IS enabled but a consumed archive has no tests/, the module cannot be used as-is: it surfaces a missing_conformance_tests render-state reason (a hard blocker that takes precedence over spec/code changes), and the user is warned and offered quit / rerender-that-module. On rerender the module rebuilds (code + tests) and its .module is removed; on quit the archive is left untouched.
  • Archive validation: flat layout, code/ (and tests/ when present) with .git, zip-slip guard, exec-bit restore. Adds InvalidModuleArchiveError.

Breaking changes

The module code-hash format changed to content-only. Existing multi-module projects see a one-time "required module code changed" prompt on their next render, and any .module archives created before this change must be regenerated (their stored hashes embed the old absolute path).

Testing

  • Full unit and integration suite: materialize / unpack / wipe, invalid archives (non-zip, missing code/, missing .git, zip-slip), folder-vs-archive parity, relative-path round-trip, clone from an archived required module, location-independent code hash, code-only (tests-less) archive validate / materialize / unpack, and detection + render-choice for a tests-less archive when conformance is enabled.
  • Manually verified end to end: rendered the react hello-world example from zipped required modules; distributed the .module files to a deeper directory and rendered successfully; changed the spec of a zipped module and confirmed correct render choices.

@gorandodig
gorandodig requested a review from VitjanZ August 18, 2026 08:21
Comment thread plain_modules.py Outdated
repo_path = os.path.join(root, subfolder)
if not os.path.isdir(repo_path):
raise InvalidModuleArchiveError(
f"Module archive '{archive_path}' is missing the '{subfolder}/' folder at its root. "

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we require a MODULE_TESTS_SUBFOLDER to exist. This might be an issue in the case where we want to make a .module file when rendering something that has conformance tests turned off (by not supplying a conformance test script).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I added a function archive_has_conformance_tests in plain_modules that checks if the .module file has tests. If it doesn't but the config file enables conformance testing, the user gets presented two options in the TUI: quit or rerender the module and delete the .module file.

Comment thread plain_modules.py Outdated
shutil.rmtree(self.module_folder)
if os.path.isdir(self._default_module_folder):
console.warning(f"Wiping module {self._default_module_folder}...")
shutil.rmtree(self._default_module_folder, ignore_errors=True)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really expecting shutil.rmtree to fail too often but if it does it now fails silently. It would just say "wiping module" and then nothing if it failed which might not be ideal.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. That's definitely not optimal.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use the file_utils anyway. Fixed this.

serializable: dict = {}
for frid, entry in conformance_tests_json.items():
if isinstance(entry, dict) and "folder_name" in entry:
entry = {**entry, "folder_name": os.path.relpath(entry["folder_name"], base)}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to support transferring of modules from Windows to Linux? Just a thought but operating with os.path.relpath and other OS-reliant operations could be an issue and should be tested to make sure the handling of things like different separators works.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's not a requirement at the moment to support transferring of modules from Windows to Linux.

@VitjanZ VitjanZ left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put some minor things as comments so you can consider them. Otherwise I tested it and seems to work fine. Also ran the branch on the benchmark to see if standard examples pass without issues and they do.

@NejcS

NejcS commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

I put some minor things as comments so you can consider them. Otherwise I tested it and seems to work fine. Also ran the branch on the benchmark to see if standard examples pass without issues and they do.

@VitjanZ Thanks for the benchmark run. I need to get into the habit of doing that.

I made two changes:

  • prevent consuming testless .module files when conf testing is enabled
  • use file_utils.delete instead of shutil.rmtree

@NejcS
NejcS force-pushed the feat/require_zipped_modules branch from 96e1f69 to c8de6aa Compare August 21, 2026 08:51
@NejcS
NejcS merged commit b710c29 into main Aug 21, 2026
10 checks passed
@NejcS
NejcS deleted the feat/require_zipped_modules branch August 21, 2026 08:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants