tests/cli-utils.ts
Rama main · réplica de lectura del repositorio original.
import { access, mkdir, rm, symlink, writeFile } from 'node:fs/promises'
import { join } from 'node:path'
import { cwd, env } from 'node:process'
import { BASE_URL, getEndpointUrl } from './test-utils'
// Helper to check if file/directory exists using promises
async function exists(path: string): Promise<boolean> {
try {
await access(path)
return true
}
catch {
return false
}
}
export const TEMP_DIR_NAME = 'temp_cli_test'
const ROOT_DIR = cwd()
export const BASE_PACKAGE_JSON = `{
"name": "%APPID%",
"version": "1.0.0",
"description": "An Amazing Test App",
"dependencies": %DEPENDENCIES%,
"devDependencies": {
"@capacitor/cli": "^7.0.0",
"typescript": "^5.2.2"
},
"author": ""
}`
export const BASE_DEPENDENCIES = {
'@capacitor/android': '^7.0.0',
'@capacitor/core': '7.0.0',
'@capgo/capacitor-updater': '7.30.0',
}
export const BASE_DEPENDENCIES_OLD = {
'@capacitor/android': '^6.0.0',
'@capacitor/core': '6.0.0',
'@capgo/capacitor-updater': '6.14.17',
}
// Cache for prepared apps to avoid repeated setup
const preparedApps = new Set<string>()
export const tempFileFolder = (appId: string) => join(ROOT_DIR, TEMP_DIR_NAME, appId)
function generateDefaultJsonCliConfig(appId: string) {
return {
appId,
appName: 'demoApp',
webDir: 'dist',
plugins: {
CapacitorUpdater: {
autoUpdate: true,
statsUrl: getEndpointUrl('/stats'),
channelUrl: getEndpointUrl('/channel_self'),
updateUrl: getEndpointUrl('/updates'),
localS3: true,
localHost: 'http://localhost:5173',
localWebHost: 'http://localhost:5173',
localApi: `${env.SUPABASE_URL}/functions/v1`,
localApiFiles: BASE_URL,
},
},
}
}
function generateCliConfig(appId: string): string {
return `import { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = ${JSON.stringify(generateDefaultJsonCliConfig(appId), null, 2)};
export default config;\n`
}
export async function setDependencies(dependencies: Record<string, string>, appId: string) {
// write package.json
const pathPack = join(tempFileFolder(appId), 'package.json')
const res = BASE_PACKAGE_JSON.replace('%APPID%', appId).replace('%DEPENDENCIES%', JSON.stringify(dependencies, null, 2))
await writeFile(pathPack, res)
}
export async function deleteTempFolders(appId: string) {
const tempFolder = tempFileFolder(appId)
if (await exists(tempFolder)) {
// console.log('Deleting temp folder', tempFolder)
await rm(tempFolder, { recursive: true, force: true })
}
preparedApps.delete(appId)
}
export function getSemver(semver = `1.0.${Date.now()}`) {
const lastNumber = Number.parseInt(semver.charAt(semver.length - 1))
const newSemver = `${semver.slice(0, -1)}${(lastNumber + 1).toString()}`
return newSemver
}
export async function prepareCli(appId: string, old = false, installDeps = false) {
// Skip if already prepared
if (preparedApps.has(appId)) {
return
}
const tempFolder = tempFileFolder(appId)
const defaultConfig = generateCliConfig(appId)
await deleteTempFolders(appId)
// Create all directories and files in parallel for better performance
const distPath = join(tempFolder, 'dist')
const nodeModulesPath = join(tempFolder, 'node_modules')
const capacitorConfigPath = join(tempFolder, 'capacitor.config.ts')
// Create directories
await mkdir(distPath, { recursive: true })
// Write all files in parallel
await Promise.all([
writeFile(capacitorConfigPath, defaultConfig),
writeFile(join(distPath, 'index.js'), 'import { CapacitorUpdater } from \'@capgo/capacitor-updater\';\nconsole.log("Hello world!!!");\nCapacitorUpdater.notifyAppReady();'),
writeFile(join(distPath, 'index.html'), ''),
])
// Create package.json
await setDependencies(old ? BASE_DEPENDENCIES_OLD : BASE_DEPENDENCIES, appId)
if (installDeps) {
// Only install dependencies for tests that specifically need them (like metadata tests)
await npmInstallMinimal(appId)
}
else {
const rootNodeModulesPath = join(ROOT_DIR, 'node_modules')
try {
await access(rootNodeModulesPath)
// Replace with symlink to root node_modules so metadata checks can resolve deps.
await rm(nodeModulesPath, { recursive: true, force: true })
await symlink(rootNodeModulesPath, nodeModulesPath, 'dir')
}
catch {
// Fallback to empty node_modules folder to satisfy CLI checks without installing
await mkdir(nodeModulesPath, { recursive: true })
// Create a minimal package.json in node_modules to indicate it's "installed"
await writeFile(join(nodeModulesPath, '.package-lock.json'), '{"name": "temp", "lockfileVersion": 1}')
}
}
preparedApps.add(appId)
}
// Minimal install that only gets essential dependencies
async function npmInstallMinimal(appId: string) {
try {
// Use exec with callback-based approach (no promisify as per user requirement)
const { exec } = await import('node:child_process')
// First try bun install
await new Promise<void>((resolve, reject) => {
exec('bun install', {
cwd: tempFileFolder(appId),
timeout: 60000,
}, (error) => {
if (error)
reject(error)
else
resolve()
})
})
}
catch (error) {
console.error(`bun install failed for ${appId}, trying npm:`, error)
try {
const { exec } = await import('node:child_process')
// Fallback to npm
await new Promise<void>((resolve, reject) => {
exec('npm install --silent --no-audit --no-fund', {
cwd: tempFileFolder(appId),
timeout: 60000,
}, (error) => {
if (error)
reject(error)
else
resolve()
})
})
}
catch (npmError) {
console.error(`Both bun and npm install failed for ${appId}:`, npmError)
// Create fake node_modules as fallback
const nodeModulesPath = join(tempFileFolder(appId), 'node_modules')
await mkdir(nodeModulesPath, { recursive: true })
await writeFile(join(nodeModulesPath, '.package-lock.json'), '{"name": "temp", "lockfileVersion": 1}')
throw npmError
}
}
}
// cleanup CLI
export async function cleanupCli(appId: string) {
await deleteTempFolders(appId)
}