pray-cli 1.6.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +47 -0
- data/LICENSE.md +21 -0
- data/README.md +125 -0
- data/SECURITY.md +28 -0
- data/bin/polyrun +108 -0
- data/bin/pray +26 -0
- data/lib/pray/archive.rb +68 -0
- data/lib/pray/auth_client.rb +97 -0
- data/lib/pray/cli/commands/auth.rb +42 -0
- data/lib/pray/cli/commands/distribution.rb +17 -0
- data/lib/pray/cli/commands/init.rb +62 -0
- data/lib/pray/cli/commands/meta.rb +15 -0
- data/lib/pray/cli/commands/packages.rb +107 -0
- data/lib/pray/cli/commands/trust.rb +73 -0
- data/lib/pray/cli/commands/workflow.rb +126 -0
- data/lib/pray/cli/help.rb +168 -0
- data/lib/pray/cli/helpers.rb +166 -0
- data/lib/pray/cli/parse.rb +136 -0
- data/lib/pray/cli/parse_auth.rb +91 -0
- data/lib/pray/cli/parse_trust.rb +152 -0
- data/lib/pray/cli/suggest.rb +57 -0
- data/lib/pray/cli.rb +117 -0
- data/lib/pray/confess.rb +113 -0
- data/lib/pray/config.rb +52 -0
- data/lib/pray/constraint.rb +80 -0
- data/lib/pray/destination.rb +158 -0
- data/lib/pray/dotenv.rb +46 -0
- data/lib/pray/environment.rb +41 -0
- data/lib/pray/error.rb +55 -0
- data/lib/pray/format_manifest.rb +255 -0
- data/lib/pray/format_serialize.rb +135 -0
- data/lib/pray/git_sources.rb +228 -0
- data/lib/pray/hashing.rb +59 -0
- data/lib/pray/invocation.rb +110 -0
- data/lib/pray/literal.rb +382 -0
- data/lib/pray/lockfile.rb +259 -0
- data/lib/pray/lockfile_serialize.rb +125 -0
- data/lib/pray/manifest.rb +126 -0
- data/lib/pray/manifest_formatter.rb +56 -0
- data/lib/pray/manifest_json.rb +128 -0
- data/lib/pray/manifest_parser.rb +152 -0
- data/lib/pray/manifest_parser_blocks.rb +216 -0
- data/lib/pray/manifest_parser_helpers.rb +208 -0
- data/lib/pray/materialize.rb +120 -0
- data/lib/pray/package_spec.rb +245 -0
- data/lib/pray/path_safety.rb +41 -0
- data/lib/pray/plan.rb +83 -0
- data/lib/pray/project_context.rb +67 -0
- data/lib/pray/publish.rb +162 -0
- data/lib/pray/registry.rb +355 -0
- data/lib/pray/render.rb +360 -0
- data/lib/pray/resolve.rb +396 -0
- data/lib/pray/resolve_context.rb +19 -0
- data/lib/pray/serve.rb +130 -0
- data/lib/pray/serve_federation.rb +109 -0
- data/lib/pray/session.rb +90 -0
- data/lib/pray/ssh_agent.rb +115 -0
- data/lib/pray/statement_surface.rb +262 -0
- data/lib/pray/substitute.rb +50 -0
- data/lib/pray/sync.rb +219 -0
- data/lib/pray/terminal.rb +30 -0
- data/lib/pray/trust.rb +201 -0
- data/lib/pray/trust_feed.rb +110 -0
- data/lib/pray/trust_ops.rb +202 -0
- data/lib/pray/verify.rb +257 -0
- data/lib/pray/version.rb +6 -0
- data/lib/pray-cli.rb +4 -0
- data/lib/pray.rb +46 -0
- data/pray-cli.gemspec +48 -0
- metadata +187 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e9a9ce52fe79c346f5ea5bf9516fb6ad813da69028fcf27c4fe166946fec77fc
|
|
4
|
+
data.tar.gz: 39062dc10eee030334edda7c10a9a71749a93c887cf2483f218ab7a9ceefcb68
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 8097c2dab4ec198b55d8fc8803ed602f82fa6afef6096602b539f25b501b597da0bcd53108de519422b2216b624d0060e21020cb7abf531e700eaaa5a83eeac5
|
|
7
|
+
data.tar.gz: f8bde377e7a44de1aeec8591c92e4f478e2758386067585ac17c07afbb0af71946c37dbd7f6a58b4035ced2e6141f32ea2cd20372f0eafe195c1075ea43d3a37
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# CHANGELOG
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 1.6.0 (2026-07-29)
|
|
6
|
+
|
|
7
|
+
- Run tests with polyrun parallel RSpec, coverage gate, Makefile lint/test, and RBS validate.
|
|
8
|
+
- Implement HTTP `login` (passkey and ssh-agent), `confess`, and `sync`.
|
|
9
|
+
- Implement full `trust` CLI against `trust.toml` (list/show/add-key/remove-key/set-*/import-*/check).
|
|
10
|
+
- Serve federation discovery, sync index/package, and confession submit over HTTP.
|
|
11
|
+
|
|
12
|
+
## 1.5.2 (2026-07-29)
|
|
13
|
+
|
|
14
|
+
- Put the Prayfile positioning line into the gem summary for RubyGems search.
|
|
15
|
+
|
|
16
|
+
## 1.5.1 (2026-07-29)
|
|
17
|
+
|
|
18
|
+
- Add `rake build` / `rake release` for gem packaging and RubyGems push.
|
|
19
|
+
- Rework gem summary, description, and README opener to match Prayfile positioning.
|
|
20
|
+
|
|
21
|
+
## 1.5.0 (2026-07-27)
|
|
22
|
+
|
|
23
|
+
- Add `((pray:symbol))` templating with project-wide `pray do` / `template do` symbol maps.
|
|
24
|
+
- Substitute symbols in rendered fragments, local embeds, and UTF-8 provisioned files.
|
|
25
|
+
- Accept Gemfile-like Ruby surface sugar: `{…}` blocks, top-level `;`, and optional call parentheses.
|
|
26
|
+
- Accept `pray` / `use` / `include` / `package` as package declaration aliases of `agent`.
|
|
27
|
+
- Parse and apply `compose`, `tree`, and `file:` destinations with role-based export selection and scoped render/provision.
|
|
28
|
+
- Exercise the shared `testdata/shared` Prayfile corpus alongside Rust and TypeScript.
|
|
29
|
+
- Rewrite Prayfile with `pray format` (`fmt`) to the recommended compose/tree/file destination DSL.
|
|
30
|
+
|
|
31
|
+
## 1.1.0 (2026-07-14)
|
|
32
|
+
|
|
33
|
+
- Add environment-aware rendering with `group` blocks and `--env` or `PRAY_ENV`.
|
|
34
|
+
- Add global `--path` and `--file-path` flags with `PRAY_PATH`, `PRAY_FILE_PATH`, and project `.env` support.
|
|
35
|
+
- Record the selected environment in `Prayfile.lock`.
|
|
36
|
+
- Improve CLI help with grouped commands, per-command help, and suggestions for unknown commands.
|
|
37
|
+
- Add `--no-input` to skip interactive prompts.
|
|
38
|
+
- Honor `PRAY_NO_COLOR` and `NO_COLOR` for plain terminal output.
|
|
39
|
+
- Refresh git distribution caches on install when a locked revision is missing locally.
|
|
40
|
+
|
|
41
|
+
## 1.0.0 (2026-07-13)
|
|
42
|
+
|
|
43
|
+
- Initial RubyGems release of `pray-cli`.
|
|
44
|
+
- Resolve local path packages and git distribution sources.
|
|
45
|
+
- Publish to local distribution roots and serve over HTTP.
|
|
46
|
+
- Install, update, render, verify, and drift workflows with `Prayfile.lock`.
|
|
47
|
+
- Git distribution integration tests for install, locked revision, and update.
|
data/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 pray contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# pray-cli
|
|
2
|
+
|
|
3
|
+
Ruby `pray` CLI and library for [Prayfile](https://pray.kisko.dev): a package manager for the language placed before inference.
|
|
4
|
+
|
|
5
|
+
Resolve, lock, render, verify, and publish shared instructions, policies, templates, and related input as versioned packages. Consumes path, git, and registry distribution points. The installed executable is `pray`.
|
|
6
|
+
|
|
7
|
+
**Website:** [pray.kisko.dev](https://pray.kisko.dev)
|
|
8
|
+
|
|
9
|
+
**Maintainer:** Andrei Makarov ([contact@kiskolabs.com](mailto:contact@kiskolabs.com))
|
|
10
|
+
|
|
11
|
+
**Repository:** [kiskolabs/pray](https://github.com/kiskolabs/pray)
|
|
12
|
+
|
|
13
|
+
**Community docs:** [CHANGELOG.md](CHANGELOG.md) · [LICENSE.md](LICENSE.md) · [SECURITY.md](SECURITY.md)
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
gem install pray-cli
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
From this repository:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
cd rubygems/pray-cli
|
|
25
|
+
bundle install
|
|
26
|
+
bundle exec pray version
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Commands
|
|
30
|
+
|
|
31
|
+
Core workflow:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pray init
|
|
35
|
+
pray install
|
|
36
|
+
pray plan
|
|
37
|
+
pray verify
|
|
38
|
+
pray drift
|
|
39
|
+
pray manifest
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Package management:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pray add sample/base "~> 1.0" --path packages/base
|
|
46
|
+
pray remove sample/base
|
|
47
|
+
pray update
|
|
48
|
+
pray unlock sample/base
|
|
49
|
+
pray list
|
|
50
|
+
pray tree
|
|
51
|
+
pray explain sample/base
|
|
52
|
+
pray outdated
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Package authoring:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pray prayer init
|
|
59
|
+
pray package
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Auth, trust, and federation:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pray login --server URL --email EMAIL --passkey-key PATH --credential-id ID
|
|
66
|
+
pray confess sample/base --accepted
|
|
67
|
+
pray sync --root PATH --peer URL
|
|
68
|
+
pray trust list
|
|
69
|
+
pray trust show
|
|
70
|
+
pray trust add-key KEY --match-prefix PREFIX
|
|
71
|
+
pray trust check [SOURCE]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Maintenance:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pray render
|
|
78
|
+
pray format
|
|
79
|
+
pray clean
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Deferred in this release (clear unsupported errors): `pray_ssh` sources, `serve --stdio`, confess/sync over `pray+ssh`, trust `--host-key` for `pray+ssh`.
|
|
83
|
+
|
|
84
|
+
Git sources clone into `.pray/cache/git`, discover distribution roots at `v1/packages` or `prayers/v1/packages`, and resolve packages through registry metadata.
|
|
85
|
+
|
|
86
|
+
Local publish writes `v1/index.json`, package metadata, and `.praypkg` artifacts. `serve` exposes the distribution tree over HTTP, including federation discovery and sync endpoints.
|
|
87
|
+
|
|
88
|
+
## Layout
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
lib/pray/
|
|
92
|
+
literal.rb Prayfile and prayspec literal parser
|
|
93
|
+
manifest.rb Prayfile parser and model
|
|
94
|
+
package_spec.rb prayspec parser
|
|
95
|
+
lockfile.rb Prayfile.lock read/write
|
|
96
|
+
resolve.rb dependency resolution
|
|
97
|
+
render.rb target rendering and skill materialization
|
|
98
|
+
verify.rb verify and drift checks
|
|
99
|
+
registry.rb HTTP and local registry resolution
|
|
100
|
+
archive.rb .praypkg archive read/write (requires tar and zstd)
|
|
101
|
+
cli.rb command dispatch
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Tests
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
make lint
|
|
108
|
+
make test
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Coverage-gated parallel run:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
POLYRUN_COVERAGE=1 bundle exec polyrun parallel-rspec --workers 5 --merge-failures
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
From the repository root:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
make ruby-test
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Compatibility
|
|
124
|
+
|
|
125
|
+
Manifest hashing, lockfile fields, pray markers, and tree hashes match the Rust reference for path-based projects. Registry resolution uses HTTP or a local distribution tree with `v1/packages/*.json` metadata.
|
data/SECURITY.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# SECURITY
|
|
2
|
+
|
|
3
|
+
## Reporting a Vulnerability
|
|
4
|
+
|
|
5
|
+
Do NOT open a public GitHub issue for security vulnerabilities.
|
|
6
|
+
|
|
7
|
+
Email security details to: security@kiskolabs.com
|
|
8
|
+
|
|
9
|
+
Include: description, steps to reproduce, potential impact, and suggested fix (if available).
|
|
10
|
+
|
|
11
|
+
### Response Timeline
|
|
12
|
+
|
|
13
|
+
- We will acknowledge receipt of your report
|
|
14
|
+
- We will provide an initial assessment
|
|
15
|
+
- We will keep you informed of our progress and resolution timeline
|
|
16
|
+
|
|
17
|
+
### Disclosure Policy
|
|
18
|
+
|
|
19
|
+
- We will work with you to understand and resolve the issue
|
|
20
|
+
- We will credit you for the discovery (unless you prefer to remain anonymous)
|
|
21
|
+
- We will publish a security advisory after the vulnerability is patched
|
|
22
|
+
- We will coordinate public disclosure with you
|
|
23
|
+
|
|
24
|
+
## Automation Security
|
|
25
|
+
|
|
26
|
+
* Context Isolation: It is strictly forbidden to include production credentials, API keys, or Personally Identifiable Information (PII) in prompts sent to third-party LLMs or automation services.
|
|
27
|
+
|
|
28
|
+
* Supply Chain: All automated dependencies must be verified.
|
data/bin/polyrun
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Delegates to `bundle exec polyrun` with repo config.
|
|
3
|
+
# - No args → same as `polyrun` with no subcommand (auto parallel RSpec from polyrun.yml / spec/**/*_spec.rb).
|
|
4
|
+
# - Path-like argv (spec files, globs, dirs) → polyrun implicit parallel (path-only argv).
|
|
5
|
+
# - Named subcommands (plan, config, parallel-rspec, ci-shard-rspec, …) → pass through.
|
|
6
|
+
# - Otherwise (RSpec flags first, e.g. --format) → `start --workers N -- bundle exec rspec "$@"`.
|
|
7
|
+
# Globals: -c/--config, -v/--verbose, -h/--help, --help (same as bundle exec polyrun).
|
|
8
|
+
# Workers: POLYRUN_WORKERS (default 5, max 10, min 1) — read by polyrun run-shards/start.
|
|
9
|
+
set -eo pipefail
|
|
10
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
11
|
+
cd "$ROOT"
|
|
12
|
+
|
|
13
|
+
W="${POLYRUN_WORKERS:-5}"
|
|
14
|
+
case "$W" in ''|*[!0-9]*) W=5 ;; esac
|
|
15
|
+
if [ "$W" -gt 10 ]; then W=10; fi
|
|
16
|
+
if [ "$W" -lt 1 ]; then W=1; fi
|
|
17
|
+
|
|
18
|
+
CONFIG="${POLYRUN_CONFIG:-polyrun.yml}"
|
|
19
|
+
polyrun_prefix=()
|
|
20
|
+
have_config=0
|
|
21
|
+
|
|
22
|
+
while [ $# -gt 0 ]; do
|
|
23
|
+
case "$1" in
|
|
24
|
+
-c|--config)
|
|
25
|
+
polyrun_prefix+=("$1" "$2")
|
|
26
|
+
CONFIG="$2"
|
|
27
|
+
have_config=1
|
|
28
|
+
shift 2
|
|
29
|
+
;;
|
|
30
|
+
-v|--verbose)
|
|
31
|
+
polyrun_prefix+=("$1")
|
|
32
|
+
shift
|
|
33
|
+
;;
|
|
34
|
+
-h|--help)
|
|
35
|
+
if [ "$have_config" -eq 0 ]; then
|
|
36
|
+
exec bundle exec polyrun "${polyrun_prefix[@]}" -c "$CONFIG" -h
|
|
37
|
+
else
|
|
38
|
+
exec bundle exec polyrun "${polyrun_prefix[@]}" -h
|
|
39
|
+
fi
|
|
40
|
+
;;
|
|
41
|
+
*)
|
|
42
|
+
break
|
|
43
|
+
;;
|
|
44
|
+
esac
|
|
45
|
+
done
|
|
46
|
+
|
|
47
|
+
if [ "${1:-}" = "--version" ]; then
|
|
48
|
+
if [ "$have_config" -eq 0 ]; then
|
|
49
|
+
exec bundle exec polyrun "${polyrun_prefix[@]}" -c "$CONFIG" version
|
|
50
|
+
else
|
|
51
|
+
exec bundle exec polyrun "${polyrun_prefix[@]}" version
|
|
52
|
+
fi
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
is_polyrun_command() {
|
|
56
|
+
case "$1" in
|
|
57
|
+
plan|prepare|merge-coverage|report-coverage|report-junit|report-timing|env|config|merge-timing|\
|
|
58
|
+
db:setup-template|db:setup-shard|db:clone-shards|run-shards|parallel-rspec|start|build-paths|\
|
|
59
|
+
ci-shard-run|ci-shard-rspec|init|queue|quick|help|version)
|
|
60
|
+
return 0 ;;
|
|
61
|
+
*)
|
|
62
|
+
return 1 ;;
|
|
63
|
+
esac
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# True if argv should use polyrun implicit path parallel (any path-like token).
|
|
67
|
+
implicit_path_in_argv() {
|
|
68
|
+
for a in "$@"; do
|
|
69
|
+
case "$a" in
|
|
70
|
+
-*) [ "$a" = "-" ] || continue ;;
|
|
71
|
+
esac
|
|
72
|
+
case "$a" in
|
|
73
|
+
*.rb|spec/*|test/*|./|/*|*[\*\?[]*) return 0 ;;
|
|
74
|
+
esac
|
|
75
|
+
[ -e "$a" ] && return 0
|
|
76
|
+
done
|
|
77
|
+
return 1
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
run_polyrun() {
|
|
81
|
+
if [ "$have_config" -eq 0 ]; then
|
|
82
|
+
exec bundle exec polyrun "${polyrun_prefix[@]}" -c "$CONFIG" "$@"
|
|
83
|
+
else
|
|
84
|
+
exec bundle exec polyrun "${polyrun_prefix[@]}" "$@"
|
|
85
|
+
fi
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
exec_parallel_rspec() {
|
|
89
|
+
if [ "$have_config" -eq 0 ]; then
|
|
90
|
+
exec bundle exec polyrun "${polyrun_prefix[@]}" -c "$CONFIG" start --workers "$W" -- bundle exec rspec "$@"
|
|
91
|
+
else
|
|
92
|
+
exec bundle exec polyrun "${polyrun_prefix[@]}" start --workers "$W" -- bundle exec rspec "$@"
|
|
93
|
+
fi
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if [ $# -eq 0 ]; then
|
|
97
|
+
run_polyrun
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
if is_polyrun_command "$1"; then
|
|
101
|
+
run_polyrun "$@"
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
if implicit_path_in_argv "$@"; then
|
|
105
|
+
run_polyrun "$@"
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
exec_parallel_rspec "$@"
|
data/bin/pray
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
Encoding.default_external = Encoding::UTF_8
|
|
5
|
+
Encoding.default_internal = Encoding::UTF_8
|
|
6
|
+
|
|
7
|
+
gemfile = File.expand_path("../Gemfile", __dir__)
|
|
8
|
+
if File.exist?(gemfile)
|
|
9
|
+
ENV["BUNDLE_GEMFILE"] ||= gemfile
|
|
10
|
+
require "bundler/setup"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
require_relative "../lib/pray-cli"
|
|
14
|
+
|
|
15
|
+
exit_code = 0
|
|
16
|
+
begin
|
|
17
|
+
Pray::CLI.run(ARGV)
|
|
18
|
+
rescue Pray::Error => error
|
|
19
|
+
warn error
|
|
20
|
+
exit_code = error.exit_code
|
|
21
|
+
rescue => error
|
|
22
|
+
warn error
|
|
23
|
+
exit_code = 1
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
exit exit_code
|
data/lib/pray/archive.rb
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "open3"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
require "tempfile"
|
|
7
|
+
|
|
8
|
+
module Pray
|
|
9
|
+
module Archive
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def build_package_archive_bytes(package)
|
|
13
|
+
prayspec_path = Resolve.find_prayspec_file(package.root)
|
|
14
|
+
prayspec_name = File.basename(prayspec_path)
|
|
15
|
+
metadata = package_metadata_json(package)
|
|
16
|
+
|
|
17
|
+
Dir.mktmpdir("pray-package-") do |staging|
|
|
18
|
+
File.write(File.join(staging, "metadata.json"), metadata)
|
|
19
|
+
File.write(File.join(staging, prayspec_name), File.binread(prayspec_path))
|
|
20
|
+
package.spec.files.each do |file|
|
|
21
|
+
destination = File.join(staging, file)
|
|
22
|
+
FileUtils.mkdir_p(File.dirname(destination))
|
|
23
|
+
File.binwrite(destination, File.binread(File.join(package.root, file)))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
tar_bytes, status = Open3.capture2("tar", "-cf", "-", "-C", staging, ".")
|
|
27
|
+
raise Error.integrity("failed to build package tar archive") unless status.success?
|
|
28
|
+
|
|
29
|
+
zstd_bytes, status = Open3.capture2("zstd", "-q", "-c", stdin_data: tar_bytes)
|
|
30
|
+
unless status.success?
|
|
31
|
+
raise Error.unsupported("zstd is required to build package archives")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
zstd_bytes
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def write_package_archive(package, output_path)
|
|
39
|
+
FileUtils.mkdir_p(File.dirname(output_path))
|
|
40
|
+
File.binwrite(output_path, build_package_archive_bytes(package))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def unpack_praypkg(artifact_bytes, output_directory)
|
|
44
|
+
FileUtils.mkdir_p(output_directory)
|
|
45
|
+
tar_bytes, status = Open3.capture2("zstd", "-d", "-q", "-c", stdin_data: artifact_bytes)
|
|
46
|
+
unless status.success?
|
|
47
|
+
raise Error.unsupported("zstd is required to unpack package archives")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
_stdout, _stderr, status = Open3.capture3("tar", "-xf", "-", "-C", output_directory, stdin_data: tar_bytes)
|
|
51
|
+
raise Error.integrity("failed to unpack package archive") unless status.success?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def package_archive_path(package_name, version)
|
|
55
|
+
slug = package_name.tr("/", "-")
|
|
56
|
+
File.join(".pray", "packages", "#{slug}-#{version}.praypkg")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def package_metadata_json(package)
|
|
60
|
+
JSON.generate(
|
|
61
|
+
"name" => package.spec.name,
|
|
62
|
+
"version" => package.spec.version,
|
|
63
|
+
"tree_hash" => package.tree_hash,
|
|
64
|
+
"exports" => package.selected_exports
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "base64"
|
|
5
|
+
require "openssl"
|
|
6
|
+
require_relative "session"
|
|
7
|
+
require_relative "ssh_agent"
|
|
8
|
+
|
|
9
|
+
module Pray
|
|
10
|
+
module AuthClient
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def login_with_passkey(server_url, credential_id, private_key_path, session_root, email:)
|
|
14
|
+
challenge = post_json(
|
|
15
|
+
"#{trim_slash(server_url)}/v1/auth/passkeys/challenge",
|
|
16
|
+
{"credential_id" => credential_id}
|
|
17
|
+
)
|
|
18
|
+
seed = File.binread(private_key_path)
|
|
19
|
+
unless seed.bytesize == 32
|
|
20
|
+
raise Error.unsupported("passkey private key must be 32 raw bytes")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
signature = Base64.strict_encode64(sign_ed25519(seed, challenge["challenge"].to_s))
|
|
24
|
+
response = post_json(
|
|
25
|
+
"#{trim_slash(server_url)}/v1/auth/passkeys/login",
|
|
26
|
+
{
|
|
27
|
+
"credential_id" => credential_id,
|
|
28
|
+
"challenge_id" => challenge["challenge_id"],
|
|
29
|
+
"signature" => signature
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
persist_login(server_url, response, email, "passkey", nil, session_root)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def login_with_ssh_agent(server_url, public_key_path, session_root, email:)
|
|
36
|
+
public_key = File.read(public_key_path).strip
|
|
37
|
+
challenge = post_json(
|
|
38
|
+
"#{trim_slash(server_url)}/v1/auth/ssh-keys/challenge",
|
|
39
|
+
{"public_key" => public_key}
|
|
40
|
+
)
|
|
41
|
+
signature = SshAgent.sign(public_key, challenge["challenge"].to_s)
|
|
42
|
+
response = post_json(
|
|
43
|
+
"#{trim_slash(server_url)}/v1/auth/ssh-keys/login",
|
|
44
|
+
{
|
|
45
|
+
"public_key" => public_key,
|
|
46
|
+
"challenge_id" => challenge["challenge_id"],
|
|
47
|
+
"signature" => signature
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
persist_login(
|
|
51
|
+
server_url, response, email, "ssh_key", challenge["fingerprint"], session_root
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def persist_login(server_url, response, email, kind, fingerprint, session_root)
|
|
56
|
+
response_email = response["email"].to_s
|
|
57
|
+
unless response_email == email
|
|
58
|
+
raise Error.resolution(
|
|
59
|
+
"login email mismatch: expected #{email}, got #{response_email}"
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
Session.persist(
|
|
64
|
+
session_root,
|
|
65
|
+
SessionFile.new(
|
|
66
|
+
server_url: server_url,
|
|
67
|
+
email: response_email,
|
|
68
|
+
token: response["token"].to_s,
|
|
69
|
+
kind: kind,
|
|
70
|
+
signer_fingerprint: fingerprint
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def sign_ed25519(seed, message)
|
|
76
|
+
unless OpenSSL::PKey.respond_to?(:generate_key)
|
|
77
|
+
raise Error.unsupported("OpenSSL Ed25519 support is required for passkey login")
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
key = OpenSSL::PKey.new_raw_private_key("ED25519", seed)
|
|
81
|
+
key.sign(nil, message)
|
|
82
|
+
rescue OpenSSL::PKey::PKeyError => error
|
|
83
|
+
raise Error.unsupported("OpenSSL Ed25519 signing failed: #{error.message}")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def post_json(url, body)
|
|
87
|
+
response = Registry.http_post(url, "application/json", JSON.generate(body))
|
|
88
|
+
JSON.parse(response)
|
|
89
|
+
rescue JSON::ParserError => error
|
|
90
|
+
raise Error.parse("auth response", error.message)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def trim_slash(value)
|
|
94
|
+
value.to_s.sub(%r{/+\z}, "")
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module CLI
|
|
5
|
+
def login_command(servers:, email:, mode:, passkey_key: nil, credential_id: nil, public_key: nil)
|
|
6
|
+
session_root = Dir.pwd
|
|
7
|
+
servers.each do |server_url|
|
|
8
|
+
session = case mode
|
|
9
|
+
when :passkey
|
|
10
|
+
AuthClient.login_with_passkey(
|
|
11
|
+
server_url, credential_id, passkey_key, session_root, email: email
|
|
12
|
+
)
|
|
13
|
+
when :ssh_agent
|
|
14
|
+
AuthClient.login_with_ssh_agent(
|
|
15
|
+
server_url, public_key, session_root, email: email
|
|
16
|
+
)
|
|
17
|
+
else
|
|
18
|
+
raise Error.unsupported("unknown login mode: #{mode}")
|
|
19
|
+
end
|
|
20
|
+
puts "logged in as #{session.email} via #{session.kind} on #{server_url}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def confess_command(package:, from_lock:, version:, accepted:, rejected:, note:, url:)
|
|
25
|
+
Confess.submit(
|
|
26
|
+
package: package, from_lock: from_lock, version: version,
|
|
27
|
+
accepted: accepted, rejected: rejected, note: note, url: url
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def sync_command(root:, peers:)
|
|
32
|
+
peer_sources = if peers.empty?
|
|
33
|
+
Sync.load_sync_peers(root).map { |peer| peer["url"] }
|
|
34
|
+
else
|
|
35
|
+
peers
|
|
36
|
+
end
|
|
37
|
+
summary = Sync.synchronize_registry(root, peer_sources)
|
|
38
|
+
puts "Synchronized #{summary[:packages]} package(s) from #{summary[:peers]} peer(s); " \
|
|
39
|
+
"learned #{summary[:known_peers]} peer(s)"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module CLI
|
|
5
|
+
def publish_command(roots:, servers:)
|
|
6
|
+
project = resolve_current_project
|
|
7
|
+
roots.each { |root| Publish.publish_to_root(project, root) }
|
|
8
|
+
servers.each { |server| Publish.publish_to_server(project, server) }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def serve_command(root:, host:, port:, stdio:)
|
|
12
|
+
raise Error.unsupported("serve --stdio is not implemented yet in pray-cli Ruby") if stdio
|
|
13
|
+
|
|
14
|
+
Serve.run_server(root: root, host: host, port: port)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module Pray
|
|
7
|
+
module CLI
|
|
8
|
+
def manifest_command
|
|
9
|
+
manifest = Pray.parse_manifest(Pray.read_manifest_text(manifest_path))
|
|
10
|
+
puts JSON.pretty_generate(ManifestJson.manifest_fields(manifest.canonicalized))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def init_command(targets)
|
|
14
|
+
path = manifest_path
|
|
15
|
+
raise Error.manifest("Prayfile already exists") if File.exist?(path)
|
|
16
|
+
|
|
17
|
+
targets = ["tool_a"] if targets.empty?
|
|
18
|
+
lines = ['prayfile "1"']
|
|
19
|
+
targets.each do |target|
|
|
20
|
+
output = default_output_for_target(target)
|
|
21
|
+
lines << "target :#{target} do"
|
|
22
|
+
lines << " output \"#{output}.md\""
|
|
23
|
+
lines << "end"
|
|
24
|
+
end
|
|
25
|
+
File.write(path, "#{lines.join("\n")}\n")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def prayer_init_command
|
|
29
|
+
root = Dir.pwd
|
|
30
|
+
package_name = File.basename(root)
|
|
31
|
+
package_name = "prayer-package" if package_name.strip.empty?
|
|
32
|
+
prayspec_path = File.join(root, "#{package_name}.prayspec")
|
|
33
|
+
raise Error.manifest("package spec already exists: #{prayspec_path}") if File.exist?(prayspec_path)
|
|
34
|
+
|
|
35
|
+
File.write(
|
|
36
|
+
prayspec_path,
|
|
37
|
+
<<~SPEC
|
|
38
|
+
Package::Specification.new do |spec|
|
|
39
|
+
spec.name = "#{package_name}"
|
|
40
|
+
spec.version = "0.1.0"
|
|
41
|
+
spec.summary = "Prayer package"
|
|
42
|
+
spec.files = []
|
|
43
|
+
end
|
|
44
|
+
SPEC
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def repo_init_command
|
|
49
|
+
distribution_root = repo_distribution_root(Dir.pwd)
|
|
50
|
+
index_path = File.join(distribution_root, "v1", "index.json")
|
|
51
|
+
raise Error.manifest("distribution repo already exists") if File.exist?(index_path)
|
|
52
|
+
|
|
53
|
+
FileUtils.mkdir_p(File.join(distribution_root, "v1", "packages"))
|
|
54
|
+
FileUtils.mkdir_p(File.join(distribution_root, "v1", "artifacts"))
|
|
55
|
+
Publish.write_registry_index(distribution_root, RegistryIndex.new)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def repo_distribution_root(root)
|
|
59
|
+
(File.basename(root) == "prayers") ? root : File.join(root, "prayers")
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module CLI
|
|
5
|
+
def version_command
|
|
6
|
+
puts "pray #{Pray::VERSION}"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def clean_command
|
|
10
|
+
remove_path_if_exists(".pray/cache")
|
|
11
|
+
remove_path_if_exists(".pray/vendor")
|
|
12
|
+
remove_path_if_exists(".pray/state.json")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|