@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.
- package/README.md +173 -3
- package/dist/application/android-configuration.d.ts +2 -0
- package/dist/application/android-configuration.js +90 -0
- package/dist/application/configuration-migrations.d.ts +6 -0
- package/dist/application/configuration-migrations.js +57 -0
- package/dist/application/configuration.d.ts +5 -0
- package/dist/application/configuration.js +31 -19
- package/dist/application/core-compatibility.d.ts +3 -0
- package/dist/application/core-compatibility.js +10 -0
- package/dist/application/doctor.d.ts +12 -0
- package/dist/application/doctor.js +304 -0
- package/dist/application/microfrontend-manifest.d.ts +3 -0
- package/dist/application/microfrontend-manifest.js +20 -0
- package/dist/application/microfrontend-profile.js +2 -1
- package/dist/application/microfrontend-update.d.ts +17 -0
- package/dist/application/microfrontend-update.js +117 -0
- package/dist/application/microfrontend.d.ts +1 -0
- package/dist/application/microfrontend.js +14 -3
- package/dist/application/project-update.d.ts +8 -0
- package/dist/application/project-update.js +106 -0
- package/dist/application/shell-configuration.js +2 -0
- package/dist/application/shell-repository.d.ts +1 -1
- package/dist/application/shell-repository.js +14 -7
- package/dist/application/shell-update.d.ts +1 -5
- package/dist/application/shell-update.js +9 -108
- package/dist/cli.js +2 -0
- package/dist/commands/capacitor.js +9 -91
- package/dist/commands/create.js +2 -2
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +25 -0
- package/dist/commands/info.js +1 -1
- package/dist/commands/microfrontend.js +40 -1
- package/dist/commands/update.js +13 -4
- package/dist/types.d.ts +5 -1
- package/package.json +7 -3
|
@@ -14,12 +14,14 @@ function renderApplicationConfiguration(configuration) {
|
|
|
14
14
|
path: ${JSON.stringify(microfrontend.route)},
|
|
15
15
|
remote: ${JSON.stringify(microfrontend.remoteName)},
|
|
16
16
|
exposedModule: './Routes',
|
|
17
|
+
requiredCoreVersion: ${JSON.stringify(microfrontend.compatibility.requiredCoreVersion)},
|
|
17
18
|
},`)
|
|
18
19
|
.join('\n');
|
|
19
20
|
return `export interface MicrofrontendDefinition {
|
|
20
21
|
readonly path: string;
|
|
21
22
|
readonly remote: string;
|
|
22
23
|
readonly exposedModule: './Routes';
|
|
24
|
+
readonly requiredCoreVersion: string;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
// Este fichero lo mantiene el CLI a partir de mova.config.json.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const SHELL_REPOSITORY
|
|
1
|
+
export declare const SHELL_REPOSITORY: string;
|
|
2
2
|
export declare const DEMO_MICROFRONTEND_REMOTE_ENTRY = "https://rgarciadelongoria.github.io/open-mova/remoteEntry.json";
|
|
3
3
|
export interface ShellVersion {
|
|
4
4
|
readonly repository: string;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { cpSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync
|
|
2
|
+
import { cpSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { basename, join } from 'node:path';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
|
-
export const SHELL_REPOSITORY = 'https://github.com/rgarciadelongoria/open-mova.git';
|
|
5
|
+
export const SHELL_REPOSITORY = process.env['OPEN_MOVA_SHELL_REPOSITORY'] ?? 'https://github.com/rgarciadelongoria/open-mova.git';
|
|
6
6
|
// El demo publicado permite probar una aplicación recién creada sin desplegar un MF propio.
|
|
7
7
|
export const DEMO_MICROFRONTEND_REMOTE_ENTRY = 'https://rgarciadelongoria.github.io/open-mova/remoteEntry.json';
|
|
8
8
|
const VERSION_PATTERN = /^v(\d+)\.(\d+)\.(\d+)$/;
|
|
@@ -41,9 +41,17 @@ export function downloadTaggedProject(project, destination, requestedVersion) {
|
|
|
41
41
|
try {
|
|
42
42
|
const checkout = join(temporaryDirectory, 'repository');
|
|
43
43
|
runGit([
|
|
44
|
-
'-c',
|
|
45
|
-
'
|
|
46
|
-
'
|
|
44
|
+
'-c',
|
|
45
|
+
'advice.detachedHead=false',
|
|
46
|
+
'clone',
|
|
47
|
+
'--quiet',
|
|
48
|
+
'--depth',
|
|
49
|
+
'1',
|
|
50
|
+
'--single-branch',
|
|
51
|
+
'--branch',
|
|
52
|
+
version,
|
|
53
|
+
SHELL_REPOSITORY,
|
|
54
|
+
checkout,
|
|
47
55
|
]);
|
|
48
56
|
const source = join(checkout, project);
|
|
49
57
|
const requiredFiles = ['package.json', 'angular.json', 'src/main.ts'];
|
|
@@ -92,8 +100,7 @@ export function configureDownloadedShell(destination, applicationName, includesS
|
|
|
92
100
|
writeFileSync(capacitorPath, customizedConfig);
|
|
93
101
|
}
|
|
94
102
|
writeFileSync(join(destination, '.nvmrc'), '22\n');
|
|
95
|
-
const installSteps = 'npm install\n' +
|
|
96
|
-
(includesStarterMicrofrontend ? 'npm --prefix mfs/home install\n' : '');
|
|
103
|
+
const installSteps = 'npm install\n' + (includesStarterMicrofrontend ? 'npm --prefix mfs/home install\n' : '');
|
|
97
104
|
const runSteps = includesStarterMicrofrontend
|
|
98
105
|
? 'Inicia `npm --prefix mfs/home start` y `npm start` en dos terminales. Abre `http://localhost:4200/home/inicio`.\n'
|
|
99
106
|
: 'Registra primero un MF con `mova mf create nombre` y después ejecuta `mova start`.\n';
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import type { OpenMovaApplicationConfiguration } from '../types.js';
|
|
2
2
|
import { type ShellVersion } from './shell-repository.js';
|
|
3
|
-
|
|
4
|
-
readonly path: string;
|
|
5
|
-
readonly content?: Buffer;
|
|
6
|
-
}
|
|
3
|
+
import { type FileChange } from './project-update.js';
|
|
7
4
|
export interface ShellUpdatePlan {
|
|
8
5
|
readonly currentVersion: string;
|
|
9
6
|
readonly target: ShellVersion;
|
|
@@ -16,4 +13,3 @@ export interface ShellUpdatePlan {
|
|
|
16
13
|
}
|
|
17
14
|
export declare function createShellUpdatePlan(applicationRoot: string, configuration: OpenMovaApplicationConfiguration, requestedVersion?: string): ShellUpdatePlan;
|
|
18
15
|
export declare function applyShellUpdate(applicationRoot: string, configuration: OpenMovaApplicationConfiguration, plan: ShellUpdatePlan): void;
|
|
19
|
-
export {};
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { dirname, join, relative } from 'node:path';
|
|
1
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
4
3
|
import { tmpdir } from 'node:os';
|
|
5
4
|
import { synchronizeShellConfiguration } from './shell-configuration.js';
|
|
6
5
|
import { downloadShell, listShellVersions } from './shell-repository.js';
|
|
7
6
|
import { writeApplicationConfiguration } from './configuration.js';
|
|
7
|
+
import { applyFileChanges, compareManagedFiles, comparePackageConfiguration, requireCleanGitRepository, } from './project-update.js';
|
|
8
|
+
import { findIncompatibleMicrofrontends } from './core-compatibility.js';
|
|
9
|
+
import { readRequiredCoreVersion } from './microfrontend-manifest.js';
|
|
8
10
|
const GENERATED_PATHS = new Set([
|
|
9
11
|
'package.json',
|
|
10
12
|
'package-lock.json',
|
|
@@ -34,8 +36,10 @@ export function createShellUpdatePlan(applicationRoot, configuration, requestedV
|
|
|
34
36
|
throw new Error(`El commit registrado para ${configuration.shell.version} no coincide con el tag remoto.`);
|
|
35
37
|
}
|
|
36
38
|
const conflicts = [];
|
|
37
|
-
const fileChanges = compareManagedFiles(applicationRoot, currentDirectory, targetDirectory, conflicts);
|
|
39
|
+
const fileChanges = compareManagedFiles(applicationRoot, currentDirectory, targetDirectory, conflicts, GENERATED_PATHS);
|
|
38
40
|
const packageUpdate = comparePackageConfiguration(applicationRoot, currentDirectory, targetDirectory, conflicts);
|
|
41
|
+
const targetCoreVersion = readRequiredCoreVersion(targetDirectory);
|
|
42
|
+
conflicts.push(...findIncompatibleMicrofrontends(configuration, targetCoreVersion));
|
|
39
43
|
const capacitorUpdate = compareCapacitorConfiguration(applicationRoot, currentDirectory, targetDirectory, conflicts);
|
|
40
44
|
const changes = [
|
|
41
45
|
...fileChanges.map((change) => `${change.content ? 'Actualizar' : 'Eliminar'} ${change.path}`),
|
|
@@ -65,16 +69,7 @@ export function applyShellUpdate(applicationRoot, configuration, plan) {
|
|
|
65
69
|
throw new Error('No se puede actualizar mientras existan conflictos.');
|
|
66
70
|
}
|
|
67
71
|
requireCleanGitRepository(applicationRoot);
|
|
68
|
-
|
|
69
|
-
const destination = join(applicationRoot, change.path);
|
|
70
|
-
if (change.content) {
|
|
71
|
-
mkdirSync(dirname(destination), { recursive: true });
|
|
72
|
-
writeFileSync(destination, change.content);
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
rmSync(destination, { force: true });
|
|
76
|
-
}
|
|
77
|
-
}
|
|
72
|
+
applyFileChanges(applicationRoot, plan.fileChanges);
|
|
78
73
|
if (plan.packageContent) {
|
|
79
74
|
writeFileSync(join(applicationRoot, 'package.json'), plan.packageContent, 'utf8');
|
|
80
75
|
}
|
|
@@ -91,60 +86,6 @@ export function applyShellUpdate(applicationRoot, configuration, plan) {
|
|
|
91
86
|
writeApplicationConfiguration(applicationRoot, updatedConfiguration);
|
|
92
87
|
synchronizeShellConfiguration(applicationRoot, updatedConfiguration);
|
|
93
88
|
}
|
|
94
|
-
function compareManagedFiles(applicationRoot, currentDirectory, targetDirectory, conflicts) {
|
|
95
|
-
const paths = new Set([
|
|
96
|
-
...listFiles(currentDirectory),
|
|
97
|
-
...listFiles(targetDirectory),
|
|
98
|
-
]);
|
|
99
|
-
const changes = [];
|
|
100
|
-
for (const path of [...paths].sort()) {
|
|
101
|
-
if (GENERATED_PATHS.has(path))
|
|
102
|
-
continue;
|
|
103
|
-
const currentContent = readOptionalFile(join(currentDirectory, path));
|
|
104
|
-
const targetContent = readOptionalFile(join(targetDirectory, path));
|
|
105
|
-
const applicationContent = readOptionalFile(join(applicationRoot, path));
|
|
106
|
-
if (buffersEqual(currentContent, targetContent))
|
|
107
|
-
continue;
|
|
108
|
-
if (buffersEqual(applicationContent, currentContent)) {
|
|
109
|
-
changes.push({ path, ...(targetContent ? { content: targetContent } : {}) });
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
if (buffersEqual(applicationContent, targetContent))
|
|
113
|
-
continue;
|
|
114
|
-
conflicts.push(path);
|
|
115
|
-
}
|
|
116
|
-
return changes;
|
|
117
|
-
}
|
|
118
|
-
function comparePackageConfiguration(applicationRoot, currentDirectory, targetDirectory, conflicts) {
|
|
119
|
-
const application = readJson(join(applicationRoot, 'package.json'));
|
|
120
|
-
const current = readJson(join(currentDirectory, 'package.json'));
|
|
121
|
-
const target = readJson(join(targetDirectory, 'package.json'));
|
|
122
|
-
let changed = false;
|
|
123
|
-
for (const section of ['scripts', 'dependencies', 'devDependencies']) {
|
|
124
|
-
const applicationSection = readStringMap(application[section]);
|
|
125
|
-
const currentSection = readStringMap(current[section]);
|
|
126
|
-
const targetSection = readStringMap(target[section]);
|
|
127
|
-
const keys = new Set([...Object.keys(currentSection), ...Object.keys(targetSection)]);
|
|
128
|
-
for (const key of keys) {
|
|
129
|
-
if (currentSection[key] === targetSection[key])
|
|
130
|
-
continue;
|
|
131
|
-
if (applicationSection[key] === currentSection[key]) {
|
|
132
|
-
if (targetSection[key] === undefined) {
|
|
133
|
-
delete applicationSection[key];
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
applicationSection[key] = targetSection[key];
|
|
137
|
-
}
|
|
138
|
-
changed = true;
|
|
139
|
-
}
|
|
140
|
-
else if (applicationSection[key] !== targetSection[key]) {
|
|
141
|
-
conflicts.push(`package.json#${section}.${key}`);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
application[section] = applicationSection;
|
|
145
|
-
}
|
|
146
|
-
return changed ? `${JSON.stringify(application, null, 2)}\n` : undefined;
|
|
147
|
-
}
|
|
148
89
|
function compareCapacitorConfiguration(applicationRoot, currentDirectory, targetDirectory, conflicts) {
|
|
149
90
|
const applicationPath = join(applicationRoot, 'capacitor.config.ts');
|
|
150
91
|
const currentPath = join(currentDirectory, 'capacitor.config.ts');
|
|
@@ -163,46 +104,6 @@ function compareCapacitorConfiguration(applicationRoot, currentDirectory, target
|
|
|
163
104
|
conflicts.push('capacitor.config.ts');
|
|
164
105
|
return undefined;
|
|
165
106
|
}
|
|
166
|
-
function requireCleanGitRepository(applicationRoot) {
|
|
167
|
-
const repository = spawnSync('git', ['-C', applicationRoot, 'rev-parse', '--is-inside-work-tree'], { encoding: 'utf8' });
|
|
168
|
-
if (repository.status !== 0) {
|
|
169
|
-
throw new Error('Inicializa Git y crea un commit antes de ejecutar mova update.');
|
|
170
|
-
}
|
|
171
|
-
const status = spawnSync('git', ['-C', applicationRoot, 'status', '--porcelain', '--untracked-files=all'], { encoding: 'utf8' });
|
|
172
|
-
if (status.status !== 0 || status.stdout.trim() !== '') {
|
|
173
|
-
throw new Error('El repositorio debe estar limpio antes de ejecutar mova update.');
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
function listFiles(directory) {
|
|
177
|
-
const files = [];
|
|
178
|
-
const visit = (currentDirectory) => {
|
|
179
|
-
for (const entry of readdirSync(currentDirectory, { withFileTypes: true })) {
|
|
180
|
-
const absolutePath = join(currentDirectory, entry.name);
|
|
181
|
-
if (entry.isDirectory()) {
|
|
182
|
-
visit(absolutePath);
|
|
183
|
-
}
|
|
184
|
-
else if (entry.isFile()) {
|
|
185
|
-
files.push(relative(directory, absolutePath));
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
|
-
visit(directory);
|
|
190
|
-
return files;
|
|
191
|
-
}
|
|
192
|
-
function readOptionalFile(path) {
|
|
193
|
-
return existsSync(path) ? readFileSync(path) : undefined;
|
|
194
|
-
}
|
|
195
|
-
function buffersEqual(first, second) {
|
|
196
|
-
return first === undefined ? second === undefined : second !== undefined && first.equals(second);
|
|
197
|
-
}
|
|
198
|
-
function readJson(path) {
|
|
199
|
-
return JSON.parse(readFileSync(path, 'utf8'));
|
|
200
|
-
}
|
|
201
|
-
function readStringMap(value) {
|
|
202
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
203
|
-
return {};
|
|
204
|
-
return { ...value };
|
|
205
|
-
}
|
|
206
107
|
function readCapacitorIdentity(content) {
|
|
207
108
|
const appId = content.match(/\bappId:\s*['"]([^'"]+)['"]/)?.[1];
|
|
208
109
|
const appName = content.match(/\bappName:\s*['"]([^'"]+)['"]/)?.[1];
|
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import { Command } from 'commander';
|
|
|
4
4
|
import { registerBuildCommand } from './commands/build.js';
|
|
5
5
|
import { registerCapacitorCommands } from './commands/capacitor.js';
|
|
6
6
|
import { registerCreateCommand } from './commands/create.js';
|
|
7
|
+
import { registerDoctorCommand } from './commands/doctor.js';
|
|
7
8
|
import { registerInfoCommand } from './commands/info.js';
|
|
8
9
|
import { registerMicrofrontendCommands } from './commands/microfrontend.js';
|
|
9
10
|
import { registerStartCommand } from './commands/start.js';
|
|
@@ -16,6 +17,7 @@ program
|
|
|
16
17
|
.description('Herramientas de desarrollo para aplicaciones Open Mova')
|
|
17
18
|
.version(packageVersion.version);
|
|
18
19
|
registerCreateCommand(program);
|
|
20
|
+
registerDoctorCommand(program);
|
|
19
21
|
registerMicrofrontendCommands(program);
|
|
20
22
|
registerStartCommand(program);
|
|
21
23
|
registerShellCommands(program);
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { existsSync,
|
|
2
|
+
import { existsSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { readApplicationConfiguration, requireApplicationRoot, } from '../application/configuration.js';
|
|
5
|
+
import { configureAndroidProject } from '../application/android-configuration.js';
|
|
5
6
|
import { localBinaryName, npmCommand, useCommandShell } from '../utils/platform.js';
|
|
6
7
|
export function registerCapacitorCommands(program) {
|
|
7
8
|
const capacitor = program.command('cap').description('Prepara la shell para Android o iOS');
|
|
8
|
-
capacitor
|
|
9
|
+
capacitor
|
|
10
|
+
.command('add <platform>')
|
|
9
11
|
.description('Crea el proyecto nativo de la plataforma')
|
|
10
12
|
.action((platform) => {
|
|
11
13
|
const selectedPlatform = parsePlatform(platform);
|
|
@@ -14,7 +16,8 @@ export function registerCapacitorCommands(program) {
|
|
|
14
16
|
runCapacitor(root, ['add', selectedPlatform]);
|
|
15
17
|
configureNativePlatform(root, selectedPlatform, readApplicationConfiguration(root));
|
|
16
18
|
});
|
|
17
|
-
capacitor
|
|
19
|
+
capacitor
|
|
20
|
+
.command('sync [platform]')
|
|
18
21
|
.description('Compila la shell y sincroniza sus assets y plugins nativos')
|
|
19
22
|
.action((platform) => {
|
|
20
23
|
const root = requireApplicationRoot(process.cwd());
|
|
@@ -28,7 +31,8 @@ export function registerCapacitorCommands(program) {
|
|
|
28
31
|
configureExistingPlatforms(root, readApplicationConfiguration(root));
|
|
29
32
|
}
|
|
30
33
|
});
|
|
31
|
-
capacitor
|
|
34
|
+
capacitor
|
|
35
|
+
.command('open <platform>')
|
|
32
36
|
.description('Abre el proyecto nativo en Android Studio o Xcode')
|
|
33
37
|
.action((platform) => {
|
|
34
38
|
const root = requireApplicationRoot(process.cwd());
|
|
@@ -79,95 +83,9 @@ function configureExistingPlatforms(root, configuration) {
|
|
|
79
83
|
}
|
|
80
84
|
function configureNativePlatform(root, platform, configuration) {
|
|
81
85
|
if (platform === 'android') {
|
|
82
|
-
|
|
83
|
-
configureBackgroundRunnerForAndroid(root);
|
|
84
|
-
configureGoogleMapsForAndroid(root, configuration);
|
|
86
|
+
configureAndroidProject(root, configuration);
|
|
85
87
|
}
|
|
86
88
|
}
|
|
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
|
-
'&': '&',
|
|
128
|
-
'<': '<',
|
|
129
|
-
'>': '>',
|
|
130
|
-
'"': '"',
|
|
131
|
-
"'": ''',
|
|
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
|
-
}
|
|
152
|
-
function configureBackgroundRunnerForAndroid(root) {
|
|
153
|
-
const packageJson = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
|
|
154
|
-
if (!packageJson.dependencies?.['@capacitor/background-runner'])
|
|
155
|
-
return;
|
|
156
|
-
const gradlePath = join(root, 'android', 'app', 'build.gradle');
|
|
157
|
-
if (!existsSync(gradlePath)) {
|
|
158
|
-
throw new Error('No se encuentra android/app/build.gradle para configurar Background Runner.');
|
|
159
|
-
}
|
|
160
|
-
const repositoryEntry = "dirs '../../node_modules/@capacitor/background-runner/android/src/main/libs', 'libs'";
|
|
161
|
-
const gradle = readFileSync(gradlePath, 'utf8');
|
|
162
|
-
if (gradle.includes(repositoryEntry))
|
|
163
|
-
return;
|
|
164
|
-
const flatDirectory = /flatDir\s*\{/;
|
|
165
|
-
if (!flatDirectory.test(gradle)) {
|
|
166
|
-
throw new Error('No se ha encontrado el bloque flatDir en android/app/build.gradle.');
|
|
167
|
-
}
|
|
168
|
-
const configuredGradle = gradle.replace(flatDirectory, (match) => `${match}\n ${repositoryEntry}`);
|
|
169
|
-
writeFileSync(gradlePath, configuredGradle, 'utf8');
|
|
170
|
-
}
|
|
171
89
|
function runCapacitor(root, args) {
|
|
172
90
|
if (!existsSync(join(root, 'capacitor.config.ts'))) {
|
|
173
91
|
throw new Error('Esta versión de shell no incluye Capacitor. Usa una shell v0.1.2 o posterior.');
|
package/dist/commands/create.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, mkdtempSync, renameSync, rmSync } from 'node:fs';
|
|
2
2
|
import { dirname, join, resolve } from 'node:path';
|
|
3
3
|
import { createMicrofrontend } from '../application/microfrontend.js';
|
|
4
|
-
import { addMicrofrontend, writeApplicationConfiguration
|
|
4
|
+
import { addMicrofrontend, writeApplicationConfiguration } from '../application/configuration.js';
|
|
5
5
|
import { synchronizeShellConfiguration } from '../application/shell-configuration.js';
|
|
6
6
|
import { configureDownloadedShell, DEMO_MICROFRONTEND_REMOTE_ENTRY, downloadShell, } from '../application/shell-repository.js';
|
|
7
7
|
import { normalizeName } from '../utils/names.js';
|
|
@@ -27,7 +27,7 @@ export function registerCreateCommand(program) {
|
|
|
27
27
|
shellVersion = shell.version;
|
|
28
28
|
configureDownloadedShell(temporaryApplication, applicationName, !options.empty);
|
|
29
29
|
let configuration = {
|
|
30
|
-
schemaVersion:
|
|
30
|
+
schemaVersion: 2,
|
|
31
31
|
name: applicationName,
|
|
32
32
|
shell,
|
|
33
33
|
microfrontends: [],
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { inspectDevelopmentEnvironment } from '../application/doctor.js';
|
|
2
|
+
const STATUS_SYMBOL = {
|
|
3
|
+
ok: '✓',
|
|
4
|
+
warning: '!',
|
|
5
|
+
error: '✗',
|
|
6
|
+
};
|
|
7
|
+
export function registerDoctorCommand(program) {
|
|
8
|
+
program
|
|
9
|
+
.command('doctor')
|
|
10
|
+
.description('Comprueba el entorno y la configuración de una aplicación Open Mova')
|
|
11
|
+
.action(() => {
|
|
12
|
+
const report = inspectDevelopmentEnvironment(process.cwd());
|
|
13
|
+
console.log('Diagnóstico de Open Mova');
|
|
14
|
+
for (const check of report.checks) {
|
|
15
|
+
console.log(`${STATUS_SYMBOL[check.status]} ${check.message}`);
|
|
16
|
+
if (check.detail)
|
|
17
|
+
console.log(` ${check.detail}`);
|
|
18
|
+
}
|
|
19
|
+
const errors = report.checks.filter((check) => check.status === 'error').length;
|
|
20
|
+
const warnings = report.checks.filter((check) => check.status === 'warning').length;
|
|
21
|
+
console.log(`Resultado: ${errors} errores, ${warnings} avisos.`);
|
|
22
|
+
if (errors > 0)
|
|
23
|
+
process.exitCode = 1;
|
|
24
|
+
});
|
|
25
|
+
}
|
package/dist/commands/info.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
|
-
import { findApplicationRoot, readApplicationConfiguration
|
|
2
|
+
import { findApplicationRoot, readApplicationConfiguration } from '../application/configuration.js';
|
|
3
3
|
export function registerInfoCommand(program) {
|
|
4
4
|
program
|
|
5
5
|
.command('info')
|
|
@@ -4,6 +4,7 @@ import { addMicrofrontend, readApplicationConfiguration, requireApplicationRoot,
|
|
|
4
4
|
import { synchronizeShellConfiguration } from '../application/shell-configuration.js';
|
|
5
5
|
import { normalizeName } from '../utils/names.js';
|
|
6
6
|
import { listShellVersions } from '../application/shell-repository.js';
|
|
7
|
+
import { applyMicrofrontendUpdate, createMicrofrontendUpdatePlan, } from '../application/microfrontend-update.js';
|
|
7
8
|
export function registerMicrofrontendCommands(program) {
|
|
8
9
|
const microfrontend = program
|
|
9
10
|
.command('mf')
|
|
@@ -21,7 +22,9 @@ export function registerMicrofrontendCommands(program) {
|
|
|
21
22
|
const applicationRoot = requireApplicationRoot(process.cwd());
|
|
22
23
|
const configuration = readApplicationConfiguration(applicationRoot);
|
|
23
24
|
const normalizedName = normalizeName(name, 'El nombre del microfrontal');
|
|
24
|
-
const templateVersion = options.templateVersion ??
|
|
25
|
+
const templateVersion = options.templateVersion ??
|
|
26
|
+
(options.demo ? configuration.shell?.version : undefined) ??
|
|
27
|
+
listShellVersions()[0];
|
|
25
28
|
if (!templateVersion) {
|
|
26
29
|
throw new Error('No hay tags estables disponibles para crear el microfrontal.');
|
|
27
30
|
}
|
|
@@ -53,6 +56,7 @@ export function registerMicrofrontendCommands(program) {
|
|
|
53
56
|
.option('--remote-entry <url>', 'URL del remoteEntry.json')
|
|
54
57
|
.option('--port <number>', 'puerto de desarrollo para un proyecto local', parsePort)
|
|
55
58
|
.option('--production-remote-entry <url>', 'URL HTTPS del remoto publicado')
|
|
59
|
+
.option('--core-version <range>', 'rango requerido de @open-mova/core')
|
|
56
60
|
.action((sourcePath, options) => {
|
|
57
61
|
const applicationRoot = requireApplicationRoot(process.cwd());
|
|
58
62
|
const configuration = readApplicationConfiguration(applicationRoot);
|
|
@@ -64,6 +68,7 @@ export function registerMicrofrontendCommands(program) {
|
|
|
64
68
|
remoteEntry: options.remoteEntry,
|
|
65
69
|
port: options.port,
|
|
66
70
|
productionRemoteEntry: options.productionRemoteEntry,
|
|
71
|
+
coreVersion: options.coreVersion,
|
|
67
72
|
});
|
|
68
73
|
const updatedConfiguration = addMicrofrontend(configuration, microfrontendConfiguration);
|
|
69
74
|
writeApplicationConfiguration(applicationRoot, updatedConfiguration);
|
|
@@ -71,6 +76,40 @@ export function registerMicrofrontendCommands(program) {
|
|
|
71
76
|
console.log(`Microfrontal "${microfrontendConfiguration.name}" registrado.`);
|
|
72
77
|
console.log(`Ruta pública: /${microfrontendConfiguration.route}`);
|
|
73
78
|
});
|
|
79
|
+
microfrontend
|
|
80
|
+
.command('update <name>')
|
|
81
|
+
.description('Actualiza un microfrontal creado desde la plantilla de Open Mova')
|
|
82
|
+
.option('--check', 'mostrar los cambios sin modificar archivos')
|
|
83
|
+
.option('--to <tag>', 'versión de destino, por ejemplo v0.2.1')
|
|
84
|
+
.action((name, options) => {
|
|
85
|
+
const applicationRoot = requireApplicationRoot(process.cwd());
|
|
86
|
+
const configuration = readApplicationConfiguration(applicationRoot);
|
|
87
|
+
const normalizedName = normalizeName(name, 'El nombre del microfrontal');
|
|
88
|
+
const plan = createMicrofrontendUpdatePlan(applicationRoot, configuration, normalizedName, options.to);
|
|
89
|
+
console.log(`Plantilla actual: ${plan.currentVersion}`);
|
|
90
|
+
console.log(`Plantilla destino: ${plan.target.version}`);
|
|
91
|
+
for (const change of plan.changes)
|
|
92
|
+
console.log(`- ${change}`);
|
|
93
|
+
if (plan.conflicts.length > 0) {
|
|
94
|
+
console.log('Conflictos que no se sobrescribirán:');
|
|
95
|
+
for (const conflict of plan.conflicts)
|
|
96
|
+
console.log(`- ${conflict}`);
|
|
97
|
+
}
|
|
98
|
+
if (options.check)
|
|
99
|
+
return;
|
|
100
|
+
if (plan.conflicts.length > 0) {
|
|
101
|
+
throw new Error('Resuelve los conflictos antes de actualizar el microfrontal.');
|
|
102
|
+
}
|
|
103
|
+
if (plan.changes.length === 0) {
|
|
104
|
+
console.log('El microfrontal ya está actualizado.');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
applyMicrofrontendUpdate(applicationRoot, configuration, plan);
|
|
108
|
+
console.log(`Microfrontal "${normalizedName}" actualizado a ${plan.target.version}.`);
|
|
109
|
+
if (plan.removePackageLock) {
|
|
110
|
+
console.log(`Ejecuta npm install en ${plan.projectRoot}.`);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
74
113
|
}
|
|
75
114
|
function parsePort(value) {
|
|
76
115
|
const port = Number(value);
|
package/dist/commands/update.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { applyShellUpdate, createShellUpdatePlan
|
|
1
|
+
import { readApplicationConfigurationDocument, requireApplicationRoot, } from '../application/configuration.js';
|
|
2
|
+
import { applyShellUpdate, createShellUpdatePlan } from '../application/shell-update.js';
|
|
3
3
|
export function registerUpdateCommand(program) {
|
|
4
4
|
program
|
|
5
5
|
.command('update')
|
|
@@ -8,20 +8,29 @@ export function registerUpdateCommand(program) {
|
|
|
8
8
|
.option('--to <tag>', 'versión de destino, por ejemplo v0.2.0')
|
|
9
9
|
.action((options) => {
|
|
10
10
|
const applicationRoot = requireApplicationRoot(process.cwd());
|
|
11
|
-
const
|
|
11
|
+
const document = readApplicationConfigurationDocument(applicationRoot);
|
|
12
|
+
const configuration = document.configuration;
|
|
12
13
|
const plan = createShellUpdatePlan(applicationRoot, configuration, options.to);
|
|
13
14
|
printPlan(plan);
|
|
15
|
+
if (document.migrations.length > 0) {
|
|
16
|
+
console.log('Migraciones de mova.config.json:');
|
|
17
|
+
for (const migration of document.migrations)
|
|
18
|
+
console.log(`- ${migration}`);
|
|
19
|
+
}
|
|
14
20
|
if (options.check)
|
|
15
21
|
return;
|
|
16
22
|
if (plan.conflicts.length > 0) {
|
|
17
23
|
throw new Error('Se han detectado archivos modificados. Resuelve los conflictos antes de actualizar.');
|
|
18
24
|
}
|
|
19
|
-
if (plan.changes.length === 0) {
|
|
25
|
+
if (plan.changes.length === 0 && document.migrations.length === 0) {
|
|
20
26
|
console.log('La aplicación ya está actualizada.');
|
|
21
27
|
return;
|
|
22
28
|
}
|
|
23
29
|
applyShellUpdate(applicationRoot, configuration, plan);
|
|
24
30
|
console.log(`Aplicación actualizada a ${plan.target.version}.`);
|
|
31
|
+
if (document.migrations.length > 0) {
|
|
32
|
+
console.log('mova.config.json se ha migrado al esquema actual.');
|
|
33
|
+
}
|
|
25
34
|
if (plan.removePackageLock) {
|
|
26
35
|
console.log('Ejecuta npm install para instalar dependencias y regenerar package-lock.json.');
|
|
27
36
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export interface OpenMovaApplicationConfiguration {
|
|
2
|
-
readonly schemaVersion:
|
|
2
|
+
readonly schemaVersion: 2;
|
|
3
3
|
readonly name: string;
|
|
4
4
|
readonly shell?: {
|
|
5
5
|
readonly repository: string;
|
|
@@ -24,6 +24,10 @@ export interface MicrofrontendConfiguration {
|
|
|
24
24
|
readonly developmentRemoteEntry: string;
|
|
25
25
|
readonly productionRemoteEntry?: string;
|
|
26
26
|
readonly sourcePath?: string;
|
|
27
|
+
readonly compatibility: {
|
|
28
|
+
/** Rango de @open-mova/core declarado y publicado por el microfrontal. */
|
|
29
|
+
readonly requiredCoreVersion: string;
|
|
30
|
+
};
|
|
27
31
|
readonly template?: {
|
|
28
32
|
readonly repository: string;
|
|
29
33
|
readonly version: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mova/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
4
4
|
"description": "CLI para crear y mantener aplicaciones Open Mova",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Open Mova contributors",
|
|
@@ -39,13 +39,17 @@
|
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsc",
|
|
41
41
|
"start": "tsx src/cli.ts",
|
|
42
|
-
"typecheck": "tsc --noEmit"
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"test": "npm run build && npm run test:unit",
|
|
44
|
+
"test:unit": "node --test test/*.test.mjs"
|
|
43
45
|
},
|
|
44
46
|
"dependencies": {
|
|
45
|
-
"commander": "^14.0.0"
|
|
47
|
+
"commander": "^14.0.0",
|
|
48
|
+
"semver": "^7.8.5"
|
|
46
49
|
},
|
|
47
50
|
"devDependencies": {
|
|
48
51
|
"@types/node": "^24.0.0",
|
|
52
|
+
"@types/semver": "^7.8.0",
|
|
49
53
|
"tsx": "^4.20.0",
|
|
50
54
|
"typescript": "^5.9.3"
|
|
51
55
|
}
|