diff --git a/packages/ionic/src/commands/deploy/core.ts b/packages/ionic/src/commands/deploy/core.ts index 3ed6b10499..17e6a7914e 100644 --- a/packages/ionic/src/commands/deploy/core.ts +++ b/packages/ionic/src/commands/deploy/core.ts @@ -7,25 +7,7 @@ import { input, strong } from '../../lib/color'; import { Command } from '../../lib/command'; import { FatalException } from '../../lib/errors'; -export abstract class DeployConfCommand extends Command { - - protected readonly optionsToPlistKeys = { - 'app-id': 'IonAppId', - 'channel-name': 'IonChannelName', - 'update-method': 'IonUpdateMethod', - 'max-store': 'IonMaxVersions', - 'min-background-duration': 'IonMinBackgroundDuration', - 'update-api': 'IonApi', - }; - protected readonly optionsToStringXmlKeys = { - 'app-id': 'ionic_app_id', - 'channel-name': 'ionic_channel_name', - 'update-method': 'ionic_update_method', - 'max-store': 'ionic_max_versions', - 'min-background-duration': 'ionic_min_background_duration', - 'update-api': 'ionic_update_api', - }; - +export abstract class DeployCoreCommand extends Command { protected async getAppIntegration(): Promise { if (this.project) { if (this.project.getIntegration('capacitor') !== undefined) { @@ -44,12 +26,32 @@ export abstract class DeployConfCommand extends Command { if (!integration) { throw new FatalException( `It looks like your app isn't integrated with Capacitor or Cordova.\n` + - `In order to add the Appflow Deploy plugin, you will need to integrate your app with Capacitor or Cordova. See the docs for setting up native projects:\n\n` + + `In order to use the Appflow Deploy plugin, you will need to integrate your app with Capacitor or Cordova. See the docs for setting up native projects:\n\n` + `iOS: ${strong('https://ionicframework.com/docs/building/ios')}\n` + `Android: ${strong('https://ionicframework.com/docs/building/android')}\n` ); } } +} + +export abstract class DeployConfCommand extends DeployCoreCommand { + + protected readonly optionsToPlistKeys = { + 'app-id': 'IonAppId', + 'channel-name': 'IonChannelName', + 'update-method': 'IonUpdateMethod', + 'max-store': 'IonMaxVersions', + 'min-background-duration': 'IonMinBackgroundDuration', + 'update-api': 'IonApi', + }; + protected readonly optionsToStringXmlKeys = { + 'app-id': 'ionic_app_id', + 'channel-name': 'ionic_channel_name', + 'update-method': 'ionic_update_method', + 'max-store': 'ionic_max_versions', + 'min-background-duration': 'ionic_min_background_duration', + 'update-api': 'ionic_update_api', + }; protected async getAppId(): Promise { if (this.project) { diff --git a/packages/ionic/src/commands/deploy/manifest.ts b/packages/ionic/src/commands/deploy/manifest.ts index 2d87bc5887..ccec174b5d 100644 --- a/packages/ionic/src/commands/deploy/manifest.ts +++ b/packages/ionic/src/commands/deploy/manifest.ts @@ -1,4 +1,5 @@ import { MetadataGroup } from '@ionic/cli-framework'; +import { prettyPath } from '@ionic/cli-framework/utils/format'; import { map } from '@ionic/utils-array'; import { readdirp, stat, writeFile } from '@ionic/utils-fs'; import * as crypto from 'crypto'; @@ -7,16 +8,17 @@ import * as path from 'path'; import { CommandMetadata } from '../../definitions'; import { input } from '../../lib/color'; -import { Command } from '../../lib/command'; import { FatalException } from '../../lib/errors'; +import { DeployCoreCommand } from './core'; + interface DeployManifestItem { href: string; size: number; integrity: string; } -export class DeployManifestCommand extends Command { +export class DeployManifestCommand extends DeployCoreCommand { async getMetadata(): Promise { return { name: 'manifest', @@ -30,11 +32,14 @@ export class DeployManifestCommand extends Command { if (!this.project) { throw new FatalException(`Cannot run ${input('ionic deploy manifest')} outside a project directory.`); } + await this.requireNativeIntegration(); - const buildDir = path.resolve(this.project.directory, 'www'); // TODO: this is hard-coded + const buildDir = await this.project.getDistDir(); const manifest = await this.getFilesAndSizesAndHashesForGlobPattern(buildDir); - await writeFile(path.resolve(buildDir, 'pro-manifest.json'), JSON.stringify(manifest, undefined, 2), { encoding: 'utf8' }); + const manifestPath = path.resolve(buildDir, 'pro-manifest.json'); + await writeFile(manifestPath, JSON.stringify(manifest, undefined, 2), { encoding: 'utf8' }); + this.env.log.ok(`Appflow Deploy manifest written to ${input(prettyPath(manifestPath))}!`); } private async getFilesAndSizesAndHashesForGlobPattern(buildDir: string): Promise { diff --git a/packages/ionic/src/lib/project/index.ts b/packages/ionic/src/lib/project/index.ts index 0f9718d733..fa1aeb369e 100644 --- a/packages/ionic/src/lib/project/index.ts +++ b/packages/ionic/src/lib/project/index.ts @@ -13,6 +13,7 @@ import { isMultiProjectConfig, isProjectConfig } from '../../guards'; import { ancillary, failure, input, strong } from '../color'; import { BaseException, FatalException, IntegrationNotFoundException, RunnerNotFoundException } from '../errors'; import { BaseIntegration } from '../integrations'; +import { CAPACITOR_CONFIG_FILE, CapacitorConfig } from '../integrations/capacitor/config'; const debug = Debug('ionic:lib:project'); @@ -520,7 +521,20 @@ export abstract class Project implements IProject { } async getDistDir(): Promise { - return path.resolve(this.directory, 'www'); + if (this.getIntegration('capacitor') !== undefined) { + const conf = new CapacitorConfig(path.resolve(this.directory, CAPACITOR_CONFIG_FILE)); + const webDir = conf.get('webDir'); + if (webDir) { + return path.resolve(this.directory, webDir); + } else { + throw new FatalException( + `The ${input('webDir')} property must be set in the Capacitor configuration file (${input(CAPACITOR_CONFIG_FILE)}). \n` + + `See the Capacitor docs for more information: ${strong('https://capacitor.ionicframework.com/docs/basics/configuring-your-app')}` + ); + } + } else { + return path.resolve(this.directory, 'www'); + } } async getInfo(): Promise {