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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,9 @@ packages you need for different projects as you navigate in your shell.
## Installation

```sh
echo 'eval "$(pkgx dev --shellcode)"' >> ~/.zshrc
pkgx dev integrate
```

We support bashlike shells (adapt the rc file above). Fish support is welcome,
but I don’t understand Fish so please PR!

> [!NOTE]
>
> `pkgx` is a required dependency.
Expand All @@ -20,6 +17,22 @@ but I don’t understand Fish so please PR!
> brew install pkgxdev/made/pkgx || sh <(curl https://pkgx.sh)
> ```

> `pkgx dev integrate` looks for and edits known `shell.rc` files adding one
> line:
>
> ```sh
> eval "$(pkgx dev --shellcode)"
> ```
>
> If you don’t trust us (good on you), then do a dry run first:
>
> ```sh
> pkgx dev integrate --dry-run
> ```

> We support **Bash** and **Zsh**. We would love to support more shells. PRs
> very welcome.

> [!TIP]
> If you like, preview the shellcode: `pkgx dev --shellcode`.

Expand Down
8 changes: 8 additions & 0 deletions app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import shellcode from "./src/shellcode().ts";
import sniff from "./src/sniff.ts";
import shell_escape from "./src/shell-escape.ts";
import app_version from "./src/app-version.ts";
import integrate from "./src/integrate.ts";

switch (Deno.args[0]) {
case "--help": {
Expand All @@ -25,6 +26,13 @@ switch (Deno.args[0]) {
console.log(`dev ${app_version}`);
Deno.exit(0);
break; // deno lint insists
case "integrate":
await integrate("install", { dryrun: Deno.args[1] == "--dry-run" });
Deno.exit(0);
break;
case "deintegrate":
await integrate("uninstall", { dryrun: Deno.args[1] == "--dry-run" });
Deno.exit(0);
}

const snuff = await sniff(Path.cwd());
Expand Down
1 change: 1 addition & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

117 changes: 117 additions & 0 deletions src/integrate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import readLines from "libpkgx/utils/read-lines.ts";
import { readAll, writeAll } from "jsr:@std/io";
import { Path, utils } from "libpkgx";
const { flatmap } = utils;

export default async function (
op: "install" | "uninstall",
{ dryrun }: { dryrun: boolean },
) {
let opd_at_least_once = false;
const encode = ((e) => e.encode.bind(e))(new TextEncoder());

const fopts = { read: true, ...dryrun ? {} : { write: true, create: true } };

here: for (const [file, line] of shells()) {
const fd = await Deno.open(file.string, fopts);
try {
let pos = 0;
for await (const readline of readLines(fd)) {
if (readline.trim().endsWith("# https://github.com/pkgxdev/dev")) {
if (op == "install") {
console.error("hook already integrated:", file);
continue here;
} else if (op == "uninstall") {
// we have to seek because readLines is buffered and thus the seek pos is probs already at the file end
await fd.seek(pos + readline.length + 1, Deno.SeekMode.Start);
const rest = await readAll(fd);

if (!dryrun) await fd.truncate(pos); // deno has no way I can find to truncate from the current seek position
await fd.seek(pos, Deno.SeekMode.Start);
if (!dryrun) await writeAll(fd, rest);

opd_at_least_once = true;
console.error("removed hook:", file);

continue here;
}
}

pos += readline.length + 1; // the +1 is because readLines() truncates it
}

if (op == "install") {
const byte = new Uint8Array(1);
if (pos) {
await fd.seek(0, Deno.SeekMode.End); // potentially the above didn't reach the end
while (true && pos > 0) {
await fd.seek(-1, Deno.SeekMode.Current);
await fd.read(byte);
if (byte[0] != 10) break;
await fd.seek(-1, Deno.SeekMode.Current);
pos -= 1;
}

if (!dryrun) {
await writeAll(
fd,
encode(`\n\n${line} # https://github.com/pkgxdev/dev\n`),
);
}
}
opd_at_least_once = true;
console.error(`${file} << \`${line}\``);
}
} finally {
fd.close();
}
}
if (dryrun && opd_at_least_once) {
console.error(
"%cthis was a dry-run. %cnothing was changed.",
"color: #5f5fff",
"color: initial",
);
} else {switch (op) {
case "uninstall":
if (!opd_at_least_once) {
console.error("nothing to deintegrate found");
}
break;
case "install":
if (opd_at_least_once) {
console.log(
"now %crestart your terminal%c for `dev` hooks to take effect",
"color: #5f5fff",
"color: initial",
);
}
}}
}

function shells(): [Path, string][] {
const eval_ln = 'eval "$(pkgx dev --shellcode)"';

const zdotdir = flatmap(Deno.env.get("ZDOTDIR"), Path.abs) ?? Path.home();
const zshpair: [Path, string] = [zdotdir.join(".zshrc"), eval_ln];

const candidates: [Path, string][] = [
zshpair,
[Path.home().join(".bashrc"), eval_ln],
[Path.home().join(".bash_profile"), eval_ln],
];

const viable_candidates = candidates.filter(([file]) => file.exists());

if (viable_candidates.length == 0) {
if (Deno.build.os == "darwin") {
/// macOS has no .zshrc by default and we want mac users to get a just works experience
return [zshpair];
} else {
console.error("no `.shellrc` files found");
Deno.exit(1);
}
}

return viable_candidates;
}
Loading