@open-mova/cli 0.1.19 → 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
@@ -167,7 +167,27 @@ mova cap sync
167
167
 
168
168
  En Android, el CLI aplica también los requisitos técnicos de los plugins
169
169
  incluidos por la shell: configura el repositorio AAR de Background Runner y
170
- eleva `minSdkVersion` a 28 cuando está instalado Local LLM.
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
+ ```
171
191
 
172
192
  ### `mova cap open`
173
193
 
@@ -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,17 +72,65 @@ 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
82
  configureMinimumAndroidSdk(root);
83
83
  configureBackgroundRunnerForAndroid(root);
84
+ configureGoogleMapsForAndroid(root, configuration);
84
85
  }
85
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
+ }
86
134
  function configureMinimumAndroidSdk(root) {
87
135
  const packageJson = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
88
136
  if (!packageJson.dependencies?.['@capacitor/local-llm'])
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.19",
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",