diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..2ed627d --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022–23 pkgx inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index dc39288..a645b70 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,10 @@ packages you need for different projects as you navigate in your shell. ```sh pkgx dev integrate +# ^^ supports Bash & Zsh. PRs welcome for more shells ``` -> [!NOTE] +> [!IMPORTANT] > > `pkgx` is a required dependency. > @@ -17,6 +18,8 @@ pkgx dev integrate > brew install pkgxdev/made/pkgx || sh <(curl https://pkgx.sh) > ``` +> [!NOTE] +> > `pkgx dev integrate` looks for and edits known `shell.rc` files adding one > line: > @@ -29,26 +32,8 @@ pkgx dev integrate > ```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`. - -> [!TIP] -> -> ### Try Before You `vi` -> -> Modifying your `shell.rc` can be… _intimidating_. If you just want to -> temporarily try `dev` out before you `:wq`—we got you: -> -> ```sh -> $ cd my-project -> $ eval "$(pkgx dev)" -> ``` > -> The devenv will only exist for the duration of your shell session. +> If you like, preview the shellcode: `pkgx dev --shellcode`. ## Usage @@ -74,6 +59,20 @@ $ node command not found: node ``` +> [!TIP] +> +> ### Try Before You `vi` +> +> Modifying your `shell.rc` can be… _intimidating_. If you just want to +> temporarily try `dev` out before you `:wq`—we got you: +> +> ```sh +> $ cd my-project +> $ eval "$(pkgx dev)" +> ``` +> +> The devenv will only exist for the duration of your shell session. + ## How Packages are Determined - We look at the files you have and figure out the packages you need. @@ -126,6 +125,13 @@ You can add your own environment variables if you like: # MY_VAR: my-value ``` +> [!CAUTION] +> +> The assignment of these variables are run through the shell, so you can do +> stuff like `$(pwd)` if you like. Obviously, be careful with that—we don’t +> sanitize the input. We will accept a PR to escape this by default or something +> ∵ we agree this is maybe a bit insane. + ## GitHub Actions ```yaml diff --git a/app.ts b/app.ts index a5f2350..ffcbb17 100755 --- a/app.ts +++ b/app.ts @@ -3,99 +3,49 @@ //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, utils } from "libpkgx"; +import { Path } from "libpkgx"; 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": { - const status = await new Deno.Command("pkgx", { - args: ["gh", "repo", "view", "pkgxdev/dev"], - }).spawn().status; - Deno.exit(status.code); - break; // deno lint insists +import { parse } from "jsr:@std/flags"; +import dump from "./src/dump.ts"; + +const parsedArgs = parse(Deno.args, { + alias: { + n: "dry-run", + "just-print": "dry-run", + recon: "dry-run", + v: "version", + h: "help", + }, + boolean: ["help", "version", "shellcode"], + default: { + "dry-run": false, + }, +}); + +if (parsedArgs.help) { + const status = await new Deno.Command("pkgx", { + args: ["gh", "repo", "view", "pkgxdev/dev"], + }).spawn().status; + Deno.exit(status.code); +} else if (parsedArgs.shellcode) { + console.log(shellcode()); +} else if (parsedArgs.version) { + console.log(`dev ${app_version}`); +} else { + const subcommand = parsedArgs._[0]; + const dryrun = parsedArgs["dry-run"] as boolean; + switch (subcommand) { + case "integrate": + await integrate("install", { dryrun }); + break; + case "deintegrate": + await integrate("uninstall", { dryrun }); + break; + default: { + const cwd = Path.cwd().join(subcommand as string); + await dump(cwd, { dryrun }); + } } - case "--shellcode": - console.log(shellcode()); - Deno.exit(0); - break; // deno lint insists - case "--version": - 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()); - -if (snuff.pkgs.length === 0 && Object.keys(snuff.env).length === 0) { - console.error("no devenv detected"); - Deno.exit(1); -} - -let env = ""; -const pkgspecs = snuff.pkgs.map((pkg) => `+${utils.pkg.str(pkg)}`); - -if (snuff.pkgs.length > 0) { - const cmd = new Deno.Command("pkgx", { - args: [...pkgspecs], - stdout: "piped", - env: { CLICOLOR_FORCE: "1" }, // unfortunate - }).spawn(); - - await cmd.status; - - const stdout = (await cmd.output()).stdout; - env = new TextDecoder().decode(stdout); -} - -// add any additional env that we sniffed -for (const [key, value] of Object.entries(snuff.env)) { - env += `${key}=${shell_escape(value)}\n`; -} - -env = env.trim(); - -let undo = ""; -for (const envln of env.trim().split("\n")) { - if (!envln) continue; - - const [key] = envln.split("=", 2); - undo += ` if [ \\"$${key}\\" ]; then - export ${key}=\\"$${key}\\" - else - unset ${key} - fi\n`; } - -const dir = Deno.cwd(); - -const bye_bye_msg = pkgspecs.map((pkgspec) => `-${pkgspec.slice(1)}`).join(" "); - -console.log(` -eval "_pkgx_dev_try_bye() { - suffix=\\"\\\${PWD#\\"${dir}\\"}\\" - if test \\"\\$PWD\\" != \\"${dir}$suffix\\"; then - ${undo.trim()} - unset -f _pkgx_dev_try_bye - echo -e \\"\\033[31m${bye_bye_msg}\\033[0m\\" >&2 - return 0 - else - return 1 - fi -}" - -set -a -${env} -set +a`); - -console.error("%c%s", "color: green", pkgspecs.join(" ")); diff --git a/deno.lock b/deno.lock index 4868c86..f505d6b 100644 --- a/deno.lock +++ b/deno.lock @@ -1,9 +1,11 @@ { "version": "4", "specifiers": { + "jsr:@std/assert@0.224": "0.224.0", "jsr:@std/bytes@^1.0.2": "1.0.2", "jsr:@std/crypto@1": "1.0.3", "jsr:@std/encoding@1": "1.0.5", + "jsr:@std/flags@*": "0.224.0", "jsr:@std/fs@1": "1.0.8", "jsr:@std/io@*": "0.225.0", "jsr:@std/io@0.225": "0.225.0", @@ -12,9 +14,13 @@ "jsr:@std/path@1": "1.0.8", "jsr:@std/path@^1.0.8": "1.0.8", "jsr:@std/yaml@*": "1.0.5", - "jsr:@std/yaml@1": "1.0.5" + "jsr:@std/yaml@1": "1.0.5", + "npm:@types/node@*": "22.5.4" }, "jsr": { + "@std/assert@0.224.0": { + "integrity": "8643233ec7aec38a940a8264a6e3eed9bfa44e7a71cc6b3c8874213ff401967f" + }, "@std/bytes@1.0.2": { "integrity": "fbdee322bbd8c599a6af186a1603b3355e59a5fb1baa139f8f4c3c9a1b3e3d57" }, @@ -24,6 +30,12 @@ "@std/encoding@1.0.5": { "integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04" }, + "@std/flags@0.224.0": { + "integrity": "d40eaf58c356b2e1313c6d4e62dc28b614aad2ddae6f5ff72a969e0b1f5ad689", + "dependencies": [ + "jsr:@std/assert" + ] + }, "@std/fs@1.0.8": { "integrity": "161c721b6f9400b8100a851b6f4061431c538b204bb76c501d02c508995cffe0", "dependencies": [ @@ -52,7 +64,31 @@ "integrity": "71ba3d334305ee2149391931508b2c293a8490f94a337eef3a09cade1a2a2742" } }, + "npm": { + "@types/node@22.5.4": { + "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", + "dependencies": [ + "undici-types" + ] + }, + "undici-types@6.19.8": { + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + } + }, "remote": { + "https://deno.land/std@0.196.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", + "https://deno.land/std@0.196.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", + "https://deno.land/std@0.196.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", + "https://deno.land/std@0.196.0/flags/mod.ts": "a5ac18af6583404f21ea03771f8816669d901e0ff4374020870334d6f61d73d5", + "https://deno.land/std@0.196.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", + "https://deno.land/std@0.196.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", + "https://deno.land/std@0.196.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0", + "https://deno.land/std@0.196.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", + "https://deno.land/std@0.196.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1", + "https://deno.land/std@0.196.0/path/mod.ts": "f065032a7189404fdac3ad1a1551a9ac84751d2f25c431e101787846c86c79ef", + "https://deno.land/std@0.196.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d", + "https://deno.land/std@0.196.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", + "https://deno.land/std@0.196.0/path/win32.ts": "4fca292f8d116fd6d62f243b8a61bd3d6835a9f0ede762ba5c01afe7c3c0aa12", "https://deno.land/x/is_what@v4.1.15/src/index.ts": "e55b975d532b71a0e32501ada85ae3c67993b75dc1047c6d1a2e00368b789af0", "https://deno.land/x/outdent@v0.8.0/mod.ts": "72630e680dcc36d5ae556fbff6900b12706c81a6fd592345fc98bcc0878fb3ca", "https://deno.land/x/outdent@v0.8.0/src/index.ts": "6dc3df4108d5d6fedcdb974844d321037ca81eaaa16be6073235ff3268841a22", diff --git a/src/dump.ts b/src/dump.ts new file mode 100644 index 0000000..fe0c90d --- /dev/null +++ b/src/dump.ts @@ -0,0 +1,71 @@ +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 }) { + const snuff = await sniff(cwd); + + if (snuff.pkgs.length === 0 && Object.keys(snuff.env).length === 0) { + console.error("no devenv detected"); + Deno.exit(1); + } + + let env = ""; + const pkgspecs = snuff.pkgs.map((pkg) => `+${utils.pkg.str(pkg)}`); + + if (opts.dryrun) { + console.log(pkgspecs.join(" ")); + return; + } + + if (snuff.pkgs.length > 0) { + const cmd = new Deno.Command("pkgx", { + args: [...pkgspecs], + stdout: "piped", + env: { CLICOLOR_FORCE: "1" }, // unfortunate + }).spawn(); + + await cmd.status; + + const stdout = (await cmd.output()).stdout; + env = new TextDecoder().decode(stdout); + } + + // add any additional env that we sniffed + for (const [key, value] of Object.entries(snuff.env)) { + env += `${key}=${shell_escape(value)}\n`; + } + + env = env.trim(); + + let undo = ""; + for (const envln of env.trim().split("\n")) { + if (!envln) continue; + + const [key] = envln.split("=", 2); + undo += ` if [ \\"$${key}\\" ]; then + export ${key}=\\"$${key}\\" + else + unset ${key} + fi\n`; + } + + const bye_bye_msg = pkgspecs.map((pkgspec) => `-${pkgspec.slice(1)}`).join( + " ", + ); + + console.error("%c%s", "color: green", pkgspecs.join(" ")); + + console.log(` + eval "_pkgx_dev_try_bye() { + suffix=\\"\\\${PWD#\\"${cwd}\\"}\\" + [ \\"\\$PWD\\" = \\"${cwd}$suffix\\" ] && return 1 + echo -e \\"\\033[31m${bye_bye_msg}\\033[0m\\" >&2 + ${undo.trim()} + unset -f _pkgx_dev_try_bye + }" + + set -a + ${env} + set +a`); +} diff --git a/src/shellcode().ts b/src/shellcode().ts index 10f9cb3..a1f4a34 100644 --- a/src/shellcode().ts +++ b/src/shellcode().ts @@ -37,7 +37,9 @@ dev() { echo "no devenv" >&2 fi;; ''|on) - if ! type -f _pkgx_dev_try_bye >/dev/null 2>&1; then + 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" eval "$(${dev_cmd})" @@ -45,7 +47,7 @@ dev() { echo "devenv already active" >&2 fi;; *) - ${dev_cmd} "$@";; + "${dev_cmd}" "$@";; esac }