@open-mova/cli 0.1.21 → 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 +65 -4
- package/dist/application/android-configuration.js +30 -5
- package/dist/application/configuration-migrations.d.ts +1 -1
- package/dist/application/configuration-migrations.js +47 -1
- package/dist/application/configuration.js +7 -1
- package/dist/application/native-capabilities.d.ts +15 -0
- package/dist/application/native-capabilities.js +143 -0
- package/dist/application/shell-configuration.d.ts +20 -0
- package/dist/application/shell-configuration.js +50 -1
- package/dist/application/shell-update.js +2 -0
- package/dist/commands/capacitor.js +47 -0
- package/dist/commands/create.js +2 -1
- package/dist/types.d.ts +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,6 +23,10 @@ El CLI descarga la shell, el core y la plantilla de microfrontales desde tags es
|
|
|
23
23
|
- [`mova shell`](#mova-shell)
|
|
24
24
|
- [`mova shell versions`](#mova-shell-versions)
|
|
25
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)
|
|
26
30
|
- [`mova cap add`](#mova-cap-add)
|
|
27
31
|
- [`mova cap sync`](#mova-cap-sync)
|
|
28
32
|
- [`mova cap open`](#mova-cap-open)
|
|
@@ -229,6 +233,47 @@ mova cap add android
|
|
|
229
233
|
mova cap add ios
|
|
230
234
|
```
|
|
231
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
|
+
|
|
232
277
|
### `mova cap sync`
|
|
233
278
|
|
|
234
279
|
Sincroniza la shell compilada, los recursos y los plugins con la plataforma. Sin plataforma, sincroniza todas las plataformas añadidas.
|
|
@@ -238,9 +283,8 @@ mova cap sync android
|
|
|
238
283
|
mova cap sync
|
|
239
284
|
```
|
|
240
285
|
|
|
241
|
-
En Android, el CLI aplica
|
|
242
|
-
|
|
243
|
-
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
|
|
244
288
|
entrada obligatoria de Google Maps. Para usar mapas reales, define una clave
|
|
245
289
|
restringida antes de sincronizar:
|
|
246
290
|
|
|
@@ -274,7 +318,8 @@ mova cap open ios
|
|
|
274
318
|
### `mova cap`
|
|
275
319
|
|
|
276
320
|
Es el grupo de comandos para preparar y abrir los proyectos nativos de
|
|
277
|
-
Capacitor.
|
|
321
|
+
Capacitor. También permite seleccionar capacidades mediante `list`, `enable`,
|
|
322
|
+
`disable` y `doctor`.
|
|
278
323
|
|
|
279
324
|
## Flujo habitual
|
|
280
325
|
|
|
@@ -283,6 +328,7 @@ mova create mi-aplicacion
|
|
|
283
328
|
cd mi-aplicacion
|
|
284
329
|
npm install
|
|
285
330
|
npm --prefix mfs/home install
|
|
331
|
+
mova cap enable camera device
|
|
286
332
|
mova start
|
|
287
333
|
```
|
|
288
334
|
|
|
@@ -316,6 +362,14 @@ aplica la configuración específica de los plugins instalados:
|
|
|
316
362
|
mova cap add android
|
|
317
363
|
```
|
|
318
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
|
+
|
|
319
373
|
Si utilizas Google Maps, proporciona la clave antes de sincronizar:
|
|
320
374
|
|
|
321
375
|
```bash
|
|
@@ -369,6 +423,13 @@ mova cap add ios
|
|
|
369
423
|
mova cap sync ios
|
|
370
424
|
```
|
|
371
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
|
+
|
|
372
433
|
Revisa en Xcode los permisos y la configuración nativa que necesiten los
|
|
373
434
|
plugins utilizados, por ejemplo las descripciones de cámara, fotos,
|
|
374
435
|
geolocalización o notificaciones en `Info.plist` y los entitlements
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
+
import { readNativeCapabilityCatalog } from './shell-configuration.js';
|
|
3
4
|
export function configureAndroidProject(applicationRoot, configuration) {
|
|
4
5
|
const packageConfiguration = readPackageConfiguration(applicationRoot);
|
|
5
|
-
configureMinimumSdk(applicationRoot,
|
|
6
|
+
configureMinimumSdk(applicationRoot, configuration);
|
|
7
|
+
configurePermissions(applicationRoot, configuration);
|
|
6
8
|
configureBackgroundRunner(applicationRoot, packageConfiguration);
|
|
7
9
|
configureGoogleMaps(applicationRoot, configuration, packageConfiguration);
|
|
8
10
|
}
|
|
@@ -42,8 +44,12 @@ function configureGoogleMaps(applicationRoot, configuration, packageConfiguratio
|
|
|
42
44
|
].join('\n');
|
|
43
45
|
writeFileSync(join(valuesDirectory, 'open_mova_google_maps.xml'), resource, 'utf8');
|
|
44
46
|
}
|
|
45
|
-
function configureMinimumSdk(applicationRoot,
|
|
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)
|
|
47
53
|
return;
|
|
48
54
|
const variablesPath = join(applicationRoot, 'android', 'variables.gradle');
|
|
49
55
|
if (!existsSync(variablesPath)) {
|
|
@@ -55,9 +61,28 @@ function configureMinimumSdk(applicationRoot, packageConfiguration) {
|
|
|
55
61
|
if (!Number.isFinite(currentMinimumSdk)) {
|
|
56
62
|
throw new Error('No se ha encontrado minSdkVersion en android/variables.gradle.');
|
|
57
63
|
}
|
|
58
|
-
if (currentMinimumSdk >=
|
|
64
|
+
if (currentMinimumSdk >= requiredMinimumSdk)
|
|
59
65
|
return;
|
|
60
|
-
writeFileSync(variablesPath, variables.replace(minimumSdkPattern,
|
|
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');
|
|
61
86
|
}
|
|
62
87
|
function configureBackgroundRunner(applicationRoot, packageConfiguration) {
|
|
63
88
|
if (!packageConfiguration.dependencies?.['@capacitor/background-runner'])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
|
-
export const LATEST_CONFIGURATION_SCHEMA_VERSION =
|
|
3
|
+
export const LATEST_CONFIGURATION_SCHEMA_VERSION = 3;
|
|
4
4
|
export function migrateConfiguration(value, applicationRoot) {
|
|
5
5
|
if (!isRecord(value) || typeof value.schemaVersion !== 'number') {
|
|
6
6
|
return { value, migrations: [] };
|
|
@@ -14,8 +14,54 @@ export function migrateConfiguration(value, applicationRoot) {
|
|
|
14
14
|
migrated = migrateVersionOne(migrated, applicationRoot);
|
|
15
15
|
migrations.push('1 → 2: declarar la compatibilidad de Core de cada microfrontal');
|
|
16
16
|
}
|
|
17
|
+
if (migrated.schemaVersion === 2) {
|
|
18
|
+
migrated = migrateVersionTwo(migrated, applicationRoot);
|
|
19
|
+
migrations.push('2 → 3: declarar las capacidades nativas habilitadas');
|
|
20
|
+
}
|
|
17
21
|
return { value: migrated, migrations };
|
|
18
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
|
+
}
|
|
19
65
|
function migrateVersionOne(configuration, applicationRoot) {
|
|
20
66
|
const shellCoreVersion = readCoreVersion(join(applicationRoot, 'package.json'));
|
|
21
67
|
const microfrontends = Array.isArray(configuration.microfrontends)
|
|
@@ -83,19 +83,25 @@ function validateApplicationConfiguration(value, configurationPath) {
|
|
|
83
83
|
if (!isRecord(value.native)) {
|
|
84
84
|
throw new Error(`${configurationPath} contiene una configuración nativa no válida.`);
|
|
85
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();
|
|
86
91
|
if (value.native.googleMaps !== undefined) {
|
|
87
92
|
if (!isRecord(value.native.googleMaps) ||
|
|
88
93
|
typeof value.native.googleMaps.androidApiKey !== 'string') {
|
|
89
94
|
throw new Error(`${configurationPath} contiene una clave de Google Maps no válida.`);
|
|
90
95
|
}
|
|
91
96
|
native = {
|
|
97
|
+
capabilities,
|
|
92
98
|
googleMaps: {
|
|
93
99
|
androidApiKey: value.native.googleMaps.androidApiKey,
|
|
94
100
|
},
|
|
95
101
|
};
|
|
96
102
|
}
|
|
97
103
|
else {
|
|
98
|
-
native = {};
|
|
104
|
+
native = { capabilities };
|
|
99
105
|
}
|
|
100
106
|
}
|
|
101
107
|
const microfrontends = value.microfrontends.map((entry) => validateMicrofrontend(entry, configurationPath));
|
|
@@ -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;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { npmCommand, useCommandShell } from '../utils/platform.js';
|
|
5
|
+
import { readApplicationConfiguration, writeApplicationConfiguration } from './configuration.js';
|
|
6
|
+
import { readNativeCapabilityCatalog, synchronizeShellConfiguration, } from './shell-configuration.js';
|
|
7
|
+
export function listNativeCapabilities(applicationRoot) {
|
|
8
|
+
const configuration = readApplicationConfiguration(applicationRoot);
|
|
9
|
+
const enabled = new Set(configuration.native?.capabilities ?? []);
|
|
10
|
+
return readNativeCapabilityCatalog(applicationRoot).capabilities.map((definition) => ({
|
|
11
|
+
definition,
|
|
12
|
+
enabled: enabled.has(definition.name),
|
|
13
|
+
}));
|
|
14
|
+
}
|
|
15
|
+
export function enableNativeCapabilities(applicationRoot, requestedNames) {
|
|
16
|
+
const configuration = readApplicationConfiguration(applicationRoot);
|
|
17
|
+
const definitions = resolveDefinitions(applicationRoot, requestedNames);
|
|
18
|
+
const enabled = new Set(configuration.native?.capabilities ?? []);
|
|
19
|
+
const newDefinitions = definitions.filter((definition) => !enabled.has(definition.name));
|
|
20
|
+
for (const definition of definitions)
|
|
21
|
+
enabled.add(definition.name);
|
|
22
|
+
installPackages(applicationRoot, newDefinitions);
|
|
23
|
+
return persistCapabilities(applicationRoot, configuration, [...enabled]);
|
|
24
|
+
}
|
|
25
|
+
export function disableNativeCapabilities(applicationRoot, requestedNames) {
|
|
26
|
+
const configuration = readApplicationConfiguration(applicationRoot);
|
|
27
|
+
const definitions = resolveDefinitions(applicationRoot, requestedNames);
|
|
28
|
+
const currentlyEnabled = new Set(configuration.native?.capabilities ?? []);
|
|
29
|
+
const activeDefinitions = definitions.filter((definition) => currentlyEnabled.has(definition.name));
|
|
30
|
+
const disabled = new Set(definitions.map((definition) => definition.name));
|
|
31
|
+
const remainingNames = (configuration.native?.capabilities ?? []).filter((name) => !disabled.has(name));
|
|
32
|
+
const remainingDefinitions = remainingNames.length > 0 ? resolveDefinitions(applicationRoot, remainingNames) : [];
|
|
33
|
+
const retainedPackages = new Set(remainingDefinitions.map((definition) => definition.package));
|
|
34
|
+
const packagesToRemove = [
|
|
35
|
+
...new Set(activeDefinitions
|
|
36
|
+
.map((definition) => definition.package)
|
|
37
|
+
.filter((packageName) => packageName !== '@capacitor/core')),
|
|
38
|
+
].filter((packageName) => !retainedPackages.has(packageName));
|
|
39
|
+
uninstallPackages(applicationRoot, packagesToRemove);
|
|
40
|
+
return persistCapabilities(applicationRoot, configuration, remainingNames);
|
|
41
|
+
}
|
|
42
|
+
export function diagnoseNativeCapabilities(applicationRoot, platform) {
|
|
43
|
+
const statuses = listNativeCapabilities(applicationRoot).filter((status) => status.enabled);
|
|
44
|
+
const messages = [];
|
|
45
|
+
if (statuses.length === 0) {
|
|
46
|
+
return ['No hay capacidades nativas habilitadas.'];
|
|
47
|
+
}
|
|
48
|
+
for (const { definition } of statuses) {
|
|
49
|
+
if (platform && !definition.platforms.includes(platform)) {
|
|
50
|
+
messages.push(`⚠ ${definition.name}: no es compatible con ${platform}.`);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const requirements = [];
|
|
54
|
+
if (platform === 'android' && definition.minimumAndroidSdk) {
|
|
55
|
+
requirements.push(`Android SDK mínimo ${definition.minimumAndroidSdk}`);
|
|
56
|
+
}
|
|
57
|
+
if (platform && definition.permissions?.[platform]?.length) {
|
|
58
|
+
requirements.push(`permisos: ${definition.permissions[platform]?.join(', ')}`);
|
|
59
|
+
}
|
|
60
|
+
if (definition.notes?.length)
|
|
61
|
+
requirements.push(...definition.notes);
|
|
62
|
+
messages.push(requirements.length > 0
|
|
63
|
+
? `• ${definition.name}: ${requirements.join('; ')}`
|
|
64
|
+
: `✓ ${definition.name}: sin configuración adicional declarada${platform ? ` para ${platform}` : ''}.`);
|
|
65
|
+
}
|
|
66
|
+
return messages;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Recompone las dependencias seleccionables después de actualizar la shell.
|
|
70
|
+
* No instala paquetes: el flujo de update regenera después el lockfile.
|
|
71
|
+
*/
|
|
72
|
+
export function synchronizeNativeCapabilityDependencies(applicationRoot, configuration) {
|
|
73
|
+
const packagePath = join(applicationRoot, 'package.json');
|
|
74
|
+
const packageConfiguration = JSON.parse(readFileSync(packagePath, 'utf8'));
|
|
75
|
+
const dependencies = { ...(packageConfiguration.dependencies ?? {}) };
|
|
76
|
+
const catalog = readNativeCapabilityCatalog(applicationRoot);
|
|
77
|
+
const enabled = new Set(configuration.native?.capabilities ?? []);
|
|
78
|
+
const selectablePackages = new Set(catalog.capabilities
|
|
79
|
+
.map((definition) => definition.package)
|
|
80
|
+
.filter((packageName) => packageName !== '@capacitor/core'));
|
|
81
|
+
for (const packageName of selectablePackages)
|
|
82
|
+
delete dependencies[packageName];
|
|
83
|
+
for (const definition of catalog.capabilities) {
|
|
84
|
+
if (enabled.has(definition.name) &&
|
|
85
|
+
definition.package !== '@capacitor/core' &&
|
|
86
|
+
definition.version) {
|
|
87
|
+
dependencies[definition.package] = definition.version;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
packageConfiguration.dependencies = Object.fromEntries(Object.entries(dependencies).sort(([first], [second]) => first.localeCompare(second)));
|
|
91
|
+
writeFileSync(packagePath, `${JSON.stringify(packageConfiguration, null, 2)}\n`, 'utf8');
|
|
92
|
+
}
|
|
93
|
+
function persistCapabilities(applicationRoot, configuration, capabilities) {
|
|
94
|
+
const updated = {
|
|
95
|
+
...configuration,
|
|
96
|
+
native: {
|
|
97
|
+
...configuration.native,
|
|
98
|
+
capabilities: [...new Set(capabilities)].sort(),
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
writeApplicationConfiguration(applicationRoot, updated);
|
|
102
|
+
synchronizeShellConfiguration(applicationRoot, updated);
|
|
103
|
+
return updated;
|
|
104
|
+
}
|
|
105
|
+
function resolveDefinitions(applicationRoot, names) {
|
|
106
|
+
if (names.length === 0)
|
|
107
|
+
throw new Error('Indica al menos una capacidad nativa.');
|
|
108
|
+
const catalog = readNativeCapabilityCatalog(applicationRoot);
|
|
109
|
+
const definitionsByName = new Map(catalog.capabilities.map((definition) => [definition.name, definition]));
|
|
110
|
+
const unknown = names.filter((name) => !definitionsByName.has(name));
|
|
111
|
+
if (unknown.length > 0) {
|
|
112
|
+
throw new Error(`Capacidades desconocidas: ${unknown.join(', ')}. Usa "mova cap list" para consultar el catálogo.`);
|
|
113
|
+
}
|
|
114
|
+
return names.map((name) => definitionsByName.get(name));
|
|
115
|
+
}
|
|
116
|
+
function installPackages(applicationRoot, definitions) {
|
|
117
|
+
const packages = [
|
|
118
|
+
...new Set(definitions
|
|
119
|
+
.filter((definition) => definition.package !== '@capacitor/core')
|
|
120
|
+
.map((definition) => `${definition.package}@${definition.version ?? 'latest'}`)),
|
|
121
|
+
];
|
|
122
|
+
if (packages.length > 0)
|
|
123
|
+
runNpm(applicationRoot, ['install', '--save', ...packages]);
|
|
124
|
+
}
|
|
125
|
+
function uninstallPackages(applicationRoot, packages) {
|
|
126
|
+
if (packages.length > 0)
|
|
127
|
+
runNpm(applicationRoot, ['uninstall', '--save', ...packages]);
|
|
128
|
+
}
|
|
129
|
+
function runNpm(applicationRoot, arguments_) {
|
|
130
|
+
if (!existsSync(join(applicationRoot, 'package.json'))) {
|
|
131
|
+
throw new Error(`No se encuentra package.json en ${applicationRoot}.`);
|
|
132
|
+
}
|
|
133
|
+
const result = spawnSync(npmCommand(), arguments_, {
|
|
134
|
+
cwd: applicationRoot,
|
|
135
|
+
stdio: 'inherit',
|
|
136
|
+
shell: useCommandShell(),
|
|
137
|
+
});
|
|
138
|
+
if (result.error)
|
|
139
|
+
throw result.error;
|
|
140
|
+
if (result.status !== 0) {
|
|
141
|
+
throw new Error(`${npmCommand()} ${arguments_.join(' ')} terminó con error.`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -1,2 +1,22 @@
|
|
|
1
1
|
import type { OpenMovaApplicationConfiguration } from '../types.js';
|
|
2
|
+
export interface NativeCapabilityDefinition {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly implementation: string;
|
|
5
|
+
readonly exportName: string;
|
|
6
|
+
readonly package: string;
|
|
7
|
+
readonly version?: string;
|
|
8
|
+
readonly platforms: readonly ('android' | 'ios' | 'web')[];
|
|
9
|
+
readonly minimumAndroidSdk?: number;
|
|
10
|
+
readonly permissions?: {
|
|
11
|
+
readonly android?: readonly string[];
|
|
12
|
+
readonly ios?: readonly string[];
|
|
13
|
+
};
|
|
14
|
+
readonly notes?: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
interface NativeCapabilityCatalog {
|
|
17
|
+
readonly schemaVersion: 1;
|
|
18
|
+
readonly capabilities: readonly NativeCapabilityDefinition[];
|
|
19
|
+
}
|
|
2
20
|
export declare function synchronizeShellConfiguration(applicationRoot: string, configuration: OpenMovaApplicationConfiguration): void;
|
|
21
|
+
export declare function readNativeCapabilityCatalog(applicationRoot: string): NativeCapabilityCatalog;
|
|
22
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { writeFileSync } from 'node:fs';
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
export function synchronizeShellConfiguration(applicationRoot, configuration) {
|
|
4
4
|
const manifest = Object.fromEntries(configuration.microfrontends.map((microfrontend) => [
|
|
@@ -7,6 +7,55 @@ export function synchronizeShellConfiguration(applicationRoot, configuration) {
|
|
|
7
7
|
]));
|
|
8
8
|
writeFileSync(join(applicationRoot, 'src', 'assets', 'federation.manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
|
|
9
9
|
writeFileSync(join(applicationRoot, 'src', 'app', 'application.config.ts'), renderApplicationConfiguration(configuration), 'utf8');
|
|
10
|
+
synchronizeNativeCapabilities(applicationRoot, configuration);
|
|
11
|
+
}
|
|
12
|
+
export function readNativeCapabilityCatalog(applicationRoot) {
|
|
13
|
+
const catalogPath = join(applicationRoot, 'native-capabilities.catalog.json');
|
|
14
|
+
const catalog = JSON.parse(readFileSync(catalogPath, 'utf8'));
|
|
15
|
+
if (catalog.schemaVersion !== 1 || !Array.isArray(catalog.capabilities)) {
|
|
16
|
+
throw new Error(`${catalogPath} no contiene un catálogo de capacidades válido.`);
|
|
17
|
+
}
|
|
18
|
+
return catalog;
|
|
19
|
+
}
|
|
20
|
+
function synchronizeNativeCapabilities(applicationRoot, configuration) {
|
|
21
|
+
const catalog = readNativeCapabilityCatalog(applicationRoot);
|
|
22
|
+
const enabledNames = new Set(configuration.native?.capabilities ?? []);
|
|
23
|
+
const knownNames = new Set(catalog.capabilities.map((capability) => capability.name));
|
|
24
|
+
const unknownNames = [...enabledNames].filter((name) => !knownNames.has(name));
|
|
25
|
+
if (unknownNames.length > 0) {
|
|
26
|
+
throw new Error(`Capacidades nativas desconocidas: ${unknownNames.join(', ')}.`);
|
|
27
|
+
}
|
|
28
|
+
const enabled = catalog.capabilities.filter((capability) => enabledNames.has(capability.name));
|
|
29
|
+
const imports = enabled
|
|
30
|
+
.map((capability) => `import { ${capability.exportName} } from './${capability.implementation}';`)
|
|
31
|
+
.join('\n');
|
|
32
|
+
const entries = catalog.capabilities
|
|
33
|
+
.map((capability) => {
|
|
34
|
+
const value = enabledNames.has(capability.name)
|
|
35
|
+
? capability.exportName
|
|
36
|
+
: `createUnavailableNativeCapability<NativeCapabilities[${JSON.stringify(capability.name)}]>(${JSON.stringify(capability.name)})`;
|
|
37
|
+
return ` ${capability.name}: ${value},`;
|
|
38
|
+
})
|
|
39
|
+
.join('\n');
|
|
40
|
+
const source = `import type { Provider } from '@angular/core';
|
|
41
|
+
import { NATIVE_CAPABILITIES, type NativeCapabilities } from '@open-mova/core';
|
|
42
|
+
import { createUnavailableNativeCapability } from './unavailable-native-capability';
|
|
43
|
+
${imports ? `\n${imports}` : ''}
|
|
44
|
+
|
|
45
|
+
// Generado por el CLI desde mova.config.json. No editar manualmente.
|
|
46
|
+
const nativeCapabilities = {
|
|
47
|
+
version: 1,
|
|
48
|
+
${entries}
|
|
49
|
+
} satisfies NativeCapabilities;
|
|
50
|
+
|
|
51
|
+
export function provideNativeCapabilities(): Provider {
|
|
52
|
+
return {
|
|
53
|
+
provide: NATIVE_CAPABILITIES,
|
|
54
|
+
useValue: nativeCapabilities,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
`;
|
|
58
|
+
writeFileSync(join(applicationRoot, 'src', 'native-capabilities', 'native-capabilities.provider.ts'), source, 'utf8');
|
|
10
59
|
}
|
|
11
60
|
function renderApplicationConfiguration(configuration) {
|
|
12
61
|
const entries = configuration.microfrontends
|
|
@@ -7,6 +7,7 @@ import { writeApplicationConfiguration } from './configuration.js';
|
|
|
7
7
|
import { applyFileChanges, compareManagedFiles, comparePackageConfiguration, requireCleanGitRepository, } from './project-update.js';
|
|
8
8
|
import { findIncompatibleMicrofrontends } from './core-compatibility.js';
|
|
9
9
|
import { readRequiredCoreVersion } from './microfrontend-manifest.js';
|
|
10
|
+
import { synchronizeNativeCapabilityDependencies } from './native-capabilities.js';
|
|
10
11
|
const GENERATED_PATHS = new Set([
|
|
11
12
|
'package.json',
|
|
12
13
|
'package-lock.json',
|
|
@@ -83,6 +84,7 @@ export function applyShellUpdate(applicationRoot, configuration, plan) {
|
|
|
83
84
|
...configuration,
|
|
84
85
|
shell: plan.target,
|
|
85
86
|
};
|
|
87
|
+
synchronizeNativeCapabilityDependencies(applicationRoot, updatedConfiguration);
|
|
86
88
|
writeApplicationConfiguration(applicationRoot, updatedConfiguration);
|
|
87
89
|
synchronizeShellConfiguration(applicationRoot, updatedConfiguration);
|
|
88
90
|
}
|
|
@@ -3,9 +3,47 @@ import { existsSync, writeFileSync } from 'node:fs';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { readApplicationConfiguration, requireApplicationRoot, } from '../application/configuration.js';
|
|
5
5
|
import { configureAndroidProject } from '../application/android-configuration.js';
|
|
6
|
+
import { diagnoseNativeCapabilities, disableNativeCapabilities, enableNativeCapabilities, listNativeCapabilities, } from '../application/native-capabilities.js';
|
|
6
7
|
import { localBinaryName, npmCommand, useCommandShell } from '../utils/platform.js';
|
|
7
8
|
export function registerCapacitorCommands(program) {
|
|
8
9
|
const capacitor = program.command('cap').description('Prepara la shell para Android o iOS');
|
|
10
|
+
capacitor
|
|
11
|
+
.command('list')
|
|
12
|
+
.description('Lista las capacidades nativas disponibles y habilitadas')
|
|
13
|
+
.action(() => {
|
|
14
|
+
const root = requireApplicationRoot(process.cwd());
|
|
15
|
+
for (const { definition, enabled } of listNativeCapabilities(root)) {
|
|
16
|
+
console.log(`${enabled ? '✓' : '○'} ${definition.name} (${definition.platforms.join(', ')})`);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
capacitor
|
|
20
|
+
.command('enable <capabilities...>')
|
|
21
|
+
.description('Instala y habilita capacidades nativas en la aplicación')
|
|
22
|
+
.action((capabilities) => {
|
|
23
|
+
const root = requireApplicationRoot(process.cwd());
|
|
24
|
+
enableNativeCapabilities(root, capabilities);
|
|
25
|
+
synchronizeExistingNativeProjects(root);
|
|
26
|
+
console.log(`Capacidades habilitadas: ${capabilities.join(', ')}.`);
|
|
27
|
+
});
|
|
28
|
+
capacitor
|
|
29
|
+
.command('disable <capabilities...>')
|
|
30
|
+
.description('Deshabilita capacidades y elimina los plugins que ya no se usan')
|
|
31
|
+
.action((capabilities) => {
|
|
32
|
+
const root = requireApplicationRoot(process.cwd());
|
|
33
|
+
disableNativeCapabilities(root, capabilities);
|
|
34
|
+
synchronizeExistingNativeProjects(root);
|
|
35
|
+
console.log(`Capacidades deshabilitadas: ${capabilities.join(', ')}.`);
|
|
36
|
+
});
|
|
37
|
+
capacitor
|
|
38
|
+
.command('doctor [platform]')
|
|
39
|
+
.description('Muestra requisitos y configuración pendiente de las capacidades')
|
|
40
|
+
.action((platform) => {
|
|
41
|
+
const root = requireApplicationRoot(process.cwd());
|
|
42
|
+
const selectedPlatform = platform ? parsePlatform(platform) : undefined;
|
|
43
|
+
for (const message of diagnoseNativeCapabilities(root, selectedPlatform)) {
|
|
44
|
+
console.log(message);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
9
47
|
capacitor
|
|
10
48
|
.command('add <platform>')
|
|
11
49
|
.description('Crea el proyecto nativo de la plataforma')
|
|
@@ -81,6 +119,15 @@ function configureExistingPlatforms(root, configuration) {
|
|
|
81
119
|
configureNativePlatform(root, 'android', configuration);
|
|
82
120
|
}
|
|
83
121
|
}
|
|
122
|
+
function synchronizeExistingNativeProjects(root) {
|
|
123
|
+
const platforms = ['android', 'ios'];
|
|
124
|
+
for (const platform of platforms) {
|
|
125
|
+
if (existsSync(join(root, platform))) {
|
|
126
|
+
runCapacitor(root, ['sync', platform]);
|
|
127
|
+
configureNativePlatform(root, platform, readApplicationConfiguration(root));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
84
131
|
function configureNativePlatform(root, platform, configuration) {
|
|
85
132
|
if (platform === 'android') {
|
|
86
133
|
configureAndroidProject(root, configuration);
|
package/dist/commands/create.js
CHANGED
|
@@ -27,9 +27,10 @@ export function registerCreateCommand(program) {
|
|
|
27
27
|
shellVersion = shell.version;
|
|
28
28
|
configureDownloadedShell(temporaryApplication, applicationName, !options.empty);
|
|
29
29
|
let configuration = {
|
|
30
|
-
schemaVersion:
|
|
30
|
+
schemaVersion: 3,
|
|
31
31
|
name: applicationName,
|
|
32
32
|
shell,
|
|
33
|
+
native: { capabilities: [] },
|
|
33
34
|
microfrontends: [],
|
|
34
35
|
};
|
|
35
36
|
if (!options.empty) {
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export interface OpenMovaApplicationConfiguration {
|
|
2
|
-
readonly schemaVersion:
|
|
2
|
+
readonly schemaVersion: 3;
|
|
3
3
|
readonly name: string;
|
|
4
4
|
readonly shell?: {
|
|
5
5
|
readonly repository: string;
|
|
@@ -11,6 +11,8 @@ export interface OpenMovaApplicationConfiguration {
|
|
|
11
11
|
readonly microfrontends: readonly MicrofrontendConfiguration[];
|
|
12
12
|
}
|
|
13
13
|
export interface NativeConfiguration {
|
|
14
|
+
/** Capacidades instaladas y proporcionadas por la shell. */
|
|
15
|
+
readonly capabilities: readonly string[];
|
|
14
16
|
readonly googleMaps?: {
|
|
15
17
|
/** Clave de Android Maps. También puede venir de OPEN_MOVA_GOOGLE_MAPS_ANDROID_API_KEY. */
|
|
16
18
|
readonly androidApiKey?: string;
|