@open-mova/cli 0.1.20 → 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.
Files changed (35) hide show
  1. package/README.md +173 -3
  2. package/dist/application/android-configuration.d.ts +2 -0
  3. package/dist/application/android-configuration.js +90 -0
  4. package/dist/application/configuration-migrations.d.ts +6 -0
  5. package/dist/application/configuration-migrations.js +57 -0
  6. package/dist/application/configuration.d.ts +5 -0
  7. package/dist/application/configuration.js +31 -19
  8. package/dist/application/core-compatibility.d.ts +3 -0
  9. package/dist/application/core-compatibility.js +10 -0
  10. package/dist/application/doctor.d.ts +12 -0
  11. package/dist/application/doctor.js +304 -0
  12. package/dist/application/microfrontend-manifest.d.ts +3 -0
  13. package/dist/application/microfrontend-manifest.js +20 -0
  14. package/dist/application/microfrontend-profile.js +2 -1
  15. package/dist/application/microfrontend-update.d.ts +17 -0
  16. package/dist/application/microfrontend-update.js +117 -0
  17. package/dist/application/microfrontend.d.ts +1 -0
  18. package/dist/application/microfrontend.js +14 -3
  19. package/dist/application/project-update.d.ts +8 -0
  20. package/dist/application/project-update.js +106 -0
  21. package/dist/application/shell-configuration.js +2 -0
  22. package/dist/application/shell-repository.d.ts +1 -1
  23. package/dist/application/shell-repository.js +14 -7
  24. package/dist/application/shell-update.d.ts +1 -5
  25. package/dist/application/shell-update.js +9 -108
  26. package/dist/cli.js +2 -0
  27. package/dist/commands/capacitor.js +9 -91
  28. package/dist/commands/create.js +2 -2
  29. package/dist/commands/doctor.d.ts +2 -0
  30. package/dist/commands/doctor.js +25 -0
  31. package/dist/commands/info.js +1 -1
  32. package/dist/commands/microfrontend.js +40 -1
  33. package/dist/commands/update.js +13 -4
  34. package/dist/types.d.ts +5 -1
  35. 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 aplicación y añade una plataforma nativa. Configura antes el `appId` definitivo en `capacitor.config.ts`.
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
@@ -198,6 +271,11 @@ mova cap open android
198
271
  mova cap open ios
199
272
  ```
200
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
+
201
279
  ## Flujo habitual
202
280
 
203
281
  ```bash
@@ -210,6 +288,98 @@ mova start
210
288
 
211
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.
212
290
 
213
- ## Limitaciones actuales
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
+ ```
214
382
 
215
- El CLI todavía no actualiza automáticamente la shell de una aplicación creada ni publica microfrontales. Los comandos móviles requieren una versión de shell que incluya Capacitor.
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,2 @@
1
+ import type { OpenMovaApplicationConfiguration } from '../types.js';
2
+ export declare function configureAndroidProject(applicationRoot: string, configuration: OpenMovaApplicationConfiguration): void;
@@ -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
+ '&': '&amp;',
85
+ '<': '&lt;',
86
+ '>': '&gt;',
87
+ '"': '&quot;',
88
+ "'": '&apos;',
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
- try {
27
- const parsed = JSON.parse(readFileSync(configurationPath, 'utf8'));
28
- return validateApplicationConfiguration(parsed, configurationPath);
29
- }
30
- catch (error) {
31
- if (error instanceof Error) {
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 !== 1 || typeof value.name !== 'string') {
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)) {
@@ -99,7 +100,7 @@ function validateApplicationConfiguration(value, configurationPath) {
99
100
  }
100
101
  const microfrontends = value.microfrontends.map((entry) => validateMicrofrontend(entry, configurationPath));
101
102
  return {
102
- schemaVersion: 1,
103
+ schemaVersion: LATEST_CONFIGURATION_SCHEMA_VERSION,
103
104
  name: value.name,
104
105
  ...(shell ? { shell } : {}),
105
106
  ...(native ? { native } : {}),
@@ -130,12 +131,18 @@ function validateMicrofrontend(value, configurationPath) {
130
131
  typeof value.productionRemoteEntry !== 'string') {
131
132
  throw new Error(`${configurationPath} contiene una URL de producción no válida.`);
132
133
  }
133
- if (value.template !== undefined && (!isRecord(value.template) ||
134
- typeof value.template.repository !== 'string' ||
135
- typeof value.template.version !== 'string' ||
136
- typeof value.template.commit !== 'string' ||
137
- value.template.project !== 'open-mova-mf-template' ||
138
- (value.template.profile !== 'minimal' && value.template.profile !== 'demo'))) {
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'))) {
139
146
  throw new Error(`${configurationPath} contiene una plantilla de microfrontal no válida.`);
140
147
  }
141
148
  return {
@@ -148,7 +155,12 @@ function validateMicrofrontend(value, configurationPath) {
148
155
  ? {}
149
156
  : { productionRemoteEntry: value.productionRemoteEntry }),
150
157
  ...(value.sourcePath === undefined ? {} : { sourcePath: value.sourcePath }),
151
- ...(value.template === undefined ? {} : { template: value.template }),
158
+ compatibility: {
159
+ requiredCoreVersion: value.compatibility.requiredCoreVersion,
160
+ },
161
+ ...(value.template === undefined
162
+ ? {}
163
+ : { template: value.template }),
152
164
  };
153
165
  }
154
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;