diff --git a/.github/workflows/ci_run.yml b/.github/workflows/ci_run.yml index e1d8a83d..54b66960 100644 --- a/.github/workflows/ci_run.yml +++ b/.github/workflows/ci_run.yml @@ -122,7 +122,7 @@ jobs: rm -rf target/ df -h - # Dependency profile resolution queries npm release metadata. + # Dependency profile resolution queries Git refs and npm release metadata. - name: "EXEC: {Setup Node.js}, independent" uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: diff --git a/ci/README.md b/ci/README.md index 6b5821f6..210a6f6d 100644 --- a/ci/README.md +++ b/ci/README.md @@ -47,8 +47,8 @@ exist unless added there. Top-level component fields: - `repository`: Git repository URL. -- `npm_package`: npm package name, for components that are resolved through npm - metadata. +- `npm_package`: npm package name, for components resolved through npm metadata + or pkg.pr.new previews. - `default`, `stability`, `frontier`: component profile selections. Profile selections always have a `strategy`. Some strategies require additional @@ -166,6 +166,23 @@ For `synapse-sdk`, npm resolution also records exact compatible versions of `@filoz/synapse-core` and the `viem` peer dependency. The scenario installs that set into a temporary consumer project. +### `pkg_pr_new` + +Resolve a branch head to an immutable commit, then install the packages built +for that commit by pkg.pr.new: + +```json +{ + "strategy": "pkg_pr_new", + "branch": "master" +} +``` + +This strategy is used for Synapse frontier runs. The resolver records the exact +commit and constructs commit-pinned preview URLs for `@filoz/synapse-sdk` and +`@filoz/synapse-core`. The scenario installs those built packages in the same +temporary npm consumer used for released versions. + ## Overrides Some profile selections can include an optional `overrides` object. Each entry @@ -192,7 +209,7 @@ override is applied. The resolver does not infer overrides from package metadata Overrides are currently allowed only for npm-installed `synapse-sdk` and `filecoin-pin` selections. They are written to the temporary consumer -`package.json`; source profiles use the checkout's committed lockfile. +`package.json`. Current consumers write npm overrides to the temporary `package.json` used by their scenario. @@ -206,7 +223,7 @@ Installation currently lives in three places (which consume the resolved metadata): - `foc-devnet init`: Lotus, Curio, filecoin-services, and optionally PDP. -- `scenarios/synapse_runtime.py`: published or source Synapse scenario runtime. +- `scenarios/synapse_runtime.py`: released or preview Synapse packages. - `scenarios/test_multi_copy_upload.py`: filecoin-pin scenario dependency. This split is intentional, but it is not necessarily the final shape. Resolution diff --git a/ci/dependency-profiles.json b/ci/dependency-profiles.json index ef01dc06..33b67fb4 100644 --- a/ci/dependency-profiles.json +++ b/ci/dependency-profiles.json @@ -106,7 +106,7 @@ "version": "latest" }, "frontier": { - "strategy": "git_branch", + "strategy": "pkg_pr_new", "branch": "master" } }, diff --git a/contracts/MockUSDFC/src/MockUSDFC.sol b/contracts/MockUSDFC/src/MockUSDFC.sol index 03bd08d5..e4b1334c 100644 --- a/contracts/MockUSDFC/src/MockUSDFC.sol +++ b/contracts/MockUSDFC/src/MockUSDFC.sol @@ -2,19 +2,20 @@ pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; /** * @title MockUSDFC * @dev Mock USDC token for testing FOC warm storage services */ -contract MockUSDFC is ERC20 { +contract MockUSDFC is ERC20, ERC20Permit { uint8 private _decimals; /** * @dev Constructor that gives msg.sender all of initial supply. * @param initialSupply The initial supply of tokens (in wei, accounting for decimals) */ - constructor(uint256 initialSupply) ERC20("Mock USDC", "USDFC") { + constructor(uint256 initialSupply) ERC20("Mock USDC", "USDFC") ERC20Permit("Mock USDC") { _decimals = 18; _mint(msg.sender, initialSupply); } @@ -26,6 +27,10 @@ contract MockUSDFC is ERC20 { return _decimals; } + function version() external pure returns (string memory) { + return "1"; + } + /** * @dev Mint new tokens (for testing purposes) * @param to Address to receive the tokens diff --git a/contracts/MockUSDFC/test/MockUSDFC.t.sol b/contracts/MockUSDFC/test/MockUSDFC.t.sol new file mode 100644 index 00000000..3d08b1ec --- /dev/null +++ b/contracts/MockUSDFC/test/MockUSDFC.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import "../src/MockUSDFC.sol"; + +contract MockUSDFCTest is Test { + bytes32 private constant PERMIT_TYPEHASH = + keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + + MockUSDFC private token; + uint256 private ownerKey; + address private owner; + + function setUp() public { + token = new MockUSDFC(0); + ownerKey = 0xA11CE; + owner = vm.addr(ownerKey); + token.mint(owner, 100 ether); + } + + function testPermit() public { + address spender = makeAddr("spender"); + uint256 value = 25 ether; + uint256 deadline = block.timestamp + 1 hours; + bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, 0, deadline)); + bytes32 digest = keccak256(abi.encodePacked("\x19\x01", token.DOMAIN_SEPARATOR(), structHash)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerKey, digest); + + token.permit(owner, spender, value, deadline, v, r, s); + + assertEq(token.allowance(owner, spender), value); + assertEq(token.nonces(owner), 1); + assertEq(token.version(), "1"); + } +} diff --git a/scenarios/synapse-e2e/source-runtime.mjs b/scenarios/synapse-e2e/source-runtime.mjs deleted file mode 100644 index b34bd543..00000000 --- a/scenarios/synapse-e2e/source-runtime.mjs +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Runs foc-devnet scenarios against Synapse TypeScript source on Node 24+. - * - * Source profiles install Synapse's production dependency closure, then preload - * this module with `node --import`. Public `@filoz/synapse-sdk` and - * `@filoz/synapse-core` imports resolve to their source counterparts instead of - * the packages' compiled `dist` targets. All other imports use Node's normal - * resolver. - * - * Mappings come from each package's export map, keeping the scenario on the - * public API and avoiding a hard-coded list that drifts as exports change. - * Wildcard exports are deliberately excluded because one wildcard can expose - * paths with no TypeScript source equivalent. - */ -import assert from 'node:assert/strict' -import { existsSync, readFileSync } from 'node:fs' -import { registerHooks } from 'node:module' -import { dirname, join, resolve } from 'node:path' -import { pathToFileURL } from 'node:url' - -const sourceRoot = process.env.SYNAPSE_SDK_SOURCE_DIR -assert(sourceRoot, 'SYNAPSE_SDK_SOURCE_DIR must name the Synapse checkout when using source-runtime.mjs') - -// Export entries may be strings or nested condition objects. Prefer the -// conditions used by this ESM runtime, then inspect package-specific branches. -function exportedTarget(entry) { - if (typeof entry === 'string') return entry - if (entry == null || typeof entry !== 'object') return undefined - for (const condition of ['node', 'import', 'default']) { - const target = exportedTarget(entry[condition]) - if (target != null) return target - } - for (const target of Object.values(entry)) { - const resolved = exportedTarget(target) - if (resolved != null) return resolved - } - return undefined -} - -// Convert concrete public dist exports to source files only when the matching -// TypeScript file exists. Dependencies and private paths remain untouched. -function sourceExports(packageName, packageDirectory) { - const packagePath = join(sourceRoot, packageDirectory, 'package.json') - const packageJson = JSON.parse(readFileSync(packagePath, 'utf8')) - const mappings = new Map() - for (const [subpath, entry] of Object.entries(packageJson.exports ?? {})) { - if (subpath.includes('*')) continue - const target = exportedTarget(entry) - if (target == null) continue - const sourcePath = resolve( - dirname(packagePath), - target.replace(/^\.\/dist\/src\//, './src/').replace(/\.js$/, '.ts') - ) - if (!existsSync(sourcePath)) continue - mappings.set(subpath === '.' ? packageName : `${packageName}/${subpath.slice(2)}`, pathToFileURL(sourcePath).href) - } - return mappings -} - -const sourceMappings = new Map([ - ...sourceExports('@filoz/synapse-sdk', 'packages/synapse-sdk'), - ...sourceExports('@filoz/synapse-core', 'packages/synapse-core'), -]) - -// Short-circuit exact public package matches; delegate everything else. -registerHooks({ - resolve(specifier, context, nextResolve) { - const sourceUrl = sourceMappings.get(specifier) - return sourceUrl == null ? nextResolve(specifier, context) : { url: sourceUrl, shortCircuit: true } - }, -}) diff --git a/scenarios/synapse_runtime.py b/scenarios/synapse_runtime.py index 585dd156..a01caafc 100755 --- a/scenarios/synapse_runtime.py +++ b/scenarios/synapse_runtime.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Prepare and run the Synapse E2E consumer or source runtime.""" +"""Prepare and run the Synapse E2E consumer runtime.""" from __future__ import annotations @@ -16,7 +16,6 @@ STATE_FORK_ERROR = "refusing explicit call due to state fork at epoch" UPLOAD_RETRY_DELAYS_SECS = (5, 10, 15, 30) -RUNTIME_MARKER = ".synapse-runtime.json" @dataclass(frozen=True) @@ -24,17 +23,12 @@ class SynapseRuntime: work_dir: Path source: str provenance: str - source_dir: Path | None = None def _scenario_dir() -> Path: return Path(__file__).with_name("synapse-e2e") -def _runtime_marker(work_dir: Path) -> Path: - return work_dir / RUNTIME_MARKER - - def _npm_view(package: str, version: str, *fields: str): result = subprocess.run( ["npm", "view", f"{package}@{version}", *fields, "--json"], @@ -117,148 +111,36 @@ def _copy_scenarios(work_dir: Path) -> None: shutil.copytree(source, work_dir, dirs_exist_ok=True) -def _source_pnpm_version(source_dir: Path) -> str: - package_manager = json.loads((source_dir / "package.json").read_text()).get( - "packageManager" - ) - if not isinstance(package_manager, str) or not package_manager.startswith("pnpm@"): - raise RuntimeError(f"Synapse source has no declared pnpm version: {source_dir}") - return package_manager.removeprefix("pnpm@") - - -def _source_commit(source_dir: Path) -> str: - result = subprocess.run( - ["git", "-C", str(source_dir), "rev-parse", "HEAD"], - text=True, - capture_output=True, - ) - if result.returncode: - raise RuntimeError(f"Cannot determine Synapse source commit at {source_dir}") - return result.stdout.strip() - - -def _load_runtime(work_dir: Path) -> SynapseRuntime: - marker = _runtime_marker(work_dir) - if not marker.is_file(): - raise RuntimeError(f"SYNAPSE_RUNTIME_DIR is not a prepared runtime: {work_dir}") - data = json.loads(marker.read_text()) - source_dir = data.get("source_dir") - return SynapseRuntime( - work_dir=work_dir, - source=data["source"], - provenance=data["provenance"], - source_dir=Path(source_dir) if source_dir else None, - ) - - def prepare_synapse_runtime(work_dir: Path) -> SynapseRuntime: - """Prepare one reusable runtime and return its executable scenario location.""" - reused_dir = os.environ.get("SYNAPSE_RUNTIME_DIR") - if reused_dir: - return _load_runtime(Path(reused_dir).resolve()) - + """Install Synapse into a temporary consumer project.""" work_dir = work_dir.resolve() work_dir.mkdir(parents=True, exist_ok=True) dependency = component("synapse-sdk") - local_source = os.environ.get("SYNAPSE_SDK_SOURCE_DIR") - - if local_source or dependency.get("source") != "npm": - source_dir = ( - Path(local_source).resolve() if local_source else work_dir / "synapse-sdk" - ) - if local_source: - if not source_dir.is_dir(): - raise RuntimeError( - f"SYNAPSE_SDK_SOURCE_DIR is not a directory: {source_dir}" - ) - provenance = f"local:{source_dir}@{_source_commit(source_dir)}" - else: - checkout = dependency.get("commit") or dependency.get("ref") - if not checkout: - raise RuntimeError("resolved Synapse source has no commit or ref") - if not run_cmd( - ["git", "clone", dependency["repository"], str(source_dir)], - label="clone synapse-sdk", - ): - raise RuntimeError("failed to clone synapse-sdk") - if not run_cmd( - ["git", "checkout", "--detach", checkout], - cwd=str(source_dir), - label=f"checkout synapse-sdk {checkout}", - ): - raise RuntimeError("failed to checkout synapse-sdk") - actual_commit = _source_commit(source_dir) - expected_commit = dependency.get("commit") - if expected_commit and actual_commit != expected_commit: - raise RuntimeError( - f"synapse-sdk checkout is {actual_commit}, expected {expected_commit}" - ) - provenance = f"git:{dependency['repository']}@{actual_commit}" + source = dependency.get("source") + if source not in {"npm", "pkg_pr_new"}: + raise RuntimeError(f"Unsupported Synapse package source: {source!r}") - pnpm_version = _source_pnpm_version(source_dir) - source_node_modules = source_dir / "packages" / "synapse-sdk" / "node_modules" - has_runtime_closure = (source_node_modules / "viem").is_dir() and ( - source_node_modules / "@filoz" / "synapse-core" - ).is_dir() - if not local_source or not has_runtime_closure: - if not run_cmd( - [ - "pnpm", - "install", - "--frozen-lockfile", - "--prod", - "--ignore-scripts", - "--filter", - "@filoz/synapse-sdk...", - ], - cwd=str(source_dir), - label=f"install Synapse production runtime (pnpm@{pnpm_version})", - ): - raise RuntimeError("failed to install Synapse production runtime") - if not source_node_modules.is_dir(): - raise RuntimeError( - f"Synapse production install has no SDK node_modules: {source_node_modules}" - ) - runtime_node_modules = work_dir / "node_modules" - if runtime_node_modules.exists() or runtime_node_modules.is_symlink(): - raise RuntimeError( - f"Synapse runtime node_modules already exists: {runtime_node_modules}" - ) - runtime_node_modules.symlink_to(source_node_modules, target_is_directory=True) - provenance = f"{provenance} (pnpm@{pnpm_version})" - runtime = SynapseRuntime(work_dir, "source", provenance, source_dir) - else: - _write_manifest(work_dir, dependency) - if not run_cmd( - [ - "npm", - "install", - "--omit=dev", - "--ignore-scripts", - "--package-lock=false", - ], - cwd=str(work_dir), - label="install Synapse consumer runtime", - ): - raise RuntimeError("failed to install Synapse consumer runtime") - runtime = SynapseRuntime( - work_dir, + _write_manifest(work_dir, dependency) + if not run_cmd( + [ "npm", - f"npm:{dependency['package']}@{dependency['version']}", - ) - - _copy_scenarios(work_dir) - _runtime_marker(work_dir).write_text( - json.dumps( - { - "source": runtime.source, - "provenance": runtime.provenance, - "source_dir": str(runtime.source_dir) if runtime.source_dir else None, - }, - indent=2, - ) - + "\n" + "install", + "--omit=dev", + "--ignore-scripts", + "--package-lock=false", + ], + cwd=str(work_dir), + label="install Synapse consumer runtime", + ): + raise RuntimeError("failed to install Synapse consumer runtime") + + identifier = dependency.get("commit") or dependency["version"] + runtime = SynapseRuntime( + work_dir, + source, + f"{source}:{dependency['package']}@{identifier}", ) + _copy_scenarios(work_dir) info(f"Synapse runtime: {runtime.provenance}") return runtime @@ -276,15 +158,8 @@ def run_node_script( if not script.is_file(): raise RuntimeError(f"Synapse scenario entrypoint not found: {script}") - cmd = ["node"] + cmd = ["node", str(script), *(args or [])] process_env = {**os.environ, **(env or {})} - if runtime.source_dir: - loader = runtime.work_dir / "source-runtime.mjs" - if not loader.is_file(): - raise RuntimeError(f"Synapse source runtime hook not found: {loader}") - cmd.extend(["--import", str(loader)]) - process_env["SYNAPSE_SDK_SOURCE_DIR"] = str(runtime.source_dir) - cmd.extend([str(script), *(args or [])]) max_attempts = len(UPLOAD_RETRY_DELAYS_SECS) + 1 for attempt in range(1, max_attempts + 1): diff --git a/scripts/resolve-ci-dependencies.py b/scripts/resolve-ci-dependencies.py index c9d2cc69..e29faa2e 100644 --- a/scripts/resolve-ci-dependencies.py +++ b/scripts/resolve-ci-dependencies.py @@ -24,6 +24,8 @@ VERSION_RE = re.compile(r"^\d+(?:\.\d+)*(?:[-+][0-9A-Za-z.-]+)?$") STABLE_VERSION_RE = re.compile(r"^\d+(?:\.\d+)*$") COMMIT_RE = re.compile(r"^[0-9a-f]{40}$") +PKG_PR_NEW_BASE_URL = "https://pkg.pr.new" +SYNAPSE_PKG_PR_NEW_REPOSITORY = "FilOzone/synapse-sdk" class ResolutionError(RuntimeError): @@ -292,6 +294,25 @@ def npm_runtime_dependencies( return {"@filoz/synapse-core": core, "viem": viem} +def pkg_pr_new_url(package: str, commit: str) -> str: + return ( + f"{PKG_PR_NEW_BASE_URL}/{SYNAPSE_PKG_PR_NEW_REPOSITORY}/" + f"{package}@{commit[:7]}" + ) + + +def synapse_preview_runtime_dependencies( + commit: str, runner=run_command +) -> dict[str, str]: + return { + "@filoz/synapse-core": pkg_pr_new_url( + "@filoz/synapse-core", + commit, + ), + "viem": npm_metadata("viem", "2.x", runner)["version"], + } + + def read_gitlink(repository: str, commit: str, path: str, runner=run_command) -> str: with tempfile.TemporaryDirectory(prefix="foc-devnet-ci-deps-") as directory: repo_dir = Path(directory) / "repo" @@ -376,6 +397,21 @@ def resolve_component( branch = selection["branch"] commit = resolve_ref(repository, f"refs/heads/{branch}", runner) resolved.update(source="git", ref_type="branch", ref=branch, commit=commit) + elif strategy == "pkg_pr_new": + if name != "synapse-sdk": + raise ResolutionError("pkg_pr_new is only supported for synapse-sdk") + branch = selection["branch"] + commit = resolve_ref(repository, f"refs/heads/{branch}", runner) + package = component["npm_package"] + resolved.update( + source="pkg_pr_new", + ref_type="branch", + ref=branch, + commit=commit, + package=package, + version=pkg_pr_new_url(package, commit), + runtime_dependencies=synapse_preview_runtime_dependencies(commit, runner), + ) elif strategy == "git_submodule": parent_repository = selection["repository"] tag = selection["tag"] diff --git a/scripts/tests/test_resolve_ci_dependencies.py b/scripts/tests/test_resolve_ci_dependencies.py index 71c15712..2fc17ac9 100644 --- a/scripts/tests/test_resolve_ci_dependencies.py +++ b/scripts/tests/test_resolve_ci_dependencies.py @@ -531,6 +531,57 @@ def test_synapse_npm_resolution_includes_exact_runtime_dependencies(self): {"@filoz/synapse-core": "1.1.1", "viem": "2.52.0"}, ) + def test_synapse_preview_resolves_commit_pinned_packages(self): + commit = "a" * 40 + component = { + "repository": "https://example.test/synapse.git", + "npm_package": "@filoz/synapse-sdk", + "frontier": {"strategy": "pkg_pr_new", "branch": "master"}, + } + runner = FakeRunner( + { + ( + "git", + "ls-remote", + "https://example.test/synapse.git", + "refs/heads/master", + ): f"{commit}\trefs/heads/master", + ("npm", "view", "viem@2.x", "version", "--json"): '"2.52.0"', + ( + "npm", + "view", + "viem@2.52.0", + "gitHead", + "--json", + ): '""', + } + ) + + resolved = resolver.resolve_component( + "synapse-sdk", component, "frontier", runner + ) + + self.assertEqual(resolved["source"], "pkg_pr_new") + self.assertEqual(resolved["ref"], "master") + self.assertEqual(resolved["commit"], commit) + self.assertEqual( + resolved["version"], + ( + "https://pkg.pr.new/FilOzone/synapse-sdk/" + f"@filoz/synapse-sdk@{commit[:7]}" + ), + ) + self.assertEqual( + resolved["runtime_dependencies"], + { + "@filoz/synapse-core": ( + "https://pkg.pr.new/FilOzone/synapse-sdk/" + f"@filoz/synapse-core@{commit[:7]}" + ), + "viem": "2.52.0", + }, + ) + def test_profile_overrides_are_copied_to_resolved_component(self): component = { "repository": "https://example.test/filecoin-pin.git", diff --git a/scripts/tests/test_scenario_dependencies.py b/scripts/tests/test_scenario_dependencies.py index d7ae81c3..e424c831 100644 --- a/scripts/tests/test_scenario_dependencies.py +++ b/scripts/tests/test_scenario_dependencies.py @@ -137,80 +137,57 @@ def test_npm_runtime_resolves_fallback_consumer_dependencies( ), ) - @patch.dict("os.environ", {"SYNAPSE_SDK_SOURCE_DIR": ""}, clear=False) @patch("scenarios.synapse_runtime._copy_scenarios") - @patch("scenarios.synapse_runtime._source_commit", return_value="deadbeef") @patch("scenarios.synapse_runtime.run_cmd", return_value=True) @patch( "scenarios.synapse_runtime.component", return_value={ - "source": "git", - "repository": "https://example.test/synapse.git", + "source": "pkg_pr_new", + "package": "@filoz/synapse-sdk", + "version": "https://pkg.pr.new/@filoz/synapse-sdk@deadbeef", "commit": "deadbeef", + "runtime_dependencies": { + "@filoz/synapse-core": ( + "https://pkg.pr.new/@filoz/synapse-core@deadbeef" + ), + "viem": "2.52.0", + }, }, ) - def test_source_runtime_checks_out_and_installs_production_closure( - self, _component, run_cmd, _source_commit, _copy_scenarios + def test_preview_runtime_installs_immutable_consumer_manifest( + self, _component, run_cmd, _copy_scenarios ): with tempfile.TemporaryDirectory() as directory: - source = Path(directory) / "synapse-sdk" - source.mkdir() - (source / "packages" / "synapse-sdk" / "node_modules").mkdir(parents=True) - (source / "package.json").write_text('{"packageManager":"pnpm@11.5.3"}') runtime = prepare_synapse_runtime(Path(directory)) - self.assertEqual( - (runtime.work_dir / "node_modules").resolve(), - source / "packages" / "synapse-sdk" / "node_modules", - ) + manifest = json.loads((Path(directory) / "package.json").read_text()) - commands = [call.args[0] for call in run_cmd.call_args_list] - self.assertIn(["git", "checkout", "--detach", "deadbeef"], commands) - self.assertIn( + self.assertEqual(runtime.source, "pkg_pr_new") + self.assertEqual( + runtime.provenance, + "pkg_pr_new:@filoz/synapse-sdk@deadbeef", + ) + self.assertEqual( + manifest["dependencies"], + { + "@filoz/synapse-sdk": ( + "https://pkg.pr.new/@filoz/synapse-sdk@deadbeef" + ), + "@filoz/synapse-core": ( + "https://pkg.pr.new/@filoz/synapse-core@deadbeef" + ), + "viem": "2.52.0", + }, + ) + self.assertEqual( + run_cmd.call_args.args[0], [ - "pnpm", + "npm", "install", - "--frozen-lockfile", - "--prod", + "--omit=dev", "--ignore-scripts", - "--filter", - "@filoz/synapse-sdk...", + "--package-lock=false", ], - commands, - ) - - @patch("scenarios.synapse_runtime._copy_scenarios") - @patch("scenarios.synapse_runtime._source_commit", return_value="localcommit") - @patch("scenarios.synapse_runtime.run_cmd", return_value=True) - @patch( - "scenarios.synapse_runtime.component", - return_value={ - "source": "npm", - "package": "@filoz/synapse-sdk", - "version": "1.1.1", - }, - ) - def test_local_source_runtime_uses_declared_pnpm( - self, _component, run_cmd, _source_commit, _copy_scenarios - ): - with tempfile.TemporaryDirectory() as directory: - work_dir = Path(directory) / "runtime" - source_dir = Path(directory) / "synapse-source" - source_dir.mkdir() - source_node_modules = ( - source_dir / "packages" / "synapse-sdk" / "node_modules" - ) - (source_node_modules / "viem").mkdir(parents=True) - (source_node_modules / "@filoz" / "synapse-core").mkdir(parents=True) - (source_dir / "package.json").write_text('{"packageManager":"pnpm@11.5.3"}') - with patch.dict("os.environ", {"SYNAPSE_SDK_SOURCE_DIR": str(source_dir)}): - runtime = prepare_synapse_runtime(work_dir) - - self.assertEqual( - runtime.provenance, f"local:{source_dir}@localcommit (pnpm@11.5.3)" ) - commands = [call.args[0] for call in run_cmd.call_args_list] - self.assertFalse(any(command[:2] == ["git", "clone"] for command in commands)) - self.assertFalse(any(command[0] == "pnpm" for command in commands)) @patch("scenarios.synapse_runtime.ok") @patch("scenarios.synapse_runtime.info") @@ -240,38 +217,6 @@ def test_run_node_script_uses_consumer_cwd_and_env(self, run, _info, ok): self.assertEqual(kwargs["timeout"], 30) ok.assert_called_once_with("run smoke") - @patch("scenarios.synapse_runtime.ok") - @patch("scenarios.synapse_runtime.info") - @patch("scenarios.synapse_runtime.subprocess.run") - def test_run_node_script_uses_source_runtime_hook(self, run, _info, _ok): - run.return_value = subprocess.CompletedProcess( - ["node"], 0, stdout="", stderr="" - ) - with tempfile.TemporaryDirectory() as directory: - work_dir = Path(directory) - source_dir = work_dir / "synapse-sdk" - source_dir.mkdir() - (work_dir / "source-runtime.mjs").touch() - (work_dir / "system-e2e.ts").touch() - run_node_script( - SynapseRuntime(work_dir, "source", "git:example@deadbeef", source_dir), - "system-e2e.ts", - "run system e2e", - ) - - self.assertEqual( - run.call_args.args[0], - [ - "node", - "--import", - str(work_dir / "source-runtime.mjs"), - str(work_dir / "system-e2e.ts"), - ], - ) - self.assertEqual( - run.call_args.kwargs["env"]["SYNAPSE_SDK_SOURCE_DIR"], str(source_dir) - ) - @patch("scenarios.synapse_runtime.time.sleep") @patch("scenarios.synapse_runtime.ok") @patch("scenarios.synapse_runtime.info")