From 09842f7be2075d6cf8691251db42eb20b729e433 Mon Sep 17 00:00:00 2001 From: Max Howell Date: Sat, 5 Apr 2025 10:26:43 -0400 Subject: [PATCH 01/10] `dev status` for `pkgm` --- README.md | 66 ++++++++++++++++++++++++++++++++++++++++++++-- app.ts | 30 ++++++++++++++++----- deno.json | 2 +- deno.lock | 4 +++ src/dump.ts | 11 +++++--- src/shellcode().ts | 18 +++++++------ 6 files changed, 111 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index c4398d4..f1e6333 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,67 @@ dev, across your team and in production. ## Getting Started +Since `dev` v1.7.0 we integrate with `pkgm` and this is the recommended way to +use `dev`. + +> [!IMPORTANT] +> +> `dev` must be installed to `/usr/local/bin/dev` for this route to work: +> +> ```sh +> sudo pkgm install dev # use of `shim` is also fine +> ``` + +```sh +$ cd my-project +$ ls +package.json + +$ node --version +command not found: node + +$ sudo pkgm install dev node +$ node --version && which node +v23.11.0 +/usr/local/bin/node + +$ cat package.json | jq .engines +{ + "node": "^20" +} + +$ dev +activated `~/my-project` (+node^20) + +$ node --version && which node +v20.19.0 +/usr/local/bin/node + +$ cd .. +$ node --version && which node +v23.11.0 +/usr/local/bin/node + +$ cd - +$ node --version && which node +v20.19.0 +/usr/local/bin/node +``` + +`pkgm` installs `dev`-aware packages to `/usr/local/bin`. Provided you have +`/usr/local/bin/dev` installed and you have activated `dev` in your project +directories the `node` that is invoked is swapped out *when invoked*. + +This is the recommended way to use `dev` because it works everywhere and not +just the terminal. + +## `dev` via Shellcode + +Shellcode works *as well* and is your preference. It has notable caveats +with regard to use in tools like editors. It also requires you to add +shellcode to your `shell.rc` files and thus is more intrusive (depending on +your outlook). + ```sh pkgx dev integrate ``` @@ -78,6 +139,7 @@ command not found: node > ```sh > $ cd my-project > $ eval "$(pkgx dev)" +> +deno^2 > ``` > > The devenv will only exist for the duration of your shell session. @@ -166,8 +228,8 @@ environment. We recommend Visual Studio Code, `dev && code .` works great. - uses: pkgxdev/dev@v1 ``` -Installs needed packages and sets up the environment the same as `dev` does in -your terminal. +Installs needed packages (via `pkgx`) and sets up the environment the same as +`dev` does in your terminal. ## Contributing diff --git a/app.ts b/app.ts index ffcbb17..1168d91 100755 --- a/app.ts +++ b/app.ts @@ -1,23 +1,26 @@ -#!/usr/bin/env -S pkgx deno^2 run -A +#!/usr/bin/env -S pkgx --quiet deno^2 run -A //TODO if you step into dev-dir/subdir and type `dev` does it find the root properly? //TODO dev off uses PWD which may not be correct if in subdir (obv) import { Path } from "libpkgx"; -import shellcode from "./src/shellcode().ts"; +import shellcode, { datadir } from "./src/shellcode().ts"; import app_version from "./src/app-version.ts"; import integrate from "./src/integrate.ts"; -import { parse } from "jsr:@std/flags"; +import { parseArgs } from "jsr:@std/cli@^1/parse-args"; import dump from "./src/dump.ts"; +import sniff from "./src/sniff.ts"; -const parsedArgs = parse(Deno.args, { +const parsedArgs = parseArgs(Deno.args, { alias: { n: "dry-run", "just-print": "dry-run", recon: "dry-run", v: "version", h: "help", + q: "quiet", }, + collect: ["quiet"], boolean: ["help", "version", "shellcode"], default: { "dry-run": false, @@ -26,7 +29,7 @@ const parsedArgs = parse(Deno.args, { if (parsedArgs.help) { const status = await new Deno.Command("pkgx", { - args: ["gh", "repo", "view", "pkgxdev/dev"], + args: ["--quiet", "gh", "repo", "view", "pkgxdev/dev"], }).spawn().status; Deno.exit(status.code); } else if (parsedArgs.shellcode) { @@ -36,6 +39,7 @@ if (parsedArgs.help) { } else { const subcommand = parsedArgs._[0]; const dryrun = parsedArgs["dry-run"] as boolean; + const quiet = parsedArgs["quiet"] != undefined; switch (subcommand) { case "integrate": await integrate("install", { dryrun }); @@ -43,9 +47,23 @@ if (parsedArgs.help) { case "deintegrate": await integrate("uninstall", { dryrun }); break; + case "status": + { + const cwd = Path.cwd(); + if ( + datadir().join(cwd.string.slice(1), "dev.pkgx.activated").isFile() + ) { + //FIXME probably slower than necessary + const { pkgs } = await sniff(cwd); + Deno.exit(pkgs.length == 0 ? 1 : 0); + } else { + Deno.exit(1); + } + } + break; default: { const cwd = Path.cwd().join(subcommand as string); - await dump(cwd, { dryrun }); + await dump(cwd, { dryrun, quiet }); } } } diff --git a/deno.json b/deno.json index f2c9b1f..39e8daf 100644 --- a/deno.json +++ b/deno.json @@ -12,7 +12,7 @@ }, "imports": { "libpkgx": "https://raw.githubusercontent.com/pkgxdev/libpkgx/refs/tags/v0.21.0/mod.ts", - "libpkgx/": "https://raw.githubusercontent.com/pkgxdev/libpkgx/refs/tags/v0.20.1/src/", + "libpkgx/": "https://raw.githubusercontent.com/pkgxdev/libpkgx/refs/tags/v0.21.0/src/", "is-what": "https://deno.land/x/is_what@v4.1.15/src/index.ts", "outdent": "https://deno.land/x/outdent@v0.8.0/mod.ts" } diff --git a/deno.lock b/deno.lock index 8576c98..96d12a1 100644 --- a/deno.lock +++ b/deno.lock @@ -5,6 +5,7 @@ "jsr:@std/assert@0.224": "0.224.0", "jsr:@std/assert@^1.0.6": "1.0.6", "jsr:@std/bytes@^1.0.2": "1.0.2", + "jsr:@std/cli@1": "1.0.11", "jsr:@std/crypto@1": "1.0.3", "jsr:@std/encoding@1": "1.0.5", "jsr:@std/flags@*": "0.224.0", @@ -40,6 +41,9 @@ "@std/bytes@1.0.2": { "integrity": "fbdee322bbd8c599a6af186a1603b3355e59a5fb1baa139f8f4c3c9a1b3e3d57" }, + "@std/cli@1.0.11": { + "integrity": "ec219619fdcd31bcf0d8e53bee1e2706ec9a02f70255365a094f69755dadd340" + }, "@std/crypto@1.0.3": { "integrity": "a2a32f51ddef632d299e3879cd027c630dcd4d1d9a5285d6e6788072f4e51e7f" }, diff --git a/src/dump.ts b/src/dump.ts index fe0c90d..62ca976 100644 --- a/src/dump.ts +++ b/src/dump.ts @@ -2,7 +2,10 @@ import { Path, utils } from "libpkgx"; import sniff from "./sniff.ts"; import shell_escape from "./shell-escape.ts"; -export default async function (cwd: Path, opts: { dryrun: boolean }) { +export default async function ( + cwd: Path, + opts: { dryrun: boolean; quiet: boolean }, +) { const snuff = await sniff(cwd); if (snuff.pkgs.length === 0 && Object.keys(snuff.env).length === 0) { @@ -20,7 +23,7 @@ export default async function (cwd: Path, opts: { dryrun: boolean }) { if (snuff.pkgs.length > 0) { const cmd = new Deno.Command("pkgx", { - args: [...pkgspecs], + args: ["--quiet", ...pkgspecs], stdout: "piped", env: { CLICOLOR_FORCE: "1" }, // unfortunate }).spawn(); @@ -54,7 +57,9 @@ export default async function (cwd: Path, opts: { dryrun: boolean }) { " ", ); - console.error("%c%s", "color: green", pkgspecs.join(" ")); + if (!opts.quiet) { + console.error("%c%s", "color: green", pkgspecs.join(" ")); + } console.log(` eval "_pkgx_dev_try_bye() { diff --git a/src/shellcode().ts b/src/shellcode().ts index a1f4a34..caf096d 100644 --- a/src/shellcode().ts +++ b/src/shellcode().ts @@ -1,10 +1,6 @@ import { Path } from "libpkgx"; export default function shellcode() { - const datadir = new Path( - Deno.env.get("XDG_DATA_HOME")?.trim() || platform_data_home_default(), - ).join("pkgx", "dev"); - // find self const dev_cmd = Deno.env.get("PATH")?.split(":").map((path) => Path.abs(path)?.join("dev") @@ -18,7 +14,7 @@ _pkgx_chpwd_hook() { if ! type _pkgx_dev_try_bye >/dev/null 2>&1 || _pkgx_dev_try_bye; then dir="$PWD" while [ "$dir" != "/" ]; do - if [ -f "${datadir}/$dir/dev.pkgx.activated" ]; then + if [ -f "${datadir()}/$dir/dev.pkgx.activated" ]; then eval "$(${dev_cmd})" break fi @@ -31,7 +27,7 @@ dev() { case "$1" in off) if type -f _pkgx_dev_try_bye >/dev/null 2>&1; then - rm "${datadir}$PWD/dev.pkgx.activated" + rm "${datadir()}$PWD/dev.pkgx.activated" PWD=/ _pkgx_dev_try_bye else echo "no devenv" >&2 @@ -40,8 +36,8 @@ dev() { if [ "$2" ]; then "${dev_cmd}" "$@" elif ! type -f _pkgx_dev_try_bye >/dev/null 2>&1; then - mkdir -p "${datadir}$PWD" - touch "${datadir}$PWD/dev.pkgx.activated" + mkdir -p "${datadir()}$PWD" + touch "${datadir()}$PWD/dev.pkgx.activated" eval "$(${dev_cmd})" else echo "devenv already active" >&2 @@ -74,6 +70,12 @@ fi `.trim(); } +export function datadir() { + return new Path( + Deno.env.get("XDG_DATA_HOME")?.trim() || platform_data_home_default(), + ).join("pkgx", "dev"); +} + function platform_data_home_default() { const home = Path.home(); switch (Deno.build.os) { From 765ce7db6b57023f1a9c9dd0d24ef5b745860a5a Mon Sep 17 00:00:00 2001 From: Max Howell Date: Mon, 7 Apr 2025 11:25:15 -0400 Subject: [PATCH 02/10] Fixes #37 --- README.md | 9 ++++----- app.ts | 2 +- src/shellcode().ts | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f1e6333..e6392ba 100644 --- a/README.md +++ b/README.md @@ -61,17 +61,16 @@ v20.19.0 `pkgm` installs `dev`-aware packages to `/usr/local/bin`. Provided you have `/usr/local/bin/dev` installed and you have activated `dev` in your project -directories the `node` that is invoked is swapped out *when invoked*. +directories the `node` that is invoked is swapped out _when invoked_. This is the recommended way to use `dev` because it works everywhere and not just the terminal. ## `dev` via Shellcode -Shellcode works *as well* and is your preference. It has notable caveats -with regard to use in tools like editors. It also requires you to add -shellcode to your `shell.rc` files and thus is more intrusive (depending on -your outlook). +Shellcode works _as well_ and is your preference. It has notable caveats with +regard to use in tools like editors. It also requires you to add shellcode to +your `shell.rc` files and thus is more intrusive (depending on your outlook). ```sh pkgx dev integrate diff --git a/app.ts b/app.ts index 1168d91..4ff1e7c 100755 --- a/app.ts +++ b/app.ts @@ -21,7 +21,7 @@ const parsedArgs = parseArgs(Deno.args, { q: "quiet", }, collect: ["quiet"], - boolean: ["help", "version", "shellcode"], + boolean: ["help", "version", "shellcode", "quiet"], default: { "dry-run": false, }, diff --git a/src/shellcode().ts b/src/shellcode().ts index caf096d..3293bd9 100644 --- a/src/shellcode().ts +++ b/src/shellcode().ts @@ -13,9 +13,9 @@ export default function shellcode() { _pkgx_chpwd_hook() { if ! type _pkgx_dev_try_bye >/dev/null 2>&1 || _pkgx_dev_try_bye; then dir="$PWD" - while [ "$dir" != "/" ]; do + while [ "$dir" != / -a "$dir" != . ]; do if [ -f "${datadir()}/$dir/dev.pkgx.activated" ]; then - eval "$(${dev_cmd})" + eval "$(${dev_cmd})" "$dir" break fi dir="$(dirname "$dir")" From a4193178acd5971a04fab3bc48b1b3e3a9c8af25 Mon Sep 17 00:00:00 2001 From: Max Howell Date: Mon, 7 Apr 2025 11:48:20 -0400 Subject: [PATCH 03/10] wip --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index 97174e0..6b424c8 100644 --- a/action.yml +++ b/action.yml @@ -9,7 +9,7 @@ inputs: runs: using: composite steps: - - uses: pkgxdev/setup@v3 + - uses: pkgxdev/setup@v4 - run: | TMP="$(mktemp)" From 46bd9bc94350d05ae390d5bf07c9e1aeab71e456 Mon Sep 17 00:00:00 2001 From: Max Howell Date: Mon, 7 Apr 2025 12:26:45 -0400 Subject: [PATCH 04/10] wip --- action.js | 6 +++++- action.yml | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/action.js b/action.js index 6ba0255..40c00d0 100644 --- a/action.js +++ b/action.js @@ -13,7 +13,7 @@ const stripQuotes = (str) => const replaceEnvVars = (str) => { const value = str .replaceAll( - /\$\{([a-zA-Z0-9_]+):\+:\$[a-zA-Z0-9_]+\}/g, + /\$\{([a-zA-Z0-9_]+):[+-]:\$[a-zA-Z0-9_]+\}/g, (_, key) => ((v) => v ? `:${v}` : "")(process.env[key]), ) .replaceAll(/\$\{([a-zA-Z0-9_]+)\}/g, (_, key) => process.env[key] ?? "") @@ -21,7 +21,11 @@ const replaceEnvVars = (str) => { return value; }; +let found = false; + readInterface.on("line", (line) => { + if (!found) found = line == "set -a"; + if (!found) return; const match = line.match(/^([^=]+)=(.*)$/); if (match) { const [_, key, value_] = match; diff --git a/action.yml b/action.yml index 6b424c8..f0b6dae 100644 --- a/action.yml +++ b/action.yml @@ -4,6 +4,7 @@ description: inputs: path: + description: path that should be evaluated by `dev` required: false runs: From 679bfca8050cd3e40eb06791822c08ba90b6c9b2 Mon Sep 17 00:00:00 2001 From: Max Howell Date: Mon, 7 Apr 2025 12:31:56 -0400 Subject: [PATCH 05/10] wip --- action.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.js b/action.js index 40c00d0..f4c51f3 100644 --- a/action.js +++ b/action.js @@ -24,7 +24,7 @@ const replaceEnvVars = (str) => { let found = false; readInterface.on("line", (line) => { - if (!found) found = line == "set -a"; + if (!found) found = line.trim() == "set -a"; if (!found) return; const match = line.match(/^([^=]+)=(.*)$/); if (match) { From a517ad2f8340746ef236d318264a718ed978b206 Mon Sep 17 00:00:00 2001 From: Max Howell Date: Mon, 7 Apr 2025 12:34:26 -0400 Subject: [PATCH 06/10] wip --- action.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.js b/action.js index f4c51f3..7a14172 100644 --- a/action.js +++ b/action.js @@ -13,7 +13,7 @@ const stripQuotes = (str) => const replaceEnvVars = (str) => { const value = str .replaceAll( - /\$\{([a-zA-Z0-9_]+):[+-]:\$[a-zA-Z0-9_]+\}/g, + /\$\{([a-zA-Z0-9_]+):\+:\$[a-zA-Z0-9_]+\}/g, (_, key) => ((v) => v ? `:${v}` : "")(process.env[key]), ) .replaceAll(/\$\{([a-zA-Z0-9_]+)\}/g, (_, key) => process.env[key] ?? "") From a30e089b6a5148d8ae1859098a8d174affbcfc53 Mon Sep 17 00:00:00 2001 From: Max Howell Date: Mon, 7 Apr 2025 12:48:50 -0400 Subject: [PATCH 07/10] wip --- action.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/action.yml b/action.yml index f0b6dae..b8e9f33 100644 --- a/action.yml +++ b/action.yml @@ -21,10 +21,8 @@ runs: shell: bash - run: | - if ! node --version >/dev/null 2>&1; then - set -a - eval "$(pkgx +node)" - set +a + if ! command -v node >/dev/null 2>&1; then + node() { pkgx node^20 "$@" || } fi node "$GITHUB_ACTION_PATH"/action.js ${{ steps.env.outputs.file }} shell: bash From 97ee24276bf370fcf8a46dace7349cb6f0eafb7e Mon Sep 17 00:00:00 2001 From: Max Howell Date: Mon, 7 Apr 2025 12:50:53 -0400 Subject: [PATCH 08/10] wip --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index b8e9f33..07782f2 100644 --- a/action.yml +++ b/action.yml @@ -22,7 +22,7 @@ runs: - run: | if ! command -v node >/dev/null 2>&1; then - node() { pkgx node^20 "$@" || } + node() { pkgx node^20 "$@" } fi node "$GITHUB_ACTION_PATH"/action.js ${{ steps.env.outputs.file }} shell: bash From c5fb66901ef7376dd4db5f5c679771b6000bf5d0 Mon Sep 17 00:00:00 2001 From: Max Howell Date: Mon, 7 Apr 2025 13:06:35 -0400 Subject: [PATCH 09/10] wip --- action.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/action.yml b/action.yml index 07782f2..f7ea17c 100644 --- a/action.yml +++ b/action.yml @@ -22,7 +22,9 @@ runs: - run: | if ! command -v node >/dev/null 2>&1; then - node() { pkgx node^20 "$@" } + node() { + pkgx node^20 "$@" + } fi node "$GITHUB_ACTION_PATH"/action.js ${{ steps.env.outputs.file }} shell: bash From 6cc78b37acd56fccb13c52642d3b1e4e6f6ba3ff Mon Sep 17 00:00:00 2001 From: Max Howell Date: Mon, 7 Apr 2025 13:31:23 -0400 Subject: [PATCH 10/10] wip --- action.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.js b/action.js index 7a14172..2cc6a36 100644 --- a/action.js +++ b/action.js @@ -30,7 +30,7 @@ readInterface.on("line", (line) => { if (match) { const [_, key, value_] = match; const value = stripQuotes(value_); - if (key === "PATH") { + if (key.trim() === "PATH") { value .replaceAll("${PATH:+:$PATH}", "") .replaceAll("$PATH", "")