@ushawarma/stack 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -0
- package/bin/stack.cjs +33 -0
- package/binaries/darwin-arm64/stack +0 -0
- package/binaries/darwin-x64/stack +0 -0
- package/binaries/linux-arm64/stack +0 -0
- package/binaries/linux-x64/stack +0 -0
- package/build-info.json +10 -0
- package/package.json +13 -0
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
<img src="assets/icon.svg" width="64" height="64" alt="">
|
|
2
|
+
|
|
3
|
+
# stack
|
|
4
|
+
|
|
5
|
+
Reusable, agent-safe development stacks.
|
|
6
|
+
|
|
7
|
+
Pick your tools and services, publish them as a **bundle** in a git repo, and use that bundle from any
|
|
8
|
+
project. Each project gets its own pinned, independent instance. stack composes bundles and generates
|
|
9
|
+
config for existing tools ([mise](https://mise.jdx.dev) installs tools; Pitchfork, via `mise daemons`,
|
|
10
|
+
runs services) instead of replacing them.
|
|
11
|
+
|
|
12
|
+
> Status: early prototype, tested end to end on Linux against real mise + Pitchfork and an OCI
|
|
13
|
+
> registry. See [docs/DESIGN.md](docs/DESIGN.md) for what is and isn't covered yet.
|
|
14
|
+
|
|
15
|
+
## Quick look
|
|
16
|
+
|
|
17
|
+
A bundle is a git repo with a `bundle.toml` and whatever files it needs:
|
|
18
|
+
|
|
19
|
+
```toml
|
|
20
|
+
# bundle.toml
|
|
21
|
+
[bundle]
|
|
22
|
+
name = "pybase"
|
|
23
|
+
version = "1.0.0"
|
|
24
|
+
|
|
25
|
+
[tools]
|
|
26
|
+
python = "3.13"
|
|
27
|
+
uv = "latest"
|
|
28
|
+
|
|
29
|
+
[services.postgres]
|
|
30
|
+
preset = "postgres"
|
|
31
|
+
version = "17"
|
|
32
|
+
|
|
33
|
+
[tasks.seed]
|
|
34
|
+
run = "psql \"$DATABASE_URL\" -f {{bundle_dir}}/fixtures/seed.sql"
|
|
35
|
+
services = ["postgres"]
|
|
36
|
+
|
|
37
|
+
[paths]
|
|
38
|
+
bin = ["bin"] # bundle-shipped CLIs go on PATH
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
A project uses bundles from git, an OCI registry, or a local path in `stack.toml`:
|
|
42
|
+
|
|
43
|
+
```toml
|
|
44
|
+
[[use]]
|
|
45
|
+
bundle = "git+https://github.com/acme/pybase?ref=v1"
|
|
46
|
+
|
|
47
|
+
[[use]]
|
|
48
|
+
bundle = "oci:ghcr.io/acme/obs:2.0.0"
|
|
49
|
+
|
|
50
|
+
[tasks.test]
|
|
51
|
+
run = "uv sync -q && uv run pytest -q"
|
|
52
|
+
services = ["postgres", "redis"]
|
|
53
|
+
|
|
54
|
+
[override.env]
|
|
55
|
+
LOG_LEVEL = "warn" # both bundles set LOG_LEVEL; the project must choose
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
stack compile # resolve, lock, assign ports, write .config/mise/conf.d/stack.toml
|
|
60
|
+
stack up --ttl 30m # start services, verify each is *this* instance, record a session
|
|
61
|
+
stack exec --require postgres -- pytest
|
|
62
|
+
stack down # succeeds only once the processes are confirmed gone
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Guarantees
|
|
66
|
+
|
|
67
|
+
- **Pinned by commit.** `stack.lock` records each bundle's commit and content hash. Moving a tag
|
|
68
|
+
upstream changes nothing until `stack compile --update`, which reports what moved.
|
|
69
|
+
- **No silent conflicts.** If two layers define the same key differently, compile fails with every
|
|
70
|
+
conflict listed. Only `[override.*]` resolves one, and the output records what it replaced.
|
|
71
|
+
- **Bundles carry files.** `{{bundle_dir}}` and `paths.bin` resolve to the bundle's own files.
|
|
72
|
+
- **Instance values stay out of bundles.** Bundles cannot pin ports. Each checkout gets its own
|
|
73
|
+
ports from a machine-wide registry (40000-49999, never service defaults), stable across restarts.
|
|
74
|
+
- **Never the wrong instance.** Before every `exec`, each service is checked live. Postgres and
|
|
75
|
+
Redis are confirmed over the app's own `DATABASE_URL`/`REDIS_URL` to be this checkout's server
|
|
76
|
+
(by data directory). Endpoints of unverified services are poisoned (host replaced with
|
|
77
|
+
`unverified.stack.invalid`) so apps with hardcoded fallbacks fail loudly instead of reaching some
|
|
78
|
+
other server. `--require` makes the command refuse to run instead.
|
|
79
|
+
- **Verified generations.** Session records fingerprint the complete compiled configuration and
|
|
80
|
+
assigned ports. Changed bundles, project overrides, or ports make service checks unavailable
|
|
81
|
+
until `stack up` restarts and verifies the new generation.
|
|
82
|
+
- **Owned lifetimes.** Sessions can lease on a TTL or a runner's PID. Active commands protect
|
|
83
|
+
TTL sessions until completion; `stack gc` (and every `stack up`) reclaims expired idle ones.
|
|
84
|
+
`down` retains ownership records if discovery or cleanup fails, and reports success only after
|
|
85
|
+
recorded processes and ports are gone.
|
|
86
|
+
- **Honest failures.** `up` reports the steps it completed, whether anything changed, and whether
|
|
87
|
+
retrying is safe.
|
|
88
|
+
- **Agent-friendly.** `--json` emits one object on stdout; errors have a stable `code`, a `hint`
|
|
89
|
+
and `details`. `stack mcp` serves the same contract over MCP. Git never prompts.
|
|
90
|
+
|
|
91
|
+
## Commands
|
|
92
|
+
|
|
93
|
+
| Command | Does |
|
|
94
|
+
|---|---|
|
|
95
|
+
| `stack compile [--update \| --locked] [--reassign-ports]` | Resolve, lock, assign ports, write provider config |
|
|
96
|
+
| `stack inspect` | Show the composed stack, origins and ports; writes nothing |
|
|
97
|
+
| `stack up [--ttl 30m] [--owner-pid N]` | Start services, verify them, record a session |
|
|
98
|
+
| `stack status` | Verify every service now; session and lease state (exit 1 if unhealthy) |
|
|
99
|
+
| `stack exec [--require S \| --require-all] -- <cmd>` | Run with tools and env; unverified endpoints poisoned |
|
|
100
|
+
| `stack down` | Stop services and confirm they are gone |
|
|
101
|
+
| `stack renew` / `stack gc` | Renew this session's lease / reclaim expired sessions machine-wide |
|
|
102
|
+
| `stack publish <dir> oci:<registry>/<repo>:<tag>` | Publish a bundle as an OCI artifact |
|
|
103
|
+
| `stack mcp` | MCP server (stdio) exposing the same operations |
|
|
104
|
+
|
|
105
|
+
All accept `-C <dir>` and `--json`. `exec -C` runs in the selected project directory.
|
|
106
|
+
Registry credentials: `STACK_OCI_USERNAME` / `STACK_OCI_PASSWORD`. External token-service origins
|
|
107
|
+
require explicit approval in `STACK_OCI_AUTH_REALMS`, a comma-separated list such as
|
|
108
|
+
`https://auth.docker.io`. Credentials and authorization headers are never forwarded to external upload
|
|
109
|
+
origins or authentication redirects. HTTPS cannot redirect authentication to HTTP. Plain HTTP
|
|
110
|
+
is used only for loopback registries, or elsewhere with exactly `STACK_OCI_PLAIN_HTTP=1`.
|
|
111
|
+
|
|
112
|
+
MCP execution is bounded on Unix: at most 64 KiB of each output stream is retained, and the
|
|
113
|
+
command's process group is terminated on timeout or completion. Detached children cannot keep
|
|
114
|
+
output collection waiting for EOF. Services and sessions are still tested end to end on Linux.
|
|
115
|
+
|
|
116
|
+
## Install
|
|
117
|
+
|
|
118
|
+
Once published, install the CLI with:
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
npm install -g @ushawarma/stack
|
|
122
|
+
stack --version
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Prebuilt binaries cover macOS 13+ and Linux with glibc 2.39+, on x64 and arm64.
|
|
126
|
+
Node.js 22.14+ is required. See [docs/RELEASING.md](docs/RELEASING.md) for CI checks,
|
|
127
|
+
trusted publishing setup, release tags, and recovery.
|
|
128
|
+
|
|
129
|
+
## Develop
|
|
130
|
+
|
|
131
|
+
```sh
|
|
132
|
+
cargo test # unit + integration tests (real git repos in temp dirs)
|
|
133
|
+
cargo run -- -C examples/app inspect
|
|
134
|
+
tests/e2e/run.sh # Docker: real mise + Pitchfork + OCI registry, all scenarios
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`eval/` holds the competitor evaluation (Flox, devbox, devenv, mise) that shaped this design:
|
|
138
|
+
[eval/REPORT.md](eval/REPORT.md).
|
package/bin/stack.cjs
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
const { spawn } = require('node:child_process');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
|
|
7
|
+
const platform = `${process.platform}-${process.arch}`;
|
|
8
|
+
const binary = path.join(__dirname, '..', 'binaries', platform, 'stack');
|
|
9
|
+
if (!['darwin-x64', 'darwin-arm64', 'linux-x64', 'linux-arm64'].includes(platform)) {
|
|
10
|
+
console.error(`stack: unsupported platform ${platform}. Build from source: https://github.com/usharma123/stack`);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
if (process.platform === 'linux' && !process.report.getReport().header.glibcVersionRuntime) {
|
|
14
|
+
console.error('stack: Linux requires glibc 2.39 or newer. Alpine/musl is not supported by this package.');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
if (!fs.existsSync(binary)) {
|
|
18
|
+
console.error(`stack: missing packaged binary for ${platform}. Reinstall @ushawarma/stack.`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const child = spawn(binary, process.argv.slice(2), { stdio: 'inherit' });
|
|
22
|
+
const signals = ['SIGINT', 'SIGTERM', 'SIGHUP'];
|
|
23
|
+
for (const signal of signals) process.on(signal, () => child.kill(signal));
|
|
24
|
+
child.on('error', error => {
|
|
25
|
+
console.error(`stack: cannot start ${platform} binary: ${error.message}`);
|
|
26
|
+
process.exitCode = 1;
|
|
27
|
+
});
|
|
28
|
+
child.on('exit', (code, signal) => {
|
|
29
|
+
if (signal) {
|
|
30
|
+
process.removeAllListeners(signal);
|
|
31
|
+
process.kill(process.pid, signal);
|
|
32
|
+
} else process.exitCode = code ?? 1;
|
|
33
|
+
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/build-info.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"commit": "9fb4def5ec41cfeab7f876730c2f3f298e51f72b",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"hashes": {
|
|
5
|
+
"linux-x64": "7fb9a80ab9f5b666c1f55694496ae9a814807f4f76264747da5b8ca325b0db5f",
|
|
6
|
+
"linux-arm64": "97553cc8008ef4bf40907903c5f881d580ab8a94d031c6296596bf99287b2edd",
|
|
7
|
+
"darwin-x64": "9922cd11a03a5fd7aef39a01864e2962cebce4ef1366763d1a54b35728f70914",
|
|
8
|
+
"darwin-arm64": "367c9fbac21523993a6ead949d8a22d070e40d31298ad93b1c49be722f1decd4"
|
|
9
|
+
}
|
|
10
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ushawarma/stack",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Reusable, agent-safe development stacks",
|
|
5
|
+
"repository": { "type": "git", "url": "git+https://github.com/usharma123/stack.git" },
|
|
6
|
+
"homepage": "https://github.com/usharma123/stack#readme",
|
|
7
|
+
"bin": { "stack": "bin/stack.cjs" },
|
|
8
|
+
"files": ["bin/stack.cjs", "binaries/", "build-info.json", "README.md"],
|
|
9
|
+
"engines": { "node": ">=22.14" },
|
|
10
|
+
"os": ["darwin", "linux"],
|
|
11
|
+
"cpu": ["x64", "arm64"],
|
|
12
|
+
"publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" }
|
|
13
|
+
}
|