hologit 0.44.0 → 0.46.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/.tool-versions +1 -0
- package/README.md +69 -286
- package/index.d.ts +31 -4
- package/lib/Lens.js +218 -31
- package/lib/Projection.js +2 -2
- package/lib/Studio.js +12 -3
- package/package.json +2 -2
package/.tool-versions
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
nodejs 20.19.3
|
package/README.md
CHANGED
|
@@ -1,328 +1,111 @@
|
|
|
1
1
|
# hologit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A Git-native framework for declarative code automation that makes it simple to combine code from multiple sources and apply transformations efficiently.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Overview
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- Advanced merge, filter, and sourcing strategies
|
|
9
|
-
- Apply arbitrary executable steps efficiently and consistently via Chef Habitat packages
|
|
10
|
-
- Content-based git-distributed caching of build steps
|
|
11
|
-
- GitHub Action for materializing holobranches to real branches
|
|
12
|
-
- `--watch` command to produce live updates (currently lazy/slow, theoretically can be made near-instant)
|
|
7
|
+
Hologit enables you to define virtual "holobranches" within your Git repository that can:
|
|
13
8
|
|
|
14
|
-
|
|
9
|
+
1. Mix together content from:
|
|
10
|
+
- The host branch
|
|
11
|
+
- Other repositories/branches
|
|
12
|
+
- Generated/transformed content
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
2. Apply transformations through "hololenses" using:
|
|
15
|
+
- Docker containers
|
|
16
|
+
- Chef Habitat packages
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
3. Project changes efficiently by:
|
|
19
|
+
- Computing new git trees in memory
|
|
20
|
+
- Caching results based on content
|
|
21
|
+
- ~~Watching for live updates~~ *Coming Soon*
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
## Key Concepts
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
### Holobranches
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
A holobranch is a virtual branch defined in `.holo/branches/` that specifies:
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
- What content to include from which sources
|
|
30
|
+
- How to transform that content through lenses
|
|
31
|
+
- Where the content should be placed in the output tree
|
|
27
32
|
|
|
28
|
-
|
|
33
|
+
Unlike regular Git branches, holobranches are computed on-demand and can mix content from multiple sources while maintaining clean history.
|
|
29
34
|
|
|
30
|
-
###
|
|
35
|
+
### Holosources
|
|
31
36
|
|
|
32
|
-
|
|
37
|
+
Sources let you pull in code from:
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
$ curl -s https://raw.githubusercontent.com/hologit/examples/basic/01-init-repo/index.html > index.html
|
|
39
|
-
$ git add index.html
|
|
40
|
-
$ git commit -m "Add Bootstrap's starter template as index.html"
|
|
41
|
-
[master (root-commit) 9fe77ec] Add Bootstrap's starter template as index.html
|
|
42
|
-
1 file changed, 22 insertions(+)
|
|
43
|
-
create mode 100644 index.html
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### Install hologit
|
|
47
|
-
|
|
48
|
-
See [docs/grand-tour/installation.md](docs/grand-tour/installation.md)
|
|
39
|
+
- Other repositories via Git submodules
|
|
40
|
+
- Other branches in the same repository
|
|
41
|
+
- Remote Git repositories
|
|
42
|
+
- Output from local or remote holobranches
|
|
49
43
|
|
|
50
|
-
|
|
44
|
+
Sources are configured in `.holo/sources/` and can be referenced by holobranches to include specific files or directories.
|
|
51
45
|
|
|
52
|
-
|
|
46
|
+
### Hololenses
|
|
53
47
|
|
|
54
|
-
|
|
55
|
-
$ git holo init
|
|
56
|
-
name=holo-example
|
|
57
|
-
initialized .holo/config.toml
|
|
58
|
-
$ cat .holo/config.toml
|
|
59
|
-
[holo]
|
|
60
|
-
name = "holo-example"
|
|
61
|
-
$ git commit -m "Initialize .holo/ configuration"
|
|
62
|
-
[master 881b0b6] Initialize .holo/ configuration
|
|
63
|
-
1 file changed, 2 insertions(+)
|
|
64
|
-
create mode 100644 .holo/config.toml
|
|
65
|
-
```
|
|
48
|
+
Lenses are transformations that can be applied to source content through:
|
|
66
49
|
|
|
67
|
-
|
|
50
|
+
- Docker containers that process input trees
|
|
51
|
+
- Habitat packages that provide build tools
|
|
68
52
|
|
|
69
|
-
|
|
53
|
+
Lenses are configured in `.holo/lenses/` and can be chained together to form complex build pipelines.
|
|
70
54
|
|
|
71
|
-
|
|
55
|
+
## Getting Started
|
|
72
56
|
|
|
73
|
-
|
|
74
|
-
$ git holo branch create --template=passthrough gh-pages
|
|
75
|
-
initialized .holo/branches/gh-pages/_holo-example.toml
|
|
76
|
-
$ cat .holo/branches/gh-pages/_holo-example.toml
|
|
77
|
-
[holomapping]
|
|
78
|
-
files = "**"
|
|
79
|
-
$ git commit -m "Initialize .holo/branches/gh-pages configuration"
|
|
80
|
-
[master 4b9aa68] Initialize .holo/branches/gh-pages configuration
|
|
81
|
-
1 file changed, 2 insertions(+)
|
|
82
|
-
create mode 100644 .holo/branches/gh-pages/_holo-example.toml
|
|
83
|
-
```
|
|
57
|
+
1. Initialize hologit configuration:
|
|
84
58
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
- The underscore prefixing the filename of`/_holo-example.toml` indicates that any files produced by the holomapping should be merged into the root directory of the projected holobranch.
|
|
88
|
-
- If the filename were just `/holo-example.toml`, a subdirectory name `/holo-example/` would be created to contain all the files produced by the holomapping.
|
|
89
|
-
- A holomapping config prefixed with an underscore could be named anything, all such holomappings at the same path will have their files merged to populate the directory.
|
|
90
|
-
- There are only two required configuration options for each holomapping:
|
|
91
|
-
- `holosource`: The name of a configured holosource referencing a repository to pull files from
|
|
92
|
-
- Ommitted in the generated holomapping config
|
|
93
|
-
- Defaults to the name of the file with the `.toml` extension and any `_` prefix stripped
|
|
94
|
-
- `files`: A string or array for strings containing [glob patterns](https://github.com/isaacs/minimatch) for matching or excluding files
|
|
95
|
-
- A value of just `'**'`, as in the generated config, matches all files in the source
|
|
96
|
-
|
|
97
|
-
### Project holobranch for first time
|
|
98
|
-
|
|
99
|
-
With a holobranch defined with at least one holomapping, we have enough for our first tree projection:
|
|
100
|
-
|
|
101
|
-
```console
|
|
102
|
-
$ git holo project gh-pages
|
|
103
|
-
info: reading mappings from holobranch: gitDir=/Users/chris/holo-example/.git, ref=HEAD, workTree=false, name=gh-pages
|
|
104
|
-
info: compositing tree...
|
|
105
|
-
info: merging holo-example:{**} -> /
|
|
106
|
-
info: stripping .holo/ tree from output tree...
|
|
107
|
-
info: writing final output tree...
|
|
108
|
-
info: projection ready:
|
|
109
|
-
ff954bb0a1e4878db424cb1033a0c356dac8d350
|
|
110
|
-
$ git cat-file -t ff954bb0a1e4878db424cb1033a0c356dac8d350
|
|
111
|
-
tree
|
|
112
|
-
$ git ls-tree -r ff954bb0a1e4878db424cb1033a0c356dac8d350
|
|
113
|
-
100644 blob 8092fa2adb4a9a395ac291fbdc9717b68be669aa index.html
|
|
59
|
+
```bash
|
|
60
|
+
git holo init
|
|
114
61
|
```
|
|
115
62
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
A tree can be used directly:
|
|
119
|
-
|
|
120
|
-
```console
|
|
121
|
-
$ git archive --format=zip $(git holo project gh-pages) > website.zip
|
|
122
|
-
info: reading mappings from holobranch: gitDir=/Users/chris/Repositories/holo-example/.git, ref=HEAD, workTree=false, name=gh-pages
|
|
123
|
-
info: compositing tree...
|
|
124
|
-
info: merging holo-example:{**} -> /
|
|
125
|
-
info: stripping .holo/ tree from output tree...
|
|
126
|
-
info: writing final output tree...
|
|
127
|
-
info: projection ready:
|
|
128
|
-
$ unzip -l website.zip
|
|
129
|
-
Archive: website.zip
|
|
130
|
-
Length Date Time Name
|
|
131
|
-
--------- ---------- ----- ----
|
|
132
|
-
1230 12-23-2018 20:32 index.html
|
|
133
|
-
--------- -------
|
|
134
|
-
1230 1 file
|
|
135
|
-
```
|
|
63
|
+
2. Create a holobranch:
|
|
136
64
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
```console
|
|
140
|
-
$ git commit-tree -m "Update gh-pages" $(git holo project gh-pages)
|
|
141
|
-
info: reading mappings from holobranch: gitDir=/Users/chris/Repositories/holo-example/.git, ref=HEAD, workTree=false, name=gh-pages
|
|
142
|
-
info: compositing tree...
|
|
143
|
-
info: merging holo-example:{**} -> /
|
|
144
|
-
info: stripping .holo/ tree from output tree...
|
|
145
|
-
info: writing final output tree...
|
|
146
|
-
info: projection ready:
|
|
147
|
-
846a551ce356d5fa4088e58b3ad0f0d05aa6d389
|
|
148
|
-
$ git cat-file -t 846a551ce356d5fa4088e58b3ad0f0d05aa6d389
|
|
149
|
-
commit
|
|
150
|
-
$ git cat-file -p 846a551ce356d5fa4088e58b3ad0f0d05aa6d389
|
|
151
|
-
tree ff954bb0a1e4878db424cb1033a0c356dac8d350
|
|
152
|
-
author Chris Alfano <chris@jarv.us> 1545615571 -0500
|
|
153
|
-
committer Chris Alfano <chris@jarv.us> 1545615571 -0500
|
|
154
|
-
|
|
155
|
-
Update gh-pages
|
|
65
|
+
```bash
|
|
66
|
+
git holo branch create my-branch
|
|
156
67
|
```
|
|
157
68
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
```console
|
|
161
|
-
$ git cat-file -p $(git holo project gh-pages --commit-branch=gh-pages)
|
|
162
|
-
info: reading mappings from holobranch: gitDir=/Users/chris/Repositories/holo-example/.git, ref=HEAD, workTree=false, name=gh-pages
|
|
163
|
-
info: compositing tree...
|
|
164
|
-
info: merging holo-example:{**} -> /
|
|
165
|
-
info: stripping .holo/ tree from output tree...
|
|
166
|
-
info: writing final output tree...
|
|
167
|
-
info: committed new tree to "gh-pages": 734f7dc034868af4e2bd23daf23e119faca1e0b8
|
|
168
|
-
info: projection ready:
|
|
169
|
-
tree ff954bb0a1e4878db424cb1033a0c356dac8d350
|
|
170
|
-
author Chris Alfano <chris@jarv.us> 1545616786 -0500
|
|
171
|
-
committer Chris Alfano <chris@jarv.us> 1545616786 -0500
|
|
172
|
-
|
|
173
|
-
Projected gh-pages from 4b9aa68
|
|
174
|
-
```
|
|
69
|
+
3. Add a source:
|
|
175
70
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
The first step to using external code in your projections is defining a holosource:
|
|
179
|
-
|
|
180
|
-
```console
|
|
181
|
-
$ git holo source create https://github.com/twbs/bootstrap --ref=v4.2.1
|
|
182
|
-
info: listing https://github.com/twbs/bootstrap#v4.2.1
|
|
183
|
-
info: fetching https://github.com/twbs/bootstrap#refs/tags/v4.2.1@9e4e94747bd698f4f61d48ed54c9c6d4d199bd32
|
|
184
|
-
fetched https://github.com/twbs/bootstrap#refs/tags/v4.2.1@9e4e94747bd698f4f61d48ed54c9c6d4d199bd32
|
|
185
|
-
initialized .holo/sources/bootstrap.toml
|
|
186
|
-
$ cat .holo/sources/bootstrap.toml
|
|
187
|
-
[holosource]
|
|
188
|
-
url = "https://github.com/twbs/bootstrap"
|
|
189
|
-
ref = "refs/tags/v4.2.1"
|
|
190
|
-
$ git commit -m "Initialize .holo/sources/bootstrap configuration"
|
|
191
|
-
[master 64ef9fc] Initialize .holo/sources/bootstrap configuration
|
|
192
|
-
1 file changed, 3 insertions(+)
|
|
193
|
-
create mode 100644 .holo/sources/bootstrap.toml
|
|
71
|
+
```bash
|
|
72
|
+
git holo source create https://github.com/example/repo
|
|
194
73
|
```
|
|
195
74
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
```console
|
|
199
|
-
$ mkdir .holo/branches/gh-pages/{js,css}
|
|
200
|
-
$ cat > .holo/branches/gh-pages/css/_bootstrap.toml <<- END_OF_TOML
|
|
201
|
-
[holomapping]
|
|
202
|
-
root = "dist/css"
|
|
203
|
-
files = "*.min.css"
|
|
204
|
-
END_OF_TOML
|
|
205
|
-
$ cat > .holo/branches/gh-pages/js/_bootstrap.toml <<- END_OF_TOML
|
|
206
|
-
[holomapping]
|
|
207
|
-
root = "dist/js"
|
|
208
|
-
files = "*.min.js"
|
|
209
|
-
END_OF_TOML
|
|
210
|
-
$ git add --all
|
|
211
|
-
$ git commit -am "Add css and js mappings for bootstrap to gh-pages holobranch"
|
|
212
|
-
[master 4180e45] Add css and js mappings for bootstrap to gh-pages holobranch
|
|
213
|
-
2 files changed, 6 insertions(+)
|
|
214
|
-
create mode 100644 .holo/branches/gh-pages/css/_bootstrap.toml
|
|
215
|
-
create mode 100644 .holo/branches/gh-pages/js/_bootstrap.toml
|
|
216
|
-
```
|
|
75
|
+
4. Project your holobranch:
|
|
217
76
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
```console
|
|
221
|
-
$ git ls-tree -r $(git holo project gh-pages)
|
|
222
|
-
info: reading mappings from holobranch: gitDir=/Users/chris/Repositories/holo-example/.git, ref=HEAD, workTree=false, name=gh-pages
|
|
223
|
-
info: compositing tree...
|
|
224
|
-
info: merging holo-example:{**} -> /
|
|
225
|
-
info: merging bootstrap:dist/css/{*.min.css} -> /css/
|
|
226
|
-
info: merging bootstrap:dist/js/{*.min.js} -> /js/
|
|
227
|
-
info: stripping .holo/ tree from output tree...
|
|
228
|
-
info: writing final output tree...
|
|
229
|
-
info: projection ready:
|
|
230
|
-
100644 blob b3e6881a586c99b55e2d1878839eede6fb3fa9d7 css/bootstrap-grid.min.css
|
|
231
|
-
100644 blob 0668a8cd93bba140c00bc0c410ad54c61af71d9e css/bootstrap-reboot.min.css
|
|
232
|
-
100644 blob e6b4977799e3a3a377e475ee765eb4a9961c6c71 css/bootstrap.min.css
|
|
233
|
-
100644 blob 8092fa2adb4a9a395ac291fbdc9717b68be669aa index.html
|
|
234
|
-
100644 blob 97f14c05c3d5960129caf3e4666f661dfdb8228a js/bootstrap.bundle.min.js
|
|
235
|
-
100644 blob 9df6b6c2ced14a60259171e1fdacc2534ddee183 js/bootstrap.min.js
|
|
77
|
+
```bash
|
|
78
|
+
git holo project my-branch
|
|
236
79
|
```
|
|
237
80
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
```console
|
|
241
|
-
$ tree .holo/branches/gh-pages
|
|
242
|
-
.holo/branches/gh-pages
|
|
243
|
-
├── _holo-example.toml
|
|
244
|
-
├── css
|
|
245
|
-
│ └── _bootstrap.toml
|
|
246
|
-
└── js
|
|
247
|
-
└── _bootstrap.toml
|
|
248
|
-
```
|
|
81
|
+
See the [Installation Guide](docs/grand-tour/installation.md) and [Grand Tour](docs/grand-tour/README.md) for detailed setup and usage instructions.
|
|
249
82
|
|
|
250
|
-
|
|
83
|
+
## Key Features
|
|
251
84
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
85
|
+
- **Git-native**: Works directly with Git's object database for maximum efficiency
|
|
86
|
+
- **Content-based caching**: Automatically caches build results based on input content, optionally sharing with other users and CI/CD via the same Git server hosting your project
|
|
87
|
+
- **Declarative configuration**: Define complex automation workflows in TOML files
|
|
88
|
+
- ~~**Live updates**: Watch mode for continuous projection of changes~~ *Coming Soon*
|
|
89
|
+
- **GitHub Action**: Materialize holobranches to real branches in CI/CD
|
|
90
|
+
- **Flexible transformations**: Use any build tool through containers or packages
|
|
256
91
|
|
|
257
|
-
|
|
92
|
+
## Use Cases
|
|
258
93
|
|
|
259
|
-
-
|
|
94
|
+
- **Monorepo Management**: Combine code from multiple repositories while maintaining clean history
|
|
95
|
+
- **Build Automation**: Create efficient, reproducible build pipelines
|
|
96
|
+
- **Documentation**: Generate and publish documentation from multiple sources
|
|
97
|
+
- **Deployment**: Prepare deployment artifacts with consistent transformations
|
|
98
|
+
- **Code Generation**: Automate code generation and transformation workflows
|
|
260
99
|
|
|
261
|
-
|
|
100
|
+
## Documentation
|
|
262
101
|
|
|
263
|
-
|
|
102
|
+
- [Installation Guide](docs/grand-tour/installation.md)
|
|
103
|
+
- [Repository Setup](docs/grand-tour/repository-setup.md)
|
|
104
|
+
- [Working with Sources](docs/workflows/work-on-sources.md)
|
|
105
|
+
- [Holobranches Guide](docs/grand-tour/holobranches.md)
|
|
106
|
+
- [Hololenses Guide](docs/grand-tour/hololenses.md)
|
|
107
|
+
- [Holoreactors Guide](docs/grand-tour/holoreactors.md)
|
|
264
108
|
|
|
265
|
-
|
|
266
|
-
$ git holo source checkout --all
|
|
267
|
-
checked out .holo/sources/bootstrap from https://github.com/twbs/bootstrap#refs/tags/v4.2.1@9e4e9474
|
|
268
|
-
$ git commit -m "Initialize .holo/sources/bootstrap submodule"
|
|
269
|
-
[basic/05-checkout-holosource ee39b88] Initialize .holo/sources/bootstrap submodule
|
|
270
|
-
2 files changed, 5 insertions(+)
|
|
271
|
-
create mode 100644 .gitmodules
|
|
272
|
-
create mode 160000 .holo/sources/bootstrap
|
|
273
|
-
```
|
|
109
|
+
## License
|
|
274
110
|
|
|
275
|
-
|
|
276
|
-
- Commit gitlink outside submodule, change all projections
|
|
277
|
-
|
|
278
|
-
### Make use of a projected tree
|
|
279
|
-
|
|
280
|
-
- Archive tree-ish
|
|
281
|
-
- Write to a real branch
|
|
282
|
-
- Push to github gh-pages
|
|
283
|
-
|
|
284
|
-
## Advanced Usage
|
|
285
|
-
|
|
286
|
-
### Overlay a project
|
|
287
|
-
|
|
288
|
-
### Build new holo lenses
|
|
289
|
-
|
|
290
|
-
## Roadmap
|
|
291
|
-
|
|
292
|
-
- [~] (in progress) Complete getting started tour, break into sections in another doc format
|
|
293
|
-
- [ ] Develop shorter quickstart
|
|
294
|
-
- [~] `* --ref` (in progress) option to use a specific ref instead of HEAD
|
|
295
|
-
- [~] `* ---no-working` (in progress) option to ignore working directory and only use ref
|
|
296
|
-
- [ ] `project --watch` option to keep running and automatically update projection with changes to input
|
|
297
|
-
- [ ] `project --audit` option to produce audit commits chain
|
|
298
|
-
- [ ] Implement `holoreactor` objects: defined like lenses within the projected branches, the handle piping result subtrees into single-run or persistent processes running in the studio via `hab exec` or `hab svc load`
|
|
299
|
-
- [ ] Implement a `holoreactor` for serving static websites
|
|
300
|
-
- [ ] Implement a `holoreactor` for running/restarting a node app
|
|
301
|
-
- [ ] Implement a `holoreactor` for running an emergence app locally or on a remote cluster
|
|
302
|
-
- [ ] Expose from the studio's HTTP interface a *virtual* holospace that can be mutated via git push or WebDAV
|
|
303
|
-
- [ ] Add option to `[holosource]` config to override submodule checkout path
|
|
304
|
-
- [ ] Leverage lower-overhead chroot environments instead of Docker containers on Linux systems
|
|
305
|
-
- [ ] Enable running and connecting to a persistent background studio for quick on-demand projection
|
|
306
|
-
- Build a studio docker image that extends Habitat's studio image with hologit pre-installed
|
|
307
|
-
- Wrap around opening interactive shells
|
|
308
|
-
- Wrap around entrypoint to start holostudio process
|
|
309
|
-
- [ ] Implement in-memory tree hashing to avoid calls to `git mktree` for tree hashes that are known in the tree cache to already exist in the repo
|
|
310
|
-
- [ ] Visual Studio Code extension
|
|
311
|
-
- Top-level hologit section with views of sources and branches
|
|
312
|
-
- Commands via context menu and command palette
|
|
313
|
-
- Ability to graphically toggle watch mode for each source
|
|
314
|
-
- Open holobranches in workspace via filesystem provider for read-only browsing of either composited or lensed content
|
|
315
|
-
- Enable writing to mounted holobranches by routing writes to estimated source via reverse-compositing, checking out submodules on-the-fly
|
|
316
|
-
- [ ] Isolate lens environments further from source
|
|
317
|
-
- Currently, `.git` directory is mounted into lensing environment, so working tree is safe but repo is exposed to damage by lens code
|
|
318
|
-
- Lensing only needs to exchange object hashes, so objects tree could be mounted read-write and the rest could be empty
|
|
319
|
-
- More robust options might include using git's fetch/push mechanism and/or mounting objects tree as read-only alternates
|
|
320
|
-
- A git SmartHTTP server could be exposed via the studio socket, but could objects be exchanged as efficiently as just bind-mounting the objects database directly?
|
|
321
|
-
- [ ] Enable running web-based code editor as a holoreactor to your code base
|
|
322
|
-
|
|
323
|
-
## Reference
|
|
324
|
-
|
|
325
|
-
## TODO
|
|
326
|
-
|
|
327
|
-
- [ ] Have `project` fetch and read source HEAD if no submodule commit is found
|
|
328
|
-
- [ ] Refactor `source add` and `source fetch` to use common code, leave things in same state
|
|
111
|
+
This project is [free and open source](https://www.fsf.org/about/what-is-free-software) software.
|
package/index.d.ts
CHANGED
|
@@ -4,9 +4,12 @@ declare module 'git-client' {
|
|
|
4
4
|
}
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
declare module '@iarna/toml' {
|
|
8
|
+
export function parse(content: string): any;
|
|
9
|
+
}
|
|
10
|
+
|
|
7
11
|
declare module 'hologit' {
|
|
8
12
|
import { Git as GitClient } from 'git-client';
|
|
9
|
-
import { Docker } from 'dockerode';
|
|
10
13
|
|
|
11
14
|
export interface GitOptions {
|
|
12
15
|
gitDir: string;
|
|
@@ -65,6 +68,30 @@ declare module 'hologit' {
|
|
|
65
68
|
cacheTo?: string | null;
|
|
66
69
|
}
|
|
67
70
|
|
|
71
|
+
export interface DockerExecOptions {
|
|
72
|
+
$relayStderr?: boolean;
|
|
73
|
+
$relayStdout?: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface StudioContainer {
|
|
77
|
+
id?: string;
|
|
78
|
+
type?: 'studio';
|
|
79
|
+
env?: { [key: string]: string };
|
|
80
|
+
defaultUser?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface HoloSpec {
|
|
84
|
+
holospec: {
|
|
85
|
+
lens: {
|
|
86
|
+
input: string;
|
|
87
|
+
container?: string;
|
|
88
|
+
package?: string;
|
|
89
|
+
command?: string;
|
|
90
|
+
[key: string]: any;
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
68
95
|
export class Git {
|
|
69
96
|
static get(): Promise<typeof GitClient>;
|
|
70
97
|
constructor(options: GitOptions);
|
|
@@ -297,13 +324,13 @@ declare module 'hologit' {
|
|
|
297
324
|
export class Studio {
|
|
298
325
|
static cleanup(): Promise<void>;
|
|
299
326
|
static getHab(): Promise<any>;
|
|
300
|
-
static getDocker(): Promise<Docker>;
|
|
301
327
|
static isEnvironmentStudio(): Promise<boolean>;
|
|
302
328
|
static get(gitDir: string): Promise<Studio>;
|
|
329
|
+
static execDocker(args: string[], options?: DockerExecOptions): Promise<string>;
|
|
303
330
|
|
|
304
|
-
constructor(options: { gitDir: string; container:
|
|
331
|
+
constructor(options: { gitDir: string; container: StudioContainer });
|
|
305
332
|
|
|
306
|
-
container:
|
|
333
|
+
container: StudioContainer;
|
|
307
334
|
gitDir: string;
|
|
308
335
|
|
|
309
336
|
isLocal(): boolean;
|
package/lib/Lens.js
CHANGED
|
@@ -44,12 +44,10 @@ class Lens extends Configurable {
|
|
|
44
44
|
const config = await super.getConfig();
|
|
45
45
|
|
|
46
46
|
// process lens configuration
|
|
47
|
-
if (
|
|
48
|
-
|
|
47
|
+
if (config.package) {
|
|
48
|
+
config.command = config.command || 'lens-tree {{ input }}';
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
config.command = config.command || 'lens-tree {{ input }}';
|
|
52
|
-
|
|
53
51
|
if (config.before) {
|
|
54
52
|
config.before =
|
|
55
53
|
typeof config.before == 'string'
|
|
@@ -103,7 +101,62 @@ class Lens extends Configurable {
|
|
|
103
101
|
async buildSpec (inputTree) {
|
|
104
102
|
const config = await this.getCachedConfig();
|
|
105
103
|
|
|
104
|
+
if (config.container) {
|
|
105
|
+
return this.buildSpecForContainer(inputTree, config);
|
|
106
|
+
} else if (config.package) {
|
|
107
|
+
return this.buildSpecForHabitatPackage(inputTree, config);
|
|
108
|
+
} else {
|
|
109
|
+
throw new Error(`hololens has no package or container defined: ${this.name}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async buildSpecForContainer (inputTree, config) {
|
|
114
|
+
const { container: containerQuery } = config;
|
|
115
|
+
|
|
116
|
+
// check if image exists locally first
|
|
117
|
+
let imageHash;
|
|
118
|
+
try {
|
|
119
|
+
const inspectOutput = await Studio.execDocker(['inspect', containerQuery]);
|
|
120
|
+
const imageInfo = JSON.parse(inspectOutput)[0];
|
|
121
|
+
imageHash = imageInfo.Id;
|
|
122
|
+
logger.info(`found local image: ${containerQuery}@${imageHash}`);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
// image doesn't exist locally or can't be inspected, try pulling
|
|
125
|
+
logger.info(`pulling image: ${containerQuery}`);
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
await Studio.execDocker(['pull', containerQuery], { $relayStdout: true });
|
|
129
|
+
const inspectOutput = await Studio.execDocker(['inspect', containerQuery]);
|
|
130
|
+
const imageInfo = JSON.parse(inspectOutput)[0];
|
|
131
|
+
imageHash = imageInfo.Id;
|
|
132
|
+
} catch (err) {
|
|
133
|
+
throw new Error(`failed to pull container image ${containerQuery}: ${err.message}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!imageHash) {
|
|
138
|
+
throw new Error(`failed to get hash for container image ${containerQuery}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// build spec
|
|
142
|
+
const data = {
|
|
143
|
+
...config,
|
|
144
|
+
container: `${containerQuery.replace(/:.*$/, '')}@${imageHash}`,
|
|
145
|
+
input: await inputTree.write(),
|
|
146
|
+
output: null,
|
|
147
|
+
before: null,
|
|
148
|
+
after: null
|
|
149
|
+
};
|
|
106
150
|
|
|
151
|
+
// write spec and return packet
|
|
152
|
+
return {
|
|
153
|
+
...await SpecObject.write(this.workspace.getRepo(), 'lens', data),
|
|
154
|
+
data,
|
|
155
|
+
type: 'container'
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async buildSpecForHabitatPackage (inputTree, config) {
|
|
107
160
|
// determine current package version
|
|
108
161
|
const { package: packageQuery } = config;
|
|
109
162
|
const [pkgOrigin, pkgName, pkgVersion, pkgBuild] = packageQuery.split('/');
|
|
@@ -163,14 +216,6 @@ class Lens extends Configurable {
|
|
|
163
216
|
}
|
|
164
217
|
|
|
165
218
|
|
|
166
|
-
// old studio method that might be useful as fallback/debug option
|
|
167
|
-
// const setupOutput = await studio.exec('hab', 'pkg', 'install', 'core/hab-plan-build');
|
|
168
|
-
// const originOutput = await studio.exec('hab', 'origin', 'key', 'generate', 'holo');
|
|
169
|
-
// const buildOutput = await studio.habPkgExec('core/hab-plan-build', 'hab-plan-build', '/src/lenses/compass');
|
|
170
|
-
// const studio = await Studio.get(this.workspace.getRepo().gitDir);
|
|
171
|
-
// let packageIdent = await studio.getPackage(packageQuery);
|
|
172
|
-
|
|
173
|
-
|
|
174
219
|
// build spec
|
|
175
220
|
const data = {
|
|
176
221
|
...config,
|
|
@@ -185,21 +230,21 @@ class Lens extends Configurable {
|
|
|
185
230
|
// write spec and return packet
|
|
186
231
|
return {
|
|
187
232
|
...await SpecObject.write(this.workspace.getRepo(), 'lens', data),
|
|
188
|
-
data
|
|
233
|
+
data,
|
|
234
|
+
type: 'habitat'
|
|
189
235
|
};
|
|
190
236
|
}
|
|
191
237
|
|
|
192
|
-
async executeSpec (specHash, options) {
|
|
193
|
-
return Lens.executeSpec(specHash, {...options, repo: this.workspace.getRepo()});
|
|
238
|
+
async executeSpec (specType, specHash, options) {
|
|
239
|
+
return Lens.executeSpec(specType, specHash, {...options, repo: this.workspace.getRepo()});
|
|
194
240
|
}
|
|
195
241
|
|
|
196
|
-
static async executeSpec (
|
|
242
|
+
static async executeSpec (specType, specHash, options) {
|
|
243
|
+
const { refresh=false, cacheFrom=null, cacheTo=null, save=true } = options;
|
|
197
244
|
|
|
198
|
-
// load holorepo
|
|
199
|
-
if (!repo) {
|
|
200
|
-
repo = await Repo.getFromEnvironment();
|
|
201
|
-
}
|
|
202
245
|
|
|
246
|
+
// load holorepo
|
|
247
|
+
const repo = options.repo || await Repo.getFromEnvironment();
|
|
203
248
|
const git = await repo.getGit();
|
|
204
249
|
|
|
205
250
|
|
|
@@ -228,9 +273,161 @@ class Lens extends Configurable {
|
|
|
228
273
|
}
|
|
229
274
|
|
|
230
275
|
|
|
231
|
-
//
|
|
276
|
+
// execute lens in container or with habitat package:
|
|
277
|
+
let lensedTreeHash;
|
|
278
|
+
if (specType == 'container') {
|
|
279
|
+
lensedTreeHash = await Lens.executeSpecForContainer(repo, specHash);
|
|
280
|
+
} else if (specType == 'habitat') {
|
|
281
|
+
lensedTreeHash = await Lens.executeSpecForHabitatPackage(repo, specHash);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// save ref to accelerate next projection
|
|
285
|
+
if (save) {
|
|
286
|
+
await git.updateRef(specRef, lensedTreeHash);
|
|
287
|
+
|
|
288
|
+
if (cacheTo) {
|
|
289
|
+
await _cacheResultTo(repo, specRef, cacheTo);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return lensedTreeHash;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
static async executeSpecForContainer (repo, specHash) {
|
|
297
|
+
const git = await repo.getGit();
|
|
298
|
+
|
|
299
|
+
// read and parse spec file
|
|
300
|
+
const specToml = await git.catFile({ p: true }, specHash);
|
|
301
|
+
const {
|
|
302
|
+
holospec: {
|
|
303
|
+
lens: spec
|
|
304
|
+
}
|
|
305
|
+
} = TOML.parse(specToml);
|
|
306
|
+
|
|
307
|
+
// write commit with input tree and spec content
|
|
308
|
+
const commitHash = await git.commitTree(spec.input, {
|
|
309
|
+
p: [],
|
|
310
|
+
m: specToml
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// extract repository and hash from container string
|
|
314
|
+
const containerMatch = spec.container.match(/^.+@sha256:([a-f0-9]{64})$/);
|
|
315
|
+
if (!containerMatch) {
|
|
316
|
+
throw new Error(`Invalid container format: ${spec.container}`);
|
|
317
|
+
}
|
|
318
|
+
const [, sha256Hash] = containerMatch;
|
|
319
|
+
|
|
320
|
+
// create and start container
|
|
321
|
+
const persistentDebugContainer = process.env.HOLO_DEBUG_PERSIST_CONTAINER;
|
|
322
|
+
let containerId;
|
|
323
|
+
try {
|
|
324
|
+
if (persistentDebugContainer) {
|
|
325
|
+
try {
|
|
326
|
+
const containerInfo = await Studio.execDocker(['inspect', persistentDebugContainer]);
|
|
327
|
+
const containerState = JSON.parse(containerInfo)[0].State;
|
|
328
|
+
|
|
329
|
+
if (containerState.Running) {
|
|
330
|
+
logger.info(`Found running debug container: ${persistentDebugContainer}`);
|
|
331
|
+
containerId = persistentDebugContainer;
|
|
332
|
+
}
|
|
333
|
+
} catch (error) {
|
|
334
|
+
containerId = null;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// create container
|
|
339
|
+
if (!containerId) {
|
|
340
|
+
containerId = await Studio.execDocker([
|
|
341
|
+
'create',
|
|
342
|
+
'-p', '9000:9000',
|
|
343
|
+
...(persistentDebugContainer ? ['--name', persistentDebugContainer] : []),
|
|
344
|
+
...(process.env.DEBUG ? ['-e', 'DEBUG=1'] : []),
|
|
345
|
+
sha256Hash
|
|
346
|
+
]);
|
|
347
|
+
containerId = containerId.trim();
|
|
348
|
+
|
|
349
|
+
logger.info('starting container');
|
|
350
|
+
await Studio.execDocker(['start', containerId]);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// wait for port 9000 to be available
|
|
354
|
+
let attempts = 0;
|
|
355
|
+
const maxAttempts = 30;
|
|
356
|
+
const waitTime = 1000; // 1 second
|
|
357
|
+
|
|
358
|
+
while (attempts < maxAttempts) {
|
|
359
|
+
try {
|
|
360
|
+
const containerInfo = await Studio.execDocker(['inspect', containerId]);
|
|
361
|
+
const containerState = JSON.parse(containerInfo)[0].State;
|
|
362
|
+
|
|
363
|
+
if (containerState.Running) {
|
|
364
|
+
// check if port 9000 is listening
|
|
365
|
+
try {
|
|
366
|
+
await Studio.execDocker([
|
|
367
|
+
'exec',
|
|
368
|
+
containerId,
|
|
369
|
+
'nc',
|
|
370
|
+
'-z',
|
|
371
|
+
'localhost',
|
|
372
|
+
'9000'
|
|
373
|
+
]);
|
|
374
|
+
break;
|
|
375
|
+
} catch (err) {
|
|
376
|
+
// ignore error and continue waiting
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
} catch (err) {
|
|
380
|
+
// ignore error and continue waiting
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
384
|
+
attempts++;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (attempts >= maxAttempts) {
|
|
388
|
+
throw new Error('Timeout waiting for git server to be ready');
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// push commit to git server
|
|
392
|
+
logger.info(`pushing and executing job: ${commitHash}`);
|
|
393
|
+
await git.push(`http://localhost:9000/`, `${commitHash}:refs/heads/lens-input`, {
|
|
394
|
+
force: true,
|
|
395
|
+
$wait: true,
|
|
396
|
+
$onStderr: (line) => process.stderr.write(`\x1b[90m${line}\x1b[0m\n`)
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// fetch and verify output commit
|
|
400
|
+
const outputRef = `refs/lens-jobs/${specHash}`;
|
|
401
|
+
logger.info('fetching result');
|
|
402
|
+
await git.fetch('http://localhost:9000/', `+refs/heads/lens-input:${outputRef}`);
|
|
403
|
+
|
|
404
|
+
// verify the output commit's parent matches our input commit
|
|
405
|
+
const outputParent = await git.revParse(`${outputRef}^`);
|
|
406
|
+
if (outputParent !== commitHash) {
|
|
407
|
+
throw new Error(`Output commit parent ${outputParent} does not match input commit ${commitHash}`);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return await git.getTreeHash(outputRef);
|
|
411
|
+
|
|
412
|
+
} finally {
|
|
413
|
+
// cleanup
|
|
414
|
+
if (containerId && !persistentDebugContainer) {
|
|
415
|
+
try {
|
|
416
|
+
await Studio.execDocker(['stop', containerId]);
|
|
417
|
+
await Studio.execDocker(['rm', containerId]);
|
|
418
|
+
} catch (err) {
|
|
419
|
+
logger.warn(`Failed to cleanup container: ${err.message}`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
static async executeSpecForHabitatPackage (repo, specHash) {
|
|
426
|
+
const git = await repo.getGit();
|
|
427
|
+
|
|
232
428
|
let lensedTreeHash;
|
|
233
429
|
|
|
430
|
+
// ensure the rest runs inside a studio environment
|
|
234
431
|
if (!await Studio.isEnvironmentStudio()) {
|
|
235
432
|
const studio = await Studio.get(repo.gitDir);
|
|
236
433
|
lensedTreeHash = await studio.holoLensExec(specHash);
|
|
@@ -307,16 +504,6 @@ class Lens extends Configurable {
|
|
|
307
504
|
}
|
|
308
505
|
|
|
309
506
|
|
|
310
|
-
// save ref to accelerate next projection
|
|
311
|
-
if (save) {
|
|
312
|
-
await git.updateRef(specRef, lensedTreeHash);
|
|
313
|
-
|
|
314
|
-
if (cacheTo) {
|
|
315
|
-
await _cacheResultTo(repo, specRef, cacheTo);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
|
|
320
507
|
// return tree hash
|
|
321
508
|
return lensedTreeHash;
|
|
322
509
|
}
|
package/lib/Projection.js
CHANGED
|
@@ -162,11 +162,11 @@ class Projection {
|
|
|
162
162
|
|
|
163
163
|
// build tree of matching files to input to lens
|
|
164
164
|
logger.info(`building input tree for lens ${lens.name} from ${inputRoot == '.' ? '' : (path.join(inputRoot, '.')+'/')}{${inputFiles}}`);
|
|
165
|
-
const { hash: specHash } = await lens.buildSpec(await lens.buildInputTree(this.output.root));
|
|
165
|
+
const { hash: specHash, type: specType } = await lens.buildSpec(await lens.buildInputTree(this.output.root));
|
|
166
166
|
|
|
167
167
|
|
|
168
168
|
// check for existing output tree
|
|
169
|
-
const outputTreeHash = await lens.executeSpec(specHash, { cacheFrom, cacheTo });
|
|
169
|
+
const outputTreeHash = await lens.executeSpec(specType, specHash, { cacheFrom, cacheTo });
|
|
170
170
|
|
|
171
171
|
|
|
172
172
|
// verify output
|
package/lib/Studio.js
CHANGED
|
@@ -15,17 +15,17 @@ let hab;
|
|
|
15
15
|
* @param {Object} options - Options for child_process.spawn.
|
|
16
16
|
* @returns {Promise<string>} - Resolves with stdout data.
|
|
17
17
|
*/
|
|
18
|
-
function execDocker(args, options = {
|
|
18
|
+
function execDocker(args, options = { }) {
|
|
19
19
|
logger.debug(`docker ${args.join(' ')}`);
|
|
20
20
|
|
|
21
21
|
return new Promise((resolve, reject) => {
|
|
22
22
|
const dockerProcess = spawn('docker', args, { stdio: 'pipe', ...options });
|
|
23
23
|
|
|
24
|
-
if (options.$relayStderr
|
|
24
|
+
if (options.$relayStderr) {
|
|
25
25
|
dockerProcess.stderr.pipe(process.stderr);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
if (options.$relayStdout
|
|
28
|
+
if (options.$relayStdout) {
|
|
29
29
|
dockerProcess.stdout.pipe(process.stdout);
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -92,6 +92,15 @@ class Studio {
|
|
|
92
92
|
return Boolean(process.env.STUDIO_TYPE);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Execute a Docker CLI command.
|
|
97
|
+
* @param {Array<string>} args - The arguments to pass to the docker command.
|
|
98
|
+
* @param {Object} options - Options for child_process.spawn.
|
|
99
|
+
* @returns {Promise<string>} - Resolves with stdout data.
|
|
100
|
+
*/
|
|
101
|
+
static execDocker(args, options = {}) {
|
|
102
|
+
return execDocker(args, options);
|
|
103
|
+
}
|
|
95
104
|
|
|
96
105
|
static async get (gitDir) {
|
|
97
106
|
const cachedStudio = studioCache.get(gitDir);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hologit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.0",
|
|
4
4
|
"description": "Hologit automates the projection of layered composite file trees based on flat, declarative plans",
|
|
5
5
|
"repository": "https://github.com/EmergencePlatform/hologit",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"chokidar": "^4.0.1",
|
|
16
16
|
"debounce": "^2.0.0",
|
|
17
17
|
"fb-watchman": "^2.0.1",
|
|
18
|
-
"git-client": "^1.
|
|
18
|
+
"git-client": "^1.9.4",
|
|
19
19
|
"hab-client": "^1.1.3",
|
|
20
20
|
"handlebars": "^4.7.6",
|
|
21
21
|
"minimatch": "^10.0.1",
|