Skip to content

Conversation

Yohe-Am
Copy link
Contributor

@Yohe-Am Yohe-Am commented Sep 20, 2025

  • Allows colliding shims by using secondary shim dirs
  • Duplicate shims also get aliases like myExec-myEnv-2

  • The change comes with new or modified tests
  • Hard-to-understand functions have explanatory comments
  • End-user documentation is updated to reflect the change

Summary by CodeRabbit

  • New Features
    • Environments now handle duplicate executable names via spillover bin directories (e.g., bin, bin2) with automatic PATH updates.
    • Optional environment display names label shims and create readable alias symlinks.
    • Activation scripts (POSIX and Fish) improved to manage multi-path variables more reliably.
  • Chores
    • Exposed filesystem utilities for broader reuse.
  • Tests
    • Added integration test validating duplicate-executable spillover and alias behavior in Bash.

@coderabbitai
Copy link

coderabbitai bot commented Sep 20, 2025

📝 Walkthrough

Walkthrough

Introduces a new test port (dup_test) and updates POSIX env cooking to handle env labels, shim spillover, and alias symlinks. Adjusts function signatures to pass optional env_name and to return shim levels. Exposes std_fs via re-export. Adds a test validating duplicate exec handling, spillover, and aliases.

Changes

Cohort / File(s) Summary
New test port: dup_test
ports/dup_test.ts
Adds a port manifest and Port implementation that writes a simple shell script (dup), returns empty exec env, validates config, and supports download/install. Exposes DupTestInstallConf and default conf().
POSIX env cook, shims, activators
src/ghjk/systems/envs/posix.rs, src/ghjk/systems/envs.rs
Adds env_name to cook; derives env_label for shims; shim_link_paths now returns max level and can create alias symlinks; path vars switch to Vec per key; updates write_activators, build_posix_script, build_fish_script signatures and logic; adjusts callsite argument order in envs.rs (env_key/env_name).
Deno utils re-export
src/deno_utils/mod.ts
Re-exports std_fs from ./deps.ts.
Tests for spillover and aliases
tests/envs.ts
Imports dup port and adds prov_exec_spillover_bash test verifying two dup shims across bin/bin2, PATH visibility, and alias symlinks (dup-main-1, dup-main-2).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Test as Test (prov_exec_spillover_bash)
  participant Env as EnvsCtx
  participant POSIX as posix::cook
  participant Shim as shim_link_paths
  participant Act as write_activators
  participant FS as Filesystem
  participant Dup as dup (shimmed exec)

  Test->>Env: activate("main")
  Env->>POSIX: cook(recipe, env_key, env_name?, env_dir, loaders)
  POSIX->>POSIX: derive env_label (env_name or sanitized env_key)
  POSIX->>Shim: link bin targets (alias_env_label=env_label)
  Shim-->>POSIX: max_bin_level (e.g., 2)
  POSIX->>Shim: link lib/include targets
  Shim-->>POSIX: max levels
  POSIX->>POSIX: build path_vars as Vec per var (spillover dirs)
  POSIX->>Act: write_activators(env_vars, path_vars, hooks, aliases)
  Act->>FS: write POSIX/Fish activation scripts
  Note right of Act: PATH includes bin, bin2, ... per level
  Test->>Dup: run dup (via bin/dup)
  Dup-->>Test: "one"
  Test->>Dup: run dup (via bin2/dup or alias symlink)
  Dup-->>Test: "two"
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description gives a brief WHAT (secondary shim dirs and aliased duplicate shims) and notes tests were added, but it does not follow the repository template: it omits the WHY rationale and the required Migration notes and does not call out important public/signature changes visible in the diff (for example posix::cook now accepts env_name, shim_link_paths signature/return changed, and write_activators/build_* now accept path_vars as Vecs), making the description insufficient for reviewers and downstream users. Expand the description to follow the template by adding a clear WHAT with affected files and user-visible behavior, a WHY explaining why spillover and aliasing are necessary, and a Migration notes section with concrete update steps; explicitly list public/signature changes (posix::cook, shim_link_paths, write_activators, build_posix_script/build_fish_script) and show example call-site fixes, then complete the checklist and update end-user docs and in-code explanatory comments as needed.
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title "fix: shim spillover" is concise and directly corresponds to the primary change in the PR (adding secondary shim directories and handling colliding shims with generated aliases), so it communicates the main intent without noise.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/shim-spillover

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/deno_utils/mod.ts (1)

262-269: Unbounded download request: add a configurable timeout.

.timeout(undefined) can hang indefinitely on network stalls. Add an optional timeout to args and default it to a sane value.

Apply this diff:

 export type DownloadFileArgs = {
   downloadPath: string;
   tmpDirPath: string;
   url: string;
   name?: string;
   mode?: number;
   headers?: Record<string, string>;
+  timeout?: dax.Delay; // optional network timeout
 };

@@
   await $.request(url)
     .header(headers)
-    .timeout(undefined)
+    .timeout(args.timeout ?? "10m")
     .pipeToPath(tmpFilePath, { create: true, mode });

Also applies to: 293-296

🧹 Nitpick comments (9)
src/deno_utils/mod.ts (3)

159-167: Harden NotFound handling and avoid swallowing unknown throwables.

The current check may accidentally swallow non‑Error throws and relies on name. Prefer Deno’s typed error.

Apply this diff:

       async removeIfExists(path: Path | string) {
         const pathRef = $.path(path);
         try {
           await pathRef.remove({ recursive: true });
         } catch (err) {
-          if (err! instanceof Error && err.name != "NotFound") {
-            throw err;
-          }
+          if (err instanceof Deno.errors.NotFound) {
+            // ignore
+          } else {
+            throw err;
+          }
         }
         return pathRef;
       },

175-178: Deduplicate worker‑context helpers; keep one and add an alias.

inWorker() and isInWorkerContext() are identical. Keep one to avoid drift and provide a back‑compat alias.

Apply this diff:

 export function inWorker() {
   return typeof WorkerGlobalScope !== "undefined" &&
     self instanceof WorkerGlobalScope;
 }
+// Back-compat alias; prefer `inWorker`.
+export const isInWorkerContext = inWorker;
@@
-export function isInWorkerContext() {
-  return typeof WorkerGlobalScope !== "undefined" &&
-    self instanceof WorkerGlobalScope;
-}
+// (removed duplicate; use alias above)

Also applies to: 503-506


209-216: Be explicit about protocol checks.

Use strict comparisons for clarity and to avoid false positives.

Apply this diff:

-  if (url.protocol.match(/^http/)) {
+  if (url.protocol === "http:" || url.protocol === "https:") {
     let request = $.request(url).timeout(timeout);
tests/envs.ts (1)

209-237: Nice spillover coverage; add PATH-execution assertions for aliases

Current checks execute alias symlinks via absolute paths. Consider also asserting they resolve on PATH after activation (e.g., type dup-main-1 and $(dup-main-1)), which future-proofs PATH handling.

@@
 [ "$(.ghjk/envs/main/shims/bin/dup-main-1)" = "one" ] || exit 107
 [ "$(.ghjk/envs/main/shims/bin/dup-main-2)" = "two" ] || exit 108
+type dup-main-1 >/dev/null 2>&1 || exit 109
+type dup-main-2 >/dev/null 2>&1 || exit 110
+[ "$(dup-main-1)" = "one" ] || exit 111
+[ "$(dup-main-2)" = "two" ] || exit 112
ports/dup_test.ts (1)

51-57: Create parent dir for bin and make script slightly more robust

Ensure bin/ exists before writing and avoid partial writes on error.

   override async download(args: DownloadArgs) {
     const conf = confValidator.parse(args.config);
-    await $.path(args.downloadPath).join("bin", "dup").writeText(
-      `#!/bin/sh\necho ${conf.output}`,
-      { mode: 0o700 },
-    );
+    const binDir = $.path(args.downloadPath).join("bin");
+    await binDir.mkdirp();
+    await binDir.join("dup").writeText(
+      `#!/bin/sh
+set -e
+printf '%s\n' "${conf.output}"`,
+      { mode: 0o700 },
+    );
   }
src/ghjk/systems/envs/posix.rs (4)

90-103: Alias label sanitization is sensible

Alnum/-/_ only and short-key fallback are reasonable. Consider documenting max label length if you ever enforce one.


175-253: shim_link_paths: deterministic ordering and alias target

  • Ordering: expansion via glob::glob() may not be deterministic across filesystems. If globbing is used for execs, you may see flakiness in which entry lands in bin/ vs bin2/. Consider stabilizing with a sort after expansion (only when inputs contain globs), or document that install order determines priority and avoid globs in execs.
  • Alias target: alias symlinks point to the real entry, not the created shim. That’s fine, but if you later rewrite shim targets (e.g., for wrappers), aliases won’t follow. Linking aliases to the created shim path would keep behavior uniform.
-            for entry in glob::glob(path_str)? {
-                expanded.push(entry?);
-            }
+            // NOTE: glob expansion order may vary; collect then sort for stability
+            let mut matches: Vec<PathBuf> = glob::glob(path_str)?.collect::<Result<_, _>>()?;
+            matches.sort();
+            expanded.extend(matches);

533-547: Use fixed-string grep for PATH cleanup to avoid regex pitfalls

grep -vE "^...$" treats . and other metachars specially. Use -F/-x for exact fixed-string matching.

-            writeln!(
-                buf,
-                r#"GHJK_CLEANUP_POSIX=$GHJK_CLEANUP_POSIX'{key}=$(echo "${key}" | tr ":" "\n" | grep -vE '\''"^{safe_val}$"'\'' | tr "\n" ":");{key}="${{{key}%:}}";';"#
-            )?;
+            writeln!(
+                buf,
+                r#"GHJK_CLEANUP_POSIX=$GHJK_CLEANUP_POSIX'{key}=$(echo "${key}" | tr ":" "\n" | grep -vFx '\''{safe_val}'\'' | tr "\n" ":");{key}="${{{key}%:}}";';"#
+            )?;

713-716: Typo in fish activator header

Should read “should not be executed directly.”

-# it should be executed directly
+# it should not be executed directly
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9a9731e and f57aa72.

📒 Files selected for processing (5)
  • ports/dup_test.ts (1 hunks)
  • src/deno_utils/mod.ts (1 hunks)
  • src/ghjk/systems/envs.rs (1 hunks)
  • src/ghjk/systems/envs/posix.rs (13 hunks)
  • tests/envs.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-09T06:06:50.518Z
Learnt from: Yohe-Am
PR: metatypedev/ghjk#130
File: ports/lade_ghrel.port.ts:22-22
Timestamp: 2025-08-09T06:06:50.518Z
Learning: In ghjk port manifests (files matching pattern `ports/*.port.ts` or `ports/*.ts`), the `version` field in the manifest object represents the version of the port module itself, not the version of the tool/software that the port installs. The actual tool version is typically passed via `args.installVersion` parameter in methods like `downloadUrls()`.

Applied to files:

  • ports/dup_test.ts
🧬 Code graph analysis (1)
ports/dup_test.ts (3)
src/deno_ports/mod.ts (1)
  • osXarch (34-41)
src/sys_deno/ports/types.ts (3)
  • InstallConfigSimple (258-260)
  • DownloadArgs (347-350)
  • InstallArgs (352-356)
src/deno_utils/mod.ts (2)
  • $ (106-173)
  • std_fs (20-20)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: test-e2e (macos-13, darwin/x86_64)
  • GitHub Check: test-e2e (macos-14, darwin/aarch64)
  • GitHub Check: test-e2e (ubuntu-22.04, linux/x86_64)
  • GitHub Check: test-e2e (ubuntu-22.04-arm, linux/aarch64)
  • GitHub Check: test-pre-commit
🔇 Additional comments (11)
src/deno_utils/mod.ts (1)

20-20: Expose std_fs via barrel export — looks good.

This aligns with existing Path re‑export and helps consumers import from a single module. Please confirm no cycles with deps.ts and update docs since this expands the public surface.

tests/envs.ts (1)

9-9: Good: dedicated test port for duplicate execs

Importing the dup_test port isolates spillover behavior and keeps tests deterministic.

ports/dup_test.ts (3)

59-64: Install logic is clean

Remove-if-exists then copy keeps installs idempotent and simple.


41-49: No-env execution is appropriate for duplicate instances

Returning {} from execEnv() avoids cross-instance env leakage. listAll() is fine for this test port.


18-24: Manifest semantics LGTM

Version is for the port module (not tool), platforms cover all OS/arch via osXarch.

src/ghjk/systems/envs/posix.rs (5)

12-15: New env_name parameter: good defaulting behavior

Preferring env_name for GHJK_ENV with fallback to env_key is the right UX.


37-40: GHJK_ENV assignment uses env_name correctly

Simple and explicit—no concerns.


104-108: shim_link_paths return levels are correctly threaded

Levels per category enable PATH spillover; passing the label only for bin is appropriate.


124-147: PATH/LIB/INCLUDE spillover construction looks correct

The dirs_for helper produces bin, bin2, … and mirrors to lib/include vars. Nice.


164-171: Join multiple path values when returning env_vars

Joining late keeps activator generation working on structured data. Good separation.

src/ghjk/systems/envs.rs (1)

429-433: Argument order to posix::cook verified — no other callers found

Repository search shows only the call at src/ghjk/systems/envs.rs:426 (the updated invocation), so no remaining callers use the old parameter order.

Comment on lines +760 to 770
for (key, values) in path_vars {
// prepend all entries in order (bin, bin2, ...)
for val in values.iter().rev() {
let safe_val = val.replace("\\", "\\\\").replace("'", "'\\''");
writeln!(
buf,
r#"set --global --export --prepend {key} "{safe_val}";"#
)?;
}
writeln!(buf)?;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fish deactivation doesn’t remove injected PATH entries

You prepend spillover dirs but don’t append matching cleanup to GHJK_CLEANUP_FISH. Re-activations will accumulate duplicates in fish shells.

-    for (key, values) in path_vars {
-        // prepend all entries in order (bin, bin2, ...)
-        for val in values.iter().rev() {
+    for (key, values) in path_vars {
+        // prepend all entries in order (bin, bin2, ...)
+        for val in values.iter().rev() {
             let safe_val = val.replace("\\", "\\\\").replace("'", "'\\''");
             writeln!(
                 buf,
                 r#"set --global --export --prepend {key} "{safe_val}";"#
             )?;
-        }
+        }
+        // add cleanup to filter out each injected value exactly
+        for val in values {
+            let safe_val = val.replace("\\", "\\\\").replace("'", "'\\''");
+            writeln!(
+                buf,
+                r#"set --global --append GHJK_CLEANUP_FISH 'set -l __ghjk_new; for p in ${key}; if test "$p" != "{safe_val}"; set -a __ghjk_new "$p"; end; end; set --global --export {key} $__ghjk_new;';"#
+            )?;
+        }
         writeln!(buf)?;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (key, values) in path_vars {
// prepend all entries in order (bin, bin2, ...)
for val in values.iter().rev() {
let safe_val = val.replace("\\", "\\\\").replace("'", "'\\''");
writeln!(
buf,
r#"set --global --export --prepend {key} "{safe_val}";"#
)?;
}
writeln!(buf)?;
}
for (key, values) in path_vars {
// prepend all entries in order (bin, bin2, ...)
for val in values.iter().rev() {
let safe_val = val.replace("\\", "\\\\").replace("'", "'\\''");
writeln!(
buf,
r#"set --global --export --prepend {key} "{safe_val}";"#
)?;
}
// add cleanup to filter out each injected value exactly
for val in values {
let safe_val = val.replace("\\", "\\\\").replace("'", "'\\''");
writeln!(
buf,
r#"set --global --append GHJK_CLEANUP_FISH 'set -l __ghjk_new; for p in ${key}; if test "$p" != "{safe_val}"; set -a __ghjk_new "$p"; end; end; set --global --export {key} $__ghjk_new;';"#
)?;
}
writeln!(buf)?;
}

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.

1 participant