@open-mova/cli 0.1.19 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +194 -4
- package/dist/application/android-configuration.d.ts +2 -0
- package/dist/application/android-configuration.js +90 -0
- package/dist/application/configuration-migrations.d.ts +6 -0
- package/dist/application/configuration-migrations.js +57 -0
- package/dist/application/configuration.d.ts +5 -0
- package/dist/application/configuration.js +52 -19
- package/dist/application/core-compatibility.d.ts +3 -0
- package/dist/application/core-compatibility.js +10 -0
- package/dist/application/doctor.d.ts +12 -0
- package/dist/application/doctor.js +304 -0
- package/dist/application/microfrontend-manifest.d.ts +3 -0
- package/dist/application/microfrontend-manifest.js +20 -0
- package/dist/application/microfrontend-profile.js +2 -1
- package/dist/application/microfrontend-update.d.ts +17 -0
- package/dist/application/microfrontend-update.js +117 -0
- package/dist/application/microfrontend.d.ts +1 -0
- package/dist/application/microfrontend.js +14 -3
- package/dist/application/project-update.d.ts +8 -0
- package/dist/application/project-update.js +106 -0
- package/dist/application/shell-configuration.js +2 -0
- package/dist/application/shell-repository.d.ts +1 -1
- package/dist/application/shell-repository.js +14 -7
- package/dist/application/shell-update.d.ts +1 -5
- package/dist/application/shell-update.js +9 -108
- package/dist/cli.js +2 -0
- package/dist/commands/capacitor.js +15 -49
- package/dist/commands/create.js +2 -2
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +25 -0
- package/dist/commands/info.js +1 -1
- package/dist/commands/microfrontend.js +40 -1
- package/dist/commands/update.js +13 -4
- package/dist/types.d.ts +13 -1
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -10,14 +10,19 @@ El CLI descarga la shell, el core y la plantilla de microfrontales desde tags es
|
|
|
10
10
|
|
|
11
11
|
## Índice de comandos
|
|
12
12
|
|
|
13
|
+
- [`mova --help` y `mova --version`](#mova---help-y-mova---version)
|
|
13
14
|
- [`mova create`](#mova-create)
|
|
15
|
+
- [`mova mf`](#mova-mf)
|
|
14
16
|
- [`mova mf create`](#mova-mf-create)
|
|
15
17
|
- [`mova mf add`](#mova-mf-add)
|
|
16
18
|
- [`mova start`](#mova-start)
|
|
17
19
|
- [`mova build`](#mova-build)
|
|
18
20
|
- [`mova info`](#mova-info)
|
|
21
|
+
- [`mova doctor`](#mova-doctor)
|
|
19
22
|
- [`mova update`](#mova-update)
|
|
23
|
+
- [`mova shell`](#mova-shell)
|
|
20
24
|
- [`mova shell versions`](#mova-shell-versions)
|
|
25
|
+
- [`mova cap`](#mova-cap)
|
|
21
26
|
- [`mova cap add`](#mova-cap-add)
|
|
22
27
|
- [`mova cap sync`](#mova-cap-sync)
|
|
23
28
|
- [`mova cap open`](#mova-cap-open)
|
|
@@ -47,6 +52,17 @@ npm link
|
|
|
47
52
|
|
|
48
53
|
## Comandos
|
|
49
54
|
|
|
55
|
+
### `mova --help` y `mova --version`
|
|
56
|
+
|
|
57
|
+
Muestra la ayuda del CLI o la versión instalada.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
mova --help
|
|
61
|
+
mova --version
|
|
62
|
+
mova mf --help
|
|
63
|
+
mova cap --help
|
|
64
|
+
```
|
|
65
|
+
|
|
50
66
|
### `mova create`
|
|
51
67
|
|
|
52
68
|
Crea una aplicación completa. Descarga una shell versionada, el core y un MF
|
|
@@ -63,6 +79,11 @@ mova create mi-aplicacion --empty
|
|
|
63
79
|
|
|
64
80
|
Sin `--shell-version` se usa el tag estable más reciente (`vMAJOR.MINOR.PATCH`). `--empty` omite el MF inicial.
|
|
65
81
|
|
|
82
|
+
### `mova mf`
|
|
83
|
+
|
|
84
|
+
Es el grupo de comandos para crear y registrar microfrontales en la aplicación.
|
|
85
|
+
Usa `mova mf --help` para consultar sus subcomandos.
|
|
86
|
+
|
|
66
87
|
### `mova mf create`
|
|
67
88
|
|
|
68
89
|
Crea un microfrontal local, lo registra y actualiza la configuración de federación de la shell.
|
|
@@ -97,6 +118,32 @@ mova mf add --name catalog --route productos \
|
|
|
97
118
|
|
|
98
119
|
El CLI actualiza `mova.config.json`, `src/assets/federation.manifest.json` y `src/app/application.config.ts`.
|
|
99
120
|
|
|
121
|
+
Para un MF remoto que no tenga un proyecto local, declara explícitamente su
|
|
122
|
+
rango de Core:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
mova mf add --name catalog --route catalog \
|
|
126
|
+
--remote catalog-microfrontend \
|
|
127
|
+
--remote-entry https://cdn.example.com/catalog/remoteEntry.json \
|
|
128
|
+
--core-version '^0.2.2'
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### `mova mf update`
|
|
132
|
+
|
|
133
|
+
Actualiza un MF local que fue creado desde una plantilla de Open Mova. Compara
|
|
134
|
+
la plantilla original, el proyecto actual y la nueva versión en tres pasos:
|
|
135
|
+
solo aplica cambios que no hayan sido personalizados y deja los conflictos
|
|
136
|
+
para revisión.
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
mova mf update home --check
|
|
140
|
+
mova mf update home --to v0.2.2
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
El comando conserva el nombre, la ruta, el puerto, el perfil y las
|
|
144
|
+
dependencias propias del proveedor. Si cambia `package.json`, elimina el
|
|
145
|
+
lockfile para que ejecutes `npm install` en el MF.
|
|
146
|
+
|
|
100
147
|
### `mova start`
|
|
101
148
|
|
|
102
149
|
Arranca los MFs locales registrados y después la shell. Los MFs configurados solo con una URL remota no se arrancan localmente.
|
|
@@ -122,6 +169,25 @@ Muestra la aplicación detectada, la versión de shell y sus microfrontales. Tam
|
|
|
122
169
|
mova info
|
|
123
170
|
```
|
|
124
171
|
|
|
172
|
+
### `mova doctor`
|
|
173
|
+
|
|
174
|
+
Diagnostica el entorno de desarrollo y la aplicación encontrada desde el
|
|
175
|
+
directorio actual. Comprueba las herramientas básicas, las dependencias, la
|
|
176
|
+
configuración de la shell y los microfrontales, las URLs de producción y la
|
|
177
|
+
compatibilidad de Core.
|
|
178
|
+
|
|
179
|
+
Si la aplicación contiene plataformas Capacitor, también revisa los
|
|
180
|
+
requisitos disponibles de Android o iOS, como el SDK, `adb`, emuladores,
|
|
181
|
+
Xcode, CocoaPods, claves y descripciones de permisos.
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
mova doctor
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Los avisos informan de elementos opcionales o todavía no configurados. Los
|
|
188
|
+
errores hacen que el comando termine con un código distinto de cero, por lo
|
|
189
|
+
que también puede utilizarse en scripts de validación.
|
|
190
|
+
|
|
125
191
|
### `mova update`
|
|
126
192
|
|
|
127
193
|
Actualiza los archivos técnicos de la shell usando como referencia el tag con
|
|
@@ -147,9 +213,16 @@ Lista los tags estables disponibles para crear aplicaciones.
|
|
|
147
213
|
mova shell versions
|
|
148
214
|
```
|
|
149
215
|
|
|
216
|
+
### `mova shell`
|
|
217
|
+
|
|
218
|
+
Es el grupo de comandos relacionado con las versiones de la shell. Actualmente
|
|
219
|
+
su subcomando disponible es `mova shell versions`.
|
|
220
|
+
|
|
150
221
|
### `mova cap add`
|
|
151
222
|
|
|
152
|
-
Compila la
|
|
223
|
+
Compila la shell, añade una plataforma nativa y aplica su configuración inicial.
|
|
224
|
+
El `appId` y el `appName` se generan al crear la aplicación; revísalos en
|
|
225
|
+
`capacitor.config.ts` si necesitas personalizarlos antes de distribuirla.
|
|
153
226
|
|
|
154
227
|
```bash
|
|
155
228
|
mova cap add android
|
|
@@ -167,7 +240,27 @@ mova cap sync
|
|
|
167
240
|
|
|
168
241
|
En Android, el CLI aplica también los requisitos técnicos de los plugins
|
|
169
242
|
incluidos por la shell: configura el repositorio AAR de Background Runner y
|
|
170
|
-
eleva `minSdkVersion` a 28 cuando está instalado Local LLM.
|
|
243
|
+
eleva `minSdkVersion` a 28 cuando está instalado Local LLM. También añade la
|
|
244
|
+
entrada obligatoria de Google Maps. Para usar mapas reales, define una clave
|
|
245
|
+
restringida antes de sincronizar:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
OPEN_MOVA_GOOGLE_MAPS_ANDROID_API_KEY=tu_clave mova cap sync android
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Como alternativa, guarda `native.googleMaps.androidApiKey` en
|
|
252
|
+
`mova.config.json`. Sin una clave configurada la aplicación sigue arrancando,
|
|
253
|
+
pero Google Maps no estará operativo.
|
|
254
|
+
|
|
255
|
+
```json
|
|
256
|
+
{
|
|
257
|
+
"native": {
|
|
258
|
+
"googleMaps": {
|
|
259
|
+
"androidApiKey": "tu_clave"
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
```
|
|
171
264
|
|
|
172
265
|
### `mova cap open`
|
|
173
266
|
|
|
@@ -178,6 +271,11 @@ mova cap open android
|
|
|
178
271
|
mova cap open ios
|
|
179
272
|
```
|
|
180
273
|
|
|
274
|
+
### `mova cap`
|
|
275
|
+
|
|
276
|
+
Es el grupo de comandos para preparar y abrir los proyectos nativos de
|
|
277
|
+
Capacitor. Sus subcomandos son `add`, `sync` y `open`.
|
|
278
|
+
|
|
181
279
|
## Flujo habitual
|
|
182
280
|
|
|
183
281
|
```bash
|
|
@@ -190,6 +288,98 @@ mova start
|
|
|
190
288
|
|
|
191
289
|
Los microfrontales siempre se cargan remotamente mediante Native Federation. En producción, cada MF debe estar publicado en HTTPS y registrado con su `productionRemoteEntry`. Capacitor no copia los MFs al paquete nativo: la shell los carga desde sus URLs en ejecución.
|
|
192
290
|
|
|
193
|
-
##
|
|
291
|
+
## Flujo para Android
|
|
292
|
+
|
|
293
|
+
> [!WARNING]
|
|
294
|
+
> Antes de empezar debes tener instalado Android Studio, el Android SDK, las
|
|
295
|
+
> Build Tools, Platform Tools (`adb`) y un emulador o un dispositivo Android.
|
|
296
|
+
> También necesitas Node.js 22, npm y Git. Android Studio incluye un JDK
|
|
297
|
+
> compatible, por lo que normalmente no necesitas instalar otro manualmente.
|
|
298
|
+
|
|
299
|
+
Desde el directorio donde quieras crear la aplicación:
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
mova create mi-aplicacion
|
|
303
|
+
cd mi-aplicacion
|
|
304
|
+
npm install
|
|
305
|
+
npm --prefix mfs/home install
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Comprueba que cada microfrontal tiene un `productionRemoteEntry` HTTPS en
|
|
309
|
+
`mova.config.json`. Android no puede utilizar las URLs de desarrollo de
|
|
310
|
+
`localhost` como si fuera el navegador del ordenador.
|
|
311
|
+
|
|
312
|
+
Añade la plataforma Android. El comando compila la shell, ejecuta Capacitor y
|
|
313
|
+
aplica la configuración específica de los plugins instalados:
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
mova cap add android
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Si utilizas Google Maps, proporciona la clave antes de sincronizar:
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
OPEN_MOVA_GOOGLE_MAPS_ANDROID_API_KEY=tu_clave mova cap sync android
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
También puedes guardarla en `mova.config.json`, como se explica en la sección
|
|
326
|
+
[`mova cap sync`](#mova-cap-sync). Para volver a sincronizar después de
|
|
327
|
+
cambiar la shell, los plugins o la configuración:
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
mova cap sync android
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Abre el proyecto nativo en Android Studio:
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
mova cap open android
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
En Android Studio selecciona un emulador o dispositivo, espera a que termine
|
|
340
|
+
la sincronización de Gradle y pulsa **Run**. Para probar una nueva versión del
|
|
341
|
+
MF remoto no hace falta recompilar Android si la URL publicada no cambia; la
|
|
342
|
+
shell lo cargará al iniciar la aplicación.
|
|
343
|
+
|
|
344
|
+
## Flujo para iOS
|
|
345
|
+
|
|
346
|
+
> [!WARNING]
|
|
347
|
+
> Antes de empezar debes tener instalado Xcode, sus Command Line Tools y
|
|
348
|
+
> CocoaPods. Necesitas macOS, Node.js 22, npm y Git. Para ejecutar la app en un
|
|
349
|
+
> dispositivo físico necesitarás también una cuenta de Apple y la firma
|
|
350
|
+
> configurada en Xcode; el simulador no requiere un dispositivo registrado.
|
|
351
|
+
|
|
352
|
+
Desde el directorio donde quieras crear la aplicación:
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
mova create mi-aplicacion
|
|
356
|
+
cd mi-aplicacion
|
|
357
|
+
npm install
|
|
358
|
+
npm --prefix mfs/home install
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Comprueba que cada microfrontal tiene un `productionRemoteEntry` HTTPS en
|
|
362
|
+
`mova.config.json`. La aplicación iOS seguirá cargando esos microfrontales
|
|
363
|
+
remotos desde la shell; no se copian dentro del proyecto nativo.
|
|
364
|
+
|
|
365
|
+
Añade y sincroniza la plataforma iOS:
|
|
366
|
+
|
|
367
|
+
```bash
|
|
368
|
+
mova cap add ios
|
|
369
|
+
mova cap sync ios
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
Revisa en Xcode los permisos y la configuración nativa que necesiten los
|
|
373
|
+
plugins utilizados, por ejemplo las descripciones de cámara, fotos,
|
|
374
|
+
geolocalización o notificaciones en `Info.plist` y los entitlements
|
|
375
|
+
correspondientes.
|
|
376
|
+
|
|
377
|
+
Abre el proyecto en Xcode:
|
|
378
|
+
|
|
379
|
+
```bash
|
|
380
|
+
mova cap open ios
|
|
381
|
+
```
|
|
194
382
|
|
|
195
|
-
|
|
383
|
+
En Xcode selecciona un simulador o dispositivo, elige el esquema de la app y
|
|
384
|
+
pulsa **Run**. Después de cambiar la shell o los plugins, ejecuta de nuevo
|
|
385
|
+
`mova cap sync ios` antes de volver a abrir o ejecutar el proyecto.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
export function configureAndroidProject(applicationRoot, configuration) {
|
|
4
|
+
const packageConfiguration = readPackageConfiguration(applicationRoot);
|
|
5
|
+
configureMinimumSdk(applicationRoot, packageConfiguration);
|
|
6
|
+
configureBackgroundRunner(applicationRoot, packageConfiguration);
|
|
7
|
+
configureGoogleMaps(applicationRoot, configuration, packageConfiguration);
|
|
8
|
+
}
|
|
9
|
+
function configureGoogleMaps(applicationRoot, configuration, packageConfiguration) {
|
|
10
|
+
if (!packageConfiguration.dependencies?.['@capacitor/google-maps'])
|
|
11
|
+
return;
|
|
12
|
+
const manifestPath = join(applicationRoot, 'android', 'app', 'src', 'main', 'AndroidManifest.xml');
|
|
13
|
+
if (!existsSync(manifestPath)) {
|
|
14
|
+
throw new Error('No se encuentra AndroidManifest.xml para configurar Google Maps.');
|
|
15
|
+
}
|
|
16
|
+
const manifest = readFileSync(manifestPath, 'utf8');
|
|
17
|
+
const apiKeyMetadata = 'android:name="com.google.android.geo.API_KEY"';
|
|
18
|
+
if (!manifest.includes(apiKeyMetadata)) {
|
|
19
|
+
const applicationCloseTag = '</application>';
|
|
20
|
+
if (!manifest.includes(applicationCloseTag)) {
|
|
21
|
+
throw new Error('No se ha encontrado el elemento application en AndroidManifest.xml.');
|
|
22
|
+
}
|
|
23
|
+
const metadata = [
|
|
24
|
+
' <meta-data',
|
|
25
|
+
` ${apiKeyMetadata}`,
|
|
26
|
+
' android:value="@string/open_mova_google_maps_api_key" />',
|
|
27
|
+
].join('\n');
|
|
28
|
+
writeFileSync(manifestPath, manifest.replace(applicationCloseTag, `${metadata}\n ${applicationCloseTag}`), 'utf8');
|
|
29
|
+
}
|
|
30
|
+
const valuesDirectory = join(applicationRoot, 'android', 'app', 'src', 'main', 'res', 'values');
|
|
31
|
+
mkdirSync(valuesDirectory, { recursive: true });
|
|
32
|
+
const apiKey = process.env['OPEN_MOVA_GOOGLE_MAPS_ANDROID_API_KEY'] ??
|
|
33
|
+
configuration.native?.googleMaps?.androidApiKey ??
|
|
34
|
+
// Google Maps aborta la aplicación si falta por completo esta entrada.
|
|
35
|
+
'OPEN_MOVA_GOOGLE_MAPS_API_KEY_NOT_CONFIGURED';
|
|
36
|
+
const resource = [
|
|
37
|
+
'<?xml version="1.0" encoding="utf-8"?>',
|
|
38
|
+
'<resources>',
|
|
39
|
+
` <string name="open_mova_google_maps_api_key" translatable="false">${escapeXml(apiKey)}</string>`,
|
|
40
|
+
'</resources>',
|
|
41
|
+
'',
|
|
42
|
+
].join('\n');
|
|
43
|
+
writeFileSync(join(valuesDirectory, 'open_mova_google_maps.xml'), resource, 'utf8');
|
|
44
|
+
}
|
|
45
|
+
function configureMinimumSdk(applicationRoot, packageConfiguration) {
|
|
46
|
+
if (!packageConfiguration.dependencies?.['@capacitor/local-llm'])
|
|
47
|
+
return;
|
|
48
|
+
const variablesPath = join(applicationRoot, 'android', 'variables.gradle');
|
|
49
|
+
if (!existsSync(variablesPath)) {
|
|
50
|
+
throw new Error('No se encuentra android/variables.gradle para configurar Local LLM.');
|
|
51
|
+
}
|
|
52
|
+
const variables = readFileSync(variablesPath, 'utf8');
|
|
53
|
+
const minimumSdkPattern = /minSdkVersion\s*=\s*(\d+)/;
|
|
54
|
+
const currentMinimumSdk = Number(minimumSdkPattern.exec(variables)?.[1]);
|
|
55
|
+
if (!Number.isFinite(currentMinimumSdk)) {
|
|
56
|
+
throw new Error('No se ha encontrado minSdkVersion en android/variables.gradle.');
|
|
57
|
+
}
|
|
58
|
+
if (currentMinimumSdk >= 28)
|
|
59
|
+
return;
|
|
60
|
+
writeFileSync(variablesPath, variables.replace(minimumSdkPattern, 'minSdkVersion = 28'), 'utf8');
|
|
61
|
+
}
|
|
62
|
+
function configureBackgroundRunner(applicationRoot, packageConfiguration) {
|
|
63
|
+
if (!packageConfiguration.dependencies?.['@capacitor/background-runner'])
|
|
64
|
+
return;
|
|
65
|
+
const gradlePath = join(applicationRoot, 'android', 'app', 'build.gradle');
|
|
66
|
+
if (!existsSync(gradlePath)) {
|
|
67
|
+
throw new Error('No se encuentra android/app/build.gradle para configurar Background Runner.');
|
|
68
|
+
}
|
|
69
|
+
const repositoryEntry = "dirs '../../node_modules/@capacitor/background-runner/android/src/main/libs', 'libs'";
|
|
70
|
+
const gradle = readFileSync(gradlePath, 'utf8');
|
|
71
|
+
if (gradle.includes(repositoryEntry))
|
|
72
|
+
return;
|
|
73
|
+
const flatDirectory = /flatDir\s*\{/;
|
|
74
|
+
if (!flatDirectory.test(gradle)) {
|
|
75
|
+
throw new Error('No se ha encontrado el bloque flatDir en android/app/build.gradle.');
|
|
76
|
+
}
|
|
77
|
+
writeFileSync(gradlePath, gradle.replace(flatDirectory, (match) => `${match}\n ${repositoryEntry}`), 'utf8');
|
|
78
|
+
}
|
|
79
|
+
function readPackageConfiguration(applicationRoot) {
|
|
80
|
+
return JSON.parse(readFileSync(join(applicationRoot, 'package.json'), 'utf8'));
|
|
81
|
+
}
|
|
82
|
+
function escapeXml(value) {
|
|
83
|
+
return value.replace(/[&<>"']/g, (character) => ({
|
|
84
|
+
'&': '&',
|
|
85
|
+
'<': '<',
|
|
86
|
+
'>': '>',
|
|
87
|
+
'"': '"',
|
|
88
|
+
"'": ''',
|
|
89
|
+
})[character] ?? character);
|
|
90
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const LATEST_CONFIGURATION_SCHEMA_VERSION = 2;
|
|
2
|
+
export interface ConfigurationMigrationResult {
|
|
3
|
+
readonly value: unknown;
|
|
4
|
+
readonly migrations: readonly string[];
|
|
5
|
+
}
|
|
6
|
+
export declare function migrateConfiguration(value: unknown, applicationRoot: string): ConfigurationMigrationResult;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
export const LATEST_CONFIGURATION_SCHEMA_VERSION = 2;
|
|
4
|
+
export function migrateConfiguration(value, applicationRoot) {
|
|
5
|
+
if (!isRecord(value) || typeof value.schemaVersion !== 'number') {
|
|
6
|
+
return { value, migrations: [] };
|
|
7
|
+
}
|
|
8
|
+
if (value.schemaVersion > LATEST_CONFIGURATION_SCHEMA_VERSION) {
|
|
9
|
+
throw new Error(`mova.config.json usa schemaVersion ${value.schemaVersion}, pero este CLI solo admite hasta ${LATEST_CONFIGURATION_SCHEMA_VERSION}.`);
|
|
10
|
+
}
|
|
11
|
+
let migrated = value;
|
|
12
|
+
const migrations = [];
|
|
13
|
+
if (migrated.schemaVersion === 1) {
|
|
14
|
+
migrated = migrateVersionOne(migrated, applicationRoot);
|
|
15
|
+
migrations.push('1 → 2: declarar la compatibilidad de Core de cada microfrontal');
|
|
16
|
+
}
|
|
17
|
+
return { value: migrated, migrations };
|
|
18
|
+
}
|
|
19
|
+
function migrateVersionOne(configuration, applicationRoot) {
|
|
20
|
+
const shellCoreVersion = readCoreVersion(join(applicationRoot, 'package.json'));
|
|
21
|
+
const microfrontends = Array.isArray(configuration.microfrontends)
|
|
22
|
+
? configuration.microfrontends.map((entry) => {
|
|
23
|
+
if (!isRecord(entry))
|
|
24
|
+
return entry;
|
|
25
|
+
const sourcePath = typeof entry.sourcePath === 'string' ? entry.sourcePath : undefined;
|
|
26
|
+
const localCoreVersion = sourcePath
|
|
27
|
+
? readCoreVersion(join(resolve(applicationRoot, sourcePath), 'package.json'))
|
|
28
|
+
: undefined;
|
|
29
|
+
const requiredCoreVersion = localCoreVersion ?? shellCoreVersion;
|
|
30
|
+
return {
|
|
31
|
+
...entry,
|
|
32
|
+
// El comodín conserva aplicaciones antiguas cuyo remoto no tenía
|
|
33
|
+
// metadatos; se debe concretar al actualizar el MF.
|
|
34
|
+
compatibility: { requiredCoreVersion: requiredCoreVersion ?? '*' },
|
|
35
|
+
};
|
|
36
|
+
})
|
|
37
|
+
: configuration.microfrontends;
|
|
38
|
+
return {
|
|
39
|
+
...configuration,
|
|
40
|
+
schemaVersion: 2,
|
|
41
|
+
microfrontends,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function readCoreVersion(packagePath) {
|
|
45
|
+
if (!existsSync(packagePath))
|
|
46
|
+
return undefined;
|
|
47
|
+
try {
|
|
48
|
+
const packageConfiguration = JSON.parse(readFileSync(packagePath, 'utf8'));
|
|
49
|
+
return packageConfiguration.dependencies?.['@open-mova/core'];
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function isRecord(value) {
|
|
56
|
+
return typeof value === 'object' && value !== null;
|
|
57
|
+
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { MicrofrontendConfiguration, OpenMovaApplicationConfiguration } from '../types.js';
|
|
2
2
|
export declare const APPLICATION_CONFIGURATION_FILE = "mova.config.json";
|
|
3
|
+
export interface ApplicationConfigurationDocument {
|
|
4
|
+
readonly configuration: OpenMovaApplicationConfiguration;
|
|
5
|
+
readonly migrations: readonly string[];
|
|
6
|
+
}
|
|
3
7
|
export declare function findApplicationRoot(startDirectory: string): string | undefined;
|
|
4
8
|
export declare function requireApplicationRoot(startDirectory: string): string;
|
|
5
9
|
export declare function readApplicationConfiguration(applicationRoot: string): OpenMovaApplicationConfiguration;
|
|
10
|
+
export declare function readApplicationConfigurationDocument(applicationRoot: string): ApplicationConfigurationDocument;
|
|
6
11
|
export declare function writeApplicationConfiguration(applicationRoot: string, configuration: OpenMovaApplicationConfiguration): void;
|
|
7
12
|
export declare function addMicrofrontend(configuration: OpenMovaApplicationConfiguration, microfrontend: MicrofrontendConfiguration): OpenMovaApplicationConfiguration;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { LATEST_CONFIGURATION_SCHEMA_VERSION, migrateConfiguration, } from './configuration-migrations.js';
|
|
3
4
|
export const APPLICATION_CONFIGURATION_FILE = 'mova.config.json';
|
|
4
5
|
export function findApplicationRoot(startDirectory) {
|
|
5
6
|
let currentDirectory = resolve(startDirectory);
|
|
@@ -22,17 +23,16 @@ export function requireApplicationRoot(startDirectory) {
|
|
|
22
23
|
return applicationRoot;
|
|
23
24
|
}
|
|
24
25
|
export function readApplicationConfiguration(applicationRoot) {
|
|
26
|
+
return readApplicationConfigurationDocument(applicationRoot).configuration;
|
|
27
|
+
}
|
|
28
|
+
export function readApplicationConfigurationDocument(applicationRoot) {
|
|
25
29
|
const configurationPath = join(applicationRoot, APPLICATION_CONFIGURATION_FILE);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
throw error;
|
|
33
|
-
}
|
|
34
|
-
throw new Error(`No se ha podido leer ${configurationPath}.`);
|
|
35
|
-
}
|
|
30
|
+
const parsed = JSON.parse(readFileSync(configurationPath, 'utf8'));
|
|
31
|
+
const migration = migrateConfiguration(parsed, applicationRoot);
|
|
32
|
+
return {
|
|
33
|
+
configuration: validateApplicationConfiguration(migration.value, configurationPath),
|
|
34
|
+
migrations: migration.migrations,
|
|
35
|
+
};
|
|
36
36
|
}
|
|
37
37
|
export function writeApplicationConfiguration(applicationRoot, configuration) {
|
|
38
38
|
const configurationPath = join(applicationRoot, APPLICATION_CONFIGURATION_FILE);
|
|
@@ -57,7 +57,8 @@ function validateApplicationConfiguration(value, configurationPath) {
|
|
|
57
57
|
if (!isRecord(value)) {
|
|
58
58
|
throw new Error(`${configurationPath} no contiene un objeto JSON válido.`);
|
|
59
59
|
}
|
|
60
|
-
if (value.schemaVersion !==
|
|
60
|
+
if (value.schemaVersion !== LATEST_CONFIGURATION_SCHEMA_VERSION ||
|
|
61
|
+
typeof value.name !== 'string') {
|
|
61
62
|
throw new Error(`${configurationPath} no tiene el formato de Open Mova esperado.`);
|
|
62
63
|
}
|
|
63
64
|
if (!Array.isArray(value.microfrontends)) {
|
|
@@ -77,11 +78,32 @@ function validateApplicationConfiguration(value, configurationPath) {
|
|
|
77
78
|
commit: value.shell.commit,
|
|
78
79
|
};
|
|
79
80
|
}
|
|
81
|
+
let native;
|
|
82
|
+
if (value.native !== undefined) {
|
|
83
|
+
if (!isRecord(value.native)) {
|
|
84
|
+
throw new Error(`${configurationPath} contiene una configuración nativa no válida.`);
|
|
85
|
+
}
|
|
86
|
+
if (value.native.googleMaps !== undefined) {
|
|
87
|
+
if (!isRecord(value.native.googleMaps) ||
|
|
88
|
+
typeof value.native.googleMaps.androidApiKey !== 'string') {
|
|
89
|
+
throw new Error(`${configurationPath} contiene una clave de Google Maps no válida.`);
|
|
90
|
+
}
|
|
91
|
+
native = {
|
|
92
|
+
googleMaps: {
|
|
93
|
+
androidApiKey: value.native.googleMaps.androidApiKey,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
native = {};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
80
101
|
const microfrontends = value.microfrontends.map((entry) => validateMicrofrontend(entry, configurationPath));
|
|
81
102
|
return {
|
|
82
|
-
schemaVersion:
|
|
103
|
+
schemaVersion: LATEST_CONFIGURATION_SCHEMA_VERSION,
|
|
83
104
|
name: value.name,
|
|
84
105
|
...(shell ? { shell } : {}),
|
|
106
|
+
...(native ? { native } : {}),
|
|
85
107
|
microfrontends,
|
|
86
108
|
};
|
|
87
109
|
}
|
|
@@ -109,12 +131,18 @@ function validateMicrofrontend(value, configurationPath) {
|
|
|
109
131
|
typeof value.productionRemoteEntry !== 'string') {
|
|
110
132
|
throw new Error(`${configurationPath} contiene una URL de producción no válida.`);
|
|
111
133
|
}
|
|
112
|
-
if (
|
|
113
|
-
typeof value.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
134
|
+
if (!isRecord(value.compatibility) ||
|
|
135
|
+
typeof value.compatibility.requiredCoreVersion !== 'string' ||
|
|
136
|
+
value.compatibility.requiredCoreVersion.trim() === '') {
|
|
137
|
+
throw new Error(`${configurationPath} contiene un microfrontal sin compatibilidad de Core declarada.`);
|
|
138
|
+
}
|
|
139
|
+
if (value.template !== undefined &&
|
|
140
|
+
(!isRecord(value.template) ||
|
|
141
|
+
typeof value.template.repository !== 'string' ||
|
|
142
|
+
typeof value.template.version !== 'string' ||
|
|
143
|
+
typeof value.template.commit !== 'string' ||
|
|
144
|
+
value.template.project !== 'open-mova-mf-template' ||
|
|
145
|
+
(value.template.profile !== 'minimal' && value.template.profile !== 'demo'))) {
|
|
118
146
|
throw new Error(`${configurationPath} contiene una plantilla de microfrontal no válida.`);
|
|
119
147
|
}
|
|
120
148
|
return {
|
|
@@ -127,7 +155,12 @@ function validateMicrofrontend(value, configurationPath) {
|
|
|
127
155
|
? {}
|
|
128
156
|
: { productionRemoteEntry: value.productionRemoteEntry }),
|
|
129
157
|
...(value.sourcePath === undefined ? {} : { sourcePath: value.sourcePath }),
|
|
130
|
-
|
|
158
|
+
compatibility: {
|
|
159
|
+
requiredCoreVersion: value.compatibility.requiredCoreVersion,
|
|
160
|
+
},
|
|
161
|
+
...(value.template === undefined
|
|
162
|
+
? {}
|
|
163
|
+
: { template: value.template }),
|
|
131
164
|
};
|
|
132
165
|
}
|
|
133
166
|
function isRecord(value) {
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { OpenMovaApplicationConfiguration } from '../types.js';
|
|
2
|
+
export declare function areCoreRangesCompatible(first: string, second: string): boolean;
|
|
3
|
+
export declare function findIncompatibleMicrofrontends(configuration: OpenMovaApplicationConfiguration, shellCoreVersion: string): readonly string[];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { intersects, validRange } from 'semver';
|
|
2
|
+
export function areCoreRangesCompatible(first, second) {
|
|
3
|
+
return validRange(first) !== null && validRange(second) !== null && intersects(first, second);
|
|
4
|
+
}
|
|
5
|
+
export function findIncompatibleMicrofrontends(configuration, shellCoreVersion) {
|
|
6
|
+
return configuration.microfrontends
|
|
7
|
+
.filter((microfrontend) => !areCoreRangesCompatible(shellCoreVersion, microfrontend.compatibility.requiredCoreVersion))
|
|
8
|
+
.map((microfrontend) => `mova.config.json#microfrontends.${microfrontend.name}.compatibility ` +
|
|
9
|
+
`(shell ${shellCoreVersion}, MF ${microfrontend.compatibility.requiredCoreVersion})`);
|
|
10
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type DoctorCheckStatus = 'ok' | 'warning' | 'error';
|
|
2
|
+
export interface DoctorCheck {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly status: DoctorCheckStatus;
|
|
5
|
+
readonly message: string;
|
|
6
|
+
readonly detail?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface DoctorReport {
|
|
9
|
+
readonly applicationRoot?: string;
|
|
10
|
+
readonly checks: readonly DoctorCheck[];
|
|
11
|
+
}
|
|
12
|
+
export declare function inspectDevelopmentEnvironment(startDirectory: string): DoctorReport;
|