@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.
Files changed (38) hide show
  1. package/README.md +237 -6
  2. package/dist/application/android-configuration.d.ts +2 -0
  3. package/dist/application/android-configuration.js +115 -0
  4. package/dist/application/configuration-migrations.d.ts +6 -0
  5. package/dist/application/configuration-migrations.js +103 -0
  6. package/dist/application/configuration.d.ts +5 -0
  7. package/dist/application/configuration.js +38 -20
  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/native-capabilities.d.ts +15 -0
  20. package/dist/application/native-capabilities.js +143 -0
  21. package/dist/application/project-update.d.ts +8 -0
  22. package/dist/application/project-update.js +106 -0
  23. package/dist/application/shell-configuration.d.ts +20 -0
  24. package/dist/application/shell-configuration.js +52 -1
  25. package/dist/application/shell-repository.d.ts +1 -1
  26. package/dist/application/shell-repository.js +14 -7
  27. package/dist/application/shell-update.d.ts +1 -5
  28. package/dist/application/shell-update.js +11 -108
  29. package/dist/cli.js +2 -0
  30. package/dist/commands/capacitor.js +55 -90
  31. package/dist/commands/create.js +3 -2
  32. package/dist/commands/doctor.d.ts +2 -0
  33. package/dist/commands/doctor.js +25 -0
  34. package/dist/commands/info.js +1 -1
  35. package/dist/commands/microfrontend.js +40 -1
  36. package/dist/commands/update.js +13 -4
  37. package/dist/types.d.ts +7 -1
  38. package/package.json +7 -3
@@ -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;
@@ -0,0 +1,304 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { join, resolve } from 'node:path';
4
+ import { findApplicationRoot, readApplicationConfigurationDocument } from './configuration.js';
5
+ import { npmCommand } from '../utils/platform.js';
6
+ import { areCoreRangesCompatible } from './core-compatibility.js';
7
+ export function inspectDevelopmentEnvironment(startDirectory) {
8
+ const checks = [];
9
+ const applicationRoot = findApplicationRoot(resolve(startDirectory));
10
+ checks.push(checkNodeVersion());
11
+ checks.push(checkCommand(npmCommand(), ['--version'], 'npm', true, 'npm'));
12
+ checks.push(checkCommand('git', ['--version'], 'Git', true));
13
+ if (!applicationRoot) {
14
+ checks.push({
15
+ id: 'application',
16
+ status: 'warning',
17
+ message: 'No se ha encontrado una aplicación Open Mova.',
18
+ detail: 'Ejecuta el comando dentro de un directorio que contenga mova.config.json.',
19
+ });
20
+ return { checks };
21
+ }
22
+ checks.push({
23
+ id: 'application',
24
+ status: 'ok',
25
+ message: `Aplicación encontrada en ${applicationRoot}.`,
26
+ });
27
+ let configuration;
28
+ try {
29
+ const document = readApplicationConfigurationDocument(applicationRoot);
30
+ configuration = document.configuration;
31
+ checks.push({
32
+ id: 'configuration',
33
+ status: 'ok',
34
+ message: 'mova.config.json tiene un formato válido.',
35
+ });
36
+ if (document.migrations.length > 0) {
37
+ checks.push({
38
+ id: 'configuration-migrations',
39
+ status: 'warning',
40
+ message: 'mova.config.json necesita migraciones.',
41
+ detail: 'Ejecuta mova update para guardarlo con el esquema actual.',
42
+ });
43
+ }
44
+ }
45
+ catch (error) {
46
+ checks.push({
47
+ id: 'configuration',
48
+ status: 'error',
49
+ message: 'mova.config.json no es válido.',
50
+ detail: error instanceof Error ? error.message : undefined,
51
+ });
52
+ return { applicationRoot, checks };
53
+ }
54
+ checks.push(checkDependencies(applicationRoot, 'shell'));
55
+ checks.push(checkShellVersion(configuration.shell?.version));
56
+ const shellPackage = readOptionalPackage(applicationRoot);
57
+ const shellCoreVersion = shellPackage?.dependencies?.['@open-mova/core'];
58
+ for (const microfrontend of configuration.microfrontends) {
59
+ if (microfrontend.sourcePath) {
60
+ const microfrontendRoot = resolve(applicationRoot, microfrontend.sourcePath);
61
+ if (!existsSync(microfrontendRoot)) {
62
+ checks.push({
63
+ id: `microfrontend:${microfrontend.name}:source`,
64
+ status: 'error',
65
+ message: `No existe el directorio del MF ${microfrontend.name}.`,
66
+ detail: microfrontendRoot,
67
+ });
68
+ }
69
+ else {
70
+ checks.push(checkDependencies(microfrontendRoot, `MF ${microfrontend.name}`));
71
+ checks.push(checkCoreCompatibility(microfrontend.name, shellCoreVersion, readOptionalPackage(microfrontendRoot)?.dependencies?.['@open-mova/core']));
72
+ }
73
+ }
74
+ checks.push(checkProductionRemote(microfrontend.name, microfrontend.productionRemoteEntry));
75
+ }
76
+ const hasAndroid = existsSync(join(applicationRoot, 'android'));
77
+ const hasIos = existsSync(join(applicationRoot, 'ios'));
78
+ checks.push({
79
+ id: 'capacitor-platforms',
80
+ status: hasAndroid || hasIos ? 'ok' : 'warning',
81
+ message: hasAndroid || hasIos
82
+ ? `Plataformas añadidas: ${[hasAndroid && 'Android', hasIos && 'iOS'].filter(Boolean).join(', ')}.`
83
+ : 'No hay plataformas nativas añadidas todavía.',
84
+ });
85
+ if (hasAndroid) {
86
+ checks.push(checkAndroidSdk());
87
+ checks.push(checkCommand('adb', ['version'], 'Android Platform Tools (adb)', true));
88
+ checks.push(checkAndroidEmulators());
89
+ checks.push(checkGoogleMapsKey(applicationRoot, configuration.native?.googleMaps?.androidApiKey));
90
+ }
91
+ if (hasIos) {
92
+ if (process.platform !== 'darwin') {
93
+ checks.push({
94
+ id: 'ios-platform',
95
+ status: 'error',
96
+ message: 'La compilación de iOS requiere macOS.',
97
+ });
98
+ }
99
+ else {
100
+ checks.push(checkCommand('xcodebuild', ['-version'], 'Xcode', true));
101
+ checks.push(checkCommand('pod', ['--version'], 'CocoaPods', false));
102
+ }
103
+ checks.push(...checkIosUsageDescriptions(applicationRoot, shellPackage));
104
+ }
105
+ return { applicationRoot, checks };
106
+ }
107
+ function checkNodeVersion() {
108
+ const majorVersion = Number(process.versions.node.split('.')[0]);
109
+ return majorVersion >= 22
110
+ ? { id: 'node', status: 'ok', message: `Node.js ${process.versions.node}.` }
111
+ : {
112
+ id: 'node',
113
+ status: 'error',
114
+ message: `Node.js ${process.versions.node} no es compatible.`,
115
+ detail: 'Instala Node.js 22 o posterior.',
116
+ };
117
+ }
118
+ function checkCommand(command, args, label, required, id = command) {
119
+ const result = spawnSync(command, [...args], { encoding: 'utf8' });
120
+ const available = !result.error && result.status === 0;
121
+ const version = available ? (result.stdout || result.stderr).trim().split(/\r?\n/)[0] : undefined;
122
+ return {
123
+ id: `command:${id}`,
124
+ status: available ? 'ok' : required ? 'error' : 'warning',
125
+ message: available ? `${label}: ${version}.` : `${label} no está disponible.`,
126
+ };
127
+ }
128
+ function checkDependencies(directory, label) {
129
+ const packagePath = join(directory, 'package.json');
130
+ if (!existsSync(packagePath)) {
131
+ return {
132
+ id: `dependencies:${label}`,
133
+ status: 'error',
134
+ message: `${label} no contiene package.json.`,
135
+ };
136
+ }
137
+ return existsSync(join(directory, 'node_modules'))
138
+ ? {
139
+ id: `dependencies:${label}`,
140
+ status: 'ok',
141
+ message: `${label}: dependencias instaladas.`,
142
+ }
143
+ : {
144
+ id: `dependencies:${label}`,
145
+ status: 'warning',
146
+ message: `${label}: faltan las dependencias.`,
147
+ detail: `Ejecuta npm install en ${directory}.`,
148
+ };
149
+ }
150
+ function checkShellVersion(version) {
151
+ return version && /^v\d+\.\d+\.\d+$/.test(version)
152
+ ? { id: 'shell-version', status: 'ok', message: `Shell registrada: ${version}.` }
153
+ : {
154
+ id: 'shell-version',
155
+ status: 'error',
156
+ message: 'La aplicación no tiene una versión estable de shell registrada.',
157
+ };
158
+ }
159
+ function checkCoreCompatibility(microfrontendName, shellVersion, microfrontendVersion) {
160
+ if (!microfrontendVersion) {
161
+ return {
162
+ id: `microfrontend:${microfrontendName}:core`,
163
+ status: 'warning',
164
+ message: `El MF ${microfrontendName} no declara @open-mova/core.`,
165
+ };
166
+ }
167
+ if (microfrontendVersion === '*') {
168
+ return {
169
+ id: `microfrontend:${microfrontendName}:core`,
170
+ status: 'warning',
171
+ message: `El MF ${microfrontendName} necesita concretar su rango de @open-mova/core.`,
172
+ detail: 'Ejecuta mova mf update o registra de nuevo el MF con --core-version.',
173
+ };
174
+ }
175
+ if (!shellVersion || !areCoreRangesCompatible(shellVersion, microfrontendVersion)) {
176
+ return {
177
+ id: `microfrontend:${microfrontendName}:core`,
178
+ status: 'error',
179
+ message: `El MF ${microfrontendName} y la shell usan rangos distintos de @open-mova/core.`,
180
+ detail: `Shell: ${shellVersion ?? 'no declarado'}; MF: ${microfrontendVersion}.`,
181
+ };
182
+ }
183
+ return {
184
+ id: `microfrontend:${microfrontendName}:core`,
185
+ status: 'ok',
186
+ message: `El MF ${microfrontendName} comparte ${microfrontendVersion} de @open-mova/core.`,
187
+ };
188
+ }
189
+ function checkProductionRemote(name, remoteEntry) {
190
+ const isHttps = remoteEntry === undefined ? false : isHttpsUrl(remoteEntry);
191
+ return isHttps
192
+ ? {
193
+ id: `microfrontend:${name}:production`,
194
+ status: 'ok',
195
+ message: `El MF ${name} tiene una URL HTTPS de producción.`,
196
+ }
197
+ : {
198
+ id: `microfrontend:${name}:production`,
199
+ status: 'warning',
200
+ message: `El MF ${name} no tiene una URL HTTPS de producción válida.`,
201
+ };
202
+ }
203
+ function isHttpsUrl(value) {
204
+ try {
205
+ return new URL(value).protocol === 'https:';
206
+ }
207
+ catch {
208
+ return false;
209
+ }
210
+ }
211
+ function checkAndroidSdk() {
212
+ const sdkRoot = process.env['ANDROID_SDK_ROOT'] ?? process.env['ANDROID_HOME'];
213
+ return sdkRoot && existsSync(sdkRoot)
214
+ ? { id: 'android-sdk', status: 'ok', message: `Android SDK: ${sdkRoot}.` }
215
+ : {
216
+ id: 'android-sdk',
217
+ status: 'warning',
218
+ message: 'No se ha detectado ANDROID_SDK_ROOT ni ANDROID_HOME.',
219
+ };
220
+ }
221
+ function checkAndroidEmulators() {
222
+ const result = spawnSync('emulator', ['-list-avds'], { encoding: 'utf8' });
223
+ if (result.error || result.status !== 0) {
224
+ return {
225
+ id: 'android-emulators',
226
+ status: 'warning',
227
+ message: 'No se ha podido consultar el listado de emuladores Android.',
228
+ };
229
+ }
230
+ const emulators = result.stdout.split(/\r?\n/).filter(Boolean);
231
+ return emulators.length > 0
232
+ ? {
233
+ id: 'android-emulators',
234
+ status: 'ok',
235
+ message: `Emuladores Android disponibles: ${emulators.length}.`,
236
+ }
237
+ : {
238
+ id: 'android-emulators',
239
+ status: 'warning',
240
+ message: 'No hay emuladores Android configurados.',
241
+ };
242
+ }
243
+ function checkGoogleMapsKey(applicationRoot, configuredKey) {
244
+ const resourcePath = join(applicationRoot, 'android', 'app', 'src', 'main', 'res', 'values', 'open_mova_google_maps.xml');
245
+ const resource = existsSync(resourcePath) ? readFileSync(resourcePath, 'utf8') : '';
246
+ const hasKey = Boolean(process.env['OPEN_MOVA_GOOGLE_MAPS_ANDROID_API_KEY'] ||
247
+ configuredKey ||
248
+ (resource && !resource.includes('OPEN_MOVA_GOOGLE_MAPS_API_KEY_NOT_CONFIGURED')));
249
+ return hasKey
250
+ ? { id: 'google-maps-key', status: 'ok', message: 'Google Maps tiene una clave configurada.' }
251
+ : {
252
+ id: 'google-maps-key',
253
+ status: 'warning',
254
+ message: 'Google Maps no tiene una clave Android configurada.',
255
+ };
256
+ }
257
+ function checkIosUsageDescriptions(applicationRoot, packageConfiguration) {
258
+ const plistPath = join(applicationRoot, 'ios', 'App', 'App', 'Info.plist');
259
+ if (!existsSync(plistPath)) {
260
+ return [
261
+ {
262
+ id: 'ios-info-plist',
263
+ status: 'error',
264
+ message: 'No se encuentra ios/App/App/Info.plist.',
265
+ },
266
+ ];
267
+ }
268
+ const plist = readFileSync(plistPath, 'utf8');
269
+ const requiredKeys = [];
270
+ if (packageConfiguration?.dependencies?.['@capacitor/camera']) {
271
+ requiredKeys.push('NSCameraUsageDescription', 'NSPhotoLibraryUsageDescription', 'NSPhotoLibraryAddUsageDescription');
272
+ }
273
+ if (packageConfiguration?.dependencies?.['@capacitor/geolocation']) {
274
+ requiredKeys.push('NSLocationWhenInUseUsageDescription');
275
+ }
276
+ const missingKeys = requiredKeys.filter((key) => !plist.includes(`<key>${key}</key>`));
277
+ return missingKeys.length === 0
278
+ ? [
279
+ {
280
+ id: 'ios-info-plist',
281
+ status: 'ok',
282
+ message: 'Info.plist contiene los permisos básicos requeridos.',
283
+ },
284
+ ]
285
+ : [
286
+ {
287
+ id: 'ios-info-plist',
288
+ status: 'warning',
289
+ message: 'Info.plist no contiene todas las descripciones de uso necesarias.',
290
+ detail: `Faltan: ${missingKeys.join(', ')}.`,
291
+ },
292
+ ];
293
+ }
294
+ function readOptionalPackage(directory) {
295
+ const packagePath = join(directory, 'package.json');
296
+ if (!existsSync(packagePath))
297
+ return undefined;
298
+ try {
299
+ return JSON.parse(readFileSync(packagePath, 'utf8'));
300
+ }
301
+ catch {
302
+ return undefined;
303
+ }
304
+ }
@@ -0,0 +1,3 @@
1
+ export declare const MICROFRONTEND_MANIFEST_PATH = "assets/open-mova.manifest.json";
2
+ export declare function readRequiredCoreVersion(projectRoot: string): string;
3
+ export declare function writeMicrofrontendManifest(projectRoot: string, name: string, remoteName: string, requiredCoreVersion: string): void;
@@ -0,0 +1,20 @@
1
+ import { readFileSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ export const MICROFRONTEND_MANIFEST_PATH = 'assets/open-mova.manifest.json';
4
+ export function readRequiredCoreVersion(projectRoot) {
5
+ const packageConfiguration = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf8'));
6
+ const requiredVersion = packageConfiguration.dependencies?.['@open-mova/core'];
7
+ if (!requiredVersion) {
8
+ throw new Error(`${projectRoot} debe declarar @open-mova/core en dependencies para definir su compatibilidad.`);
9
+ }
10
+ return requiredVersion;
11
+ }
12
+ export function writeMicrofrontendManifest(projectRoot, name, remoteName, requiredCoreVersion) {
13
+ const manifest = {
14
+ schemaVersion: 1,
15
+ name,
16
+ remoteName,
17
+ core: { requiredVersion: requiredCoreVersion },
18
+ };
19
+ writeFileSync(join(projectRoot, 'src', 'assets', 'open-mova.manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
20
+ }
@@ -1,6 +1,7 @@
1
1
  import { readFileSync, rmSync, writeFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { toDisplayName, toRemoteName } from '../utils/names.js';
4
+ import { readRequiredCoreVersion, writeMicrofrontendManifest } from './microfrontend-manifest.js';
4
5
  export function configureDownloadedMicrofrontend(destination, name, port, profile) {
5
6
  const projectName = `mova-mf-${name}`;
6
7
  const packagePath = join(destination, 'package.json');
@@ -9,7 +10,6 @@ export function configureDownloadedMicrofrontend(destination, name, port, profil
9
10
  packageJson.scripts.start = `ng serve ${projectName}`;
10
11
  packageJson.scripts.build = `ng build ${projectName}`;
11
12
  if (profile === 'minimal') {
12
- delete packageJson.dependencies['@open-mova/core'];
13
13
  writeFileSync(join(destination, 'src/app/app.routes.ts'), minimalRoutes(name));
14
14
  writeFileSync(join(destination, 'src/app/app.config.ts'), minimalAppConfig());
15
15
  }
@@ -27,6 +27,7 @@ export function configureDownloadedMicrofrontend(destination, name, port, profil
27
27
  .replaceAll('mova-demo-microfrontend', `mova-${name}-microfrontend`));
28
28
  const appPath = join(destination, 'src/app/app.ts');
29
29
  writeFileSync(appPath, readFileSync(appPath, 'utf8').replace('mova-demo-microfrontend', `mova-${name}-microfrontend`));
30
+ writeMicrofrontendManifest(destination, name, toRemoteName(name), readRequiredCoreVersion(destination));
30
31
  // El lock heredado ya no representa el proyecto renombrado ni su dependencia local.
31
32
  rmSync(join(destination, 'package-lock.json'), { force: true });
32
33
  writeFileSync(join(destination, 'README.md'), `# ${projectName}\n\nMicrofrontal Open Mova (${profile}). Ejecuta \`npm install\` y \`npm start\`. La shell lo carga en http://localhost:${port}/remoteEntry.json.\n`);
@@ -0,0 +1,17 @@
1
+ import type { MicrofrontendConfiguration, OpenMovaApplicationConfiguration } from '../types.js';
2
+ import { type FileChange } from './project-update.js';
3
+ import { type ShellVersion } from './shell-repository.js';
4
+ export interface MicrofrontendUpdatePlan {
5
+ readonly microfrontend: MicrofrontendConfiguration;
6
+ readonly projectRoot: string;
7
+ readonly currentVersion: string;
8
+ readonly target: ShellVersion;
9
+ readonly targetCoreVersion: string;
10
+ readonly changes: readonly string[];
11
+ readonly conflicts: readonly string[];
12
+ readonly fileChanges: readonly FileChange[];
13
+ readonly packageContent?: string;
14
+ readonly removePackageLock: boolean;
15
+ }
16
+ export declare function createMicrofrontendUpdatePlan(applicationRoot: string, configuration: OpenMovaApplicationConfiguration, name: string, requestedVersion?: string): MicrofrontendUpdatePlan;
17
+ export declare function applyMicrofrontendUpdate(applicationRoot: string, configuration: OpenMovaApplicationConfiguration, plan: MicrofrontendUpdatePlan): void;
@@ -0,0 +1,117 @@
1
+ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { isAbsolute, join, relative, resolve } from 'node:path';
4
+ import { writeApplicationConfiguration } from './configuration.js';
5
+ import { readRequiredCoreVersion } from './microfrontend-manifest.js';
6
+ import { configureDownloadedMicrofrontend } from './microfrontend-profile.js';
7
+ import { applyFileChanges, compareManagedFiles, comparePackageConfiguration, requireCleanGitRepository, } from './project-update.js';
8
+ import { downloadTaggedProject, listShellVersions } from './shell-repository.js';
9
+ import { synchronizeShellConfiguration } from './shell-configuration.js';
10
+ const GENERATED_PATHS = new Set(['package.json', 'package-lock.json']);
11
+ export function createMicrofrontendUpdatePlan(applicationRoot, configuration, name, requestedVersion) {
12
+ const microfrontend = configuration.microfrontends.find((entry) => entry.name === name);
13
+ if (!microfrontend)
14
+ throw new Error(`No existe un microfrontal llamado "${name}".`);
15
+ if (!microfrontend.sourcePath || !microfrontend.template) {
16
+ throw new Error(`El MF ${name} no fue creado desde una plantilla gestionada por Open Mova y no se puede actualizar automáticamente.`);
17
+ }
18
+ const targetVersion = requestedVersion ?? listShellVersions()[0];
19
+ if (!targetVersion)
20
+ throw new Error('No hay versiones estables disponibles para actualizar.');
21
+ if (compareVersions(targetVersion, microfrontend.template.version) < 0) {
22
+ throw new Error('mova mf update no realiza downgrades de plantillas.');
23
+ }
24
+ const projectRoot = resolve(applicationRoot, microfrontend.sourcePath);
25
+ const port = readPort(microfrontend.developmentRemoteEntry);
26
+ const temporaryRoot = mkdtempSync(join(tmpdir(), 'open-mova-mf-update-'));
27
+ try {
28
+ const currentDirectory = join(temporaryRoot, 'current');
29
+ const targetDirectory = join(temporaryRoot, 'target');
30
+ const current = downloadTaggedProject('open-mova-mf-template', currentDirectory, microfrontend.template.version);
31
+ const target = downloadTaggedProject('open-mova-mf-template', targetDirectory, targetVersion);
32
+ if (current.commit !== microfrontend.template.commit) {
33
+ throw new Error(`El commit registrado para la plantilla ${microfrontend.template.version} no coincide con su tag.`);
34
+ }
35
+ configureDownloadedMicrofrontend(currentDirectory, microfrontend.name, port, microfrontend.template.profile);
36
+ configureDownloadedMicrofrontend(targetDirectory, microfrontend.name, port, microfrontend.template.profile);
37
+ const conflicts = [];
38
+ const fileChanges = compareManagedFiles(projectRoot, currentDirectory, targetDirectory, conflicts, GENERATED_PATHS);
39
+ const packageContent = comparePackageConfiguration(projectRoot, currentDirectory, targetDirectory, conflicts);
40
+ const targetCoreVersion = readRequiredCoreVersion(targetDirectory);
41
+ const changes = [
42
+ ...fileChanges.map((change) => `${change.content ? 'Actualizar' : 'Eliminar'} ${change.path}`),
43
+ ...(packageContent ? ['Actualizar package.json', 'Regenerar package-lock.json'] : []),
44
+ ...(target.version === microfrontend.template.version
45
+ ? []
46
+ : [`Registrar plantilla ${target.version}`]),
47
+ ...(targetCoreVersion === microfrontend.compatibility.requiredCoreVersion
48
+ ? []
49
+ : [`Actualizar compatibilidad de Core a ${targetCoreVersion}`]),
50
+ ];
51
+ return {
52
+ microfrontend,
53
+ projectRoot,
54
+ currentVersion: microfrontend.template.version,
55
+ target,
56
+ targetCoreVersion,
57
+ changes,
58
+ conflicts,
59
+ fileChanges,
60
+ ...(packageContent ? { packageContent } : {}),
61
+ removePackageLock: packageContent !== undefined,
62
+ };
63
+ }
64
+ finally {
65
+ rmSync(temporaryRoot, { recursive: true, force: true });
66
+ }
67
+ }
68
+ export function applyMicrofrontendUpdate(applicationRoot, configuration, plan) {
69
+ if (plan.conflicts.length > 0) {
70
+ throw new Error('No se puede actualizar el microfrontal mientras existan conflictos.');
71
+ }
72
+ requireCleanGitRepository(applicationRoot);
73
+ const projectPath = relative(applicationRoot, plan.projectRoot);
74
+ if (isAbsolute(projectPath) || projectPath.startsWith('..')) {
75
+ requireCleanGitRepository(plan.projectRoot);
76
+ }
77
+ applyFileChanges(plan.projectRoot, plan.fileChanges);
78
+ if (plan.packageContent) {
79
+ writeFileSync(join(plan.projectRoot, 'package.json'), plan.packageContent, 'utf8');
80
+ }
81
+ if (plan.removePackageLock) {
82
+ rmSync(join(plan.projectRoot, 'package-lock.json'), { force: true });
83
+ }
84
+ const updatedConfiguration = {
85
+ ...configuration,
86
+ microfrontends: configuration.microfrontends.map((entry) => entry.name === plan.microfrontend.name
87
+ ? {
88
+ ...entry,
89
+ compatibility: { requiredCoreVersion: plan.targetCoreVersion },
90
+ template: {
91
+ ...plan.target,
92
+ project: 'open-mova-mf-template',
93
+ profile: plan.microfrontend.template.profile,
94
+ },
95
+ }
96
+ : entry),
97
+ };
98
+ writeApplicationConfiguration(applicationRoot, updatedConfiguration);
99
+ synchronizeShellConfiguration(applicationRoot, updatedConfiguration);
100
+ }
101
+ function readPort(remoteEntry) {
102
+ const port = Number(new URL(remoteEntry).port);
103
+ if (!Number.isInteger(port) || port < 1) {
104
+ throw new Error(`No se puede obtener el puerto de desarrollo desde ${remoteEntry}.`);
105
+ }
106
+ return port;
107
+ }
108
+ function compareVersions(first, second) {
109
+ const firstParts = first.slice(1).split('.').map(Number);
110
+ const secondParts = second.slice(1).split('.').map(Number);
111
+ for (let index = 0; index < 3; index += 1) {
112
+ const difference = (firstParts[index] ?? 0) - (secondParts[index] ?? 0);
113
+ if (difference !== 0)
114
+ return difference;
115
+ }
116
+ return 0;
117
+ }
@@ -17,6 +17,7 @@ export interface ExistingMicrofrontendOptions {
17
17
  readonly remoteEntry?: string;
18
18
  readonly productionRemoteEntry?: string;
19
19
  readonly port?: number;
20
+ readonly coreVersion?: string;
20
21
  }
21
22
  export declare function createMicrofrontend(applicationRoot: string, configuration: OpenMovaApplicationConfiguration, options: CreateMicrofrontendOptions): MicrofrontendConfiguration;
22
23
  export declare function inspectExistingMicrofrontend(applicationRoot: string, options: ExistingMicrofrontendOptions): MicrofrontendConfiguration;
@@ -1,8 +1,9 @@
1
1
  import { existsSync, readFileSync, rmSync } from 'node:fs';
2
2
  import { relative, resolve, sep } from 'node:path';
3
3
  import { normalizeName, toRemoteName } from '../utils/names.js';
4
- import { configureDownloadedMicrofrontend } from './microfrontend-profile.js';
4
+ import { configureDownloadedMicrofrontend, } from './microfrontend-profile.js';
5
5
  import { downloadTaggedProject } from './shell-repository.js';
6
+ import { readRequiredCoreVersion } from './microfrontend-manifest.js';
6
7
  export function createMicrofrontend(applicationRoot, configuration, options) {
7
8
  const name = normalizeName(options.name, 'El nombre del microfrontal');
8
9
  const route = normalizeName(options.route ?? name, 'La ruta del microfrontal');
@@ -32,6 +33,9 @@ export function createMicrofrontend(applicationRoot, configuration, options) {
32
33
  ? { productionRemoteEntry: options.productionRemoteEntry }
33
34
  : {}),
34
35
  sourcePath: toConfigurationPath(applicationRoot, destination),
36
+ compatibility: {
37
+ requiredCoreVersion: readRequiredCoreVersion(destination),
38
+ },
35
39
  template: { ...template, project: 'open-mova-mf-template', profile },
36
40
  };
37
41
  }
@@ -44,7 +48,7 @@ export function inspectExistingMicrofrontend(applicationRoot, options) {
44
48
  : undefined;
45
49
  const discovered = sourceDirectory
46
50
  ? inspectLocalProject(sourceDirectory)
47
- : { remoteName: undefined, port: undefined };
51
+ : { remoteName: undefined, port: undefined, requiredCoreVersion: undefined };
48
52
  const remoteName = options.remoteName ?? discovered.remoteName;
49
53
  const inferredName = remoteName?.replace(/-microfrontend$/, '');
50
54
  const name = normalizeName(options.name ?? inferredName ?? '', 'El nombre del microfrontal');
@@ -59,6 +63,12 @@ export function inspectExistingMicrofrontend(applicationRoot, options) {
59
63
  if (!remoteEntry) {
60
64
  throw new Error('No se ha podido detectar el puerto. Indica --remote-entry o --port.');
61
65
  }
66
+ const requiredCoreVersion = options.coreVersion ??
67
+ discovered.requiredCoreVersion ??
68
+ readRequiredCoreVersion(applicationRoot);
69
+ if (!requiredCoreVersion) {
70
+ throw new Error('Indica --core-version para declarar qué versión de @open-mova/core necesita el microfrontal remoto.');
71
+ }
62
72
  return {
63
73
  name,
64
74
  route,
@@ -71,6 +81,7 @@ export function inspectExistingMicrofrontend(applicationRoot, options) {
71
81
  ...(sourceDirectory
72
82
  ? { sourcePath: toConfigurationPath(applicationRoot, sourceDirectory) }
73
83
  : {}),
84
+ compatibility: { requiredCoreVersion },
74
85
  };
75
86
  }
76
87
  function inspectLocalProject(directory) {
@@ -85,7 +96,7 @@ function inspectLocalProject(directory) {
85
96
  const federationContent = readFileSync(federationConfiguration, 'utf8');
86
97
  const remoteName = federationContent.match(/\bname\s*:\s*['"]([^'"]+)['"]/)?.[1];
87
98
  const port = readPort(angularConfiguration);
88
- return { remoteName, port };
99
+ return { remoteName, port, requiredCoreVersion: readRequiredCoreVersion(directory) };
89
100
  }
90
101
  function readPort(configurationPath) {
91
102
  const parsed = JSON.parse(readFileSync(configurationPath, 'utf8'));
@@ -0,0 +1,15 @@
1
+ import type { OpenMovaApplicationConfiguration } from '../types.js';
2
+ import { type NativeCapabilityDefinition } from './shell-configuration.js';
3
+ export interface NativeCapabilityStatus {
4
+ readonly definition: NativeCapabilityDefinition;
5
+ readonly enabled: boolean;
6
+ }
7
+ export declare function listNativeCapabilities(applicationRoot: string): readonly NativeCapabilityStatus[];
8
+ export declare function enableNativeCapabilities(applicationRoot: string, requestedNames: readonly string[]): OpenMovaApplicationConfiguration;
9
+ export declare function disableNativeCapabilities(applicationRoot: string, requestedNames: readonly string[]): OpenMovaApplicationConfiguration;
10
+ export declare function diagnoseNativeCapabilities(applicationRoot: string, platform?: 'android' | 'ios'): readonly string[];
11
+ /**
12
+ * Recompone las dependencias seleccionables después de actualizar la shell.
13
+ * No instala paquetes: el flujo de update regenera después el lockfile.
14
+ */
15
+ export declare function synchronizeNativeCapabilityDependencies(applicationRoot: string, configuration: OpenMovaApplicationConfiguration): void;