diff --git a/.github/deno-to-node.ts b/.github/deno-to-node.ts
index 6ca4204..014e2e6 100755
--- a/.github/deno-to-node.ts
+++ b/.github/deno-to-node.ts
@@ -13,17 +13,17 @@ dependencies:
npmjs.com: '*'
---*/
-import { build, emptyDir } from "https://deno.land/x/dnt@0.38.0/mod.ts";
-import SemVer from "../src/utils/semver.ts";
+import { build, emptyDir } from "https://deno.land/x/dnt@0.38.0/mod.ts"
+import SemVer from "../src/utils/semver.ts"
-await emptyDir("./dist");
+await emptyDir("./dist")
const version = (() => {
try {
- return new SemVer(Deno.args[0]).toString();
+ return new SemVer(Deno.args[0]).toString()
} catch {
console.warn("no version specified, do not release this!")
- return '0.0.0'
+ return "0.0.0"
}
})()
@@ -43,7 +43,7 @@ await build({
mappings: {
"https://deno.land/x/is_what@v4.1.15/src/index.ts": "is-what",
"https://deno.land/x/outdent@v0.8.0/mod.ts": "outdent",
- "./src/utils/flock.deno.ts": "./src/utils/flock.node.ts"
+ "./src/utils/flock.deno.ts": "./src/utils/flock.node.ts",
},
package: {
name: "@teaxyz/lib",
@@ -64,24 +64,24 @@ await build({
exports: {
"./src/src/utils/semver": {
//TODO remove when gui is updated to use `@teaxyz/lib/semver`
- import: "./src/src/utils/semver.ts"
+ import: "./src/src/utils/semver.ts",
},
"./semver": {
import: "./esm/src/utils/semver.js",
- require: "./script/src/utils/semver.js"
+ require: "./script/src/utils/semver.js",
},
"./plumbing/*": {
"import": "./esm/src/plumbing/*.js",
- "require": "./script/src/plumbing/*.js"
+ "require": "./script/src/plumbing/*.js",
},
"./hooks/*": {
"import": "./esm/src/hooks/*.js",
- "require": "./script/src/hooks/*.js"
- }
- }
+ "require": "./script/src/hooks/*.js",
+ },
+ },
},
postBuild() {
- Deno.copyFileSync("LICENSE.txt", "dist/LICENSE.txt");
- Deno.copyFileSync("README.md", "dist/README.md");
+ Deno.copyFileSync("LICENSE.txt", "dist/LICENSE.txt")
+ Deno.copyFileSync("README.md", "dist/README.md")
},
-});
+})
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 3ad9aee..155491d 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,20 +1,20 @@
{
- "version": "0.2.0",
- "configurations": [
- {
- "request": "launch",
- "name": "Debug Test",
- "type": "node",
- "cwd": "${workspaceFolder}",
- "runtimeExecutable": "deno",
- "runtimeArgs": [
- "test",
- "--unstable",
- "--inspect-brk",
- "--allow-all",
- "${file}",
- ],
- "attachSimplePort": 9229
- }
- ]
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "request": "launch",
+ "name": "Debug Test",
+ "type": "node",
+ "cwd": "${workspaceFolder}",
+ "runtimeExecutable": "deno",
+ "runtimeArgs": [
+ "test",
+ "--unstable",
+ "--inspect-brk",
+ "--allow-all",
+ "${file}"
+ ],
+ "attachSimplePort": 9229
+ }
+ ]
}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index ce45680..9fe49ad 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -2,5 +2,5 @@
"deno.enable": true,
"deno.lint": true,
"deno.unstable": true,
- "deno.config": "deno.json"
+ "deno.config": "deno.jsonc"
}
diff --git a/README.md b/README.md
index 64cfd7e..11f94fa 100644
--- a/README.md
+++ b/README.md
@@ -15,13 +15,11 @@
-
# libtea
-tea aims to provide packaging primitives. This library is a route to that
-goal. libtea can install and provide sandboxed environments for packages that
-have no effect on the wider system without you or your user needing to install
-[tea/cli].
+tea aims to provide packaging primitives. This library is a route to that goal. libtea can install
+and provide sandboxed environments for packages that have no effect on the wider system without you
+or your user needing to install [tea/cli].
## Getting Started
@@ -39,10 +37,10 @@ import * as tea from "https://deno.land/x/libtea/mod.ts"
## Usage
```ts
-import { porcelain } from "@teaxyz/lib";
-const { run } = porcelain;
+import { porcelain } from "@teaxyz/lib"
+const { run } = porcelain
-await run(`python -c 'print("Hello, World!")'`);
+await run(`python -c 'print("Hello, World!")'`)
// ^^ installs python and its deps (into ~/.tea/python.org/v3.x.y)
// ^^ runs the command
// ^^ output goes to the terminal
@@ -53,73 +51,68 @@ await run(`python -c 'print("Hello, World!")'`);
Capture stdout easily:
```ts
-const { stdout } = await run(`ruby -e 'puts ", World!"'`, { stdout: true });
-console.log("Hello,", stdout);
+const { stdout } = await run(`ruby -e 'puts ", World!"'`, { stdout: true })
+console.log("Hello,", stdout)
```
> `{ stderr: true }` also works.
-If there’s a non-zero exit code, we `throw`. However, when you need to,
-you can capture it instead:
+If there’s a non-zero exit code, we `throw`. However, when you need to, you can capture it instead:
```ts
-const { status } = await run(`perl -e 'exit(7)'`, { status: true });
-assert(status == 7); // ^^ didn’t throw!
+const { status } = await run(`perl -e 'exit(7)'`, { status: true })
+assert(status == 7) // ^^ didn’t throw!
```
-> The run function’s options also takes `env` if you need to supplement or
-> replace the inherited environment (which is passed by default).
+> The run function’s options also takes `env` if you need to supplement or replace the inherited
+> environment (which is passed by default).
-Need a specific version of something? [tea][tea/cli] can install any version
-of any package:
+Need a specific version of something? [tea][tea/cli] can install any version of any package:
```ts
-await run(["node^16", "-e", "console.log(process.version)"]);
+await run(["node^16", "-e", "console.log(process.version)"])
// => v16.18.1
```
-> Notice we passed args as `string[]`. This is also supported and is often
-> preferable since shell quoting rules can be tricky. If you pass `string[]`
-> we execute the command directly rather than via `/bin/sh`.
+> Notice we passed args as `string[]`. This is also supported and is often preferable since shell
+> quoting rules can be tricky. If you pass `string[]` we execute the command directly rather than
+> via `/bin/sh`.
-All of tea’s packages are relocatable so you can configure libtea to install
-wherever you want:
+All of tea’s packages are relocatable so you can configure libtea to install wherever you want:
```ts
-import { hooks, Path, porcelain } from "tea";
-const { install } = porcelain;
-const { useConfig } = hooks;
+import { hooks, Path, porcelain } from "tea"
+const { install } = porcelain
+const { useConfig } = hooks
-useConfig({ prefix: Path.home().join(".local/share/my-app") });
+useConfig({ prefix: Path.home().join(".local/share/my-app") })
// ^^ must be done **before** any other libtea calls
-const go = await install("go.dev");
+const go = await install("go.dev")
// ^^ go.path = /home/you/.local/share/my-app/go.dev/v1.20.4
```
### Designed for Composibility
-The library is split into [plumbing](src/plumbing) and [porcelain](src/porcelain) (copying git’s lead).
-The porcelain is what most people need, but if you need more control, dive
-into the porcelain sources to see how to use the plumbing primitives to get
-precisely what you need.
+The library is split into [plumbing](src/plumbing) and [porcelain](src/porcelain) (copying git’s
+lead). The porcelain is what most people need, but if you need more control, dive into the porcelain
+sources to see how to use the plumbing primitives to get precisely what you need.
-For example if you want to run a command with node’s `spawn` instead it is
-simple enough to first use our porcelain `install` function then grab the
-`env` you’ll need to pass to `spawn` using our `useShellEnv` hook.
+For example if you want to run a command with node’s `spawn` instead it is simple enough to first
+use our porcelain `install` function then grab the `env` you’ll need to pass to `spawn` using our
+`useShellEnv` hook.
Perhaps what you create should go into the porcelain? If so, please open a PR.
### Logging
-Most functions take an optional `logger` parameter so you can output logging
-information if you so choose. `tea/cli` has a fairly sophisticated logger, so
-go check that out if you want. For our porcelain functions we provide a simple
-debug-friendly logger (`ConsoleLogger`) that will output everything via
-`console.error`:
+Most functions take an optional `logger` parameter so you can output logging information if you so
+choose. `tea/cli` has a fairly sophisticated logger, so go check that out if you want. For our
+porcelain functions we provide a simple debug-friendly logger (`ConsoleLogger`) that will output
+everything via `console.error`:
```ts
-import { porcelain, plumbing, utils } from "tea"
+import { plumbing, porcelain, utils } from "tea"
const { ConsoleLogger } = utils
const { run } = porcelain
@@ -129,27 +122,23 @@ await run("youtube-dl youtu.be/xiq5euezOEQ", { logger }).exec()
### Caveats
-We have our own implementation of semver because open source has existed for
-decades and Semantic Versioning is much newer than that. Our implementation is
-quite compatible but not completely so. Use our semver with libtea.
-Our implementation is 100% compatible with strings output from node’s own
-semver.
+We have our own implementation of semver because open source has existed for decades and Semantic
+Versioning is much newer than that. Our implementation is quite compatible but not completely so.
+Use our semver with libtea. Our implementation is 100% compatible with strings output from node’s
+own semver.
-Setting `useConfig()` is not thread safe. Thus if you are using web workers
-you must ensure the initial call to `useConfig()` is called on the main thread
-before any other calls might happen. We call it explicitly in our code so you
-will need to call it yourself in such a case. This is not ideal and we’d
-appreciate your help in fixing it.
+Setting `useConfig()` is not thread safe. Thus if you are using web workers you must ensure the
+initial call to `useConfig()` is called on the main thread before any other calls might happen. We
+call it explicitly in our code so you will need to call it yourself in such a case. This is not
+ideal and we’d appreciate your help in fixing it.
-The plumbing has no magic. Libraries need well defined behavior.
-You’ll need to read the docs to use them effectively.
+The plumbing has no magic. Libraries need well defined behavior. You’ll need to read the docs to use
+them effectively.
-libtea almost certainly will not work in a browser. Potentially it's possible.
-The first step would be compiling our bottles to WASM. We could use your help
-with that…
+libtea almost certainly will not work in a browser. Potentially it's possible. The first step would
+be compiling our bottles to WASM. We could use your help with that…
-We use a hook-like pattern because it is great. This library is not itself
-designed for React.
+We use a hook-like pattern because it is great. This library is not itself designed for React.
We support the same platforms as [tea/cli].
@@ -157,24 +146,21 @@ We support the same platforms as [tea/cli].
We can install anything in the [pantry].
-If something you need is not there, adding to the pantry has been designed to
-be an easy and enjoyable process. Your contribution is both welcome and
-desired!
+If something you need is not there, adding to the pantry has been designed to be an easy and
+enjoyable process. Your contribution is both welcome and desired!
-To see what is available refer to the [pantry] docs or you can run:
-`tea pkg search foo`.
+To see what is available refer to the [pantry] docs or you can run: `tea pkg search foo`.
# Interesting Uses
-* You can grab cURL’s CA certificates which we pkg and keep up to date
- (`curl.se/ca-certs`). These are commonly needed across ecosystems but not
- always easily accessible.
-* grab libraries that wrappers need like openssl or sqlite
-* run a real database (like postgres) easily
-* load local AI models and their engines
-* load libraries and then use ffi to load symbols
+- You can grab cURL’s CA certificates which we pkg and keep up to date (`curl.se/ca-certs`). These
+ are commonly needed across ecosystems but not always easily accessible.
+- grab libraries that wrappers need like openssl or sqlite
+- run a real database (like postgres) easily
+- load local AI models and their engines
+- load libraries and then use ffi to load symbols
@@ -186,24 +172,23 @@ We would be thrilled to hear your ideas† or receive your pull requests.
## Anatomy
-The code is written with Deno (just like [tea/cli]) but is compiled to a
-node package for wider accessibility (and ∵ [tea/gui] is node/electron).
+The code is written with Deno (just like [tea/cli]) but is compiled to a node package for wider
+accessibility (and ∵ [tea/gui] is node/electron).
-The library is architected into hooks, plumbing and porcelain. Where the hooks
-represent the low level primitives of pkging, the plumbing glues those
-primitives together into useful components and the porcelain is a user
-friendly *façade* pattern for the plumbing.
+The library is architected into hooks, plumbing and porcelain. Where the hooks represent the low
+level primitives of pkging, the plumbing glues those primitives together into useful components and
+the porcelain is a user friendly _façade_ pattern for the plumbing.
## Supporting Other Languages
-We would love to port this code to every language. We are deliberately keeping
-the scope *tight*. Probably we would prefer to have one repo per language.
+We would love to port this code to every language. We are deliberately keeping the scope _tight_.
+Probably we would prefer to have one repo per language.
-tea has sensible rules for how packages are defined and installed so writing
-a port should be simple.
+tea has sensible rules for how packages are defined and installed so writing a port should be
+simple.
-We would love to explore how possible writing this in rust and then compiling
-to WASM for all other languages would be. Can you help?
+We would love to explore how possible writing this in rust and then compiling to WASM for all other
+languages would be. Can you help?
Open a [discussion] to start.
@@ -217,7 +202,6 @@ Open a [discussion] to start.
-
# Tasks
Run eg. `xc coverage` or `xc bump patch`.
@@ -233,8 +217,7 @@ open cov_profile/html/index.html
## Bump
-Bumps version by creating a pre-release which then engages the deployment
-infra in GitHub Actions.
+Bumps version by creating a pre-release which then engages the deployment infra in GitHub Actions.
Inputs: LEVEL
diff --git a/deno.json b/deno.jsonc
similarity index 89%
rename from deno.json
rename to deno.jsonc
index 3c22ccd..4b2326e 100644
--- a/deno.json
+++ b/deno.jsonc
@@ -4,11 +4,8 @@
"strict": true
},
"fmt": {
- "files": {
- "exclude": [
- "./"
- ]
- }
+ "semiColons": false,
+ "lineWidth": 100
},
"tea": {
"dependencies": {
diff --git a/examples/awscli/index.mjs b/examples/awscli/index.mjs
index 6ecd398..31b954d 100644
--- a/examples/awscli/index.mjs
+++ b/examples/awscli/index.mjs
@@ -1,5 +1,5 @@
-import * as tea from '@teaxyz/lib';
-import * as awsclijs from 'aws-cli-js';
+import * as tea from "@teaxyz/lib"
+import * as awsclijs from "aws-cli-js"
const { Options, Aws } = awsclijs
const { porcelain: { install }, hooks: { useShellEnv } } = tea
@@ -9,13 +9,13 @@ const opts = new Options(process.env.AWS_ACCESS_KEY_ID, process.env.AWS_SECRET_A
await installAwsCli()
const aws = new Aws(opts)
-const users = await aws.command('iam list-users')
+const users = await aws.command("iam list-users")
console.log(users)
///////////////////////////////////////////
async function installAwsCli() {
const { map, flatten } = useShellEnv()
- const installations = await install('aws.amazon.com/cli')
+ const installations = await install("aws.amazon.com/cli")
Object.assign(process.env, flatten(await map({ installations })))
}
diff --git a/examples/whisper.js b/examples/whisper.js
index bf5c773..65ccb96 100644
--- a/examples/whisper.js
+++ b/examples/whisper.js
@@ -8,10 +8,10 @@ const https = require("node:https")
const { run } = porcelain
const fs = require("node:fs")
-const url = 'https://github.com/ggerganov/whisper.cpp/raw/master/samples/jfk.wav'
+const url = "https://github.com/ggerganov/whisper.cpp/raw/master/samples/jfk.wav"
-const fetch = new Promise(done =>
- https.get(url, rsp =>
- rsp.pipe(fs.createWriteStream("jfk.wav")).on('finish', done)))
+const fetch = new Promise((done) =>
+ https.get(url, (rsp) => rsp.pipe(fs.createWriteStream("jfk.wav")).on("finish", done))
+)
-fetch.then(() =>run("whisper.cpp jfk.wav"))
+fetch.then(() => run("whisper.cpp jfk.wav"))
diff --git a/examples/whisper.mjs b/examples/whisper.mjs
index fac539e..fcb00b6 100644
--- a/examples/whisper.mjs
+++ b/examples/whisper.mjs
@@ -8,10 +8,10 @@ import https from "node:https"
const { run } = porcelain
import fs from "node:fs"
-const url = 'https://raw.githubusercontent.com/ggerganov/whisper.cpp/raw/master/samples/jfk.wav'
+const url = "https://raw.githubusercontent.com/ggerganov/whisper.cpp/raw/master/samples/jfk.wav"
-await new Promise(done =>
- https.get(url, rsp =>
- rsp.pipe(fs.createWriteStream("jfk.wav")).on('finish', done)))
+await new Promise((done) =>
+ https.get(url, (rsp) => rsp.pipe(fs.createWriteStream("jfk.wav")).on("finish", done))
+)
await run("whisper.cpp jfk.wav")
diff --git a/examples/whisper.ts b/examples/whisper.ts
index 7230e75..fbd36ba 100755
--- a/examples/whisper.ts
+++ b/examples/whisper.ts
@@ -8,7 +8,7 @@ import { porcelain } from "https://raw.github.com/teaxyz/lib/v0/mod.ts"
import { green } from "https://deno.land/std/fmt/colors.ts"
const { run } = porcelain
-const url = 'https://github.com/ggerganov/whisper.cpp/raw/master/samples/jfk.wav'
+const url = "https://github.com/ggerganov/whisper.cpp/raw/master/samples/jfk.wav"
const rsp = await fetch(url)
await Deno.writeFile("jfk.wav", rsp.body!)
diff --git a/fixtures/npm-integration-test/index.js b/fixtures/npm-integration-test/index.js
index db1ac8c..9b5658c 100644
--- a/fixtures/npm-integration-test/index.js
+++ b/fixtures/npm-integration-test/index.js
@@ -5,4 +5,3 @@ const { ConsoleLogger } = require("@teaxyz/lib/plumbing/install")
console.log(ConfigDefault(), ConsoleLogger())
run("ls -la")
-
diff --git a/mod.ts b/mod.ts
index c12e8ce..f99879f 100644
--- a/mod.ts
+++ b/mod.ts
@@ -10,11 +10,16 @@ import * as pkg from "./src/utils/pkg.ts"
import { panic, TeaError } from "./src/utils/error.ts"
import useConfig from "./src/hooks/useConfig.ts"
-import useOffLicense from "./src/hooks/useOffLicense.ts"
+import useOffLicense from "./src/hooks/useOffLicense.ts"
import useCache from "./src/hooks/useCache.ts"
-import useCellar, { InstallationNotFoundError} from "./src/hooks/useCellar.ts"
+import useCellar, { InstallationNotFoundError } from "./src/hooks/useCellar.ts"
import useMoustaches from "./src/hooks/useMoustaches.ts"
-import usePantry, { PantryError, PantryParseError, PantryNotFoundError, PackageNotFoundError } from "./src/hooks/usePantry.ts"
+import usePantry, {
+ PackageNotFoundError,
+ PantryError,
+ PantryNotFoundError,
+ PantryParseError,
+} from "./src/hooks/usePantry.ts"
import useFetch from "./src/hooks/useFetch.ts"
import useDownload, { DownloadError } from "./src/hooks/useDownload.ts"
import useShellEnv from "./src/hooks/useShellEnv.ts"
@@ -30,7 +35,12 @@ import run, { RunError } from "./src/porcelain/run.ts"
import porcelain_install from "./src/porcelain/install.ts"
const utils = {
- pkg, host, flatmap, validate, panic, ConsoleLogger
+ pkg,
+ host,
+ flatmap,
+ validate,
+ panic,
+ ConsoleLogger,
}
const hooks = {
@@ -52,27 +62,34 @@ const plumbing = {
link,
install,
resolve,
- which
+ which,
}
const porcelain = {
install: porcelain_install,
- run
+ run,
}
const hacks = {
- validatePackageRequirement
+ validatePackageRequirement,
}
export {
- utils, hooks, plumbing, porcelain, hacks,
+ DownloadError,
+ hacks,
+ hooks,
+ InstallationNotFoundError,
+ PackageNotFoundError,
+ PantryError,
+ PantryNotFoundError,
+ PantryParseError,
+ plumbing,
+ porcelain,
+ ResolveError,
+ RunError,
semver,
TeaError,
- RunError,
- ResolveError,
- PantryError, PantryParseError, PantryNotFoundError, PackageNotFoundError,
- InstallationNotFoundError,
- DownloadError
+ utils,
}
/// export types
diff --git a/src/hooks/useCache.test.ts b/src/hooks/useCache.test.ts
index e58b261..a44ce49 100644
--- a/src/hooks/useCache.test.ts
+++ b/src/hooks/useCache.test.ts
@@ -11,22 +11,22 @@ Deno.test("useCache", () => {
const stowage = StowageNativeBottle({
pkg: { project: "foo/bar", version: new SemVer("1.0.0") },
- compression: "xz"
- });
+ compression: "xz",
+ })
assertEquals(useCache().path(stowage), cache.join(`foo∕bar-1.0.0+${hw}.tar.xz`))
const stowage2: Stowage = {
- type: 'bottle',
+ type: "bottle",
pkg: stowage.pkg,
host: { platform: "linux", arch: "aarch64" },
- compression: 'xz'
+ compression: "xz",
}
assertEquals(useCache().path(stowage2), cache.join("foo∕bar-1.0.0+linux+aarch64.tar.xz"))
const stowage3: Stowage = {
pkg: stowage.pkg,
type: "src",
- extname: ".tgz"
+ extname: ".tgz",
}
assertEquals(useCache().path(stowage3), cache.join("foo∕bar-1.0.0.tgz"))
})
diff --git a/src/hooks/useCache.ts b/src/hooks/useCache.ts
index bf83809..2cca994 100644
--- a/src/hooks/useCache.ts
+++ b/src/hooks/useCache.ts
@@ -11,7 +11,7 @@ const path = (stowage: Stowage) => {
const stem = pkg.project.replaceAll("/", "∕")
let filename = `${stem}-${pkg.version}`
- if (type == 'bottle') {
+ if (type == "bottle") {
const { platform, arch } = stowage.host ?? host()
filename += `+${platform}+${arch}.tar.${stowage.compression}`
} else {
diff --git a/src/hooks/useCellar.test.ts b/src/hooks/useCellar.test.ts
index 753c881..6cecea2 100644
--- a/src/hooks/useCellar.test.ts
+++ b/src/hooks/useCellar.test.ts
@@ -7,7 +7,7 @@ import useCellar from "./useCellar.ts"
Deno.test("useCellar.resolve()", async () => {
useTestConfig()
- const pkgrq = { project: "python.org", version: new SemVer("3.11.3")}
+ const pkgrq = { project: "python.org", version: new SemVer("3.11.3") }
const installation = await install(pkgrq)
await useCellar().resolve(installation)
@@ -15,7 +15,9 @@ Deno.test("useCellar.resolve()", async () => {
await useCellar().resolve({ project: "python.org", constraint: new semver.Range("^3") })
await useCellar().resolve(installation.path)
- assertRejects(() => useCellar().resolve({ project: "python.org", constraint: new semver.Range("@300")}))
+ assertRejects(() =>
+ useCellar().resolve({ project: "python.org", constraint: new semver.Range("@300") })
+ )
})
Deno.test("useCellar.has()", async () => {
diff --git a/src/hooks/useCellar.ts b/src/hooks/useCellar.ts
index ea4e016..a835838 100644
--- a/src/hooks/useCellar.ts
+++ b/src/hooks/useCellar.ts
@@ -1,4 +1,4 @@
-import { Package, PackageRequirement, Installation } from "../types.ts"
+import { Installation, Package, PackageRequirement } from "../types.ts"
import { TeaError } from "../utils/error.ts"
import * as pkgutils from "../utils/pkg.ts"
import SemVer from "../utils/semver.ts"
@@ -24,7 +24,8 @@ export default function useCellar() {
const keg = (pkg: Package) => shelf(pkg.project).join(`v${pkg.version}`)
/// returns the `Installation` if the pkg is installed
- const has = (pkg: Package | PackageRequirement | Path) => resolve(pkg).swallow(InstallationNotFoundError)
+ const has = (pkg: Package | PackageRequirement | Path) =>
+ resolve(pkg).swallow(InstallationNotFoundError)
return {
has,
@@ -41,13 +42,13 @@ export default function useCellar() {
if (!d.isDirectory()) return []
const rv: Installation[] = []
- for await (const [path, {name, isDirectory}] of d.ls()) {
+ for await (const [path, { name, isDirectory }] of d.ls()) {
try {
if (!isDirectory) continue
- if (!name.startsWith("v") || name == 'var') continue
+ if (!name.startsWith("v") || name == "var") continue
const version = new SemVer(name)
- if (await vacant(path)) continue // failed build probs
- rv.push({path, pkg: {project, version}})
+ if (await vacant(path)) continue // failed build probs
+ rv.push({ path, pkg: { project, version } })
} catch {
//noop: other directories can exist
}
@@ -59,7 +60,7 @@ export default function useCellar() {
/// if package is installed, returns its installation
async function resolve(pkg: Package | PackageRequirement | Path | Installation) {
const installation = await (async () => {
- if ("pkg" in pkg) { return pkg }
+ if ("pkg" in pkg) return pkg
// ^^ is `Installation`
const { prefix } = config
@@ -68,17 +69,18 @@ export default function useCellar() {
const version = new SemVer(path.basename())
const project = path.parent().relative({ to: prefix })
return {
- path, pkg: { project, version }
+ path,
+ pkg: { project, version },
}
} else if ("version" in pkg) {
const path = keg(pkg)
return { path, pkg }
} else {
const installations = await ls(pkg.project)
- const versions = installations.map(({ pkg: {version}}) => version)
+ const versions = installations.map(({ pkg: { version } }) => version)
const version = pkg.constraint.max(versions)
if (version) {
- const path = installations.find(({pkg: {version: v}}) => v.eq(version))!.path
+ const path = installations.find(({ pkg: { version: v } }) => v.eq(version))!.path
return { path, pkg: { project: pkg.project, version } }
} else {
throw new InstallationNotFoundError(pkg)
@@ -96,8 +98,8 @@ export default function useCellar() {
async function vacant(path: Path): Promise {
if (!path.isDirectory()) {
return true
- } else for await (const _ of path.ls()) {
- return false
- }
+ } else {for await (const _ of path.ls()) {
+ return false
+ }}
return true
}
diff --git a/src/hooks/useConfig.test.ts b/src/hooks/useConfig.test.ts
index 709a449..b84456b 100644
--- a/src/hooks/useConfig.test.ts
+++ b/src/hooks/useConfig.test.ts
@@ -1,4 +1,10 @@
-import { assert, assertEquals, assertFalse, assertThrows, assertMatch } from "deno/testing/asserts.ts"
+import {
+ assert,
+ assertEquals,
+ assertFalse,
+ assertMatch,
+ assertThrows,
+} from "deno/testing/asserts.ts"
import { _internals, ConfigDefault } from "./useConfig.ts"
import { useTestConfig } from "./useTestConfig.ts"
import Path from "../utils/Path.ts"
@@ -13,7 +19,7 @@ Deno.test("useConfig", () => {
}
config = ConfigDefault({ TEA_PANTRY_PATH: "/foo:/bar", CI: "true" })
- assertEquals(config.pantries.map(x => x.string), ["/foo", "/bar"])
+ assertEquals(config.pantries.map((x) => x.string), ["/foo", "/bar"])
assertEquals(config.options.compression, "gz")
assertFalse(_internals.boolize("false"))
diff --git a/src/hooks/useConfig.ts b/src/hooks/useConfig.ts
index 5a0fe37..277caff 100644
--- a/src/hooks/useConfig.ts
+++ b/src/hooks/useConfig.ts
@@ -9,7 +9,7 @@ export interface Config {
options: {
/// prefer xz or gz for bottle downloads
- compression: 'xz' | 'gz'
+ compression: "xz" | "gz"
}
UserAgent?: string
@@ -18,14 +18,17 @@ export interface Config {
}
export function ConfigDefault(env = Deno.env.toObject()): Config {
- const prefix = flatmap(env['TEA_PREFIX']?.trim(), x => new Path(x)) ?? Path.home().join('.tea')
- const pantries = env['TEA_PANTRY_PATH']?.split(":").compact(x => flatmap(x.trim(), x => Path.abs(x) ?? Path.cwd().join(x))) ?? []
- const cache = Path.abs(env['TEA_CACHE_DIR']) ?? prefix.join('tea.xyz/var/www')
- const isCI = boolize(env['CI']) ?? false
- const UserAgent = flatmap(getv(), v => `tea.lib/${v}`) ?? 'tea.lib'
+ const prefix = flatmap(env["TEA_PREFIX"]?.trim(), (x) => new Path(x)) ?? Path.home().join(".tea")
+ const pantries =
+ env["TEA_PANTRY_PATH"]?.split(":").compact((x) =>
+ flatmap(x.trim(), (x) => Path.abs(x) ?? Path.cwd().join(x))
+ ) ?? []
+ const cache = Path.abs(env["TEA_CACHE_DIR"]) ?? prefix.join("tea.xyz/var/www")
+ const isCI = boolize(env["CI"]) ?? false
+ const UserAgent = flatmap(getv(), (v) => `tea.lib/${v}`) ?? "tea.lib"
//TODO prefer 'xz' on Linux (as well) if supported
- const compression = !isCI && host().platform == 'darwin' ? 'xz' : 'gz'
+ const compression = !isCI && host().platform == "darwin" ? "xz" : "gz"
return {
prefix,
@@ -35,22 +38,22 @@ export function ConfigDefault(env = Deno.env.toObject()): Config {
options: {
compression,
},
- git: git(prefix, env.PATH)
+ git: git(prefix, env.PATH),
}
}
function getv(): string | undefined {
- if (typeof Deno === 'undefined') {
+ if (typeof Deno === "undefined") {
const url = new URL(import.meta.url)
const path = new Path(url.pathname).parent().parent().parent().join("package.json")
const blob = Deno.readFileSync(path.string)
const txt = new TextDecoder().decode(blob)
const { version } = JSON.parse(txt)
- return typeof version == 'string' ? version : undefined
+ return typeof version == "string" ? version : undefined
}
}
-const gt = globalThis as unknown as {xyz_tea_config?: Config}
+const gt = globalThis as unknown as { xyz_tea_config?: Config }
export default function useConfig(input?: Config): Config {
// storing on globalThis so our config is shared across
@@ -58,18 +61,18 @@ export default function useConfig(input?: Config): Config {
if (!gt.xyz_tea_config || input) {
gt.xyz_tea_config = input ?? ConfigDefault()
}
- return {...gt.xyz_tea_config} // copy to prevent mutation
+ return { ...gt.xyz_tea_config } // copy to prevent mutation
}
function boolize(input: string | undefined): boolean | undefined {
switch (input?.trim()?.toLowerCase()) {
- case '0':
- case 'false':
- case 'no':
+ case "0":
+ case "false":
+ case "no":
return false
- case '1':
- case 'true':
- case 'yes':
+ case "1":
+ case "true":
+ case "yes":
return true
}
}
@@ -84,7 +87,6 @@ function initialized() {
export const _internals = { reset, initialized, boolize }
-
/// we support a tea installed or system installed git, nothing else
/// eg. `git` could be a symlink in `PATH` to tea, which would cause a fork bomb
/// on darwin if xcode or xcode/clt is not installed this will fail to our http fallback above
@@ -99,10 +101,10 @@ function git(_prefix: Path, PATH?: string): Path | undefined {
/// don’t cause macOS to abort and then prompt the user to install the XcodeCLT
//FIXME test! but this is hard to test without docker images or something!
- if (host().platform == 'darwin') {
+ if (host().platform == "darwin") {
if (new Path("/Library/Developer/CommandLineTools/usr/bin/git").isExecutableFile()) return rv
if (new Path("/Applications/Xcode.app").isDirectory()) return rv
- return // don’t use `git`
+ return // don’t use `git`
}
return rv?.join("bin/git")
diff --git a/src/hooks/useDownload.test.ts b/src/hooks/useDownload.test.ts
index 927aa23..f5d5afe 100644
--- a/src/hooks/useDownload.test.ts
+++ b/src/hooks/useDownload.test.ts
@@ -2,14 +2,14 @@ import { useTestConfig } from "./useTestConfig.ts"
import { assert } from "deno/testing/asserts.ts"
import useDownload from "./useDownload.ts"
-Deno.test("etag-mtime-check", async runner => {
+Deno.test("etag-mtime-check", async (runner) => {
useTestConfig({ TEA_CACHE_DIR: Deno.makeTempDirSync() })
const src = new URL("https://dist.tea.xyz/ijg.org/versions.txt")
const { download, cache } = useDownload()
await runner.step("download", async () => {
- await download({src})
+ await download({ src })
const mtimePath = cache({ for: src }).join("mtime")
const etagPath = cache({ for: src }).join("etag")
@@ -29,12 +29,15 @@ Deno.test("etag-mtime-check", async runner => {
await runner.step("second download doesn’t http", async () => {
let n = 0
- await download({src}, blob => { n += blob.length; return Promise.resolve() }) // for coverage
+ await download({ src }, (blob) => {
+ n += blob.length
+ return Promise.resolve()
+ }) // for coverage
assert(n > 0)
})
await runner.step("second download doesn’t http and is fine if we do nothing", async () => {
- const dst = await download({src})
+ const dst = await download({ src })
assert(dst.isFile())
})
})
diff --git a/src/hooks/useDownload.ts b/src/hooks/useDownload.ts
index 0dae3f2..68a70a4 100644
--- a/src/hooks/useDownload.ts
+++ b/src/hooks/useDownload.ts
@@ -1,7 +1,7 @@
import { deno } from "../deps.ts"
const { crypto: crypto_, streams: { writeAll } } = deno
const { toHashString, crypto } = crypto_
-import { TeaError, panic } from "../utils/error.ts"
+import { panic, TeaError } from "../utils/error.ts"
import useConfig from "./useConfig.ts"
import useFetch from "./useFetch.ts"
import Path from "../utils/Path.ts"
@@ -12,7 +12,7 @@ interface DownloadOptions {
src: URL
dst?: Path
headers?: Record
- logger?: (info: {src: URL, dst: Path, rcvd?: number, total?: number }) => void
+ logger?: (info: { src: URL; dst: Path; rcvd?: number; total?: number }) => void
}
export class DownloadError extends TeaError {
@@ -20,9 +20,9 @@ export class DownloadError extends TeaError {
src: URL
headers?: Record
- constructor(status: number, opts: { src: URL, headers?: Record}) {
+ constructor(status: number, opts: { src: URL; headers?: Record }) {
super(`http: ${status}: ${opts.src}`)
- this.name = 'DownloadError'
+ this.name = "DownloadError"
this.status = status
this.src = opts.src
this.headers = opts.headers
@@ -31,7 +31,10 @@ export class DownloadError extends TeaError {
const tmpname = (dst: Path) => dst.parent().join(dst.basename() + ".incomplete")
-async function download(opts: DownloadOptions, chunk?: (blob: Uint8Array) => Promise): Promise {
+async function download(
+ opts: DownloadOptions,
+ chunk?: (blob: Uint8Array) => Promise,
+): Promise {
const [dst, stream] = await the_meat(opts)
if (stream || chunk) {
@@ -39,8 +42,8 @@ async function download(opts: DownloadOptions, chunk?: (blob: Uint8Array) => Pro
const writer = await (() => {
if (stream) {
- dst.parent().mkdir('p')
- return Deno.open(tmpname(dst).string, {write: true, create: true, truncate: true})
+ dst.parent().mkdir("p")
+ return Deno.open(tmpname(dst).string, { write: true, create: true, truncate: true })
}
})()
@@ -63,12 +66,12 @@ async function download(opts: DownloadOptions, chunk?: (blob: Uint8Array) => Pro
return dst
}
-function cache({ for: url }: {for: URL}): Path {
+function cache({ for: url }: { for: URL }): Path {
return useConfig().cache
.join(url.protocol.slice(0, -1))
.join(url.hostname)
.join(hash())
- .mkdir('p')
+ .mkdir("p")
function hash() {
let key = url.pathname
@@ -82,15 +85,15 @@ function cache({ for: url }: {for: URL}): Path {
export default function useDownload() {
return {
download,
- cache
+ cache,
}
}
-
/// internal
-async function the_meat({ src, logger, headers, dst }: DownloadOptions): Promise<[Path, ReadableStream | undefined, number | undefined]>
-{
+async function the_meat(
+ { src, logger, headers, dst }: DownloadOptions,
+): Promise<[Path, ReadableStream | undefined, number | undefined]> {
const hash = cache({ for: src })
const mtime_entry = hash.join("mtime")
const etag_entry = hash.join("etag")
@@ -116,36 +119,43 @@ async function the_meat({ src, logger, headers, dst }: DownloadOptions): Prom
const rsp = await useFetch(src, { headers })
switch (rsp.status) {
- case 200: {
- const sz = parseInt(rsp.headers.get("Content-Length")!).chuzzle()
-
- if (logger) logger({ src, dst, total: sz })
-
- const reader = rsp.body ?? panic()
-
- const text = rsp.headers.get("Last-Modified")
- if (text) mtime_entry.write({text, force: true})
- const etag = rsp.headers.get("ETag")
- if (etag) etag_entry.write({text: etag, force: true})
-
- if (!logger) {
- return [dst, reader, sz]
- } else {
- let n = 0
- return [dst, reader.pipeThrough(new TransformStream({
- transform: (buf, controller) => {
- n += buf.length
- logger({ src, dst: dst!, rcvd: n, total: sz })
- controller.enqueue(buf)
- }})), sz]
+ case 200: {
+ const sz = parseInt(rsp.headers.get("Content-Length")!).chuzzle()
+
+ if (logger) logger({ src, dst, total: sz })
+
+ const reader = rsp.body ?? panic()
+
+ const text = rsp.headers.get("Last-Modified")
+ if (text) mtime_entry.write({ text, force: true })
+ const etag = rsp.headers.get("ETag")
+ if (etag) etag_entry.write({ text: etag, force: true })
+
+ if (!logger) {
+ return [dst, reader, sz]
+ } else {
+ let n = 0
+ return [
+ dst,
+ reader.pipeThrough(
+ new TransformStream({
+ transform: (buf, controller) => {
+ n += buf.length
+ logger({ src, dst: dst!, rcvd: n, total: sz })
+ controller.enqueue(buf)
+ },
+ }),
+ ),
+ sz,
+ ]
+ }
}
- }
- case 304: {
- const sz = (await Deno.stat(dst.string)).size
- if (logger) logger({ src, dst, rcvd: sz, total: sz })
- return [dst, undefined, sz]
- }
- default:
- throw new DownloadError(rsp.status, { src, headers })
+ case 304: {
+ const sz = (await Deno.stat(dst.string)).size
+ if (logger) logger({ src, dst, rcvd: sz, total: sz })
+ return [dst, undefined, sz]
+ }
+ default:
+ throw new DownloadError(rsp.status, { src, headers })
}
}
diff --git a/src/hooks/useFetch.test.ts b/src/hooks/useFetch.test.ts
index 761cee4..25ae85f 100644
--- a/src/hooks/useFetch.test.ts
+++ b/src/hooks/useFetch.test.ts
@@ -1,8 +1,7 @@
-import { stub, assertSpyCallArgs } from "deno/testing/mock.ts"
+import { assertSpyCallArgs, stub } from "deno/testing/mock.ts"
import useConfig, { _internals, ConfigDefault } from "./useConfig.ts"
import useFetch from "./useFetch.ts"
-
Deno.test("fetch user-agent header check", async () => {
/// doesn't work inside DNT because fetch is shimmed to undici
if (Deno.env.get("NODE")) return
@@ -10,22 +9,22 @@ Deno.test("fetch user-agent header check", async () => {
const UserAgent = "tests/1.2.3"
_internals.reset()
- useConfig({...ConfigDefault(), UserAgent});
+ useConfig({ ...ConfigDefault(), UserAgent })
- const url = "https://example.com";
+ const url = "https://example.com"
const fetchStub = stub(
globalThis,
"fetch",
() => Promise.resolve(new Response("")),
- );
+ )
try {
- await useFetch(url, {});
+ await useFetch(url, {})
} finally {
- fetchStub.restore();
+ fetchStub.restore()
}
assertSpyCallArgs(fetchStub, 0, [url, {
- headers: {"User-Agent": UserAgent}
- }]);
-});
+ headers: { "User-Agent": UserAgent },
+ }])
+})
diff --git a/src/hooks/useFetch.ts b/src/hooks/useFetch.ts
index c86f515..85e8b78 100644
--- a/src/hooks/useFetch.ts
+++ b/src/hooks/useFetch.ts
@@ -1,7 +1,10 @@
import useConfig from "./useConfig.ts"
// useFetch wraps the native Deno fetch api and inserts a User-Agent header
-export default function useFetch(input: string | URL | Request, init?: RequestInit | undefined): Promise {
+export default function useFetch(
+ input: string | URL | Request,
+ init?: RequestInit | undefined,
+): Promise {
const { UserAgent } = useConfig()
const requestInit = init ?? {} as RequestInit
if (UserAgent) {
diff --git a/src/hooks/useInventory.ts b/src/hooks/useInventory.ts
index 903fad4..ee3e42e 100644
--- a/src/hooks/useInventory.ts
+++ b/src/hooks/useInventory.ts
@@ -19,7 +19,7 @@ const select = async (rq: PackageRequirement | Package) => {
if ("constraint" in rq) {
return rq.constraint.max(versions)
- } else if (versions.find(x => x.eq(rq.version))) {
+ } else if (versions.find((x) => x.eq(rq.version))) {
return rq.version
}
}
@@ -27,24 +27,24 @@ const select = async (rq: PackageRequirement | Package) => {
const get = async (rq: PackageRequirement | Package) => {
const { platform, arch } = host()
- const url = new URL('https://dist.tea.xyz')
- url.pathname = Path.root.join(rq.project, platform, arch, 'versions.txt').string
+ const url = new URL("https://dist.tea.xyz")
+ url.pathname = Path.root.join(rq.project, platform, arch, "versions.txt").string
const rsp = await useFetch(url)
if (!rsp.ok) {
- throw new DownloadError(rsp.status, {src: url})
+ throw new DownloadError(rsp.status, { src: url })
}
const releases = await rsp.text()
- let versions = releases.split("\n").compact(x => new SemVer(x))
+ let versions = releases.split("\n").compact((x) => new SemVer(x))
if (versions.length < 1) throw new Error()
- if (rq.project == 'openssl.org') {
+ if (rq.project == "openssl.org") {
// workaround our previous sins
const v = new SemVer("1.1.118")
- versions = versions.filter(x => x.neq(v))
+ versions = versions.filter((x) => x.neq(v))
}
return versions
diff --git a/src/hooks/useMoustaches.test.ts b/src/hooks/useMoustaches.test.ts
index e32636e..955179a 100644
--- a/src/hooks/useMoustaches.test.ts
+++ b/src/hooks/useMoustaches.test.ts
@@ -10,7 +10,7 @@ Deno.test("useMoustaches", () => {
const pkg: Package = {
project: "tea.xyz/test",
- version: new SemVer("1.0.0")
+ version: new SemVer("1.0.0"),
}
const tokens = moustaches.tokenize.all(pkg, [])
diff --git a/src/hooks/useMoustaches.ts b/src/hooks/useMoustaches.ts
index 49902cf..720ca83 100644
--- a/src/hooks/useMoustaches.ts
+++ b/src/hooks/useMoustaches.ts
@@ -1,7 +1,7 @@
import useConfig from "./useConfig.ts"
import SemVer from "../utils/semver.ts"
import useCellar from "./useCellar.ts"
-import { Package, Installation } from "../types.ts"
+import { Installation, Package } from "../types.ts"
import host from "../utils/host.ts"
import * as os from "node:os"
@@ -9,18 +9,18 @@ function tokenizePackage(pkg: Package) {
return [{ from: "prefix", to: useCellar().keg(pkg).string }]
}
-function tokenizeVersion(version: SemVer, prefix = 'version') {
+function tokenizeVersion(version: SemVer, prefix = "version") {
const rv = [
- { from: prefix, to: `${version}` },
- { from: `${prefix}.major`, to: `${version.major}` },
- { from: `${prefix}.minor`, to: `${version.minor}` },
- { from: `${prefix}.patch`, to: `${version.patch}` },
+ { from: prefix, to: `${version}` },
+ { from: `${prefix}.major`, to: `${version.major}` },
+ { from: `${prefix}.minor`, to: `${version.minor}` },
+ { from: `${prefix}.patch`, to: `${version.patch}` },
{ from: `${prefix}.marketing`, to: `${version.major}.${version.minor}` },
- { from: `${prefix}.build`, to: version.build.join('+') },
- { from: `${prefix}.raw`, to: version.raw },
+ { from: `${prefix}.build`, to: version.build.join("+") },
+ { from: `${prefix}.raw`, to: version.raw },
]
- if ('tag' in version) {
- rv.push({from: `${prefix}.tag`, to: (version as unknown as {tag: string}).tag})
+ if ("tag" in version) {
+ rv.push({ from: `${prefix}.tag`, to: (version as unknown as { tag: string }).tag })
}
return rv
}
@@ -29,32 +29,33 @@ function tokenizeVersion(version: SemVer, prefix = 'version') {
function tokenizeHost() {
const { arch, target, platform } = host()
return [
- { from: "hw.arch", to: arch },
- { from: "hw.target", to: target },
- { from: "hw.platform", to: platform },
- { from: "hw.concurrency", to: os.cpus().length.toString() }
+ { from: "hw.arch", to: arch },
+ { from: "hw.target", to: target },
+ { from: "hw.platform", to: platform },
+ { from: "hw.concurrency", to: os.cpus().length.toString() },
]
}
-function apply(input: string, map: { from: string, to: string }[]) {
- return map.reduce((acc, {from, to}) =>
- acc.replace(new RegExp(`(^\\$)?{{\\s*${from}\\s*}}`, "g"), to),
- input)
+function apply(input: string, map: { from: string; to: string }[]) {
+ return map.reduce(
+ (acc, { from, to }) => acc.replace(new RegExp(`(^\\$)?{{\\s*${from}\\s*}}`, "g"), to),
+ input,
+ )
}
-export default function() {
+export default function () {
const config = useConfig()
const base = {
apply,
tokenize: {
version: tokenizeVersion,
host: tokenizeHost,
- pkg: tokenizePackage
- }
+ pkg: tokenizePackage,
+ },
}
const deps = (deps: Installation[]) => {
- const map: {from: string, to: string}[] = []
+ const map: { from: string; to: string }[] = []
for (const dep of deps ?? []) {
map.push({ from: `deps.${dep.pkg.project}.prefix`, to: dep.path.string })
map.push(...base.tokenize.version(dep.pkg.version, `deps.${dep.pkg.project}.version`))
@@ -76,7 +77,9 @@ export default function() {
apply: base.apply,
tokenize: {
...base.tokenize,
- deps, tea, all
- }
+ deps,
+ tea,
+ all,
+ },
}
}
diff --git a/src/hooks/useOffLicense.ts b/src/hooks/useOffLicense.ts
index a3d3ffb..351db9f 100644
--- a/src/hooks/useOffLicense.ts
+++ b/src/hooks/useOffLicense.ts
@@ -2,7 +2,7 @@ import { Stowage } from "../types.ts"
import host from "../utils/host.ts"
import Path from "../utils/Path.ts"
-type Type = 's3'
+type Type = "s3"
export default function useOffLicense(_type: Type) {
return { url, key }
@@ -10,15 +10,15 @@ export default function useOffLicense(_type: Type) {
function key(stowage: Stowage) {
let rv = Path.root.join(stowage.pkg.project)
- if (stowage.type == 'bottle') {
+ if (stowage.type == "bottle") {
const { platform, arch } = stowage.host ?? host()
rv = rv.join(`${platform}/${arch}`)
}
let fn = `v${stowage.pkg.version}`
- if (stowage.type == 'bottle') {
+ if (stowage.type == "bottle") {
fn += `.tar.${stowage.compression}`
} else {
- fn += stowage.extname
+ fn += stowage.extname
}
return rv.join(fn).string.slice(1)
}
diff --git a/src/hooks/usePantry.test.ts b/src/hooks/usePantry.test.ts
index 56d08c1..63d7c71 100644
--- a/src/hooks/usePantry.test.ts
+++ b/src/hooks/usePantry.test.ts
@@ -19,26 +19,26 @@ Deno.test("which()", async () => {
Deno.test("provider()", async () => {
const provides = await usePantry().project("npmjs.com").provider()
- const foo = provides!('truffle')
- assertEquals(foo![0], 'npx')
+ const foo = provides!("truffle")
+ assertEquals(foo![0], "npx")
})
Deno.test("available()", async () => {
- const stubber = stub(_internals, 'platform', () => "darwin" as "darwin" | "linux")
+ const stubber = stub(_internals, "platform", () => "darwin" as "darwin" | "linux")
assert(await usePantry().project("agpt.co").available())
stubber.restore()
})
Deno.test("runtime.env", async () => {
const TEA_PANTRY_PATH = new Path(Deno.env.get("SRCROOT")!).join("fixtures").string
- const { prefix } = useTestConfig({ TEA_PANTRY_PATH })
+ const { prefix } = useTestConfig({ TEA_PANTRY_PATH })
const deps = [{
pkg: {
project: "bar.com",
- version: new SemVer("1.2.3")
+ version: new SemVer("1.2.3"),
},
- path: prefix.join("bar.com/v1.2.3")
+ path: prefix.join("bar.com/v1.2.3"),
}]
const env = await usePantry().project("foo.com").runtime.env(new SemVer("2.3.4"), deps)
@@ -49,7 +49,7 @@ Deno.test("runtime.env", async () => {
})
Deno.test("missing()", () => {
- useTestConfig({TEA_PANTRY_PATH: "/a"})
+ useTestConfig({ TEA_PANTRY_PATH: "/a" })
assert(usePantry().missing())
})
diff --git a/src/hooks/usePantry.ts b/src/hooks/usePantry.ts
index c03bcf5..d1a8e18 100644
--- a/src/hooks/usePantry.ts
+++ b/src/hooks/usePantry.ts
@@ -1,7 +1,7 @@
import { is_what, PlainObject } from "../deps.ts"
const { isNumber, isPlainObject, isString, isArray, isPrimitive, isBoolean } = is_what
import { validatePackageRequirement } from "../utils/hacks.ts"
-import { Package, Installation } from "../types.ts"
+import { Installation, Package } from "../types.ts"
import useMoustaches from "./useMoustaches.ts"
import { TeaError } from "../utils/error.ts"
import { validate } from "../utils/misc.ts"
@@ -15,8 +15,7 @@ export interface Interpreter {
args: string[]
}
-export class PantryError extends TeaError
-{}
+export class PantryError extends TeaError {}
export class PantryParseError extends PantryError {
project: string
@@ -46,7 +45,7 @@ export class PantryNotFoundError extends PantryError {
export default function usePantry() {
const config = useConfig()
- const prefix = config.prefix.join('tea.xyz/var/pantry/projects')
+ const prefix = config.prefix.join("tea.xyz/var/pantry/projects")
async function* ls(): AsyncGenerator {
const seen = new Set()
@@ -73,9 +72,12 @@ export default function usePantry() {
let memo: Promise | undefined
- return () => memo ?? (memo = filename.readYAML()
- .then(validate.obj)
- .catch(cause => { throw new PantryParseError(project, filename, cause) }))
+ return () =>
+ memo ?? (memo = filename.readYAML()
+ .then(validate.obj)
+ .catch((cause) => {
+ throw new PantryParseError(project, filename, cause)
+ }))
}
throw new PackageNotFoundError(project)
})()
@@ -93,7 +95,8 @@ export default function usePantry() {
if (!platforms) return true
if (isString(platforms)) platforms = [platforms]
if (!isArray(platforms)) throw new PantryParseError(project)
- return platforms.includes(host().platform) ||platforms.includes(`${host().platform}/${host().arch}`)
+ return platforms.includes(host().platform) ||
+ platforms.includes(`${host().platform}/${host().arch}`)
}
const drydeps = async () => parse_pkgs_node((await yaml()).dependencies)
@@ -106,7 +109,7 @@ export default function usePantry() {
}
if (!isArray(node)) throw new PantryParseError(project)
- return node.compact(x => {
+ return node.compact((x) => {
if (isPlainObject(x)) {
x = x["executable"]
}
@@ -127,12 +130,12 @@ export default function usePantry() {
const cmds = validate.arr(yaml.cmds)
return (binname: string) => {
if (!cmds.includes(binname)) return
- const args = yaml['args']
+ const args = yaml["args"]
if (isPlainObject(args)) {
if (args[binname]) {
return get_args(args[binname])
} else {
- return get_args(args['...'])
+ return get_args(args["..."])
}
} else {
return get_args(args)
@@ -152,12 +155,12 @@ export default function usePantry() {
companions,
runtime: {
env: runtime_env,
- deps: drydeps
+ deps: drydeps,
},
available,
provides,
provider,
- yaml
+ yaml,
}
}
@@ -171,7 +174,7 @@ export default function usePantry() {
//TODO not very performant due to serial awaits
const rv: Foo[] = []
for await (const pkg of ls()) {
- const proj = {...project(pkg.project), ...pkg}
+ const proj = { ...project(pkg.project), ...pkg }
if (pkg.project.toLowerCase() == name) {
rv.push(proj)
continue
@@ -179,15 +182,17 @@ export default function usePantry() {
const yaml = await proj.yaml()
if (yaml["display-name"]?.toLowerCase() == name) {
rv.push(proj)
- } else if ((await proj.provides()).map(x => x.toLowerCase()).includes(name)) {
+ } else if ((await proj.provides()).map((x) => x.toLowerCase()).includes(name)) {
rv.push(proj)
}
}
return rv
}
- async function which({ interprets: extension }: { interprets: string }): Promise {
- if (extension[0] == '.') extension = extension.slice(1)
+ async function which(
+ { interprets: extension }: { interprets: string },
+ ): Promise {
+ if (extension[0] == ".") extension = extension.slice(1)
if (!extension) return
for await (const pkg of ls()) {
const yml = await project(pkg).yaml()
@@ -195,8 +200,10 @@ export default function usePantry() {
if (!isPlainObject(node)) continue
try {
const { extensions, args } = yml["interprets"]
- if ((isString(extensions) && extensions === extension) ||
- (isArray(extensions) && extensions.includes(extension))) {
+ if (
+ (isString(extensions) && extensions === extension) ||
+ (isArray(extensions) && extensions.includes(extension))
+ ) {
return { project: pkg.project, args: isArray(args) ? args : [args] }
}
} catch {
@@ -208,7 +215,7 @@ export default function usePantry() {
const missing = () => {
try {
- return !pantry_paths().some(x => x.exists())
+ return !pantry_paths().some((x) => x.exists())
} catch (e) {
if (e instanceof PantryNotFoundError) {
return true
@@ -234,7 +241,7 @@ export default function usePantry() {
parse_pkgs_node,
expand_env_obj,
missing,
- neglected
+ neglected,
}
function pantry_paths(): Path[] {
@@ -262,8 +269,7 @@ export function parse_pkgs_node(node: any) {
platform_reduce(node)
return Object.entries(node)
- .compact(([project, constraint]) =>
- validatePackageRequirement(project, constraint))
+ .compact(([project, constraint]) => validatePackageRequirement(project, constraint))
}
/// expands platform specific keys into the object
@@ -275,7 +281,7 @@ function platform_reduce(env: PlainObject) {
let match = key.match(/^(darwin|linux)\/(aarch64|x86-64)$/)
if (match) return [match[1], match[2]]
if ((match = key.match(/^(darwin|linux)$/))) return [match[1]]
- if ((match = key.match(/^(aarch64|x86-64)$/))) return [,match[1]]
+ if ((match = key.match(/^(aarch64|x86-64)$/))) return [, match[1]]
return []
})()
@@ -300,8 +306,12 @@ function platform_reduce(env: PlainObject) {
}
}
-export function expand_env_obj(env_: PlainObject, pkg: Package, deps: Installation[]): Record {
- const env = {...env_}
+export function expand_env_obj(
+ env_: PlainObject,
+ pkg: Package,
+ deps: Installation[],
+): Record {
+ const env = { ...env_ }
platform_reduce(env)
@@ -309,7 +319,7 @@ export function expand_env_obj(env_: PlainObject, pkg: Package, deps: Installati
for (let [key, value] of Object.entries(env)) {
if (isArray(value)) {
- value = value.map(x => transform(x)).join(" ")
+ value = value.map((x) => transform(x)).join(" ")
} else {
value = transform(value)
}
@@ -321,7 +331,9 @@ export function expand_env_obj(env_: PlainObject, pkg: Package, deps: Installati
// deno-lint-ignore no-explicit-any
function transform(value: any): string {
- if (!isPrimitive(value)) throw new PantryParseError(pkg.project, undefined, JSON.stringify(value))
+ if (!isPrimitive(value)) {
+ throw new PantryParseError(pkg.project, undefined, JSON.stringify(value))
+ }
if (isBoolean(value)) {
return value ? "1" : "0"
@@ -331,8 +343,8 @@ export function expand_env_obj(env_: PlainObject, pkg: Package, deps: Installati
const mm = useMoustaches()
const home = Path.home().string
const obj = [
- { from: 'env.HOME', to: home }, // historic, should be removed at v1
- { from: 'home', to: home } // remove, stick with just ~
+ { from: "env.HOME", to: home }, // historic, should be removed at v1
+ { from: "home", to: home }, // remove, stick with just ~
]
obj.push(...mm.tokenize.all(pkg, deps))
return mm.apply(value, obj)
diff --git a/src/hooks/useShellEnv.ts b/src/hooks/useShellEnv.ts
index 35fe290..c136a76 100644
--- a/src/hooks/useShellEnv.ts
+++ b/src/hooks/useShellEnv.ts
@@ -4,19 +4,19 @@ import usePantry from "./usePantry.ts"
import host from "../utils/host.ts"
export const EnvKeys = [
- 'PATH',
- 'MANPATH',
- 'PKG_CONFIG_PATH',
- 'LIBRARY_PATH',
- 'LD_LIBRARY_PATH',
- 'CPATH',
- 'XDG_DATA_DIRS',
- 'CMAKE_PREFIX_PATH',
- 'DYLD_FALLBACK_LIBRARY_PATH',
- 'SSL_CERT_FILE',
- 'LDFLAGS',
- 'TEA_PREFIX',
- 'ACLOCAL_PATH'
+ "PATH",
+ "MANPATH",
+ "PKG_CONFIG_PATH",
+ "LIBRARY_PATH",
+ "LD_LIBRARY_PATH",
+ "CPATH",
+ "XDG_DATA_DIRS",
+ "CMAKE_PREFIX_PATH",
+ "DYLD_FALLBACK_LIBRARY_PATH",
+ "SSL_CERT_FILE",
+ "LDFLAGS",
+ "TEA_PREFIX",
+ "ACLOCAL_PATH",
] as const
export type EnvKey = typeof EnvKeys[number]
@@ -24,28 +24,27 @@ interface Options {
installations: Installation[]
}
-export default function() {
+export default function () {
return {
map,
expand,
- flatten
+ flatten,
}
}
/// returns an environment that supports the provided packages
-async function map({installations}: Options): Promise> {
+async function map({ installations }: Options): Promise> {
const vars: Partial>> = {}
- const isMac = host().platform == 'darwin'
+ const isMac = host().platform == "darwin"
- const projects = new Set(installations.map(x => x.pkg.project))
- const has_cmake = projects.has('cmake.org')
+ const projects = new Set(installations.map((x) => x.pkg.project))
+ const has_cmake = projects.has("cmake.org")
const archaic = true
const rv: Record = {}
const seen = new Set()
for (const installation of installations) {
-
if (!seen.insert(installation.pkg.project).inserted) {
console.warn("tea: env is being duped:", installation.pkg.project)
}
@@ -57,7 +56,10 @@ async function map({installations}: Options): Promise>
}
if (archaic) {
- vars.LIBRARY_PATH = compact_add(vars.LIBRARY_PATH, installation.path.join("lib").chuzzle()?.string)
+ vars.LIBRARY_PATH = compact_add(
+ vars.LIBRARY_PATH,
+ installation.path.join("lib").chuzzle()?.string,
+ )
vars.CPATH = compact_add(vars.CPATH, installation.path.join("include").chuzzle()?.string)
}
@@ -65,11 +67,14 @@ async function map({installations}: Options): Promise>
vars.CMAKE_PREFIX_PATH = compact_add(vars.CMAKE_PREFIX_PATH, installation.path.string)
}
- if (projects.has('gnu.org/autoconf')) {
- vars.ACLOCAL_PATH = compact_add(vars.ACLOCAL_PATH, installation.path.join("share/aclocal").chuzzle()?.string)
+ if (projects.has("gnu.org/autoconf")) {
+ vars.ACLOCAL_PATH = compact_add(
+ vars.ACLOCAL_PATH,
+ installation.path.join("share/aclocal").chuzzle()?.string,
+ )
}
- if (installation.pkg.project === 'openssl.org') {
+ if (installation.pkg.project === "openssl.org") {
const certPath = installation.path.join("ssl/cert.pem").chuzzle()?.string
// this is a single file, so we assume a
// valid entry is correct
@@ -80,17 +85,20 @@ async function map({installations}: Options): Promise>
}
// pantry configured runtime environment
- const runtime = await usePantry().project(installation.pkg).runtime.env(installation.pkg.version, installations)
+ const runtime = await usePantry().project(installation.pkg).runtime.env(
+ installation.pkg.version,
+ installations,
+ )
for (const key in runtime) {
rv[key] ??= []
rv[key].push(runtime[key])
}
}
- // this is how we use precise versions of libraries
- // for your virtual environment
- //FIXME SIP on macOS prevents DYLD_FALLBACK_LIBRARY_PATH from propagating to grandchild processes
- if (vars.LIBRARY_PATH) {
+ // this is how we use precise versions of libraries
+ // for your virtual environment
+ //FIXME SIP on macOS prevents DYLD_FALLBACK_LIBRARY_PATH from propagating to grandchild processes
+ if (vars.LIBRARY_PATH) {
vars.LD_LIBRARY_PATH = vars.LIBRARY_PATH
if (isMac) {
// non FALLBACK variety causes strange issues in edge cases
@@ -119,32 +127,33 @@ async function map({installations}: Options): Promise>
function suffixes(key: EnvKey) {
switch (key) {
- case 'PATH':
+ case "PATH":
return ["bin", "sbin"]
- case 'MANPATH':
+ case "MANPATH":
return ["man", "share/man"]
- case 'PKG_CONFIG_PATH':
- return ['share/pkgconfig', 'lib/pkgconfig']
- case 'XDG_DATA_DIRS':
- return ['share']
- case 'LIBRARY_PATH':
- case 'LD_LIBRARY_PATH':
- case 'DYLD_FALLBACK_LIBRARY_PATH':
- case 'CPATH':
- case 'CMAKE_PREFIX_PATH':
- case 'SSL_CERT_FILE':
- case 'LDFLAGS':
- case 'TEA_PREFIX':
- case 'ACLOCAL_PATH':
- return [] // we handle these specially
+ case "PKG_CONFIG_PATH":
+ return ["share/pkgconfig", "lib/pkgconfig"]
+ case "XDG_DATA_DIRS":
+ return ["share"]
+ case "LIBRARY_PATH":
+ case "LD_LIBRARY_PATH":
+ case "DYLD_FALLBACK_LIBRARY_PATH":
+ case "CPATH":
+ case "CMAKE_PREFIX_PATH":
+ case "SSL_CERT_FILE":
+ case "LDFLAGS":
+ case "TEA_PREFIX":
+ case "ACLOCAL_PATH":
+ return [] // we handle these specially
default: {
const exhaustiveness_check: never = key
throw new Error(`unhandled id: ${exhaustiveness_check}`)
- }}
+ }
+ }
}
export function expand(env: Record) {
- let rv = ''
+ let rv = ""
for (const [key, value] of Object.entries(env)) {
if (value.length == 0) continue
rv += `export ${key}="${value.join(":")}"\n`
@@ -168,23 +177,23 @@ function compact_add(set: OrderedSet | undefined, item: T | null | undefin
}
class OrderedSet {
- private items: T[];
- private set: Set;
+ private items: T[]
+ private set: Set
constructor() {
- this.items = [];
- this.set = new Set();
+ this.items = []
+ this.set = new Set()
}
add(item: T): void {
if (!this.set.has(item)) {
- this.items.push(item);
- this.set.add(item);
+ this.items.push(item)
+ this.set.add(item)
}
}
toArray(): T[] {
- return [...this.items];
+ return [...this.items]
}
isEmpty(): boolean {
diff --git a/src/hooks/useSync.test.ts b/src/hooks/useSync.test.ts
index 5d4983f..2933af6 100644
--- a/src/hooks/useSync.test.ts
+++ b/src/hooks/useSync.test.ts
@@ -3,7 +3,7 @@ import { assert } from "deno/testing/asserts.ts"
import usePantry from "./usePantry.ts"
import useSync from "./useSync.ts"
-Deno.test("useSync", async runner => {
+Deno.test("useSync", async (runner) => {
await runner.step("w/o git", async () => {
const TEA_PREFIX = Deno.makeTempDirSync()
const conf = useTestConfig({ TEA_PREFIX, TEA_PANTRY_PATH: `${TEA_PREFIX}/tea.xyz/var/pantry` })
@@ -13,7 +13,11 @@ Deno.test("useSync", async runner => {
await runner.step("w/git", async () => {
const TEA_PREFIX = Deno.makeTempDirSync()
- const conf = useTestConfig({ TEA_PREFIX, TEA_PANTRY_PATH: `${TEA_PREFIX}/tea.xyz/var/pantry`, PATH: "/usr/bin" })
+ const conf = useTestConfig({
+ TEA_PREFIX,
+ TEA_PANTRY_PATH: `${TEA_PREFIX}/tea.xyz/var/pantry`,
+ PATH: "/usr/bin",
+ })
assert(conf.git !== undefined)
await test()
diff --git a/src/hooks/useSync.ts b/src/hooks/useSync.ts
index 8fa5b5d..0b735ba 100644
--- a/src/hooks/useSync.ts
+++ b/src/hooks/useSync.ts
@@ -16,13 +16,13 @@ interface Logger {
syncd(path: Path): void
}
-export default async function(logger?: Logger) {
+export default async function (logger?: Logger) {
const pantry_dir = usePantry().prefix.parent()
logger?.syncing(pantry_dir)
- const { rid } = await Deno.open(pantry_dir.mkdir('p').string)
- await flock(rid, 'ex')
+ const { rid } = await Deno.open(pantry_dir.mkdir("p").string)
+ await flock(rid, "ex")
try {
//TODO if there was already a lock, just wait on it, don’t do the following stuff
@@ -32,11 +32,17 @@ export default async function(logger?: Logger) {
if (git_dir.join("HEAD").isFile()) {
await git("-C", git_dir, "fetch", "--quiet", "origin", "--force", "main:main")
} else {
- await git("clone", "--quiet", "--bare", "--depth=1", "https://github.com/teaxyz/pantry", git_dir)
+ await git(
+ "clone",
+ "--quiet",
+ "--bare",
+ "--depth=1",
+ "https://github.com/teaxyz/pantry",
+ git_dir,
+ )
}
await git("--git-dir", git_dir, "--work-tree", pantry_dir, "checkout", "--quiet", "--force")
-
} catch {
// git failure or no git installed
// ∴ download the latest tarball and uncompress over the top
@@ -45,9 +51,9 @@ export default async function(logger?: Logger) {
const proc = Deno.run({
cmd: ["tar", "xz", "--strip-components=1"],
cwd: pantry_dir.string,
- stdin: "piped"
+ stdin: "piped",
})
- await useDownload().download({ src }, blob => writeAll(proc.stdin, blob))
+ await useDownload().download({ src }, (blob) => writeAll(proc.stdin, blob))
proc.stdin.close()
if (!(await proc.status()).success) {
@@ -55,10 +61,9 @@ export default async function(logger?: Logger) {
}
proc.close()
-
} finally {
- await flock(rid, 'un')
- Deno.close(rid) // docs aren't clear if we need to do this or not
+ await flock(rid, "un")
+ Deno.close(rid) // docs aren't clear if we need to do this or not
}
logger?.syncd(pantry_dir)
@@ -68,8 +73,8 @@ export default async function(logger?: Logger) {
async function git(...args: (string | Path)[]) {
const { git } = useConfig()
- if (!git) throw new Error("no-git") // caught above to trigger http download instead
- await run({cmd: [git, ...args]})
+ if (!git) throw new Error("no-git") // caught above to trigger http download instead
+ await run({ cmd: [git, ...args] })
}
export interface RunOptions {
@@ -77,8 +82,8 @@ export interface RunOptions {
}
async function run(opts: RunOptions) {
- const cmd = opts.cmd.map(x => `${x}`)
- const proc = Deno.run({ ...opts, cmd, stdout: 'null', clearEnv: true })
+ const cmd = opts.cmd.map((x) => `${x}`)
+ const proc = Deno.run({ ...opts, cmd, stdout: "null", clearEnv: true })
try {
const exit = await proc.status()
if (!exit.success) throw new Error(`run.exit(${exit.code})`)
diff --git a/src/plumbing/hydrate.test.ts b/src/plumbing/hydrate.test.ts
index 8001f18..27f0a07 100644
--- a/src/plumbing/hydrate.test.ts
+++ b/src/plumbing/hydrate.test.ts
@@ -5,38 +5,38 @@ import * as semver from "../utils/semver.ts"
import hydrate from "./hydrate.ts"
describe("hydrate()", () => {
- it("hydrates.1", async function() {
+ it("hydrates.1", async function () {
const pkgs = [
- { project: 'nodejs.org', constraint: new semver.Range('*') },
- { project: 'nodejs.org', constraint: new semver.Range('>=18.14') }
+ { project: "nodejs.org", constraint: new semver.Range("*") },
+ { project: "nodejs.org", constraint: new semver.Range(">=18.14") },
]
const rv1 = semver.intersect(pkgs[0].constraint, pkgs[1].constraint)
- assertEquals(rv1.toString(), '>=18.14')
+ assertEquals(rv1.toString(), ">=18.14")
- const rv = await hydrate(pkgs, (_a: PackageRequirement, _b: boolean) => Promise.resolve([]))
+ const rv = await hydrate(pkgs, (_a: PackageRequirement, _b: boolean) => Promise.resolve([]))
let nodes = 0
for (const pkg of rv.pkgs) {
- if (pkg.project === 'nodejs.org') {
+ if (pkg.project === "nodejs.org") {
nodes++
- assertEquals(pkg.constraint.toString(), '>=18.14')
+ assertEquals(pkg.constraint.toString(), ">=18.14")
}
}
assertEquals(nodes, 1)
})
- it("hydrates.2", async function() {
+ it("hydrates.2", async function () {
const pkgs = [
- { project: 'pipenv.pypa.io', constraint: new semver.Range('*') },
- { project: 'python.org', constraint: new semver.Range('~3.9') }
+ { project: "pipenv.pypa.io", constraint: new semver.Range("*") },
+ { project: "python.org", constraint: new semver.Range("~3.9") },
]
const rv = await hydrate(pkgs, (pkg: PackageRequirement, _dry: boolean) => {
- if (pkg.project === 'pipenv.pypa.io') {
+ if (pkg.project === "pipenv.pypa.io") {
return Promise.resolve([
- { project: 'python.org', constraint: new semver.Range('>=3.7') }
+ { project: "python.org", constraint: new semver.Range(">=3.7") },
])
} else {
return Promise.resolve([])
@@ -45,8 +45,8 @@ describe("hydrate()", () => {
let nodes = 0
for (const pkg of rv.pkgs) {
- if (pkg.project === 'python.org') {
- assertEquals(pkg.constraint.toString(), '~3.9')
+ if (pkg.project === "python.org") {
+ assertEquals(pkg.constraint.toString(), "~3.9")
nodes++
}
}
@@ -54,16 +54,16 @@ describe("hydrate()", () => {
assertEquals(nodes, 1)
})
- it("hydrates.3", async function() {
+ it("hydrates.3", async function () {
const pkgs = [
- { project: 'pipenv.pypa.io', constraint: new semver.Range('*') },
- { project: 'python.org', constraint: new semver.Range('~3.9') }
+ { project: "pipenv.pypa.io", constraint: new semver.Range("*") },
+ { project: "python.org", constraint: new semver.Range("~3.9") },
]
const rv = await hydrate(pkgs, (pkg: PackageRequirement, _dry: boolean) => {
- if (pkg.project === 'pipenv.pypa.io') {
+ if (pkg.project === "pipenv.pypa.io") {
return Promise.resolve([
- { project: 'python.org', constraint: new semver.Range('~3.9.1') }
+ { project: "python.org", constraint: new semver.Range("~3.9.1") },
])
} else {
return Promise.resolve([])
@@ -72,8 +72,8 @@ describe("hydrate()", () => {
let nodes = 0
for (const pkg of rv.pkgs) {
- if (pkg.project === 'python.org') {
- assertEquals(pkg.constraint.toString(), '~3.9.1')
+ if (pkg.project === "python.org") {
+ assertEquals(pkg.constraint.toString(), "~3.9.1")
nodes++
}
}
diff --git a/src/plumbing/hydrate.ts b/src/plumbing/hydrate.ts
index dff956a..de6e90c 100644
--- a/src/plumbing/hydrate.ts
+++ b/src/plumbing/hydrate.ts
@@ -1,10 +1,9 @@
-import { PackageRequirement, Package } from "../types.ts"
+import { Package, PackageRequirement } from "../types.ts"
import * as semver from "../utils/semver.ts"
import usePantry from "../hooks/usePantry.ts"
import { is_what } from "../deps.ts"
const { isArray } = is_what
-
//TODO linktime cyclic dependencies cannot be allowed
//NOTE however if they aren’t link time it's presumably ok in some scenarios
// eg a tool that lists a directory may depend on a tool that identifies the
@@ -12,7 +11,6 @@ const { isArray } = is_what
//FIXME actually we are not refining the constraints currently
//TODO we are not actually restricting subsequent asks, eg. deno^1 but then deno^1.2
-
interface ReturnValue {
/// full list topologically sorted (ie dry + wet)
pkgs: PackageRequirement[]
@@ -38,13 +36,12 @@ const get = (x: PackageRequirement) => usePantry().project(x).runtime.deps()
export default async function hydrate(
input: (PackageRequirement | Package)[] | (PackageRequirement | Package),
get_deps: (pkg: PackageRequirement, dry: boolean) => Promise = get,
-): Promise
-{
+): Promise {
if (!isArray(input)) input = [input]
- const dry = condense(input.map(spec => {
+ const dry = condense(input.map((spec) => {
if ("version" in spec) {
- return {project: spec.project, constraint: new semver.Range(`=${spec.version}`)}
+ return { project: spec.project, constraint: new semver.Range(`=${spec.version}`) }
} else {
return spec
}
@@ -52,7 +49,7 @@ export default async function hydrate(
const graph: Record = {}
const bootstrap = new Set()
- const initial_set = new Set(dry.map(x => x.project))
+ const initial_set = new Set(dry.map((x) => x.project))
const stack: Node[] = []
// Starting the DFS loop for each package in the dry list
@@ -96,23 +93,25 @@ export default async function hydrate(
// Sorting and constructing the return value
const pkgs = Object.values(graph)
.sort((a, b) => b.count() - a.count())
- .map(({pkg}) => pkg)
+ .map(({ pkg }) => pkg)
//TODO strictly we need to record precisely the bootstrap version constraint
- const bootstrap_required = new Set(pkgs.compact(({project}) => bootstrap.has(project) && project))
+ const bootstrap_required = new Set(
+ pkgs.compact(({ project }) => bootstrap.has(project) && project),
+ )
return {
pkgs,
- dry: pkgs.filter(({project}) => initial_set.has(project)),
- wet: pkgs.filter(({project}) => !initial_set.has(project) || bootstrap_required.has(project)),
- bootstrap_required
+ dry: pkgs.filter(({ project }) => initial_set.has(project)),
+ wet: pkgs.filter(({ project }) => !initial_set.has(project) || bootstrap_required.has(project)),
+ bootstrap_required,
}
}
function condense(pkgs: PackageRequirement[]) {
const out: PackageRequirement[] = []
for (const pkg of pkgs) {
- const found = out.find(x => x.project === pkg.project)
+ const found = out.find((x) => x.project === pkg.project)
if (found) {
found.constraint = semver.intersect(found.constraint, pkg.constraint)
} else {
@@ -122,7 +121,6 @@ function condense(pkgs: PackageRequirement[]) {
return out
}
-
/////////////////////////////////////////////////////////////////////////// lib
class Node {
parent: Node | undefined
diff --git a/src/plumbing/install.test.ts b/src/plumbing/install.test.ts
index 1b0a034..c0e89fb 100644
--- a/src/plumbing/install.test.ts
+++ b/src/plumbing/install.test.ts
@@ -5,10 +5,10 @@ import { stub } from "deno/testing/mock.ts"
import SemVer from "../utils/semver.ts"
import { Package } from "../types.ts"
-Deno.test("install()", async runner => {
+Deno.test("install()", async (runner) => {
const pkg: Package = {
project: "tea.xyz/brewkit",
- version: new SemVer("0.30.0")
+ version: new SemVer("0.30.0"),
}
const conf = useTestConfig()
@@ -16,7 +16,7 @@ Deno.test("install()", async runner => {
await runner.step("download & install", async () => {
// for coverage
const logger = ConsoleLogger()
- const stubber = stub(console, "error", x => assert(x))
+ const stubber = stub(console, "error", (x) => assert(x))
const installation = await install(pkg, logger)
@@ -44,7 +44,7 @@ Deno.test("install()", async runner => {
Deno.test("install locks", async () => {
const pkg: Package = {
project: "tea.xyz/brewkit",
- version: new SemVer("0.30.0")
+ version: new SemVer("0.30.0"),
}
const conf = useTestConfig()
@@ -55,7 +55,7 @@ Deno.test("install locks", async () => {
locking: () => {},
installed: () => {},
installing: () => assertFalse(unlocked_once),
- unlocking: () => unlocked_once = true
+ unlocking: () => unlocked_once = true,
}
const installer1 = install(pkg, logger)
diff --git a/src/plumbing/install.ts b/src/plumbing/install.ts
index f9bbb99..6ba39e1 100644
--- a/src/plumbing/install.ts
+++ b/src/plumbing/install.ts
@@ -1,7 +1,7 @@
// deno-lint-ignore-file no-deprecated-deno-api
// ^^ dnt doesn’t support Deno.Command yet so we’re stuck with the deprecated Deno.run for now
-import { Package, Installation, StowageNativeBottle } from "../types.ts"
+import { Installation, Package, StowageNativeBottle } from "../types.ts"
import useOffLicense from "../hooks/useOffLicense.ts"
import useDownload from "../hooks/useDownload.ts"
import { flock } from "../utils/flock.deno.ts"
@@ -21,13 +21,13 @@ export default async function install(pkg: Package, logger?: Logger): Promise {
+ logger: (info) => {
logger?.downloading?.({ pkg, ...info })
total ??= info.total
- }
- }, blob => {
+ },
+ }, (blob) => {
n += blob.length
hasher.update(blob)
logger?.installing?.({ pkg, progress: total ? n / total : total })
@@ -78,7 +80,7 @@ export default async function install(pkg: Package, logger?: Logger): Promise {
+Deno.test("plumbing.link", async (runner) => {
const pkg: Package = {
project: "tea.xyz/brewkit",
- version: new SemVer("0.30.0")
+ version: new SemVer("0.30.0"),
}
await runner.step("link()", async () => {
@@ -16,7 +16,7 @@ Deno.test("plumbing.link", async runner => {
const installation = await install(pkg)
await link(installation)
- await link(installation) // test that calling twice serially works
+ await link(installation) // test that calling twice serially works
/// test symlinks work
assert(installation.path.parent().join("v*").isDirectory())
diff --git a/src/plumbing/link.ts b/src/plumbing/link.ts
index afdd3eb..55cc98a 100644
--- a/src/plumbing/link.ts
+++ b/src/plumbing/link.ts
@@ -1,5 +1,5 @@
import SemVer, * as semver from "../utils/semver.ts"
-import { Package, Installation } from "../types.ts"
+import { Installation, Package } from "../types.ts"
import useCellar from "../hooks/useCellar.ts"
import { panic } from "../utils/error.ts"
import fs from "node:fs/promises"
@@ -11,11 +11,11 @@ export default async function link(pkg: Package | Installation) {
const versions = (await useCellar()
.ls(installation.pkg.project))
- .map(({pkg: {version}, path}) => [version, path] as [SemVer, Path])
- .sort(([a],[b]) => a.compare(b))
+ .map(({ pkg: { version }, path }) => [version, path] as [SemVer, Path])
+ .sort(([a], [b]) => a.compare(b))
if (versions.length <= 0) {
- const err = new Error('no versions')
+ const err = new Error("no versions")
err.cause = pkg
throw err
}
@@ -24,7 +24,7 @@ export default async function link(pkg: Package | Installation) {
const newest = versions.slice(-1)[0]
const vMm = `${pkg.version.major}.${pkg.version.minor}`
const minorRange = new semver.Range(`^${vMm}`)
- const mostMinor = versions.filter(v => minorRange.satisfies(v[0])).at(-1) ?? panic()
+ const mostMinor = versions.filter((v) => minorRange.satisfies(v[0])).at(-1) ?? panic()
if (mostMinor[0].neq(pkg.version)) return
// ^^ if we’re not the most minor we definitely not the most major
@@ -32,7 +32,7 @@ export default async function link(pkg: Package | Installation) {
await makeSymlink(`v${vMm}`)
const majorRange = new semver.Range(`^${pkg.version.major.toString()}`)
- const mostMajor = versions.filter(v => majorRange.satisfies(v[0])).at(-1) ?? panic()
+ const mostMajor = versions.filter((v) => majorRange.satisfies(v[0])).at(-1) ?? panic()
if (mostMajor[0].neq(pkg.version)) return
// ^^ if we’re not the most major we definitely aren’t the newest
@@ -40,7 +40,7 @@ export default async function link(pkg: Package | Installation) {
await makeSymlink(`v${pkg.version.major}`)
if (pkg.version.eq(newest[0])) {
- await makeSymlink('v*')
+ await makeSymlink("v*")
}
async function makeSymlink(symname: string) {
@@ -54,22 +54,23 @@ export default async function link(pkg: Package | Installation) {
} catch (err) {
// we were deleted by another thing linking simultaneously
//FIXME our flock should surround the link step too
- if (err.code != 'ENOENT') throw err
+ if (err.code != "ENOENT") throw err
}
}
await Deno.symlink(
- installation.path.basename(), // makes it relative
+ installation.path.basename(), // makes it relative
shelf.join(symname).rm().string,
- {type: 'dir'})
- } catch (err) {
- if (err instanceof Deno.errors.AlreadyExists || err.code === 'EEXIST') {
- //FIXME race condition for installing the same pkg simultaneously
- // real fix is to lock around the entire download/untar/link process
- return
- } else {
- throw err
- }
+ { type: "dir" },
+ )
+ } catch (err) {
+ if (err instanceof Deno.errors.AlreadyExists || err.code === "EEXIST") {
+ //FIXME race condition for installing the same pkg simultaneously
+ // real fix is to lock around the entire download/untar/link process
+ return
+ } else {
+ throw err
}
+ }
}
}
diff --git a/src/plumbing/resolve.test.ts b/src/plumbing/resolve.test.ts
index 9fc500d..a86eb1b 100644
--- a/src/plumbing/resolve.test.ts
+++ b/src/plumbing/resolve.test.ts
@@ -1,5 +1,5 @@
// deno-lint-ignore-file require-await
-import { assert, assertEquals, fail, assertRejects } from "deno/assert/mod.ts"
+import { assert, assertEquals, assertRejects, fail } from "deno/assert/mod.ts"
import { Installation, Package, PackageRequirement } from "../types.ts"
import { useTestConfig } from "../hooks/useTestConfig.ts"
import useInventory from "../hooks/useInventory.ts"
@@ -11,8 +11,12 @@ import SemVer from "../utils/semver.ts"
import Path from "../utils/Path.ts"
Deno.test("resolve cellar.has", {
- permissions: {'read': true, 'env': ["TMPDIR", "TMP", "TEMP", "HOME"], 'write': [Deno.env.get("TMPDIR") || Deno.env.get("TMP") || Deno.env.get("TEMP") || "/tmp"] }
-}, async runner => {
+ permissions: {
+ "read": true,
+ "env": ["TMPDIR", "TMP", "TEMP", "HOME"],
+ "write": [Deno.env.get("TMPDIR") || Deno.env.get("TMP") || Deno.env.get("TEMP") || "/tmp"],
+ },
+}, async (runner) => {
const prefix = useTestConfig().prefix
const pkg = { project: "foo", version: new SemVer("1.0.0") }
@@ -20,9 +24,9 @@ Deno.test("resolve cellar.has", {
const has = async (pkg_: Package | PackageRequirement | Path) => {
if (pkg_ instanceof Path) fail()
if (pkg.project == pkg_.project) {
- if ('constraint' in pkg_ && !pkg_.constraint.satisfies(pkg.version)) return
- if ('version' in pkg_ && !pkg_.version.eq(pkg.version)) return
- const a: Installation = {pkg, path: prefix.join(pkg.project, `v${pkg.version}`) }
+ if ("constraint" in pkg_ && !pkg_.constraint.satisfies(pkg.version)) return
+ if ("version" in pkg_ && !pkg_.version.eq(pkg.version)) return
+ const a: Installation = { pkg, path: prefix.join(pkg.project, `v${pkg.version}`) }
return a
}
}
@@ -30,14 +34,15 @@ Deno.test("resolve cellar.has", {
await runner.step("happy path", async () => {
const stub1 = stub(_internals, "useInventory", () => ({
get: () => fail(),
- select: () => Promise.resolve(pkg.version)
+ select: () => Promise.resolve(pkg.version),
}))
const stub2 = stub(_internals, "useCellar", () => ({
- ...cellar, has
+ ...cellar,
+ has,
}))
try {
- const rv = await resolve([pkg])
+ const rv = await resolve([pkg])
assertEquals(rv.pkgs[0].project, pkg.project)
assertEquals(rv.installed[0].pkg.project, pkg.project)
} finally {
@@ -53,7 +58,7 @@ Deno.test("resolve cellar.has", {
}))
const stub2 = stub(_internals, "useCellar", () => ({
...cellar,
- has: () => Promise.resolve(undefined)
+ has: () => Promise.resolve(undefined),
}))
let errord = false
@@ -74,7 +79,8 @@ Deno.test("resolve cellar.has", {
select: () => Promise.resolve(pkg.version),
}))
const stub2 = stub(_internals, "useCellar", () => ({
- ...cellar, has
+ ...cellar,
+ has,
}))
try {
@@ -87,38 +93,51 @@ Deno.test("resolve cellar.has", {
}
})
- await runner.step("updates version if latest is not installed when update is set", async runner => {
- const stub1 = stub(_internals, "useInventory", () => ({
- get: () => fail(),
- select: () => Promise.resolve(new SemVer("1.0.1")),
- }))
- const stub2 = stub(_internals, "useCellar", () => ({
- ...cellar, has
- }))
-
- try {
- await runner.step("update: true", async () => {
- const rv = await resolve([{ project: pkg.project, constraint: new semver.Range("^1") }], { update: true })
- assertEquals(rv.pkgs[0].project, pkg.project)
- assertEquals(rv.pending[0].project, pkg.project)
- assertEquals(rv.pending[0].version, new SemVer("1.0.1"))
- })
-
- await runner.step("update: set", async () => {
- const update = new Set([pkg.project])
- const rv = await resolve([{ project: pkg.project, constraint: new semver.Range("^1") }], { update })
- assertEquals(rv.pkgs[0].project, pkg.project)
- assertEquals(rv.pending[0].project, pkg.project)
- assertEquals(rv.pending[0].version, new SemVer("1.0.1"))
- })
- } finally {
- stub1.restore()
- stub2.restore()
- }
- })
+ await runner.step(
+ "updates version if latest is not installed when update is set",
+ async (runner) => {
+ const stub1 = stub(_internals, "useInventory", () => ({
+ get: () => fail(),
+ select: () => Promise.resolve(new SemVer("1.0.1")),
+ }))
+ const stub2 = stub(_internals, "useCellar", () => ({
+ ...cellar,
+ has,
+ }))
+
+ try {
+ await runner.step("update: true", async () => {
+ const rv = await resolve([{ project: pkg.project, constraint: new semver.Range("^1") }], {
+ update: true,
+ })
+ assertEquals(rv.pkgs[0].project, pkg.project)
+ assertEquals(rv.pending[0].project, pkg.project)
+ assertEquals(rv.pending[0].version, new SemVer("1.0.1"))
+ })
+
+ await runner.step("update: set", async () => {
+ const update = new Set([pkg.project])
+ const rv = await resolve([{ project: pkg.project, constraint: new semver.Range("^1") }], {
+ update,
+ })
+ assertEquals(rv.pkgs[0].project, pkg.project)
+ assertEquals(rv.pending[0].project, pkg.project)
+ assertEquals(rv.pending[0].version, new SemVer("1.0.1"))
+ })
+ } finally {
+ stub1.restore()
+ stub2.restore()
+ }
+ },
+ )
})
-const permissions = { net: false, read: true, env: ["TMPDIR", "HOME", "TMP", "TEMP"], write: true /*FIXME*/ }
+const permissions = {
+ net: false,
+ read: true,
+ env: ["TMPDIR", "HOME", "TMP", "TEMP"],
+ write: true, /*FIXME*/
+}
// https://github.com/teaxyz/cli/issues/655
Deno.test("postgres@500 fails", { permissions }, async () => {
@@ -126,7 +145,7 @@ Deno.test("postgres@500 fails", { permissions }, async () => {
const pkg = {
project: "posqtgres.org",
- version: new SemVer("15.0.1")
+ version: new SemVer("15.0.1"),
}
const select = useInventory().select
@@ -136,7 +155,7 @@ Deno.test("postgres@500 fails", { permissions }, async () => {
}))
const pkgs = [
- { project: pkg.project, constraint: new semver.Range('@500') }
+ { project: pkg.project, constraint: new semver.Range("@500") },
]
try {
@@ -151,14 +170,14 @@ Deno.test("postgres@500 fails", { permissions }, async () => {
Deno.test("postgres@500 fails if installed", { permissions }, async () => {
const pkg = {
project: "posqtgres.org",
- version: new SemVer("15.0.1")
+ version: new SemVer("15.0.1"),
}
const prefix = useTestConfig().prefix
const cellar = useCellar()
const has = (b: Path | Package | PackageRequirement) => {
if ("constraint" in b && b.constraint.satisfies(pkg.version)) {
- const a: Installation = {pkg, path: prefix.join(pkg.project, `v${pkg.version}`) }
+ const a: Installation = { pkg, path: prefix.join(pkg.project, `v${pkg.version}`) }
return Promise.resolve(a)
} else {
return Promise.resolve(undefined)
@@ -172,11 +191,11 @@ Deno.test("postgres@500 fails if installed", { permissions }, async () => {
}))
const stub2 = stub(_internals, "useCellar", () => ({
...cellar,
- has
+ has,
}))
const pkgs = [
- { project: pkg.project, constraint: new semver.Range('@500') }
+ { project: pkg.project, constraint: new semver.Range("@500") },
]
try {
diff --git a/src/plumbing/resolve.ts b/src/plumbing/resolve.ts
index d3106b7..bc93071 100644
--- a/src/plumbing/resolve.ts
+++ b/src/plumbing/resolve.ts
@@ -1,4 +1,4 @@
-import { Package, PackageRequirement, Installation } from "../types.ts"
+import { Installation, Package, PackageRequirement } from "../types.ts"
import useInventory from "../hooks/useInventory.ts"
import { str as pkgstr } from "../utils/pkg.ts"
import useCellar from "../hooks/useCellar.ts"
@@ -32,7 +32,10 @@ export class ResolveError extends TeaError {
/// that resolve so if we are resolving `node>=12`, node 13 is installed, but
/// node 19 is the latest we return node 13. if `update` is true we return node
/// 19 and *you will need to install it*.
-export default async function resolve(reqs: (Package | PackageRequirement)[], {update}: {update: boolean | Set} = {update: false}): Promise {
+export default async function resolve(
+ reqs: (Package | PackageRequirement)[],
+ { update }: { update: boolean | Set } = { update: false },
+): Promise {
const inventory = _internals.useInventory()
const cellar = _internals.useCellar()
const rv: Resolution = { pkgs: [], installed: [], pending: [] }
@@ -47,7 +50,7 @@ export default async function resolve(reqs: (Package | PackageRequirement)[], {u
rv.installed.push(installation)
rv.pkgs.push(installation.pkg)
} else {
- const promise = inventory.select(req).then(async version => {
+ const promise = inventory.select(req).then(async (version) => {
if (!version) {
throw new ResolveError(req)
}
@@ -76,5 +79,5 @@ export default async function resolve(reqs: (Package | PackageRequirement)[], {u
export const _internals = {
useInventory,
- useCellar
+ useCellar,
}
diff --git a/src/plumbing/which.test.ts b/src/plumbing/which.test.ts
index 8b71c63..9594472 100644
--- a/src/plumbing/which.test.ts
+++ b/src/plumbing/which.test.ts
@@ -3,22 +3,22 @@ import { isArray } from "is-what"
import which from "./which.ts"
Deno.test("which('ls')", async () => {
- const foo = await which('ls')
+ const foo = await which("ls")
assert(!isArray(foo))
assert(foo)
})
Deno.test("which('kill-port')", async () => {
- const foo = await which('kill-port')
+ const foo = await which("kill-port")
assert(!isArray(foo))
assert(foo)
- const bar = await which('kill-port', { providers: false })
+ const bar = await which("kill-port", { providers: false })
assertEquals(bar, undefined)
})
Deno.test("which('nvim')", async () => {
- const foo = await which('kill-port', { all: true })
+ const foo = await which("kill-port", { all: true })
assert(isArray(foo))
assert(foo.length)
})
diff --git a/src/plumbing/which.ts b/src/plumbing/which.ts
index c69d47d..1468128 100644
--- a/src/plumbing/which.ts
+++ b/src/plumbing/which.ts
@@ -6,12 +6,19 @@ export type WhichResult = PackageRequirement & {
shebang: string[]
}
-
-export default async function which(arg0: string, opts?: { providers?: boolean }): Promise;
-export default async function which(arg0: string, opts: { providers?: boolean, all: false }): Promise;
-export default async function which(arg0: string, opts: { providers?: boolean, all: true }): Promise;
-export default async function which(arg0: string, opts_?: { providers?: boolean, all?: boolean }) {
-
+export default async function which(
+ arg0: string,
+ opts?: { providers?: boolean },
+): Promise
+export default async function which(
+ arg0: string,
+ opts: { providers?: boolean; all: false },
+): Promise
+export default async function which(
+ arg0: string,
+ opts: { providers?: boolean; all: true },
+): Promise
+export default async function which(arg0: string, opts_?: { providers?: boolean; all?: boolean }) {
const opts = { providers: opts_?.providers ?? true, all: opts_?.all ?? false }
const rv: WhichResult[] = []
@@ -29,7 +36,7 @@ export default async function which(arg0: string, opts_?: { providers?: boolean,
}
}
-async function *_which(arg0: string, opts: { providers: boolean }): AsyncGenerator {
+async function* _which(arg0: string, opts: { providers: boolean }): AsyncGenerator {
arg0 = arg0.trim()
/// sanitize and reject anything with path components
if (!arg0 || arg0.includes("/")) return
@@ -43,16 +50,16 @@ async function *_which(arg0: string, opts: { providers: boolean }): AsyncGenerat
for (const f of found) yield f
found = []
}
- const p = pantry.project(entry).provides().then(providers => {
+ const p = pantry.project(entry).provides().then((providers) => {
for (const provider of providers) {
if (provider == arg0) {
const constraint = new semver.Range("*")
- found.push({...entry, constraint, shebang: [provider] })
+ found.push({ ...entry, constraint, shebang: [provider] })
} else if (arg0.startsWith(provider)) {
// eg. `node^16` symlink
try {
const constraint = new semver.Range(arg0.substring(provider.length))
- found.push({...entry, constraint, shebang: [provider] })
+ found.push({ ...entry, constraint, shebang: [provider] })
} catch {
// not a valid semver range; fallthrough
}
@@ -64,13 +71,13 @@ async function *_which(arg0: string, opts: { providers: boolean }): AsyncGenerat
let rx = /({{\s*version\.(marketing|major)\s*}})/
let match = provider.match(rx)
if (!match?.index) continue
- const regx = match[2] == 'major' ? '\\d+' : '\\d+\\.\\d+'
+ const regx = match[2] == "major" ? "\\d+" : "\\d+\\.\\d+"
const foo = subst(match.index, match.index + match[1].length, provider, `(${regx})`)
rx = new RegExp(`^${foo}$`)
match = arg0.match(rx)
if (match) {
const constraint = new semver.Range(`~${match[1]}`)
- found.push({...entry, constraint, shebang: [arg0] })
+ found.push({ ...entry, constraint, shebang: [arg0] })
}
}
}
@@ -79,14 +86,16 @@ async function *_which(arg0: string, opts: { providers: boolean }): AsyncGenerat
promises.push(p)
if (opts.providers) {
- const pp = pantry.project(entry).provider().then(f => {
+ const pp = pantry.project(entry).provider().then((f) => {
if (!f) return
const rv = f(arg0)
- if (rv) found.push({
- ...entry,
- constraint: new semver.Range('*'),
- shebang: [...rv, arg0]
- })
+ if (rv) {
+ found.push({
+ ...entry,
+ constraint: new semver.Range("*"),
+ shebang: [...rv, arg0],
+ })
+ }
})
promises.push(pp)
}
@@ -101,6 +110,6 @@ async function *_which(arg0: string, opts: { providers: boolean }): AsyncGenerat
}
}
-const subst = function(start: number, end: number, input: string, what: string) {
+const subst = function (start: number, end: number, input: string, what: string) {
return input.substring(0, start) + what + input.substring(end)
}
diff --git a/src/porcelain/install.test.ts b/src/porcelain/install.test.ts
index b07a0eb..237297a 100644
--- a/src/porcelain/install.test.ts
+++ b/src/porcelain/install.test.ts
@@ -8,7 +8,7 @@ import type { Package } from "../types.ts"
Deno.test("porcelain.install.1", async () => {
useTestConfig()
const installations = await install("tea.xyz/brewkit")
- const projects = new Set(installations.map(x => x.pkg.project))
+ const projects = new Set(installations.map((x) => x.pkg.project))
assert(projects.has("tea.xyz/brewkit"))
})
@@ -20,14 +20,14 @@ Deno.test("porcelain.install.2", async () => {
Deno.test("porcelain.install.3", async () => {
useTestConfig()
const installations = await install(["tea.xyz/brewkit@0.31", "zlib.net"])
- const projects = new Set(installations.map(x => x.pkg.project))
+ const projects = new Set(installations.map((x) => x.pkg.project))
assert(projects.has("tea.xyz/brewkit"))
assert(projects.has("zlib.net"))
})
Deno.test("porcelain.install.4", async () => {
useTestConfig()
- await install([{ project: 'tea.xyz/brewkit', constraint: new semver.Range("^0.31") }])
+ await install([{ project: "tea.xyz/brewkit", constraint: new semver.Range("^0.31") }])
})
Deno.test("porcelain.install.resolved", async () => {
@@ -36,11 +36,11 @@ Deno.test("porcelain.install.resolved", async () => {
let resolution: Resolution = { pkgs: [] as Package[] } as Resolution
const logger = {
...ConsoleLogger(),
- resolved: (r: Resolution) => resolution = r
+ resolved: (r: Resolution) => resolution = r,
}
await install("tea.xyz/brewkit^0.32", logger)
const resolvedProjects = resolution.pkgs.map((p: Package) => p.project)
- assertArrayIncludes(resolvedProjects, [ "deno.land", "gnu.org/bash", "tea.xyz", "tea.xyz/brewkit"])
+ assertArrayIncludes(resolvedProjects, ["deno.land", "gnu.org/bash", "tea.xyz", "tea.xyz/brewkit"])
})
diff --git a/src/porcelain/install.ts b/src/porcelain/install.ts
index bf97a75..2aa4910 100644
--- a/src/porcelain/install.ts
+++ b/src/porcelain/install.ts
@@ -1,4 +1,7 @@
-import install, { Logger as BaseLogger, ConsoleLogger as BaseConsoleLogger } from "../plumbing/install.ts"
+import install, {
+ ConsoleLogger as BaseConsoleLogger,
+ Logger as BaseLogger,
+} from "../plumbing/install.ts"
import { Installation, PackageSpecification } from "../types.ts"
import resolve, { Resolution } from "../plumbing/resolve.ts"
import usePantry from "../hooks/usePantry.ts"
@@ -22,15 +25,19 @@ export function ConsoleLogger(prefix?: any): Logger {
prefix = prefix ? `${prefix}: ` : ""
return {
...BaseConsoleLogger(prefix),
- progress: function() { console.error(`${prefix}progress`, ...arguments) },
+ progress: function () {
+ console.error(`${prefix}progress`, ...arguments)
+ },
}
}
/// eg. install("python.org~3.10")
-export default async function(pkgs: PackageSpecification[] | string[] | string, logger?: Logger): Promise {
-
+export default async function (
+ pkgs: PackageSpecification[] | string[] | string,
+ logger?: Logger,
+): Promise {
if (isString(pkgs)) pkgs = pkgs.split(/\s+/)
- pkgs = pkgs.map(pkg => isString(pkg) ? parse(pkg) : pkg)
+ pkgs = pkgs.map((pkg) => isString(pkg) ? parse(pkg) : pkg)
const pantry = usePantry()
@@ -47,8 +54,10 @@ export default async function(pkgs: PackageSpecification[] | string[] | string,
const { pending, installed } = resolution
logger = WrapperLogger(pending, logger)
const installers = pending
- .map(pkg => install(pkg, logger)
- .then(i => link(i).then(() => i)))
+ .map((pkg) =>
+ install(pkg, logger)
+ .then((i) => link(i).then(() => i))
+ )
installed.push(...await Promise.all(installers))
@@ -58,13 +67,13 @@ export default async function(pkgs: PackageSpecification[] | string[] | string,
function WrapperLogger(pending: PackageSpecification[], logger?: Logger): Logger | undefined {
if (!logger?.progress) return logger
- const projects = pending.map(pkg => pkg.project)
+ const projects = pending.map((pkg) => pkg.project)
const totals: Record = {}
const progresses: Record = {}
return {
...logger,
- downloading: args => {
- const { pkg: {project}, total } = args
+ downloading: (args) => {
+ const { pkg: { project }, total } = args
if (total) {
totals[project] = total
updateProgress()
@@ -73,8 +82,8 @@ function WrapperLogger(pending: PackageSpecification[], logger?: Logger): Logger
logger.downloading(args)
}
},
- installing: args => {
- const { pkg: {project}, progress } = args
+ installing: (args) => {
+ const { pkg: { project }, progress } = args
if (progress) {
progresses[project] = progress
updateProgress()
@@ -82,7 +91,7 @@ function WrapperLogger(pending: PackageSpecification[], logger?: Logger): Logger
if (logger?.installing) {
logger.installing(args)
}
- }
+ },
}
function updateProgress() {
diff --git a/src/porcelain/run.test.ts b/src/porcelain/run.test.ts
index 7c634e6..904e5fe 100644
--- a/src/porcelain/run.test.ts
+++ b/src/porcelain/run.test.ts
@@ -2,12 +2,15 @@ import { assertEquals, assertMatch, assertRejects } from "deno/testing/asserts.t
import { useTestConfig } from "../hooks/useTestConfig.ts"
import run from "./run.ts"
-Deno.test("porcelain.run", async runner => {
+Deno.test("porcelain.run", async (runner) => {
await runner.step("std", async () => {
useTestConfig()
- const { stdout, stderr, status } = await run(`python -c 'print(1)'`) as unknown as
- { stdout: string, stderr: string, status: number }
- // ^^ type system hack to ensure we don’t actually capture the stdout/stderr
+ const { stdout, stderr, status } = await run(`python -c 'print(1)'`) as unknown as {
+ stdout: string
+ stderr: string
+ status: number
+ }
+ // ^^ type system hack to ensure we don’t actually capture the stdout/stderr
assertEquals(stdout, "")
assertEquals(stderr, "")
assertEquals(status, 0)
@@ -16,28 +19,34 @@ Deno.test("porcelain.run", async runner => {
// we had a scenario where no args would truncate the cmd-name
await runner.step("no args works", async () => {
useTestConfig()
- const { stdout, stderr, status } = await run(`ls`) as unknown as { stdout: string, stderr: string, status: number }
+ const { stdout, stderr, status } = await run(`ls`) as unknown as {
+ stdout: string
+ stderr: string
+ status: number
+ }
assertEquals(stdout, "")
assertEquals(stderr, "")
assertEquals(status, 0)
})
- await runner.step("node^16", async runner => {
+ await runner.step("node^16", async (runner) => {
useTestConfig()
await runner.step("string", async () => {
- const { stdout } = await run('node^16 --version', { stdout: true })
+ const { stdout } = await run("node^16 --version", { stdout: true })
assertMatch(stdout, /^v16\./)
})
await runner.step("array", async () => {
- const { stdout } = await run(['node^16', '--version'], { stdout: true })
+ const { stdout } = await run(["node^16", "--version"], { stdout: true })
assertMatch(stdout, /^v16\./)
})
})
await runner.step("env", async () => {
useTestConfig()
- await run(['node', '-e', 'if (process.env.FOO !== "FOO") throw new Error()'], { env: { FOO: "FOO" }})
+ await run(["node", "-e", 'if (process.env.FOO !== "FOO") throw new Error()'], {
+ env: { FOO: "FOO" },
+ })
})
await runner.step("status", async () => {
@@ -53,19 +62,29 @@ Deno.test("porcelain.run", async runner => {
await runner.step("stdout", async () => {
useTestConfig()
- const { stdout } = await run(['python', '-c', "import os; print(os.getenv('FOO'))"], { stdout: true, env: { FOO: "FOO" } })
+ const { stdout } = await run(["python", "-c", "import os; print(os.getenv('FOO'))"], {
+ stdout: true,
+ env: { FOO: "FOO" },
+ })
assertEquals(stdout, "FOO\n")
})
await runner.step("stderr", async () => {
useTestConfig()
- const { stderr } = await run(['node', '-e', "console.error(process.env.FOO)"], { stderr: true, env: { FOO: "BAR" } })
+ const { stderr } = await run(["node", "-e", "console.error(process.env.FOO)"], {
+ stderr: true,
+ env: { FOO: "BAR" },
+ })
assertEquals(stderr, "BAR\n")
})
await runner.step("all", async () => {
useTestConfig()
- const { stderr, stdout, status } = await run(['node', '-e', "console.error(1); console.log(2); process.exit(3)"], { stderr: true, stdout: true, status: true })
+ const { stderr, stdout, status } = await run([
+ "node",
+ "-e",
+ "console.error(1); console.log(2); process.exit(3)",
+ ], { stderr: true, stdout: true, status: true })
assertEquals(stderr, "1\n")
assertEquals(stdout, "2\n")
assertEquals(status, 3)
diff --git a/src/porcelain/run.ts b/src/porcelain/run.ts
index 2f239a9..d15e933 100644
--- a/src/porcelain/run.ts
+++ b/src/porcelain/run.ts
@@ -1,6 +1,6 @@
import install, { Logger } from "../plumbing/install.ts"
-import useShellEnv from '../hooks/useShellEnv.ts'
-import usePantry from '../hooks/usePantry.ts'
+import useShellEnv from "../hooks/useShellEnv.ts"
+import usePantry from "../hooks/usePantry.ts"
import hydrate from "../plumbing/hydrate.ts"
import resolve from "../plumbing/resolve.ts"
import { TeaError } from "../utils/error.ts"
@@ -27,20 +27,45 @@ type Cmd = string | (string | Path)[]
/// if you pass a single string we call that string via /bin/sh
/// if you don’t want that pass an array of args
-export default async function run(cmd: Cmd, opts?: OptsEx): Promise;
-export default async function run(cmd: Cmd, opts: {stdout: true} & OptsEx): Promise<{ stdout: string }>;
-export default async function run(cmd: Cmd, opts: {stderr: true} & OptsEx): Promise<{ stderr: string }>;
-export default async function run(cmd: Cmd, opts: {status: true} & OptsEx): Promise<{ status: number }>;
-export default async function run(cmd: Cmd, opts: {stdout: true, stderr: true} & OptsEx): Promise<{ stdout: string, stderr: string }>;
-export default async function run(cmd: Cmd, opts: {stdout: true, status: true} & OptsEx): Promise<{ stdout: string, status: number }>;
-export default async function run(cmd: Cmd, opts: {stderr: true, status: true} & OptsEx): Promise<{ stderr: string, status: number }>;
-export default async function run(cmd: Cmd, opts: {stdout: true, stderr: true, status: true } & OptsEx): Promise<{ stdout: string, stderr: string, status: number }>;
-export default async function run(cmd: Cmd, opts?: Options): Promise {
-
+export default async function run(cmd: Cmd, opts?: OptsEx): Promise
+export default async function run(
+ cmd: Cmd,
+ opts: { stdout: true } & OptsEx,
+): Promise<{ stdout: string }>
+export default async function run(
+ cmd: Cmd,
+ opts: { stderr: true } & OptsEx,
+): Promise<{ stderr: string }>
+export default async function run(
+ cmd: Cmd,
+ opts: { status: true } & OptsEx,
+): Promise<{ status: number }>
+export default async function run(
+ cmd: Cmd,
+ opts: { stdout: true; stderr: true } & OptsEx,
+): Promise<{ stdout: string; stderr: string }>
+export default async function run(
+ cmd: Cmd,
+ opts: { stdout: true; status: true } & OptsEx,
+): Promise<{ stdout: string; status: number }>
+export default async function run(
+ cmd: Cmd,
+ opts: { stderr: true; status: true } & OptsEx,
+): Promise<{ stderr: string; status: number }>
+export default async function run(
+ cmd: Cmd,
+ opts: { stdout: true; stderr: true; status: true } & OptsEx,
+): Promise<{ stdout: string; stderr: string; status: number }>
+export default async function run(
+ cmd: Cmd,
+ opts?: Options,
+): Promise<
+ void | { stdout?: string | undefined; stderr?: string | undefined; status?: number | undefined }
+> {
const { usesh, arg0: whom } = (() => {
if (!isArray(cmd)) {
const s = cmd.trim()
- const i = s.indexOf(' ')
+ const i = s.indexOf(" ")
if (i == -1) {
cmd = []
return { usesh: false, arg0: s }
@@ -50,37 +75,37 @@ export default async function run(cmd: Cmd, opts?: Options): Promise x.toString())]
+ ? ["-c", `${shebang.join(" ")} ${cmd}`]
+ : [...shebang, ...(cmd as (string | Path)[]).map((x) => x.toString())]
return new Promise((resolve, reject) => {
const proc = spawn(arg0, args, {
env,
stdio: [
"pipe",
- opts?.stdout ? 'pipe' : 'inherit',
- opts?.stderr ? 'pipe' : 'inherit'
- ]
+ opts?.stdout ? "pipe" : "inherit",
+ opts?.stderr ? "pipe" : "inherit",
+ ],
})
- let stdout = '', stderr = ''
- proc.stdout?.on('data', data => stdout += data)
- proc.stderr?.on('data', data => stderr += data)
- proc.on('close', status => {
+ let stdout = "", stderr = ""
+ proc.stdout?.on("data", (data) => stdout += data)
+ proc.stderr?.on("data", (data) => stderr += data)
+ proc.on("close", (status) => {
if (status && !opts?.status) {
- const err = new RunError('EIO', `${cmd} exited with: ${status}`)
+ const err = new RunError("EIO", `${cmd} exited with: ${status}`)
err.cause = status
reject(err)
} else {
@@ -91,7 +116,11 @@ export default async function run(cmd: Cmd, opts?: Options): Promise, logger: Logger | undefined) {
+async function setup(
+ cmd: string,
+ env: Record,
+ logger: Logger | undefined,
+) {
const pantry = usePantry()
const sh = useShellEnv()
@@ -100,7 +129,7 @@ async function setup(cmd: string, env: Record, logge
}
const wut = await which(cmd)
- if (!wut) throw new RunError('ENOENT', `No project in pantry provides ${cmd}`)
+ if (!wut) throw new RunError("ENOENT", `No project in pantry provides ${cmd}`)
const { pkgs } = await hydrate(wut)
const { pending, installed } = await resolve(pkgs)
@@ -125,8 +154,7 @@ async function setup(cmd: string, env: Record, logge
return { env: sh.flatten(pkgenv), shebang: wut.shebang }
}
-
-type RunErrorCode = 'ENOENT' | 'EUSAGE' | 'EIO'
+type RunErrorCode = "ENOENT" | "EUSAGE" | "EIO"
export class RunError extends TeaError {
code: RunErrorCode
diff --git a/src/types.ts b/src/types.ts
index 4adc232..1757893 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,4 +1,4 @@
-import host, { SupportedPlatform, SupportedArchitecture } from "./utils/host.ts"
+import host, { SupportedArchitecture, SupportedPlatform } from "./utils/host.ts"
import SemVer, { Range } from "./utils/semver.ts"
import Path from "./utils/Path.ts"
@@ -21,19 +21,19 @@ export interface Installation {
/// remotely available package content (bottles or source tarball)
export type Stowage = {
- type: 'src'
+ type: "src"
pkg: Package
extname: string
} | {
- type: 'bottle'
+ type: "bottle"
pkg: Package
- compression: 'xz' | 'gz'
- host?: { platform: SupportedPlatform, arch: SupportedArchitecture }
+ compression: "xz" | "gz"
+ host?: { platform: SupportedPlatform; arch: SupportedArchitecture }
}
/// once downloaded, `Stowage` becomes `Stowed`
export type Stowed = Stowage & { path: Path }
-export function StowageNativeBottle(opts: { pkg: Package, compression: 'xz' | 'gz' }): Stowage {
- return { ...opts, host: host(), type: 'bottle' }
+export function StowageNativeBottle(opts: { pkg: Package; compression: "xz" | "gz" }): Stowage {
+ return { ...opts, host: host(), type: "bottle" }
}
diff --git a/src/utils/Path.test.ts b/src/utils/Path.test.ts
index 673ec57..910aa2a 100644
--- a/src/utils/Path.test.ts
+++ b/src/utils/Path.test.ts
@@ -1,17 +1,17 @@
import { assert, assertEquals, assertFalse, assertThrows } from "deno/testing/asserts.ts"
import Path from "./Path.ts"
-Deno.test("test Path", async test => {
+Deno.test("test Path", async (test) => {
await test.step("creating files", () => {
assertEquals(new Path("/a/b/c").components(), ["", "a", "b", "c"])
assertEquals(new Path("/a/b/c").split(), [new Path("/a/b"), "c"])
- const tmp = Path.mktemp({prefix: "tea-"})
+ const tmp = Path.mktemp({ prefix: "tea-" })
assert(tmp.isEmpty())
const child = tmp.join("a/b/c")
assertFalse(child.parent().isDirectory())
- child.parent().mkdir('p')
+ child.parent().mkdir("p")
assert(child.parent().isDirectory())
assertThrows(() => child.readlink()) // not found
@@ -23,15 +23,14 @@ Deno.test("test Path", async test => {
assertFalse(tmp.isEmpty())
assertEquals(child.readlink(), child) // not a link
-
assertEquals(new Path("/").string, "/")
})
await test.step("write and read", async () => {
- const tmp = Path.mktemp({prefix: "tea-"})
+ const tmp = Path.mktemp({ prefix: "tea-" })
const data = tmp.join("test.dat")
- data.write({text: "hello\nworld"})
+ data.write({ text: "hello\nworld" })
const lines = await asyncIterToArray(data.readLines())
assertEquals(lines, ["hello", "world"])
@@ -44,7 +43,7 @@ Deno.test("test Path", async test => {
})
await test.step("test walk", async () => {
- const tmp = Path.mktemp({prefix: "tea-"})
+ const tmp = Path.mktemp({ prefix: "tea-" })
const a = tmp.join("a").mkdir()
a.join("a1").touch()
@@ -63,28 +62,28 @@ Deno.test("test Path", async test => {
const walked = (await asyncIterToArray(tmp.walk()))
.map(([path, entry]) => {
- return {name: path.basename(), isDir: entry.isDirectory}
+ return { name: path.basename(), isDir: entry.isDirectory }
})
.sort((a, b) => a.name.localeCompare(b.name))
assertEquals(walked, [
- { name: "a", isDir: true},
- { name: "a1", isDir: false},
- { name: "a2", isDir: false},
- { name: "b", isDir: true},
- { name: "b1", isDir: false},
- { name: "b2", isDir: false},
- { name: "c", isDir: true},
- { name: "c1", isDir: false},
- { name: "c2", isDir: false},
+ { name: "a", isDir: true },
+ { name: "a1", isDir: false },
+ { name: "a2", isDir: false },
+ { name: "b", isDir: true },
+ { name: "b1", isDir: false },
+ { name: "b2", isDir: false },
+ { name: "c", isDir: true },
+ { name: "c1", isDir: false },
+ { name: "c2", isDir: false },
])
})
await test.step("test symlink created", () => {
- const tmp = Path.mktemp({prefix: "tea-"}).join("foo").mkdir()
+ const tmp = Path.mktemp({ prefix: "tea-" }).join("foo").mkdir()
const a = tmp.join("a").touch()
const b = tmp.join("b")
- b.ln('s', { target: a })
+ b.ln("s", { target: a })
assertEquals(b.readlink(), a)
assert(b.isSymlink())
})
@@ -114,7 +113,7 @@ Deno.test("Path.join()", () => {
})
Deno.test("Path.isExecutableFile()", () => {
- const tmp = Path.mktemp({prefix: "tea-"}).mkdir()
+ const tmp = Path.mktemp({ prefix: "tea-" }).mkdir()
const executable = tmp.join("executable").touch()
executable.chmod(0o755)
const notExecutable = tmp.join("not-executable").touch()
@@ -129,7 +128,7 @@ Deno.test("Path.extname()", () => {
})
Deno.test("Path.mv()", () => {
- const tmp = Path.mktemp({prefix: "tea-"})
+ const tmp = Path.mktemp({ prefix: "tea-" })
const a = tmp.join("a").touch()
const b = tmp.join("b")
@@ -150,7 +149,7 @@ Deno.test("Path.mv()", () => {
})
Deno.test("Path.cp()", () => {
- const tmp = Path.mktemp({prefix: "tea-"}).mkdir()
+ const tmp = Path.mktemp({ prefix: "tea-" }).mkdir()
const a = tmp.join("a").touch()
const b = tmp.join("b").mkdir()
@@ -167,9 +166,9 @@ Deno.test("Path.relative()", () => {
})
Deno.test("Path.realpath()", () => {
- const tmp = Path.mktemp({prefix: "tea-"}).mkdir()
+ const tmp = Path.mktemp({ prefix: "tea-" }).mkdir()
const a = tmp.join("a").touch()
- const b = tmp.join("b").ln('s', { target: a })
+ const b = tmp.join("b").ln("s", { target: a })
assertEquals(b.realpath(), a.realpath())
})
@@ -190,21 +189,21 @@ Deno.test("Path.chuzzle()", () => {
})
Deno.test("Path.ls()", async () => {
- const tmp = Path.mktemp({prefix: "tea-"}).mkdir()
+ const tmp = Path.mktemp({ prefix: "tea-" }).mkdir()
tmp.join("a").touch()
tmp.join("b").touch()
tmp.join("c").mkdir()
- const entries = (await asyncIterToArray(tmp.ls())).map(([,{name}]) => name)
+ const entries = (await asyncIterToArray(tmp.ls())).map(([, { name }]) => name)
assertEquals(entries.sort(), ["a", "b", "c"])
})
-async function asyncIterToArray (iter: AsyncIterable){
- const result = [];
- for await(const i of iter) {
- result.push(i);
+async function asyncIterToArray(iter: AsyncIterable) {
+ const result = []
+ for await (const i of iter) {
+ result.push(i)
}
- return result;
+ return result
}
Deno.test("ctor throws", () => {
@@ -212,4 +211,4 @@ Deno.test("ctor throws", () => {
assertThrows(() => new Path(" "))
assertThrows(() => new Path(" \n "))
assertThrows(() => new Path(" / "))
-})
\ No newline at end of file
+})
diff --git a/src/utils/Path.ts b/src/utils/Path.ts
index a52e8c8..5d38473 100644
--- a/src/utils/Path.ts
+++ b/src/utils/Path.ts
@@ -28,13 +28,14 @@ export default class Path {
static home(): Path {
return new Path(
(() => {
- switch (Deno.build.os) {
- case "windows":
- return Deno.env.get("USERPROFILE")!
- default:
- return Deno.env.get("HOME")!
- }
- })())
+ switch (Deno.build.os) {
+ case "windows":
+ return Deno.env.get("USERPROFILE")!
+ default:
+ return Deno.env.get("HOME")!
+ }
+ })(),
+ )
}
/// normalizes the path
@@ -42,13 +43,15 @@ export default class Path {
constructor(input: string | Path) {
if (input instanceof Path) {
this.string = input.string
- } else if (!input || input[0] != '/') {
+ } else if (!input || input[0] != "/") {
throw new Error(`invalid absolute path: ${input}`)
} else {
this.string = sys.normalize(input)
// ^^ seemingly doesn’t normalize trailing slashes away
- if (this.string != "/") while (this.string.endsWith("/")) {
- this.string = this.string.slice(0, -1)
+ if (this.string != "/") {
+ while (this.string.endsWith("/")) {
+ this.string = this.string.slice(0, -1)
+ }
}
}
}
@@ -78,10 +81,10 @@ export default class Path {
} catch (err) {
const code = err.code
switch (code) {
- case 'EINVAL':
- return this // is file
- case 'ENOENT':
- throw err // there is no symlink at this path
+ case "EINVAL":
+ return this // is file
+ case "ENOENT":
+ throw err // there is no symlink at this path
}
throw err
}
@@ -106,8 +109,8 @@ export default class Path {
/// rationale: usually if you are trying to join an absolute path it is a bug in your code
/// TODO should warn tho
join(...components: string[]): Path {
- const joined = components.filter(x => x).join("/")
- if (joined[0] == '/') {
+ const joined = components.filter((x) => x).join("/")
+ if (joined[0] == "/") {
return new Path(joined)
} else if (joined) {
return new Path(`${this.string}/${joined}`)
@@ -205,16 +208,16 @@ export default class Path {
}
components(): string[] {
- return this.string.split('/')
+ return this.string.split("/")
}
- static mktemp(opts?: { prefix?: string, dir?: Path }): Path {
- let {prefix, dir} = opts ?? {}
+ static mktemp(opts?: { prefix?: string; dir?: Path }): Path {
+ let { prefix, dir } = opts ?? {}
dir ??= new Path(os.tmpdir())
prefix ??= ""
- if (!prefix.startsWith('/')) prefix = `/${prefix}`
+ if (!prefix.startsWith("/")) prefix = `/${prefix}`
// not using deno.makeTempDirSync because it's bugg’d and the node shim doesn’t handler `dir`
- const rv = mkdtempSync(`${dir.mkdir('p')}${prefix}`)
+ const rv = mkdtempSync(`${dir.mkdir("p")}${prefix}`)
return new Path(rv)
}
@@ -254,7 +257,7 @@ export default class Path {
end result preexists, checking for this condition is too expensive a
trade-off.
*/
- mv({force, ...opts}: {to: Path, force?: boolean} | {into: Path, force?: boolean}): Path {
+ mv({ force, ...opts }: { to: Path; force?: boolean } | { into: Path; force?: boolean }): Path {
if ("to" in opts) {
fs.moveSync(this.string, opts.to.string, { overwrite: force })
return opts.to
@@ -267,13 +270,13 @@ export default class Path {
//FIXME operates in ”force” mode
//TODO needs a recursive option
- cp({into}: {into: Path}): Path {
+ cp({ into }: { into: Path }): Path {
const dst = into.join(this.basename())
Deno.copyFileSync(this.string, dst.string)
return dst
}
- rm({recursive} = {recursive: false}) {
+ rm({ recursive } = { recursive: false }) {
if (this.exists()) {
try {
Deno.removeSync(this.string, { recursive })
@@ -285,12 +288,12 @@ export default class Path {
}
}
}
- return this // may seem weird but I've had cases where I wanted to chain
+ return this // may seem weird but I've had cases where I wanted to chain
}
- mkdir(opts?: 'p'): Path {
+ mkdir(opts?: "p"): Path {
if (!this.isDirectory()) {
- Deno.mkdirSync(this.string, { recursive: opts == 'p' })
+ Deno.mkdirSync(this.string, { recursive: opts == "p" })
}
return this
}
@@ -313,7 +316,7 @@ export default class Path {
/// `this` is the symlink that is created pointing at `target`
/// in Path.ts we always create `this`, our consistency helps with the notoriously difficuly argument order of `ln -s`
/// note symlink is full and absolute path
- ln(_: 's', {target}: { target: Path }): Path {
+ ln(_: "s", { target }: { target: Path }): Path {
Deno.symlinkSync(target.string, this.string)
return this
}
@@ -325,10 +328,10 @@ export default class Path {
async *readLines(): AsyncIterableIterator {
const fd = Deno.openSync(this.string)
try {
- for await (const line of readLines(fd))
+ for await (const line of readLines(fd)) {
yield line
}
- finally {
+ } finally {
fd.close()
}
}
@@ -347,10 +350,14 @@ export default class Path {
}
readJSON(): Promise {
- return this.read().then(x => JSON.parse(x))
+ return this.read().then((x) => JSON.parse(x))
}
- write({ force, ...content }: ({text: string} | {json: PlainObject, space?: number}) & {force?: boolean}): Path {
+ write(
+ { force, ...content }: ({ text: string } | { json: PlainObject; space?: number }) & {
+ force?: boolean
+ },
+ ): Path {
if (this.exists()) {
if (!force) throw new Error(`file-exists:${this}`)
this.rm()
@@ -366,7 +373,7 @@ export default class Path {
touch(): Path {
//FIXME work more as expected
- return this.write({force: true, text: ""})
+ return this.write({ force: true, text: "" })
}
chmod(mode: number): Path {
@@ -379,8 +386,8 @@ export default class Path {
}
relative({ to: base }: { to: Path }): string {
- const pathComps = ['/'].concat(this.string.split("/").filter(x=>x))
- const baseComps = ['/'].concat(base.string.split("/").filter(x=>x))
+ const pathComps = ["/"].concat(this.string.split("/").filter((x) => x))
+ const baseComps = ["/"].concat(base.string.split("/").filter((x) => x))
if (this.string.startsWith(base.string)) {
return pathComps.slice(baseComps.length).join("/")
@@ -393,7 +400,7 @@ export default class Path {
newBaseComps.shift()
}
- const relComps = Array.from({ length: newBaseComps.length } , () => "..")
+ const relComps = Array.from({ length: newBaseComps.length }, () => "..")
relComps.push(...newPathComps)
return relComps.join("/")
}
@@ -404,13 +411,15 @@ export default class Path {
}
prettyString(): string {
- return this.string.replace(new RegExp(`^${Path.home()}`), '~')
+ return this.string.replace(new RegExp(`^${Path.home()}`), "~")
}
// if we’re inside the CWD we print that
prettyLocalString(): string {
const cwd = Path.cwd()
- return this.string.startsWith(cwd.string) ? `./${this.relative({ to: cwd })}` : this.prettyString()
+ return this.string.startsWith(cwd.string)
+ ? `./${this.relative({ to: cwd })}`
+ : this.prettyString()
}
[Symbol.for("Deno.customInspect")]() {
diff --git a/src/utils/error.test.ts b/src/utils/error.test.ts
index cf71e9a..c497bc0 100644
--- a/src/utils/error.test.ts
+++ b/src/utils/error.test.ts
@@ -1,7 +1,7 @@
import { assertRejects, assertThrows } from "deno/testing/asserts.ts"
import { panic } from "../utils/error.ts"
-Deno.test("errors", async test => {
+Deno.test("errors", async (test) => {
await test.step("panic", () => {
assertThrows(() => panic("test msg"), "test msg")
})
@@ -12,11 +12,8 @@ Deno.test("errors", async test => {
})
})
-class FooError extends Error
-{}
+class FooError extends Error {}
-class BarError extends Error
-{}
+class BarError extends Error {}
-class BazError extends BarError
-{}
+class BazError extends BarError {}
diff --git a/src/utils/error.ts b/src/utils/error.ts
index 15b5bc4..0e5a8a0 100644
--- a/src/utils/error.ts
+++ b/src/utils/error.ts
@@ -10,10 +10,10 @@ declare global {
}
}
-Promise.prototype.swallow = function(errorClass?: new (...args: any) => any) {
+Promise.prototype.swallow = function (errorClass?: new (...args: any) => any) {
return this.catch((err: unknown) => {
if (errorClass && !(err instanceof errorClass)) {
- throw err;
+ throw err
}
})
}
diff --git a/src/utils/flock.deno.ts b/src/utils/flock.deno.ts
index 4248be5..8827e1c 100644
--- a/src/utils/flock.deno.ts
+++ b/src/utils/flock.deno.ts
@@ -1,5 +1,5 @@
-export async function flock(fd: number, op: 'ex' | 'un') {
- if (op == 'ex') {
+export async function flock(fd: number, op: "ex" | "un") {
+ if (op == "ex") {
await Deno.flock(fd, true)
} else {
await Deno.funlock(fd)
diff --git a/src/utils/flock.node.ts b/src/utils/flock.node.ts
index a16ccb4..ff1b9d4 100644
--- a/src/utils/flock.node.ts
+++ b/src/utils/flock.node.ts
@@ -1,18 +1,18 @@
-import koffi from 'npm:koffi@2'
+import koffi from "npm:koffi@2"
import * as util from "node:util"
import host from "./host.ts"
-const filename = host().platform == 'darwin' ? '/usr/lib/libSystem.dylib' : 'libc.so.6'
+const filename = host().platform == "darwin" ? "/usr/lib/libSystem.dylib" : "libc.so.6"
const libc = koffi.load(filename)
-const LOCK_EX = 2;
-const LOCK_UN = 8;
+const LOCK_EX = 2
+const LOCK_UN = 8
-const cflock = libc.func('int flock(int, int)');
-const flockAsync = util.promisify(cflock.async);
+const cflock = libc.func("int flock(int, int)")
+const flockAsync = util.promisify(cflock.async)
-async function flock(fd: number, op: 'un' | 'ex') {
- const rv = await flockAsync(fd, op == 'ex' ? LOCK_EX : LOCK_UN);
+async function flock(fd: number, op: "un" | "ex") {
+ const rv = await flockAsync(fd, op == "ex" ? LOCK_EX : LOCK_UN)
if (rv === -1) {
throw new Error("flock failed") // TODO read errno
}
diff --git a/src/utils/hacks.test.ts b/src/utils/hacks.test.ts
index e76176e..ff08e29 100644
--- a/src/utils/hacks.test.ts
+++ b/src/utils/hacks.test.ts
@@ -28,7 +28,8 @@ Deno.test({
fn: () => {
const result = validatePackageRequirement("apple.com/xcode/clt", "*")
assertEquals(result, undefined)
-}})
+ },
+})
Deno.test("validatePackageRequirement - linux hack", () => {
if (host().platform !== "linux") return
diff --git a/src/utils/hacks.ts b/src/utils/hacks.ts
index fbf4ce1..89f1303 100644
--- a/src/utils/hacks.ts
+++ b/src/utils/hacks.ts
@@ -5,19 +5,24 @@ import * as semver from "./semver.ts"
import host from "./host.ts"
import { TeaError } from "../../mod.ts"
-export function validatePackageRequirement(project: string, constraint: unknown): PackageRequirement | undefined
-{
- if (host().platform == 'darwin' && (project == "apple.com/xcode/clt" || project == "tea.xyz/gx/make")) {
+export function validatePackageRequirement(
+ project: string,
+ constraint: unknown,
+): PackageRequirement | undefined {
+ if (
+ host().platform == "darwin" &&
+ (project == "apple.com/xcode/clt" || project == "tea.xyz/gx/make")
+ ) {
// Apple will error out and prompt the user to install when the tool is used
- return // compact this dep away
+ return // compact this dep away
}
- if (host().platform == 'linux' && project == "tea.xyz/gx/make") {
+ if (host().platform == "linux" && project == "tea.xyz/gx/make") {
project = "gnu.org/make"
- constraint = '*'
+ constraint = "*"
}
- if (constraint == 'c99' && project == 'tea.xyz/gx/cc') {
- constraint = '^0.1'
+ if (constraint == "c99" && project == "tea.xyz/gx/cc") {
+ constraint = "^0.1"
}
if (isNumber(constraint)) {
@@ -33,6 +38,6 @@ export function validatePackageRequirement(project: string, constraint: unknown)
return {
project,
- constraint: constraint as semver.Range
+ constraint: constraint as semver.Range,
}
}
diff --git a/src/utils/host.test.ts b/src/utils/host.test.ts
index f590648..f6293e5 100644
--- a/src/utils/host.test.ts
+++ b/src/utils/host.test.ts
@@ -7,26 +7,26 @@ Deno.test("host()", async () => {
const { platform, arch } = host()
switch (uname[0]) {
- case "Darwin":
- assertEquals(platform, "darwin")
- break
- case "Linux":
- assertEquals(platform, "linux")
- break
- default:
- fail()
+ case "Darwin":
+ assertEquals(platform, "darwin")
+ break
+ case "Linux":
+ assertEquals(platform, "linux")
+ break
+ default:
+ fail()
}
switch (uname[1]) {
- case "aarch64":
- case "arm64":
- assertEquals(arch, "aarch64")
- break
- case "x86_64":
- assertEquals(arch, "x86-64")
- break
- default:
- fail()
+ case "aarch64":
+ case "arm64":
+ assertEquals(arch, "aarch64")
+ break
+ case "x86_64":
+ assertEquals(arch, "x86-64")
+ break
+ default:
+ fail()
}
async function run(cmd: string) {
diff --git a/src/utils/host.ts b/src/utils/host.ts
index cbd6705..ad2852a 100644
--- a/src/utils/host.ts
+++ b/src/utils/host.ts
@@ -2,7 +2,7 @@ import process from "node:process"
// when we support more variants of these that require specification
// we will tuple a version in with each eg. 'darwin' | ['windows', 10 | 11 | '*']
-export const SupportedPlatforms = ["darwin" , "linux" , "windows"] as const
+export const SupportedPlatforms = ["darwin", "linux", "windows"] as const
export type SupportedPlatform = typeof SupportedPlatforms[number]
export const SupportedArchitectures = ["x86-64", "aarch64"] as const
@@ -19,25 +19,27 @@ export default function host(): HostReturnValue {
const platform = (() => {
const platform = _internals.platform()
switch (platform) {
- case "darwin":
- case "linux":
- case "windows":
- return platform
- default:
- console.warn(`operating incognito as linux (${platform})`)
- return 'linux'
- }})()
+ case "darwin":
+ case "linux":
+ case "windows":
+ return platform
+ default:
+ console.warn(`operating incognito as linux (${platform})`)
+ return "linux"
+ }
+ })()
const arch = (() => {
const arch = _internals.arch()
switch (arch) {
- case "arm64":
- return "aarch64"
- case "x64":
- return "x86-64"
- default:
- throw new Error(`unsupported-arch: ${arch}`)
- }})()
+ case "arm64":
+ return "aarch64"
+ case "x64":
+ return "x86-64"
+ default:
+ throw new Error(`unsupported-arch: ${arch}`)
+ }
+ })()
const { target } = Deno.build
@@ -45,13 +47,13 @@ export default function host(): HostReturnValue {
platform,
arch,
target,
- build_ids: [platform, arch]
+ build_ids: [platform, arch],
}
}
const _internals = {
arch: () => process.arch,
- platform: () => Deno.build.os
+ platform: () => Deno.build.os,
}
export { _internals }
diff --git a/src/utils/misc.test.ts b/src/utils/misc.test.ts
index 577d092..bde00f1 100644
--- a/src/utils/misc.test.ts
+++ b/src/utils/misc.test.ts
@@ -16,7 +16,7 @@ Deno.test("validate array", () => {
})
Deno.test("validate obj", () => {
- assertEquals(validate.obj({a: 1}), {a: 1})
+ assertEquals(validate.obj({ a: 1 }), { a: 1 })
assertThrows(() => validate.obj("jkl"), "not-array: jkl")
})
@@ -29,7 +29,7 @@ Deno.test("flatmap", () => {
throw Error("test error")
}
- assertEquals(flatmap(1, throws, {rescue: true}), undefined)
+ assertEquals(flatmap(1, throws, { rescue: true }), undefined)
assertThrows(() => flatmap(1, throws), "test error")
})
@@ -47,7 +47,7 @@ Deno.test("async flatmap", async () => {
assertEquals(await flatmap(producer(undefined), add), undefined)
assertEquals(await flatmap(producer(1), (_n) => undefined), undefined)
- assertEquals(await flatmap(producer(1, Error()), add, {rescue: true}), undefined)
+ assertEquals(await flatmap(producer(1, Error()), add, { rescue: true }), undefined)
await assertRejects(() => flatmap(producer(1, Error()), add, undefined))
})
@@ -61,8 +61,8 @@ Deno.test("chuzzle", () => {
Deno.test("set insert", () => {
const s = new Set([1, 2, 3])
- assertEquals(s.insert(1), {inserted: false})
- assertEquals(s.insert(4), {inserted: true})
+ assertEquals(s.insert(1), { inserted: false })
+ assertEquals(s.insert(4), { inserted: true })
assertEquals(s.size, 4)
assertEquals(s.has(1), true)
@@ -83,6 +83,10 @@ Deno.test("array compact", () => {
const throws = () => {
throw Error("test error")
}
- assertEquals([()=>1, ()=>2, throws, ()=>3].compact((n) => n() * 2, { rescue: true }), [2, 4, 6])
- assertThrows(() => [()=>1, ()=>2, throws, ()=>3].compact((n) => n() * 2))
+ assertEquals([() => 1, () => 2, throws, () => 3].compact((n) => n() * 2, { rescue: true }), [
+ 2,
+ 4,
+ 6,
+ ])
+ assertThrows(() => [() => 1, () => 2, throws, () => 3].compact((n) => n() * 2))
})
diff --git a/src/utils/misc.ts b/src/utils/misc.ts
index b32a4a0..4c6e727 100644
--- a/src/utils/misc.ts
+++ b/src/utils/misc.ts
@@ -4,9 +4,9 @@ import { is_what, PlainObject } from "../deps.ts"
const { isPlainObject, isArray } = is_what
function validate_str(input: unknown): string {
- if (typeof input == 'boolean') return input ? 'true' : 'false'
- if (typeof input == 'number') return input.toString()
- if (typeof input != 'string') throw new Error(`not-string: ${input}`)
+ if (typeof input == "boolean") return input ? "true" : "false"
+ if (typeof input == "number") return input.toString()
+ if (typeof input != "string") throw new Error(`not-string: ${input}`)
return input
}
@@ -23,17 +23,17 @@ function validate_arr(input: unknown): Array {
const validate = {
str: validate_str,
obj: validate_plain_obj,
- arr: validate_arr
+ arr: validate_arr,
}
export { validate }
////////////////////////////////////////////////////////////// base extensions
-type Falsy = false | 0 | '' | null | undefined;
+type Falsy = false | 0 | "" | null | undefined
declare global {
interface Array {
- compact(): Array>;
+ compact(): Array>
compact(body: (t: T) => S | Falsy): Array
compact(body?: (t: T) => S | T | Falsy, opts?: { rescue: boolean }): Array
}
@@ -43,16 +43,19 @@ declare global {
}
}
-Set.prototype.insert = function(t: T) {
+Set.prototype.insert = function (t: T) {
if (this.has(t)) {
- return {inserted: false}
+ return { inserted: false }
} else {
this.add(t)
- return {inserted: true}
+ return { inserted: true }
}
}
-Array.prototype.compact = function(body?: (t: T) => S | Falsy, opts?: { rescue: boolean }): S[] {
+Array.prototype.compact = function (
+ body?: (t: T) => S | Falsy,
+ opts?: { rescue: boolean },
+): S[] {
const rv: S[] = []
for (const e of this) {
try {
@@ -65,18 +68,33 @@ Array.prototype.compact = function(body?: (t: T) => S | Falsy, opts?: { re
return rv
}
-export function flatmap(t: T | Falsy, body: (t: T) => S | Falsy, opts?: {rescue?: boolean}): S | undefined;
-export function flatmap(t: Promise, body: (t: T) => Promise, opts?: {rescue?: boolean}): Promise;
-export function flatmap(t: Promise | (T | Falsy), body: (t: T) => (S | Falsy) | Promise, opts?: {rescue?: boolean}): Promise | (S | undefined) {
+export function flatmap(
+ t: T | Falsy,
+ body: (t: T) => S | Falsy,
+ opts?: { rescue?: boolean },
+): S | undefined
+export function flatmap(
+ t: Promise,
+ body: (t: T) => Promise,
+ opts?: { rescue?: boolean },
+): Promise
+export function flatmap(
+ t: Promise | (T | Falsy),
+ body: (t: T) => (S | Falsy) | Promise,
+ opts?: { rescue?: boolean },
+): Promise | (S | undefined) {
try {
if (t instanceof Promise) {
- const foo = t.then(t => {
+ const foo = t.then((t) => {
if (!t) return
const s = body(t) as Promise
if (!s) return
const bar = s
- .then(body => body || undefined)
- .catch(err => { if (!opts?.rescue) throw err; else return undefined } )
+ .then((body) => body || undefined)
+ .catch((err) => {
+ if (!opts?.rescue) throw err
+ else return undefined
+ })
return bar
})
return foo
@@ -108,10 +126,10 @@ declare global {
}
}
-String.prototype.chuzzle = function() {
+String.prototype.chuzzle = function () {
return this.trim() || undefined
}
-Number.prototype.chuzzle = function() {
+Number.prototype.chuzzle = function () {
return Number.isNaN(this) ? undefined : this as number
}
diff --git a/src/utils/pkg.test.ts b/src/utils/pkg.test.ts
index 4f25468..0350f86 100644
--- a/src/utils/pkg.test.ts
+++ b/src/utils/pkg.test.ts
@@ -2,13 +2,13 @@ import { assert, assertEquals, assertFalse, assertThrows } from "deno/testing/as
import SemVer, { Range } from "./semver.ts"
import * as pkg from "./pkg.ts"
-Deno.test("pkg.str", async test => {
+Deno.test("pkg.str", async (test) => {
let out: string
await test.step("precise", () => {
out = pkg.str({
project: "test",
- version: new SemVer("1.2.3")
+ version: new SemVer("1.2.3"),
})
assertEquals(out, "test=1.2.3")
})
@@ -17,17 +17,19 @@ Deno.test("pkg.str", async test => {
await test.step(range, () => {
out = pkg.str({
project: "test",
- constraint: new Range(range)
+ constraint: new Range(range),
})
assertEquals(out, `test${range}`)
})
}
- for (const [range, expected] of [[">=1 <2", "^1"], [">=1.2 <2", "^1.2"], [">=1.2.3 <2", "^1.2.3"]]) {
+ for (
+ const [range, expected] of [[">=1 <2", "^1"], [">=1.2 <2", "^1.2"], [">=1.2.3 <2", "^1.2.3"]]
+ ) {
await test.step(`${range} == ${expected}`, () => {
out = pkg.str({
project: "test",
- constraint: new Range(range)
+ constraint: new Range(range),
})
assertEquals(out, `test${expected}`)
})
@@ -38,33 +40,33 @@ Deno.test("pkg.str", async test => {
out = pkg.str({
project: "test",
- constraint
+ constraint,
})
assert(constraint.single())
assertEquals(out, `test=1.2.3`)
})
})
-Deno.test("pkg.parse", async test => {
+Deno.test("pkg.parse", async (test) => {
await test.step("@5", () => {
const { constraint } = pkg.parse("test@5")
- assert(constraint.satisfies(new SemVer([5,0,0])))
- assert(constraint.satisfies(new SemVer([5,1,0])))
- assertFalse(constraint.satisfies(new SemVer([6,0,0])))
+ assert(constraint.satisfies(new SemVer([5, 0, 0])))
+ assert(constraint.satisfies(new SemVer([5, 1, 0])))
+ assertFalse(constraint.satisfies(new SemVer([6, 0, 0])))
})
await test.step("@5.0", () => {
const { constraint } = pkg.parse("test@5.0")
- assert(constraint.satisfies(new SemVer([5,0,0])))
- assert(constraint.satisfies(new SemVer([5,0,1])))
- assertFalse(constraint.satisfies(new SemVer([5,1,0])))
+ assert(constraint.satisfies(new SemVer([5, 0, 0])))
+ assert(constraint.satisfies(new SemVer([5, 0, 1])))
+ assertFalse(constraint.satisfies(new SemVer([5, 1, 0])))
})
await test.step("@5.0.0", () => {
const { constraint } = pkg.parse("test@5.0.0")
- assert(constraint.satisfies(new SemVer([5,0,0])))
- assert(constraint.satisfies(new SemVer([5,0,0,1])))
- assertFalse(constraint.satisfies(new SemVer([5,0,1])))
+ assert(constraint.satisfies(new SemVer([5, 0, 0])))
+ assert(constraint.satisfies(new SemVer([5, 0, 0, 1])))
+ assertFalse(constraint.satisfies(new SemVer([5, 0, 1])))
})
await test.step("bad input", () => {
@@ -73,11 +75,11 @@ Deno.test("pkg.parse", async test => {
await test.step("leading & trailing space", () => {
const { constraint } = pkg.parse(" test@5\t")
- assert(constraint.satisfies(new SemVer([5,0,0])))
+ assert(constraint.satisfies(new SemVer([5, 0, 0])))
})
})
-Deno.test("pkg.compare", async test => {
+Deno.test("pkg.compare", async (test) => {
await test.step("compare versions", () => {
const a = { project: "test", version: new SemVer("1.2.3") }
const b = { project: "test", version: new SemVer("2.1.3") }
diff --git a/src/utils/pkg.ts b/src/utils/pkg.ts
index 3a488c5..04b0910 100644
--- a/src/utils/pkg.ts
+++ b/src/utils/pkg.ts
@@ -13,9 +13,7 @@ export function parse(input: string): PackageRequirement {
}
export function compare(a: Package, b: Package): number {
- return a.project === b.project
- ? a.version.compare(b.version)
- : a.project.localeCompare(b.project)
+ return a.project === b.project ? a.version.compare(b.version) : a.project.localeCompare(b.project)
}
export function str(pkg: Package | PackageRequirement): string {
diff --git a/src/utils/semver.test.ts b/src/utils/semver.test.ts
index bfe453c..c7a27dc 100644
--- a/src/utils/semver.test.ts
+++ b/src/utils/semver.test.ts
@@ -2,10 +2,15 @@
import { assert, assertEquals, assertFalse, assertThrows } from "deno/assert/mod.ts"
import SemVer, * as semver from "./semver.ts"
-
-Deno.test("semver", async test => {
+Deno.test("semver", async (test) => {
await test.step("sort", () => {
- const input = [new SemVer([1,2,3]), new SemVer("10.3.4"), new SemVer("1.2.4"), semver.parse("1.2.3.1")!, new SemVer("2.3.4")]
+ const input = [
+ new SemVer([1, 2, 3]),
+ new SemVer("10.3.4"),
+ new SemVer("1.2.4"),
+ semver.parse("1.2.3.1")!,
+ new SemVer("2.3.4"),
+ ]
const sorted1 = [...input].sort(semver.compare)
const sorted2 = [...input].sort()
@@ -16,7 +21,13 @@ Deno.test("semver", async test => {
})
await test.step("calver sort", () => {
- const input = [new SemVer([1,2,3]), new SemVer("2.3.4"), new SemVer("2023.03.04"), semver.parse("1.2.3.1")!, new SemVer([3,4,5])]
+ const input = [
+ new SemVer([1, 2, 3]),
+ new SemVer("2.3.4"),
+ new SemVer("2023.03.04"),
+ semver.parse("1.2.3.1")!,
+ new SemVer([3, 4, 5]),
+ ]
const sorted1 = [...input].sort(semver.compare)
const sorted2 = [...input].sort()
@@ -63,11 +74,11 @@ Deno.test("semver", async test => {
assertEquals(new SemVer("v1").toString(), "1.0.0")
assertEquals(new SemVer("9e").toString(), "9e")
- assertEquals(new SemVer("9e").components, [9,5])
+ assertEquals(new SemVer("9e").components, [9, 5])
assertEquals(new SemVer("3.3a").toString(), "3.3a")
- assertEquals(new SemVer("3.3a").components, [3,3,1])
+ assertEquals(new SemVer("3.3a").components, [3, 3, 1])
assertEquals(new SemVer("1.1.1q").toString(), "1.1.1q")
- assertEquals(new SemVer("1.1.1q").components, [1,1,1,17])
+ assertEquals(new SemVer("1.1.1q").components, [1, 1, 1, 17])
})
await test.step("ranges", () => {
@@ -164,7 +175,7 @@ Deno.test("semver", async test => {
assertEquals(new semver.Range(">=300.1.0<300.1.1").toString(), "@300.1.0")
})
- await test.step("intersection", async test => {
+ await test.step("intersection", async (test) => {
await test.step("^3.7…=3.11", () => {
const a = new semver.Range("^3.7")
const b = new semver.Range("=3.11")
@@ -248,10 +259,10 @@ Deno.test("semver", async test => {
})
Deno.test("coverage", () => {
- assert(new SemVer("1.2.3").eq(new SemVer([1,2,3])))
- assert(new SemVer("1.2.3").neq(new SemVer([1,2,4])))
- assert(new SemVer("1.2.3").lt(new SemVer([1,2,4])))
- assert(new SemVer("1.2.4").gt(new SemVer([1,2,3])))
+ assert(new SemVer("1.2.3").eq(new SemVer([1, 2, 3])))
+ assert(new SemVer("1.2.3").neq(new SemVer([1, 2, 4])))
+ assert(new SemVer("1.2.3").lt(new SemVer([1, 2, 4])))
+ assert(new SemVer("1.2.4").gt(new SemVer([1, 2, 3])))
assertThrows(() => new SemVer("1.q.3"))
@@ -283,13 +294,15 @@ Deno.test("coverage", () => {
assert(new semver.Range("*").satisfies(new SemVer("1.2.3")))
- assertEquals(new semver.Range("^1").max([new SemVer("1.2.3"), new SemVer("1.2.4")]), new SemVer("1.2.4"))
+ assertEquals(
+ new semver.Range("^1").max([new SemVer("1.2.3"), new SemVer("1.2.4")]),
+ new SemVer("1.2.4"),
+ )
assertEquals(new semver.Range("*").single(), undefined)
assert(semver.intersect(new semver.Range("*"), new semver.Range("^2")))
assert(semver.intersect(new semver.Range("^2"), new semver.Range("*")))
-
assertEquals(new semver.Range("^1.2.0").toString(), "^1.2")
})
diff --git a/src/utils/semver.ts b/src/utils/semver.ts
index ed6109a..8b07598 100644
--- a/src/utils/semver.ts
+++ b/src/utils/semver.ts
@@ -4,8 +4,8 @@ import { isArray, isString } from "https://deno.land/x/is_what@v4.1.15/src/index
/**
* we have our own implementation because open source is full of weird
* but *almost* valid semver schemes, eg:
- * openssl 1.1.1q
- * ghc 5.64.3.2
+ * openssl 1.1.1q
+ * ghc 5.64.3.2
* it also allows us to implement semver_intersection without hating our lives
*/
export default class SemVer {
@@ -23,10 +23,10 @@ export default class SemVer {
readonly pretty?: string
constructor(input: string | number[] | Range | SemVer) {
- if (typeof input == 'string') {
- const vprefix = input.startsWith('v')
+ if (typeof input == "string") {
+ const vprefix = input.startsWith("v")
const raw = vprefix ? input.slice(1) : input
- const parts = raw.split('.')
+ const parts = raw.split(".")
let pretty_is_raw = false
this.components = parts.flatMap((x, index) => {
const match = x.match(/^(\d+)([a-z])$/)
@@ -37,7 +37,7 @@ export default class SemVer {
pretty_is_raw = true
return [n, char_to_num(match[2])]
} else if (/^\d+$/.test(x)) {
- const n = parseInt(x) // parseInt will parse eg. `5-start` to `5`
+ const n = parseInt(x) // parseInt will parse eg. `5-start` to `5`
if (isNaN(n)) throw new Error(`invalid version: ${input}`)
return [n]
} else {
@@ -54,7 +54,7 @@ export default class SemVer {
this.pretty = v.pretty
} else {
this.components = [...input]
- this.raw = input.join('.')
+ this.raw = input.join(".")
}
this.major = this.components[0]
@@ -62,7 +62,7 @@ export default class SemVer {
this.patch = this.components[2] ?? 0
function char_to_num(c: string) {
- return c.charCodeAt(0) - 'a'.charCodeAt(0) + 1
+ return c.charCodeAt(0) - "a".charCodeAt(0) + 1
}
}
@@ -70,7 +70,7 @@ export default class SemVer {
return this.pretty ??
(this.components.length <= 3
? `${this.major}.${this.minor}.${this.patch}`
- : this.components.join('.'))
+ : this.components.join("."))
}
eq(that: SemVer): boolean {
@@ -124,11 +124,11 @@ export function isValid(input: string) {
/// we don’t support as much as node-semver but we refuse to do so because it is badness
export class Range {
// contract [0, 1] where 0 != 1 and 0 < 1
- readonly set: ([SemVer, SemVer] | SemVer)[] | '*'
+ readonly set: ([SemVer, SemVer] | SemVer)[] | "*"
constructor(input: string | ([SemVer, SemVer] | SemVer)[]) {
if (input === "*") {
- this.set = '*'
+ this.set = "*"
} else if (!isString(input)) {
this.set = input
} else {
@@ -136,7 +136,7 @@ export class Range {
const err = () => new Error(`invalid semver range: ${input}`)
- this.set = input.split(/(?:,|\s*\|\|\s*)/).map(input => {
+ this.set = input.split(/(?:,|\s*\|\|\s*)/).map((input) => {
let match = input.match(/^>=((\d+\.)*\d+)\s*(<((\d+\.)*\d+))?$/)
if (match) {
const v1 = new SemVer(match[1])
@@ -145,46 +145,49 @@ export class Range {
} else if ((match = input.match(/^([~=<^@])(.+)$/))) {
let v1: SemVer | undefined, v2: SemVer | undefined
switch (match[1]) {
- // deno-lint-ignore no-case-declarations
- case "^":
- v1 = new SemVer(match[2])
- const parts = []
- for (let i = 0; i < v1.components.length; i++) {
- if (v1.components[i] === 0 && i < v1.components.length - 1) {
- parts.push(0)
- } else {
- parts.push(v1.components[i] + 1)
- break
+ // deno-lint-ignore no-case-declarations
+ case "^":
+ v1 = new SemVer(match[2])
+ const parts = []
+ for (let i = 0; i < v1.components.length; i++) {
+ if (v1.components[i] === 0 && i < v1.components.length - 1) {
+ parts.push(0)
+ } else {
+ parts.push(v1.components[i] + 1)
+ break
+ }
}
+ v2 = new SemVer(parts)
+ return [v1, v2]
+ case "~":
+ {
+ v1 = new SemVer(match[2])
+ if (v1.components.length == 1) {
+ // yep this is the official policy
+ v2 = new SemVer([v1.major + 1])
+ } else {
+ v2 = new SemVer([v1.major, v1.minor + 1])
+ }
+ }
+ return [v1, v2]
+ case "<":
+ v1 = new SemVer([0])
+ v2 = new SemVer(match[2])
+ return [v1, v2]
+ case "=":
+ return new SemVer(match[2])
+ case "@": {
+ // @ is not a valid semver operator, but people expect it to work like so:
+ // @5 => latest 5.x (ie ^5)
+ // @5.1 => latest 5.1.x (ie. ~5.1)
+ // @5.1.0 => latest 5.1.0 (usually 5.1.0 since most stuff hasn't got more digits)
+ const parts = match[2].split(".").map((x) => parseInt(x))
+ v1 = new SemVer(parts)
+ const last = parts.pop()!
+ v2 = new SemVer([...parts, last + 1])
+ return [v1, v2]
}
- v2 = new SemVer(parts)
- return [v1, v2]
- case "~": {
- v1 = new SemVer(match[2])
- if (v1.components.length == 1) {
- // yep this is the official policy
- v2 = new SemVer([v1.major + 1])
- } else {
- v2 = new SemVer([v1.major, v1.minor + 1])
- }
- } return [v1, v2]
- case "<":
- v1 = new SemVer([0])
- v2 = new SemVer(match[2])
- return [v1, v2]
- case "=":
- return new SemVer(match[2])
- case "@": {
- // @ is not a valid semver operator, but people expect it to work like so:
- // @5 => latest 5.x (ie ^5)
- // @5.1 => latest 5.1.x (ie. ~5.1)
- // @5.1.0 => latest 5.1.0 (usually 5.1.0 since most stuff hasn't got more digits)
- const parts = match[2].split(".").map(x => parseInt(x))
- v1 = new SemVer(parts)
- const last = parts.pop()!
- v2 = new SemVer([...parts, last + 1])
- return [v1, v2]
- }}
+ }
}
throw err()
})
@@ -199,10 +202,10 @@ export class Range {
}
toString(): string {
- if (this.set === '*') {
- return '*'
+ if (this.set === "*") {
+ return "*"
} else {
- return this.set.map(v => {
+ return this.set.map((v) => {
if (!isArray(v)) return `=${v.toString()}`
const [v1, v2] = v
if (v1.major > 0 && v2.major == v1.major + 1 && v2.minor == 0 && v2.patch == 0) {
@@ -222,7 +225,7 @@ export class Range {
}).join(",")
}
- function at(v1: SemVer, {components: cc2}: SemVer) {
+ function at(v1: SemVer, { components: cc2 }: SemVer) {
const cc1 = [...v1.components]
if (cc1.length > cc2.length) {
@@ -282,10 +285,10 @@ export class Range {
}
satisfies(version: SemVer): boolean {
- if (this.set === '*') {
+ if (this.set === "*") {
return true
} else {
- return this.set.some(v => {
+ return this.set.some((v) => {
if (isArray(v)) {
const [v1, v2] = v
return version.compare(v1) >= 0 && version.compare(v2) < 0
@@ -297,11 +300,11 @@ export class Range {
}
max(versions: SemVer[]): SemVer | undefined {
- return versions.filter(x => this.satisfies(x)).sort((a,b) => a.compare(b)).pop()
+ return versions.filter((x) => this.satisfies(x)).sort((a, b) => a.compare(b)).pop()
}
single(): SemVer | undefined {
- if (this.set === '*') return
+ if (this.set === "*") return
if (this.set.length > 1) return
return isArray(this.set[0]) ? undefined : this.set[0]
}
@@ -320,9 +323,8 @@ function zip(a: T[], b: U[]) {
return rv
}
-
function _compare(a: SemVer, b: SemVer): number {
- for (const [c,d] of zip(cmpcomponents(a), cmpcomponents(b))) {
+ for (const [c, d] of zip(cmpcomponents(a), cmpcomponents(b))) {
if (c != d) return (c ?? 0) - (d ?? 0)
}
return 0
@@ -331,7 +333,7 @@ function _compare(a: SemVer, b: SemVer): number {
/// we worry that one day we will severely regret this but… it’s what we do for now
function cmpcomponents(v: SemVer) {
if (v.major > 1996 && v.major != Infinity) {
- return [0,0,0, ...v.components]
+ return [0, 0, 0, ...v.components]
} else {
return v.components
}
@@ -339,10 +341,9 @@ function _compare(a: SemVer, b: SemVer): number {
}
export { _compare as compare }
-
export function intersect(a: Range, b: Range): Range {
- if (b.set === '*') return a
- if (a.set === '*') return b
+ if (b.set === "*") return a
+ if (a.set === "*") return b
// calculate the intersection between two semver.Ranges
const set: ([SemVer, SemVer] | SemVer)[] = []
@@ -377,10 +378,9 @@ export function intersect(a: Range, b: Range): Range {
return new Range(set)
}
-
//FIXME yes yes this is not sufficient
export const regex = /\d+\.\d+\.\d+/
function chomp(v: SemVer) {
- return v.toString().replace(/(\.0)+$/g, '') || '0'
+ return v.toString().replace(/(\.0)+$/g, "") || "0"
}