@open-mova/cli 0.1.20 → 0.1.22
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 +237 -6
- package/dist/application/android-configuration.d.ts +2 -0
- package/dist/application/android-configuration.js +115 -0
- package/dist/application/configuration-migrations.d.ts +6 -0
- package/dist/application/configuration-migrations.js +103 -0
- package/dist/application/configuration.d.ts +5 -0
- package/dist/application/configuration.js +38 -20
- 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/native-capabilities.d.ts +15 -0
- package/dist/application/native-capabilities.js +143 -0
- package/dist/application/project-update.d.ts +8 -0
- package/dist/application/project-update.js +106 -0
- package/dist/application/shell-configuration.d.ts +20 -0
- package/dist/application/shell-configuration.js +52 -1
- 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 +11 -108
- package/dist/cli.js +2 -0
- package/dist/commands/capacitor.js +55 -90
- package/dist/commands/create.js +3 -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 +7 -1
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -10,14 +10,23 @@ 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)
|
|
26
|
+
- [`mova cap list`](#mova-cap-list)
|
|
27
|
+
- [`mova cap enable`](#mova-cap-enable)
|
|
28
|
+
- [`mova cap disable`](#mova-cap-disable)
|
|
29
|
+
- [`mova cap doctor`](#mova-cap-doctor)
|
|
21
30
|
- [`mova cap add`](#mova-cap-add)
|
|
22
31
|
- [`mova cap sync`](#mova-cap-sync)
|
|
23
32
|
- [`mova cap open`](#mova-cap-open)
|
|
@@ -47,6 +56,17 @@ npm link
|
|
|
47
56
|
|
|
48
57
|
## Comandos
|
|
49
58
|
|
|
59
|
+
### `mova --help` y `mova --version`
|
|
60
|
+
|
|
61
|
+
Muestra la ayuda del CLI o la versión instalada.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
mova --help
|
|
65
|
+
mova --version
|
|
66
|
+
mova mf --help
|
|
67
|
+
mova cap --help
|
|
68
|
+
```
|
|
69
|
+
|
|
50
70
|
### `mova create`
|
|
51
71
|
|
|
52
72
|
Crea una aplicación completa. Descarga una shell versionada, el core y un MF
|
|
@@ -63,6 +83,11 @@ mova create mi-aplicacion --empty
|
|
|
63
83
|
|
|
64
84
|
Sin `--shell-version` se usa el tag estable más reciente (`vMAJOR.MINOR.PATCH`). `--empty` omite el MF inicial.
|
|
65
85
|
|
|
86
|
+
### `mova mf`
|
|
87
|
+
|
|
88
|
+
Es el grupo de comandos para crear y registrar microfrontales en la aplicación.
|
|
89
|
+
Usa `mova mf --help` para consultar sus subcomandos.
|
|
90
|
+
|
|
66
91
|
### `mova mf create`
|
|
67
92
|
|
|
68
93
|
Crea un microfrontal local, lo registra y actualiza la configuración de federación de la shell.
|
|
@@ -97,6 +122,32 @@ mova mf add --name catalog --route productos \
|
|
|
97
122
|
|
|
98
123
|
El CLI actualiza `mova.config.json`, `src/assets/federation.manifest.json` y `src/app/application.config.ts`.
|
|
99
124
|
|
|
125
|
+
Para un MF remoto que no tenga un proyecto local, declara explícitamente su
|
|
126
|
+
rango de Core:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
mova mf add --name catalog --route catalog \
|
|
130
|
+
--remote catalog-microfrontend \
|
|
131
|
+
--remote-entry https://cdn.example.com/catalog/remoteEntry.json \
|
|
132
|
+
--core-version '^0.2.2'
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### `mova mf update`
|
|
136
|
+
|
|
137
|
+
Actualiza un MF local que fue creado desde una plantilla de Open Mova. Compara
|
|
138
|
+
la plantilla original, el proyecto actual y la nueva versión en tres pasos:
|
|
139
|
+
solo aplica cambios que no hayan sido personalizados y deja los conflictos
|
|
140
|
+
para revisión.
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
mova mf update home --check
|
|
144
|
+
mova mf update home --to v0.2.2
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
El comando conserva el nombre, la ruta, el puerto, el perfil y las
|
|
148
|
+
dependencias propias del proveedor. Si cambia `package.json`, elimina el
|
|
149
|
+
lockfile para que ejecutes `npm install` en el MF.
|
|
150
|
+
|
|
100
151
|
### `mova start`
|
|
101
152
|
|
|
102
153
|
Arranca los MFs locales registrados y después la shell. Los MFs configurados solo con una URL remota no se arrancan localmente.
|
|
@@ -122,6 +173,25 @@ Muestra la aplicación detectada, la versión de shell y sus microfrontales. Tam
|
|
|
122
173
|
mova info
|
|
123
174
|
```
|
|
124
175
|
|
|
176
|
+
### `mova doctor`
|
|
177
|
+
|
|
178
|
+
Diagnostica el entorno de desarrollo y la aplicación encontrada desde el
|
|
179
|
+
directorio actual. Comprueba las herramientas básicas, las dependencias, la
|
|
180
|
+
configuración de la shell y los microfrontales, las URLs de producción y la
|
|
181
|
+
compatibilidad de Core.
|
|
182
|
+
|
|
183
|
+
Si la aplicación contiene plataformas Capacitor, también revisa los
|
|
184
|
+
requisitos disponibles de Android o iOS, como el SDK, `adb`, emuladores,
|
|
185
|
+
Xcode, CocoaPods, claves y descripciones de permisos.
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
mova doctor
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Los avisos informan de elementos opcionales o todavía no configurados. Los
|
|
192
|
+
errores hacen que el comando termine con un código distinto de cero, por lo
|
|
193
|
+
que también puede utilizarse en scripts de validación.
|
|
194
|
+
|
|
125
195
|
### `mova update`
|
|
126
196
|
|
|
127
197
|
Actualiza los archivos técnicos de la shell usando como referencia el tag con
|
|
@@ -147,15 +217,63 @@ Lista los tags estables disponibles para crear aplicaciones.
|
|
|
147
217
|
mova shell versions
|
|
148
218
|
```
|
|
149
219
|
|
|
220
|
+
### `mova shell`
|
|
221
|
+
|
|
222
|
+
Es el grupo de comandos relacionado con las versiones de la shell. Actualmente
|
|
223
|
+
su subcomando disponible es `mova shell versions`.
|
|
224
|
+
|
|
150
225
|
### `mova cap add`
|
|
151
226
|
|
|
152
|
-
Compila la
|
|
227
|
+
Compila la shell, añade una plataforma nativa y aplica su configuración inicial.
|
|
228
|
+
El `appId` y el `appName` se generan al crear la aplicación; revísalos en
|
|
229
|
+
`capacitor.config.ts` si necesitas personalizarlos antes de distribuirla.
|
|
153
230
|
|
|
154
231
|
```bash
|
|
155
232
|
mova cap add android
|
|
156
233
|
mova cap add ios
|
|
157
234
|
```
|
|
158
235
|
|
|
236
|
+
### `mova cap list`
|
|
237
|
+
|
|
238
|
+
Muestra el catálogo de capacidades nativas. `✓` indica que la capacidad está
|
|
239
|
+
habilitada y `○` que está disponible pero no instalada.
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
mova cap list
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### `mova cap enable`
|
|
246
|
+
|
|
247
|
+
Habilita una o varias capacidades. Instala únicamente sus paquetes npm,
|
|
248
|
+
actualiza `mova.config.json`, regenera el provider Angular y sincroniza las
|
|
249
|
+
plataformas nativas que ya existan.
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
mova cap enable camera device geolocation
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### `mova cap disable`
|
|
256
|
+
|
|
257
|
+
Deshabilita capacidades y elimina los paquetes que ninguna otra capacidad
|
|
258
|
+
activa necesita. El contrato de Core permanece disponible, pero
|
|
259
|
+
`isAvailable()` devuelve `false` y cualquier operación explica cómo volver a
|
|
260
|
+
habilitar la capacidad.
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
mova cap disable geolocation
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### `mova cap doctor`
|
|
267
|
+
|
|
268
|
+
Muestra plataformas compatibles, permisos, SDK mínimo y configuración manual
|
|
269
|
+
de las capacidades activas.
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
mova cap doctor
|
|
273
|
+
mova cap doctor android
|
|
274
|
+
mova cap doctor ios
|
|
275
|
+
```
|
|
276
|
+
|
|
159
277
|
### `mova cap sync`
|
|
160
278
|
|
|
161
279
|
Sincroniza la shell compilada, los recursos y los plugins con la plataforma. Sin plataforma, sincroniza todas las plataformas añadidas.
|
|
@@ -165,9 +283,8 @@ mova cap sync android
|
|
|
165
283
|
mova cap sync
|
|
166
284
|
```
|
|
167
285
|
|
|
168
|
-
En Android, el CLI aplica
|
|
169
|
-
|
|
170
|
-
eleva `minSdkVersion` a 28 cuando está instalado Local LLM. También añade la
|
|
286
|
+
En Android, el CLI aplica los requisitos declarados por las capacidades
|
|
287
|
+
activas: permisos, SDK mínimo, el repositorio AAR de Background Runner y la
|
|
171
288
|
entrada obligatoria de Google Maps. Para usar mapas reales, define una clave
|
|
172
289
|
restringida antes de sincronizar:
|
|
173
290
|
|
|
@@ -198,6 +315,12 @@ mova cap open android
|
|
|
198
315
|
mova cap open ios
|
|
199
316
|
```
|
|
200
317
|
|
|
318
|
+
### `mova cap`
|
|
319
|
+
|
|
320
|
+
Es el grupo de comandos para preparar y abrir los proyectos nativos de
|
|
321
|
+
Capacitor. También permite seleccionar capacidades mediante `list`, `enable`,
|
|
322
|
+
`disable` y `doctor`.
|
|
323
|
+
|
|
201
324
|
## Flujo habitual
|
|
202
325
|
|
|
203
326
|
```bash
|
|
@@ -205,11 +328,119 @@ mova create mi-aplicacion
|
|
|
205
328
|
cd mi-aplicacion
|
|
206
329
|
npm install
|
|
207
330
|
npm --prefix mfs/home install
|
|
331
|
+
mova cap enable camera device
|
|
208
332
|
mova start
|
|
209
333
|
```
|
|
210
334
|
|
|
211
335
|
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.
|
|
212
336
|
|
|
213
|
-
##
|
|
337
|
+
## Flujo para Android
|
|
338
|
+
|
|
339
|
+
> [!WARNING]
|
|
340
|
+
> Antes de empezar debes tener instalado Android Studio, el Android SDK, las
|
|
341
|
+
> Build Tools, Platform Tools (`adb`) y un emulador o un dispositivo Android.
|
|
342
|
+
> También necesitas Node.js 22, npm y Git. Android Studio incluye un JDK
|
|
343
|
+
> compatible, por lo que normalmente no necesitas instalar otro manualmente.
|
|
344
|
+
|
|
345
|
+
Desde el directorio donde quieras crear la aplicación:
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
mova create mi-aplicacion
|
|
349
|
+
cd mi-aplicacion
|
|
350
|
+
npm install
|
|
351
|
+
npm --prefix mfs/home install
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Comprueba que cada microfrontal tiene un `productionRemoteEntry` HTTPS en
|
|
355
|
+
`mova.config.json`. Android no puede utilizar las URLs de desarrollo de
|
|
356
|
+
`localhost` como si fuera el navegador del ordenador.
|
|
357
|
+
|
|
358
|
+
Añade la plataforma Android. El comando compila la shell, ejecuta Capacitor y
|
|
359
|
+
aplica la configuración específica de los plugins instalados:
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
mova cap add android
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Antes o después de añadir la plataforma puedes habilitar solo lo que use la
|
|
366
|
+
aplicación:
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
mova cap enable camera device
|
|
370
|
+
mova cap doctor android
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Si utilizas Google Maps, proporciona la clave antes de sincronizar:
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
OPEN_MOVA_GOOGLE_MAPS_ANDROID_API_KEY=tu_clave mova cap sync android
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
También puedes guardarla en `mova.config.json`, como se explica en la sección
|
|
380
|
+
[`mova cap sync`](#mova-cap-sync). Para volver a sincronizar después de
|
|
381
|
+
cambiar la shell, los plugins o la configuración:
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
mova cap sync android
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
Abre el proyecto nativo en Android Studio:
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
mova cap open android
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
En Android Studio selecciona un emulador o dispositivo, espera a que termine
|
|
394
|
+
la sincronización de Gradle y pulsa **Run**. Para probar una nueva versión del
|
|
395
|
+
MF remoto no hace falta recompilar Android si la URL publicada no cambia; la
|
|
396
|
+
shell lo cargará al iniciar la aplicación.
|
|
397
|
+
|
|
398
|
+
## Flujo para iOS
|
|
399
|
+
|
|
400
|
+
> [!WARNING]
|
|
401
|
+
> Antes de empezar debes tener instalado Xcode, sus Command Line Tools y
|
|
402
|
+
> CocoaPods. Necesitas macOS, Node.js 22, npm y Git. Para ejecutar la app en un
|
|
403
|
+
> dispositivo físico necesitarás también una cuenta de Apple y la firma
|
|
404
|
+
> configurada en Xcode; el simulador no requiere un dispositivo registrado.
|
|
405
|
+
|
|
406
|
+
Desde el directorio donde quieras crear la aplicación:
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
mova create mi-aplicacion
|
|
410
|
+
cd mi-aplicacion
|
|
411
|
+
npm install
|
|
412
|
+
npm --prefix mfs/home install
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Comprueba que cada microfrontal tiene un `productionRemoteEntry` HTTPS en
|
|
416
|
+
`mova.config.json`. La aplicación iOS seguirá cargando esos microfrontales
|
|
417
|
+
remotos desde la shell; no se copian dentro del proyecto nativo.
|
|
418
|
+
|
|
419
|
+
Añade y sincroniza la plataforma iOS:
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
mova cap add ios
|
|
423
|
+
mova cap sync ios
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
Activa previamente las capacidades que necesites y revisa sus requisitos:
|
|
427
|
+
|
|
428
|
+
```bash
|
|
429
|
+
mova cap enable camera device
|
|
430
|
+
mova cap doctor ios
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Revisa en Xcode los permisos y la configuración nativa que necesiten los
|
|
434
|
+
plugins utilizados, por ejemplo las descripciones de cámara, fotos,
|
|
435
|
+
geolocalización o notificaciones en `Info.plist` y los entitlements
|
|
436
|
+
correspondientes.
|
|
437
|
+
|
|
438
|
+
Abre el proyecto en Xcode:
|
|
439
|
+
|
|
440
|
+
```bash
|
|
441
|
+
mova cap open ios
|
|
442
|
+
```
|
|
214
443
|
|
|
215
|
-
|
|
444
|
+
En Xcode selecciona un simulador o dispositivo, elige el esquema de la app y
|
|
445
|
+
pulsa **Run**. Después de cambiar la shell o los plugins, ejecuta de nuevo
|
|
446
|
+
`mova cap sync ios` antes de volver a abrir o ejecutar el proyecto.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { readNativeCapabilityCatalog } from './shell-configuration.js';
|
|
4
|
+
export function configureAndroidProject(applicationRoot, configuration) {
|
|
5
|
+
const packageConfiguration = readPackageConfiguration(applicationRoot);
|
|
6
|
+
configureMinimumSdk(applicationRoot, configuration);
|
|
7
|
+
configurePermissions(applicationRoot, configuration);
|
|
8
|
+
configureBackgroundRunner(applicationRoot, packageConfiguration);
|
|
9
|
+
configureGoogleMaps(applicationRoot, configuration, packageConfiguration);
|
|
10
|
+
}
|
|
11
|
+
function configureGoogleMaps(applicationRoot, configuration, packageConfiguration) {
|
|
12
|
+
if (!packageConfiguration.dependencies?.['@capacitor/google-maps'])
|
|
13
|
+
return;
|
|
14
|
+
const manifestPath = join(applicationRoot, 'android', 'app', 'src', 'main', 'AndroidManifest.xml');
|
|
15
|
+
if (!existsSync(manifestPath)) {
|
|
16
|
+
throw new Error('No se encuentra AndroidManifest.xml para configurar Google Maps.');
|
|
17
|
+
}
|
|
18
|
+
const manifest = readFileSync(manifestPath, 'utf8');
|
|
19
|
+
const apiKeyMetadata = 'android:name="com.google.android.geo.API_KEY"';
|
|
20
|
+
if (!manifest.includes(apiKeyMetadata)) {
|
|
21
|
+
const applicationCloseTag = '</application>';
|
|
22
|
+
if (!manifest.includes(applicationCloseTag)) {
|
|
23
|
+
throw new Error('No se ha encontrado el elemento application en AndroidManifest.xml.');
|
|
24
|
+
}
|
|
25
|
+
const metadata = [
|
|
26
|
+
' <meta-data',
|
|
27
|
+
` ${apiKeyMetadata}`,
|
|
28
|
+
' android:value="@string/open_mova_google_maps_api_key" />',
|
|
29
|
+
].join('\n');
|
|
30
|
+
writeFileSync(manifestPath, manifest.replace(applicationCloseTag, `${metadata}\n ${applicationCloseTag}`), 'utf8');
|
|
31
|
+
}
|
|
32
|
+
const valuesDirectory = join(applicationRoot, 'android', 'app', 'src', 'main', 'res', 'values');
|
|
33
|
+
mkdirSync(valuesDirectory, { recursive: true });
|
|
34
|
+
const apiKey = process.env['OPEN_MOVA_GOOGLE_MAPS_ANDROID_API_KEY'] ??
|
|
35
|
+
configuration.native?.googleMaps?.androidApiKey ??
|
|
36
|
+
// Google Maps aborta la aplicación si falta por completo esta entrada.
|
|
37
|
+
'OPEN_MOVA_GOOGLE_MAPS_API_KEY_NOT_CONFIGURED';
|
|
38
|
+
const resource = [
|
|
39
|
+
'<?xml version="1.0" encoding="utf-8"?>',
|
|
40
|
+
'<resources>',
|
|
41
|
+
` <string name="open_mova_google_maps_api_key" translatable="false">${escapeXml(apiKey)}</string>`,
|
|
42
|
+
'</resources>',
|
|
43
|
+
'',
|
|
44
|
+
].join('\n');
|
|
45
|
+
writeFileSync(join(valuesDirectory, 'open_mova_google_maps.xml'), resource, 'utf8');
|
|
46
|
+
}
|
|
47
|
+
function configureMinimumSdk(applicationRoot, configuration) {
|
|
48
|
+
const enabled = new Set(configuration.native?.capabilities ?? []);
|
|
49
|
+
const requiredMinimumSdk = Math.max(0, ...readNativeCapabilityCatalog(applicationRoot)
|
|
50
|
+
.capabilities.filter((capability) => enabled.has(capability.name))
|
|
51
|
+
.map((capability) => capability.minimumAndroidSdk ?? 0));
|
|
52
|
+
if (requiredMinimumSdk === 0)
|
|
53
|
+
return;
|
|
54
|
+
const variablesPath = join(applicationRoot, 'android', 'variables.gradle');
|
|
55
|
+
if (!existsSync(variablesPath)) {
|
|
56
|
+
throw new Error('No se encuentra android/variables.gradle para configurar Local LLM.');
|
|
57
|
+
}
|
|
58
|
+
const variables = readFileSync(variablesPath, 'utf8');
|
|
59
|
+
const minimumSdkPattern = /minSdkVersion\s*=\s*(\d+)/;
|
|
60
|
+
const currentMinimumSdk = Number(minimumSdkPattern.exec(variables)?.[1]);
|
|
61
|
+
if (!Number.isFinite(currentMinimumSdk)) {
|
|
62
|
+
throw new Error('No se ha encontrado minSdkVersion en android/variables.gradle.');
|
|
63
|
+
}
|
|
64
|
+
if (currentMinimumSdk >= requiredMinimumSdk)
|
|
65
|
+
return;
|
|
66
|
+
writeFileSync(variablesPath, variables.replace(minimumSdkPattern, `minSdkVersion = ${requiredMinimumSdk}`), 'utf8');
|
|
67
|
+
}
|
|
68
|
+
function configurePermissions(applicationRoot, configuration) {
|
|
69
|
+
const manifestPath = join(applicationRoot, 'android', 'app', 'src', 'main', 'AndroidManifest.xml');
|
|
70
|
+
if (!existsSync(manifestPath))
|
|
71
|
+
return;
|
|
72
|
+
const enabled = new Set(configuration.native?.capabilities ?? []);
|
|
73
|
+
const permissions = [
|
|
74
|
+
...new Set(readNativeCapabilityCatalog(applicationRoot)
|
|
75
|
+
.capabilities.filter((capability) => enabled.has(capability.name))
|
|
76
|
+
.flatMap((capability) => capability.permissions?.android ?? [])),
|
|
77
|
+
];
|
|
78
|
+
let manifest = readFileSync(manifestPath, 'utf8');
|
|
79
|
+
const applicationTag = /\s*<application\b/;
|
|
80
|
+
for (const permission of permissions) {
|
|
81
|
+
if (manifest.includes(`android:name="${permission}"`))
|
|
82
|
+
continue;
|
|
83
|
+
manifest = manifest.replace(applicationTag, `\n <uses-permission android:name="${permission}" />\n\n <application`);
|
|
84
|
+
}
|
|
85
|
+
writeFileSync(manifestPath, manifest, 'utf8');
|
|
86
|
+
}
|
|
87
|
+
function configureBackgroundRunner(applicationRoot, packageConfiguration) {
|
|
88
|
+
if (!packageConfiguration.dependencies?.['@capacitor/background-runner'])
|
|
89
|
+
return;
|
|
90
|
+
const gradlePath = join(applicationRoot, 'android', 'app', 'build.gradle');
|
|
91
|
+
if (!existsSync(gradlePath)) {
|
|
92
|
+
throw new Error('No se encuentra android/app/build.gradle para configurar Background Runner.');
|
|
93
|
+
}
|
|
94
|
+
const repositoryEntry = "dirs '../../node_modules/@capacitor/background-runner/android/src/main/libs', 'libs'";
|
|
95
|
+
const gradle = readFileSync(gradlePath, 'utf8');
|
|
96
|
+
if (gradle.includes(repositoryEntry))
|
|
97
|
+
return;
|
|
98
|
+
const flatDirectory = /flatDir\s*\{/;
|
|
99
|
+
if (!flatDirectory.test(gradle)) {
|
|
100
|
+
throw new Error('No se ha encontrado el bloque flatDir en android/app/build.gradle.');
|
|
101
|
+
}
|
|
102
|
+
writeFileSync(gradlePath, gradle.replace(flatDirectory, (match) => `${match}\n ${repositoryEntry}`), 'utf8');
|
|
103
|
+
}
|
|
104
|
+
function readPackageConfiguration(applicationRoot) {
|
|
105
|
+
return JSON.parse(readFileSync(join(applicationRoot, 'package.json'), 'utf8'));
|
|
106
|
+
}
|
|
107
|
+
function escapeXml(value) {
|
|
108
|
+
return value.replace(/[&<>"']/g, (character) => ({
|
|
109
|
+
'&': '&',
|
|
110
|
+
'<': '<',
|
|
111
|
+
'>': '>',
|
|
112
|
+
'"': '"',
|
|
113
|
+
"'": ''',
|
|
114
|
+
})[character] ?? character);
|
|
115
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const LATEST_CONFIGURATION_SCHEMA_VERSION = 3;
|
|
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,103 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
export const LATEST_CONFIGURATION_SCHEMA_VERSION = 3;
|
|
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
|
+
if (migrated.schemaVersion === 2) {
|
|
18
|
+
migrated = migrateVersionTwo(migrated, applicationRoot);
|
|
19
|
+
migrations.push('2 → 3: declarar las capacidades nativas habilitadas');
|
|
20
|
+
}
|
|
21
|
+
return { value: migrated, migrations };
|
|
22
|
+
}
|
|
23
|
+
function migrateVersionTwo(configuration, applicationRoot) {
|
|
24
|
+
const currentNative = isRecord(configuration.native) ? configuration.native : {};
|
|
25
|
+
return {
|
|
26
|
+
...configuration,
|
|
27
|
+
schemaVersion: 3,
|
|
28
|
+
native: {
|
|
29
|
+
...currentNative,
|
|
30
|
+
// Las aplicaciones anteriores incluían todos los plugins. Se conservan
|
|
31
|
+
// explícitamente hasta que el equipo decida deshabilitarlos.
|
|
32
|
+
capabilities: readLegacyCapabilities(configuration, applicationRoot),
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function readLegacyCapabilities(configuration, applicationRoot) {
|
|
37
|
+
if (isRecord(configuration.native) && Array.isArray(configuration.native.capabilities)) {
|
|
38
|
+
return configuration.native.capabilities.filter((capability) => typeof capability === 'string');
|
|
39
|
+
}
|
|
40
|
+
const packageConfiguration = readJson(join(applicationRoot, 'package.json'));
|
|
41
|
+
const catalog = readJson(join(applicationRoot, 'native-capabilities.catalog.json'));
|
|
42
|
+
const dependencies = isRecord(packageConfiguration?.dependencies)
|
|
43
|
+
? packageConfiguration.dependencies
|
|
44
|
+
: {};
|
|
45
|
+
if (!Array.isArray(catalog?.capabilities))
|
|
46
|
+
return [];
|
|
47
|
+
return catalog.capabilities
|
|
48
|
+
.filter((capability) => isRecord(capability) &&
|
|
49
|
+
typeof capability.name === 'string' &&
|
|
50
|
+
typeof capability.package === 'string')
|
|
51
|
+
.filter((capability) => capability.package === '@capacitor/core' || capability.package in dependencies)
|
|
52
|
+
.map((capability) => capability.name);
|
|
53
|
+
}
|
|
54
|
+
function readJson(path) {
|
|
55
|
+
if (!existsSync(path))
|
|
56
|
+
return undefined;
|
|
57
|
+
try {
|
|
58
|
+
const value = JSON.parse(readFileSync(path, 'utf8'));
|
|
59
|
+
return isRecord(value) ? value : undefined;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function migrateVersionOne(configuration, applicationRoot) {
|
|
66
|
+
const shellCoreVersion = readCoreVersion(join(applicationRoot, 'package.json'));
|
|
67
|
+
const microfrontends = Array.isArray(configuration.microfrontends)
|
|
68
|
+
? configuration.microfrontends.map((entry) => {
|
|
69
|
+
if (!isRecord(entry))
|
|
70
|
+
return entry;
|
|
71
|
+
const sourcePath = typeof entry.sourcePath === 'string' ? entry.sourcePath : undefined;
|
|
72
|
+
const localCoreVersion = sourcePath
|
|
73
|
+
? readCoreVersion(join(resolve(applicationRoot, sourcePath), 'package.json'))
|
|
74
|
+
: undefined;
|
|
75
|
+
const requiredCoreVersion = localCoreVersion ?? shellCoreVersion;
|
|
76
|
+
return {
|
|
77
|
+
...entry,
|
|
78
|
+
// El comodín conserva aplicaciones antiguas cuyo remoto no tenía
|
|
79
|
+
// metadatos; se debe concretar al actualizar el MF.
|
|
80
|
+
compatibility: { requiredCoreVersion: requiredCoreVersion ?? '*' },
|
|
81
|
+
};
|
|
82
|
+
})
|
|
83
|
+
: configuration.microfrontends;
|
|
84
|
+
return {
|
|
85
|
+
...configuration,
|
|
86
|
+
schemaVersion: 2,
|
|
87
|
+
microfrontends,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function readCoreVersion(packagePath) {
|
|
91
|
+
if (!existsSync(packagePath))
|
|
92
|
+
return undefined;
|
|
93
|
+
try {
|
|
94
|
+
const packageConfiguration = JSON.parse(readFileSync(packagePath, 'utf8'));
|
|
95
|
+
return packageConfiguration.dependencies?.['@open-mova/core'];
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function isRecord(value) {
|
|
102
|
+
return typeof value === 'object' && value !== null;
|
|
103
|
+
}
|
|
@@ -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)) {
|
|
@@ -82,24 +83,30 @@ function validateApplicationConfiguration(value, configurationPath) {
|
|
|
82
83
|
if (!isRecord(value.native)) {
|
|
83
84
|
throw new Error(`${configurationPath} contiene una configuración nativa no válida.`);
|
|
84
85
|
}
|
|
86
|
+
if (!Array.isArray(value.native.capabilities) ||
|
|
87
|
+
value.native.capabilities.some((capability) => typeof capability !== 'string')) {
|
|
88
|
+
throw new Error(`${configurationPath} contiene capacidades nativas no válidas.`);
|
|
89
|
+
}
|
|
90
|
+
const capabilities = [...new Set(value.native.capabilities)].sort();
|
|
85
91
|
if (value.native.googleMaps !== undefined) {
|
|
86
92
|
if (!isRecord(value.native.googleMaps) ||
|
|
87
93
|
typeof value.native.googleMaps.androidApiKey !== 'string') {
|
|
88
94
|
throw new Error(`${configurationPath} contiene una clave de Google Maps no válida.`);
|
|
89
95
|
}
|
|
90
96
|
native = {
|
|
97
|
+
capabilities,
|
|
91
98
|
googleMaps: {
|
|
92
99
|
androidApiKey: value.native.googleMaps.androidApiKey,
|
|
93
100
|
},
|
|
94
101
|
};
|
|
95
102
|
}
|
|
96
103
|
else {
|
|
97
|
-
native = {};
|
|
104
|
+
native = { capabilities };
|
|
98
105
|
}
|
|
99
106
|
}
|
|
100
107
|
const microfrontends = value.microfrontends.map((entry) => validateMicrofrontend(entry, configurationPath));
|
|
101
108
|
return {
|
|
102
|
-
schemaVersion:
|
|
109
|
+
schemaVersion: LATEST_CONFIGURATION_SCHEMA_VERSION,
|
|
103
110
|
name: value.name,
|
|
104
111
|
...(shell ? { shell } : {}),
|
|
105
112
|
...(native ? { native } : {}),
|
|
@@ -130,12 +137,18 @@ function validateMicrofrontend(value, configurationPath) {
|
|
|
130
137
|
typeof value.productionRemoteEntry !== 'string') {
|
|
131
138
|
throw new Error(`${configurationPath} contiene una URL de producción no válida.`);
|
|
132
139
|
}
|
|
133
|
-
if (
|
|
134
|
-
typeof value.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
140
|
+
if (!isRecord(value.compatibility) ||
|
|
141
|
+
typeof value.compatibility.requiredCoreVersion !== 'string' ||
|
|
142
|
+
value.compatibility.requiredCoreVersion.trim() === '') {
|
|
143
|
+
throw new Error(`${configurationPath} contiene un microfrontal sin compatibilidad de Core declarada.`);
|
|
144
|
+
}
|
|
145
|
+
if (value.template !== undefined &&
|
|
146
|
+
(!isRecord(value.template) ||
|
|
147
|
+
typeof value.template.repository !== 'string' ||
|
|
148
|
+
typeof value.template.version !== 'string' ||
|
|
149
|
+
typeof value.template.commit !== 'string' ||
|
|
150
|
+
value.template.project !== 'open-mova-mf-template' ||
|
|
151
|
+
(value.template.profile !== 'minimal' && value.template.profile !== 'demo'))) {
|
|
139
152
|
throw new Error(`${configurationPath} contiene una plantilla de microfrontal no válida.`);
|
|
140
153
|
}
|
|
141
154
|
return {
|
|
@@ -148,7 +161,12 @@ function validateMicrofrontend(value, configurationPath) {
|
|
|
148
161
|
? {}
|
|
149
162
|
: { productionRemoteEntry: value.productionRemoteEntry }),
|
|
150
163
|
...(value.sourcePath === undefined ? {} : { sourcePath: value.sourcePath }),
|
|
151
|
-
|
|
164
|
+
compatibility: {
|
|
165
|
+
requiredCoreVersion: value.compatibility.requiredCoreVersion,
|
|
166
|
+
},
|
|
167
|
+
...(value.template === undefined
|
|
168
|
+
? {}
|
|
169
|
+
: { template: value.template }),
|
|
152
170
|
};
|
|
153
171
|
}
|
|
154
172
|
function isRecord(value) {
|