mirror of
https://github.com/pnpm/pnpm.git
synced 2026-06-30 10:55:23 -04:00
511e74200d00a9c4706cb22433da1cbef7d7c310
25 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
83f06a6046 |
refactor(pacquet): trim comments that restate code or record history (#12482)
## What A repository-wide sweep of the Rust source under `pacquet/` to bring comments in line with the comment policy in `AGENTS.md` / `pacquet/AGENTS.md`: **comments are for the non-obvious _why_, not a translation of the _what_, and behavior already proven by a test should not be re-narrated in code.** Two focused commits: **1. Trim comments that restate code or record history** (`refactor(pacquet): trim comments that restate code or record history`) - comments that merely restate the adjacent code; - history / refactor-shape narration (`used to`, `before this PR`, `Copilot flagged`, "the old X", removed-type references); - call-site comments duplicating a callee's own doc comment; - test prose that just re-describes what a test's name and assertions demonstrate. **2. Drop comments that narrate test-covered behavior** (`refactor(pacquet): drop comments that narrate test-covered behavior`) — enforcing the "tests are documentation" rule. Each implementation file was paired with its co-located test module so the duplication was visible, then comments enumerating tested scenarios, edge cases, failure modes, or worked examples were removed — **including when they carried a pnpm-parity reference/link**, since that context lives in the test names and git history. Preserved throughout: genuine `///`/`//!` contracts (guarantees, preconditions, postconditions, panics), hidden invariants, workarounds, ordering/safety reasons, parity notes that aren't behavior-restatement, and `SAFETY:`/`TODO:` notes. ## Scope Comments only — **no code, identifiers, or string literals were modified** in either commit. ~500 file edits in total; several thousand comment lines removed. ## How it was done Multi-agent sweeps: one agent per batch applied the policy, and a second agent diff-checked each edited batch for over-deletion (lost contracts) or accidental code changes. Over-trimmed contracts were restored; zero code changes were introduced (independently re-verified: every added line is identical to a removed line modulo a stripped trailing comment). |
||
|
|
23716ed9b0 |
fix: preserve user-defined npm_config_* env vars in lifecycle scripts (#12400)
* fix: preserve user-defined npm_config_* env vars in lifecycle scripts * fix: use released `@pnpm/npm-lifecycle` and port npm_config_* filter to pacquet Pin the catalog to the released `@pnpm/npm-lifecycle` ^1100.0.0 instead of a mutable PR-head ref, regenerating the lockfile to the immutable registry tarball. Port the upstream env filter to pacquet's make_env so user-defined npm_config_* vars (e.g. npm_config_platform_arch) survive lifecycle scripts while (npm|pnpm)_config_* auth keys are still stripped, matching `@pnpm/npm-lifecycle` 9e2ac78148. Harden the new TS test to save/restore npm_config_platform_arch. * test(executor): restore env vars to pre-test value in lifecycle EnvGuard The guard removed the seeded var unconditionally on drop, which would discard any value the process env already had. Capture the original via var_os and restore it (or remove only when originally absent). --------- Co-authored-by: Zoltan Kochan <z@kochan.io> |
||
|
|
4759e0559a |
chore(rust/clippy): enable some lints (#12363)
* chore(rust/clippy): enable undocumented_unsafe_blocks Move the SAFETY rationale out of the EnvGuard doc comment and onto the two env-var unsafe blocks it actually justifies, so every unsafe block in the workspace now carries its own // SAFETY: comment. * chore(rust/clippy): enable unnecessary_safety_comment Reword a comment in local_tracing that used the // SAFETY: prefix to justify an expect() on safe code; the prefix is reserved for unsafe blocks. * chore(rust/clippy): enable todo No occurrences in the workspace; the lint keeps stray todo!() markers out of committed code from now on. * chore(rust/clippy): enable unimplemented No occurrences in the workspace; the lint keeps stray unimplemented!() markers out of committed code from now on. * chore(rust/clippy): enable exit The one deliberate process exit — dlx propagating the spawned command's exit status, matching pnpm — now carries an #[expect] stating that; any new std::process::exit call site has to justify itself the same way. * chore(rust/clippy): enable infinite_loop No occurrences in the workspace; a loop that can never terminate must return ! from now on. * chore(rust/clippy): enable mem_forget The overrides test helper leaked its TempDir via mem::forget to keep the fixture on disk; TempDir::keep() is the purpose-built API for that. The two registry-mock forget(mock_instance) calls stay — the leak is how the shared mock-registry process survives the spawning test process — but now carry #[expect] so the intent is machine-checked. * chore(rust/clippy): enable get_unwrap Replace .get(...).unwrap() / .get_mut(...).unwrap() with indexing across the test suites (machine-applicable clippy fixes): the panic is the same, but the intent is clearer and the panic message names the missing key instead of 'unwrap on None'. * chore(rust/clippy): enable unused_result_ok Two best-effort calls in tests discarded their Result with .ok(); spell the discard out with let _ = instead, so .ok() is reserved for actually consuming the Option. * chore(rust/clippy): enable pathbuf_init_then_push resolve_path built its joined path with to_path_buf + push; Path::join expresses the same thing in one allocation-aware call. * chore(rust/clippy): enable string_add The phase-event fixture concatenated string literals at runtime with String + &str; concat! assembles the same fixture at compile time. * chore(rust/clippy): enable verbose_file_reads No occurrences in the workspace; whole-file reads go through fs::read / fs::read_to_string rather than manual open + read_to_end loops from now on. * chore(rust/clippy): enable allow_attributes Convert every #[allow] in the workspace to #[expect]. The conversion immediately paid for itself by surfacing four suppressions whose lint no longer fires: - resolve_peers: a too_many_arguments expect on a three-argument method (stale since a refactor) - removed. - resolve_peers: two dead_code expects on MissingPeerInfo fields the compiler considers used - removed. - tarball: fetch_and_extract_zip_once stacked two copies of the same too_many_arguments suppression - deduplicated. - ensure_file: a cfg_attr(windows, allow(unused)) on a mode parameter that IS used on Windows (via verify_or_rewrite) - removed. The remaining windows-side cfg_attr suppressions become expect with a reason, and fire correctly there (the parameters are genuinely unused on that cfg). * chore(rust/clippy): enable allow_attributes_without_reason Every #[expect] in the workspace already carries a reason; the lint locks the convention in. * chore(rust/clippy): enable negative_feature_names No occurrences in the workspace; cargo features stay additive (no no-std / not-x style names) from now on. * chore(rust/clippy): enable redundant_feature_names No occurrences in the workspace; feature names won't restate that they are features (use-x / with-x prefixes and suffixes). * chore(rust/clippy): enable wildcard_dependencies No occurrences in the workspace; every dependency keeps a real version requirement instead of "*". * chore(rust/clippy): drop allow_attributes and allow_attributes_without_reason perfectionist is adding better-targeted replacements for both, so leave the clippy versions off to avoid duplicate diagnostics. The #[expect] conversions they prompted stay — they are valid without the lints and already removed four stale suppressions. * chore(rust/clippy): trim redundant lint comments The enabled lints' names already say what they enforce, so the inline notes and per-theme section headers restated the obvious. Drop them to match the convention of the surrounding activations, keeping only the two category headers that carry non-obvious context. * chore(rust/clippy): drop get_unwrap The lint only rewrites `x.get(i).unwrap()` to `x[i]`, erasing the word "unwrap" from panicking call sites and making them harder to grep for. No safety benefit, so revert the enablement and all of its rewrites. This reverts commit |
||
|
|
ac367fce91 |
chore(rust/clippy): pedantic, nursery, and some (#12209)
* chore: enable clippy::pedantic lint group for pacquet workspace * style(pacquet): comply with clippy::pedantic Apply clippy's machine-applicable pedantic fixes across the workspace (inlined format args, removed needless borrows/closures, added must_use, etc.), fix a few doc-comment backtick nits, and drop pointless #[inline(always)] on trivial accessors. Opt specific pedantic lints back out in [workspace.lints.clippy] with documented justifications, grouped into false positives, library-API hygiene that doesn't fit an internal CLI, suggestions that conflict with the cardinal rule of porting pnpm 1:1, and opinionated style. * style: taplo-format Cargo.toml lint table * style(pnpr): comply with clippy::pedantic in merged auth backend code Re-apply pedantic compliance to the networked-SQLite auth backend that landed on main (#12186, #12199/#12206): doc-comment backticks, #[must_use] on constructors and status_code, i64::from over `as`, map_or, and a method-reference closure. * docs(clippy): trim and inline the pedantic allow-list comments * docs(clippy): note perfectionist supersedes many_single_char_names * docs(clippy): note pnpm-mirroring rationale on structure/naming lints * docs(clippy): mark unused_async as deferred pending audit * style: enable clippy::match_wildcard_for_single_variants * refactor: enable clippy::unused_self Convert two self-less private methods (overrides pick_most_specific, tarball head_only_result) to associated functions. * refactor: enable clippy::ref_option Widen engine_json to Option<&str>; #[expect] the two serde serialize_with helpers, which serde must call as f(&field, ser). * perf: enable clippy::trivially_copy_pass_by_ref Pass the 1-byte Copy types NodeLinker and FilterWorkspaceProjectsOptions by value; #[expect] the serde skip_serializing_if helper is_false. * perf: enable clippy::assigning_clones Use clone_from for seven field assignments to reuse allocations. * style: enable clippy::manual_let_else Convert 27 match/if-let guards to let-else; preserve the non-UTF-8 skip rationale comment in the directory walker. * style: enable clippy::default_trait_access Name the concrete type on Default::default() call sites; #[expect] two struct-literal test fixtures where naming each field type would force ~20 imports. * refactor: enable clippy::format_push_string Replace push_str(&format!(...)) with write!/writeln! into the target String (local 'use std::fmt::Write as _'); writeln! preserves the exact LF/CRLF shell-shim output. * refactor: enable clippy::needless_pass_by_value Take by reference where the argument is only read (incl. dropping some redundant clones in resolve_peers' recursion). Where converting would cascade badly, #[expect] with a reason: functions that destructure/consume the arg (build_resolve_result, PrefetchingResolver, S3Store::new), the by-value `impl IntoIterator + Clone` in build_direct_deps_by_importer, and the serde/test helpers whose owned fixtures keep call sites clean. * fix(perfectionist): satisfy dylint after format_push_string changes Add trailing commas to the multi-line writeln! shell-shim templates (macro_trailing_comma) and merge the new `fmt::Write as _` imports into each file's existing `use std::{...}` block (import_granularity). * docs(clippy): explain missing_errors_doc suppression; mark missing_panics_doc deferred * fix(perfectionist): collapse fmt::{self, Write as _} in work_env imports The format_push_string Write import landed as a sibling fmt:: path next to the existing fmt import; merge them so import_granularity passes. * style: enable clippy::return_self_not_must_use Add #[must_use] to the WorkspaceTreeCtx builder methods, matching the #[must_use] already on the parallel TreeCtx builders. * perf: enable clippy::large_stack_arrays Heap-allocate the 64 KiB read buffer in verify_file_integrity with a Vec instead of placing it on the stack. * chore(clippy): enable clippy::nursery group Enable the nursery lint group on the pacquet/pnpr workspace and bring the code into compliance. Fixed in code: - iter_on_single_items: [x].into_iter()/.iter() -> std::iter::once - equatable_if_let: pattern match -> equality check (the install_accelerator rewrite wraps in a multi-line matches!, which gets a trailing comma for perfectionist::macro_trailing_comma) - needless_pass_by_ref_mut: load_pending_row/apply_write_msg take &StoreIndex Opted back out in Cargo.toml, each with a documented justification: use_self, too_long_first_doc_paragraph, missing_const_for_fn, option_if_let_else, significant_drop_tightening, redundant_pub_crate, derive_partial_eq_without_eq, branches_sharing_code, useless_let_if_seq, single_option_map, iter_with_drain, literal_string_with_formatting_args, collection_is_never_read. Dropped the now-redundant individual nursery warns (needless_collect, or_fun_call, redundant_clone) the group now covers, plus the default-on unnecessary_lazy_evaluations. Kept clone_on_ref_ptr and if_then_some_else_none (restriction lints not enabled by any group). * style: bring merged main code into clippy pedantic compliance The 17 commits merged from main predate this branch's pedantic/nursery lint config, so their new code tripped pedantic lints. Apply the machine-applicable fixes (uninlined_format_args, if_not_else, elidable_lifetime_names, must_use_candidate, single_match_else, map_unwrap_or, default_trait_access, assigning_clones, doc_markdown, ...) and re-add the documented #[expect(needless_pass_by_value)] on S3Store::new that this branch had carried on the now-replaced file. * style: bring merged main code into clippy pedantic compliance The 28 commits merged from main predate this branch's lint config, so their new code tripped pedantic lints. Apply the machine-applicable fixes (uninlined_format_args, manual_let_else, needless_raw_string_hashes, redundant_closure_for_method_calls, map_unwrap_or, elidable_lifetime_names, doc_markdown, ...) plus a few by hand: - derive Copy on LinkSlotsParallel (all fields are Copy/refs) to clear needless_pass_by_value without a signature change - deduplicate_all takes &[Vec<DepPath>] (it only borrows the duplicates) - pick_most_specific becomes an associated fn (it never used self) - default_trait_access -> concrete types; assigning_clones -> clone_from; format_push_string -> write! - #[expect] with reasons where a fix would churn main's feature code: needless_pass_by_value on the recursive resolve_node and a test helper, and float_cmp on two deterministic-fixture assertions * style: enable clippy::allow_attributes and allow_attributes_without_reason Both are restriction lints (not implied by any group), enabled alongside the existing clone_on_ref_ptr / if_then_some_else_none. Convert every #[allow(...)] (including one nested in cfg_attr) to #[expect(...)]; all already carried a reason, so allow_attributes_without_reason is satisfied. Drop two now-redundant suppressions surfaced by the conversion: a duplicated #[expect(too_many_arguments)] on fetch_and_extract_zip_once (a prior merge left both an allow and an expect), and the #[expect(dead_code)] on MissingPeerInfo's fields (the #[derive(Debug, Clone)] already reads them, so dead_code never fired). clone_on_ref_ptr was already enabled. mod_module_files is intentionally NOT enabled: it mandates mod.rs, the opposite of the flat module.rs pattern this project requires (CODE_STYLE_GUIDE.md, enforced by perfectionist::flat_module_pattern). * style: enable clippy::mod_module_files to enforce the flat module layout mod_module_files bans mod.rs files, enforcing the flat module.rs pattern this project already uses (0 mod.rs in the tree, so no violations). Update CODE_STYLE_GUIDE.md to cite it as the enforcer; perfectionist's flat_module_pattern is being retired in favor of this Clippy rule. * fix(perfectionist): trailing comma on wrapped assert_eq! in workspace_yaml tests The default_trait_access fix lengthened the assert_eq! so fmt wrapped it to multi-line, which perfectionist::macro_trailing_comma requires to end with a trailing comma. * fix(fs): use cfg_attr expect instead of allow for Windows-unused mode args With clippy::allow_attributes enabled, the #[cfg_attr(windows, allow(unused))] on make_file_executable and the ensure_file/write_atomic mode params fails Windows CI. Switch to #[cfg_attr(windows, expect(unused, reason = ...))]; on Windows the lint fires (Unix mode unused there) so the expectation is fulfilled, and the attribute stays inert on Unix. * fix(fs): drop the Windows unused suppression on ensure_file's mode arg ensure_file forwards mode to verify_or_rewrite unconditionally, so it is used on Windows too; the #[cfg_attr(windows, expect(unused))] was therefore unfulfilled and failed Windows CI under -D warnings. write_atomic and make_file_executable keep their expect — they use mode/file only under #[cfg(unix)], so the lint fires (and the expectation holds) on Windows. * chore(git): revert "fix(fs): drop the Windows unused suppression on ensure_file's mode arg" This reverts commit |
||
|
|
622c056bbc |
feat(pacquet/cli): initial implementations of run, exec, dlx (#11938)
> [!WARNING] > **Scope note.** Per [`pacquet/CONTRIBUTING.md`](https://github.com/pnpm/pnpm/blob/d4a2b0364c/pacquet/CONTRIBUTING.md), pacquet's current focus is Stage 1 (the headless installer); `exec` and `dlx` are new top-level commands, so this PR sits outside Stage 1 and is opened for review/discussion ([roadmap pnpm/pacquet#299](https://github.com/pnpm/pacquet/issues/299)). ## Summary Ports of `run`, `exec`, and `dlx` from the TypeScript pnpm CLI. - **`run`**: runs scripts through a new foreground `run_script` in `pacquet-executor` (sets up `node_modules/.bin` on `PATH` + the `npm_*` env). Handles `pre`/`post` scripts under `enablePrePostScripts`, arg shell-quoting (with the Windows `cmd /d /s /c` verbatim `raw_arg` path), script listing, hidden (`.`-prefixed) scripts, `--if-present`, the `start`→`server.js` fallback (with the NO_SCRIPT_OR_SERVER guard, including empty-string `start`), the `[ELIFECYCLE]` failure line (with the `test`-stage and signal-killed variants), and exit-code propagation. The recursive runner's scaffolding (`--resume-from` / `--report-summary`) landed separately on `main` via [#12093](https://github.com/pnpm/pnpm/pull/12093); this PR dispatches to it when `-r` is set and hardens it to match pnpm — per-project `pre`/`post`, the `PNPM_SCRIPT_SRC_DIR` recursion guard, pnpm's per-stage no-op guards, hidden-script handling, and `--no-bail`. - **`exec`**: runs a command with `node_modules/.bin` + `extraBinPaths` on `PATH` (resolved via `which`), stamps `npm_config_user_agent` / `PNPM_PACKAGE_NAME` / `NODE_OPTIONS`, supports `--shell-mode` (the joined command goes through the shared `push_script_arg` helper, so the Windows `cmd /d /s /c` verbatim path uses `raw_arg` and embedded quoting survives), rejects delimiter-containing dirs (`ERR_PNPM_BAD_PATH_DIR`). The **recursive variant** (`-r`) runs the command in every workspace project, topologically sorted and sequential, with `--resume-from` / `--report-summary` / `--no-bail` and pnpm's error codes (`ERR_PNPM_RECURSIVE_EXEC_NO_PACKAGE` / `ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL` / `ERR_PNPM_RECURSIVE_FAIL`). The workspace-graph / summary machinery is shared with recursive `run` through a new `cli_args::recursive` module. - **`dlx`**: installs the package(s) into a TTL cache dir (reusing the install pipeline, anchored at the cache dir, with a *fresh* per-install build-script allow-list — caller's `allow_builds`/`dangerouslyAllowAllBuilds` don't leak in), then runs the resolved bin in the process cwd. Supports `--package`, `--allow-build`, `--shell-mode` (same `push_script_arg` verbatim path as exec), `--cpu`/`--os`/`--libc` architecture overrides (folded into the per-axis `supportedArchitectures` of the dlx install **and** into the cache key, so different overrides don't share a cached install), `dlxCacheMaxAge`; same PATH guard as exec. New `Config` settings: `enablePrePostScripts`, `scriptShell`, `nodeOptions`, `dlxCacheMaxAge` (wired into `pnpm-workspace.yaml` + the `PNPM_CONFIG_*` overlay). Their defaults match pnpm and are asserted by the `pnpm_default_parity` contract test — this PR moves `enablePrePostScripts` (which pnpm defaults to `true`, a breaking change in [#7634](https://github.com/pnpm/pnpm/pull/7634) shipped in v9) and `dlxCacheMaxAge` into its mapped rows. `extraBinPaths` is kept as a computed field (empty until workspace support lands), matching pnpm — it is not a user-settable key. ## Deferred (documented in code) - **`--filter` and `--workspace-concurrency`.** Recursive `run` and `exec` run every workspace project sequentially; the `--filter` package-selector subsystem and `--workspace-concurrency` parallelism are not ported yet (the global `--filter` / `--recursive` flags are accepted via clap but `--filter` is not consumed). `dlx` stays single-package by design (matches pnpm). - `run`: the `/regexp/` script selector and the fuzzy "did you mean" hint are not ported (no regex/levenshtein dep); `scriptsPrependNodePath: always` can't prepend the node dir (pacquet resolves no node execpath anywhere yet). - `dlx`: the cache key uses raw specs (not resolved ids); no `approve-builds` prompt. |
||
|
|
577a90f819 |
feat(pacquet): --resume-from, --report-summary (#12093)
* feat(cli): port recursive run with --resume-from and --report-summary Port pnpm's `pnpm run -r` (recursive run) to pacquet, including the `--resume-from` and `--report-summary` flags, which previously existed only in the TypeScript CLI. - `pacquet -r run <script>` now runs the script in every workspace project in topological order, mirroring pnpm's runRecursive: discover projects, build the inter-project dependency graph, sort it into chunks via graph_sequencer (the port of sortProjects), and execute. - `--resume-from <pkg>` drops every chunk before the one containing <pkg>, mirroring getResumedPackageChunks; an unknown package fails with ERR_PNPM_RESUME_FROM_NOT_FOUND. - `--report-summary` writes pnpm-exec-summary.json with the per-package status (queued/running/passed/skipped/failure) and duration, nested under an executionStatus key, mirroring writeRecursiveSummary. - `--no-bail` keeps running after a failure (recursive runs bail by default). Failures surface ERR_PNPM_RECURSIVE_FAIL, or ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL when bailing; a run that matches no script fails with ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT. A new executor helper, execute_shell_with_status, returns the child's exit status so per-package pass/fail can be recorded; execute_shell is unchanged. Not yet ported (noted in the module): --no-sort, --reverse, --workspace-concurrency parallelism, --filter narrowing of the selected set, and the RegExp script selector. The selected set is every workspace project, matching pacquet's currently-unfiltered install. Integration tests port the upstream resume-from and report-summary cases from exec/commands/test/runRecursive.ts. https://claude.ai/code/session_01QUdrDcP9iU3DwxR2TATobQ * test(cli): gate recursive-run tests to unix and add macro trailing commas Fix two CI failures on the recursive-run integration tests: - Dylint (`perfectionist::macro_trailing_comma`): add the trailing comma to the four multi-line `assert!` invocations. - Lint and Test (windows-latest): the shared helpers are used only by the Unix-gated tests, so on Windows they tripped `dead_code` under `-D warnings`. Gate the whole file with `#![cfg(unix)]` (the build scripts run through pacquet's `sh -c` executor anyway), matching the single-package `run` tests. https://claude.ai/code/session_01QUdrDcP9iU3DwxR2TATobQ * test(cli): cover recursive-run bail summary and no-script branches Fill the two coverage holes in the recursive-run handler: - bail + report-summary: the first failing script writes the summary, then aborts with ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL; a package that sorts after the failure stays `queued`. - no-script: a recursive run for a script no package defines fails with ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT, and `--if-present` turns that into a clean no-op. https://claude.ai/code/session_01QUdrDcP9iU3DwxR2TATobQ * test(cli): cover the bail path without --report-summary The bail tests always passed --report-summary, leaving the report-summary-off side of the bail block (recursive.rs:136) uncovered. Add a test for a failing script with bail on and no --report-summary: it still fails with ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL and writes no summary file. Verified with cargo llvm-cov that recursive.rs now has no missing lines. https://claude.ai/code/session_01QUdrDcP9iU3DwxR2TATobQ * fix(cli): run each recursive-run script from its own package root Recursive run spawned every package's script via `sh -c` without setting the working directory, so scripts ran in pacquet's process CWD (the workspace root) instead of their own package root. That breaks scripts relying on relative paths and diverges from pnpm, whose `runLifecycleHook` runs with `pkgRoot` as the working directory. - Give `execute_shell_with_status` a `current_dir` argument (factored through a private `spawn_shell` helper); `execute_shell` keeps its inherited-CWD behavior, so its callers are unchanged. - Pass each package's root as the script's working directory. - Make the marker-based recursive-run tests cwd-sensitive: scripts now write a relative `ran.txt`, and the tests assert it lands under each package root (and not at the workspace root), so a wrong-CWD regression fails the suite. https://claude.ai/code/session_01QUdrDcP9iU3DwxR2TATobQ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
13b1b9aaa2 |
chore(rust/dylint): upgrade perfectionist to 0.0.0-rc.17 (#12070)
Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
64ce69aadc |
feat(package-manager): run project lifecycle scripts during install (#12051)
Port pnpm's project (workspace/root) lifecycle scripts that run during `pnpm install` — preinstall, install, postinstall, preprepare, prepare, postprepare — distinct from the dependency build scripts already run in `BuildModules`. - executor: extract the per-stage loop into a shared `run_lifecycle_stages`, keeping the `binding.gyp -> node-gyp rebuild` fallback and the `npx only-allow pnpm` skip identical for both paths. Add `run_project_lifecycle_scripts` + `PROJECT_LIFECYCLE_STAGES`, mirroring the `runLifecycleHooksConcurrently` call sites in pkg-manager/core and pkg-manager/headless. - executor: honor `SelectedShell::windows_verbatim_args` — on the Windows `cmd /d /s /c` path the script body is now appended with `raw_arg` so embedded quoting (e.g. `node -e "..."`) reaches the child intact, matching Node's `windowsVerbatimArguments`. Previously a no-op, which mangled quoted scripts under cmd.exe. - package-manager: run each project's scripts in `Install::run` after the dependency graph is materialized, bins are linked, and `.modules.yaml` / the current lockfile are written, before the closing `pnpm:summary`. Runs on both the frozen and fresh paths. A project script failure always fails the install via the new `InstallError::ProjectLifecycleScript` variant (unlike optional dependency build failures). - gate on `Install::is_full_install`: `pacquet add` is a partial install (pnpm's `mutation: 'installSome'`), so the project's own scripts must not run — mirroring pnpm's `mutation === 'install'` filter. - tests: stage ordering, re-run on --frozen-lockfile, failure propagation, name-differs-from-directory, INIT_CWD, and the `add`-skips-project-scripts gate. Projects run root-first and sequentially; pnpm's buildIndex ordering and child_concurrency fan-out are a follow-up once pacquet computes a per-importer build index. No `ignoreScripts` gate yet — pacquet hardcodes `ignore_scripts: false` across the dep-build path, so this matches pnpm's default of running them. Ref: https://github.com/pnpm/pnpm/blob/80037699fb/pkg-manager/core/src/install/index.ts#L1517-L1530 |
||
|
|
d4136eb6f6 |
chore(pacquet/lint): more clippy (#11839)
Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
61e724cef3 |
ci(pacquet): pass --workspace --all-targets to clippy and check (#11735)
Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
e2a3c68309 |
chore(pacquet/dylint): upgrade perfectionist to 0.0.0-rc.15 (#11709)
Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
e74a6b7e48 |
test(pacquet): coverage (#11710)
* test: fill coverage holes across lockfile, package-manifest, fs, and friends
Adds ~44 unit and integration tests to close the easier targets in the
coverage analysis at #339-style boundaries. Touches:
- `lockfile::resolved_dependency`: cover `as_alias` / `ver_peer` for
non-matching variants, alias / parse error variants, `TryFrom<Cow>`,
serialize-alias / serialize-link, `From<PkgVerPeer> / From<PkgNameVerPeer>`,
and a Display vs. `String` round-trip.
- `lockfile::freshness`: cover the plural arms of `SpecDiff::Display`
and the comma separator inside the removed/modified loops.
- `lockfile::pkg_name`: cover `TryFrom<String>` and `TryFrom<Cow>` happy
and empty-input paths.
- `lockfile::snapshot_dep_ref`: cover `ver_peer` and `From<PkgVerPeer>`.
- `lockfile::save_lockfile`: cover the `CreateDir` / `RemoveFile` /
`RenameFile` error classifications by planting a regular file or
directory where the writer expects a file or a writable path.
- `registry::package`: cover `PartialEq`, `latest`, and `pinned_version`
happy and no-match paths.
- `graph-hasher::engine_name`: cover `detect_node_version` /
`detect_node_major` when `node` is on PATH; skip cleanly otherwise.
- `graph-hasher::object_hasher`: cover the null / bool / array arms of
the bytestream serializer.
- `patching::apply`: cover non-NotFound read-patch error, partial-delete
non-empty result, unsupported rename/copy operation, and `Create`
with an unwritable parent.
- `workspace-state`: cover the `CreateDir` / `ReadFile` / `ParseJson`
error variants.
- `package-manifest`: cover `from_path` ENOENT, `add_dependency` on a
non-object field, `safe_read_package_json_from_dir` non-NotFound IO
error, and `convert_engines_runtime_to_dependencies` for unsupported
shapes.
- `fs::file_mode`: cover `EXEC_MASK` / `EXEC_MODE` constants,
`is_executable` for all positions, and `make_file_executable` on Unix.
- `executor`: cover `execute_shell` happy and non-zero-exit paths.
- `cli/tests/run.rs`: new integration tests for `pacquet run`,
including argument forwarding and `--if-present`.
Also bumps `hex_decode` in `graph-hasher::tests` to use
`is_multiple_of` so test-time clippy stays clean under Rust 1.95.
https://claude.ai/code/session_01D8WBTfQzTpsZsRknrzwNKL
* style(package-manifest): drop trailing comma in single-line `assert!`
Dylint's perfectionist::macro-trailing-comma rule rejected the single-
line `assert!` formatting I'd accidentally inherited from the multi-
line shape — remove the trailing comma so the lint clears.
https://claude.ai/code/session_01D8WBTfQzTpsZsRknrzwNKL
* test(graph-hasher): make `node` a hard prerequisite for detect tests
The two `detect_node_*` tests previously skipped silently when `node`
was missing from `PATH`. `node` is a documented prerequisite of the
test suite (see `pacquet/CONTRIBUTING.md`'s setup section), so a
missing binary is a test-env bug to surface, not a condition to paper
over. Switch the `let Some(...) = ... else { return }` skips to
`expect` so a missing `node` fails loudly with a clear message.
https://claude.ai/code/session_01D8WBTfQzTpsZsRknrzwNKL
* test(cli,patching): switch fixtures to `json!` and `text_block_fnl!`
Addresses six review comments on #11710:
- `cli/tests/run.rs`: the three `package.json` fixtures used
`format!("{{ ... }}", marker = …)` with literal-brace escapes,
which is awkward and brittle when path strings contain
JSON-special characters. Switched to
`json!({...}).to_string()` so serde handles escaping and the
shape reads as JSON, not as a brace-escaped template.
- `patching/src/apply/tests.rs`: the three patch fixtures I
added used `"\<newline>...\n"` raw strings. Converted them to
`text_block_fnl!`, matching the convention the rest of the
workspace (lockfile, package-manager, testing-utils) uses for
multi-line fixture text. The `_fnl` variant keeps the trailing
newline that git-diff parsers expect.
Pre-existing fixtures in the same file (`IS_POSITIVE_PATCH`,
etc.) were not touched — keeping the scope to the lines
reviewers flagged.
https://claude.ai/code/session_01D8WBTfQzTpsZsRknrzwNKL
* style(registry): use `assert_ne!` over `assert!(lhs != rhs)`
Idiomatic Rust and produces a better failure message when the
assertion trips. Caught during a self-review pass over #11710.
https://claude.ai/code/session_01D8WBTfQzTpsZsRknrzwNKL
* test: address zkochan + Copilot review feedback on #11710
- Move executor + fs::file_mode unit tests from inline modules
to the project's standard `src/<parent>/tests.rs` layout. The
convention is documented in `CODE_STYLE_GUIDE.md` under "Unit
test file layout"; zkochan flagged the violation in the
executor file.
- Gate `pacquet-executor::tests` on `cfg(all(test, unix))` at
the declaration site so the `use super::execute_shell` import
isn't dead on Windows. Copilot flagged that
`clippy --tests --deny warnings` would fail there.
- Drop `execute_shell_propagates_nonzero_exit_as_ok`. Copilot
flagged that it regression-pinned a behavior that *should*
change: pnpm/npm `run` exits with the script's status, but
pacquet's wrapper currently returns `Ok(())` regardless. A
fix belongs in its own PR; until then we should not add tests
that block that fix.
- Quote the temp-path redirect targets in the `cli/tests/run.rs`
shell fixtures so a tempdir path containing a space
(`/var/folders/...` on macOS) doesn't split the `touch` /
redirect argument. Copilot flagged the unquoted paths.
The `fs::file_mode::make_file_executable_sets_exec_bits` test
keeps `#[cfg(unix)]` at the test site (and moves its
`make_file_executable` import inside) rather than gating the
whole `tests` module — the other two tests
(`exec_constants_pin_pnpm_layout`,
`is_executable_matches_any_exec_bit`) are platform-neutral and
should still run on Windows.
Copilot's concern about `detect_node_version`'s `expect` (in
`engine_name.rs`) is intentionally not addressed: KSXGitHub
asked for the hard-fail in
|
||
|
|
540fa7664a |
feat(git-fetcher): install git-hosted packages via git CLI + preparePackage (#436 §B+D) (#446)
Builds on §A (#440). Adds a new `pacquet-git-fetcher` crate that handles `LockfileResolution::Git` snapshots during frozen-lockfile installs, plus the supporting `preparePackage` port. The install dispatcher now routes git resolutions through it instead of returning `UnsupportedResolution { resolution_kind: "git" }`. ### New crate `pacquet-git-fetcher` - **`GitFetcher`** shells out to the system `git` (clone, or `init` + `fetch --depth 1` on hosts in `git_shallow_hosts`), checks out the pinned commit, verifies via `rev-parse HEAD`, runs `preparePackage`, removes `.git`, computes a packlist, and imports the resulting file set into the CAS. Surfaces a friendly `GitNotFound` when `git` isn't on `PATH` — pacquet does not bundle it. Mirrors [`fetching/git-fetcher/src/index.ts`](https://github.com/pnpm/pnpm/blob/94240bc046/fetching/git-fetcher/src/index.ts). - **`prepare_package`** ports [`exec/prepare-package/src/index.ts`](https://github.com/pnpm/pnpm/blob/94240bc046/exec/prepare-package/src/index.ts). Reads the manifest, decides via `package_should_be_built`, honors an `AllowBuildFn` (the dispatcher adapts pacquet's `AllowBuildPolicy` into this shape), runs `<pm>-install` plus any `prepublish` / `prepack` / `publish` scripts via the existing `pacquet_executor::run_lifecycle_hook`, then deletes `node_modules`. Emits the upstream error codes `GIT_DEP_PREPARE_NOT_ALLOWED`, `ERR_PNPM_PREPARE_PACKAGE`, `INVALID_PATH`. - **`detect_preferred_pm`** sniffs the cloned tree for a lockfile to pick the install pm. Workspace-root walking deferred. - **Minimal `packlist`** honors the manifest's `files` field with a tiny `*` / `**` / `?` glob matcher (`?` and single `*` reject `/`; `**` matches across directories), always-includes the README / LICENSE / package.json set, and always-excludes `.git`, `node_modules`, lockfiles for sibling pms, and common cruft. Full `.npmignore` / `.gitignore` semantics and `bundleDependencies` walking are deferred and tracked in module docs. ### Wiring in `pacquet-package-manager` - `InstallPackageBySnapshot` now matches `LockfileResolution::Git` and runs the fetcher in place of `DownloadTarballToStore`. - `&AllowBuildPolicy` is computed once per install in `InstallFrozenLockfile` and threaded through `CreateVirtualStore` → `InstallPackageBySnapshot`. - `snapshot_cache_key` returns `Ok(None)` for `Git` resolutions so they cold-batch. Warm prefetch for git-hosted slots is a follow-up alongside §C. ### Supporting changes - `GitResolution.path: Option<String>` so a lockfile pinning a sub-directory of a git-hosted monorepo deserializes without tripping `deny_unknown_fields`. - `Config::git_shallow_hosts` with pnpm's default list + `pnpm-workspace.yaml` override. - `pacquet_executor::run_lifecycle_hook` is now `pub` so the preparePackage port can dispatch by arbitrary stage name. ## Out of scope (follow-ups for #436) - §C — git-hosted *tarball* fetcher (the `gitHosted: true` shape). - §E — full integration-test matrix (`plans/TEST_PORTING.md` 563-572). This PR ships crate-level tests against a local bare repo that prove the path works end-to-end. - Warm prefetch for git resolutions (re-clones on every install today — slow but correct). - Full `npm-packlist` semantics (`.npmignore`, gitignore layering, `bundleDependencies` walking). |
||
|
|
2291bc6c2e |
feat: build concurrency, unsafe-perm, scriptsPrependNodePath (#397 items 12, 14, 15) (#429)
Bundles the three remaining moderate/minor parity items from #397 into one PR. Each adds a `pnpm-workspace.yaml` setting, threads it through `Config`, and applies it to `BuildModules` so a pnpm yaml that exercises these knobs produces the same install behavior in pacquet. - **Item 12 — `childConcurrency`** ([upstream `getWorkspaceConcurrency`](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/config/reader/src/concurrency.ts#L25-L34); [`runGroups(...)`](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/building/during-install/src/index.ts#L124)). Adds `Config.child_concurrency: u32` with the upstream-matching default `min(4, availableParallelism())` and the negative-offset semantics (`n < 0` → `max(1, parallelism - |n|)`). `BuildModules::run` now dispatches each chunk's members across a bounded rayon thread pool via `par_iter().try_for_each` — chunks themselves remain sequential to preserve topological order. `ignored_builds` and `deps_state_cache` are wrapped in `Mutex` so the recursive memo and the dedup set survive concurrent chunk-member access. - **Item 14 — `unsafePerm`**. Adds `Config.unsafe_perm: bool` (default `true`) and threads it through to `RunPostinstallHooks`. When `false`, the executor sets `TMPDIR=node_modules/.tmp` for the spawn. The uid/gid drop side is a no-op upstream too because pnpm's [`@pnpm/npm-lifecycle`](https://github.com/pnpm/npm-lifecycle/blob/d2d8e790/index.js#L204-L220) never populates `opts.user` / `opts.group`. The POSIX auto-detect (`getuid() === 0 && setgid → unsafePerm = false`) needs `libc`, which isn't in `[workspace.dependencies]` yet — for now, root-run CI must set `unsafePerm: false` in yaml explicitly. Windows is force-overridden to `true` in `WorkspaceSettings::apply_to`, matching upstream's `process.platform === 'win32'` gate. - **Item 15 — `scriptsPrependNodePath`** ([`Config.scriptsPrependNodePath`](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/config/reader/src/Config.ts#L108)). Adds a tri-state `ScriptsPrependNodePath` enum in `pacquet-config` (custom serde `Deserialize` for the `boolean | "warn-only"` yaml shape) and converts to `pacquet_executor::ScriptsPrependNodePath` at the `BuildModules` call site so the executor crate stays free of serde wiring. Default is `Never` to match upstream's [`StrictBuildOptions.scriptsPrependNodePath: false`](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/building/after-install/src/extendBuildOptions.ts#L78). - **Item 16 — `getSubgraphToBuild` filter trimming**. Already implemented in pacquet's `get_subgraph_to_build` via the `child_should_be_built || needs_build || has_patch` gate. Confirmed during this slice; no code change needed. Why one PR: items 12 / 14 / 15 are three independent yaml-driven config knobs, all small. Bundling keeps the cross-crate signature churn from happening three times (`BuildModules`'s field list, `install_frozen_lockfile`'s call site, and the test fixtures all touch each one), and the upstream sources for the three settings live next to each other so the porting context is shared. |
||
|
|
2f64c727ea |
feat(executor): swallow optional-dep build failures and report via pnpm:skipped-optional-dependency (#397) (#419)
## Summary Closes item #6 of #397. Optional dependencies whose `postinstall` hooks fail no longer abort the install — pacquet now reports the failure through the `pnpm:skipped-optional-dependency` channel (reason `build_failure`) and continues, matching [pnpm v11's behavior at `building/during-install/src/index.ts:218-240`](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/building/during-install/src/index.ts#L218-L240). Three commits: - **`feat(lockfile): surface snapshot-level optional flag`** — pacquet's lockfile reader previously dropped the `snapshots[<key>].optional` field. Adding it as `pub optional: bool` on `SnapshotEntry` with `#[serde(default, skip_serializing_if = "is_false")]` so absent ↔ false and `false` never serializes. The flag is pre-computed by pnpm's resolver at install time (ALL-paths- optional fold), so pacquet trusts the precomputed value rather than re-deriving it — matching [`lockfileToDepGraph` at deps/graph-builder/src/lockfileToDepGraph.ts:315](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/deps/graph-builder/src/lockfileToDepGraph.ts#L315). - **`feat(reporter): add pnpm:skipped-optional-dependency event`** — new `LogEvent::SkippedOptionalDependency(SkippedOptionalDependencyLog)` variant + `SkippedOptionalPackage` + `SkippedOptionalReason` enum. Wire shape mirrors [pnpm's `SkippedOptionalDependencyMessage`](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/core/core-loggers/src/skippedOptionalDependencyLogger.ts). All four upstream reasons (`BuildFailure`, `UnsupportedEngine`, `UnsupportedPlatform`, `ResolutionFailure`) are declared even though only `BuildFailure` is emitted today — keeps the enum closed so the other emit sites can land without widening it. - **`feat(package-manager): swallow optional-dep build failures`** — `BuildModules.run` reads `snapshot.optional`. When `run_postinstall_hooks` returns an `Err`, the dep's optional flag decides: optional ⇒ emit a `pnpm:skipped-optional-dependency` event (`reason: build_failure`, `package.id` = the dep's pkg dir to match upstream's `depNode.dir`) and continue; non-optional ⇒ propagate as `BuildModulesError::LifecycleScript` so the install aborts. Two `cfg(unix)`-gated tests cover both branches. |
||
|
|
1452682669 |
feat(executor): port makeEnv, extendPath, and shell selection for lifecycle scripts (#397) (#418)
## Summary Closes three of the critical items in #397 by porting the corresponding behaviors from `@pnpm/npm-lifecycle@d2d8e790` and `pnpm/pnpm@b4f8f47ac2`: - **#1 — Lifecycle env vars.** New `make_env` module ports `makeEnv` and the surrounding env block in `lifecycle()` ([npm-lifecycle/index.js:73-104](https://github.com/pnpm/npm-lifecycle/blob/d2d8e790/index.js#L73-L104), [:354-414](https://github.com/pnpm/npm-lifecycle/blob/d2d8e790/index.js#L354-L414)) plus the pnpm wrapper's `extraEnv` additions ([runLifecycleHook.ts:119-124](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/exec/lifecycle/src/runLifecycleHook.ts#L119-L124)). Lifecycle scripts now see `npm_lifecycle_event`, `npm_lifecycle_script`, `npm_node_execpath`/`NODE`, `npm_package_json`, `npm_execpath`, `npm_package_*` (`name`, `version`, and recursive `config`/`engines`/`bin`), `npm_config_node_gyp`, `npm_config_user_agent`, `INIT_CWD`, `PNPM_SCRIPT_SRC_DIR`, and `TMPDIR` (when `unsafe_perm` is false). The spawn now strips inherited env (`env_clear()`) so leftover `npm_*` keys from a wrapping invocation cannot leak through. - **#2 — PATH ancestor walk.** New `extend_path` module ports `extendPath` ([npm-lifecycle/lib/extendPath.js:5-27](https://github.com/pnpm/npm-lifecycle/blob/d2d8e790/lib/extendPath.js#L5-L27)) plus the tri-state `scriptsPrependNodePath` gating ([:29-61](https://github.com/pnpm/npm-lifecycle/blob/d2d8e790/lib/extendPath.js#L29-L61)). For a dep at `<root>/node_modules/.pnpm/<slot>/node_modules/<pkg>`, `PATH` now contains the dep's own `.bin`, the `.pnpm` slot's `.bin`, the project root's `.bin`, the bundled `node-gyp-bin` (when supplied), `extra_bin_paths`, and finally the inherited PATH. - **#4 — Shell selection.** New `shell` module ports the [shell-selection block](https://github.com/pnpm/npm-lifecycle/blob/d2d8e790/index.js#L241-L252) and the [pnpm-side `.bat`/`.cmd` guard](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/exec/lifecycle/src/runLifecycleHook.ts#L63-L71). `cmd /d /s /c` on Windows, custom `scriptShell` on either platform, otherwise `sh -c`. The Windows-batch-file `scriptShell` case surfaces as `ERR_PNPM_INVALID_SCRIPT_SHELL_WINDOWS` (matching upstream's error code). `RunPostinstallHooks` grows seven new fields to surface these knobs; `BuildModules` passes safe defaults (`None` / `true` / `Never`) for all of them — full config plumbing for `user-agent`, `unsafe-perm`, `scripts-prepend-node-path`, `node-gyp` bundling, and `script-shell` are tracked as separate items in #397 (#14, #15) or follow-ups. ### Explicit non-goals in this PR Three caveats called out in #397 that are deliberately deferred: - `windowsVerbatimArguments` (Rust equivalent: `CommandExt::raw_arg`) is signalled by `SelectedShell.windows_verbatim_args` but not yet applied to the spawned `Command`. - `@yarnpkg/shell` / `shellEmulator: true` has no clean Rust port; pacquet ignores the flag for now. - `unsafe_perm` uid/gid drop (#14) — `BuildModules` passes `true`, which keeps current behavior (no TMPDIR creation, no privilege drop). ### Test parity Per the project guide ("port the relevant pnpm tests too whenever they translate"), this branch ports: - `test('makeEnv')` from [npm-lifecycle/test/index.js:97-124](https://github.com/pnpm/npm-lifecycle/blob/d2d8e790/test/index.js#L97-L124). - The `extendPath` ordering test from [npm-lifecycle/test/extendPath.test.js:5-8](https://github.com/pnpm/npm-lifecycle/blob/d2d8e790/test/extendPath.test.js#L5-L8). - `runLifecycleHook() does not set npm_config env vars` from [pnpm/exec/lifecycle/test/index.ts:65-77](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/exec/lifecycle/test/index.ts#L65-L77), adapted to a file-dump model so we don't need the IPC fixture. - `onlyOnWindows('pnpm shows error if script-shell is .cmd')` from [pnpm/exec/commands/test/index.ts:509-542](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/exec/commands/test/index.ts#L509-L542) and the custom-shell case from [:478-508](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/exec/commands/test/index.ts#L478-L508). Plus fills the gaps where upstream coverage is thin: tri-state `scriptsPrependNodePath`, two-level pnpm virtual-store PATH walks, `extra_bin_paths` ordering, TMPDIR gating on `unsafe_perm`, and `extra_env` precedence vs `npm_lifecycle_script`. |
||
|
|
a8e8dd3091 |
ci: setup dylint with perfectionist (#416)
Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
d685005de9 |
feat: lifecycle script execution and allowBuilds policy (#391)
## Summary - Add lifecycle script runner to `pacquet-executor`: preinstall, install, postinstall in correct order, `binding.gyp` auto-detection, `INIT_CWD`/`PNPM_SCRIPT_SRC_DIR`/PATH environment setup - Add `allowBuilds` build policy matching pnpm 11's default-deny behavior, with `dangerouslyAllowAllBuilds` escape hatch - Wire build step into the frozen-lockfile install path - Port `buildSequence` + `graphSequencer` so packages with `requires_build` run children-first - Add `pnpm:lifecycle` and `pnpm:ignored-scripts` reporter channels — script start/stdio/exit events flow through the same NDJSON pipeline `@pnpm/cli.default-reporter` consumes from upstream pnpm - Port 11 upstream lifecycle script tests as `known_failures` Upstream refs: - [`runPostinstallHooks`](https://github.com/pnpm/pnpm/blob/7e91e4b35f/exec/lifecycle/src/index.ts) - [`runLifecycleHook`](https://github.com/pnpm/pnpm/blob/80037699fb/exec/lifecycle/src/runLifecycleHook.ts) - [`createAllowBuildFunction`](https://github.com/pnpm/pnpm/blob/7e91e4b35f/building/policy/src/index.ts) - [`buildSequence`](https://github.com/pnpm/pnpm/blob/80037699fb/building/during-install/src/buildSequence.ts) - [`graphSequencer`](https://github.com/pnpm/pnpm/blob/80037699fb/deps/graph-sequencer/src/index.ts) - [`lifecycleLogger`](https://github.com/pnpm/pnpm/blob/80037699fb/core/core-loggers/src/lifecycleLogger.ts) - [`ignoredScriptsLogger`](https://github.com/pnpm/pnpm/blob/80037699fb/core/core-loggers/src/ignoredScriptsLogger.ts) ## What's implemented - `run_postinstall_hooks` in `pacquet-executor` faithfully ports pnpm's hook runner. The hook is now generic over `R: Reporter` and emits `pnpm:lifecycle` events through the standard pacquet reporter pipeline: `Script` before each spawn, one `Stdio` event per stdout/stderr line read through `Stdio::piped()` byline buffering, and `Exit` after the script returns - `AllowBuildPolicy` reads `pnpm.allowBuilds` and `pnpm.dangerouslyAllowAllBuilds` from `package.json` with correct tri-state semantics (allow/deny/ignored) - Default-deny matches pnpm 11: packages must be explicitly listed in `allowBuilds` to run their scripts - `BuildModules::run::<R>` runs lifecycle scripts for `requires_build: true` packages in the frozen-lockfile path and returns the sorted (peer-stripped) list of packages whose scripts were skipped because they weren't in `allowBuilds`. `InstallFrozenLockfile::run` emits one `pnpm:ignored-scripts` event with that list (always, even when empty), mirroring upstream - `build_sequence` walks the dep graph from each importer, keeps only nodes whose subtree contains a build node, and feeds that subgraph into `graph_sequencer` to produce topologically ordered chunks. `BuildModules` iterates the chunks in order so children build before parents - `AllowBuildPolicy` and `BuildModules.lockfile_dir` use the existing `requester` field (derived from `manifest.path().parent()`) instead of `std::env::current_dir()` - New `LogEvent::Lifecycle` and `LogEvent::IgnoredScripts` variants with wire-shape tests - Recording-fake reporter tests for `run_postinstall_hooks` (Script→Stdio→Exit ordering, non-zero exit propagation) and for `BuildModules::run` (sorted ignored-builds, explicit-deny exclusion). Existing `install_emits_pnpm_event_sequence` updated to expect the new `IgnoredScripts` event between `Stats` and `ImportingDone` - Unit tests for the build policy, key parsing, `graph_sequencer`, and `build_sequence` --------- Co-authored-by: Zoltan Kochan <z@kochan.io> |
||
|
|
0db25f85a2 |
refactor: re-organize code (#143)
## Summary * Move code that handles package managing to its own crate named `pacquet_package_manager`. * Convert functions with multiple arguments into structs. * Make `pacquet add` reuses the algorithm from `pacquet install`. * Achieved by having `pacquet add` creates an in-memory `package.json` and pass it to the `Install` subroutine. * Convert the error-prone tests in `crates/cli` into proper integration tests that invoke the `pacquet` binary. * Tests become shorter and more readable. * Solve race condition issues with `cargo test`. * All code with `set_current_dir` have been replaced by either integration tests or dependency injection. * Solve race condition issues with `cargo test`. * `thiserror` has been completely removed. `derive_more` is used in its stead. * `thiserror` cannot handle generic trait bound. * `derive_more` is already used to generate custom string types in the lockfile. * one less macro crate leads to slightly faster compile time. * `pacquet_package_json::PackageJson` has been renamed to `pacquet_package_manifest::PackageManifest`. * Other minor changes. |
||
|
|
cdb53246a1 | feat: add pacquet_diagnostics module | ||
|
|
71a705aee6 | feat: add criterion benchmarks | ||
|
|
888a613210 | fix: use sh to run commands instead of directly passing script | ||
|
|
9086e21ad0 | feat: use miette for diagnostics | ||
|
|
e49712744e | refactor: simplify error messages | ||
|
|
c76079d138 |
feat: add pacquet start command
|