octo-dev 0.2.2
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/LICENSE +21 -0
- package/README.md +270 -0
- package/package.json +62 -0
- package/scripts/install.sh +117 -0
- package/src/build/adapters/docker-build-engine.adapter.ts +39 -0
- package/src/build/affected-detector.ts +126 -0
- package/src/build/build-orchestrator.ts +169 -0
- package/src/build/build-scheduler.ts +174 -0
- package/src/build/ports/build-engine.port.ts +38 -0
- package/src/cli/build.command.ts +101 -0
- package/src/cli/bump.command.ts +98 -0
- package/src/cli/down.command.ts +36 -0
- package/src/cli/graph.command.ts +40 -0
- package/src/cli/index.ts +80 -0
- package/src/cli/init.command.ts +106 -0
- package/src/cli/status.command.ts +46 -0
- package/src/cli/up.command.ts +52 -0
- package/src/graph/aggregated-graph.ts +77 -0
- package/src/graph/build-graph.ts +125 -0
- package/src/graph/dependency-graph.ts +82 -0
- package/src/graph/index.ts +4 -0
- package/src/graph/topological-sort.ts +104 -0
- package/src/hooks/hook-runner.ts +57 -0
- package/src/infra/compose-aggregator.ts +152 -0
- package/src/infra/compose-smart-merger.ts +93 -0
- package/src/infra/infra-manager.ts +157 -0
- package/src/manifest/manifest-discovery.ts +144 -0
- package/src/manifest/manifest-parser.ts +109 -0
- package/src/manifest/manifest-printer.ts +75 -0
- package/src/manifest/manifest-schema.ts +34 -0
- package/src/shared/errors.ts +43 -0
- package/src/shared/logger.ts +14 -0
- package/src/shared/process-runner.ts +47 -0
- package/src/shared/shutdown.ts +36 -0
- package/src/version/changelog-generator.ts +112 -0
- package/src/version/version-bumper.ts +116 -0
- package/src/version/version-propagator.ts +120 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 congeant
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://img.shields.io/badge/node-%3E%3D25-brightgreen" alt="Node.js >= 25" />
|
|
3
|
+
<img src="https://img.shields.io/badge/typescript-5.8-blue" alt="TypeScript" />
|
|
4
|
+
<img src="https://img.shields.io/badge/license-MIT-green" alt="License" />
|
|
5
|
+
<img src="https://img.shields.io/badge/pnpm-11-orange" alt="pnpm" />
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<h1 align="center">🐙 Octo</h1>
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<strong>Build orchestration, semantic versioning, and local infrastructure management for monorepos.</strong>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<code>octo build</code> · <code>octo bump</code> · <code>octo up</code> · <code>octo down</code> · <code>octo status</code>
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Why Octo?
|
|
21
|
+
|
|
22
|
+
Managing a monorepo with multiple microservices and shared packages means dealing with:
|
|
23
|
+
|
|
24
|
+
- **Manual build ordering** — services depend on shared packages that must be built first
|
|
25
|
+
- **Version drift** — bumping a shared SDK requires updating every consumer by hand
|
|
26
|
+
- **Infrastructure sprawl** — each service has its own `docker-compose.yml` with overlapping containers
|
|
27
|
+
|
|
28
|
+
Octo solves all three with a single CLI that understands your dependency graph.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Install globally
|
|
36
|
+
pnpm add -g octo-monorepo
|
|
37
|
+
|
|
38
|
+
# Initialize in your monorepo
|
|
39
|
+
octo init
|
|
40
|
+
|
|
41
|
+
# Build everything in dependency order
|
|
42
|
+
octo build
|
|
43
|
+
|
|
44
|
+
# Bump a shared package and propagate
|
|
45
|
+
octo bump @myorg/shared-lib minor --install
|
|
46
|
+
|
|
47
|
+
# Spin up all infrastructure
|
|
48
|
+
octo up
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Requirements
|
|
54
|
+
|
|
55
|
+
| Tool | Version |
|
|
56
|
+
|------|---------|
|
|
57
|
+
| Node.js | >= 25 |
|
|
58
|
+
| Docker | Latest |
|
|
59
|
+
| Git | >= 2.x |
|
|
60
|
+
| pnpm | >= 11 |
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Commands
|
|
65
|
+
|
|
66
|
+
### `octo init`
|
|
67
|
+
|
|
68
|
+
Scans the monorepo, discovers services (directories with `Dockerfile`) and packages, and generates `octo.yaml`.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
octo init # Recursive scan, generates root manifest
|
|
72
|
+
octo init --standalone # Current directory only
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
### `octo graph`
|
|
78
|
+
|
|
79
|
+
Displays the dependency graph as an indented adjacency list.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
$ octo graph
|
|
83
|
+
|
|
84
|
+
api-gateway
|
|
85
|
+
@myorg/shared-lib
|
|
86
|
+
@myorg/config
|
|
87
|
+
user-service
|
|
88
|
+
@myorg/shared-lib
|
|
89
|
+
@myorg/shared-lib
|
|
90
|
+
@myorg/config
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
### `octo build`
|
|
96
|
+
|
|
97
|
+
Orchestrates Docker builds respecting topological order with maximum parallelism.
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
octo build # Build all services
|
|
101
|
+
octo build api-gateway # Build api-gateway + modified dependencies
|
|
102
|
+
octo build --affected # Build only changed services since last build
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Features:**
|
|
106
|
+
- Parallel execution limited by available CPUs
|
|
107
|
+
- Failure propagation — if a dependency fails, dependents are cancelled
|
|
108
|
+
- Independent services continue building
|
|
109
|
+
- Real-time progress reporting (updated every 1s)
|
|
110
|
+
- Affected detection via file mtime comparison
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
### `octo bump`
|
|
115
|
+
|
|
116
|
+
Increments a package version following [Semantic Versioning 2.0.0](https://semver.org/).
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
octo bump @myorg/shared-lib # patch (default)
|
|
120
|
+
octo bump @myorg/shared-lib minor # minor
|
|
121
|
+
octo bump @myorg/shared-lib major # major
|
|
122
|
+
octo bump @myorg/shared-lib --install # also runs pnpm install in consumers
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Pipeline:**
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
pre-bump hooks → version increment → build verification → changelog → git commit → propagation
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Safety guarantees:**
|
|
132
|
+
- Uncommitted changes trigger interactive confirmation
|
|
133
|
+
- Build failure triggers automatic rollback (byte-for-byte)
|
|
134
|
+
- Incompatible version ranges are skipped with conflict report
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### `octo up`
|
|
139
|
+
|
|
140
|
+
Merges all `docker-compose.yml` files and starts infrastructure containers.
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
octo up # All infrastructure
|
|
144
|
+
octo up user-service # Only user-service's dependencies
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Features:**
|
|
148
|
+
- Smart merge via local LLM (Phi-4/Ollama) for deduplication
|
|
149
|
+
- Deterministic fallback when LLM is unavailable
|
|
150
|
+
- Healthcheck polling (60s timeout per container)
|
|
151
|
+
- Automatic log tail on healthcheck failure
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
### `octo down`
|
|
156
|
+
|
|
157
|
+
Stops and removes infrastructure containers.
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
octo down # Stop containers, preserve volumes
|
|
161
|
+
octo down --volumes # Also remove persistent volumes
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
### `octo status`
|
|
167
|
+
|
|
168
|
+
Displays container state in tabular format.
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
$ octo status
|
|
172
|
+
|
|
173
|
+
NAME IMAGE STATE PORT
|
|
174
|
+
my-postgres postgres:16 running 5432:5432
|
|
175
|
+
my-redis redis:7-alpine running 6379:6379
|
|
176
|
+
my-nats nats:2.10 running 4222:4222
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Configuration
|
|
182
|
+
|
|
183
|
+
### `octo.yaml`
|
|
184
|
+
|
|
185
|
+
```yaml
|
|
186
|
+
# Pre-validation hooks (run before build/bump)
|
|
187
|
+
hooks:
|
|
188
|
+
pre-build:
|
|
189
|
+
- name: lint
|
|
190
|
+
command: pnpm run lint
|
|
191
|
+
- name: type-check
|
|
192
|
+
command: pnpm run type-check
|
|
193
|
+
pre-bump:
|
|
194
|
+
- name: lint
|
|
195
|
+
command: pnpm run lint
|
|
196
|
+
|
|
197
|
+
# Services — directories with Dockerfile
|
|
198
|
+
services:
|
|
199
|
+
- api-gateway
|
|
200
|
+
- user-service
|
|
201
|
+
- billing-service
|
|
202
|
+
|
|
203
|
+
# Shared packages — libraries consumed by services
|
|
204
|
+
packages:
|
|
205
|
+
- "@myorg/shared-lib"
|
|
206
|
+
- "@myorg/config"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Path Resolution
|
|
210
|
+
|
|
211
|
+
By default, Octo resolves paths automatically by searching for a directory whose `package.json` `name` field matches the entry. To override:
|
|
212
|
+
|
|
213
|
+
```yaml
|
|
214
|
+
services:
|
|
215
|
+
- api-gateway:
|
|
216
|
+
path: ./custom/gateway-dir
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Dependency Detection
|
|
220
|
+
|
|
221
|
+
Dependencies are resolved automatically from `package.json` fields (`dependencies` + `devDependencies`). Only internal packages (those declared in the manifest) create graph edges. External npm packages are ignored.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Operating Modes
|
|
226
|
+
|
|
227
|
+
| Mode | Trigger | Behavior |
|
|
228
|
+
|------|---------|----------|
|
|
229
|
+
| **Standalone** | Single `octo.yaml` in CWD | Operates on local manifest only |
|
|
230
|
+
| **Aggregated** | Multiple `octo.yaml` in subdirectories | Discovers all manifests, builds unified dependency graph with cross-project resolution |
|
|
231
|
+
|
|
232
|
+
In aggregated mode, if a root `octo.yaml` exists alongside sub-manifests, the root takes priority.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Graceful Shutdown
|
|
237
|
+
|
|
238
|
+
Octo handles `SIGINT` (Ctrl+C) and `SIGTERM` gracefully:
|
|
239
|
+
|
|
240
|
+
1. Cancels builds in progress
|
|
241
|
+
2. Reports partial state (completed / in-progress / pending)
|
|
242
|
+
3. Exits with code 130
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Error Handling
|
|
247
|
+
|
|
248
|
+
All errors follow a consistent format:
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
[ERRO] <category>: <descriptive message>
|
|
252
|
+
→ <additional context (path, line, column)>
|
|
253
|
+
→ <suggested fix when applicable>
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
| Category | Behavior | Exit Code |
|
|
257
|
+
|----------|----------|-----------|
|
|
258
|
+
| Configuration error | Reports all problems at once | 1 |
|
|
259
|
+
| Dependency cycle | Reports cycle path `A -> B -> ... -> A` | 1 |
|
|
260
|
+
| Hook failure | Aborts operation, shows hook output | 1 |
|
|
261
|
+
| Build failure | Cancels dependents, continues independents | 1 |
|
|
262
|
+
| Version rollback | Automatic on build failure post-bump | 1 |
|
|
263
|
+
| Infrastructure timeout | Shows last 20 log lines | 1 |
|
|
264
|
+
| Unknown YAML keys | Warning on stderr, continues | 0 |
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## License
|
|
269
|
+
|
|
270
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "octo-dev",
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "CLI for monorepo build orchestration, semantic versioning, and local infrastructure management",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "congeant",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/congeant/octo.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/congeant/octo#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/congeant/octo/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"cli",
|
|
18
|
+
"monorepo",
|
|
19
|
+
"build",
|
|
20
|
+
"orchestration",
|
|
21
|
+
"docker",
|
|
22
|
+
"semver",
|
|
23
|
+
"versioning",
|
|
24
|
+
"infrastructure",
|
|
25
|
+
"dependency-graph",
|
|
26
|
+
"topological-sort"
|
|
27
|
+
],
|
|
28
|
+
"bin": {
|
|
29
|
+
"octo": "./src/cli/index.ts"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"src/",
|
|
33
|
+
"scripts/",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"dev": "tsx src/cli/index.ts",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"test:watch": "vitest",
|
|
41
|
+
"lint": "eslint src/",
|
|
42
|
+
"type-check": "tsc --noEmit"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"commander": "^13.1.0",
|
|
46
|
+
"ollama": "^0.5.16",
|
|
47
|
+
"semver": "^7.7.2",
|
|
48
|
+
"tsx": "^4.19.4",
|
|
49
|
+
"yaml": "^2.7.1",
|
|
50
|
+
"zod": "^3.25.67"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/node": "^22.15.0",
|
|
54
|
+
"@types/semver": "^7.7.0",
|
|
55
|
+
"fast-check": "^4.1.1",
|
|
56
|
+
"typescript": "^5.8.3",
|
|
57
|
+
"vitest": "^3.2.4"
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=25.0.0"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Octo CLI — Installer
|
|
3
|
+
# Usage: curl -fsSL https://raw.githubusercontent.com/congeant/octo/main/scripts/install.sh | sh
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
RED='\033[0;31m'
|
|
8
|
+
GREEN='\033[0;32m'
|
|
9
|
+
YELLOW='\033[0;33m'
|
|
10
|
+
NC='\033[0m'
|
|
11
|
+
|
|
12
|
+
info() { printf "${GREEN}[INFO]${NC} %s\n" "$1"; }
|
|
13
|
+
warn() { printf "${YELLOW}[AVISO]${NC} %s\n" "$1"; }
|
|
14
|
+
error() { printf "${RED}[ERRO]${NC} %s\n" "$1"; }
|
|
15
|
+
|
|
16
|
+
missing=()
|
|
17
|
+
|
|
18
|
+
# Check Node.js >= 25
|
|
19
|
+
check_node() {
|
|
20
|
+
if ! command -v node &>/dev/null; then
|
|
21
|
+
missing+=("node")
|
|
22
|
+
error "Node.js não encontrado"
|
|
23
|
+
echo " → Instale via: https://nodejs.org/ ou use nvm:"
|
|
24
|
+
echo " curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash"
|
|
25
|
+
echo " nvm install 25"
|
|
26
|
+
return
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
local version
|
|
30
|
+
version=$(node --version | sed 's/^v//')
|
|
31
|
+
local major
|
|
32
|
+
major=$(echo "$version" | cut -d. -f1)
|
|
33
|
+
|
|
34
|
+
if [ "$major" -lt 25 ]; then
|
|
35
|
+
missing+=("node")
|
|
36
|
+
error "Node.js >= 25 necessário (encontrado: v${version})"
|
|
37
|
+
echo " → Atualize via nvm: nvm install 25"
|
|
38
|
+
else
|
|
39
|
+
info "Node.js v${version} ✓"
|
|
40
|
+
fi
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# Check Docker
|
|
44
|
+
check_docker() {
|
|
45
|
+
if ! command -v docker &>/dev/null; then
|
|
46
|
+
missing+=("docker")
|
|
47
|
+
error "Docker não encontrado"
|
|
48
|
+
echo " → Instale via: https://docs.docker.com/get-docker/"
|
|
49
|
+
else
|
|
50
|
+
local version
|
|
51
|
+
version=$(docker --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
|
52
|
+
info "Docker v${version} ✓"
|
|
53
|
+
fi
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Check Git
|
|
57
|
+
check_git() {
|
|
58
|
+
if ! command -v git &>/dev/null; then
|
|
59
|
+
missing+=("git")
|
|
60
|
+
error "Git não encontrado"
|
|
61
|
+
echo " → Instale via: https://git-scm.com/downloads"
|
|
62
|
+
else
|
|
63
|
+
local version
|
|
64
|
+
version=$(git --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
|
65
|
+
info "Git v${version} ✓"
|
|
66
|
+
fi
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Check pnpm
|
|
70
|
+
check_pnpm() {
|
|
71
|
+
if ! command -v pnpm &>/dev/null; then
|
|
72
|
+
missing+=("pnpm")
|
|
73
|
+
error "pnpm não encontrado"
|
|
74
|
+
echo " → Instale via: corepack enable && corepack prepare pnpm@latest --activate"
|
|
75
|
+
echo " ou: npm install -g pnpm"
|
|
76
|
+
else
|
|
77
|
+
local version
|
|
78
|
+
version=$(pnpm --version)
|
|
79
|
+
info "pnpm v${version} ✓"
|
|
80
|
+
fi
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
main() {
|
|
84
|
+
echo ""
|
|
85
|
+
echo "╔══════════════════════════════════════╗"
|
|
86
|
+
echo "║ Octo CLI — Installer ║"
|
|
87
|
+
echo "╚══════════════════════════════════════╝"
|
|
88
|
+
echo ""
|
|
89
|
+
|
|
90
|
+
info "Verificando pré-requisitos..."
|
|
91
|
+
echo ""
|
|
92
|
+
|
|
93
|
+
check_node
|
|
94
|
+
check_docker
|
|
95
|
+
check_git
|
|
96
|
+
check_pnpm
|
|
97
|
+
|
|
98
|
+
echo ""
|
|
99
|
+
|
|
100
|
+
if [ ${#missing[@]} -gt 0 ]; then
|
|
101
|
+
error "Pré-requisitos faltantes: ${missing[*]}"
|
|
102
|
+
echo ""
|
|
103
|
+
echo "Instale os itens acima e execute novamente."
|
|
104
|
+
exit 1
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
info "Todos os pré-requisitos atendidos."
|
|
108
|
+
info "Instalando @spectre/octo globalmente..."
|
|
109
|
+
echo ""
|
|
110
|
+
|
|
111
|
+
pnpm add -g @spectre/octo
|
|
112
|
+
|
|
113
|
+
echo ""
|
|
114
|
+
info "Instalação concluída. Verifique com: octo --version"
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
main
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { run } from '../../shared/process-runner.js';
|
|
2
|
+
import type { BuildEngine, BuildTarget, BuildEngineResult } from '../ports/build-engine.port.js';
|
|
3
|
+
|
|
4
|
+
/** Adapter — Docker build engine via CLI */
|
|
5
|
+
export class DockerBuildEngine implements BuildEngine {
|
|
6
|
+
name = 'docker';
|
|
7
|
+
|
|
8
|
+
async build(target: BuildTarget): Promise<BuildEngineResult> {
|
|
9
|
+
const context = target.context ?? target.path;
|
|
10
|
+
const start = Date.now();
|
|
11
|
+
|
|
12
|
+
const result = await run('docker', ['build', '-f', target.buildFile, context], {
|
|
13
|
+
cwd: target.path,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const durationMs = Date.now() - start;
|
|
17
|
+
const output = (result.stdout + result.stderr).trim();
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
success: result.exitCode === 0,
|
|
21
|
+
output,
|
|
22
|
+
durationMs,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async isAvailable(): Promise<boolean> {
|
|
27
|
+
try {
|
|
28
|
+
const result = await run('docker', ['--version'], { timeout: 5_000 });
|
|
29
|
+
return result.exitCode === 0;
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
detect(buildFile: string): boolean {
|
|
36
|
+
const name = buildFile.split('/').pop() ?? buildFile;
|
|
37
|
+
return name === 'Dockerfile' || name.startsWith('Dockerfile.');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import type { DependencyGraph } from '../graph/dependency-graph.js';
|
|
4
|
+
|
|
5
|
+
const CACHE_FILE = '.octo-cache.json';
|
|
6
|
+
|
|
7
|
+
interface CacheData {
|
|
8
|
+
builds: Record<string, number>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface AffectedDetector {
|
|
12
|
+
detect(graph: DependencyGraph, rootDir: string): Promise<string[]>;
|
|
13
|
+
recordBuild(names: string[], rootDir: string): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function readCache(rootDir: string): Promise<CacheData | null> {
|
|
17
|
+
try {
|
|
18
|
+
const content = await fs.readFile(path.join(rootDir, CACHE_FILE), 'utf-8');
|
|
19
|
+
return JSON.parse(content) as CacheData;
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function writeCache(rootDir: string, data: CacheData): Promise<void> {
|
|
26
|
+
await fs.writeFile(path.join(rootDir, CACHE_FILE), JSON.stringify(data, null, 2));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Get the latest mtime of any file in a directory (recursive, skipping node_modules/dist) */
|
|
30
|
+
async function getLatestMtime(dir: string): Promise<number> {
|
|
31
|
+
let latest = 0;
|
|
32
|
+
|
|
33
|
+
let entries: string[];
|
|
34
|
+
try {
|
|
35
|
+
entries = await fs.readdir(dir);
|
|
36
|
+
} catch {
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for (const name of entries) {
|
|
41
|
+
if (name === 'node_modules' || name === 'dist' || name.startsWith('.')) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const fullPath = path.join(dir, name);
|
|
46
|
+
const stat = await fs.stat(fullPath);
|
|
47
|
+
|
|
48
|
+
if (stat.isDirectory()) {
|
|
49
|
+
const sub = await getLatestMtime(fullPath);
|
|
50
|
+
if (sub > latest) latest = sub;
|
|
51
|
+
} else {
|
|
52
|
+
if (stat.mtimeMs > latest) latest = stat.mtimeMs;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return latest;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Calculate transitive closure of dependents for a set of modified nodes */
|
|
60
|
+
function getTransitiveDependents(modified: Set<string>, graph: DependencyGraph): Set<string> {
|
|
61
|
+
const affected = new Set<string>(modified);
|
|
62
|
+
const queue = [...modified];
|
|
63
|
+
|
|
64
|
+
while (queue.length > 0) {
|
|
65
|
+
const current = queue.shift()!;
|
|
66
|
+
for (const dep of graph.getDependents(current)) {
|
|
67
|
+
if (!affected.has(dep)) {
|
|
68
|
+
affected.add(dep);
|
|
69
|
+
queue.push(dep);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return affected;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function createAffectedDetector(): AffectedDetector {
|
|
78
|
+
return {
|
|
79
|
+
async detect(graph, rootDir) {
|
|
80
|
+
const cache = await readCache(rootDir);
|
|
81
|
+
const allNames = graph.getNodeNames();
|
|
82
|
+
|
|
83
|
+
// No previous build → all affected
|
|
84
|
+
if (!cache || Object.keys(cache.builds).length === 0) {
|
|
85
|
+
return allNames;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Find directly modified nodes
|
|
89
|
+
const modified = new Set<string>();
|
|
90
|
+
|
|
91
|
+
for (const name of allNames) {
|
|
92
|
+
const node = graph.getNode(name);
|
|
93
|
+
if (!node) continue;
|
|
94
|
+
|
|
95
|
+
const lastBuild = cache.builds[name];
|
|
96
|
+
if (lastBuild === undefined) {
|
|
97
|
+
// Never built → affected
|
|
98
|
+
modified.add(name);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const latestMtime = await getLatestMtime(node.path);
|
|
103
|
+
if (latestMtime > lastBuild) {
|
|
104
|
+
modified.add(name);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (modified.size === 0) return [];
|
|
109
|
+
|
|
110
|
+
// Transitive closure
|
|
111
|
+
const affected = getTransitiveDependents(modified, graph);
|
|
112
|
+
return [...affected];
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
async recordBuild(names, rootDir) {
|
|
116
|
+
const cache = await readCache(rootDir) ?? { builds: {} };
|
|
117
|
+
const now = Date.now();
|
|
118
|
+
|
|
119
|
+
for (const name of names) {
|
|
120
|
+
cache.builds[name] = now;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
await writeCache(rootDir, cache);
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|