@open-mova/cli 0.1.18 → 0.1.20

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 CHANGED
@@ -165,6 +165,30 @@ mova cap sync android
165
165
  mova cap sync
166
166
  ```
167
167
 
168
+ En Android, el CLI aplica también los requisitos técnicos de los plugins
169
+ incluidos por la shell: configura el repositorio AAR de Background Runner y
170
+ eleva `minSdkVersion` a 28 cuando está instalado Local LLM. También añade la
171
+ entrada obligatoria de Google Maps. Para usar mapas reales, define una clave
172
+ restringida antes de sincronizar:
173
+
174
+ ```bash
175
+ OPEN_MOVA_GOOGLE_MAPS_ANDROID_API_KEY=tu_clave mova cap sync android
176
+ ```
177
+
178
+ Como alternativa, guarda `native.googleMaps.androidApiKey` en
179
+ `mova.config.json`. Sin una clave configurada la aplicación sigue arrancando,
180
+ pero Google Maps no estará operativo.
181
+
182
+ ```json
183
+ {
184
+ "native": {
185
+ "googleMaps": {
186
+ "androidApiKey": "tu_clave"
187
+ }
188
+ }
189
+ }
190
+ ```
191
+
168
192
  ### `mova cap open`
169
193
 
170
194
  Abre el proyecto nativo en Android Studio o Xcode.
@@ -77,11 +77,32 @@ function validateApplicationConfiguration(value, configurationPath) {
77
77
  commit: value.shell.commit,
78
78
  };
79
79
  }
80
+ let native;
81
+ if (value.native !== undefined) {
82
+ if (!isRecord(value.native)) {
83
+ throw new Error(`${configurationPath} contiene una configuración nativa no válida.`);
84
+ }
85
+ if (value.native.googleMaps !== undefined) {
86
+ if (!isRecord(value.native.googleMaps) ||
87
+ typeof value.native.googleMaps.androidApiKey !== 'string') {
88
+ throw new Error(`${configurationPath} contiene una clave de Google Maps no válida.`);
89
+ }
90
+ native = {
91
+ googleMaps: {
92
+ androidApiKey: value.native.googleMaps.androidApiKey,
93
+ },
94
+ };
95
+ }
96
+ else {
97
+ native = {};
98
+ }
99
+ }
80
100
  const microfrontends = value.microfrontends.map((entry) => validateMicrofrontend(entry, configurationPath));
81
101
  return {
82
102
  schemaVersion: 1,
83
103
  name: value.name,
84
104
  ...(shell ? { shell } : {}),
105
+ ...(native ? { native } : {}),
85
106
  microfrontends,
86
107
  };
87
108
  }
@@ -1,5 +1,5 @@
1
1
  import { spawnSync } from 'node:child_process';
2
- import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import { readApplicationConfiguration, requireApplicationRoot, } from '../application/configuration.js';
5
5
  import { localBinaryName, npmCommand, useCommandShell } from '../utils/platform.js';
@@ -12,7 +12,7 @@ export function registerCapacitorCommands(program) {
12
12
  const root = requireApplicationRoot(process.cwd());
13
13
  buildMobileShell(root);
14
14
  runCapacitor(root, ['add', selectedPlatform]);
15
- configureNativePlatform(root, selectedPlatform);
15
+ configureNativePlatform(root, selectedPlatform, readApplicationConfiguration(root));
16
16
  });
17
17
  capacitor.command('sync [platform]')
18
18
  .description('Compila la shell y sincroniza sus assets y plugins nativos')
@@ -22,10 +22,10 @@ export function registerCapacitorCommands(program) {
22
22
  const selectedPlatform = platform ? parsePlatform(platform) : undefined;
23
23
  runCapacitor(root, ['sync', ...(selectedPlatform ? [selectedPlatform] : [])]);
24
24
  if (selectedPlatform) {
25
- configureNativePlatform(root, selectedPlatform);
25
+ configureNativePlatform(root, selectedPlatform, readApplicationConfiguration(root));
26
26
  }
27
27
  else {
28
- configureExistingPlatforms(root);
28
+ configureExistingPlatforms(root, readApplicationConfiguration(root));
29
29
  }
30
30
  });
31
31
  capacitor.command('open <platform>')
@@ -72,16 +72,83 @@ function isHttpsUrl(value) {
72
72
  return false;
73
73
  }
74
74
  }
75
- function configureExistingPlatforms(root) {
75
+ function configureExistingPlatforms(root, configuration) {
76
76
  if (existsSync(join(root, 'android'))) {
77
- configureNativePlatform(root, 'android');
77
+ configureNativePlatform(root, 'android', configuration);
78
78
  }
79
79
  }
80
- function configureNativePlatform(root, platform) {
80
+ function configureNativePlatform(root, platform, configuration) {
81
81
  if (platform === 'android') {
82
+ configureMinimumAndroidSdk(root);
82
83
  configureBackgroundRunnerForAndroid(root);
84
+ configureGoogleMapsForAndroid(root, configuration);
83
85
  }
84
86
  }
87
+ function configureGoogleMapsForAndroid(root, configuration) {
88
+ const packageJson = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
89
+ if (!packageJson.dependencies?.['@capacitor/google-maps'])
90
+ return;
91
+ const manifestPath = join(root, 'android', 'app', 'src', 'main', 'AndroidManifest.xml');
92
+ if (!existsSync(manifestPath)) {
93
+ throw new Error('No se encuentra AndroidManifest.xml para configurar Google Maps.');
94
+ }
95
+ const manifest = readFileSync(manifestPath, 'utf8');
96
+ const apiKeyMetadata = 'android:name="com.google.android.geo.API_KEY"';
97
+ if (!manifest.includes(apiKeyMetadata)) {
98
+ const applicationCloseTag = '</application>';
99
+ if (!manifest.includes(applicationCloseTag)) {
100
+ throw new Error('No se ha encontrado el elemento application en AndroidManifest.xml.');
101
+ }
102
+ const metadata = [
103
+ ' <meta-data',
104
+ ` ${apiKeyMetadata}`,
105
+ ' android:value="@string/open_mova_google_maps_api_key" />',
106
+ ].join('\n');
107
+ writeFileSync(manifestPath, manifest.replace(applicationCloseTag, `${metadata}\n ${applicationCloseTag}`), 'utf8');
108
+ }
109
+ const valuesDirectory = join(root, 'android', 'app', 'src', 'main', 'res', 'values');
110
+ mkdirSync(valuesDirectory, { recursive: true });
111
+ const apiKey = process.env.OPEN_MOVA_GOOGLE_MAPS_ANDROID_API_KEY
112
+ ?? configuration.native?.googleMaps?.androidApiKey
113
+ // Google Maps aborta la aplicación si falta por completo esta entrada.
114
+ // El valor explícito permite arrancar; Maps seguirá requiriendo una clave real al usarse.
115
+ ?? 'OPEN_MOVA_GOOGLE_MAPS_API_KEY_NOT_CONFIGURED';
116
+ const resource = [
117
+ '<?xml version="1.0" encoding="utf-8"?>',
118
+ '<resources>',
119
+ ` <string name="open_mova_google_maps_api_key" translatable="false">${escapeXml(apiKey)}</string>`,
120
+ '</resources>',
121
+ '',
122
+ ].join('\n');
123
+ writeFileSync(join(valuesDirectory, 'open_mova_google_maps.xml'), resource, 'utf8');
124
+ }
125
+ function escapeXml(value) {
126
+ return value.replace(/[&<>"']/g, (character) => ({
127
+ '&': '&amp;',
128
+ '<': '&lt;',
129
+ '>': '&gt;',
130
+ '"': '&quot;',
131
+ "'": '&apos;',
132
+ })[character] ?? character);
133
+ }
134
+ function configureMinimumAndroidSdk(root) {
135
+ const packageJson = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
136
+ if (!packageJson.dependencies?.['@capacitor/local-llm'])
137
+ return;
138
+ const variablesPath = join(root, 'android', 'variables.gradle');
139
+ if (!existsSync(variablesPath)) {
140
+ throw new Error('No se encuentra android/variables.gradle para configurar Local LLM.');
141
+ }
142
+ const variables = readFileSync(variablesPath, 'utf8');
143
+ const minimumSdkPattern = /minSdkVersion\s*=\s*(\d+)/;
144
+ const currentMinimumSdk = Number(minimumSdkPattern.exec(variables)?.[1]);
145
+ if (!Number.isFinite(currentMinimumSdk)) {
146
+ throw new Error('No se ha encontrado minSdkVersion en android/variables.gradle.');
147
+ }
148
+ if (currentMinimumSdk >= 28)
149
+ return;
150
+ writeFileSync(variablesPath, variables.replace(minimumSdkPattern, 'minSdkVersion = 28'), 'utf8');
151
+ }
85
152
  function configureBackgroundRunnerForAndroid(root) {
86
153
  const packageJson = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
87
154
  if (!packageJson.dependencies?.['@capacitor/background-runner'])
package/dist/types.d.ts CHANGED
@@ -6,8 +6,16 @@ export interface OpenMovaApplicationConfiguration {
6
6
  readonly version: string;
7
7
  readonly commit: string;
8
8
  };
9
+ /** Configuración opcional de las capacidades nativas de la aplicación. */
10
+ readonly native?: NativeConfiguration;
9
11
  readonly microfrontends: readonly MicrofrontendConfiguration[];
10
12
  }
13
+ export interface NativeConfiguration {
14
+ readonly googleMaps?: {
15
+ /** Clave de Android Maps. También puede venir de OPEN_MOVA_GOOGLE_MAPS_ANDROID_API_KEY. */
16
+ readonly androidApiKey?: string;
17
+ };
18
+ }
11
19
  export interface MicrofrontendConfiguration {
12
20
  readonly name: string;
13
21
  readonly route: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mova/cli",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "CLI para crear y mantener aplicaciones Open Mova",
5
5
  "license": "MIT",
6
6
  "author": "Open Mova contributors",