Skip to content

Commit 02d1f68

Browse files
committed
façade for run/install (#13)
1 parent 9f11cf4 commit 02d1f68

24 files changed

Lines changed: 463 additions & 101 deletions

.github/deno-to-node.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,16 @@ await build({
6363
},
6464
exports: {
6565
"./src/src/utils/semver": {
66+
//TODO remove when gui is updated to use `@teaxyz/lib/semver`
6667
import: "./src/src/utils/semver.ts"
68+
},
69+
"./semver": {
70+
import: "./esm/src/utils/semver.js",
71+
require: "./script/src/utils/semver.js"
72+
},
73+
"./porcelain/install": {
74+
import: "./esm/src/porcelain/install.js",
75+
require: "./script/src/porcelain/install.js"
6776
}
6877
}
6978
},

.github/workflows/cd.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ concurrency:
1414
cancel-in-progress: true
1515

1616
jobs:
17+
check:
18+
uses: ./.github/workflows/ci.yml
19+
1720
build:
21+
needs: [check]
1822
if: ${{ github.event.release.prerelease }}
1923
runs-on: ubuntu-latest
2024
steps:

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ on:
77
- '**/*.ts'
88
- .github/workflows/ci.yml
99
workflow_dispatch:
10+
workflow_call:
1011

1112
concurrency:
1213
group: ${{ github.ref }}

LICENSE.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@
186186
same "printed page" as the copyright notice for easier
187187
identification within third-party archives.
188188

189-
Copyright 2022 tea.inc.
189+
Copyright 2023 tea.inc.
190190

191191
Licensed under the Apache License, Version 2.0 (the "License");
192192
you may not use this file except in compliance with the License.

README.md

Lines changed: 129 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -38,69 +38,103 @@ import * as tea from "https://raw.github.com/teaxyz/lib/v0/mod.ts"
3838

3939
## Usage
4040

41-
To install and utilize Python 3.10:
41+
```ts
42+
import { porcelain } from "@teaxyz/lib";
43+
const { run } = porcelain;
44+
45+
await run(`python -c 'print("Hello, World!")'`).exec();
46+
// ^^ installs python and its deps (into ~/.tea/python.org/v3.x.y)
47+
// ^^ runs the command
48+
// ^^ output goes to the terminal
49+
// ^^ throws on execution error or non-zero exit code
50+
// ^^ executes via `/bin/sh` (so quoting and that work as expected)
51+
```
52+
53+
Capturing stdout is easy:
4254

4355
```ts
44-
import { prefab, semver, hooks } from "@teaxyz/lib"
45-
import { exec } from "node:child_process"
46-
const { install, hydrate, resolve } = prefab
47-
const { useSync, useShellEnv } = hooks
48-
49-
// ensure pantry exists and is up-to-date
50-
await useSync()
51-
52-
// define the pkg(s) you want
53-
// see https://devhints.io/semver for semver syntax (~, ^, etc)
54-
const pkg = { project: 'python.org', constraint: semver.Range("~3.10") }
55-
// hydrate the full dependency tree
56-
const { pkgs: tree } = await hydrate(pkg)
57-
// resolve the tree of constraints to specific package versions
58-
const { installed, pending } = await resolve(tree)
59-
60-
for (const pkg of pending) {
61-
const installation = await install(pkg)
62-
// ^^ install packages that aren’t yet installed
63-
// ^^ takes a logger parameter so you can show progress to the user
64-
// ^^ you could do these in parallel to speed things up
65-
// ^^ by default, versioned installs go to ~/.tea, separated from the user’s system. The install location can be customized, see next section.
66-
await link(installation)
67-
// ^^ creates v*, vx, vx.y symlinks ∵ some packages depend on this
68-
installed.push(installation)
69-
}
70-
71-
const { map, flatten } = useShellEnv()
72-
const env = flatten(map(installed))
73-
74-
exec("python -c 'print(\"Hello, World!\")'", { env })
75-
76-
// the above is quite verbose, but we’ll provide a façade pattern soon
56+
const { stdout } = await run(`ruby -e 'puts ", World!"'`, { stdout: true });
57+
console.log("Hello,", code);
7758
```
7859

60+
> `{ stderr: true }` also works.
61+
62+
If there’s a non-zero exit code, we `throw`. However, when you need to,
63+
you can capture it instead:
64+
65+
```ts
66+
const { status } = await run(`perl -e 'exit(7)'`, { status: true });
67+
assert(status == 7); // ^^ didn’t throw!
68+
```
69+
70+
> The run function’s options also takes `env` if you need to supplement or
71+
> replace the inherited environment (which is passed by default).
72+
73+
Need a specific version of something? [tea][tea/cli] can install any version
74+
of any package:
75+
76+
```ts
77+
const { install, run } = porcelain;
78+
79+
const node16 = await install("nodejs.org^16.18"); // ※ https://devhints.io/semver
80+
81+
await run(['node', '-e', 'console.log(process.version)']);
82+
// => v16.18.1
83+
```
84+
85+
> Notice we passed args as `string[]`. This is also supported and is often
86+
> preferable since shell quoting rules can be tricky. If you pass `string[]`
87+
> we execute the command directly rather than via `/bin/sh`.
88+
7989
All of tea’s packages are relocatable so you can configure libtea to install
8090
wherever you want:
8191

8292
```ts
83-
import { hooks, Path } from "tea"
84-
const { useConfig } = hooks
93+
import { hooks, Path, porcelain } from "tea";
94+
const { install } = porcelain;
95+
const { useConfig } = hooks;
8596

86-
useConfig({ prefix: Path.home().join(".local/share/my-app") })
87-
// ^^ must be done before any other libtea calls
97+
useConfig({ prefix: Path.home().join(".local/share/my-app") });
98+
// ^^ must be done **before** any other libtea calls
8899

89-
// now if you install python you’ll get:
90-
// /home/you/.local/share/my-app/python.org/v3.10.11/bin/python
100+
const go = await install("go.dev");
101+
// ^^ /home/you/.local/share/my-app/go.dev/v1.20.4
91102
```
92103

93-
### Notes
104+
### Designed for Composibility
94105

95-
We use a hook-like pattern because it is great. This library is not itself
96-
designed for React.
106+
The library is split into [plumbing] and [porcelain] (copying git’s lead).
107+
The porcelain is what most people need, but if you need more control, dive
108+
into the porcelain sources to see how to use the plumbing primitives to get
109+
precisely what you need.
110+
111+
For example if you want to run a command with node’s `spawn` instead it is
112+
simple enough to first use our porcelain `install` function then grab the
113+
`env` you’ll need to pass to `spawn` using our `useShellEnv` hook.
114+
115+
Perhaps what you create should go into the porcelain? If so, please open a PR.
116+
117+
### Logging
118+
119+
Most functions take an optional `logger` parameter so you can output logging
120+
information if you so choose. `tea/cli` has a fairly sophisticated logger, so
121+
go check that out if you want. For our porcelain functions we provide a simple
122+
debug-friendly logger (`ConsoleLogger`) that will output everything via
123+
`console.error`:
124+
125+
```ts
126+
import { porcelain, plumbing, utils } from "tea"
127+
const { ConsoleLogger } = utils
128+
const { run } = porcelain
129+
130+
const logger = ConsoleLogger()
131+
await run("youtube-dl youtu.be/xiq5euezOEQ", logger).exec()
132+
```
97133

98134
### Caveats
99135

100-
If the user has no existing tea/cli or you use your own prefix then the
101-
pantry must be sync’d with `useSync()` at least once. `useSync` requires
102-
either `git` or `tar` to be in `PATH`. We’ll remove this requirement with
103-
time.
136+
We use a hook-like pattern because it is great. This library is not itself
137+
designed for React.
104138

105139
We have our own implementation of semver because open source has existed for
106140
decades and Semantic Versioning is much newer than that. Our implementation is
@@ -114,10 +148,15 @@ before any other calls might happen. We call it explicitly in our code so you
114148
will need to call it yourself in such a case. This is not ideal and we’d
115149
appreciate your help in fixing it.
116150

117-
There is minimal magic, [tea/cli] has magic because the end-user appreciates
118-
it but libraries need well defined behavior. We will provide a façade patterns
119-
to make life easier, but the primitives of libtea require you to read the
120-
docs to use them effectively.
151+
The plumbing has no magic. Libraries need well defined behavior.
152+
You’ll need to read the docs to use them effectively.
153+
154+
libtea almost certainly will not work in a browser. Potentially its possible.
155+
The first step would be compiling our bottles to WASM. We could use your help
156+
with that…
157+
158+
Windows is not yet supported, but we otherwise support everything tea/cli
159+
does.
121160

122161
## What Packages are Available?
123162

@@ -127,6 +166,9 @@ If something you need is not there, adding to the pantry has been designed to
127166
be an easy and enjoyable process. Your contribution is both welcome and
128167
desired!
129168

169+
To see what is available refer to the [pantry] docs or you can run:
170+
`tea pkg search foo`.
171+
130172
 
131173

132174

@@ -139,7 +181,12 @@ We would be thrilled to hear your ideas† or receive your pull requests.
139181
## Anatomy
140182

141183
The code is written with Deno (just like [tea/cli]) but is compiled to a
142-
node package for wider accessibility (and ∵ [tea/gui] is node/electron)
184+
node package for wider accessibility (and ∵ [tea/gui] is node/electron).
185+
186+
The library is architected into hooks, plumbing and porcelain. Where the hooks
187+
represent the low level primitives of pkging, the plumbing glues those
188+
primitives together into useful components and the porcelain is a user
189+
friendly *façade* pattern for the plumbing.
143190

144191
## Supporting Other Languages
145192

@@ -149,19 +196,26 @@ the scope *tight*. Probably we would prefer to have one repo per language.
149196
tea has sensible rules for how packages are defined and installed so writing
150197
a port should be simple.
151198

199+
We would love to explore how possible writing this in rust and then compiling
200+
to WASM for all other languages would be. Can you help?
201+
152202
Open a [discussion] to start.
153203

154204
[discussion]: https://github.com/orgs/teaxyz/discussions
155205
[tea/cli]: https://github.com/teaxyz/cli
156206
[tea/gui]: https://github.com/teaxyz/gui
157207
[Deno]: https://deno.land
158208
[pantry]: https://github.com/teaxyz/pantry
209+
[plumbing]: ./plumbing/
210+
[porcelain]: ./porcelain/
159211

160212
 
161213

162214

163215
# Tasks
164216

217+
Run eg. `xc coverage` or `xc bump patch`.
218+
165219
## Coverage
166220

167221
```sh
@@ -170,3 +224,26 @@ deno coverage cov_profile --lcov --output=cov_profile.lcov
170224
tea genhtml -o cov_profile/html cov_profile.lcov
171225
open cov_profile/html/index.html
172226
```
227+
228+
## Bump
229+
230+
Bumps version by creating a pre-release which then engages the deployment
231+
infra in GitHub Actions.
232+
233+
```sh
234+
if ! git diff-index --quiet HEAD --; then
235+
echo "error: dirty working tree" >&2
236+
exit 1
237+
fi
238+
239+
if [ "$(git rev-parse --abbrev-ref HEAD)" != "main" ]; then
240+
echo "error: requires main branch" >&2
241+
exit 1
242+
fi
243+
244+
V=$(git describe --tags --abbrev=0 --match "v[0-9]*.[0-9]*.[0-9]*")
245+
V=$(tea semverator bump $V $PRIORITY)
246+
247+
git push origin main
248+
tea gh release create "v$V" --prerelease --generate-notes --title
249+
```

deno.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,6 @@
3030
"imports": {
3131
"is-what": "https://deno.land/x/is_what@v4.1.8/src/index.ts",
3232
"outdent": "https://deno.land/x/outdent@v0.8.0/mod.ts",
33-
"deno/": "https://deno.land/std@0.187.0/"
33+
"deno/": "https://deno.land/std@0.189.0/"
3434
}
3535
}

examples/whisper.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/usr/bin/env -S deno run --allow-read --allow-write --allow-run --allow-env --unstable --allow-net
2+
3+
import { porcelain } from "https://raw.github.com/teaxyz/lib/v0/mod.ts"
4+
const { run } = porcelain
5+
6+
const url = 'https://raw.githubusercontent.com/ggerganov/whisper.cpp/raw/master/samples/jfk.wav'
7+
const rsp = await fetch(url)
8+
await Deno.writeFile("jfk.wav", rsp.body!)
9+
10+
await run("whisper.cpp jfk.wav")

mod.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,17 @@ import useFetch from "./src/hooks/useFetch.ts"
1818
import useDownload from "./src/hooks/useDownload.ts"
1919
import useShellEnv from "./src/hooks/useShellEnv.ts"
2020
import useInventory from "./src/hooks/useInventory.ts"
21-
import hydrate from "./src/prefab/hydrate.ts"
22-
import link from "./src/prefab/link.ts"
23-
import install from "./src/prefab/install.ts"
24-
import resolve from "./src/prefab/resolve.ts"
21+
import hydrate from "./src/plumbing/hydrate.ts"
22+
import link from "./src/plumbing/link.ts"
23+
import install, { ConsoleLogger } from "./src/plumbing/install.ts"
24+
import resolve from "./src/plumbing/resolve.ts"
2525
import { validatePackageRequirement } from "./src/utils/hacks.ts"
2626
import useSync from "./src/hooks/useSync.ts"
27+
import run from "./src/porcelain/run.ts"
28+
import porcelain_install from "./src/porcelain/install.ts"
2729

2830
const utils = {
29-
pkg, host, flatmap, validate, panic
31+
pkg, host, flatmap, validate, panic, ConsoleLogger
3032
}
3133

3234
const hooks = {
@@ -43,18 +45,23 @@ const hooks = {
4345
useSync,
4446
}
4547

46-
const prefab = {
48+
const plumbing = {
4749
hydrate,
4850
link,
4951
install,
5052
resolve
5153
}
5254

55+
const porcelain = {
56+
install: porcelain_install,
57+
run
58+
}
59+
5360
const hacks = {
5461
validatePackageRequirement
5562
}
5663

57-
export { utils, hooks, prefab, hacks, semver }
64+
export { utils, hooks, plumbing, porcelain, hacks, semver }
5865

5966
/// export types
6067
// we cannot add these to the above objects or they cannot be used as types

src/hooks/useCellar.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import SemVer, * as semver from "../utils/semver.ts"
22
import { useTestConfig } from "./useTestConfig.ts"
3-
import install from "../prefab/install.ts"
3+
import install from "../plumbing/install.ts"
44
import useCellar from "./useCellar.ts"
55

66
Deno.test("resolve()", async () => {

0 commit comments

Comments
 (0)