@ingeniomaps/cauce 0.54.0 → 0.55.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/CHANGELOG.md +43 -0
- package/engine/automation/config.js +43 -11
- package/engine/automation/index.js +19 -2
- package/engine/cli/instance.js +43 -10
- package/engine/cli/ops.js +14 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,49 @@ desde este repositorio no va, porque el que lee no puede actuar sobre eso. Cuand
|
|
|
14
14
|
unas pocas líneas casi siempre es porque cuenta cómo se descubrió el problema o por qué se eligió el
|
|
15
15
|
diseño — eso vive en el commit y en el código.
|
|
16
16
|
|
|
17
|
+
## [0.55.0] - 2026-09-02
|
|
18
|
+
|
|
19
|
+
### Corregido
|
|
20
|
+
|
|
21
|
+
- **`upgrade` ya no pisa lo que `init --force` conservó.** Adoptar Cauce en un repositorio que ya tiene
|
|
22
|
+
contenido es `init --force` y después `upgrade`. El primero conserva tus archivos y lo cumple; el
|
|
23
|
+
segundo los reemplazaba sin avisar e imprimía que no había tocado nada propio. El registro se grababa
|
|
24
|
+
hasheando el disco, así que un archivo conservado quedaba anotado como entregado por Cauce con **tu**
|
|
25
|
+
contenido, y la comparación no veía ninguna diferencia. Ahora se anota el digest de lo que el molde
|
|
26
|
+
habría escrito: tu archivo se ve como lo que es —una edición local— y `upgrade` se detiene
|
|
27
|
+
nombrándolo. **Qué cambia para vos**: el primer `upgrade` después de una adopción con `--force` se
|
|
28
|
+
detiene listando esos archivos. Resolvelos a mano, o repetí con `--force` para descartarlos, que
|
|
29
|
+
ahora además los enumera en la salida.
|
|
30
|
+
- **Tu guard propio sobrevive a `automation install`.** `AGENTS.md` te dice que lo pongas en
|
|
31
|
+
`automatization/hooks/guard-<nombre>.sh` y promete que sobrevive a cada actualización. Sobrevivía al
|
|
32
|
+
`upgrade` y no a la reinstalación del runner: lo nuestro se reconocía por la carpeta, así que
|
|
33
|
+
cualquier `guard-*.sh` de ahí se desregistraba antes de fusionar. El archivo quedaba en disco, el
|
|
34
|
+
guard dejaba de correr y lo único que se veía era una línea diciendo que se había quitado una entrada
|
|
35
|
+
obsoleta. Ahora se reconoce por lo que efectivamente entregamos: los guards que el motor trae, más
|
|
36
|
+
los que esta instancia anotó haber recibido —el manifiesto gana una entrada por runner con el wiring
|
|
37
|
+
que dejó puesto, y por eso una entrada nuestra se sigue retirando el día que el motor deje de traer
|
|
38
|
+
ese guard—. **Si moviste tu guard fuera de esa carpeta para sortearlo, ya podés devolverlo.**
|
|
39
|
+
- **`automation doctor` deja de llamar divergente a una configuración completa.** Un hook propio sumado
|
|
40
|
+
*dentro* de un grupo que escribió el toolkit dejaba a `doctor` en rojo aunque estuvieran todas las
|
|
41
|
+
entradas esperadas: comparaba el grupo entero serializado, así que uno con un hook de más contaba
|
|
42
|
+
como ausente. Ahora compara por `matcher` y contenido. Lo que falta se sigue reportando igual.
|
|
43
|
+
- **`upgrade --check` avisa de lo que tenés editado aunque la versión coincida.** Salía por un camino
|
|
44
|
+
corto que informaba «al día» sin llegar a listarlo, que es el estado normal entre actualizaciones
|
|
45
|
+
—`init` fija la versión exacta—: el único modo que no toca nada era también el único que no podía
|
|
46
|
+
anticipar el conflicto. **Qué cambia para vos**: `--check` ahora sale 1 cuando hay archivos del
|
|
47
|
+
sistema editados, un caso donde antes salía 0. Si lo tenés en un script, revisá esa condición.
|
|
48
|
+
|
|
49
|
+
### Cambiado
|
|
50
|
+
|
|
51
|
+
- **`init` e `init .` eligen el mismo modo en el mismo directorio.** El default salía de si escribiste
|
|
52
|
+
el argumento, no de a dónde apunta: en `acme-ops/`, `init` daba `sidecar` e `init .` daba `embedded`.
|
|
53
|
+
No es cosmético — en `embedded` la raíz del workspace pasa a ser la carpeta ops misma, así que
|
|
54
|
+
`guard-workspace-boundary` deja de reconocer los repos hermanos y el wiring del runner se escribe
|
|
55
|
+
adentro en vez de al lado, donde abrís tu herramienta. Ahora lo decide el destino resuelto, con la
|
|
56
|
+
misma regla que ya elegía dónde aterrizar: una carpeta llamada `ops` o terminada en `-ops` **es** la
|
|
57
|
+
instancia. **Qué cambia para vos**: `init .` en una carpeta así ahora da `sidecar`, donde antes daba
|
|
58
|
+
`embedded`. El caso embebido sigue disponible pidiéndolo: `init . --mode embedded`.
|
|
59
|
+
|
|
17
60
|
## [0.54.0] - 2026-09-02
|
|
18
61
|
|
|
19
62
|
### Agregado
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
const fs = require('node:fs')
|
|
9
9
|
const path = require('node:path')
|
|
10
10
|
const F = require('../core/files')
|
|
11
|
-
const { supersededGuards } = require('./hooks')
|
|
11
|
+
const { supersededGuards, expectedHooks } = require('./hooks')
|
|
12
12
|
|
|
13
13
|
function mergeConfig(current, incoming) {
|
|
14
14
|
if (Array.isArray(incoming)) {
|
|
@@ -32,19 +32,40 @@ function mergeConfig(current, incoming) {
|
|
|
32
32
|
return incoming
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
// Una entrada de hook que puso Cauce se reconoce por el guard al que apunta
|
|
36
|
-
//
|
|
37
|
-
// el
|
|
38
|
-
|
|
35
|
+
// Una entrada de hook que puso Cauce se reconoce por el guard al que apunta, y lo nuestro es lo que
|
|
36
|
+
// efectivamente entregamos, no todo lo que vive en nuestra carpeta. Reconocer por directorio
|
|
37
|
+
// desregistraba el guard propio del proyecto en cada reinstalación: `template/AGENTS.md` invita a
|
|
38
|
+
// ponerlo justo ahí y promete que sobrevive a cada actualización, el archivo quedaba en disco, y lo
|
|
39
|
+
// único que se veía era una línea diciendo que se había quitado una entrada obsoleta.
|
|
40
|
+
// Se saca del archivo del usuario antes de fusionar para que el merge deje exactamente las de esta
|
|
41
|
+
// versión, ni una más.
|
|
42
|
+
const HOOK_PATH = /automatization\/hooks\/([a-z0-9-]+\.sh)(?:\s|$)/
|
|
39
43
|
|
|
40
|
-
|
|
44
|
+
// Dos fuentes, y la segunda es la que cierra el borde. `expectedHooks()` dice qué entrega el motor de
|
|
45
|
+
// hoy y alcanza para una instancia que nunca instaló; `delivered` es lo que esta instancia registró
|
|
46
|
+
// haber recibido la última vez, y es lo único que reconoce un guard que entregamos en una versión y
|
|
47
|
+
// retiramos en la siguiente: su nombre ya no está en la lista de hoy, así que sin el registro quedaría
|
|
48
|
+
// vivo en la configuración llamando a un guard que Cauce ya no mantiene. Un guard propio del proyecto
|
|
49
|
+
// no está en ninguna de las dos, que es exactamente por lo que sobrevive.
|
|
50
|
+
function isDelivered(command, delivered) {
|
|
51
|
+
const hit = String(command).match(HOOK_PATH)
|
|
52
|
+
if (!hit) return false
|
|
53
|
+
return expectedHooks().includes(hit[1]) || delivered.has(String(command))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Lo que de un conjunto de comandos es wiring de guards nuestro, para anotarlo como entregado.
|
|
57
|
+
function deliveredHookCommands(commands) {
|
|
58
|
+
return [...commands].filter((command) => HOOK_PATH.test(String(command)))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function withoutDeliveredHooks(config, live, delivered = new Set()) {
|
|
41
62
|
const dropped = []
|
|
42
63
|
const walk = (node) => {
|
|
43
64
|
if (Array.isArray(node)) {
|
|
44
65
|
return node
|
|
45
66
|
.filter((item) => {
|
|
46
67
|
const command = item && typeof item === 'object' ? String(item.command || '') : ''
|
|
47
|
-
if (!
|
|
68
|
+
if (!isDelivered(command, delivered)) return true
|
|
48
69
|
// Sólo se anuncia lo que ya no vuelve: una entrada que el merge repone quedó igual, y decir
|
|
49
70
|
// que se quitó y se puso la misma línea es ruido que esconde el caso que sí importa.
|
|
50
71
|
if (!live.has(command)) dropped.push(command)
|
|
@@ -83,9 +104,20 @@ function reportRemoved(name, dropped, live, output) {
|
|
|
83
104
|
|
|
84
105
|
function includesConfig(actual, expected) {
|
|
85
106
|
if (Array.isArray(expected)) {
|
|
86
|
-
return Array.isArray(actual) && expected.every((item) => {
|
|
87
|
-
|
|
88
|
-
|
|
107
|
+
return Array.isArray(actual) && expected.every((item) => actual.some((value) => {
|
|
108
|
+
// Un grupo de hooks se compara por su contenido, no por su forma serializada. La comprobación es
|
|
109
|
+
// «contiene», que es lo correcto, pero con el ítem entero como unidad un grupo `{matcher, hooks}`
|
|
110
|
+
// con un hook de más dejaba de ser el mismo objeto y contaba como ausente: `doctor` llamaba
|
|
111
|
+
// divergente a una configuración que tenía todo lo esperado, y el anidamiento —donde vive la
|
|
112
|
+
// diferencia real— no se miraba nunca. La instalación promete conservar lo que el proyecto ya
|
|
113
|
+
// tenía, así que sumar un hook propio a un grupo nuestro es exactamente lo que permite.
|
|
114
|
+
if (item && typeof item === 'object' && Array.isArray(item.hooks)) {
|
|
115
|
+
return value && typeof value === 'object'
|
|
116
|
+
&& value.matcher === item.matcher
|
|
117
|
+
&& includesConfig(value.hooks, item.hooks)
|
|
118
|
+
}
|
|
119
|
+
return JSON.stringify(value) === JSON.stringify(item)
|
|
120
|
+
}))
|
|
89
121
|
}
|
|
90
122
|
if (expected && typeof expected === 'object') {
|
|
91
123
|
return actual && typeof actual === 'object' && Object.entries(expected).every(([key, value]) => {
|
|
@@ -170,6 +202,6 @@ function blockUpToDate(file, name, content) {
|
|
|
170
202
|
|
|
171
203
|
module.exports = {
|
|
172
204
|
blockStart,
|
|
173
|
-
mergeConfig, withoutDeliveredHooks, reportRemoved, includesConfig, hasHooks,
|
|
205
|
+
mergeConfig, withoutDeliveredHooks, deliveredHookCommands, reportRemoved, includesConfig, hasHooks,
|
|
174
206
|
unmergeConfig, isSharedFile, withoutBlock, mergeInstruction, blockUpToDate,
|
|
175
207
|
}
|
|
@@ -17,7 +17,7 @@ const {
|
|
|
17
17
|
legacyGuardWiring, staleHooks, listHooks,
|
|
18
18
|
} = require('./hooks')
|
|
19
19
|
const {
|
|
20
|
-
blockStart, mergeConfig, withoutDeliveredHooks, reportRemoved, includesConfig, hasHooks,
|
|
20
|
+
blockStart, mergeConfig, withoutDeliveredHooks, deliveredHookCommands, reportRemoved, includesConfig, hasHooks,
|
|
21
21
|
unmergeConfig, isSharedFile, withoutBlock, mergeInstruction, blockUpToDate,
|
|
22
22
|
} = require('./config')
|
|
23
23
|
|
|
@@ -226,6 +226,11 @@ function deliveryKey(name, target) {
|
|
|
226
226
|
return `${name}/${target.split(path.sep).join('/')}`
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
// La sección `runners` del manifiesto anota archivo → digest; ésta es la única entrada que guarda una
|
|
230
|
+
// lista, porque lo que registra no es un archivo sino las entradas que dejamos dentro del archivo de
|
|
231
|
+
// configuración del usuario. No colisiona con ningún `target`: ninguno se llama así.
|
|
232
|
+
const HOOKS_KEY = 'config.hooks'
|
|
233
|
+
|
|
229
234
|
// En qué estado quedó un archivo que este adaptador entregó alguna vez.
|
|
230
235
|
//
|
|
231
236
|
// nuevo no existe todavía
|
|
@@ -277,6 +282,7 @@ function uninstall(root, name, output = console) {
|
|
|
277
282
|
|
|
278
283
|
const items = [...(runner.instructions || []), ...(runner.artifacts || [])]
|
|
279
284
|
const deliveredPaths = { ...recorded }
|
|
285
|
+
delete deliveredPaths[deliveryKey(name, HOOKS_KEY)]
|
|
280
286
|
for (const item of items) {
|
|
281
287
|
const resolved = { item, ...resolveItem(paths, root, name, item) }
|
|
282
288
|
const key = deliveryKey(name, item.target)
|
|
@@ -378,7 +384,12 @@ function install(root, name, output = console, options = {}) {
|
|
|
378
384
|
// guard que el runner intenta ejecutar y falla. Se quitan las nuestras y las vuelve a poner el merge.
|
|
379
385
|
const live = new Set((JSON.stringify(incoming).match(/"command":"[^"]*"/g) || [])
|
|
380
386
|
.map((entry) => JSON.parse(`{${entry}}`).command))
|
|
381
|
-
|
|
387
|
+
// Lo que este adaptador anotó haber entregado la última vez. Es lo único que reconoce una entrada
|
|
388
|
+
// nuestra cuyo guard el motor ya no trae: por nombre no se distingue de la que agregó el proyecto.
|
|
389
|
+
const previous = new Set(recorded[deliveryKey(name, HOOKS_KEY)] || [])
|
|
390
|
+
const clean = runner.config.owned
|
|
391
|
+
? { config: {}, dropped: [] }
|
|
392
|
+
: withoutDeliveredHooks(current, live, previous)
|
|
382
393
|
reportRemoved(name, clean.dropped, live, output)
|
|
383
394
|
F.atomicWriteJson(paths.configTarget, mergeConfig(clean.config, incoming))
|
|
384
395
|
// Dónde aterrizó, no sólo qué archivo: en sidecar el destino no es el repo desde el que se corrió
|
|
@@ -388,6 +399,12 @@ function install(root, name, output = console, options = {}) {
|
|
|
388
399
|
}
|
|
389
400
|
output.log(`✓ ${name}: configuración instalada en ${runner.config.target}`)
|
|
390
401
|
const deliveredPaths = { ...recorded }
|
|
402
|
+
// Qué wiring de guards dejamos puesto, para poder retirarlo el día que el motor deje de traerlo. Se
|
|
403
|
+
// anota lo que instalamos y no lo que quedó en el archivo: ahí conviven las entradas del proyecto, y
|
|
404
|
+
// registrarlas nos autorizaría a borrar lo ajeno en la instalación siguiente.
|
|
405
|
+
const ourHooks = deliveredHookCommands(live)
|
|
406
|
+
if (ourHooks.length) deliveredPaths[deliveryKey(name, HOOKS_KEY)] = ourHooks
|
|
407
|
+
else delete deliveredPaths[deliveryKey(name, HOOKS_KEY)]
|
|
391
408
|
for (const resolved of resolvedItems) {
|
|
392
409
|
const status = state.get(resolved)
|
|
393
410
|
const ownFile = runner.instructions.includes(resolved.item)
|
package/engine/cli/instance.js
CHANGED
|
@@ -29,9 +29,14 @@ function providerNames() {
|
|
|
29
29
|
} catch { return [] }
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
// Devuelve, por archivo conservado, el digest de lo que el molde **habría** escrito. Adoptar Cauce en
|
|
33
|
+
// un repositorio con contenido es `init --force`, y lo que se conserva ahí lo escribió la empresa: si
|
|
34
|
+
// el manifiesto lo registra hasheando el disco queda declarado como entregado por Cauce con contenido
|
|
35
|
+
// que Cauce nunca entregó, y el `upgrade` siguiente no ve ninguna edición local y lo reemplaza.
|
|
32
36
|
function copyTemplate(source, target, replacements, force, skip = [], quiet = false) {
|
|
33
37
|
F.assertNoSymlinkPath(path.dirname(target), target)
|
|
34
38
|
fs.mkdirSync(target, { recursive: true })
|
|
39
|
+
const preserved = {}
|
|
35
40
|
for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
|
|
36
41
|
if (skip.includes(entry.name)) continue
|
|
37
42
|
const from = path.join(source, entry.name)
|
|
@@ -39,11 +44,14 @@ function copyTemplate(source, target, replacements, force, skip = [], quiet = fa
|
|
|
39
44
|
// 11.16.0: lo deja afuera en la raíz y en subdirectorios—, así que viaja sin punto y se restituye
|
|
40
45
|
// acá. Sin esto el archivo existe en el repo del toolkit y desaparece para todo consumidor real.
|
|
41
46
|
const to = path.join(target, entry.name === 'gitignore' ? '.gitignore' : entry.name)
|
|
42
|
-
if (entry.isDirectory()) copyTemplate(from, to, replacements, force, skip, quiet)
|
|
47
|
+
if (entry.isDirectory()) Object.assign(preserved, copyTemplate(from, to, replacements, force, skip, quiet))
|
|
43
48
|
else {
|
|
44
49
|
if (fs.existsSync(to)) {
|
|
45
50
|
if (!force) fail(`El destino contiene ${to}. Usa un directorio vacío o --force.`)
|
|
46
51
|
if (!quiet) console.log(`= conservado ${to}`)
|
|
52
|
+
let would = fs.readFileSync(from, 'utf8')
|
|
53
|
+
for (const [key, value] of Object.entries(replacements)) would = would.replaceAll(key, value)
|
|
54
|
+
preserved[to] = M.digestText(would)
|
|
47
55
|
continue
|
|
48
56
|
}
|
|
49
57
|
let content = fs.readFileSync(from, 'utf8')
|
|
@@ -53,22 +61,29 @@ function copyTemplate(source, target, replacements, force, skip = [], quiet = fa
|
|
|
53
61
|
if (!quiet) console.log(`+ ${to}`)
|
|
54
62
|
}
|
|
55
63
|
}
|
|
64
|
+
return preserved
|
|
56
65
|
}
|
|
57
66
|
|
|
67
|
+
// Devuelve lo conservado igual que `copyTemplate`, y por la misma razón: acá el runtime no lleva
|
|
68
|
+
// reemplazos, así que lo que habríamos escrito es el archivo del paquete tal cual.
|
|
58
69
|
function copyRuntime(source, target, preserve = false, boundary = target, skip = []) {
|
|
59
70
|
F.assertNoSymlinkPath(boundary, target)
|
|
60
71
|
fs.mkdirSync(target, { recursive: true })
|
|
72
|
+
const preserved = {}
|
|
61
73
|
for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
|
|
62
74
|
if (skip.includes(entry.name)) continue
|
|
63
75
|
const from = path.join(source, entry.name)
|
|
64
76
|
const to = path.join(target, entry.name)
|
|
65
|
-
if (entry.isDirectory()) copyRuntime(from, to, preserve, boundary, skip)
|
|
66
|
-
else if (preserve && fs.existsSync(to))
|
|
67
|
-
|
|
77
|
+
if (entry.isDirectory()) Object.assign(preserved, copyRuntime(from, to, preserve, boundary, skip))
|
|
78
|
+
else if (preserve && fs.existsSync(to)) {
|
|
79
|
+
console.log(`= conservado ${to}`)
|
|
80
|
+
preserved[to] = M.digest(from)
|
|
81
|
+
} else {
|
|
68
82
|
F.assertNoSymlinkPath(boundary, to)
|
|
69
83
|
fs.copyFileSync(from, to)
|
|
70
84
|
}
|
|
71
85
|
}
|
|
86
|
+
return preserved
|
|
72
87
|
}
|
|
73
88
|
|
|
74
89
|
// Declara el motor como dependencia exacta: el lockfile decide qué versión corre, no una copia.
|
|
@@ -116,7 +131,7 @@ function undeclareEngine(manifest) {
|
|
|
116
131
|
// el banco de evaluación lo llama directo: crear una instancia programáticamente no puede depender de
|
|
117
132
|
// cómo venga escrita la línea de comandos.
|
|
118
133
|
function scaffold(root, { name, mode, force = false, quiet = false }) {
|
|
119
|
-
copyTemplate(path.join(PROJECT_ROOT, 'template'), root, {
|
|
134
|
+
const preserved = copyTemplate(path.join(PROJECT_ROOT, 'template'), root, {
|
|
120
135
|
'{{PROJECT_NAME}}': name,
|
|
121
136
|
'{{MODE}}': mode,
|
|
122
137
|
'{{WORKSPACE_PATH}}': mode === 'embedded' ? '.' : '..',
|
|
@@ -124,12 +139,12 @@ function scaffold(root, { name, mode, force = false, quiet = false }) {
|
|
|
124
139
|
// No se copia `.github/`: `ci.yml` valida el toolkit con `npm run ci` —que una instancia no tiene— y
|
|
125
140
|
// el ciclo de aprendizaje dejó de distribuirse en 0.4.0. Copiar salteando lo que no aplica dejaba
|
|
126
141
|
// `.github/workflows/` vacío en cada instancia.
|
|
127
|
-
copyRuntime(
|
|
142
|
+
Object.assign(preserved, copyRuntime(
|
|
128
143
|
path.join(PROJECT_ROOT, 'automatization', 'hooks'),
|
|
129
144
|
path.join(root, 'automatization', 'hooks'),
|
|
130
145
|
force,
|
|
131
146
|
root,
|
|
132
|
-
)
|
|
147
|
+
))
|
|
133
148
|
const version = require(path.join(PROJECT_ROOT, 'package.json')).version
|
|
134
149
|
// El motor llega como dependencia para que el lockfile fije su versión. El repo ops es un sidecar:
|
|
135
150
|
// declarar npm acá no convierte en Node al servicio de Go de al lado.
|
|
@@ -140,6 +155,14 @@ function scaffold(root, { name, mode, force = false, quiet = false }) {
|
|
|
140
155
|
if (fs.existsSync(dir)) deliveredPaths = M.record(root, relative, O.treeFiles(dir), deliveredPaths)
|
|
141
156
|
}
|
|
142
157
|
deliveredPaths = M.recordPaths(root, O.SYSTEM_FILES, deliveredPaths)
|
|
158
|
+
// Lo conservado entra con el digest del molde, no con el del disco: la diferencia entre los dos es
|
|
159
|
+
// justamente lo que `localChanges` tiene que ver para que `upgrade` se detenga antes de pisar el
|
|
160
|
+
// archivo de la empresa. Sólo se pisan claves que ya están: el manifiesto declara lo que Cauce
|
|
161
|
+
// rastrea, y un archivo propio fuera de esa frontera no le incumbe.
|
|
162
|
+
for (const [file, hash] of Object.entries(preserved)) {
|
|
163
|
+
const relative = path.relative(root, file).split(path.sep).join('/')
|
|
164
|
+
if (relative in deliveredPaths) deliveredPaths[relative] = hash
|
|
165
|
+
}
|
|
143
166
|
M.write(root, deliveredPaths)
|
|
144
167
|
// La instancia recuerda de qué versión salió: sin esto no hay actualización posible.
|
|
145
168
|
const configFile = path.join(root, 'ops.config.json')
|
|
@@ -305,12 +328,20 @@ function upgrade(dir, cli) {
|
|
|
305
328
|
const overrides = O.overrides(root)
|
|
306
329
|
|
|
307
330
|
if (dry) {
|
|
331
|
+
// Lo editado localmente se informa siempre, antes de mirar versiones: son dos preguntas distintas
|
|
332
|
+
// —«¿hay algo más nuevo?» y «¿qué tengo editado que se perdería?»— y la segunda tiene respuesta
|
|
333
|
+
// útil aunque la primera sea que no. Sin esto, el único modo que no toca nada era también el único
|
|
334
|
+
// que no podía avisar del conflicto, justo en el estado normal entre actualizaciones: `init` fija
|
|
335
|
+
// la versión exacta, así que instancia y motor coinciden casi todo el tiempo.
|
|
336
|
+
for (const file of changed) console.log(` editado localmente: ${file}`)
|
|
308
337
|
if (from === to) {
|
|
309
338
|
// Contra el motor instalado, no contra lo publicado: la comparación es local y sin red. Decirlo
|
|
310
339
|
// importa porque `init` fija la versión exacta, así que el motor no se mueve solo y esta línea,
|
|
311
340
|
// a secas, se leía como «no hay nada nuevo» durante todas las versiones siguientes.
|
|
312
341
|
console.log(`= ${to}: la instancia está al día con el motor instalado`)
|
|
313
|
-
|
|
342
|
+
console.log(' para traer una versión más nueva: npm install --save-dev @ingeniomaps/cauce@latest')
|
|
343
|
+
if (changed.length) process.exit(1)
|
|
344
|
+
return
|
|
314
345
|
}
|
|
315
346
|
// Hacia atrás también es legítimo —una versión rompió algo y se vuelve—, pero anunciarlo como «hay
|
|
316
347
|
// una versión más nueva» era mentir con el número a la vista. Y lo que corresponde imprimir es lo
|
|
@@ -322,7 +353,6 @@ function upgrade(dir, cli) {
|
|
|
322
353
|
console.log(`⚠ hay una versión más nueva: ${to} (la instancia tiene ${from || 'una previa'})`)
|
|
323
354
|
printChangelog(from, to)
|
|
324
355
|
}
|
|
325
|
-
for (const file of changed) console.log(` editado localmente: ${file}`)
|
|
326
356
|
process.exit(1)
|
|
327
357
|
}
|
|
328
358
|
|
|
@@ -427,7 +457,10 @@ function upgrade(dir, cli) {
|
|
|
427
457
|
for (const override of overrides) {
|
|
428
458
|
console.log(`= conservado ${override.collection}/${override.project}: sobrescribe ${override.system}`)
|
|
429
459
|
}
|
|
430
|
-
|
|
460
|
+
// Sólo cuando es cierto. Llegar acá con algo en `changed` significa que se corrió con --force y se
|
|
461
|
+
// descartó contenido de la empresa: las líneas de arriba lo enumeran, y afirmar a continuación que
|
|
462
|
+
// todo lo propio quedó intacto contradice a la única señal que recibe quien corrió el comando.
|
|
463
|
+
if (!changed.length) console.log(' planning, organization y todo lo propio quedaron intactos')
|
|
431
464
|
// No se borra: sin la dependencia declarada, quitarle `.ops/` la dejaría sin motor. Se avisa y
|
|
432
465
|
// decide una persona.
|
|
433
466
|
if (fs.existsSync(path.join(root, '.ops', 'engine'))) {
|
package/engine/cli/ops.js
CHANGED
|
@@ -21,8 +21,14 @@ const DEFAULT_TARGET = 'ops'
|
|
|
21
21
|
// de otra— y el proyecto quedaba llamándose «acme-ops». La carpeta que ya nombra al toolkit es la
|
|
22
22
|
// instancia; no hay una segunda adentro.
|
|
23
23
|
function implicitTarget(cwd) {
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
return isInstanceDir(cwd) ? '.' : DEFAULT_TARGET
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// La misma heurística, aplicada al destino ya resuelto: una carpeta que ya nombra al toolkit **es** la
|
|
28
|
+
// instancia, y el código de la empresa vive al lado.
|
|
29
|
+
function isInstanceDir(dir) {
|
|
30
|
+
const base = path.basename(dir)
|
|
31
|
+
return base === DEFAULT_TARGET || base.endsWith('-ops')
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
// El nombre sale de la carpeta del proyecto, no de la que aloja la instancia: `ops/` y `acme-ops/`
|
|
@@ -73,7 +79,12 @@ async function init(target, cli) {
|
|
|
73
79
|
// install` ya asume —el wiring del runner va al padre, donde se abre la herramienta—, así que lo
|
|
74
80
|
// único que faltaba era que fuera lo que pasa cuando no se elige nada.
|
|
75
81
|
const root = path.resolve(target || implicitTarget(process.cwd()))
|
|
76
|
-
|
|
82
|
+
// El modo lo decide a dónde apunta el destino, no si alguien escribió el argumento. `init` e `init .`
|
|
83
|
+
// resuelven el mismo directorio y elegían modos opuestos: escribir el punto —la forma más natural de
|
|
84
|
+
// decir «acá»— daba embedded, y ahí la raíz del workspace pasa a ser la carpeta ops misma, así que
|
|
85
|
+
// `guard-workspace-boundary` deja de reconocer los repos hermanos y el wiring del runner se escribe
|
|
86
|
+
// adentro en vez de al lado, donde el dev abre su herramienta.
|
|
87
|
+
const mode = cli.value('--mode', isInstanceDir(root) ? 'sidecar' : 'embedded')
|
|
77
88
|
if (!['embedded', 'sidecar'].includes(mode)) fail('--mode debe ser embedded o sidecar.', 2)
|
|
78
89
|
const name = cli.value('--name', defaultName(root))
|
|
79
90
|
const force = cli.has('--force')
|